Pico-8 筆記
(Deprecated)
v0.16 - by 晴☆
Slides.com
就是你現在正在看的簡報網站,按 吧


使用方法
右下角的箭頭可以換頁,但簡報頁面的排列方式不是網格狀的,而是像很多串投影片,而它會記住使用者在每一串投影片的垂直位置(預設是最上方),所以先往下滑到底看完一個章節,再往右滑至另一個章節吧!
Title A
Title B
Title C
Title D
A1
B1
C1
D1
A2
C2
為什麼要用
- 線上方便共用
- 編輯介面簡單
- 支援數學嵌入
- 支援程式嵌入
- 很容易分章節
- 看起來就很酷

小提示:
-
也可以用鍵盤的方向鍵換頁
-
按 [Esc] 可以看縮圖
-
回到標題頁可以重置簡報順序
Pico-8
介紹一下

什麼是 Pico-8?
根據 Lexaloffle 官方說明:
Pico-8 是一款用於製作、分享和遊玩小遊戲和其他電腦程式的奇幻遊戲機。它有自己的主機控制台,但它是在個人電腦上運行
當你打開它時,會看見命令列、一套遊戲製作工具和一個名為 Splore 的線上卡夾瀏覽器

根據我的解釋:
Pico-8 可以用來寫小遊戲
硬體受限,所以考驗創意!
硬體限制
Pico-8 有刻意限制過硬體規格
希望真正有趣、好玩的遊戲可以不被「不夠華麗」所拘束
| 介面 | 128x128 像素,挑選過的 16 色 |
| 卡夾尺寸 | 32 KB |
| 聲軌 | 8-bit、4 通道 |
| 程式語言 | Pico-8 加強版的 Lua |
| CPU | 4M vm insts/sec |
| Sprite | 256 個 8x8 像素的拼合圖 |
| 地圖 | 128x32 瓦片 |
小遊戲 & 操作
話說,小遊戲的定義是什麼?
就是一些小巧的專案!

遊戲的檔案內容會以 .p8 儲存(內容是純文字)
一個小遊戲稱為 卡帶 Cartridge(簡稱 Cart)
卡帶聽起來很古老吧
就像早期遊戲主機,Pico-8 只有 6 個輸入:

[↑] [←][↓][→]


[c]&[z]
[x]&[v]
推薦的卡帶
- Mea's Castle 在地牢中蒐集各種能力,以達到更後面的關卡 翻頁式關卡
- Pakpok 可以滑翔的瓢蟲,被橘色瓢蟲撞到會飛起來 垂直捲動式的關卡
- To a Starling 可以傳送到定點的一隻鳥,但得小心 翻頁式關卡
- Slimey, Jump! 史萊姆出門買蛋糕,盡量蒐集路上的錢幣 單畫面關卡
- Celeste 經典作品。跑酷,玩家可以在空中「衝」一次 單畫面關卡,上方為終點
- Pico Tetris 就是俄羅斯方塊
- Dank Tomb 擁有超強光影的解謎遊戲 翻頁式關卡
- Shift Trap 跳一次就會改變方塊型態 單畫面關卡
- Terra 泰拉瑞亞重製。可以蓋房子、打 Boss 開放世界
使用編輯器
使用 Pico-8 製作遊戲吧

下載 Pico-8
Pico-8 是付費產品
但購買後可以無限更新
推薦買一下!
但是有點貴……

奇異的文字
在提到編輯介面前,必須先看得懂 Pico-8 的文字
畢竟他只有 128 像素,所以每個字必須最精簡
特別注意 M、W、Q 等字的顯示方法
預設打出來的字都是大寫,比較不會糊成一團


一個字元是 3 x 5 像素,字元之間空一像素
按著 Shift 會打出特殊符號(兩字元寬)
編輯介面
進到 Pico-8 應該會看到這個畫面 →
這就是 Pico-8 的 命令列 Console

直接講指令!
| save <name> | 將目前文件存檔 |
| load <name> | 讀取檔案 |
| run | 開始跑程式碼 |
| folder | 打開所有存檔所在的資料夾 |
| ls | 列出所有檔案 |
| reboot | 重啟 Pico-8 |
程式碼
我們來用 Lua 寫一個大家一定寫過的……
印出 "Hello World!" 吧
print("hello world!")首先按 Esc 來到編輯介面,然後輸入:
然後再按一次 Esc 回到 Console,輸入 run 來執行!
(或者直接按 Ctrl + R)

