Unityに関連する記事です

 前回の処理に続けて、今度はプレイヤーの攻撃の処理について、処理を考え追加します。



 以下の内容で順番に実装を進めていきます。

手順12 ープレイヤーの攻撃処理ー
19.プレイヤーの攻撃処理



 新しい学習内容は、以下の通りです。

 ・プロパティ
 ・クラス内に enum を作成する(入れ子構造)
 ・while 文とコルーチンメソッドを利用したループ処理
 ・ローカル関数



19.プレイヤーの攻撃処理

1.設計


 enumを利用したプレイヤーの現在の状態の管理方法について設計します。

 PlayerController スクリプトを修正して、プレイヤーの現在の状態を管理する enum である PlayerState を新しく作成します。

 この PlayerState の列挙子には Move や Battle_Before といった、プレイヤーのゲーム内での現在の状態を表現する値を用意し、それを管理用の変数に代入して管理します。
変数により管理することで、いずれかの1つの列挙子の状態のみを保持することになるため、この機能を利用することで、プレイヤーの現在の状態を一元化して管理できます。
(複数の異なるプレイヤーの状態が重なることがなくなります。例えば、Move と Reslut という列挙子の状態に一緒にならないため、状態が重複する問題を防げます)


 プレイヤーの現在の状態の制御処理は以下の通りです。すべて PlayerController スクリプト内で制御を行います。

1.Update メソッド内に処理を追加し、PlayerState が Move 以外の状態ではメソッド内の処理(移動用のキー入力処理など)が実行されないように停止する制御処理を追加する
   ↓
2.FixedUpdate メソッド内に処理を追加し、PlayerStateが Move以外の状態ではメソッド内の処理(移動処理など)が実行されないように停止する制御処理を追加する
   ↓
3.AutoBattle メソッド内の処理を追加し、バトルの開始に合させて PlayerState を Battle_Before に切り替える。この状態の間、プレイヤーは攻撃を繰り返すように制御する
  障害物の Hp が 0 以下になったら PlayerState を Result に切り替える。この状態になることでプレイヤーは攻撃を終了し、バトル後の処理に移行する
  その後、再度 PlayerState を Move に切り替える
   ↓
4.PlayerState が Move になったので、停止していた Update メソッドと FixedUpdate メソッド内の処理が動き始める
   ↓
5.これを繰り返す

 このような制御を行うようにします。



2.ObstacleBase スクリプトを修正する


 障害物に耐久力・体力の概念を与えます。今回は hp という名前の int 型の変数を作成し、この値を障害物の耐久力として利用します。
変数の名前は任意ですが、その変数をプログラム内において、どういった役割として利用するかを設計した上で名称を考えると分かりやすくなります。

 また hp 変数は private 修飾子として外部のクラスからは直接アクセスできない状態にしておきます。
代わりにプロパティを作成し、プロパティを通じて、hp 変数へのアクセスを行うように設計します。

 ただし、インスペクターより、値を任意に入力・変更・確認できるようにしておくため、hp 変数には SerializeField 属性を一時的に付与しておきます
こうすることにより、デバッグを行いやすくなります。
正式な運用が始まったら、SerializeField 属性は削除し、private 修飾子のみの変数とします。

 オブジェクト指向プログラミングにはカプセル化という概念があり、なるべく変数は1つのクラス内でのみアクセスして管理できるように心がけます。
ただし、外部のクラスに対して情報を提供したい状況もありますので、そういったケースに対応するためにプロパティが用意されています。


ObstacleBase.cs

 <= クリックすると開きます。


 スクリプトを修正したら、セーブを行います。



 スクリプトを修正したので、このスクリプトがアタッチされている障害物のゲームオブジェクトをヒエラルキーで選択して確認します。

 インスペクターより ObstacleBase スクリプトを確認すると、Hp 変数が追加されています。
この値については、最後のデバッグの段階で入力しますので、現時点では 0 のままで問題ありません。


<インスペクター画像>



3.<プロパティ>


<プロパティ>

 プロパティとは、クラス外部から見るとメンバー変数のように振る舞いクラス内部から見るとメソッドのように振る舞う機能です。
そのため、実装状態(private修飾子のまま)を変更することなく、外部クラスへの参照・変更を行える機能であるため、扱いを覚えておくと非常に便利です。

 この特性より、プロパティを作成する場合、多くは private 修飾子の変数の情報を扱うために作成します
今回の場合は、ObstacleBase クラスの hp 変数に対してのプロパティを作成します

<プロパティの作成>
    private int hp;

  /// <summary>
    /// hp のプロパティ
    /// </summary>
    public int Hp
    {
        get {
            return hp;
        }
        set {
            hp = value;
        } 
    }

 上記のようなプロパティを作成することで、private 修飾子で宣言している hp 変数を
