2D Game Kitの敵の動きを増やす

コーディングなしでゲームが作れるのがウリの2D Game Kitですが、どのような手法を使っているのかを知るのも勉強になります。

敵キャラクターChomperの行動を増やしてみて、2D Game Kitの敵の制御方法の半分を知ることにしましょう。

手っ取り早く作業手順を知りたい方はこちらからどうぞ。

 

なぜ半分?

Spitterボスは、Chomperとは違うアルゴリズムを採用しています。Chomperはデザイナーなどの非プログラマーが調整する想定Spitterとボスはプログラマーが調整することを想定しているのでしょう。

(2018/4/8 敵が天井に貼り付く症状の解消を追記)

目次

想定スキル

「このメソッドを適切な場所に追加してください」という指示で作業できるレベルを想定しています。

やりたいこと

Chomperの通常時の動きは以下のような感じです。

地面がある範囲をうろうろしてパトロールします。

ここでは、移動できなかった時に、引き返すの他に、反転してからジャンプする動作を追加します。

Chomperの行動決定方法

Chomperは、以下で紹介されているSceneLinked SMB(State Machine Behaviour)を利用して、行動決定の処理が組み立てられています。

ざっくりと解説すると、SceneLinked SMBとは、シーンに配置されているゲームオブジェクトの動作と、Animatorのステートマシーン(State Machine)のステートを連動させる手法です。

Scene Machine Behaviourについては以下にUnity公式のマニュアルがあります。

 

これはAI?

AI(artificial intelligence)、日本語では人工知能と訳され、「人間や生物の知能を, 機械によって実現したもの, あるいはその研究分野.」(OR辞典, Weblioより引用)などと解説されています。

ゲームの敵の行動決定を、架空の生き物の思考をシミュレーションするもの、と解釈すれば、AIの一種といって構わないでしょう。Unityのドキュメントでも、敵の思考ルーチンはAIと表現しています。

 

Chomperのアニメーター

以下はChompoer用のAnimatorです。

四角がステート(State)と呼ばれるもので、状態を表しています。名前を見ると、ChompoerWalkとか、ChomperRunとか、ChomperAttackなど、行動を表しています。

ステート間を結ぶ線がトランジション(Transition)と呼ばれるもので、状態を遷移(切り替えること)させる条件や時間などを設定するものです。

Unityでは、キャラクターのアニメーションをこのような図で構築することができます。その時々の行動に応じたアニメーションを自動的に再生するというのが本来の用途ですが、State Machine Behaviourを使うと、このステートにスクリプトを設定することができるようになります。あるアニメーションが始まった時や、続いている間終わった時などにスクリプトを実行させることができます。そこに思考ルーチンを実装することで、アニメーションに応じてAIを切り替えることができるのです。

Animatorは、非プログラマーの人が扱えるように作られたインターフェースなので、このような環境を構築することで、敵の動きを設定するだけならプログラミングをコーディングするスキルが不要にできます。

Chomperの思考スクリプト

Chomper自身にアタッチされているCharacter Controller 2DEnemy Behaviour、AnimatorのステートにアタッチされているChomper ?? SMBスクリプトの3つのスクリプトが連携してChomperを制御しています。

Character Controller 2D

プレイヤーや敵キャラクターが持っているスクリプトで、移動や地面との当たり判定といった、全キャラクターが必要とする機能が実装されています。

Enemy Behaviour

敵キャラクターが共通して持っているスクリプトで、体当たりやショット攻撃をするかの判断や、攻撃処理といった全ての敵キャラクターが必要とする機能が実装されています。

Chomper ?? SMB

SceneLinkedSMBを継承したクラスで、??の部分は、各行動の名前が入ります。具体的には以下の4種類があります。

  • ChomperAttackSMB
  • ChomperDeathSMB
  • ChomperPatrolSMB
  • ChomperRunToTargetSMB

これらがAnimatorの該当するステートにアタッチされていて、結び付けられているEnemy Behaviourの機能を利用して状況を判断して行動を決定しています。決定した行動を実行するのはEnemy Bahaviourの役割です。

