こんにちは。NYLONです。ゲーム作るの楽しいのでみんなもPICO8始めよう。
1.PICO8とは
PICO8とはゲームエンジンです。ゲームエンジンとはゲームを作るにあたり必要な処理(ウィンドウを出す、画像を出す、音を鳴らすなど)を簡単に出来るようにまとめてくれたものです。画像を出すのにいちいち長いコードを書かなくていいので便利です。
PICO8は主にLuaという言語で書きます。Luaの難しいことを知らなくても書けるのでちょっと読めれば問題ないです。早速今回のゲーム(https://nylon1919.github.io/vsmisw/)について解説していきます。
2.1タブ目
コード全体はこんな感じ
function _init()
game=0
select=0
col={}
scores={}
t=0
for i=1,10 do
scores[i]=0
end
end
function _update()
if game==0 then
update_menu()
elseif game==1 then
update_game()
else
if btnp(4) or btnp(5) then
game=0
sfx(62)
end
end
end
function _draw()
cls()
if game==0 then
draw_menu()
elseif game==1 then
cls(2)
draw_game()
elseif game==2 then
draw_howto()
elseif game==3 then
drawscore(64,17,5)
drawscore(64,16,7)
end
end
--menu
function update_menu()
if btnp(2) then
select-=1
sfx(63)
end
if btnp(3) then
select+=1
sfx(63)
end
if select==3 then
select=0
end
if select==-1 then
select=2
end
for i=0,2 do
col[i]=7
end
col[select]=8
if btnp(5) then
sfx(62)
if select==0 then
game_init()
end
game=select+1
end
t+=1
end
function draw_menu()
for i=0,15 do
for j=0,15 do
spr(9,i*8,j*8)
end
end
spr(224,8,32,14,2)
spr(192,8+cos(t/4),32+sin(t/4),14,2)
printcs("game start",64,0,col[0])
printcs("how to play",72,0,col[1])
printcs("high score",80,0,col[2])
printcs("press ❎",96,1,7)
printcs("thanks for mis.w",108,1)
printcs("presented by wce",116,1)
end
function printcs(a,y,c,s)
if s==nil then
s=0
end
local x=64-2*#a
for i=0,3 do
print(a,x+cos(i/4),y+sin(i/4),s)
end
print(a,x,y,c)
end
function newscore(score)
if scores[10]scores[10-i] then
scores[11-i],scores[10-i]=scores[10-i],scores[11-i]
end
end
end
function drawscore(x,y,c)
for i=1,10 do
local l=s_length(scores[i])
print(scores[i],x-l*2,y+i*8,c)
end
end
function draw_howto()
local x=4 y=4
palt(0,false)
palt(15,true)
for i=0,4 do
rectfill(x-1,y-1+i*24,x+8,y+8+i*24,6)
spr(25+i,x,y+24*i)
end
palt(15,false)
palt(0,true)
for i=0,2 do
spr(48,x+12,y+i*24,3,1)
spr(55,x+36,y+i*24)
spr(37,x+12,y+10+i*24,3,1)
spr(55,x+36,y+10+i*24,5,1)
end
spr(37,x+44,y,7,1)
spr(37,x+44,y+24,3,1)
spr(47,x+68,y+24)
spr(37,x+44,y+48,3,1)
spr(44,x+68,y+48,3,1)
spr(68,x+12,y+72,7,1)
spr(68,x+12,y+82,7,1)
spr(50,x+12,y+96,3,1)
spr(100,x+12,y+106,12,1)
end
左上が0で右に進んでいきます。9番のスプライトは1番上の左から10個目のMIS.W様のロゴマーク(のつもりで描いたやつ)です。二重forループによって縦横満遍なく敷き詰められているのがわかります。いいですね。
その下は”みすさいこう”の黄色、その下が”みすさいこう”の水色の表示です。水色は三角関数を使いグルグル回してます。回し放題なので回す周期を変えたりしてゲラゲラ笑って見てました。ちなみにPICO8の仕様かLuaの仕様かは知りませんがこの三角関数は特殊で1周期は1(≠2π)であり、描画上の見やすさからsinの値が正負反転しています。(ゲーム画面上y軸は下向きを正と取る為)なので難しいことを考えなくても三角関数は反時計回りです。
その下に並んでいるprintcsは文字列を真ん中(center)に影付き(shadow)で表示できる関数です。前になんかのPICO8記事で見たのを参考にしてます。引数はprintcs(string,y,c,s)で文字列、書きたいy座標、文字色、影色(nilなら黒(0))です。#stringで文字列の長さを取得できるので真ん中(=64)から文字の横幅が4であることに注意してずらしています。PICO8内の通常のprint関数は文字列と位置(x,y)と色で描けるので先に影でぐるっと文字を書いてから、その上に文字を書いてます。
その下はスコア更新用の関数で新しいスコアを引数にscores[10]から比べさせて大きければ上に進む方式を取ってます。このときに前はswap関数を作っていたのですがLuaだとx,y=y,xでxとyの入れ替えができるみたいです。便利ですね。
スコアの表示画面はめんどくてシンプルに書いてますが何桁の数だろうと中央に配置したかったのでs_length関数を作りました。これは数値の桁数を取得する関数ですがPICO8で扱える数値の最大値は確か32768(=215)しかないので5桁まで地道に数えさせました。かっこいい方法を知っている人は教えてください。
遊び方のところはめんどいので省略。paltという関数はPICO8独自のもので非表示色の切り替えができます。通常は0(黒)がtrueにされていて黒は切り抜かれていますがここで自由に肌色を切り抜いたり黄緑を切り抜いたりしてます。フォントはニンテンドーDSのフォントを参考に作りました。7×7に収まるので偉いです。
4.3タブ目
このタブはゲーム画面についてです。
--game
function game_init()
t=0
score=0
pl_init()
enemy={}
gim={}
hole={}
music(0)
end
function update_game()
if pl.life>0 then
update_pl()
foreach(enemy,update_enemy)
foreach(gim,update_gim)
foreach(hole,update_hole)
t+=1
if t%10==1 then
score+=1
end
if t%30==0 then
add_enemy()
end
if t%10==0 then
if rnd(3)<1 then
if rnd(2)<1 then
add_gim()
else
add_hole()
end
end
end
else
if btnp(4) then
game=0
elseif btnp(5) then
game_init()
end
end
end
function draw_game()
rectfill(0,112,127,127,14)
palt(0,false)
palt(11,true)
spr(64,0-t%8/4,48,4,8)
palt(11,false)
palt(15,true)
foreach(hole,draw_hole)
foreach(enemy,draw_88)
foreach(gim,draw_88)
palt(15,false)
palt(0,true)
draw_pl()
rectfill(0,0,127,12,0)
print("highscore",2,1,7)
print(scores[1],20-2*s_length(scores[1]),7,7)
if pl.life==0 then
rectfill(0,48,127,80,0)
spr(48,2,60,3,1)
spr(53,26,60,2,1)
draw_score(score)
spr(48,80,60)
spr(32,88,60,5,1)
printcs("restart ❎/back to menu 🅾️",72,1)
else
print("score",64-10,1,7)
print(score,64-s_length(score)*2,7,7)
end
for i=1,pl.life do
spr(10,91+i*9,4)
end
end
function s_length(s)
local l=0
if s<10 then
l=1
elseif s<100 then
l=2
elseif s<1000 then
l=3
elseif s<10000 then
l=4
else
l=5
end
return l
end
function draw_score(s)
for i=1,s_length(s) do
spr(84+s%10,78-6*i,60)
s=flr(s/10)
end
end
function dist(a,b)
return sqrt((a.x-b.x)^2+(a.y-b.y)^2)
end
function s_length(s)
if s<10 then
return 1
else
return s_length(s/10)+1
end
end
--player
function pl_init()
pl={}
pl.x=64
pl.y=104
pl.vx=0
pl.vy=0
pl.jump=false プレイヤーのジャンプ判定
pl.jcf=0 プレイヤーのジャンプチェックフレーム
pl.mt=0 プレイヤーのアニメーション状態
pl.s=0 プレイヤーのスプライト
pl.life=3 プレイヤーの残機
pl.df=0 プレイヤーのダメージ判定
pl.drop=false プレイヤーの穴への落下判定
end
function update_pl()
if pl.drop then
pl_drop()
else
pl_movex()
if not pl.jump then
pl_jump()
else
pl_movey()
end
pl_mot()
end
pl_flag()
end
function pl_jump() プレイヤーのジャンプ処理
if btn(4) or btn(2) then
pl.jcf+=1
elseif pl.jcf>0 then
pl.jump=true
pl.vy=-6
pl.jcf=0
sfx(63)
end
if pl.jcf==3 then
pl.jump=true
pl.vy=-8
pl.jcf=0
sfx(63)
end
end
function pl_movex() プレイヤーの左右移動
if btn(0) then
if pl.vx>-4 then
if pl.jump then
pl.vx-=0.5
else
pl.vx-=1
end
end
end
if btn(1) then
if pl.vx<4 then
if pl.jump then
pl.vx+=0.5
else
pl.vx+=1
end
end
end
if not btn(0) and not btn(1) then
if pl.vx>2 then
pl.vx-=1
elseif pl.vx<-2 then
pl.vx+=1
else
pl.vx=0
end
end
if abs(pl.x+pl.vx-64)<64 then
pl.x+=pl.vx
end
if pl.x<16 then
if pl.vx==0 then
pl.x=16
else
pl.x-=pl.vx
end
if pl.df==0 then
pl_damage()
end
end
end
function pl_movey() プレイヤーの上下移動
pl.y+=pl.vy
pl.vy+=1
if pl.y>104 then
pl.y=104
pl.jump=false
pl.vy=0
end
end
function pl_mot() プレイヤーのアニメーション管理
if pl.jump then
pl.s=0
else
if pl.vx>0 then
pl.mt=2
elseif pl.vx==0 then
pl.mt=4
else
pl.mt=6
end
pl.s=flr((t%(4*pl.mt))/pl.mt)
end
end
function pl_damage() プレイヤーのダメージ処理
pl.df+=1
pl.life-=1
sfx(62)
if pl.life==0 then
newscore(score)
pl.df=0
music(2)
end
end
function pl_flag() プレイヤーのフラグ管理(?)
if pl.df>0 then
pl.df+=1
end
if pl.df==30 then
pl.df=0
end
end
function pl_drop() プレイヤーの落下処理
if pl.vy<0 then
pl.drop=false
else
pl.x-=3
pl.s=0
pl.y+=pl.vy
pl.vy+=1
if pl.vy<3 then
pl_jump()
if btn(4) or btn(2) then
pl.drop=false
end
end
end
if pl.y>135 then
pl.life=0
newscore(score)
music(2)
end
end
function draw_pl() プレイヤーの描画
if pl.df%2==0 then
spr(2*pl.s,pl.x-8,pl.y-4,2,2)
spr(8,pl.x-pl.s%2,pl.y-8-pl.s%2)
end
end
--enemy
function add_enemy() 敵の追加
local e={}
e.x=rnd(96)+24 ランダムにプレイヤーのいそうなところに発生
e.y=-8 上の方
e.n=flr(rnd(3)) 0で音符、1で鉛筆、2でコード
e.s=e.n+25 スプライト番号
e.v=(e.n+1)*2 落下速度
add(enemy,e)
end
function add_gim() しょうがいぶつの追加
local g={}
g.x=135 右の方
g.y=112 床上
g.s=28 スプライト番号
add(gim,g)
end
function add_hole() 穴の追加
local h={}
h.x=135 穴の中央
add(hole,h)
end
function update_enemy(e) 敵の更新
e.y+=e.v 落下処理
if dist(e,pl)<12 then プレイヤーとの当たり判定
if pl.df==0 then
pl_damage() プレイヤーのダメージ処理
end
end
if e.y>135 then
del(enemy,e) 画面外から出たら消去
end
end
function update_gim(g) しょうがいぶつの更新
g.x-=4 左へ流す
if dist(g,pl)<12 then プレイヤーとの当たり判定
if pl.x>g.x then プレイヤーの座標の直接更新
pl.x+=1
elseif pl.x-4>8 then
pl.x-=4
end
end
if g.x<-4 then
del(gim,g) 画面外から出たら消去
end
end
function update_hole(h) 穴の更新
h.x-=4 左へ流す
if abs(pl.x-h.x)<8 and pl.y==104 then プレイヤーは接地しているときに穴に近いと落下する
pl.drop=true 落下判定をtrueに
end
if h.x<-4 then
del(hole,h) 画面外から出たら消去
end
end
function draw_88(a) スプライトの描画
spr(a.s,a.x-4,a.y-4) 左上が基準点なので中央にするためのずらし
end
function draw_hole(h) 穴の描画
rectfill(h.x-8,112,h.x+7,127,2) 単に塗りつぶしている。
end