質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.61%

  • Unity

    3825questions

    Unityは、ユニティテクノロジーが開発したゲームエンジンです。 主にモバイルやブラウザ向けのゲーム製作に利用されていましたが、3Dの重力付きゲームが簡単に作成できることから需要が増え、現在はマルチプラットフォームに対応しています。 言語はC言語/C++で書かれていますが、C#、JavaScript、Booで書かれたコードにも対応しています。

2D Roguelike tutorialでの移動動作について

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 249

meia

score 20

前提・実現したいこと

Unity 2017.1.1f1
を使っています。

現在以下のUnityのAssetを使って勉強中です
2D Roguelike tutorial

また、日本語の解説は以下のサイト(ソース付き)を参考にさせて頂いてます。
【Unity7】キャラクターを動かすための抽象クラス作成【2Dローグライク6】

このTutorialの完成版を編集して、Unityを勉強しているところです。

そこで、敵キャラクターの攻撃動作を分かりやすくするために
「敵の攻撃時に、敵キャラクターがプレイヤーに半マス分体当たりして、自分のマスに戻る」
という表現をさせたいのです。

しかし、自分で書いたスクリプトでは2つの点で上手くいきませんでした。
1.敵キャラクターがプレイヤーのマスに入るまではいいが、自分のマスに戻らず、次のターンに必ず下のマスに抜ける
2.プレイヤー方向への移動距離を1マスではなく1/2にすると動かなくなる

もしお詳しい方がいらしたら、見ていただけたらと思います。
よろしくお願いします

言語はC♯を使っています

Enemy.cs

//Enemy inherits from MovingObject, our base class for objects that can move, Player also inherits from this.
    public class Enemy : MovingObject
    {
        public int playerDamage;

        private Animator animator;                          //Variable of type Animator to store a reference to the enemy's Animator component.
        private Transform target;                           //プレイヤーの位置情報
        private bool skipMove;                              //敵キャラが動くかどうかの判定


        //MovingObjectのStartメソッドを継承
        protected override void Start ()
        {
            //Register this enemy with our instance of GameManager by adding it to a list of Enemy objects. 
            //This allows the GameManager to issue movement commands.
            GameManager.instance.AddEnemyToList (this);

            //Animatorをキャッシュしておく
            animator = GetComponent<Animator>();

            //Playerの位置情報を取得
            target = GameObject.FindGameObjectWithTag("Player").transform;

            //MovingObjectのStartメソッド呼び出し
            base.Start();
        }


        //Override the AttemptMove function of MovingObject to include functionality needed for Enemy to skip turns.
        //See comments in MovingObject for more on how base AttemptMove function works.
        protected override void AttemptMove <T> (int xDir, int yDir)
        {
            //Check if skipMove is true, if so set it to false and skip this turn.
            if(skipMove)
            {
                skipMove = false;
                return;

            }

            //Call the AttemptMove function from MovingObject.
            base.AttemptMove <T> (xDir, yDir);

            //移動が終了したらtrueにする
            skipMove = true;
        }


        //敵キャラ移動用メソッド GameManagerから呼ばれる
        public void MoveEnemy ()
        {
            //Declare variables for X and Y axis move directions, these range from -1 to 1.
            //These values allow us to choose between the cardinal directions: up, down, left and right.
            int xDir = 0;
            int yDir = 0;

            //Mathf.Abs: 絶対値をとる。-1なら1となる。
            if (Mathf.Abs(target.position.x - transform.position.x) < float.Epsilon)
            {
                //プレイヤーが上にいれば+1、下に入れば-1する
                yDir = target.position.y > transform.position.y ? 1 : -1;
            }
            else
            {
                //プレイヤーが右にいれば+1、左にいれば-1する
                xDir = target.position.x > transform.position.x ? 1 : -1;
            }
            //ジェネリック機能 攻撃対象はPlayerのみなので、型引数はPlayer
            AttemptMove<Player>(xDir, yDir);
        }


        //OnCantMove is called if Enemy attempts to move into a space occupied by a Player, it overrides the OnCantMove function of MovingObject 
        //and takes a generic parameter T which we use to pass in the component we expect to encounter, in this case Player
        protected override void OnCantMove <T> (T component)
        {
            //Playerクラスを取得
            Player hitPlayer = component as Player;

            //PlayerクラスのLoseFoodメソッドを呼び出す 引数はダメージ量
            hitPlayer.LoseFood(playerDamage);

            //敵の攻撃動作
            //animator.SetTrigger ("enemyAttack");
            boxCollider.enabled = false;

            int xDir = 0;
            int yDir = 0;

            //Mathf.Abs: 絶対値をとる。-1なら1となる。
            if (Mathf.Abs(target.position.x - transform.position.x) < float.Epsilon)
            {
                //プレイヤーが上にいれば+1、下に入れば-1する
                yDir = target.position.y > transform.position.y ? 1 : -1;

                animator.SetInteger("RabbitIdle", yDir);
            }
            else
            {
                //プレイヤーが右にいれば+1、左にいれば-1する
                xDir = target.position.x > transform.position.x ? 1 : -1;

                Vector3 scale = transform.localScale;
                scale.x = -xDir;
                transform.localScale = scale;
                animator.SetInteger("RabbitIdle", 2);
            }

            float halfX = xDir;
            float halfY = yDir;

            //現在地→プレイヤーへ
            Vector2 start = transform.position;
            Vector2 end = start + new Vector2(halfX, halfY);


           StartCoroutine(SmoothMovement(end));
           StartCoroutine(SmoothMovement(start));

      boxCollider.enabled = true;
        }
}

