ゲームを作りました。"FARM PANIC"ってタイトルです。よろしくお願いします。
1.概要
天才だったので、天才っぽく話します。1/26の11:00くらいに目が覚めて、そのときにはっとしてゲームのアイディアが思いつきました。それをその日のうちに5時間くらいかけて形にしました。 (https://nylon-wce.github.io/farm_demo/) そこからレベルを調整したり演出を強化したりで3日でできました。天才的。
2.コード解説
最近あまりやってなかったですけどコード見せます。前よりはそれっぽいコード書いてると思いますがキモ過ぎるところは相変わらずキモいと思います。
--farmpanic --presented by nylon version=1 function _init() cartdata("nylon_farmpanic") if version==dget(0) then hscore=dget(1) else hscore=0 end dset(0,version) music() init_game() init_cloud() t,title=0,64 end function init_game() init_pl() init_spnr() shot={} kabu={} enemy={} num={} ptcl={} shake,score,sky,st=0,0,0,0 music(0) end function _update60() if title>-16 then if title==64 then if btn(🅾️) or btn(❎) then title-=1 sfx(2) end else title-=(64-title) end else if #spnrc==0 then pl.s=4 if (btn(🅾️) or btn(❎)) init_game() sfx(2) else if (t%60==0) add_enemy() update_pl() st+=1 if (st==1800) st=0 sky+=1 sky%=3 end foreach(shot,update) foreach(enemy,update) foreach(spnr,update) foreach(kabu,update) foreach(ptcl,update) foreach(num,update) end foreach(cloud,update) t+=1 t%=360 end function _draw() local c=9 if sky==0 then cls(12) c=10 elseif sky==1 then cls(8) pal(7,2) pal(6,1) pal(13,1) pal(11,9) else cls(0) pal(3,1) pal(11,6) pal(1,13) pal(7,1) pal(6,13) pal(13,6) end circfill(16,16,8,c) if sky==2 then circfill(17,15,7,10) elseif sky==0 then for i=0,11 do local theta=i/12+t/360 pset(16.5+12*cos(theta),16.5+12*sin(theta),c) end end foreach(cloud,draw) if shake>0.5 then local x,y=myrnd(shake),myrnd(shake) camera(x,y) shake*=0.95 else shake=0 end spr(192,0,16,16,4) for i=0,5 do for j=-1,8 do spr(64,16*j,16*i+48,2,2) end end pal() palt(0,false) palt(14,true) foreach(spnr,draw) foreach(shot,draw) foreach(kabu,draw) foreach(enemy,draw) draw_pl() for i=0,3 do spr(96,32*i,41,4,1) end palt() if title>-16 then spr(128,11,title-8,14,4) print("\#5start:🅾️ or ❎",36,80,6) end foreach(ptcl,draw) foreach(num,draw) camera() print_score() if #spnrc==0 then print("\#0 game over \nrestart:🅾️ or ❎",33,60,7) end end function update(self) self:_u() end function draw(self) self:_d() end function myrnd(a) return rnd(2*a)-a end function frnd(a) return flr(rnd(a)) end function dist(a,b) return sqrt((a.x-b.x)^2+(a.y-b.y)^2) end function limit(a,b,x,y) a[b]=max(x,min(a[b],y)) end function print_score() local s1,s2=tostr(hscore),tostr(score) s1,s2=s1.."0",s2.."0" for i=1,5-s_len(hscore) do s1="0"..s1 end for i=1,5-s_len(score) do s2="0"..s2 end print("hi-score:"..s1.." score:"..s2,2,2,5) end function s_len(s) if s<10 then return 1 else return s_len(s/10)+1 end end --player function init_pl() pl={} pl.x,pl.vx,pl.s,pl.mf=64,0,0,0 pl.flip,pl.af,pl.a=true,0,0 end function update_pl() pl_flag() pl_move() pl_mot() if (pl.af==0) pl.a=0 end function pl_move() if pl.af==0 then if (btn(⬅️) and pl.vx>-1) pl.vx-=0.25 if (btn(➡️) and pl.vx<1) pl.vx+=0.25 end if (abs(pl.vx)>1) pl.vx=sgn(pl.vx) if (not btn(⬅️) and not btn(➡️)) or (btn(⬅️) and btn(➡️)) or pl.af>0 then if abs(pl.vx)>0.125 then pl.vx*=0.6 else pl.vx=0 end end pl.x+=pl.vx limit(pl,"x",4,123) end function pl_mot() if pl.a==0 then if abs(pl.vx)>0 then pl.mf+=1 pl.mf%=8 if (pl.mf%8==7) sfx(0) else pl.mf=0 end if pl.vx<-0.25 then pl.flip=false elseif pl.vx>0.25 then pl.flip=true end pl.s=2*flr(pl.mf/4 elseif pl.a==1 then if pl.af>10 then pl.s=4 else pl.s=6 end else pl.s=4 end end function pl_flag() if (pl.af>0) pl.af-=1 end function draw_pl() spr(pl.s,pl.x-8,32,2,2,pl.flip,false) end --spawner function init_spnr() spnr={} spnrc={1,2,3,4} for i=1,4 do local s={} s.x,s.f,s.r,s.n,s.d=32*i-16,0,2,i,false function s._u() if not s.d then if s.f<30 then s.f+=1 if (s.f==30) add_ptcl(s.x,48,7) end s.r=2+flr(s.f/5) if s.f>=10 and abs(pl.x-s.x)<12 then if btn(⬇️) or btn(❎) then pl.af,pl.a=10,2 shake=2 s.f=-90 add_shot(s.x,s.r) elseif s.f==30 and (btn(⬆️) or btn(🅾️)) then pl.af,pl.a=20,1 s.f=-90 add_kabu(s.x) end end end end function s._d() if s.d then spr(36+2*flr(t%60/30),s.x-8,36,2,2) else if s.f>0 then sspr(64,0,16,24,s.x-s.r,48-s.r*1.5,s.r*2,s.r*3) rectfill(s.x-9,63,s.x+8,65,0) local c=8 if s.f==30 then c=11 elseif s.f>=10 then c=10 end line(s.x-8,64,s.x-8+s.f/2,64,c) end end end add(spnr,s) end end --effect function add_ptcl(x,y,c,r) local p={} p.x,p.y,p.v,p.r,p.t,p.c,p.rr=x,y,1.5,0,16,c,r if (p.rr==nil) p.rr=0 function p._u() if (p.t<=0) del(ptcl,p) p.r+=p.v p.v*=0.9 p.t-=1 end function p._d() for i=0,7 do circfill(p.x+p.r*cos(i/8),p.y+p.r*sin(i/8),p.rr,p.c) end end add(ptcl,p) end function init_cloud() cloud={} for i=0,10 do local c={} c.x,c.y,c.r=rnd(256),myrnd(8),16+rnd(12) function c._u() c.x-=0.25+0.2*sin(t/360) if (c.x<-48) c.x,c.y,c.r=256,myrnd(8),12+rnd(12) end function c._d() sspr(80,0,32,16,c.x,c.y,c.r*2,c.r) end add(cloud,c) end end function add_num(a,n,y) local s={} s.x,s.y,s.n,s.col,s.t=a.x,a.y,n,1,0 if (y!=nil) s.y-=y function s._u() if s.t>16 then del(num,s) elseif s.t>15 then s.y-=1 elseif s.t>14 then s.col=15 end s.t+=1 end function s._d() print(s.n,s.x,s.y,s.col) end add(num,s) end --shot function add_shot(x,r) local s={} s.x,s.y,s.r,s.c=x,48+r,r,1 function s._u() s.y+=6 if (s.y>140) del(shot,s) end function s._d() sspr(64,0,16,24,s.x-s.r,s.y-2*s.r,s.r*2,s.r*3) end add(shot,s) sfx(1) end --kabu function add_kabu(x) local s={} s.x,s.y,s.t=x,48,0 function s._u() if s.t<8 then s.t+=1 if (s.t==8) shake=1 else s.y-=6 if (s.y<-12) del(kabu,s) end end function s._d() spr(8,s.x-8,s.y-12,2,3) end add(kabu,s) sfx(2) score+=5*(5-#spnrc) add_num(s,50*(5-#spnrc),20) end --enemy function add_enemy() local e={} e.x,e.y,e.n,e.f,e.t=rnd(128),136,spnrc[frnd(#spnrc)+1],true,0 local d=sqrt((32*e.n-16-e.x)^2+7056) e.vx=0.5*(32*e.n-16-e.x)/d e.vy=-42/d e.flip=e.vx<0 function e._u() e.x+=e.vx e.y+=e.vy if #spnrc>0 then for s in all(shot) do if dist(s,e)<8+s.r then e.f=false add_ptcl(e.x,e.y,7,2) score+=s.c add_num(e,s.c*10) sfx(min(3+s.c,8)) s.c+=1 function e._u() if (e.y>140) del(enemy,e) e.y+=6 end function e._d() spr(42,e.x-8,e.y-8,2,2) end end end end if e.y<52 and e.f then for s in all(spnr) do if s.n==e.n and not s.d then s.d=true del(spnrc,e.n) sfx(13) if #spnrc==0 then if (score>hscore) hscore=score dset(1,hscore) music(4) end end end add_ptcl(32*e.n-16,48,15,1) del(enemy,e) end e.t+=1 e.t%=12 end function e._d() spr(32+2*flr(e.t/6),e.x-8,e.y-8,2,2,e.flip,false) end add(enemy,e) end
これじゃよくわかんないと思うのでいくつか解説します。まず大まかにこのゲームを構成する5つのテーブルを解説します。
・プレイヤー (pl)
・もぐら (enemy)
・カブスポナー (spnr)
・抜けたカブ (kabu)
・発射されたカブ (shot)
今読み返すとカブはもう少しまとめてもよかったかもしれないですね。抜けたカブに攻撃判定があると距離関数がinfしたとき怖いから分けましたがケアしなくてもよかったかも。あとスポナーにはスポナーの数を教えてくれるspnrcというテーブルがあってそれももっとうまくまとめられたかもしれないですね。最近テーブルに関数を持たせることを覚えたので最近のゲームは関数が引き渡されてたり結構いろいろしてます。あと前からよく作ってたのですがlimitという関数を作り直しました。前はlimit(x,a,b)でxをa以上b以下に切り取る関数を作っていたのですが、そのときx=limit(x,a,b)としないといけなかったのをlimit関数を呼ぶだけで処理が終わるようにしました。引数に文字列を渡してテーブル内のあるメンバにlimitを作用させられます。プレイヤーの移動処理で摩擦によって指数関数的に減衰させられるところとか8方向にきれいに舞うパーティクルとか自分のほかのゲームでも出てくるやつも多いですね。
3.グラフィック
寝てるときに見た夢のイメージだとリズム天国ゴールドの農家の人が地面踏みしめる動作をしていて下にカブが発射されるって感じだったんですけど、16*16に収まるように二頭身の動物にしました。人でもよかったんですがカワイイのでクマにしました。麦藁帽と半パンはこだわりです。土は「土 ドット」でググったものを参考にして、茶色すぎると全体的に鮮やかさが薄れるので赤紫をベースにしました。空も雲だけだと寂しかったので4色で山を描きました。山もいい味出てますが5分もかからずに描けました。演出で夕方や夜も用意しましたが、山が浮かないよう山の色味も変わるようにしてます。画面振動やスコア表示の数字などはいつもやってる通りです。
4.音楽
効果音はこだわりました。爽快な音に加え、モグラを連続で倒すと音階が上がるようにしてます。マリオの1up演出みたいな。BGMはなかなか締まらなくてデモ版のままでもよかった気がしてきました。でもゲームオーバー音があるのは楽しい。
5.総評
改めて継続は力だなって思いました。今までいろいろ作ってたからストレスフリーにサクサク形になっていくし、全体的に仕上がりもよかったので楽しかったです。あまりやりこんではないですが6430点くらいとったのでみんながんばって超えてみてください。