つまり、このChomper ?? SMBスクリプトが敵の思考ルーチンそのものということです。

ChomperPatrolSMBを読む

今回は敵のパトロール時の動きを追加したいので、ChomperPatrolSMBが目的のスクリプトです。中身は以下の通りです。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Gamekit2D
{
    public class ChomperPatrolSMB : SceneLinkedSMB<EnemyBehaviour>
    {
        public override void OnSLStateNoTransitionUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
        {
            //We do this explicitly here instead of in the enemy class, that allow to handle obstacle differently according to state
            // (e.g. look at the ChomperRunToTargetSMB that stop the pursuit if there is an obstacle) 
            float dist = m_MonoBehaviour.speed;
            if (m_MonoBehaviour.CheckForObstacle(dist))
            {
                //this will inverse the move vector, and UpdateFacing will then flip the sprite & forward vector as moveVector will be in the other direction
                m_MonoBehaviour.SetHorizontalSpeed(-dist);
                m_MonoBehaviour.UpdateFacing();
            }
            else
            {
                m_MonoBehaviour.SetHorizontalSpeed(dist);
            }

            m_MonoBehaviour.ScanForPlayer();
        }
    }
}

OnSLStateNoTransitionUpdateメソッドしか持っていません。これは、遷移(Transition)が発生していない更新(Update)時の処理です。

コード中のm_MonoBehaviourは、Chomperが持っているEnemyBehaviourクラスのインスタンスです。ざっくりと以下のことをやっています。

  • Chomperが現在の移動速度(m_MonoBehaviour.speed)で動いた時に、障害(Obstacle)があるかを調べる(m_MonoBehaviour.CheckForObstacle)
  • 移動できない場合
    • 逆方向に移動
    • 向きを反転
  • 移動できる場合
    • 現在の方向に移動
  • プレイヤーを探索する(m_MonoBehaviour.ScanForPlayer)

以上から、移動できない場合の処理を増やせばよい、ということになります。

作戦を立てる

Chomperはジャンプする手段を持ちません。Ellenがどうやってジャンプしているかを確認して、その機能をもらいましょう。

ジャンプ処理

Ellenのジャンプ処理は、LocomotionSMB.csLocomotionWithGunSMB.csで確認することができます。

m_MonoBehaviour.CheckForJumpInput()メソッドで操作を確認して、ジャンプする場合はm_MonoBehaviour.SetVerticalMovement()メソッドにジャンプ力を渡して呼び出しています。

m_MonoBehaviourにはPlayerCharacterが結び付けられていますので、そこから探すと処理が見つかります。それをEnemyBehaviourに移植していきましょう。

 

ジャンプを敵に移植する

それではジャンプを敵に実装していきます。以下の手順で行います。

  • EnemyBehaviourSetVerticalMovement()メソッドを作成
  • ジャンプ力のパラメーターを追加
  • AIにジャンプの分岐を実装

それでは作業します。

SetVerticalMovement()メソッドを作成

  • EnemyBevaviour.csをエディターで開きます
  • 以下のメソッドをEnemyBehaviourクラスに追加します


これで、敵に縦方向の速度を設定することができるようになりました。

ジャンプ力のパラメーターを追加

必要な変数を追加します。

  • EnemyBehaviour.csの22行目付近のMovementの以下の辺りに、ジャンプ力を設定するための変数宣言を追加します

AIにジャンプの分岐を実装

パトロール用のAIに、移動できない場合の選択肢としてジャンプを追加します。

  • Projectビューから2DGameKit -> Scripts -> Character -> StateMachineBehaviours -> Enemiesフォルダーを開きます
  • ChomperPatrolSMBスクリプトをダブルクリックして開きます
  • 障害があった時のif文の中を以下のように修正します

保存をしたら、Unityで実行して動きを確認してみてください。半分ぐらいの確率でジャンプするようになります。

 

真上に飛ばせる?

最初は、真上にジャンプをさせようとしたのですが、やっかいな問題があったので「反転してジャンプ」という動きにしました。

