シューティングゲームの作り方3


1. 画像を表示する
2. 方向キーで画像を移動する
3. 自機クラスを作る
4. シーンクラスを作る

5. 弾を撃つ
6. 敵を作る
7. 当たり判定を作る

8. 敵に弾を撃たせる
9. 耐久力を持たせる
10. 敵を爆発させる
11. スコアを表示する

12. 背景を表示する
13. 表示優先順位を決める
14. 背景をスクロールさせる
15. 背景を多重スクロールさせる
16. 地面に当たり判定を付ける

17. タイトル画面を作る
18. スコアランキング画面を作る
19. ネームエントリー画面を作る
20. ランキングデータを読み書きする


8. 敵に弾を撃たせる

敵の弾用に新しいクラスを作ってもいいですが、ここではStg_bulletクラスを改造して
敵にも利用できるようにしてみましょう。
まず敵の弾を用意します。

e_bullet

Stg_bulletのinitializeの内容を弾の種類ごとに変数で分岐するように改造します。
  def initialize(x, y, type = 0)
    super()
    @type = type
    case @type
    when 0
      #自機が撃つ弾
      self.bitmap = RPG::Cache.picture("bullet") 
      @dx = 8
      @dy = 2
    when 1
      #敵が撃つ弾
      self.bitmap = RPG::Cache.picture("e_bullet") 
      @dx = 8
      @dy = 8
    end
    self.ox = self.bitmap.width / 2
    self.oy = self.bitmap.height / 2
    self.x = x
    self.y = y
  end

引数typeを追加しました。1が代入された時に敵の弾として処理します。
当たり判定は見た目よりやや小さくしてみます。
今はmoveで弾の動きを直接書いていますが、両方の弾に対応できるように
X軸方向、Y軸方向への移動量をそれぞれ変数で操作するようにします。
class Stg_bullet < Sprite
  #--------------------------------------------------------------------------
  # ● 公開インスタンス変数
  #--------------------------------------------------------------------------
  attr_reader :dx
  attr_reader :dy
  
  #--------------------------------------------------------------------------
  # ● オブジェクト初期化
  #--------------------------------------------------------------------------
  def initialize(x, y, type = 0)
    super()
    @type = type
    case @type
    when 0
      #自機が撃つ弾
      self.bitmap = RPG::Cache.picture("bullet") 
      @dx = 8
      @dy = 2
      @spx = 12
      @spy = 0
    when 1
      #敵が撃つ弾
      self.bitmap = RPG::Cache.picture("e_bullet") 
      @dx = 8
      @dy = 8
      @spx = -6
      @spy = 0
    end
    self.ox = self.bitmap.width / 2
    self.oy = self.bitmap.height / 2
    self.x = x
    self.y = y
  end
  
  #--------------------------------------------------------------------------
  # ● 弾の移動処理
  #--------------------------------------------------------------------------
  def move
    self.x += @spx
    self.y += @spy
    if screenout?
      self.dispose
      return true
    end
    return false
  end
  
  #--------------------------------------------------------------------------
  # ● 画面外判定
  #--------------------------------------------------------------------------
  def screenout?
    return true if self.x > 640 + self.bitmap.width / 2
    return true if self.x < 0 - self.bitmap.width / 2
    return true if self.y > 480 + self.bitmap.height / 2
    return true if self.y < 0 - self.bitmap.height / 2
    return false
  end
end

そしてStg_enemyに攻撃を判定するメソッドを作ります。
  #--------------------------------------------------------------------------
  # ● 攻撃判定
  #--------------------------------------------------------------------------
  def attack
    case @type
    when 1
      #20フレームに1回弾を発射
      if @count % 20 == 0
        return 1
      end
    end
    return 0
  end

attackメソッドを追加しました。数字を返り値にしていますが、これは弾のtypeを指します。