MovingObject.cs(継承元のSmoothMovemtを持つ)

//abstract:継承される側(スーパークラス)に付ける修飾子
    public abstract class MovingObject : MonoBehaviour
    {
        public float moveTime = 0.1f;            //Time it will take object to move, in seconds.
        public LayerMask blockingLayer;         //Layer on which collision will be checked.


        [HideInInspector] public BoxCollider2D boxCollider;         //The BoxCollider2D component attached to this object.
        [HideInInspector] public Rigidbody2D rb2D;               //The Rigidbody2D component attached to this object.

        //moveTimeを計算するのを単純化するための変数
        private float inverseMoveTime;

        //virtual : 継承されるメソッドに付ける修飾子
        protected virtual void Start()
        {
            //BoxCollider2DとRigidbody2Dを何度もGetComponentしなくて済むよう
            //Startメソッドにてキャッシュしておく

            boxCollider = GetComponent<BoxCollider2D>();
            rb2D = GetComponent<Rigidbody2D>();
            //デフォルトだと 1f ÷ 0.1f = 10.0f
            inverseMoveTime = 1f / moveTime;
        }


        //移動可能かを判断するメソッド 可能な場合はSmoothMovementへ
        protected bool Move(int xDir, int yDir, out RaycastHit2D hit)
        {
            //現在地を取得
            Vector2 start = transform.position;
            //目的地を取得
            Vector2 end = start + new Vector2(xDir, yDir);
            //自身のColliderを無効にし、Linecastで自分自身を判定しないようにする
            boxCollider.enabled = false;
            //現在地と目的地との間にblockingLayerのついたオブジェクトが無いか判定
            hit = Physics2D.Linecast(start, end, blockingLayer);
            //Colliderを有効に戻す
            boxCollider.enabled = true;
            //何も無ければSmoothMovementへ遷移し移動処理
            if (hit.transform == null)
            {
                StartCoroutine(SmoothMovement(end));
                //移動が成功したことを伝える
                return true;
            }
            //移動に失敗したことを伝える
            return false;
        }


        //現在地から目的地(引数end)へ移動するためのメソッド
        protected IEnumerator SmoothMovement(Vector3 end)
        {
            //現在地から目的地を引き、2点間の距離を求める(Vector3型)
            //sqrMagnitudeはベクトルを2乗したあと2点間の距離に変換する(float型)
            float sqrRemainingDistance = (transform.position - end).sqrMagnitude;
            //2点間の距離が0になった時、ループを抜ける
            //Epsilon : ほとんど0に近い数値を表す
            while (sqrRemainingDistance > float.Epsilon)
            {
                //現在地と移動先の間を1秒間にinverseMoveTime分だけ移動する場合の、
                //1フレーム分の移動距離を算出する
                Vector3 newPosition = Vector3.MoveTowards(rb2D.position, end, inverseMoveTime * Time.deltaTime);
                //算出した移動距離分、移動する
                rb2D.MovePosition(newPosition);
                //現在地が目的地寄りになった結果、sqrRemainDistanceが小さくなる
                sqrRemainingDistance = (transform.position - end).sqrMagnitude;
                //1フレーム待ってから、while文の先頭へ戻る
                yield return null;
            }
        }


        //移動を試みるメソッド
        //virtual : 継承されるメソッドに付ける修飾子
        //<T>:ジェネリック機能 型を決めておかず、後から指定する
        //Playerが移動するスクリプトの場合はWall型が、Enemyが移動するスクリプトの場合はPlayer型がこの<T>に入る
        protected virtual void AttemptMove<T>(int xDir, int yDir)
            //ジェネリック用の型引数をComponent型で限定
            where T : Component
        {
            RaycastHit2D hit;
            //Moveメソッド実行 戻り値がtrueなら移動成功、falseなら移動失敗
            bool canMove = Move(xDir, yDir, out hit);
            //Moveメソッドで確認した障害物が何も無ければメソッド終了
            if (hit.transform == null)
            {
                return;
            }
            //障害物があった場合、障害物を型引数の型で取得
            //型が<T>で指定したものと違う場合、取得できない
            T hitComponent = hit.transform.GetComponent<T>();
            //障害物がある場合OnCantMoveを呼び出す
            if (!canMove && hitComponent != null)
            {
                OnCantMove(hitComponent);
            }
        }


        //abstract: メソッドの中身はこちらでは書かず、サブクラスにて書く
        //<T>:AttemptMoveと同じくジェネリック機能
        //障害物があり移動ができなかった場合に呼び出される
        protected abstract void OnCantMove<T>(T component) where T : Component;
    }

