「敵キャラを作成する」の編集履歴(バックアップ)一覧に戻る

敵キャラを作成する - (2009/05/04 (月) 08:56:23) のソース

入門編・10
シューティングゲームもどきを作る
*敵キャラを表示する1・敵キャラクラスの設計
今回から、敵キャラを作成していきます。が、この部分はかなり管理人のオリジナルな色が強く、
この方法が効率的であるかかなり疑問があります。
詳しい方、よろしければご教授下さいませ……!

**敵キャラに求められるものと、配列の使い方
自機と敵の一番の違いは(特にプログラム上の違いは)、敵キャラは複数存在する、ということです。
プログラムをやったことのある方なら聞いたことがあると思いますが、敵キャラを作る際には
敵キャラの『配列』を作ることが好ましいのです。
配列というのは、(1,1,2,4)のように、&bold(){要素(ここでは数字)を、いくつか並べたもの}。
例えば、(1,1,2,4)は、要素数4の配列であると言い、3番目の要素は2である、というような使い方をします。
そして、配列の何が便利かというと、配列にすると、&bold(){通し番号を使って繰り返しで効率的に処理できる}という一点が最も重要です。
例えば、敵キャラを5体出す場合、配列を使わないと、処理内容としては
>(敵キャラAの処理)
>(敵キャラBの処理)
>(敵キャラCの処理)
>(敵キャラDの処理)
>(敵キャラEの処理)
と、敵キャラの数だけ繰り返して記述する必要があります。これでは、記述が面倒だし、敵キャラ数が変わった場合への対応も明らかに難しくなります。
では、配列を使うとどうなるでしょうか。敵キャラが5体なら、次のような配列を作ります。
&bold(){(敵キャラ[0],敵キャラ[1],敵キャラ[2],敵キャラ[3],敵キャラ[4],)}
[]の中の数字は通し番号。要素には、それぞれ敵キャラクラスのインスタンスが入ります。
そして、処理内容はこうです。
>nを0から4(敵の数-1)まで変化させて、以下の処理を行う。
>  敵キャラ[n]の処理
>ここまでを繰り返す
日本語で書いていますが、こんな感じです。まず、n=0、すなわち敵キャラ[0]の処理が行われ、『ここまで繰り返す』まで行ったら次はn=1となり、敵キャラ[1]の処理。と、敵の数だけ繰り返し、結果、先ほどの単に敵の数だけ処理を書いた場合と同じになります。
&bold(){冗長な繰り返しが無くなり、さらに敵の数をいくつに変えても応用可能です。}
敵キャラの数が100体とかになった場合、どちらが有利かは明らかでしょう。
というわけで、今回、敵キャラクラスの他に、敵キャラの配列を管理する、敵全体クラスを作成します。このクラスには、繰り返し用に、敵が全部で何人いるかといった情報も持たせるようにします。
では、クラスを設計しましょう。

**敵キャラクラスを作る
自機と基本的に同じなので、ここでは簡単にいきましょう。
>#==============================================================================
># ■ Game_Ememy
>#------------------------------------------------------------------------------
>#  シューティングゲームの敵キャラを扱うクラスです。
>#==============================================================================
>
>class Game_Enemy
>  #--------------------------------------------------------------------------
>  # ● 公開インスタンス変数
>  #--------------------------------------------------------------------------
>  attr_accessor   :x                        # 左上 X 座標
>  attr_accessor   :y                        # 左上 Y 座標
>  attr_accessor   :alive                    # 生きているか否か
>
>  #--------------------------------------------------------------------------
>  # ● 定数
>  #--------------------------------------------------------------------------
>  WND_X = 544 #ウィンドウのサイズ
>  WND_Y = 416
>  SIZE_X = 32 #敵キャラのサイズ
>  SIZE_Y = 32
>  #--------------------------------------------------------------------------
>  # ● オブジェクト初期化
>  #--------------------------------------------------------------------------
>  def initialize(x,y)
>    @x=x
>    @y=y
>    @alive = true
>  end
>  #--------------------------------------------------------------------------
>  # ● 更新
>  #--------------------------------------------------------------------------
>  def update
>  end
>end
まあ、こんなところでしょうか。インスタンス変数には、自機と同様x座標、y座標のほか、生きているか否かを表す@aliveを用意しました。ここに入る値はtrue(正しい)かfalse(間違っている)で、生きている時trueになります。
これにより、例えばfalseの時は表示しない、あたり判定が無いという使い方をします。
また、敵の位置は敵ごとに自由に決めたいので、initializeの際に座標を渡し、それによって位置が決まるようにしました。
updateは後で必要になりそうだから作っただけで、今はとりあえず何もしません。

