前回(第4回)では、敵の経路を作成しました。
前回の記事↓

第5回である今回は、その経路に沿って実際に敵オブジェクトが移動する仕組みなどを作っていきます。
どんどんタワーディフェンスらしくなってきますね。
敵(Enemy)スクリプトの修正
では、早速修正を始めていきます。Enemyスクリプトをダブルクリックして、プログラムの編集をしていきます。
今は、このような状態になっていると思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Enemy : MonoBehaviour { public int hp; public float speed; public int gold; // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { var v = Vector3.right; //移動方向 今回は右 transform.position += v * speed * Time.deltaTime; } } |
今はまだ右に移動することしかできていませんね。
Routeの指定、スタート位置
このEnemyスクリプトに、前回作った 経路(Route) を指定できるようにします。
メンバ変数に public Route route; を追加します。
指定された route にはPointの配列(Point[])型のメンバ変数(配列)points が入っているんでした。
このpoints[0]がスタート地点になるので、Start関数でその場所に移動させてしまいます。
11 12 13 14 15 16 17 18 19 |
public int gold; public Route route; // Start is called before the first frame update void Start() { transform.position = route.points[0].transform.position; } |
スクリプトの変更を保存をして、UnityEditorに戻ります。
今、シーンには3体のEnemyが置いてあると思います。 そのうちの1体、どれでも良いので選択して(例ではEnemyオブジェクトにしました)、Inspectorを見ると Route という項目が None(Route) になっていると思います。
外からRouteが指定できるようになっていて、かつ今は何も指定されていないので「None」と表示されています。
ではこのRouteに、前回作った Routeオブジェクト(Route,Route (1),Route (2),Route (3) の4つを作りましたね)をどれでも良いのでドラッグアンドロップして指定し、再生ボタンを押してみてください。
Routeを指定したEnemyオブジェクトは、再生と同時にStart地点へ移動し、そこから右へ移動開始するようになりましたね。
もちろん、これだけでは右にしか移動できないので、さらに修正をしていきます。
Route(Points)に沿った移動
route.points には、スタートからゴールまでの通過点(Point)が入っています。
スタート位置は route.points[0].transform.position でした、そこからどこに向かうかというと、次のポイントが入っている route.points[1].tarnsform.position に向かわないといけません。
この、route.points[0].transform.position から、route.points[1].tarnsform.position へと向かうベクトル(Vector3)を計算します。
急にベクトルが出てきて拒否反応が出てしまう方もおられるかもしれませんが、Unityを使う分にはほぼ必須な項目になりますし、使い方さえ覚えてしまえば意外となんとかなるのでやっていきましょう。
まず、ある地点(From)から、ある地点(To)へ移動するベクトルを求めるには、
var v = To – From;
と2つのベクトルの引き算をするだけです。
いきなり多次元で考えるとややこしいので、1次元で考えます。
定規を想像してください。今、5cmの位置(From)に鉛筆の先端があります。 12cmの位置(To)まで移動したい(線を引きたい)です。 鉛筆をどの方向に何cm移動させる必要があるでしょう。
繰り返しになりますが、ある地点(From)から、ある地点(To)へ移動するベクトルを求めるには、
var v = To – From;
でした。 Toは12cm,Fromは5cmです。 なので、v = 12-5 = +7cm ということになります。
この「+(方向)に、7cm(長さ)」がFromからToへと移動するベクトルなわけです。(1次元なので、方向は+かーのみ)
では、同じように今route.points[0].transform.position (From) から、route.points[1].tarnsform.position (To)へと向かうベクトル(Vector3)を計算するよう、修正をします。
今、 v = Vector3.Right;
となっていますが、以下のように修正します。
1 |
var v = route.points[1].transform.position - route.points[0].transform.position; |
スクリプトの保存をして、再生をしてみましょう。
1つ目のPoint (route.points[0]) から、2つ目のPoint (route.points[1]) へ移動するようになりました。
しかしまだ問題点が2つあります
- 移動速度が速くなってしまっている
- 曲がるべきところで曲がっていない
ですね。
問題点1 移動速度が速くなってしまっている
これは、ベクトルの正規化(Normalize)をしていないためです。この正規化(Normalize)というのは、ベクトルの方向はそのままに、距離だけを1にするという意味になり、正規化をしないと、PointからPointの距離が長ければ長いほど、移動速度が速くなってしまいます。
正規化をしたベクトルで移動を計算したいので、
1 |
transform.position += v * speed * Time.deltaTime; |
としているところを
1 |
transform.position += v.normalized * speed * Time.deltaTime; |
に修正し移動ベクトル(v) をそのまま使うのではなく、normalized(正規化)したものを使用するようにします。
問題点2 曲がるべきところで曲がっていない
これは当然でこのEnemyスクリプトでは、ずっと スタート地点(route.points[0])から、次のポイント(route.points[1])への移動ベクトル(v)だけで移動しています。
Enemyが移動をして、次のポイント(route.points[1])まで辿り着いたら、今度はその次のポイント(route.points[2])への移動ベクトルを計算。また次のポイント(route.points[2])まで辿り着いたらさらのその次のポイント(route.points[3])を・・・・
といったように参照するroute.pointsを状況(今、どのPointからどのPointへの移動中なのか)に応じて変化させていく必要があります。
この、「どのPointから移動中なのか」を示すメンバ変数として
1 |
private int pointIndex; |
を追加し、移動ベクトル(v)を求める式も以下のように変更します。
1 |
var v = route.points[pointIndex + 1].transform.position - route.points[pointIndex].transform.position; |
そして、次のポイント(route.points[pointIndex + 1])まで辿り着いたかどうかの判断をします。
これには色々な方法がありますが、現在参照しているroute.pointsの2点間(route.points[pointIndex]からroute.points[pointIndex+1])の距離と今のEnemyの現在のPointからの距離(route.points[pointIndex]からEnemyの位置)を比較して、判断することにします。
これをプログラムに直すとこうなります。
1 2 3 4 5 |
var pv = transform.position - route.points[pointIndex].transform.position; if (pv.magnitude >= v.magnitude) { pointIndex++; //次のPointへ } |
まず、現在のPointの起点であるroute.points[pointIndex] (From)からEnemyの位置 (To) のベクトルを変数pvに求めています。
次にベクトルの長さを調べるのには magnitude というプロパティを使います。(なお、magnitudeを求める計算は内部で√(平方根)が使用されるため、若干計算コストが高いです。ベクトルの長さの大小比較の場合には sqrMagnitude という、√(平方根)が使われていない、距離の二乗を返すプロパティを使う方法もあります)
そして pv.magnitude >= v.magnitude 、すなわち、Enemyが次のポイントに付いた(または通り過ぎてしまった)場合は、pointIndexを1増やして、参照するroute.pointsを1つ先に進めるようにします。
今の段階でのプログラム全体はこのようになっているはずです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Enemy : MonoBehaviour { public int hp; public float speed; public int gold; public Route route; private int pointIndex; // Start is called before the first frame update void Start() { transform.position = route.points[0].transform.position; } // Update is called once per frame void Update() { var v = route.points[pointIndex + 1].transform.position - route.points[pointIndex].transform.position; transform.position += v.normalized * speed * Time.deltaTime; var pv = transform.position - route.points[pointIndex].transform.position; if (pv.magnitude >= v.magnitude) { pointIndex++; } } } |
プログラムの編集を保存をして再生してみましょう。
指定した経路の通りに移動しましたね!
他2体のEnemyにもそれぞれ違うRouteオブジェクトをInspectorで指定してみて、それぞれが指定した経路をEnemy毎に設定したスピードで移動をするか確認してみましょう。
ゴール判定
このEnemyの移動処理、一見問題が無いように見えますが、実は裏でエラーが出ています。
というのも、ゴールに到着した後の処理が書いてないため、route.points を参照するための変数pointIndexがroute.pointsの配列の要素数を超えてしまっているからです。
では、ゴールに到着したかどうかの判定などを追加しましょう。
pointIndexを1増やして、参照するroute.pointsを1つ先に進めた時点で、pointIndexがゴールである route.pointsの最後の要素を差している場合は、ゴールに到着したと考えてもよさそうです。
これをプログラムにすると if(pointIndex >= route.points.Length – 1) というif文になります。
最後まで到達したら、Destroy(gameObject) して、Enemyオブジェクトそのものを消すと同時にプレイヤーにダメージを与える必要があるのですが、まだプレイヤーのHP等を管理する仕組みが存在しないため、「//TODO プレイヤーにダメージ」 と追記する予定の内容をコメントとして書いておきます(これは実際のゲーム開発の現場でも良く行われる慣習です)
ゴール判定部分を追加したEnemyスクリプトの全体は以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Enemy : MonoBehaviour { public int hp; public float speed; public int gold; public Route route; private int pointIndex; // Start is called before the first frame update void Start() { transform.position = route.points[0].transform.position; } // Update is called once per frame void Update() { var v = route.points[pointIndex + 1].transform.position - route.points[pointIndex].transform.position; transform.position += v.normalized * speed * Time.deltaTime; var pv = transform.position - route.points[pointIndex].transform.position; if (pv.magnitude >= v.magnitude) { pointIndex++; if (pointIndex >= route.points.Length - 1)//最後まで到達した { Destroy(gameObject); //TODO プレイヤーにダメージ } } } } |
では保存してUnityEditorで再生をしてみましょう。
ゴールに到着した敵は消えるようになりエラーも無くなりました。
おさらいと次回予告
今回は敵(Enemy)が作成した経路(Route)に従って移動するようになりました。
次回は、この敵を生成する仕組みを作っていきます。
次回の記事↓

コメント