ではシーンクラスに敵の弾の処理を追加します。
自機への当たり判定もついでにやってしまいましょう。
class Scene_stg
  #--------------------------------------------------------------------------
  # ● メイン処理
  #--------------------------------------------------------------------------
  def main
    @shot = Array.new          #自機の弾を格納
    @shot_interval = 3         #弾の発射間隔(フレーム)
    @shot_count = 0            #弾の発射間隔をカウントする変数
    @enemy = Array.new         #敵を格納
    @enemyshot = Array.new     #敵の弾を格納
    Graphics.frame_count = 0   #フレームカウントの初期化
    
    #自機表示
    @player = Stg_player.new

    #メインループ
    loop do
      #自機の操作
      @player.move
      
      #自機の弾を発射
      player_shot()
      
      #敵キャラの生成
      make_enemy()
      
      #弾と敵の接触処理
      enemy_crash()
      
      #敵のショット
      enemy_shot()
      
      #敵の弾と自機の接触処理
      player_crash()
      
      #各種更新
      Input.update 
      Graphics.update
    end
  end
  
  #--------------------------------------------------------------------------
  # ● 自機のショット
  #--------------------------------------------------------------------------
  def player_shot
    if Input.press?(Input::A) and @shot.size < 10
      @shot_count += 1
      if @shot_count == @shot_interval
        @shot_count = 0
        @shot.push Stg_bullet.new(@player.x + 32, @player.y)
      end
    end
    #弾の移動&削除
    @shot.reject!{|i|i.move}
  end
  
  #--------------------------------------------------------------------------
  # ● 敵キャラの生成
  #--------------------------------------------------------------------------
  def make_enemy
    if Graphics.frame_count % 100 == 0
      @enemy.push Stg_enemy.new(672, rand(480),1)
    end
    if Graphics.frame_count % 250 == 0
      @enemy.push Stg_enemy.new(672, rand(480),2)
    end
    if Graphics.frame_count % 550 == 0
      @enemy.push Stg_enemy.new(672, rand(280)+100,3)
    end
    @enemy.reject!{|i|i.move}
  end
  
  #--------------------------------------------------------------------------
  # ● 弾と敵の接触処理
  #--------------------------------------------------------------------------
  def enemy_crash
    @shot.each{|i|
      @enemy.each{|j|
        if crash?(i, j)
          i.dispose
          j.dispose
          @enemy.delete(j)
          break
        end
      }
    }
    @shot.reject!{|i|i.disposed?}
  end
  
  #--------------------------------------------------------------------------
  # ● 当たり判定
  #--------------------------------------------------------------------------
  def crash?(obj1, obj2)
    if (obj1.x - obj2.x).abs < obj1.dx + obj2.dx and 
      (obj1.y - obj2.y).abs < obj1.dy + obj2.dy
      return true 
    else
      return false
    end
  end
  
  #--------------------------------------------------------------------------
  # ● 敵のショット
  #--------------------------------------------------------------------------
  def enemy_shot
    @enemy.each{|i|
      type = i.attack
      if type > 0
        @enemyshot.push Stg_bullet.new(i.x, i.y, type)
      end
    }
    @enemyshot.reject!{|i|i.move}
  end
  
  #--------------------------------------------------------------------------
  # ● 敵の弾と自機の接触処理
  #--------------------------------------------------------------------------
  def player_crash
    @enemyshot.each{|i|
      if crash?(i, @player)
        i.dispose
        @player.flash(Color.new(255, 255, 255, 255), 5) 
      end   
    }
    @enemyshot.reject!{|i|i.disposed?}
    @player.update
  end
end

まだゲームオーバー画面等を作っていないのでとりあえず今は当たっても
フラッシュするだけにしました。

さあこの調子で自機狙い弾も作ってみましょう!
と、行きたいところですが…。ここからいきなり難しくなります。
自機を狙うということは位置関係によっては微妙な斜め方向に撃ったりしなければなりません。
しかし設定できるのは1フレームにx座標とy座標を何ドット動かすかだけです。
さあ、どうやって計算しましょうか?ここからちょっと数学の知識が必要になります。
一応解説してみますが、よく分からない人は結果だけ見て「なるもんはなる」と思ってください。

