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

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

新規登録して質問してみよう
ただいま回答率
85.48%
Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

1回答

865閲覧

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

meia

総合スコア26

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

0クリップ

投稿2018/05/07 00:55

編集2018/05/07 00:56

###前提・実現したいこと

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;

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

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

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

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

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

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


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

C#

1//敵の攻撃動作 2IEnumerator EnemyAction () { 3 4//~(中略)~ 5 6//ここで1/2にするとそもそも動かなくなる 7float halfX = xDir; 8float halfY = yDir; 9 10//現在地→プレイヤーへ 11Vector2 start = transform.position; 12Vector2 end = start + new Vector2(halfX, halfY); 13 14//StartCoroutineから変更 15yield return SmoothMovement(end); 16yield return SmoothMovement(start); 17 18boxCollider.enabled = true; 19}

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

投稿2018/05/07 03:00

編集2018/05/08 08:35
sakura_hana

総合スコア11427

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

meia

2018/05/08 07:42

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

2018/05/08 08:37

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

2018/05/09 06:53

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問