**敵キャラのスプライトのクラスを作る
これも、自機の時と同様です。
>#==============================================================================
># ■ Sprite_Enemy
>#------------------------------------------------------------------------------
>#  敵キャラ表示用のスプライトです。Game_Enemy クラスのインスタンスを
># 監視し、スプライトの状態を自動的に変化させます。
>#==============================================================================
>
>class Sprite_Enemy < Sprite_Base
>  #--------------------------------------------------------------------------
>  # ● 公開インスタンス変数
>  #--------------------------------------------------------------------------
>  attr_accessor :enemy
>  #--------------------------------------------------------------------------
>  # ● オブジェクト初期化
>  #     viewport  : ビューポート
>  #     z         : スプライト・ビューポートのz座標
>  #     myMachine : キャラクター (Game_Enemy)
>  #--------------------------------------------------------------------------
>  def initialize(viewport,z,enemy)
>    super(viewport)
>    @enemy = enemy
>    self.bitmap = Cache.character("Enemy")
>    self.z=z
>    self.viewport.z=z
>    update
>  end
>  #--------------------------------------------------------------------------
>  # ● 解放
>  #--------------------------------------------------------------------------
>  def dispose
>    super
>  end
>  #--------------------------------------------------------------------------
>  # ● フレーム更新
>  #--------------------------------------------------------------------------
>   def update
>      self.x = @enemy.x
>      self.y = @enemy.y
>  end
>end
というか、MyMachineがEnemyになっただけです。あ、あとグラフィックがenemyに。よかったら下の画像を使って下さい。名前をEnemyとしてインポートして下さい。
#ref(Enemy.bmp)
ここでは、簡略のため大きさを32×32と定数で決めてしまっているので、オリジナルのものを使う場合もこのサイズでお願いします。
大きさの違う色んな敵を出す場合は、敵キャラの大きさを変数にしたり、画像のファイル名をnewの際に渡したりする必要があるでしょう。あるいは、敵の種類ごとに種類idを振って、それで管理するとか。
使い方も、基本的には同じです。ただ、敵キャラごとにビューポートを作成するのは面倒なので、敵キャラのビューポートは全敵キャラで共通のものを使います。
敵キャラのz座標も、敵キャラごとに変えずに同じ値にします。この場合、後に作成したオブジェクトほど上に表示されます。