まずは敵と自機の距離と方向を調べなくてはなりません。
自機の座標を(a,b)、敵の座標を(c,d)とします。
するとX軸成分の距離は|a-c|、Y軸成分の距離は|b-d|になります。
では自機と敵の距離はいくらになるでしょうか?
ピタゴラスの定理が使えます。
「直角三角形の斜辺の2乗は他の辺の2乗の和に等しい」てなものです。
これを利用すると
距離の2乗 = (a-c)^2 + (b-d)^2
よって
距離 = √ {(a-c)^2 + (b-d)^2}
となります。RGSSでの表現に直すと
距離 = Math.sqrt((a-c) ** 2 + (b-d) ** 2)
です。

以上をまとめると図のようになります。
math

問題はspの方向へ進ませたいのですが、直接は指定できないので
X軸方向とY軸方向にそれぞれ進ませるドット数を計算しなければいけません。

実はspとMath.sqrt ((a-c) ** 2 + (b-d) ** 2)の関係はspxとa-c、spyとb-dの関係と同じです。
つまり
sp : Math.sqrt ((a-c) ** 2 + (b-d) ** 2) = spx : a-c
sp : Math.sqrt ((a-c) ** 2 + (b-d) ** 2) = spy : b-d
が成立するわけです。
さあ、あとは比の計算です。spxとspyは次のようになります。
spx = sp * (a-c) / Math.sqrt ((a-c) ** 2+ (b-d) ** 2)
spy = sp * (b-d) / Math.sqrt ((a-c) ** 2+ (b-d) ** 2)
あとはspに1フレーム分の移動量を代入すれば自機狙い弾の完成です。

と、思いきや…

なんだか命中精度が低いような気がしませんか?
それもそのはずでx,yは常に整数値しか取らないのです。
毎フレーム小数部分が削られるのでズレが出てしまいます。
そこで小数値として確保する座標変数@fx,@fyを別に用意しましょう。
class Stg_bullet < Sprite
  #--------------------------------------------------------------------------
  # ● 公開インスタンス変数
  #--------------------------------------------------------------------------
  attr_reader :dx #横方向の当たり判定
  attr_reader :dy #縦方向の当たり判定
  
  #--------------------------------------------------------------------------
  # ● オブジェクト初期化
  #--------------------------------------------------------------------------
  def initialize(x, y, type = 0, target_x = 0, target_y = 0)
    super()
    @type = type
    case @type
    when 0
      #自機が撃つ弾
      self.bitmap = RPG::Cache.picture("bullet") 
      @dx = 8
      @dy = 4
      @spx = 12
      @spy = 0
    when 1
      #敵が撃つ弾・左に直進する
      self.bitmap = RPG::Cache.picture("e_bullet") 
      @dx = 8
      @dy = 8
      @spx = -6
      @spy = 0
    when 2
      #敵が撃つ弾・自機に向かって直進する
      self.bitmap = RPG::Cache.picture("e_bullet") 
      @dx = 8
      @dy = 8
      @spx = 6
      @spy = 6
      @spx = @spx*(target_x-x) / Math.sqrt((target_x-x)**2+(target_y-y)**2)
      @spy = @spy*(target_y-y) / Math.sqrt((target_x-x)**2+(target_y-y)**2)
    end
    self.ox = self.bitmap.width / 2
    self.oy = self.bitmap.height / 2
    self.x = x
    self.y = y
    @fx = x  #浮動少数x座標
    @fy = y  #浮動少数y座標
  end
  
  #--------------------------------------------------------------------------
  # ● 弾の移動処理
  #--------------------------------------------------------------------------
  def move
    #浮動少数座標に移動量を加算
    @fx += @spx
    @fy += @spy
    #浮動少数座標を座標に代入(座標には整数値が入る)
    self.x = @fx
    self.y = @fy
    #画面外に出たら消滅
    if screenout?
      self.dispose
      return true
    end
    return false
  end
  
  #--------------------------------------------------------------------------
  # ● 画面外判定
  #--------------------------------------------------------------------------
  def screenout?
    return true if self.x > 640 + self.bitmap.width / 2
    return true if self.x < 0 - self.bitmap.width / 2
    return true if self.y > 480 + self.bitmap.height / 2
    return true if self.y < 0 - self.bitmap.height / 2
    return false
  end