プロパティを利用することによって、外部クラスから参照出来るようにしています。

 プロパティは public 修飾子ではありますが、インスペクターには表示されません
そのためデバッグ作業の効率化のため、hp 変数に SerializeField 属性を付与して、private である情報をインスペクターに表示してもよいでしょう。



 private 修飾子にて宣言フィールドで宣言した変数については、外部のクラスからは参照・変更を行うことが出来ません
このとき参照を行いたい場合には、変数の修飾子を public 修飾子に変更して対応するのではなく
プロパティの持つ get キーワードを利用して、戻り値を利用して private 修飾子の変数を外部クラスに参照させることで対応することが出来ます。

 こういった機能を知っているかどうかで設計部分が大きく変わります
色々な機能を知っていれば、例えば、今回はプロパティを使おうか、どうしようか、という選択肢が増えますが、知らなければ選択肢自体が生まれません。

 設計の引き出しを広げる上でも、新しい技術を覚えること、常に学習する意欲と向上心を持つことがプログラム学習のポイントです



 プロパティを利用した設計により、public ではない変数を外部クラスで利用できるようにします。
外部からプロパティを呼び出して戻り値を参照する処理のことをゲッター(getter)と呼びます。

  get {
      return hp;
  }



 同様に、private 修飾子の値を外部クラスより変更したい場合には、プロパティを仲介する手法を使って書き換える処理を実装出来ます。
こちらの処理は set キーワードを利用して処理を記述します。そのため、この処理をセッター(setter)と呼びます。

 set キーワード内には呼び出し元から value の値で情報が届いていますので、value の値を利用することで private 修飾子の変数に代入処理を行うことが出来ます。

  set {
      hp = value;
  }

 この処理を外部のクラスから実行する場合には、参照する場合と同じで「クラスの変数.プロパティ名」と記述することで値の書き換えが可能です。

 get、set キーワードはどちらか片方だけでも記述できます。また、記述する順番も任意です。set から書き始めることも出来ます。
またプロパティ内部に条件式を用意して、その結果に合わせて処理を変更する記述も出来ます


参考サイト
未確認飛行 C 様
プロパティ
https://ufcpp.net/study/csharp/oo_property.html

FEnetインフラ様 テックブログ
C#のプロパティを使いこなそう!さまざまな実装方法を紹介
https://www.fenet.jp/infla/column/technology/c%E3%...


4.PlayerController スクリプトを修正する


 PlayerState の enum を作成し、プレイヤーの状態について種類の登録を行います。
また、 PlayerState 型の変数も作成し、現在の状態を管理出来るようにします。

 作成した PlayerState を利用した制御の処理を追加し、プレイヤーの攻撃の処理、
および、攻撃の間は移動やキー入力を制限する制御の処理を追加します。
 
 どういった処理が、どのタイミングで実行されれば制御として成立するのかを考えてから、処理の内容を確認して実装してください。


PlayerController.cs

 <= クリックすると開きます。


 スクリプトを修正したらセーブをします。


5.<クラス内に enum を作成する(入れ子構造)>


 enum(イニューム) はゲーム内に登場させたい種類の情報を、列挙子(れっきょし)という形で種類を作成できます。
今回は、プレイヤーの状態の種類という情報PlayerState という名前で作成し、その中にプレイヤーの状態の種類を登録しておきます。
これは追加可能な情報ですので、先々にプレイヤーの状態の種類が増えても対応できます

 enum 型の宣言は1つのスクリプト・ファイルに宣言することもできますし、クラス内に宣言する(入れ子構造といいます)ことも出来ます。
今回は PlayerController クラス内に入れ子構造で PlayerState 型の enum の宣言を行っています。


PlayerController.cs
using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public class PlayerController : MonoBehaviour
{
  // 他の変数の宣言


    /// <summary>
    /// プレイヤーの状態の種類
    /// </summary>
    public enum PlayerState {
        Move,
        Battle_Before,
        Battle_After,
        Result,
        Info,
        GameUp
    }

    〜省略〜

}

 ここで宣言した情報は、新しく PlayerState 型の変数を作成することで利用できるようになります。


  public PlayerState currentPlayerstate;

 この変数には、PlayerState 型に登録されている列挙子のうち、いずれか1つだけが代入されます。
例えば、Move と Result が一緒になり重複してしまうケースを避けることが出来ます。



 2つ以上の情報を管理する場合には、enum でその種類を登録しておくことをおすすめします
enum を利用する場合、その登録してある列挙子からしか情報を指定できませんので、
例えば、文字列と異なり、指定に際して打ち間違えが発生しませんので、不備の値が入ることも防ぐことが出来ます。

 ゲームの内容に応じた enum を考えて作成して運用します
