ゲーム制作   Unity ソフトウエア 

Unity ソフトウエアでゲーム制作#1モグラたたき編(5.基底クラスを作って継承する)

キャッチ画像モグラたたき基底クラスと継承

本投稿は2024年9月時点の内容になります。アップデートにより変更となる場合があります。また環境によって違いがあると思いますのであくまで参考として、ご了承ください。

様々な書籍、ブログや動画を参考にさせていただきました。多すぎて一つ一つは紹介できませんが感謝です。

初心者の自分がUnity ソフトウエアでゲームを作ってみました。とりあえずシンプルなものということでモグラたたきに挑戦です。ゲーム作ってみるかという感じになったときに、いいタイミングで某ゲームのイベントシナリオ内ミニゲームにモグラたたきが実装されていたのでUIとかエフェクトとか、諸々の仕様をぱくって参考にして作ってみましたよ。様々なHowToの中の選択肢のひとつとして、同じ初心者さんの参考になればよいです。

\ チェック /

キャラクターの基本となる基底クラスを作ってみよう

お待たせしました。いよいよ本格的にゲームを作っていきます。モグラたたきを作っていくのですが今後に役立てるため思いっきり遠回りして作っていきます。今回はRPGとかアクションゲームとかに使えそうなキャラクターの元をとなるクラスを作って、さらにそれを継承して敵キャラを実装していきます。

本記事のポイント

  • キャラクタのパラメータを表す基底クラス「UnitStatus」を作りながら、クラスを構成する要素をざっくり説明
  • ゲームでいろいろ使えそうな列挙型enumを少し学習
  • 基底クラスを継承した敵のパラメータを表すクラス「EnemyStatus」を作成してみる
  • さらに「EnemyStatus」クラスを継承した、実際のゲームキャラにアタッチする「Enemy」クラスを作成します

\ スクリプトがわからないときは前回をチェック /

スクリプトとDebug.Logを解説してます。

キャラクタのパラメータを表す基底クラス「UnitStatus」を作る

スクリプトを作成してみよう

  1. プロジェクトで 右クリック>作成>C#スクリプト をクリック
  2. 今回は「UnitStatus」名前で作成しました。
  3. 「using UnityEngine;」以下に下記のようにコーディングしました。
public enum UnitType { player, enemy, jammer }//unitのタイプを表す型

[Flags]
public enum Statuses//キャラクターのステイタス(状態)を表す型
{
    Normal = 0,//通常0000
    Runaway = 1,//逃げる0001
    Positive = 2,//友好的0010
    Anger = 4,//怒り0100
    Dead = 8//死亡1000
}
public class UnitStatus
{
    //player,enemyの必須パラメータ
    //自動実装プロパティ
    //getは外部から読込みたいものsetは外部から変更したいもの
    public string Name { get; set; }//名前
    public int Atk { get; set; }//攻撃力
    public int Def { get; set; }//防御力
    public int Level { get; set; }//レベル
    public int MaxHp { get; }//HPの最大値
    public int CurrentHp { get; set; }//現在のHP
    public float Exp { get; set; }//経験値
    public UnitType UnitType { get; set; }//タイプ
    public Statuses Status { get; set; }//状態


    //コンストラクタ初期化処理
    public UnitStatus(string name, int atk, int def, int level, int maxHp, int exp, UnitType unitType)
    {
        Name = name;//キャラクター名
        Atk = atk;//攻撃力
        Def = def;//防御力
        Level = level;//レベル
        MaxHp = maxHp;//最大HP
        Exp = exp;//経験値
        UnitType = unitType;//キャラクタータイプ
        CurrentHp = MaxHp;//現在のHPを最大HPに設定
        Status = Statuses.Normal;//
    }

    //攻撃の処理をするメソッド
    public void Attack(UnitStatus target)
    {
        //ダメージ=攻撃力ー相手の防御力
        //マイナス値はとらない。0以下の時は0
        int damage = Mathf.Max(0, Atk - target.Def);
        //相手にダメージを与える
        target.GetDamage(damage);
    }
    //受けるダメージを処理するメソッド
    public void GetDamage(int damage)
    {
        //現在のHPを現在のHPーダメージで更新
        //マイナス値はとらない。0以下の時は0
        CurrentHp = Mathf.Max(0, CurrentHp - damage);
    }

    //キャラクタにステータスを追加するメソッド
    public void AddStatus(Statuses status)
    {
        Status |= status;
    }

    //キャラクタにステータスを削除するメソッド
    public void RemoveStatus(Statuses status)
    {
        Status &= ~status;
    }
}

以下ざっくり解説です。コードのコメントを見ながらチェックしてみてください