end

引数にtarget_x = 0, target_y = 0が追加しました。
呼び出すときに自機の座標@player.x, @player.yを代入すれば自機がターゲットになります。

meichu
当たりまくり…

それではついでにNウェイ弾と回転弾も追加してみましょう。
もう私には細かい解説はできませんので気になる方はその筋のサイトを訪ねてみてください。
  def initialize(x, y, type = 0, target_x = 0, target_y = 0, rad = 0, rot = 0)
    super()
    @type = type
    case @type
    when 0
      #自機が撃つ弾
      self.bitmap = RPG::Cache.picture("bullet") 
      @dx = 8
      @dy = 4
      @spx = 12
      @spy = 0
    when 1
      #敵が撃つ弾・左に直進する
      self.bitmap = RPG::Cache.picture("e_bullet") 
      @dx = 8
      @dy = 8
      @spx = -6
      @spy = 0
    when 2
      #敵が撃つ弾・自機に向かって直進する
      self.bitmap = RPG::Cache.picture("e_bullet") 
      @dx = 8
      @dy = 8
      @spx = 6
      @spy = 6
      @spx = @spx*(target_x-x) / Math.sqrt((target_x-x)**2+(target_y-y)**2)
      @spy = @spy*(target_y-y) / Math.sqrt((target_x-x)**2+(target_y-y)**2)
    when 3
      #敵が撃つ弾・Nウェイ弾
      self.bitmap = RPG::Cache.picture("e_bullet") 
      @dx = 8
      @dy = 8
      @spx = 6
      @spy = 6
      @spx = @spx*(target_x-x) / Math.sqrt((target_x-x)**2+(target_y-y)**2)
      @spy = @spy*(target_y-y) / Math.sqrt((target_x-x)**2+(target_y-y)**2)
      @spx = @spx * Math.cos(rad) - @spy * Math.sin(rad)
      @spy = @spx * Math.sin(rad) + @spy * Math.cos(rad)
    when 4
      #敵が撃つ弾・回転弾
      self.bitmap = RPG::Cache.picture("e_bullet") 
      @dx = 8
      @dy = 8
      @spx = 6
      @spy = 6
      @spx = @spx * Math.cos(rot * Math::PI / 180)
      @spy = @spy * Math.sin(rot * Math::PI / 180)
    end
    self.ox = self.bitmap.width / 2
    self.oy = self.bitmap.height / 2
    self.x = x
    self.y = y
    @fx = x
    @fy = y
  end

タイプ3はradに-1.0〜1.0の範囲で値を入れるとその角度だけ方向をずらすことができます。
次のようにすると自機を中心とした3方向を狙って撃ちます。
@enemyshot.push Stg_bullet.new(i.x, i.y, 3, @player.x, @player.y, -0.1)
@enemyshot.push Stg_bullet.new(i.x, i.y, 3, @player.x, @player.y, 0)
@enemyshot.push Stg_bullet.new(i.x, i.y, 3, @player.x, @player.y, 0.1)

タイプ4はrotに0〜360の値を入れて360度系で弾の移動方向を指定します。
次のようにすると敵を中心に12発の弾が円状に発射されます。
@enemyshot.push Stg_bullet.new(i.x, i.y, 4, 0,0,0,0)
@enemyshot.push Stg_bullet.new(i.x, i.y, 4, 0,0,0,30)
@enemyshot.push Stg_bullet.new(i.x, i.y, 4, 0,0,0,60)
@enemyshot.push Stg_bullet.new(i.x, i.y, 4, 0,0,0,90)
@enemyshot.push Stg_bullet.new(i.x, i.y, 4, 0,0,0,120)
@enemyshot.push Stg_bullet.new(i.x, i.y, 4, 0,0,0,150)
@enemyshot.push Stg_bullet.new(i.x, i.y, 4, 0,0,0,180)
@enemyshot.push Stg_bullet.new(i.x, i.y, 4, 0,0,0,210)
@enemyshot.push Stg_bullet.new(i.x, i.y, 4, 0,0,0,240)
@enemyshot.push Stg_bullet.new(i.x, i.y, 4, 0,0,0,270)
@enemyshot.push Stg_bullet.new(i.x, i.y, 4, 0,0,0,300)
@enemyshot.push Stg_bullet.new(i.x, i.y, 4, 0,0,0,330)