試したこと

特に見ていただきたいのは自分で記述した以下の部分です。
もともと設定してあったanimatorをコメントアウトして、体当たりに見える動作を加えています

//敵の攻撃動作

~(中略)~

//ここで1/2にするとそもそも動かなくなる
float halfX = xDir;
float halfY = yDir;

//現在地→プレイヤーへ
Vector2 start = transform.position;
Vector2 end = start + new Vector2(halfX, halfY);

//実行すると1つ目のSmoothMovementは動くが、2つ目が動作していないように見える
StartCoroutine(SmoothMovement(end));
StartCoroutine(SmoothMovement(start));

boxCollider.enabled = true;

もしどなたか原因や修正方法がわかる方がいたら、教えていただきたいです。
足りない情報は追加させていただきますので、指摘をしていただければと思います。

よろしくお願いいたします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

(スクリプト内部は精査していませんので参考までに)
StartCoroutineはコルーチンを呼び出す命令ですが、そのままではコルーチンの完了を待ちません。
また、複数のコルーチンを呼び出した場合、(staticでない限り)並列で進行します。

なので今回の場合は以下の流れになっているものと思います。
1.StartCoroutine(SmoothMovement(end));が呼び出されて移動開始。
2.直後にStartCoroutine(SmoothMovement(start));が呼び出されるも、内部のwhileの条件に合致せずすぐにこのコルーチンは終了。
3.SmoothMovement(end)側のコルーチンは維持されているので「1つ目だけ移動する」ように見える。

対処方法としては、呼び出し側のメソッドもvoidからIEnumeratorに変えて、
yield return SmoothMovement(end);などとすると処理完了を待つようになります。
また、コールバックを設定してそれを受け取りつつ次の処理をするようにしてもいいと思います。


(コメントを受けて追記)
以下はyield returnにしたスクリプト例です。

//敵の攻撃動作
IEnumerator EnemyAction () {

//~(中略)~

//ここで1/2にするとそもそも動かなくなる
float halfX = xDir;
float halfY = yDir;

//現在地→プレイヤーへ
Vector2 start = transform.position;
Vector2 end = start + new Vector2(halfX, halfY);

//StartCoroutineから変更
yield return SmoothMovement(end);
yield return SmoothMovement(start);

boxCollider.enabled = true;
}

コールバックのやり方は「Kamanii Square - Unity - コールバックの記述まとめ」などを参考にどうぞ。
(SmoothMovement内部にて、一番最後に登録したメソッドを呼ぶイメージです)

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/05/08 16:42

    回答してくださってありがとうございます!コルーチンの説明がとてもわかり易く、そこを参考にSmoothMovement内のwhile部分を2つにして、行きと帰りを含めることで、目的の動作が実現できました。
    ただ、自分の理解力では対処方法のところまでは理解できず、試しても?となってしまったので、参考にしきれなかったことが悔やまれます…
    ともかく、ありがとう御座いました!

    キャンセル

  • 2018/05/08 17:37

    動作未検証ですがソースコードを追記しました。
    コールバックは色々あるんで調べてもらえればと思います(今回の内容だとあんまり合わないと思いますが「処理完了時に何かしたい」場合に便利です)
    参考になれば幸いです。

    キャンセル

  • 2018/05/09 15:53

    わざわざソースコードまでありがとうございます!
    こっちのほうがスッキリしてて、いいですね!
    早速参考にさせていただきますm(_ _)m

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.61%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • Unity

    3825questions

    Unityは、ユニティテクノロジーが開発したゲームエンジンです。 主にモバイルやブラウザ向けのゲーム製作に利用されていましたが、3Dの重力付きゲームが簡単に作成できることから需要が増え、現在はマルチプラットフォームに対応しています。 言語はC言語/C++で書かれていますが、C#、JavaScript、Booで書かれたコードにも対応しています。