スクリプトざっくり解説

  1. (1)キャラクターのタイプを決めるenum型のUnitTypeを定義。
  2. (3-11)キャラクターの状態を表すenum型のStatusesを定義。複数の状態をフラグとして管理したいので[flags]でflag属性を加え、ビット演算という方法が使えるように2累乗を割り当てています。
  3. (12-70)「UnitStatus」クラスを定義。
  4. このクラスは「MonoBehaviour」を継承していません。
  5. (17-25)キャラクターに必要なパラメタ宣言、自動実装プロパティ。
  6. (29-40)コンストラクタ インスタンス化した時のメンバ変数の初期化をしています
  7. (43-69)メソッドです。キャラクタに共通する処理関数です。
  8. (43-57)攻撃とダメージに関する処理。マイナスにならないようにMaxで篩にかけてます。
  9. (60-69)ビット演算を使ってキャラクターの状態を付加したり削除したりする処理です。

列挙型enumて何?

列挙型enumとは基になる整数型の一連の名前付き定数によって定義された値の型です。名前付き定数の集まりで特定の値しか取らないものを定義するのに便利です。

わかりにくいですが、よく例に上がるのが曜日、月、性別などですね。マケイヌ的にenumのいいところはコード上意味がはっきりするところと、ビット演算で複数のフラグが管理できるところです。

ビット演算とは2進数を操作する演算です。大きな役割の一つが複数のyes(1)/no(0)を処理する論理演算です(ANDとかORとか)。今回の例だと各状態を2の累乗に設定しているので、各状態が「yes」ならその「位」に1がフラグとして立つイメージです。例えば「”友好的”かつ”逃げてる”状態は”0011”」、「”怒り”かつ”逃げてる”状態は”0101”」となるイメージです。

ざっくり解説なのでもっと詳しく知りたい方はレファレンスやもっと詳しい情報にふれてみてください。

クラスの構成要素ざっくり解説

フィールド(メンバ変数)

クラスの中の意味のある変数て感じだとイメージ的にいいかも。

メソッド(メンバ関数)

クラス内の関数

コンストラクタ

クラスをインスタンス化(実体化)したときに呼ばれる関数です。おもに初期値の設定に使います。

プロパティ、自動実装プロパティ

メンバ変数は本来アクセス修飾子をprivateにして外部からアクセスを制限するのが理想です。この時、変数の値を返したり、変更したりするメソッドを作用意することで限定的にアクセスすることができるようになります。

private int hp;
//読み取り
public int GetHp()
{
  return hp;
}
//書き込み
public void SetHp(int value)
{
  hp=value;
 }

こんなのです。

その関数をコーディングする負担を軽減し、さらにコードを読みやすくしてくれるのがプロパティです。クラスの中では関数ですが、外部からはメンバ変数のようにふるまいます(関数特有の”()”がない)。

private int hp;
public int Hp
{
  get{return hp;}
  set{hp=value;}
}

こんなのです。

さらにそれを簡略化したのが今回使っている自動実装プロパティです。getもsetもしている自動実装プロパティと、publicな変数と何が違うのかて感じですよね。アクセスを制限したいという意思表示と、アクセスを制限したくなった時に容易に変更が可能なのがポイントかなと思っています。

どうしてMonoBehaviourを継承しないのか

前回Unity プロジェクトではMonoBehaviourクラスを継承した方が良いよと書いたのに、今回継承しなかったのはどうしてか。まずMonoBehaviourメリットを見てみます。

MonoBehaviourクラスを継承のメリット

  • Unity プロジェクトのイベントをフックできる。
  • ゲームオブジェクトにアタッチできる。

次にデメリットです。

MonoBehaviourクラスを継承のデメリット

  • コンストラクタを使った「new」インスタンス化ができない

さらにクラスは直列な多段継承はできるけど、並列の多重継承はできない。

これを踏まえて今回MonoBehaviourを継承しなかった理由です。

MonoBehaviourクラスを継承しなかった理由

  • キャラクターのステータスはデータ的な意味合いが強く、ゲームオブジェクトと直接の関係が薄いため
  • 後述になりますが、コンストラクタを使って実体化することでインスタンス化/初期設定を明示したかった

というわけで、ここは個人的な好みですね。ここでMonoBehaviourを継承した場合は初期設定はAwakeの中など書いていくことで可能です。

UnitStatusクラスを継承したEnemyStatusクラスを作る

スクリプトを作成してみよう

  1. プロジェクトでスクリプト「EnemyStatus」を作成。
  2. 「using UnityEngine;」以下に下記のようにコーディングしました。