シーンクラスのenemy_shotメソッドに追加してみます。
  #--------------------------------------------------------------------------
  # ● 敵のショット
  #--------------------------------------------------------------------------
  def enemy_shot
    @enemy.each{|i|
      type = i.attack
      case type
      when 1
        @enemyshot.push Stg_bullet.new(i.x, i.y, 1)
      when 2
        @enemyshot.push Stg_bullet.new(i.x, i.y, 2,@player.x, @player.y)
      when 3
        @enemyshot.push Stg_bullet.new(i.x, i.y, 3, @player.x, @player.y, -0.1)
        @enemyshot.push Stg_bullet.new(i.x, i.y, 3, @player.x, @player.y, 0)
        @enemyshot.push Stg_bullet.new(i.x, i.y, 3, @player.x, @player.y, 0.1)
      when 4
        for rot in 0..11
          @enemyshot.push Stg_bullet.new(i.x, i.y, 4, 0,0,0,rot*30)
        end
      end
    }
    @enemyshot.reject!{|i|i.move}
  end

せっかくですから残った2種類の敵にこの攻撃を設定してみましょう。
  #--------------------------------------------------------------------------
  # ● 攻撃判定
  #--------------------------------------------------------------------------
  def attack
    case @type
    when 1
      return 2 if @count % 50 == 0
    when 2
      return 3 if @count % 50 == 0
    when 3
      return 4 if @count % 150 == 50
    end
    return 0
  end

一応これらがシューティングでの基本形だと思います。
それぞれを組み合わせることで多彩な攻撃が可能となるでしょう。



メニューへ


9. 耐久力を持たせる

固い敵を演出するために耐久力の概念を加えます。 要するにヒットポイントを作ります。
class Stg_enemy < Sprite
  #--------------------------------------------------------------------------
  # ● 公開インスタンス変数
  #--------------------------------------------------------------------------
  attr_reader :dx #横方向の当たり判定
  attr_reader :dy #縦方向の当たり判定
  
  #--------------------------------------------------------------------------
  # ● オブジェクト初期化
  #--------------------------------------------------------------------------
  def initialize(x, y, type)
    super()
    @type = type
    self.bitmap = RPG::Cache.picture("enemy"+"#{@type}") 
    self.ox = self.bitmap.width / 2
    self.oy = self.bitmap.height / 2
    self.x = x
    self.y = y
    case @type
    when 1
      @spx = -2
      @spy = 0
      @life = 5
    when 2
      @spx = -4
      @spy = 0
      @life = 7
    when 3
      @spx = -3
      @spy = 1
      @life = 9
    end
    @dx = 32
    @dy = 32
    @count = 0 #生存時間
  end
  
  #--------------------------------------------------------------------------
  # ● 敵キャラの移動処理
  #--------------------------------------------------------------------------
  def move
    #このupdateはフラッシュ処理に必要
    self.update
    move_select()
    self.x += @spx
    self.y += @spy
    #画面外に出たら消滅
    if screenout?
      self.dispose
      return true
    end
    return false
  end
  
  #--------------------------------------------------------------------------
  # ● 敵キャラの種類別移動
  #--------------------------------------------------------------------------
  def move_select
    @count += 1
    case @type
    when 2
      #途中で引き返す
      @spx = 4 if @count == 100
    when 3
      #ジグザグ移動
      @spy *= -1 if @count % 100 == 0
    end
  end
  
  #--------------------------------------------------------------------------
  # ● 画面外判定
  #--------------------------------------------------------------------------
  def screenout?
    return true if self.x > 640 + self.bitmap.width / 2
    return true if self.x < 0 - self.bitmap.width / 2
    return true if self.y > 480 + self.bitmap.height / 2
    return true if self.y < 0 - self.bitmap.height / 2
    return false
  end
  
  #--------------------------------------------------------------------------
  # ● 攻撃判定
  #--------------------------------------------------------------------------
  def attack
    case @type
    when 1
      return 2 if @count % 50 == 0
    when 2
      return 3 if @count % 50 == 0
    when 3
      return 4 if @count % 150 == 50
    end
    return 0
  end
  
  #--------------------------------------------------------------------------
  # ● 敵キャラのダメージ処理
  #--------------------------------------------------------------------------
  def damage
    @life -= 1
    #5フレームの間白くフラッシュ
    self.flash(Color.new(255, 255, 255, 255), 5) 
    if @life == 0
      return true
    end
    return false
  end
