aliens.vbs
2008年5月17日
先日配布の、oyagameを利用したゲーム、aliensのコードを少しばかり解説。
まず、ライセンスはGPL。これは、CでできたオリジナルのaliensがGPLであることによる。
はじめに、3つのoyagameオブジェクトをCreateObject()を使って作成。『execute SDL.ConstVBS』で定数の定義を行った後、SDLを初期化。SDL_imageについては、『call IMG.LoadLibrary("SDL_image.dll","IMG_")』のように設定している。2番目の引数に"IMG_"と入れておくことで、以後の呼び出しでは『IMG.IMG_xxx』ではなく『IMG.xxx』を用いることができる。SDL_mixerについても、同様。
DATAFILE()は、aliens.cで使われていたマクロをVBScriptの関数に置き換えたもの。Mix_LoadWAV()は、SDL_mixer.hで定義されているマクロをVBScriptの関数にしたもの。このあたりがSDLコア以外のライブラリを使うときのウィークポイントになりそう。ただ、一部のライブラリだけを差別化したくないので、仕様としてはこういった記述を加えないといけないことになると思う。
このあたりは、オリジナルのaliensを素直に移植したようなコード。『SDL.CreateStructure("SDL_Rect")』は、oyagameを使う場合、おそらく頻繁に出てくるコードだ。
LoadImage()関数を定義するコードの4行目、『SDL.int8(image.pixels)』に注目。オリジナルのaliens.cでは、『SDL_SetColorKey(image, (SDL_SRCCOLORKEY|SDL_RLEACCEL), *(Uint8 *)image->pixels);』となっている部分。SDL_Surfaceのpixels要素は void* 型なので、ここの例のようにSDL.int8()を利用して、void* というポインタの参照先から8ビット整数を取り出している。
UpdateScreen()関数の定義コード5行目。ここは、オリジナルではSDL_UpdateRects()を使っている部分だが、ここにoyagameの弱点があるかも。たぶん、oyagameではSDL_UpdateRects()は使えない。ここでは代わりにSDL.UpdateRectを使っている。
上記コードの、下から13行目、『SDL.int8(keys+SDLK_SPACE)=SDL_PRESSED』は、オリジナルでは『keys[SDLK_SPACE] == SDL_PRESSED 』となっている。このあたりの使い方は、Cにおけるメモリ管理について知識がないと分かりにくい部分だ。キーステータスの取得はこんな風に行うと覚えるしかないかなと言うところ。
残りの部分は、おそらく解説の必要は無いと思う。『'Main routine follows』から後の記述(上記コードの最後の数行)は、Cにおけるmain()関数内に記述されているコードを移植したもの。
ゲームを一つ移植してみて分かったことは
1)現在の仕様でちゃんとゲームが作れる
2)oyagame独特のコード記述がいくつかある
といったこと。最終目標は、中学生・高校生にゲームを作ってもらうことだが、まだまだそのレベルではなさそう。速いうちに、pygame互換のインターフェースを用意したほうが良いかもしれない。
''''''''''''''''''''''''''''''''''''' ' The license of this script is GPL ' ' ' ''''''''''''''''''''''''''''''''''''' option explicit dim SDL,IMG,Mix,i set SDL=CreateObject("sfcmini.oyagame") execute SDL.ConstVBS call SDL.Init(SDL_INIT_EVERYTHING) set IMG=CreateObject("sfcmini.oyagame") call IMG.LoadLibrary("SDL_image.dll","IMG_") set Mix=CreateObject("sfcmini.oyagame") call Mix.LoadLibrary("SDL_mixer.dll","Mix_")
まず、ライセンスはGPL。これは、CでできたオリジナルのaliensがGPLであることによる。
はじめに、3つのoyagameオブジェクトをCreateObject()を使って作成。『execute SDL.ConstVBS』で定数の定義を行った後、SDLを初期化。SDL_imageについては、『call IMG.LoadLibrary("SDL_image.dll","IMG_")』のように設定している。2番目の引数に"IMG_"と入れておくことで、以後の呼び出しでは『IMG.IMG_xxx』ではなく『IMG.xxx』を用いることができる。SDL_mixerについても、同様。
Function DATAFILE(X) DATAFILE="data\" & X End Function Function Mix_LoadWAV(file) Mix_LoadWAV=Mix.LoadWAV_RW(SDL.RWFromFile(file,"rb"),1) End Function
DATAFILE()は、aliens.cで使われていたマクロをVBScriptの関数に置き換えたもの。Mix_LoadWAV()は、SDL_mixer.hで定義されているマクロをVBScriptの関数にしたもの。このあたりがSDLコア以外のライブラリを使うときのウィークポイントになりそう。ただ、一部のライブラリだけを差別化したくないので、仕様としてはこういった記述を加えないといけないことになると思う。
const FRAMES_PER_SEC=50 const PLAYER_SPEED=4 const MAX_SHOTS=3 const SHOT_SPEED=6 const MAX_ALIENS=30 const ALIEN_SPEED=5 const ALIEN_ODDS=50 '(1*FRAMES_PER_SEC) const EXPLODE_TIME=20 const MAX_UPDATES=102 '3*(1+MAX_SHOTS+MAX_ALIENS) class object public alive public facing public x,y public image private Sub Class_Initialize alive=0 facing=0 x=0 y=0 image=0 end sub end class dim screen,background dim player set player=new object dim reloading reloading=0 dim shots(3)'MAX_SHOTS for i=0 to MAX_SHOTS-1 set shots(i)=new object next dim aliens(30)'MAX_ALIENS for i=0 to MAX_ALIENS-1 set aliens(i)=new object next dim explosions(31)'MAX_ALIENS+1 for i=0 to MAX_ALIENS set explosions(i)=new object next dim numupdates dim srcupdates(102),dstupdates(102)'MAX_UPDATES for i=0 to MAX_UPDATES-1 set srcupdates(i)=SDL.CreateStructure("SDL_Rect") set dstupdates(i)=SDL.CreateStructure("SDL_Rect") next class blit public src public srcrect public dstrect private Sub Class_Initialize set srcrect=SDL.CreateStructure("SDL_Rect") set dstrect=SDL.CreateStructure("SDL_Rect") End Sub end class dim blits(102)'MAX_UPDATES for i=0 to MAX_UPDATES-1 set blits(i)=new blit next
このあたりは、オリジナルのaliensを素直に移植したようなコード。『SDL.CreateStructure("SDL_Rect")』は、oyagameを使う場合、おそらく頻繁に出てくるコードだ。
dim music const MUSIC_WAV=0 const SHOT_WAV=1 const EXPLODE_WAV=2 const NUM_WAVES=3 dim sounds(3)'NUM_WAVES Function LoadImage(datafile, transparent) dim image,surface image=IMG.Load(datafile) set image=SDL.CreateStructure("SDL_Surface",image) if transparent then call SDL.SetColorKey(image,SDL_SRCCOLORKEY+SDL_RLEACCEL,SDL.int8(image.pixels)) set surface=SDL.DisplayFormat(image) SDL.FreeSurface(image) set LoadImage=surface End Function
LoadImage()関数を定義するコードの4行目、『SDL.int8(image.pixels)』に注目。オリジナルのaliens.cでは、『SDL_SetColorKey(image, (SDL_SRCCOLORKEY|SDL_RLEACCEL), *(Uint8 *)image->pixels);』となっている部分。SDL_Surfaceのpixels要素は void* 型なので、ここの例のようにSDL.int8()を利用して、void* というポインタの参照先から8ビット整数を取り出している。
Function LoadData dim i music=Mix.LoadMUS(DATAFILE("music.it")) sounds(MUSIC_WAV)=Mix_LoadWAV(DATAFILE("music.wav")) sounds(SHOT_WAV)=Mix_LoadWAV(DATAFILE("shot.wav")) sounds(EXPLODE_WAV)=Mix_LoadWAV(DATAFILE("explode.wav")) set player.image=LoadImage(DATAFILE("player.gif"),1) set shots(0).image=LoadImage(DATAFILE("shot.gif"),0) for i=1 to MAX_SHOTS-1 set shots(i).image=shots(0).image next set aliens(0).image=LoadImage(DATAFILE("alien.gif"),1) for i=1 to MAX_ALIENS-1 set aliens(i).image=aliens(0).image next set explosions(0).image=LoadImage(DATAFILE("explosion.gif"),1) for i=1 to MAX_ALIENS set explosions(i).image=explosions(0).image next set background=LoadImage(DATAFILE("background.gif"),0) for i=0 to MAX_UPDATES-1 set blits(i).srcrect=srcupdates(i) set blits(i).dstrect=dstupdates(i) next LoadData=1 End Function Function FreeData dim i if music then Mix.FreeMusic(music) for i=0 to NUM_WAVES-1 if sounds(i) then Mix.FreeMusic(sounds(i)) next SDL.FreeSurface(player.image) SDL.FreeSurface(shots(0).image) SDL.FreeSurface(aliens(0).image) SDL.FreeSurface(explosions(0).image) SDL.FreeSurface(background) End Function Function CreateAlien dim i for i=0 to MAX_ALIENS-1 if aliens(i).alive=0 then exit for next if i=MAX_ALIENS then exit function with aliens(i) .facing=1 if rnd(1)<0.5 then .facing=-1 .y=0 .x=0 if .facing<0 then .x=screen.w - .image.w - 1 .alive=1 end with End Function Function DrawObject(sprite) dim update set update=blits(numupdates) numupdates=numupdates+1 set update.src=sprite.image update.srcrect.x=0 update.srcrect.y=0 update.srcrect.w=sprite.image.w update.srcrect.h=sprite.image.h update.dstrect.x=sprite.x update.dstrect.y=sprite.y update.dstrect.w=sprite.image.w update.dstrect.h=sprite.image.h End Function Function EraseObject(sprite) dim update,wrap set update=blits(numupdates) numupdates=numupdates+1 set update.src=background update.srcrect.x=sprite.x mod background.w update.srcrect.y=sprite.y update.srcrect.w=sprite.image.w update.srcrect.h=sprite.image.h wrap=(update.srcrect.x+update.srcrect.w)-(background.w) if wrap>0 then update.srcrect.w=update.srcrect.w-wrap update.dstrect.x=sprite.x update.dstrect.y=sprite.y update.dstrect.w=update.srcrect.w update.dstrect.h=update.srcrect.h if wrap>0 then set update=blits(numupdates) numupdates=numupdates+1 set update.src=background update.srcrect.x=0 update.srcrect.y=sprite.y update.srcrect.w=wrap update.srcrect.h=sprite.image.h update.dstrect.x=((sprite.x\background.w)+1)*background.w update.dstrect.y=sprite.y update.dstrect.w=update.srcrect.w update.dstrect.h=update.srcrect.h end if End Function Function UpdateScreen dim i for i=0 to numupdates-1 call SDL.LowerBlit(blits(i).src,blits(i).srcrect,screen,blits(i).dstrect) next call SDL.UpdateRect(screen, 0, 0, 0, 0) 'SDL.Flip() numupdates=0 End Function
UpdateScreen()関数の定義コード5行目。ここは、オリジナルではSDL_UpdateRects()を使っている部分だが、ここにoyagameの弱点があるかも。たぶん、oyagameではSDL_UpdateRects()は使えない。ここでは代わりにSDL.UpdateRectを使っている。
Function Collide(sprite1,sprite2) Collide=0 if sprite1.y >= (sprite2.y+sprite2.image.h) then exit function if sprite1.x >= (sprite2.x+sprite2.image.w) then exit function if sprite2.y >= (sprite1.y+sprite1.image.h) then exit function if sprite2.x >= (sprite1.x+sprite1.image.w) then exit function Collide=1 End Function dim next_tick 'used as static var in following function next_tick=0 Function WaitFrame dim this_tick this_tick=SDL.GetTicks() if (this_tick<next_tick) then SDL.Delay(next_tick-this_tick) next_tick=this_tick + (1000/FRAMES_PER_SEC) End Function Function RunGame dim i,j,ev,keys,dst set ev=SDL.CreateStructure("SDL_Event") set dst=SDL.CreateStructure("SDL_Rect") 'Paint the background numupdates=0 for i=0 to screen.w step background.w dst.x=i dst.y=0 dst.w=background.w dst.h=background.h call SDL.BlitSurface(background,0,screen,dst) next call SDL.UpdateRect(screen, 0, 0, 0, 0) 'SDL.Flip() 'Initialize the objects player.alive=1 player.x=(screen.w-player.image.w)\2 player.y=(screen.h-player.image.h)-1 player.facing=0 DrawObject(player) for i=0 to MAX_SHOTS-1 shots(i).alive=0 next for i=0 to MAX_ALIENS -1 aliens(i).alive=0 next CreateAlien() DrawObject(aliens(0)) UpdateScreen() do while (player.alive) 'Wait for the next frame WaitFrame() 'Poll input quese, run keyboard loop do while (SDL.PollEvent(ev)) if ev.type=SDL_QUIT then exit function loop keys=SDL.GetKeyState(0) 'Erase everything from the screen for i=0 to MAX_SHOTS-1 if shots(i).alive then EraseObject(shots(i)) next for i=0 to MAX_ALIENS-1 if aliens(i).alive then EraseObject(aliens(i)) next EraseObject(player) for i=0 to MAX_ALIENS if explosions(i).alive then EraseObject(explosions(i)) next 'decrement the lifetime of explosions for i=0 to MAX_ALIENS j=explosions(i).alive if j then explosions(i).alive=j-1 next 'Create new alien if (rnd(1)*ALIEN_ODDS<1) then CreateAlien() 'Create new shots if reloading=0 then if SDL.int8(keys+SDLK_SPACE)=SDL_PRESSED then for i=0 to MAX_SHOTS-1 if shots(i).alive=0 then exit for next if i<MAX_SHOTS then shots(i).x=player.x+(player.image.w-shots(i).image.w)\2 shots(i).y=player.y-shots(i).image.h shots(i).alive=1 call Mix.PlayChannelTimed(SHOT_WAV,sounds(SHOT_WAV),0,-1) end if end if end if reloading=SDL.int8(keys+SDLK_SPACE)
上記コードの、下から13行目、『SDL.int8(keys+SDLK_SPACE)=SDL_PRESSED』は、オリジナルでは『keys[SDLK_SPACE] == SDL_PRESSED 』となっている。このあたりの使い方は、Cにおけるメモリ管理について知識がないと分かりにくい部分だ。キーステータスの取得はこんな風に行うと覚えるしかないかなと言うところ。
'Move the player dim facing,x facing=0 if SDL.int8(keys+SDLK_RIGHT) then facing=facing+1 if SDL.int8(keys+SDLK_LEFT) then facing=facing-1 x=player.x+facing*PLAYER_SPEED if x<0 then x=0 if x>=screen.w-player.image.w then x=screen.w-player.image.w-1 player.x=x player.facing=facing 'Move the aliens for i=0 to MAX_ALIENS-1 if aliens(i).alive then x=aliens(i).x+aliens(i).facing*ALIEN_SPEED if x<0 then x=0 aliens(i).y=aliens(i).y+aliens(i).image.h aliens(i).facing=1 elseif x>=screen.w-aliens(i).image.w then x=screen.w-aliens(i).image.w-1 aliens(i).y=aliens(i).y+aliens(i).image.h aliens(i).facing=-1 end if aliens(i).x=x end if next 'Move the shots for i=0 to MAX_SHOTS-1 if shots(i).alive then shots(i).y=shots(i).y-SHOT_SPEED if shots(i).y<0 then shots(i).alive=0 end if next 'Detect collisions for j=0 to MAX_SHOTS-1 for i=0 to MAX_ALIENS-1 if shots(j).alive and aliens(i).alive then if Collide(shots(j),aliens(i)) then aliens(i).alive=0 explosions(i).x=aliens(i).x explosions(i).y=aliens(i).y explosions(i).alive=EXPLODE_TIME call Mix.PlayChannelTimed(EXPLODE_WAV,sounds(EXPLODE_WAV),0,-1) shots(j).alive=0 exit for end if end if next next for i=0 to MAX_ALIENS-1 if aliens(i).alive then if Collide(player,aliens(i)) then aliens(i).alive=0 explosions(i).x=aliens(i).x explosions(i).y=aliens(i).y explosions(i).alive=EXPLODE_TIME player.alive=0 explosions(MAX_ALIENS).x=player.x explosions(MAX_ALIENS).y=player.y explosions(MAX_ALIENS).alive=EXPLODE_TIME call Mix.PlayChannelTimed(EXPLODE_WAV,sounds(EXPLODE_WAV),0,-1) end if end if next 'Draw the aliens, shots, player, and explosions for i=0 to MAX_ALIENS-1 if aliens(i).alive then DrawObject(aliens(i)) next for i=0 to MAX_SHOTS-1 if shots(i).alive then DrawObject(shots(i)) next if player.alive then DrawObject(player) for i=0 to MAX_ALIENS if explosions(i).alive then DrawObject(explosions(i)) next UpdateScreen() 'Loop the music if Mix.PlayingMusic()=0 then call Mix.PlayMusic(music,0) 'Check for keyboard abort if SDL.int8(keys+SDLK_ESCAPE) then player.alive=0 loop do while Mix.Playing(EXPLODE_WAV) WaitFrame() loop Mix.HaltChannel(-1) End Function 'Main routine follows randomize call Mix.OpenAudio(11025, AUDIO_U8, 1, 512) set screen = SDL.SetVideoMode(640, 480, 0, SDL_SWSURFACE) LoadData() RunGame() FreeData() Mix.CloseAudio() WScript.Quit
残りの部分は、おそらく解説の必要は無いと思う。『'Main routine follows』から後の記述(上記コードの最後の数行)は、Cにおけるmain()関数内に記述されているコードを移植したもの。
ゲームを一つ移植してみて分かったことは
1)現在の仕様でちゃんとゲームが作れる
2)oyagame独特のコード記述がいくつかある
といったこと。最終目標は、中学生・高校生にゲームを作ってもらうことだが、まだまだそのレベルではなさそう。速いうちに、pygame互換のインターフェースを用意したほうが良いかもしれない。