**敵全体クラスを作る
さて、ここが新しい部分です。敵全体クラスを作りましょう。
必要最低限のものは、敵キャラの配列と、敵キャラの数、すなわち配列の要素数です。
今回は、便利かと思って生きている敵の数も一応インスタンス変数にしました。
クラス名は、Game_Enemiesとしました。
>#==============================================================================
># ■ Game_Ememies
>#------------------------------------------------------------------------------
>#  シューティングゲームの敵キャラ全体を扱うクラスです。
>#==============================================================================
>
>class Game_Enemies
>  #--------------------------------------------------------------------------
>  # ● 公開インスタンス変数
>  #--------------------------------------------------------------------------
>  attr_accessor   :enemy_num                 # 敵の総数
>  attr_accessor   :alive_num                 # 存在している(alive)敵の数
>  attr_accessor   :enemies                   # Game_Enemyの配列
>  attr_accessor   ;s_enemies                 # Sprite_Enemyの配列
>  #--------------------------------------------------------------------------
>  # ● オブジェクト初期化
>  #--------------------------------------------------------------------------
>  def initialize(enemy_num) # 敵キャラの総数をenemy_numとして渡す
>    @enemy_num = enemy_num
>    @alive_num = enemy_num
>    @enemies = Array.new(enemy_num)
>    @s_enemies = Array.new(enemy_num)
>    viewport = Viewport.new(0,0,544,416)
>    viewport.z = 200
>    for i in 0..enemy_num-1 do
>      @enemies[i] = Game_Enemy.new(200+rand(312),rand(384))
>      @s_enemies[i] = Sprite_Enemy.new(viewport,200,@enemies[i])
>    end
>  end
>  #--------------------------------------------------------------------------
>  # ● 更新
>  #--------------------------------------------------------------------------
>  def update
>    for i in 0..enemy_num-1 do
>      @enemies[i].update
>      @s_enemies[i].update
>    end
>  end
>end
さて、微妙に複雑ですが一つずつ見ていきましょう。
まずはinitialize。
newの際は、数字を渡し、これを敵の総数とします。本当は、変な値が入っていた時にチェックできたりするといいのですが、
今回は面倒なのでそれはしていません。余力があれば挑戦しましょう。
そして、最初敵は全員生きているものとして、enemy_numとalive_numに渡した数、つまり敵の数を入れます。
>    @enemies = Array.new(enemy_num)
>    @s_enemies = Array.new(enemy_num)
問題が、これです。このArrayというのが、配列という意味です。Array.newによって、配列を作り出しています。()の中に渡すのは、想像できると思いますが、敵キャラの数。敵キャラの配列の要素数ですね。これによって、enemy_numの要素を持つ配列が作られます。@s_enemiesも同様で、こちらは敵キャラのスプライトを配列にしています。
&bold(){なお、この段階では要素は何も入っておらず、これが敵キャラの配列であることすら未確定です。}
続いて、ビューポートを作成し、このz座標を200としています。この値は敵キャラ全体で共通になります。
>    for i in 0..enemy_num-1 do
>      @enemies[i] = Game_Enemy.new(200+rand(312),rand(384))
>      @s_enemies[i] = Sprite_Enemy.new(viewport,200,@enemies[i])
>    end
続くこれが、配列のさっき言った『効率のいい繰り返し』の記述です。
先ほど、配列にはまだ何も入っていないと書きましたが、ここで配列に中身を入れています。すなわち、newによって作成した、Game_EnemyクラスのインスタンスとSprite_Enemyクラスのインスタンス。
iが、0からenemy_num-1まで変化して、この2行を実行します。
"enemy_num-1"になるのは、配列の添え字が0から始まるため、最後の要素の添え字が全体の要素数の数字より1小さくなることによります。
1行目で敵キャラを、2行目で敵キャラのスプライトを、それぞれ割り当てている(自機のcreate_myMachineの場合と似ている)わけですが、微妙に見慣れないものがあります。それは、rand(312)など、rand(数字)という命令。
これは、乱数です。実行のたびに、0以上数字未満(rand(100)なら0から99)の値を返します。
Game_Enemyのinitializeでは、敵の位置を渡すことにしていましたね。
つまり、ここでは敵の位置を乱数を使って決め、配置しているのです(x座標では、定数に乱数を足し、ある程度右の位置に行くようにしています)。
繰り返しの度にrandの値は変わるので、敵にはそれぞれ別のx座標、y座標が割り当てられることになります。
>  def update
>    for i in 0..enemy_num-1 do
>      @enemies[i].update
>      @s_enemies[i].update
>    end
>  end
updateの中でも繰り返しが使われています。&italic(){(誤りがあったため、4行目を追記しました。)}
敵全体クラスのインスタンスのupdateを行うことで、そこに所属する敵全てがupdateされます。
シーン中の記述は、敵全体のupdateのみを行えばいいことになります。

では、次回この敵キャラのクラスをシーンに組み込み、敵キャラを表示してみましょう。

----
[[前へ>入力を受け取る]]・[[次へ>敵キャラを作成する2]]
----
#comment_num2(size=40,vsize=4,num=20,logpage=コメント一覧)
目安箱バナー