end

ダメージメソッドを追加しました。
これに合わせてシーンクラスの当たり判定部分を書き換えましょう。
  #--------------------------------------------------------------------------
  # ● 弾と敵の接触処理
  #--------------------------------------------------------------------------
  def enemy_crash
    @shot.each{|i|
      @enemy.each{|j|
        if crash?(i, j)
          i.dispose
          if j.damage
            j.dispose
            @enemy.delete(j)
          end
          break
        end
      }
    }
    @shot.reject!{|i|i.disposed?}
  end

もうこのくらいは問題ないでしょう。
自機の耐久も同じように作れますね。
class Stg_player < Sprite
  #--------------------------------------------------------------------------
  # ● 公開インスタンス変数
  #--------------------------------------------------------------------------
  attr_reader :dx   #横方向の当たり判定
  attr_reader :dy   #縦方向の当たり判定
  attr_reader :life #ライフ
  
  #--------------------------------------------------------------------------
  # ● オブジェクト初期化
  #--------------------------------------------------------------------------
  def initialize
    super()
    self.bitmap = RPG::Cache.picture("fighter") 
    #原点を画像の中心に設定
    self.ox = self.bitmap.width / 2
    self.oy = self.bitmap.height / 2
    self.x = 32
    self.y = 240
    @dx = 16
    @dy = 4
    @life = 5
    @muteki = 0 #無敵時間を計る
  end
  
  #--------------------------------------------------------------------------
  # ● 自機の移動処理
  #--------------------------------------------------------------------------
  def move
    move_x = 0  #横方向の移動量 
    move_y = 0  #縦方向の移動量 
    #入力されたキーによって画像の移動量を決める
    move_y = 8 if Input.press?(Input::DOWN) 
    move_x = -8 if Input.press?(Input::LEFT) 
    move_x = 8 if Input.press?(Input::RIGHT) 
    move_y = -8 if Input.press?(Input::UP) 
    #斜め入力のときは速度を落とす
    if move_x * move_y != 0
      move_x *= 0.7
      move_y *= 0.7
    end
    #移動量を座標に加算する
    self.x += move_x
    self.y += move_y
    #画面外に出たら座標を画面内に強制する
    self.x = 620 if self.x > 620
    self.x = 20 if self.x < 20
    self.y = 470 if self.y > 470
    self.y = 10 if self.y < 10
    #無敵時間があればカウントを減らす
    if @muteki > 0
      @muteki -= 1
      self.opacity = 255 if @muteki == 0
    end
  end
  
  #--------------------------------------------------------------------------
  # ● 自機のダメージ処理
  #--------------------------------------------------------------------------
  def damage
    #無敵状態でなければライフを減らす
    if @muteki == 0
      @life -= 1
      #無敵時間50フレーム
      @muteki = 50
      #自機を半透明にする
      self.opacity = 127
      if @life < 0
        @life = 0
        return true
      end
    end
    return false
  end

end

ついでに無敵時間を追加しました。
ダメージを受けると50フレームの間ダメージを受けません。
opacityは不透明度で、無敵の間は半透明になります。

シーンクラスでもここを呼ぶようにしましょう。
  #--------------------------------------------------------------------------
  # ● 敵の弾と自機の接触処理
  #--------------------------------------------------------------------------
  def player_crash
    @enemyshot.each{|i|
      if crash?(i, @player)
        @player.damage
        i.dispose
      end
    }
    @enemyshot.reject!{|i|i.disposed?}
    @player.update
  end

ライフ5としていますが、現段階ではまだ死亡処理は作らないでおきます。



メニューへ


10. 敵を爆発させる

敵がただ消えるのも味気ないので爆発させてみましょう。
爆発させるといっても、敵がいた場所にアニメーションを表示させるだけです。
というわけで爆発画像を用意しました。

burst1 burst2 burst3 burst4 burst5 burst6
burst1.png / burst2.png / burst3.png / burst4.png / burst5.png / burst6.png

では爆発クラスを作ってみましょう。
class Stg_burst < Sprite
  #--------------------------------------------------------------------------
  # ● オブジェクト初期化
  #--------------------------------------------------------------------------
  def initialize(x,y)
    super()
    self.bitmap = RPG::Cache.picture("burst6") 
    self.ox = self.bitmap.width / 2
    self.oy = self.bitmap.height / 2
    self.x = x
    self.y = y
    @time = 0
    #爆発の効果音
    Audio.se_play("Audio/SE/death",90)
  end
  
  #--------------------------------------------------------------------------
  # ● アニメーション進行
  #--------------------------------------------------------------------------
  def move
    #2フレームに1回実行
    if Graphics.frame_count % 2 == 0
      @time += 1
      if @time == 7
        self.dispose
        return true
      end
      self.bitmap = RPG::Cache.picture("burst"+"#{@time}")
    end
    return false
  end
end

def moveでは2フレームに1回のペースで画像を変えています。

ここでは効果音を出すようにしました。
音楽や効果音関係はAudioモジュールを参照してください。
"ヘルプ→RGSSリファレンス→ゲームライブラリ→RGSS 組み込みモジュール→Audio"
ちなみに"death"はこれです。
XPのRTPにはどうも気軽に使える効果音がないので自作しました。
効果音の作成にはKanaWaveやSWaveがお勧めです。
結構楽しいですよ。

それではくどいようですがシーンクラスに処理を追加します。
ループの前に@burst = Array.newをやってから、当たり判定の部分を書き換えます。
  #--------------------------------------------------------------------------
  # ● 弾と敵の接触処理
  #--------------------------------------------------------------------------
  def enemy_crash
    @shot.each{|i|
      @enemy.each{|j|
        if crash?(i, j)
          i.dispose
          if j.damage
            @burst.push Stg_burst.new(j.x, j.y)
            j.dispose
            @enemy.delete(j)
          end
          break
        end
      }
    }
    @shot.reject!{|i|i.disposed?}
    @burst.reject!{|i|i.move}
  end

おしまい♪

爆発クラスは汎用アニメーションクラスとして使ってもいいですね。



メニューへ


11. スコアを表示する

シューティングのお約束、スコア表示です。
ここまでくればもう立派なゲームですね。

まずはStg_enemyクラスにスコア用変数を作って、外部から読めるようにします。
attr_reader :score #点数

個々の敵キャラの設定は@lifeと同じように@score = 100などと代入すればOKです。

シーンクラスに表示部分も作りましょう。
class Scene_stg
  #--------------------------------------------------------------------------
  # ● メイン処理
  #--------------------------------------------------------------------------
  def main
    @shot = Array.new          #自機の弾を格納
    @shot_interval = 3         #弾の発射間隔(フレーム)
    @shot_count = 0            #弾の発射間隔をカウントする変数
    @enemy = Array.new         #敵を格納
    @enemyshot = Array.new     #敵の弾を格納
    @burst = Array.new         #爆発アニメーションを格納
    Graphics.frame_count = 0   #フレームカウントの初期化
    
    #スコアの表示
    @score = 0
    @prescore = 0
    @scoreview = Sprite.new
    @scoreview.bitmap = Bitmap.new(96,32)
    @scoreview.bitmap.draw_text(0, 0, 96, 32, "0" ,2)
    
    #自機表示
    @player = Stg_player.new

    #メインループ
    loop do
      #自機の操作
      @player.move
      
      #自機の弾を発射
      player_shot()
      
      #敵キャラの生成
      make_enemy()
      
      #弾と敵の接触処理
      enemy_crash()
      
      #敵のショット
      enemy_shot()
      
      #敵の弾と自機の接触処理
      player_crash()
      
      #スコア書き換え
      change_score()
      
      #各種更新
      Input.update 
      Graphics.update
    end
  end
  
  #--------------------------------------------------------------------------
  # ● 自機のショット
  #--------------------------------------------------------------------------
  def player_shot
    if Input.press?(Input::A) and @shot.size < 10
      @shot_count += 1
      if @shot_count == @shot_interval
        @shot_count = 0
        @shot.push Stg_bullet.new(@player.x + 32, @player.y)
      end
    end
    #弾の移動&削除
    @shot.reject!{|i|i.move}
  end
  
  #--------------------------------------------------------------------------
  # ● 敵キャラの生成
  #--------------------------------------------------------------------------
  def make_enemy
    if Graphics.frame_count % 100 == 0
      @enemy.push Stg_enemy.new(672, rand(480),1)
    end
    if Graphics.frame_count % 250 == 0
      @enemy.push Stg_enemy.new(672, rand(480),2)
    end
    if Graphics.frame_count % 550 == 0
      @enemy.push Stg_enemy.new(672, rand(280)+100,3)
    end
    @enemy.reject!{|i|i.move}
  end
  
  #--------------------------------------------------------------------------
  # ● 弾と敵の接触処理
  #--------------------------------------------------------------------------
  def enemy_crash
    @shot.each{|i|
      @enemy.each{|j|
        if crash?(i, j)
          i.dispose
          if j.damage
            @score += j.score
            @burst.push Stg_burst.new(j.x, j.y)
            j.dispose
            @enemy.delete(j)
          end
          break
        end
      }
    }
    @shot.reject!{|i|i.disposed?}
    @burst.reject!{|i|i.move}
  end
  
  #--------------------------------------------------------------------------
  # ● 当たり判定
  #--------------------------------------------------------------------------
  def crash?(obj1, obj2)
    if (obj1.x - obj2.x).abs < obj1.dx + obj2.dx and 
      (obj1.y - obj2.y).abs < obj1.dy + obj2.dy
      return true 
    else
      return false
    end
  end
  
  #--------------------------------------------------------------------------
  # ● 敵のショット
  #--------------------------------------------------------------------------
  def enemy_shot
    @enemy.each{|i|
      type = i.attack
      case type
      when 1
        @enemyshot.push Stg_bullet.new(i.x, i.y, 1)
      when 2
        @enemyshot.push Stg_bullet.new(i.x, i.y, 2,@player.x, @player.y)
      when 3
        @enemyshot.push Stg_bullet.new(i.x, i.y, 3, @player.x, @player.y, -0.1)
        @enemyshot.push Stg_bullet.new(i.x, i.y, 3, @player.x, @player.y, 0)
        @enemyshot.push Stg_bullet.new(i.x, i.y, 3, @player.x, @player.y, 0.1)
      when 4
        for rot in 0..11
          @enemyshot.push Stg_bullet.new(i.x, i.y, 4, 0,0,0,rot*30)
        end
      end
    }
    @enemyshot.reject!{|i|i.move}
  end
  
  #--------------------------------------------------------------------------
  # ● 敵の弾と自機の接触処理
  #--------------------------------------------------------------------------
  def player_crash
    @enemyshot.each{|i|
      if crash?(i, @player)
        @player.damage
        i.dispose
      end   
    }
    @enemyshot.reject!{|i|i.disposed?}
    @player.update
  end
  
  #--------------------------------------------------------------------------
  # ● スコア書き換え
  #--------------------------------------------------------------------------
  def change_score
    if @score != @prescore
      @scoreview.bitmap.clear
      @scoreview.bitmap.draw_text(0, 0, 96, 32, "#{@score}" ,2)
      @prescore = @score
    end
  end
end

scoreとprescoreの2つの変数を使って値が変わったときだけ
文字を書き換えるようにしています。
こうしておかないと毎フレーム文字画像を埋め込む処理がされて重くなってしまうからです。



メニューへ

次のページへ
前のページへ
一覧へ戻る
トップページへ戻る



更新日時:2006/12/18 18:00
管理人:すっぴぃ ◆ADGYPSYxII