FARM PANIC

ゲームを作りました。"FARM PANIC"ってタイトルです。よろしくお願いします。
f:id:himawarifurutani1919:20220128221953g:plain

www.lexaloffle.com



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点くらいとったのでみんながんばって超えてみてください。