Vimでゲームを作るためのtips
なんかvimでゲームを作るのがブームのようなので(w,いくつかvimscriptでゲームを作ってみて分かったことをまとめようと思います.
最初に言っておくと,これはゲーム作成に限りませんが,vimscriptを書くコツはいかに他のvimscriptから似たような処理を見つけて抜き出してくるかだと思います.
Redirecting…
にいくつか紹介されているので,それのソースを見るのが一番速いと思います^^;
・バッファ作成
もしバッファが作成されていなければ画面を分割して新たにバッファを作り,バッファがあればそのバッファに移動します.(この例では ==MineSweeper== )
let winnum = bufwinnr(bufnr('==MineSweeper==')) if winnum != -1 if winnum != bufwinnr('%') exe "normal \<c-w>".winnum."w" endif else exec 'silent split ==MineSweepe== endif
現在のウィンドウをそのまま分割せず利用するのであれば以下の記述で十分です.
edit `='==SPACE INVADER=='`
・バッファのクリア
silent %d _
・画面描写(バッファの書き換え)
setline()を使います.setline()の第二引数に文字列のリストを渡すことで複数行を一気に書き換えることができます.ある2次元の配列をフィールドとして確保するというのがいいと思います.ただし,文字列はインデックスでアクセスできても値を変更することができないことに注意が必要です.
let s:field = ["#######", \"# #", \"# #", \"#######",] function! s:update() call setline(1,s:field) endfunction
この例でs:fieldを書き換えたい場合は以下のようにして処理できます.
funciton s:change(x,y,c) let line = s:field[a:y] let left = (a:x == 0 ? '' : line[: a:x - 1]) let right = (a:x == len(line) - 1 ? '' : line[a:x + 1 :]) let s:field[a:y] = left . a:c . right endfunction
また,s:field[i]をcharの配列にして,setline()するときにjoin()することもできます.この場合変更は s:field[y][x] = c のようにできますが,前のと比べて描画は遅くなると思います.
let s:field = [['#','#','#','#','#','#','#'], \['#',' ',' ',' ',' ',' ','#'], \['#',' ',' ',' ',' ',' ','#'], \['#','#','#','#','#','#','#'],] function! s:update() for i in range(len(s:field)) let str = join(s:field[i],'') call setline(i+1,str) endfor endfunction
また,バッファの特定の場所を変更する処理は以下の処理でできます(GitHub - mattn/invader-vim: invader game in vimより)
" y行目x列目の文字をcに変更する function! s:update(x, y, c) let s = getline(a:y) let o = '' if a:x > 0 let o .= s[:a:x-1] elseif a:x < 0 let o .= a:c[-a:x :] endif let o .= a:c let o .= s[a:x+(len(a:c)+1)-1:] call setline(a:y, o) endfunction
・10fpsで動作
sleep を使います.
while s:loop == 1 (何かする…) call s:update() sleep 100m redraw endwhile
また,autocmdを使って疑似的に定期的な処理をおこなうことができます.autocmdを使って疑似タイマーを作る方法はhttp://vim-users.jp/2010/09/hack173/を参照してください.また,以下の例も参考になると思います.(GitHub - tyru/pacman.vim: *incomplete yet* *patches welcome*より)
function! s:create_buffer() ... " Global options. let b:pacman.save_updatetime = &updatetime set updatetime=100 let b:pacman.save_lazyredraw = &lazyredraw set lazyredraw let b:pacman.save_virtualedit = &virtualedit set virtualedit= let b:pacman.save_insertmode = &insertmode set noinsertmode ... augroup pacman autocmd! call s:register_polling_autocmd() " Inhibit insert-mode. autocmd InsertEnter <buffer> stopinsert " Pause on BufLeave, BufEnter. autocmd BufLeave <buffer> call s:pause() autocmd BufEnter <buffer> call s:restart() " Clean up all thingies about pacman. autocmd BufDelete <buffer> call s:clean_up() augroup END ... endfunction function! s:register_polling_autocmd() autocmd pacman CursorHold <buffer> silent call feedkeys("g\<Esc>", "n") autocmd pacman CursorHold <buffer> call s:main_loop() let b:pacman.pausing = 0 endfunction function! s:clean_up() ... "設定を元に戻す let &updatetime = b:pacman.save_updatetime let b:pacman.save_updatetime = -1 let &lazyredraw = b:pacman.save_lazyredraw let b:pacman.save_lazyredraw = -1 let &virtualedit = b:pacman.save_virtualedit let b:pacman.save_virtualedit = '' let &insertmode = b:pacman.save_insertmode let b:pacman.save_insertmode = '' ... endfunction
・キー入力
マッピングを使うのが素直なやり方だと思います.スクリプトローカル関数をマッピングする場合には<SID>を使う必要があることに注意してください.
nnoremap <silent> <buffer> x :call <SID>_click()<CR>
マッピングを使う注意点として,例えば z をマッピングした場合,標準的なvimでは zz が行を中央に持ってくる動作にマッピングされているので,zを押してもすぐにzのマッピングが動作せず,timeoutlen(標準で1000ms)待ってからzのキーが反応します.なるべく被らないマッピングを選ぶのがいいです.ノーマルモードの場合おそらく x をマッピングして変更している人はいないと思うので,何かのアクションにはxを使うといいと思います.
もう一つの方法として,getchar()を使う方法があります.特にgetchar(0)を使うと1文字読み込めるときだけ読み込むので,sleepを使って動作させている場合はこの方法がいいと思います.
let c = nr2char(getchar(0)) if c == 'x' (何か処理) else if ... endif (何か処理) sleep 100m ...
・設定しとくといいオプション
setl buftype=nowrite "バッファの内容を保存しない setl noswapfile "スワップファイルを作成しない setl bufhidden=wipe "バッファがウィンドウ内から表示されなくなったら削除 setl nonumber "行番号を表示しない setl nowrap "ラップしない setl nocursorline "カーソル行をハイライトしない setl nocursorcolumn "カーソル列をハイライトしない
・乱数
http://vim-users.jp/2009/11/hack98/ で紹介されている方法を使います.万が一reltime()が使えない場合には連番を返すようにしています.
let s:rand_num = 1 function! s:rand() if has('reltime') let match_end = matchend(reltimestr(reltime()), '\d\+\.') + 1 return reltimestr(reltime())[match_end:] else let s:rand_num += 1 return s:rand_num endif endfunction
追記
あまりこの乱数は良いとは言えないので, ynkdirさんによる msvc で使われている乱数の実装を使った方が良いみたいです.
・メッセージをハイライトして表示
function! s:message(msg) echohl WarningMsg echo a:msg echohl None endfunction
追記するかも?
是非皆さんも作ってみてくださいw