【Unity】CharacterControllerのMove()とSimpleMove()の使い分け

前置き

Unityの便利な機能に、CharacterControllerというものがあります。
これはRigidbodyなどコンポーネントを突っ込むだけで利用できる機能とは異なり、制御用のスクリプトを記述する必要があります。
サンプルスクリプトは公式リファレンスに掲載されているのですが、CharacterControllerの頁ではなく、その中のMove()またはSimpleMove()の頁に存在し、それぞれの解説では「絶対値の移動デルタを受け取り複雑な移動を行います。(Move)」「キャラクターを speed 付きで動かします。(SimpleMove)」と書かれています。

......この説明、ちょっと分かりにくいと思いませんか?

ちなみに英文ではより詳しく説明されているため、そちらを翻訳サイトに投げることで大方理解はできます。
しかし私は当初この日本語だけ読んで理解するのに偉く時間を取られたので、私のあと沼にハマる人が一人でも減ることを願い、これらの使い分けについて記述します。

本題

ザックリ言ってしまうと。

  • Move()はジャンプをさせたい、重力を操作したいとき
  • SimpleMove()はジャンプをしない、左右キーで回転させたいとき

となります。

SimpleMove()の方が制限が多い代わりに、上記の要件を満たすゲームではサンプルコードをコピペするだけでほぼ完全に動いてくれます。
それ以外の場合は基本的にMove()を使うことになります。

というわけでMove()のサンプルコードを引っ張ってくると、こんな感じの動きになりました。

manu.gif

動かしたいオブジェクトのInspectorビューにCharacterControllerを追加し、サンプルスクリプトを追加します。スクリプトをコピペするときは、ファイル名とクラス名の不一致が無いよう注意してください(サンプルではExampleClassとなっているので、自分で作成したファイル名に修正が必要です)。

このサンプルだと左右のキー入力で平行移動となり、回転ができないんですよね。
回転をさせたい場合はこのスクリプトを書き換える必要があるため、そちらは記事を分割して解説したいと思います。(18.04.10追記 書きました)

メインの解説はここまでです。以下ではサンプルスクリプトの詳細な解説を記載しました。サンプルスクリプトだけ見せられても全然分からん! という方の参考になればと思います。


サンプルスクリプトの解説

※自分で記述するスクリプトなのでファイル名は任意です

MyCharacterController.cs
//Unityスクリプトリファレンス/CharacterController.Moveより
//https://docs.unity3d.com/ja/current/ScriptReference/CharacterController.Move.html

using UnityEngine;
using System.Collections;

//クラス名はファイル名に一致させる
public class MyCharacterController : MonoBehaviour {
    public float speed = 6.0F;
    public float jumpSpeed = 8.0F;
    public float gravity = 20.0F;
    private Vector3 moveDirection = Vector3.zero;
    void Update() {
        CharacterController controller = GetComponent<CharacterController>();
        if (controller.isGrounded) {
            moveDirection = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
            moveDirection = transform.TransformDirection(moveDirection);
            moveDirection *= speed;
            if (Input.GetButton("Jump"))
                moveDirection.y = jumpSpeed;

        }
        moveDirection.y -= gravity * Time.deltaTime;
        controller.Move(moveDirection * Time.deltaTime);
    }
}

Update()内の一行目、GetComponent<CharacterController>()で、このスクリプトを保有するオブジェクトからCharacterControllerコンポーネントを探してきて取得しています。
......この処理って同じコンポーネントを毎フレーム取得し直していて、かなり負荷が大きそうですよね? サンプルスクリプトは実行速度よりも読みやすさを重視しているという事でしょうか。

そして前後左右の移動はcontroller.isGroundがtrueの時だけ実行します。isGroundは関数ではなく変数なので注意。

Vector3型のオブジェクトを新規に作成して、ローカル変数moveDirectionに格納します。
xの値にはInput.GetAxis("Horizontal")、yの値は0、zの値にはInput.GetAxis("Vertical")を指定します。
Input.GetAxis()はキーボードの十字キーやWASDキー、パッドコントローラのジョイスティックなどの移動に使いそうな入力から、-1~1までのfloat値を返します。入力の開始や終了時にイイ感じに増大・減衰してくれるので、見た目のクオリティもちょっと上がります。
引数に指定するHorizontalとVerticalはただの文字列なので、前後のダブルクォーテーション「""」を忘れずに。また文字列を書き間違えてもconsoleビューで指摘されないため注意しましょう。

この行で作られたベクトルはあくまでオブジェクトから見た相対的な進行方向なので、これをワールド視点での絶対的な進行方向に変換する必要があります。

その変換をするのが、次の行で実行されるtransform.TransformDirection()です。ここが一番のキモですかね。

こうして無事取得できた絶対的な方向ベクトルにメンバ変数のspeedを掛けてあげれば、無事平行移動の完成です。

もしJumpボタンが押されていたら、y方向にも動くよう値を増やします。
平地でジャンプした場合はその瞬間にisGroundの条件を脱するので、ボタンの長押しや連打で永遠にジャンプし続けることはありません。
このif文、処理が1行なので中括弧{ }を省略していますが、この書き方はいつか後悔する日が訪れるので推奨はできません。理由を語り始めると脱線しすぎるので興味のある方は調べてみてください。

isGroundのif文を抜けたところで重力を加えています。一見すると地面にめり込み続けてしまう気がしますが、着地しているときはisGroundのif文内で毎フレームy方向の相対値を0に書き換えているので大丈夫です。

そして最終的に得られた絶対的な移動方向のベクトルをcontroller.Move()の引数に入れています。
y方向だけ、直前の重力を掛ける処理とMove()の中で2回Time.deltaTimeが掛けられていますが、これは落下速度を時間の二乗に比例させるためでしょう。高校物理等でお馴染みの公式ですね。

以上がサンプルスクリプトの解説となります。長々とお付き合いいただきありがとうございました。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.