プログラミング

aliens.vbs

2008年5月17日

先日配布の、oyagameを利用したゲーム、aliensのコードを少しばかり解説。

'''''''''''''''''''''''''''''''''''''
' 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互換のインターフェースを用意したほうが良いかもしれない。

コメント

コメントはありません

コメント送信