Lua 語言
Lua 是一種程式語言:
一些基本的語法例子:
if hungry then
eat()
endwhile hungry do
eat()
end如果餓就吃
餓的話就一直吃
repeat
eat()
until full一直吃到飽為止
-
不用加分號
-
大部分東西用 end 結尾
-
用 -- 註解
編輯器
但是 VS Code 和 Pico-8 兩邊任何一邊有更動就一定要存檔
VS Code 存完檔後,Pico-8 要按 Ctrl + R 開始執行才會更新
Pico-8 存檔後,VS Code 會自動更新
注意:
- 如果 Pico-8 還沒存檔就到 VS Code 編輯,VS Code 做的編輯就無效
插件
如果要用 VS Code,可以裝這個差件:
滑鼠停在函數上時會有提示:


內建函數
一個遊戲不會只有文字
我們要印一些圖形出來
試試這段程式碼:
rect(8, 8, 119, 119, 8)
結果應該會像這樣 →
抱歉,這真的非常醜
解釋一下

所以剛才的程式碼代表「畫一個從 8, 8 到 119, 119 的 8 號顏色方形」
那什麼是 8, 8 到 119, 119?什麼又是 8 號顏色?
看到

頁面,可以找到:
(有 [ ] 括號的是選擇性參數,不一定要有)
座標
Pico-8 的介面是 128 x 128
於是他的座標也是 128 x 128
左上角為 0, 0
往右方是 x+
往下方是 y+

問題來了,右下角是多少?
是 127, 127
因為從 0 開始!
看到編輯介面右上角

| 程式 | 圖形 | 地圖 | 音效 | 音樂 |
程式:寫程式的地方
圖形:可以編輯很多個 8 x 8 的 拼合圖 Sprite
地圖:由很多的 Sprite 排成網格狀的地圖
音效:根據音高、音色、音量與效果等等編輯單一音軌
音樂:由 4 個音軌組成可以連續播放的音樂
編輯介面 +
顏色 & 圖形
看到圖形編輯的介面 →

在滑鼠移動到 綠色上時,左下角有 "Colour 11"
代表綠色就是 11 號顏色!

顏色是按照順序排
不小心就記起來了
在 API 中 col 就是指顏色
還附上了這張圖 →
拼合圖 Sprite
先解釋一下什麼是 Sprite

看到右邊這是 Slimey, Jump! 的編輯介面截圖
底下就是很多 Sprite 組成的表(Spritesheet)
Sprite 可以想像成很多的格子
每格裡面都是一張圖,把它們隨便排列在一起
可以取其中的一塊當作圖片輸出
遊戲中的角色都是一格一格的動畫
地圖也是很多 Sprite 排列在一起的
編輯 Sprite

先看到底下的區塊
這裡是 Sprite 區,現在是空的
白色框框是現在正在編輯的 Sprite
不要去動這個叉叉,之後會解釋

這是正在編輯的 Sprite 的編號
之後會用到!
編輯 Sprite


| 畫筆 | 印章 | 選取 | 移動 | 填滿 | 圓形 |
畫筆:畫圖
印章:會印出剪貼簿上的東西
選取:矩形選取
移動:可以自由移動
(也可以按著 Space 並拖曳鼠標)
填滿:填滿相鄰的顏色
圓形:畫出圓形
編輯 Sprite


上方的滑桿可以調整畫筆粗細
下方的滑桿則是視野大小
(也可以用滑鼠滾輪調整)
一般來講,不太會調整畫筆粗細
而調整視野大小則很方便
比方說,可以很方便的畫 2 x 2 的圖片
編輯 Sprite

這邊可以切換不同頁
其實它儲存的方式是連續往下的
第 1 頁在第 0 頁的下方……等
視野放到最大的時候可以跨頁!
注意:第 2 和第 3 頁是與地圖共用的
建議不要使用!
完整的地圖比更多的 Sprite 更重要

藝術
接下來就是考驗創意能力了!

註:記得存檔

這次我畫的角色:
對齊左邊的版本:

印出角色
我們需要用一個函數來把剛才畫的 Sprite 印出來:

其中 n 是編號:

w & h 是 Spritesheet 上的寬和高
一個 Sprite 是 8 x 8,所以 w = 2, h = 2 時
就是 2 x 2 格 Sprite,也就是 16 x 16 像素
flip_x 和 flip_y 是翻轉(水平 / 垂直 鏡像)
試試看
在程式碼區打上:
spr(1, 8, 8)
應該會得到這個畫面 →
仔細看的話會發現它在這裡:

清空螢幕
我們需要把螢幕淨空再印東西出來
這時需要這個函數:

於是我們把程式碼改成這樣:
cls()
spr(1, 8, 8)
應該會得到這個畫面 →
看起來整潔多了!
主角
動畫格、角色的物件