プレイヤーのバフ・デバフ用(毒、混乱、痺れとか)、アイテムの種類(消耗品、武器、防具、など)、
ゲームの状態管理(ゲーム開始前、ゲーム中、ゲーム終了)など、非常に応用が利く機能です。



 なお enum では各列挙子に自動的に整数の番号が与えられます一番上から 0 で連番になっています
今回の場合であれば、Talk には 0、Search には 1 の数字が与えられています。

 この番号は見えない情報ですが、列挙子を int 型にキャストを行うことで取得して利用出来ます
下記の例の場合、eventValue には 0 が代入されます。

<enum の列挙子のキャスト>
int  value = (int)PlayerState.Move;

 また、列挙子の宣言時に数字を指定して代入することも可能です。その場合には連番ではなく、指定した数値を取得出来ます。

<数字の代入の例(今回この方式は利用しません)>
EnemyType.cs
public enum PlayerState {
    Move = 10,
    Result = 5,
}

 上記のように代入されている場合には、列挙子を int 型にキャストすると、代入してある値が取得出来ます。
今回は数字の代入は行っていませんので一番上の列挙子には 0 から順番に採番されています。


6.<while 文とコルーチンメソッドを利用したループ処理>


 while(ホワイル) 文は反復処理と呼ばれる処理です。条件を満たしている限り、繰り返し処理を行います。

 今回のケースでは条件が「currentPlayerState の値が Battle_Before の間」です。
よって、currentPlayerState の値が Battle_Before 以外の値になるまではずっと繰り返されて終わることのない処理になります。 

 大きく分けて、for 文による繰り返しの処理は繰り返す回数が決まっている場合に利用し、
繰り返しの処理を回数ではなく条件によって設定したい場合には while 文を利用します



 while文によるループ処理はコルーチンメソッド内でも記述することができます。この場合には何秒後にループする、といった柔軟な書式が可能になります。
yield return null を利用し、1フレームだけ待機してループするという処理も実装できます。

 なお、while文は条件式を誤ると無限ループに入ってしまってUnityエディターが停止してしまいます。
こうなると再起動しなければならなくなりますので、while文を使用する際には注意してください。

    // while 文は 条件式 を満たす限りループする。ここでは typeCount 変数の値が 0 よりも大きい間は while 内の処理が繰り返し実行される
    while (currentPlayerState == PlayerState.Battle_Before) {
	    
        // 処理を記述する
 
    AttackPlayer();
			
        // 処理を 0.25 秒待機させて再開する
        yield return new WaitForSeconds(0.25f);
    }

    // while文の外に処理が記述されていた場合には、上記のwhile文の処理が終了してからでないと処理が実行されない
}


 今回実装している while 文内の処理は2つあり、1つは AttackPlayer() メソッドの実行処理です。
プレイヤーによる障害物への攻撃を実行する制御を行っています。

 もう1つは yield による中断(待機・遅延)の処理です。 yield return new WaitForSeconds(0.25f) により、0.25 秒だけ処理を中断します。

 そのため、この while 文による繰り返しの処理は次のような挙動になります。

 1.while 文の条件を満たしているか、確認する。満たしていない場合には、処理を終了する
 2.条件を満たしている場合には、while 文内の処理を実行する。AttackPlayer メソッドを実行する
 3.0.25 秒待つ
 4.【1】の処理に戻る

 Update メソッドを利用していませんが、while 文による反復処理により、しっかりと一定の間隔で繰り返し攻撃を処理を実装することが出来ました。

 なお、while 文では処理を中断する処理を挟まないと繰り返しの処理が止まらなくなって、Unity のエディターが動かなくなります
こうなってしまうと再起動するしかなくなりますので、while 文は特に気を付けて作業してください


参考サイト
Unity 公式マニュアル
コルーチン
未確認飛行 C 様
反復処理
@kwst様
コルーチンの初歩的な使い方【Unity, 初心者】


 他にも自動的に攻撃を行うようにする処理は考えられます。色々な処理を試しておくことで処理の引き出しが広がり、
より多くの処理を実装できるようになりますので、いつも使っている処理だけではなく、新しい技術も取り入れていきましょう。


7.<ローカル関数>


 メソッド内には、修飾子を持たないメソッドを入れ子として宣言(作成)することができます。
このような入れ子になっているプライベートなメソッドをローカル関数といいます。