ジャンプする時に横方向の移動速度を0にすれば真上に飛べると考えたのですが、そうなりませんでした。2D Game Kitの敵キャラクターは、Character Controller 2DEnemy Behaviour、そしてState Machine Behaviourの3つのスクリプトを連携させていることは先に述べました。問題は、これらのスクリプトの実行順番です。

本来は、以下の順番で実行されるべきです。

  1. State Machine BehaviourのAIで行動を決定
  2. Enemy Behaviourで行動を動作として反映
  3. Character Controller 2Dで動作の実行と状態(接地しているかなど)を反映

ところが、Character Controller 2Dの方がEnemy Behaviourより先に実行され(おそらくコンポーネントの並び順)、State Machine Behaviourに至ってはフレームごとに変化していました。

そのため、AIがジャンプを決断した時点で横方向の移動を止めても、次のAIの処理ではまだ地面にいると判断して、横方向の移動を再開してしまいます。現在の仕組みだと、AIがジャンプしたことを把握するのは、最悪2フレーム後になります。

処理順番を整理するか、強引にジャンプ状態を持たせれば解決できますが、ややこしくなるので現在の動作にしました。

問題発生!

天井を追加したところ、以下のようにジャンプするとChomperが天井に貼り付いてしまうことが分かりました。

CharacterController2Dの着地判定の範囲を描画してみると、以下の赤の描画をRaycastで調べていました。

緑の四角がコライダーなので、コライダーより上から足場チェックしていることになります。頭の上の床に対しても着地してしまうのです。CharacterController2DCheckCapsuleEndCollisions()で、Raycastの開始位置が以下のように計算されていました。

 raycastStart = m_Rigidbody2D.position + Vector2.up;

Rigidbodyの中央座標に有無を言わせずVector2.upを足してます。つまり、キャラクターのサイズを無視して1単位上からチェックしているということになります。これが原因です。

解決策

キャラクターのコライダーの中央から下方向にチェックするようにちゃんと計算しましょう。敵はChomperSpitterBoxColliderを持っています。これを取得して、BoxColliderOffset.ySize.yを使って、キャラクターの中心から足元に向けて着地判定をするように修正します。

  • CharacterController2Dをエディターで開きます
  • 適当な場所(m_Capsuleの定義の下辺り)に、以下を追加します
 BoxCollider2D m_BoxCollider;
  • Awake()の中に以下を追加します
 m_BoxCollider = GetComponent<BoxCollider2D>();
  • CheckCapsuleEndCollisions()メソッドに以下の修正をします

    if (m_BoxCollider == null)
    {
    }
    else
    {
        raycastStart = m_Rigidbody2D.position + Vector2.up*m_BoxCollider.offset.y;
        raycastDistance = m_BoxCollider.size.y*0.5f + groundedRaycastDistance;
    }

以上で完了です。実行すると、以下のように足場判定がキャラクターの中心から足元に変更されて、頭をぶつけても天井に貼り付かないようになります。

オマケ

頭がぶつかってもしばらく勢いで空中に滞在しています。これを調整して仕上げます。

  • EnemyBehaviour.csをエディターで開きます
  • 以下のメソッドを追加します

衝突が発生した時に、上方向に移動していて、かつ、相手がPlatformEffector2Dコンポーネントを持っていない(すり抜け床じゃない)場合、Y方向の速度を0にすることで、頭をぶつけたらすぐに落下するようにしました。

まとめ

敵のうち、Chomperの動かし方を調査して、ジャンプを追加しました。他にも、例えば前方にジャンプさせるとか、地面が関連しない処理は本記事の内容の応用で簡単に実装できると思います。

一方で、Ellenのように、床から降りれるようにするのは難しいことが分かりました。AIと移動設定、移動処理の実行順が前後することがあるため、動作が遷移していることを想定した動作を追加するのは難しいです。

2D Game Kitの思想に沿った敵の動きを考えてみるのも面白いと思います。

参考URL

Print Friendly, PDF & Email

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA