つづき…ですが,ある程度読んだところで概ね活躍してる関数には馴染んでしまった気がして, これ以上伸ばしてもあんまりおもしろみがないなと思ったので途中で唐突に終わります. 唐突に終わることも合って advent calendar 外,たんに「つづき」ということにします
func s:IntroFilter(id, key) if a:key == 's' || a:key == 'S' call s:Clear() let s:round = 0 let s:ready = popup_create('Get Ready!', #{border: [], padding: [2, 4, 2, 4]}) call s:BlinkLevel(s:ready, 1) call timer_start(s:blink_time * 8, { -> s:NextRound()}) let s:ready_timer = timer_start(300, {... -> s:ReadySound()}) elseif a:key == 'x' || a:key == 'X' || a:key == "\<Esc>" call s:Clear() endif return 1 endfunc func s:BlinkLevel(winid, on) call popup_setoptions(a:winid, #{highlight: a:on ? 'KillerLevelX': 'KillerLevel'}) let s:blink_timer = timer_start(s:blink_time, {x -> s:BlinkLevel(a:winid, !a:on)}) endfunc func s:ReadySound() call s:PlaySound('quack') let s:ready_timer = timer_start(s:blink_time * 2, {... -> s:ReadySound()}) endfunc
これが popup_create
の filter
として呼ばれているので,key
でどのキーが押下されたかが
受け取れます. s
でスタート,x
か <Esc>
で終了,ほかは無視,ですね.
まず timer_start({time}, {callback} [, {options}])
についておさらい
- 返り値: timer ID
time
: ms 単位で待ち時間を設定callback
: コールバックを設定.timer ID
が引数として渡されるrepeat
: 何回 callback を呼ぶか.-1
で無限に呼び続ける
というわけでチカチカ (s:BlinkLevel()
) したあとに
クエックエックエックエッとs:ReadySound()
が繰り返し呼ばれたころに s:NextRound()
が呼ばれるようになっている.
func s:NextRound() call s:Clear() let s:round += 1 let s:sheepcount = 0 let s:frozen = 0 call s:ShowBulletSoon() " Every once in a while let the next sheep that moves poop. let s:wantpoop = 0 let s:poop_timer = timer_start(s:poop_interval[s:round - 1], {x -> s:WantPoop()}, #{repeat: -1}) " Create a few sheep to kill. let topline = &lines > 50 ? &lines - 50 : 0 call s:PlaceSheep(topline + 0, 5, 'KillerSheep') call s:PlaceSheep(topline + 5, 75, 'KillerSheep2') " 中略 call s:ShowLevel(topline) let s:canon_id = popup_create([' /#\ ', ' /###\ ', '/#####\'], #{ \ line: &lines - 2, " 開始場所でしたね \ highlight: 'KillerCannon', " ハイライト \ filter: function('s:MoveCanon'), " 操作を受け取る \ zindex: s:cannon_zindex, " CSS と同じ,デフォルト50. \ mask: [[1,2,1,1], [6,7,1,1], [1,1,2,2], [7,7,2,2]], \ " 列行で指定する長方形を透明に \ mapping: 0, \ }) endfunc const s:poop_interval = [700, 500, 300, 200, 100] " えげつなくなっていく
こういう画面をイメージしてくれ
まず s:ShowBulletSoon()
まわり
func s:ShowBulletSoon() let s:bullet_available = 0 let s:bullet_timer = timer_start(s:bullet_holdoff, {x -> ShowBullet()}) endfunc func ShowBullet() if s:frozen return endif let s:bullet_timer = 0 let pos = popup_getpos(s:canon_id) let s:bullet_available = popup_create(['|', '|'], #{ \ col: pos.col + 3, \ line: &lines - 3, \ highlight: 'KillerBullet', \ zindex: s:bullet_zindex, \ }) endfunc
Bullet は 機体に生えてくる→発射→消滅→機体に生える というのを繰り返してて,ここはその機体に生えるパートだ.
popup_getpos({id})
で id
のポップアップの場所を教えてくれるので,機体の場所を取得して
そこにKillerBullet 色の | を生やしている.
続いて
let s:poop_timer = timer_start(s:poop_interval[s:round - 1], {x -> s:WantPoop()}, #{repeat: -1})
s:poop_interval
ごとに無限に繰り返される s:WantPoop()
: let s:wantpoop=1
で状態管理.
それから羊を置く
func s:NextRound() " ... " Create a few sheep to kill. let topline = &lines > 50 ? &lines - 50 : 0 call s:PlaceSheep(topline + 0, 5, 'KillerSheep') call s:PlaceSheep(topline + 5, 75, 'KillerSheep2') " 中略 call s:ShowLevel(topline) "... endfunction func s:PlaceSheep(line, col, hl) let id = popup_create(s:sheepSprite[0], #{ \ line: a:line, \ col: a:col, \ highlight: a:hl, \ mask: s:sheepMasks[0], \ fixed: 1, \ zindex: s:sheep_zindex, \ wrap: 0, \}) call setwinvar(id, 'left', 0) call setwinvar(id, 'dead', 0) call timer_start(s:sheep_anim, {x -> s:AnimSheep(id, 1)}) let s:sheepcount += 1 sleep 10m return id endfunc
だいぶ popup_create
に慣れてきた.popup_create
は window を作るので,
setwinvar
で変数を覚えさせておくことができるのだった.そうなると s:AnimSheep
が…
ちょっと長いので諦めてコメントで読んだことにするね
func s:AnimSheep(id, state) " 前略 let pos = popup_getpos(a:id) if getwinvar(a:id, 'dead') return endif let left = getwinvar(a:id, 'left') if left == 1 " 左端に来たら " i) 高さを判定(だんだん下に降りてきて,下端でゲームオーバ) if pos.line > &lines - 11 call s:PlaySoundForEnd() endif " ii) 5行降りて右端に戻る call popup_setoptions(a:id, #{pos: 'topleft', col: &columns + 1, line: pos.line + 5}) let left = 0 elseif pos.col > 1 " 左に移動 call popup_move(a:id, #{col: pos.col - 1}) else " left != 1 && pos.col <= 0 " 普段は左端から col を数えてる.羊の頭が左端にはみ出られるように " 端にきたら `topright` から数えるようにして左に進めていく if left == 0 let left = 15 " ひつじの幅 endif call popup_setoptions(a:id, #{pos: 'topright', col: left - 1}) let left -= 1 endif " 概ねそのまま let poopid = getwinvar(a:id, 'poopid') if poopid let poopcount = getwinvar(a:id, 'poopcount') if poopcount == 1 " drop the poop call popup_close(poopid) call setwinvar(a:id, 'poopid', 0) call s:Poop(pos.line + 1, left ? left : pos.col + 12) else call popup_move(poopid, #{col: left ? left + 1 : pos.col + 14, \ line: pos.line + 1}) endif call setwinvar(a:id, 'poopcount', poopcount - 1) endif call setwinvar(a:id, 'left', left) call popup_settext(a:id, s:sheepSprite[a:state]) call popup_setoptions(a:id, #{mask: s:sheepMasks[a:state]}) call timer_start(s:sheep_anim, {x -> s:AnimSheep(a:id, a:state == 3 ? 0 : a:state + 1)}) if left || pos.col < &columns - 14 if s:wantpoop && !getwinvar(a:id, 'poopid') let s:wantpoop = 0 call setwinvar(a:id, 'poopcount', s:poop_countdown) let poopid = popup_create('x', #{ \ col: left ? left + 1 : pos.col + 14, \ line: pos.line + 1, \ highlight: 'KillerPoop', \ zindex: s:bullet_zindex, \ }) call setwinvar(a:id, 'poopid', poopid) endif endif endfunc
…ということで i) はじめの描画と ii) ひつじの動き がわかったわけで,概ね読んで楽しいフェーズが終わってきた気がします. あとは弾の当たり判定だけ読んで終わりにします……
func s:MoveBullet(x, id) if s:frozen return endif let pos = popup_getpos(a:id) if pos == {} call timer_stop(a:x) return endif if pos.line <= 2 call popup_close(a:id) call timer_stop(a:x) else call popup_move(a:id, #{line: pos.line - 2}) let winid = popup_locate(pos.line - 2, pos.col) if winid != 0 && winid != a:id call s:KillSheep(winid, 4) if s:sheepcount == 1 let s:frozen = 1 endif call s:PlaySound('beh') call popup_close(a:id) endif endif endfunc
この popup_locate
, ゲーム作るためにあるんじゃないかという関数ですが(?)
popup_locate({row}, {col}) *popup_locate()* Return the |window-ID| of the popup at screen position {row} and {col}. If there are multiple popups the one with the highest zindex is returned. If there are no popups at this position then zero is returned.
というわけでスッキリ.おしまい(唐突).