<参考サイト>
MicroSoft
ローカル関数 (C# プログラミング ガイド)
https://docs.microsoft.com/ja-jp/dotnet/csharp/pro...



 ローカル関数は、入れ子として宣言しているメソッド(親メソッド)内でのみ、呼び出しの命令が可能です。

 利用する主なケースとしては、クラス内での呼び出し命令が1つであるメソッド(1箇所からしか呼び出し命令がないメソッド)がローカル関数として宣言するメソッドの対象となります。
また、ローカル関数は処理を隠蔽することにもなりますので、通常のメソッドよりも内部的な処理を記述することができ、スコープが短くなることで、エラーの特定が早い段階で判断できるようになります。

 今回は、AttackPlayer メソッドを AutoBattle メソッド内に宣言して作成しています。
つまり、AutoBattle メソッドが親メソッドAttackPlayer メソッドが入れ子メソッドです。
そのため、この AttackPlayer メソッドは、AutoBattle メソッド内からのみ、呼び出し命令を行うことができます。



 本来 AttackPlayer メソッドは、普通のメソッドのように記述できますが、すべての処理を書いてから精査した際、
呼び出し命令が AutoBattle メソッドから存在していないため、ローカル関数として宣言しています。

 ローカル関数は、呼び出し命令と、関数を宣言している場所が同じメソッド内にあるため、
より処理をコンパクトにまとめて、スコープを狭くすることで、見やすく、管理しやすいメソッドとして機能しています。

 今回実装している部分です。内部の処理を省略していますので、各メソッドのブロック({ }) について注目してください。
各ブロックをわかりやすくするために、数字を追加しています。


<ローカル関数>
  /// <summary>
    /// 自動バトル
    /// </summary>
    /// <param name="obstacle"></param>
    /// <returns></returns>
    private IEnumerator AutoBattle(ObstacleBase obstacle) {  、 スタート

        // AutoBattle メソッド内の処理


        /// <summary>
        /// プレイヤーの攻撃(ローカル関数)
        /// </summary>
    void AttackPlayer() { ◆、 スタート

      // AttackPlayer メソッド内の処理

        } → ◆―わり
    }  ―わり

 メソッド内であれば、処理の途中でも宣言できます。最後にまとめてローカル関数を宣言することも出来ます。
また、ローカル関数の中に入れ子構造として、別のローカル関数を宣言することもできます。
ただし、入れ子が深くなると処理が読みにくくなるため、ローカル関数内の入れ子となるローカル関数はなるべくない方がよいでしょう。


8.ゲームを実行して動作を確認する


 新しい機能をたくさん実装しているので、それらの復習を行いながら、処理の理解を深めていきましょう。

 まず、障害物のゲームオブジェクトを選択し、インスペクターより ObstacleBase スクリプトを確認します。
Hp 変数が表示されていますので、こちらに任意の値を入力します。この例では 5 としています。


<障害物のゲームオブジェクトの ObstacleBase スクリプト>




 どのような処理が動くのか、理解できたら、ゲームを実行してデバッグを行います。

 まず、Game ビューでの処理は変わりません。プレイヤーと障害物とが接触して、一定時間後に破壊されます。

 ただし、Console ビューに【障害物の残り HP : 】という形でデバッグの内容が表示されますので、
こちらで Hp が 0 になったら障害物が破壊されて、再度、プレイヤーの移動が可能になることを確認してください。

 そのため、ObstacleBase スクリプトの Hp 変数を変えると、破壊されるまでの時間に変化が現れます。


<Game ビュー 動画>
動画ファイルへのリンク


<Console ビュー>



 それ以外にも注目すべきロジックとしては、PlayerState の利用があります。
攻撃の処理、バトル後の処理により、移動の制限と再移動の許可が出るように制御処理が入っています。

 どのような状態のとき移動できる(そして移動できない)のか、どのタイミングで PlayerState の切り替えを行い、
ロジックとして成立するようにしているのかを、読み解き、理解できるように復習を行ってください。



 以上でこの手順は終了です。

 次は 手順13 −障害物の攻撃処理− です。

コメントをかく


「http://」を含む投稿は禁止されています。

利用規約をご確認のうえご記入下さい

Menu



プログラムの基礎学習

コード練習

技術/知識(実装例)

2Dおはじきゲーム(発展編)

2D強制横スクロールアクション(発展編)

3Dダイビングアクション(発展編)

2Dタップシューティング(拡張編)

レースゲーム(抜粋)

2D放置ゲーム(発展編)

3D脱出ゲーム(抜粋)

2Dリアルタイムストラテジー

2Dトップビューアドベンチャー(宴アセット使用)

3Dタップアクション(NavMeshAgent 使用)

2Dトップビューアクション(カエルの為に〜、ボコスカウォーズ風)

3Dトップビューアクション(白猫風)

VideoPlayer イベント連動の実装例

VideoPlayer リスト内からムービー再生の実装例(発展)

AR 画像付きオブジェクト生成の実装例

AR リスト内から生成の実装例(発展)

private



このサイト内の作品はユニティちゃんライセンス条項の元に提供されています。

管理人/副管理人のみ編集できます