更新動畫
剛才的程式碼只執行一次就停止了
顯然不是一個遊戲應有的樣子
這時我們需要三個函數:
function _init()
-- initialize 初始化
endfunction _update()
-- 30 frame / 1 秒的更新
endfunction _draw()
-- 繪製
end注意前面的底線喔!
Frame
Frame 翻譯成「幀」或「影格」
意思就是動畫的每一格

frame rate 越高
動畫看起來越順
螢幕並不是連續的
而是每 Frame 更新後
產生下一格並繪製到螢幕上
試試看
在程式區打上這個:
function _init()
end
function _update()
end
function _draw()
cls()
spr(1, 59, 59)
end
現在應該會一直執行,按 Esc 可以中斷
玩家的物件
我們在 _init 加入一個玩家物件,並在 _draw 讀取它的 x & y:
function _init()
-- the player object
pl = {
x = 59,
y = 59
}
end
function _update()
end
function _draw()
cls()
spr(1, pl.x, pl.y)
endpl = {
x = 59,
y = 59
}
-- 也可以寫成:
pl = {}
pl.x = 59
pl.y = 59
在 Lua 中,{ } 括號像是一個物件
它可以有自己的屬性,並且可增加屬性
試試看移動角色
如果剛才加入了 pl 沒有改變
沒什麼問題!
我們現在才要讓它動起來:
function _init()
pl = {
x = 59,
y = 59
}
end
function _update()
pl.x += 1 -- move right!
end
function _draw()
cls()
spr(1, pl.x, pl.y)
end
操作
這是輸入代碼圖:

可以用 btn 函數取得
例如想看看玩家是否正按著「上」鍵:
if btn(2) then
end其實讓玩家操作角色沒有那麼單純,有的時候是遊戲可以玩,但操作很不順暢
平時也多觀察別人做的遊戲的細節吧
可移動的角色
把 _update 改成這樣:
function _update()
if btn(0) then
pl.x -= 1
end
if btn(1) then
pl.x += 1
end
if btn(2) then
pl.y -= 1
end
if btn(3) then
pl.y += 1
end
end
地圖
加入牆壁吧!

製作地圖

我在 64 號 Sprite 畫了牆壁:

(第 1 頁第 1 個)
進到地圖編輯畫面,畫一個畫面的框框
一個畫面是 128 x 128 像素
一個 Sprite 是 8 x 8
所以一個畫面有 16 格地圖,也就是 1~15 格
可以從左下角的座標看到
註:按 Space 並拖曳可以很方便的移動視角
顯示地圖
試試看這個函數:

其中 cel_ 開頭的東西是「地圖上的座標系統」,是螢幕座標的 1/8
而 sx, xy 是顯示在螢幕上的位置
那麼先不要管 layer,試著把地圖畫出來:
function _draw()
cls()
map(0, 0, 0, 0, 16, 16)
spr(1, pl.x, pl.y)
end穿牆術
因為我們在玩家碰到牆壁的時候做任何事
所以牆壁就只有圖像,沒有實質功能

那麼要怎麼樣有辦法讓玩家碰到牆會停住?
- 檢測玩家碰到的地圖
- 看看那是不是牆
- 如果那是牆,就取消移動!
聽起來不難……
實作看看吧!
碰撞箱
一個角色需要自己的碰撞箱
用來檢查有沒有撞到牆等等
我的角色大小是 5 x 7 像素
所以先以一個 5 x 7 的碰撞箱代表它
試試這個:
function _draw()
cls()
map(0, 0, 0, 0, 16, 16)
spr(1, pl.x, pl.y)
rect(pl.x, pl.y, pl.x + 4, pl.y + 6, 12)
end位移
剛才的框框應該會像這樣:
往上移了一格……

因為這格 sprite 並不是在最左上角!
我留了一格之後做動畫比較方便

這時有兩種選擇:
1. 移動碰撞箱
2. 移動角色顯示的位置
但因為碰撞箱是用來判定的
所以它應該要是準確的
那……應該要移動角色顯示的位置
修正
改一下 spr 的位置:
function _draw()
cls()
map(0, 0, 0, 0, 16, 16)
spr(1, pl.x, pl.y - 1)
rect(pl.x, pl.y, pl.x + 4, pl.y + 6, 12)
end然後保留圖像會有點花,所以我先把它註解掉了
function _draw()
cls()
map(0, 0, 0, 0, 16, 16)
-- spr(1, pl.x, pl.y - 1)
rect(pl.x, pl.y, pl.x + 4, pl.y + 6, 12)
end判斷牆壁
先想一下如何判斷有沒有撞到牆壁?
看四個角落就可以了!
註:這種做法是在「玩家碰撞箱大小 < 地圖每格大小」的前提下
否則有可能地圖會從兩個角中間卡進去
判斷地圖
使用這個函數:

