Unity 6.3 LTS|3D的当てゲームを作ろう⑫ 敵をRigidbodyで左右に巡回させよう

Robot Sphereの敵を配置したUnityゲーム画面 Unity

第12回では、Robot Sphereの敵をRigidbodyで動かし、一定範囲を左右に巡回させます。物理処理を担当する親と見た目を担当する子を分け、EnemyPatrolスクリプトで移動と折り返しを作ります。

今回できること

親は移動と当たり判定、子は見た目という役割に分けます。その後、Rigidbodyの速度を使って敵を左右へ動かし、開始位置から指定距離へ到達したら反対方向へ折り返します。

STEP 1

敵を親・回転軸・モデルの3段に分けよう

Hierarchyを次の形にします。現在の親はSphereですが、あとでEnemyのような名前へ変えても構いません。

Sphere(移動・物理・当たり判定)
└─ ModelRoot(進行方向へ回す軸)
   └─ robotSphere(画面に表示するモデル)
Sphereの子にModelRoot、その子にrobotSphereを置いたHierarchy
Rigidbodyで動かす親と、向きを変えるModelRoot、見た目のrobotSphereを分けました。
SphereRigidbody、Collider、EnemyAttack、これから作る巡回スクリプトを持ちます。
ModelRoot敵モデルを進行方向へ向けるための回転軸です。
robotSphereMeshとMaterialを使って実際の敵を表示します。
STEP 2

親からMesh FilterとMesh Rendererを削除しよう

親のSphereにMesh FilterとMesh Rendererが残っていると、親の球と子のRobot Sphereが同時に表示されます。白い球が重なったり、モデルが不自然に大きく見えたりする原因になります。

親SphereにMesh FilterとMesh Rendererが残っているInspector
親に表示用Componentが残っている状態です。今回、この2つを削除しました。

親のSphereを選択し、各Component右上のメニューからRemove Componentを実行します。

親Sphereから削除
1. Mesh Filter
2. Mesh Renderer

削除後も子のrobotSphereにはMesh FilterやMesh Rendererが必要です。子からは削除しません。

大切な考え方

親はゲームの仕組み、子は見た目を担当します。見た目を別のモデルへ交換しても、親のRigidbodyや攻撃処理をそのまま再利用できる構成です。

STEP 3

Rigidbodyの設定を確認しよう

敵の親に設定したRigidbodyのInspector
Use Gravityをオン、Is Kinematicをオフにし、回転はXYZすべて固定します。

敵が物理演算で落下し、床へ乗るように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へ変更します。

STEP 4

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は止める

同じ敵にMovingTargetが付いている場合は削除するか、Component左上のチェックを外します。Transformを直接動かす処理とRigidbodyの移動を同時に使うと、敵が震えたり位置がずれたりします。

STEP 5

Enemyへ追加して値を設定しよう

EnemyPatrolを親のSphereへドラッグし、Inspectorを次のように設定します。

Move Speed    = 2
Move Distance = 3
Turn Speed    = 10
Model Root    = ModelRoot

Model Root欄には、Hierarchyにある子のModelRootをドラッグします。親のSphereやrobotSphereを登録しないようにします。

Move Speed敵が1秒間に進む速さです。最初は2がおすすめです。
Move Distance開始位置から片側へ進める距離です。3なら左右に約3mずつ動きます。
Turn SpeedModelRootが進行方向へ向く速さです。
STEP 6

左右移動のコードを読み解こう

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へ切り替えます。これが敵を往復させる折り返し処理です。

STEP 7

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だけを進行方向へなめらかに向けます。

CHECK

再生して巡回を確認しよう

Robot Sphereの敵を表示したゲーム画面
まず1体だけで確認し、正常に動いたらPrefabへ反映してほかの敵へ広げます。
  • 白い球がRobot Sphereへ重なっていない
  • 敵が地面へ落ちて停止する
  • 敵が左右へ一定距離移動する
  • 端まで進むと反対方向へ折り返す
  • ModelRootが進行方向を向く
  • 敵が転がったり横倒しになったりしない
  • 敵を撃つと得点が入り、敵全体が消える
  • Consoleにエラーが出ていない
移動方向を変えたいとき

親のSphereをY軸で回転します。EnemyPatrolは再生開始時のtransform.rightを巡回方向として覚えるため、ステージに合わせて横方向や奥行き方向へ変更できます。

次回へ

壁や足場を見つけて自動で折り返そう

次回はRaycastなどで壁や足場の端を調べ、距離だけではなくステージの形に合わせて敵が方向転換できるように発展させます。

コメント

タイトルとURLをコピーしました