public class EnemyStatus : UnitStatus
{
    //コンストラクタ base以降は継承した引数です
    public EnemyStatus(string name, int atk, int def, int level, int maxHp, int exp, UnitType unitType)
        : base(name, atk, def, level, maxHp, exp, unitType)
    {
    }
}

スクリプトざっくり解説

  1. (1)UnitStatusクラスを継承したEnemyStatusクラスを定義。
  2. (4-7)コンストラクタです。
  3. (5)継承元のコンストラクタの引数を明示しています。

特に難しくないですね。

さらに継承してEnemyクラスを作成してオブジェクトにアタッチする

スクリプトを作成して配置してあるUnityちゃんにアタッチしよう

  1. プロジェクトでスクリプト「Enemy」を作成。
  2. 「using UnityEngine;」以下に下記のようにコーディングしました。
  3. このスクリプトを前回シーンに配置したUnityちゃんにコンポーネントとしてアタッチします。
public class Enemy : MonoBehaviour
{
    public EnemyStatus EnemyStatus {  get; set; }
    
    public void Setup()
    {
        //各パラメータの設定
        string name = "Unitychan";
        int atk = 1;
        int def = 0;
        int level = 1;
        int maxHp = 1;
        int exp =750;
        UnitType unitType = UnitType.enemy;
        //新しいインスタンスを作成
        EnemyStatus = new EnemyStatus(name, atk, def, level, maxHp, exp, unitType);
    }
    
    // Start is called before the first frame update
    void Start()
    {
        Debug.Log(enemyStatus.Name + "の攻撃力:" + enemyStatus.Atk);
        Debug.Log(enemyStatus.Name + "の防御力:" + enemyStatus.Def);
        Debug.Log(enemyStatus.Name + "のMaxHP:" + enemyStatus.MaxHp);
        Debug.Log(enemyStatus.Name + "のレベル:" + enemyStatus.Level);
        Debug.Log(enemyStatus.Name + "の経験値:" + enemyStatus.Exp);
    }

    // Update is called once per frame
    void Update()
    {
    
    }

    private void OnEnable()
    {
        //enemyを設定
        Setup();
    }

}

スクリプトざっくり解説

  1. (1)MonoBehaviourを継承したEnemyクラスを定義。このスクリプトはゲームオブジェクトにアタッチするのでMonoBehaviourを継承しました。
  2. (3)EnemyStatus型 enemystatusを宣言
  3. (5-17)EnemyStatusを初期値引数でenemystatusとしてインスタンス化するメソッドSetupを定義
  4. (35-39)ゲームオブジェクトがアクティブになったときに呼ばれるイベント関数OnEnable関数内でSetupを実行
  5. (20-27)確認ログ用です。

ゲーム再生してコンソールに正しい値が表示されていればOKです。ちなみに常時左を向かせるためにSpriteRendererの反転Xにチェックを入れました。

※ちなみにアタッチする際はインスペクターをロックすると便利です。

まとめ

ざっくり解説でした。

まとめ

  • クラスの構成はフィールド、コンストラクタ、メソッド、プロパティ。
  • プロパティの設定でフィールドのアクセスを制限できる。
  • MonoBehaviourクラスの継承にはメリットが多いが、コンストラクタを使ったインスタンス化ができないというデメリットもあるので自分のデザインに合うように適材適所で使おう
  • 列挙型enumは特定の状態やフラグに便利である。たぶんこの先のより実践的なところで分かってもらえるかなあ
  • より詳細で正確な情報を身に着けたい場合は書籍やリファレンスなどの学習がおすすめです。
ユニティちゃん公式ホームページへ
ユニティちゃん公式ホームページ
ユニティちゃんライセンス
ユニティちゃんの画像、素材、ライセンスロゴはユニティちゃんライセンス条項を元に使用しています

\ Unityのスクリプトを書くのに役立ちます /

もっと早く教えてほしかった!Unity C#入門

MARU

マケイヌ的おすすめ度

わかりやすい度

目指せ脱初心者

〇おすすめポイント

ボリューム大でC#学習にもよく使う関数のチェックにもOK

×よくないポイント

始めたばかりの人にはちょっと難しい

おすすめ記事

 

プロフィール

マケイヌ

人生のメインストリームから外れた40代の♂。

90年代オルタナにはまり、文字通りメインストリームから逸脱。 その後もたびたび人生から逃亡。

心が動いた作品の紹介や 自分のちいさな経験、HowToを発信できればと日々模索中。

1年後までにイラストと写真のポートフォリオをつくりたい。

記:2019年12月

▼プロフィールはこちら

Follow me

アーカイヴ