Menu
CoddyTech

ヒーロー

CoddyのUnityジャーニー「Unityにおけるゲームアーキテクチャ」セクションの一部。レッスン 41/50。

この章では、旅の最後のゲームである Arena Survivor を作ります。敵の大群が壁に囲まれたアリーナになだれ込み、剣で撃退します。こちらに向かって歩いてくる雑魚敵と、待ち構えてから突進してくるオーブが登場します。ウェーブを倒すとパワーアップが残り、HUDには体力とウェーブが表示されます。体力が尽きるとプレイが終了し、最良のプレイ記録が保存されます。このゲームでは、このセクションのツールを適した場面で使います。2種類の敵を持つ抽象クラス Enemy、ダメージを受けるすべてのものに使う IDamageable、ScriptableObject アセットとしてのウェーブ、インターフェースを介したピックアップ、C# イベントだけをリッスンする HUD、そして JSON セーブです。12個のスクリプトはすべて最初からプロジェクトにあります。コードが入っているのは Hero だけで、各レッスンで少しずつ追加していきます。

ヒーローの剣は正面にあるものに当たるため、振るときには「正面にあるか」を判定する必要があります。Vector3.Dot(a, b) は2つのベクトルの対応する成分を掛け合わせ、合計します。つまり、a.x * b.x + a.y * b.y + a.z * b.z です。長さが1の2つの方向ベクトルの場合、その値は2つのベクトルのなす角の余弦になります。同じ方向を向いているときは1、30度では0.866、45度では0.707、直角では0、反対方向を向いているときは-1です。したがって、「向いている方向から30度以内」は、角度を計算せずに「内積が0.866以上」と表せます。この警備ロボットは、プレイヤーが10ユニット以内かつ視野角30度以内にいると警報を鳴らします。

public Transform player;
public float range = 10f;

void Update()
{
    Vector3 toPlayer = player.position - transform.position;
    toPlayer.y = 0f;
    bool near = toPlayer.magnitude <= range;
    bool inView = Vector3.Dot(transform.forward, toPlayer.normalized) >= 0.866f;
    if (near && inView)
    {
        Debug.Log("Alarm!");
    }
}

両方の方向ベクトルの長さは1である必要があります。transform.forward の長さは常に1ですが、toPlayer の長さは距離と同じなので、先に正規化します。そうしないと、プレイヤーが2倍遠くにいる場合、内積も2倍になります。正規化する前に y を0に設定しているため、高い通路にいるプレイヤーも、高さではなくマップ上の位置で判定されます。符号によって前後がわかります。内積が0より大きければ前方の半分のどこかにあり、0より小さければ後方のどこかにあります。

challenge icon

チャレンジ

中級

Arena Survivorをヒーローの剣で始めましょう。歩行は次のように実装されています。MoveアクションはSimpleMoveでヒーローを歩かせ、歩く方向を向かせます。Enemyタグが付いた訓練用ダミーが5体、その周囲に立っています。インスペクターでHeroにreach(2)とswingCooldown(0.5)を設定し、スイング中に表示する斬撃も設定します。

  • スペースキーまたは左クリックでスイングします。ただし、swingCooldown秒ごとに1回までです。クールダウンが終わる前に押しても何も起こりません。Keyboard.currentとMouse.currentでキー入力を読み取り、Swing()を呼び出します。
  • Swing()では、斬撃を表示した後、Enemyタグが付いていて、reach以内かつヒーローの前方にあるすべてのオブジェクトを破壊します。リーチ内とは、Physics.OverlapSphereがヒーローの周囲で半径reach以内に見つけたコライダーのことです。前方とは、ヒーローの前方ベクトルと、地面に沿ったオブジェクトへの方向ベクトルの内積が0.5以上であることです。左右どちらにも最大60度までを含みます。

ほかのスクリプトはそのままにしておきます。Playを押し、ダミーの近くまで歩いてスイングしてから、Checkを押しましょう。

自分で試してみよう

using System.Collections;
using UnityEngine;
using UnityEngine.InputSystem;

public class Hero : MonoBehaviour
{
    [SerializeField] private float speed = 5f;
    [SerializeField] private float reach = 2f;
    [SerializeField] private float swingCooldown = 0.5f;
    [SerializeField] private GameObject slash;

    private CharacterController controller;
    private InputAction moveAction;

    void Start()
    {
        controller = GetComponent<CharacterController>();
        moveAction = InputSystem.actions.FindAction("Move");
    }

    void Update()
    {
        Vector2 input = moveAction.ReadValue<Vector2>();
        Vector3 move = new Vector3(input.x, 0f, input.y);
        controller.SimpleMove(move * speed);
        if (move != Vector3.zero)
        {
            transform.rotation = Quaternion.LookRotation(move);
        }

        // Spaceまたは左クリックで攻撃する。攻撃できるのはswingCooldown秒ごとに最大1回
    }

    void Swing()
    {
        StartCoroutine(ShowSlash());
        // 主人公の前方かつreachの範囲内にある、Enemyタグが付いたものをすべて破壊する
    }

    IEnumerator ShowSlash()
    {
        slash.SetActive(true);
        yield return new WaitForSeconds(0.1f);
        slash.SetActive(false);
    }
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

Unityにおけるゲームアーキテクチャのすべてのレッスン