第12回では、Robot Sphereの敵をRigidbodyで動かし、一定範囲を左右に巡回させます。物理処理を担当する親と見た目を担当する子を分け、EnemyPatrolスクリプトで移動と折り返しを作ります。
親は移動と当たり判定、子は見た目という役割に分けます。その後、Rigidbodyの速度を使って敵を左右へ動かし、開始位置から指定距離へ到達したら反対方向へ折り返します。
敵を親・回転軸・モデルの3段に分けよう
Hierarchyを次の形にします。現在の親はSphereですが、あとでEnemyのような名前へ変えても構いません。
Sphere(移動・物理・当たり判定)
└─ ModelRoot(進行方向へ回す軸)
└─ robotSphere(画面に表示するモデル)
親からMesh FilterとMesh Rendererを削除しよう
親のSphereにMesh FilterとMesh Rendererが残っていると、親の球と子のRobot Sphereが同時に表示されます。白い球が重なったり、モデルが不自然に大きく見えたりする原因になります。
親のSphereを選択し、各Component右上のメニューからRemove Componentを実行します。
親Sphereから削除
1. Mesh Filter
2. Mesh Renderer
削除後も子のrobotSphereにはMesh FilterやMesh Rendererが必要です。子からは削除しません。
親はゲームの仕組み、子は見た目を担当します。見た目を別のモデルへ交換しても、親のRigidbodyや攻撃処理をそのまま再利用できる構成です。
Rigidbodyの設定を確認しよう
敵が物理演算で落下し、床へ乗るようにUse Gravityをオンにします。接触したときに球体が転がらないよう、Freeze RotationのX・Y・Zをオンにします。
Mass = 1
Linear Damping = 0
Angular Damping = 0.05
Use Gravity = On
Is Kinematic = Off
Interpolate = Interpolate
Collision Detection = Continuous
Freeze Position X/Y/Z = Off
Freeze Rotation X/Y/Z = On
画像ではInterpolateがNone、Collision DetectionがDiscreteになっています。巡回を滑らかに見せ、移動中の衝突を安定させるため、それぞれInterpolateとContinuousへ変更します。
EnemyPatrolスクリプトを作ろう
Projectウィンドウで右クリックし、Create → Scripting → MonoBehaviour Scriptを選びます。名前をEnemyPatrolにして、次のコードへ書き換えます。
EnemyPatrol.csの完成コード
using UnityEngine;
[RequireComponent(typeof(Rigidbody))]
public class EnemyPatrol : MonoBehaviour
{
public float moveSpeed = 2f;
public float moveDistance = 3f;
public float turnSpeed = 10f;
public Transform modelRoot;
Rigidbody rb;
Vector3 startPosition;
Vector3 patrolDirection;
int moveDirection = 1;
void Start()
{
rb = GetComponent<Rigidbody>();
startPosition = rb.position;
patrolDirection = transform.right;
patrolDirection.y = 0f;
patrolDirection.Normalize();
}
void FixedUpdate()
{
Vector3 difference =
rb.position - startPosition;
float movedDistance =
Vector3.Dot(
difference,
patrolDirection);
if (moveDirection > 0 &&
movedDistance >= moveDistance)
{
moveDirection = -1;
}
else if (moveDirection < 0 &&
movedDistance <= -moveDistance)
{
moveDirection = 1;
}
Vector3 patrolVelocity =
patrolDirection *
moveDirection *
moveSpeed;
Vector3 currentVelocity =
rb.linearVelocity;
rb.linearVelocity =
new Vector3(
patrolVelocity.x,
currentVelocity.y,
patrolVelocity.z);
TurnModel(patrolVelocity);
}
void TurnModel(Vector3 moveVelocity)
{
if (modelRoot == null)
{
return;
}
if (moveVelocity.sqrMagnitude < 0.01f)
{
return;
}
Quaternion targetRotation =
Quaternion.LookRotation(
moveVelocity,
Vector3.up);
modelRoot.rotation =
Quaternion.Slerp(
modelRoot.rotation,
targetRotation,
turnSpeed * Time.fixedDeltaTime);
}
}
同じ敵にMovingTargetが付いている場合は削除するか、Component左上のチェックを外します。Transformを直接動かす処理とRigidbodyの移動を同時に使うと、敵が震えたり位置がずれたりします。
Enemyへ追加して値を設定しよう
EnemyPatrolを親のSphereへドラッグし、Inspectorを次のように設定します。
Move Speed = 2
Move Distance = 3
Turn Speed = 10
Model Root = ModelRoot
Model Root欄には、Hierarchyにある子のModelRootをドラッグします。親のSphereやrobotSphereを登録しないようにします。
左右移動のコードを読み解こう
startPosition = rb.position;
patrolDirection = transform.right;
patrolDirection.y = 0f;
patrolDirection.Normalize();
startPositionに再生開始時の位置を保存します。transform.rightは親の赤いX軸方向です。親をY軸で回転すると、巡回する向きも一緒に変えられます。
Vector3 difference =
rb.position - startPosition;
float movedDistance =
Vector3.Dot(difference, patrolDirection);
Vector3.Dotを使い、開始位置から巡回方向へどれだけ進んだかを1つの数字にします。右側ならプラス、左側ならマイナスになります。
if (moveDirection > 0 &&
movedDistance >= moveDistance)
{
moveDirection = -1;
}
else if (moveDirection < 0 &&
movedDistance <= -moveDistance)
{
moveDirection = 1;
}
右端へ到達したら-1、左端へ到達したら1へ切り替えます。これが敵を往復させる折り返し処理です。
Rigidbodyの速度で動かそう
Vector3 patrolVelocity =
patrolDirection *
moveDirection *
moveSpeed;
Vector3 currentVelocity =
rb.linearVelocity;
rb.linearVelocity =
new Vector3(
patrolVelocity.x,
currentVelocity.y,
patrolVelocity.z);
XとZへ巡回速度を入れ、Yには現在の落下速度を残します。Yを0にすると重力による落下が毎回打ち消されるため、currentVelocity.yを使います。
Rigidbodyを変更する処理なので、毎フレーム呼ばれるUpdateではなく、物理演算と同じ間隔で呼ばれるFixedUpdate()に書きます。
Quaternion targetRotation =
Quaternion.LookRotation(
moveVelocity,
Vector3.up);
modelRoot.rotation =
Quaternion.Slerp(
modelRoot.rotation,
targetRotation,
turnSpeed * Time.fixedDeltaTime);
親のRigidbodyは回転固定のまま、子のModelRootだけを進行方向へなめらかに向けます。
再生して巡回を確認しよう
- 白い球がRobot Sphereへ重なっていない
- 敵が地面へ落ちて停止する
- 敵が左右へ一定距離移動する
- 端まで進むと反対方向へ折り返す
- ModelRootが進行方向を向く
- 敵が転がったり横倒しになったりしない
- 敵を撃つと得点が入り、敵全体が消える
- Consoleにエラーが出ていない
親のSphereをY軸で回転します。EnemyPatrolは再生開始時のtransform.rightを巡回方向として覚えるため、ステージに合わせて横方向や奥行き方向へ変更できます。
壁や足場を見つけて自動で折り返そう
次回はRaycastなどで壁や足場の端を調べ、距離だけではなくステージの形に合わせて敵が方向転換できるように発展させます。

コメント