其中的 x, y 是地圖的 x, y
從螢幕座標轉換記得 ÷8

function _draw()
cls()
map(0, 0, 0, 0, 16, 16)
-- spr(1, pl.x, pl.y)
rect(pl.x, pl.y, pl.x + 4, pl.y + 6, 12)
if mget(pl.x / 8, pl.y / 8) == 64 then
pset(pl.x, pl.y, 8)
-- set screen pixel color
end
end看看效果
左上角碰到牆壁就會變成紅色!

註:
在 _draw 函數中運算其實是很不好的做法
這裡只是做個測試,暫時寫在 _draw 以求方便
檢測
function rect_check(x, y, w, h, the_map)
return mget((x ) / 8, (y ) / 8) == the_map
or mget((x + w) / 8, (y ) / 8) == the_map
or mget((x ) / 8, (y + h) / 8) == the_map
or mget((x + w) / 8, (y + h) / 8) == the_map
end改成只要四個角其中一個有碰到
就把整個框框變紅色
寫成函數:
完整的程式碼有點塞不下了
之後應該都會放在下一頁,可以捲動看

-- 碰撞箱函數
function rect_check(x, y, w, h, the_map)
return mget((x ) / 8, (y ) / 8) == the_map
or mget((x + w) / 8, (y ) / 8) == the_map
or mget((x ) / 8, (y + h) / 8) == the_map
or mget((x + w) / 8, (y + h) / 8) == the_map
end
function _init()
pl = {
x = 59,
y = 59
}
-- 預設是藍色
box_color = 12
end
function _update()
-- 玩家移動
if btn(0) then
pl.x -= 1
end
if btn(1) then
pl.x += 1
end
if btn(2) then
pl.y -= 1
end
if btn(3) then
pl.y += 1
end
-- 如果碰到,換成紅色,否則是藍色
if rect_check(pl.x, pl.y, 4, 6, 64) then
box_color = 8
else
box_color = 12
end
end
function _draw()
cls()
map(0, 0, 0, 0, 16, 16)
-- spr(1, pl.x, pl.y - 1)
rect(pl.x, pl.y, pl.x + 4, pl.y + 6, box_color)
end阻擋
如果玩家這 frame 移動後,碰到了牆壁
就把玩家推回去!
注意這邊新增了 xv、yv 變數(Velocity)
根據玩家輸入改變 xv、yv 的值
如果碰到牆就把速度減回去,就取消移動了
pl.x += pl.xv
pl.y += pl.yv
if rect_check(...) then
pl.x -= pl.xv
pl.y -= pl.yv
end
xv & yv
輸入速度的方法:
-- 玩家移動 (x)
if btn(0) then
pl.xv = -1
elseif btn(1) then
pl.xv = 1
else
pl.xv = 0
end
-- 玩家移動 (y)
if btn(2) then
pl.yv = -1
elseif btn(3) then
pl.yv = 1
else
pl.yv = 0
end
抵銷
剛才的方法如果左右同時按,會向左移動
可能會覺得「不要同時按就好啦」,但其實影響很多!
如何抵銷相反方向的速度?用減的!
如果按鈕的布林值轉換成數字
那麼 x 速度就是「右 - 左」,同理 y 速度就是「下 - 上」
-- 輸入速度
pl.xv = btn(1) - btn(0)
pl.yv = btn(3) - btn(2)轉換布林值
出錯了,無法轉換布林值為數字!

這時我們需要一個轉換表
eval = {
[true] = 1,
[false] = 0
}eval[true]
-- returns 1
eval[false]
-- returns 0-- 右 - 左
pl.xv = eval[btn(1)] - eval[btn(0)]
-- 下 - 上
pl.yv = eval[btn(3)] - eval[btn(2)]
斜著走的問題
如果你按著右上
就會發現到了上方的牆邊就卡住了
但應該要繼續往右才對!
這是因為:

改良
因為我們有水平和垂直的牆兩種
水平的牆不影響垂直移動
垂直的牆不影響水平移動
所以應該分開!
-- 更新玩家位置 (x)
pl.x += pl.xv
if rect_check(pl.x, pl.y, 4, 6, 64) then
pl.x -= pl.xv
end
-- 更新玩家位置 (y)
pl.y += pl.yv
if rect_check(pl.x, pl.y, 4, 6, 64) then
pl.y -= pl.yv
end
加速看看
假設我們要加速:
寫不完
還沒結束!
我寫得還可以……吧
Pico-8 筆記
By 晴☆
Pico-8 筆記
- 124