top of page

LessonEX 剣を振る/ボールを投げる

EX-1 剣を振る

 ゲームを作る上で「剣を振って敵を攻撃する」というアクションをしたいと思うことは多々あると思います。ここでは剣を振って、振っている途中の剣に当たり判定を実装してみましょう。また、剣を振る動作の応用でボールを投げてみましょう。

 適当な3Dプロジェクトを開いて、こちらのパッケージを

​インポートしてください →→→→→→→→→→→→→→→→→

 ユニティちゃんと剣のモデル、アニメーションが同梱されて

​います。

 インポートできたら新しいシーンを作成して、地面とユニティちゃんを配置してください。流れは3Dアクションゲーム編Lesson1の序盤と同じです。ここでは追尾カメラの実装まではしないので、ユニティちゃんを見やすいようにカメラの位置を調整してください。

 剣を振るアニメーションを設定するために、ユニティちゃんにアニメーターを設定しましょう。

 プロジェクト内で右クリックして「Create」→「Animator Controller」を追加してください。名前は何でもOKです(サンプルでは「PlayerAnim」)

 PlayerAnimをユニティちゃんにドラッグ&ドロップしてください。

 PlayerAnimをダブルクリックして、Animatorウィンドウを開いてください。

​ Attackトリガーを追加して、IdleステートとAttackステートを作成してください。

3Dアクションゲーム編1-7を参考にしましょう)

​ TriggerはBoolと扱いが似ていますが、有効にしたあと自動で元に戻る性質があります。trueにすると次のフレームで勝手にfalseに戻るbool型をイメージすると分かりやすいかと思います。

 IdleステートのMotionは「2Hand-Sword-Idle」、AttackステートのMotionは「2Hand-Sword-Attack1」に設定してください。

 IdleとAttackを相互に繋げるトランジションを作成してください。

 トランジションの設定をしてください。

 

​ IdleからAttack: HasExitTimeのチェックを外す、Conditions(遷移条件)はAttackトリガー

 AttackからIdle: HasExitTimeにチェックを入れる、Conditionsは不要

 Animatorウィンドウをドラッグして、Gameウィンドウと同時に見られるように移動してください。

​ 移動できたらゲームを実行して、AnimatorウィンドウのAttackトリガーをクリックしてみましょう。

 アニメーションを再生すると足が地面に埋まってしまう場合、IdleとAttackステートを選択してFoot IKにチェックを入れてください。

 IKとはInverse Kinematics(インバースキネマティクス)の略で、足の衝突解決を行ってそこから逆算的に膝などの座標を決める手法です。

Unity Tips!

 Idleアニメーション中は問題ありませんが、Attackアニメーションを再生した瞬間にエラーが発生してしまいます。​これは外部からアニメーションをインポートした時に時々起こる問題で、アニメーションに埋め込まれているアニメーションイベントによるエラーになります。

 「Animation」→「2Hand-Sword」→「RPG-Character@2Hand-Sword-Attack1」を選択して、「2Hand-Sword-Attack1.FBX​」内のアニメーションクリップ(三▲ みたいなアイコン)をダブルクリックしてください。アニメーションウィンドウが開きます。

 アニメーションウィンドウの15フレーム目に「Hit」という名前のキーが置かれています。これがアニメーションイベントで、アニメーションイベントのタイミングで特定の関数を呼び出すことができるという機能です。アニメーションの制作者が設定してくれたものですが、呼び出す関数がないためエラーが出ているという感じです(アニメーションイベントについては後で詳しく解説します}

 とにかく今はこのアニメーションイベントを一旦削除したいところです。ですが、FBXに埋め込まれているアニメーションは直接編集することができません。

 この問題を解決するためには「2Hand-Sword-Attack1.FBX​」からアニメーションクリップだけを取り出す必要があります。これはアニメーションクリップをコピー&ペーストすることで取り出すことが可能です。

​ アニメーションクリップ(三▲ のアイコン)を選んだ状態で、キーボードのCtrl+Cを押してからCtrl+Vを押してください。この措置を行うと「2Hand-Sword-Attack1.anim」が生成されます。

 生成されたアニメーションクリップ(2Hand-Sword-Attack1.anim)をダブルクリックして、アニメーションウィンドウを開いてください。

EX16

 先ほどの措置で生成したアニメーションクリップは編集することができます。15フレーム目のアニメーションイベントを選択して、Deleteキーを押してください。

 アニメーションイベントを削除できたら、新しく生成した方のアニメーションクリップをAttackステートのMotionにドラッグ&ドロップしてください。

 この状態でゲームを開始して、Attackトリガーをクリックしてください。今度はエラーが起きずに、剣を振るアニメーションを再生できると思います。

​ いよいよユニティちゃんに剣を持たせます。と言っても、3Dモデルに物を持たせるのは簡単です。しっかり剣を持たせたい場合はアニメーション側での調整が必要ですが、遠目に持っているように見せるだけであれば、Unity側だけで解決することができます。

 まずは「Model」→「LongSword」→「Prefabs」→「LongSword」をシーン​上にドラッグ&ドロップしてください。

 剣をユニティちゃんの右手の子オブジェクトにしましょう。子オブジェクトは親オブジェクトに追従するため、右手の子オブジェクトに剣を設定することで剣を持つことが可能です。

 ヒエラルキーで「unitychan」→「Character1_Reference」→「Character1_Hips」→「Character1_Spine」→「Character1_Spine1」→「Character1_Spine2」→「Character1_RightShoulder」→「Character1_RightArm」→「Character1_RightForeArm」→「Character1_RightHand」→「Character1_RightHandThumb1」

と開いていき、剣をCharacter1_RightHandThumb1の子オブジェクトにしてください

EX21

 子オブジェクトにできたらゲームを開始してください。2Hand-Sword-Idle アニメーションから開始するはずなので、この状態でゲームを一時停止しましょう。

​ 一時停止したらシーンビューに切り替えて、右手に合う位置に剣の座標や回転を調整してください。

【サンプルのパラメータ(参考までに)】

 Position X=-0.057 Y=0.019 Z=0.018

 Rotation X=48.046 Y=-73.922 Z=39.708

 アニメーションウィンドウのAttackトリガーをクリックして、攻撃アニメーションを再生してみましょう。前述した通り、剣が右手に追従するため剣を振ることができます。至近距離で見ると違和感が出ますが、遠くから見る分にはしっかり振っているように見えるはずです。

​ このようにオブジェクトを手の子オブジェクトにすることで、剣に限らずキャラクターに色々なものを持たせることができます。

​※ まだゲームの実行は止めないでください!

 しかし、剣の座標調整はゲームの実行中に行ったものであるため、ゲームを止めると値が元に戻ってしまいます

 それを防ぐために現在のパラメータを保持しておきます。剣のインスペクター内でTransformの右上にある3つの点のボタンをクリックしてください。「Copy」→「Component」と選択することで、値をコピーしておくことができます。

 コピーできたらゲームを終了して、再度剣のTransform右上のボタンから「Paste」→「Component Values」を選択してください。実行中に設定したパラメータに戻すことができます。

 興味のある人は他のアニメーションも再生して、ユニティちゃんが剣をしっかり持てているか確認してみましょう。

 ここまでの流れで剣を持つことができましたが、この剣で衝突判定が取れなければゲームが作れません。剣の攻撃判定を作ってみましょう。

 先ほど軽く触れたアニメーションイベントを使って、攻撃アニメーションの開始と終了を判定します。アニメーションイベントとは、スクリプト内の関数をアニメーション中の特定のタイミングで呼び出すことができる機能です(k2Engineにも同じ機能があります)

 今回は剣の「振り始め」と「振り終わり」にアニメーションイベントを設定し、剣を振っている間だけ当たり判定を有効にすることで攻撃判定を実装しましょう。

 まずは剣に当たり判定を追加しましょう。

​ LongSwordを選択して、BoxColliderをアタッチしてください。

 BoxColliderのEdit Colliderを選択して、剣全体を覆うように当たり判定を調整しましょう。

 Is Triggerにチェックを入れておきましょう。これで剣が他の当たり判定を貫通するようになります。

 次は攻撃される側のオブジェクトを作成します。今回はサンプルなのでただの箱にします。

​ Enemyタグを作成して、設定しておいてください(タグについては3Dアクションゲーム編1-6参照)

 プレイヤーのスクリプトを実装します。

 今回は歩きのアニメーションなどは実装せずに、ただ「WASDキー入力で立ちモーションのまま移動」「Enterキーが押されたら攻撃モーションを再生」といった流れだけにします。

​ Playerスクリプトを作成して、以下のように入力してください。攻撃以外は3Dアクションゲーム編Lesson1とほとんど同じです。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

 

public class Player : MonoBehaviour
{
    public float MoveSpeed = 0.01f;

 

    Animator m_animator;
 

    void Start()
    {
        // 自分にアタッチされているAnimatorを取得
        m_animator = GetComponent<Animator>();
    }

 

    void Update()
    {
        // 移動速度を初期化
        Vector3 move = Vector3.zero;

 

        // 前後移動
        if (Input.GetKey(KeyCode.W))
        {
            move.z += MoveSpeed;
        }
        if (Input.GetKey(KeyCode.S))
        {
            move.z += -MoveSpeed;
        }
        // 左右移動
        if (Input.GetKey(KeyCode.D))
        {
            move.x += MoveSpeed;
        }
        if (Input.GetKey(KeyCode.A))
        {
            move.x += -MoveSpeed;
        }

        // 移動させる
        transform.localPosition += move;

 

        // 回転
        if (move.sqrMagnitude > 0.0f)
        {
            transform.rotation = Quaternion.LookRotation(move.normalized);
        }

 

        // エンターキーが押されたら攻撃アニメーションを再生
        if (Input.GetKeyDown(KeyCode.Return))
        {
            m_animator.SetTrigger("Attack");
        }

 

    }

}

 AnimatorクラスのSetTrigger関数は名前の通りトリガーを有効にする関数です。前述の通りTriggerは自動で元に戻るため、引数は対象の名前だけで構いません。

​ コードが書けたら保存して、ユニティちゃんにPlayerスクリプトをアタッチしてください。実行するとWASDキーで移動して、Enterキーで攻撃ができるようになっているはずです。

 しかし、このままでは常に剣の当たり判定が有効になってしまいます。攻撃中だけ剣の当たり判定を有効にするためにアニメーションイベントを設定しましょう。

​ 先ほど作成したAttackのアニメーションクリップをダブルクリックして、Animationウィンドウを開いてください。

 再生バーの位置を14フレーム目に合わせて、Add eventボタンをクリックしてください。アニメーションイベントが追加されます。

 24フレーム目にもアニメーションイベントを追加してください。

 アニメーションクリップをクリックしてからアニメーションイベントをクリックしてください。インスペクターにアニメーションイベントの情報が表示されます。

 アニメーションイベントのFunctionで、このアニメーションイベントのタイミングで呼び出す関数の名前を指定することができます。

​ 攻撃開始のタイミングでは「AttackStart」関数、攻撃終了のタイミングでは「AttackEnd」関数を呼ぶようにしてください。

 AttackStart関数とAttackEnd関数を追加しましょう。

​ Playerスクリプトを開いて、赤い部分のコードを追加してください。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

 

public class Player : MonoBehaviour
{
    public float MoveSpeed = 0.01f;

 

    Animator m_animator;
 

    public BoxCollider SwordCollider;   // 剣の当たり判定
 

    void Start()
    {
        // 自分にアタッチされているAnimatorを取得
        m_animator = GetComponent<Animator>();
        // 剣の当たり判定を無効にする
        SwordCollider.enabled = false;

    }

 

    void Update()
    {
        // 移動速度を初期化
        Vector3 move = Vector3.zero;

 

        // 前後移動
        if (Input.GetKey(KeyCode.W))
        {
            move.z += MoveSpeed;
        }
        if (Input.GetKey(KeyCode.S))
        {
            move.z += -MoveSpeed;
        }
        // 左右移動
        if (Input.GetKey(KeyCode.D))
        {
            move.x += MoveSpeed;
        }
        if (Input.GetKey(KeyCode.A))
        {
            move.x += -MoveSpeed;
        }

        // 移動させる
        transform.localPosition += move;

 

        // 回転
        if (move.sqrMagnitude > 0.0f)
        {
            transform.rotation = Quaternion.LookRotation(move.normalized);
        }

 

        // エンターキーが押されたら攻撃アニメーションを再生
        if (Input.GetKeyDown(KeyCode.Return))
        {
            m_animator.SetTrigger("Attack");
        }

 

    }
 

    // 攻撃開始
    void AttackStart()
    {
        // 当たり判定を有効にする
        SwordCollider.enabled = true;
        // デバッグ
        Debug.Log("攻撃開始");
    }

 

    // 攻撃終了
    void AttackEnd()
    {
        // 当たり判定を無効にする
        SwordCollider.enabled = false;
        // デバッグ
        Debug.Log("攻撃終了");
    }

 

}

 (コンポーネント名).enabled はコンポーネントの有効/無効を切り替えるためのパラメータです。trueの間は有効で、falseの間は無効になります。

 前述の通りAttackStartやAttackEnd関数はアニメーションイベントから呼び出されるので、スクリプト内で呼び出す必要はありません。これで剣を振っている間だけ当たり判定が有効になります。

​ 最後に剣に「当たり判定がEnemyに触れたら、触れたEnemyを削除する」スクリプトをアタッチして完成させましょう。

​ Swordスクリプトを作成して、以下のように入力してください。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

 

public class Sword : MonoBehaviour
{
    // 自分に何かが接触した瞬間呼ばれる
    private void OnTriggerEnter(Collider other)
    {
        // 触れたオブジェクトのタグがEnemyなら…
        if (other.CompareTag("Enemy"))
        {
            // 対象を削除する
            Destroy(other.gameObject);
        }
    }
}

 PlayerスクリプトにBoxColliderを指定するパラメータが追加されているので、LongSwordをドラッグ&ドロップしてください。ここで指定したBoxColliderが、剣を振っている間だけ有効になります。

 あと少しです。最後に剣の設定を完成させましょう。

 3Dアクションゲーム編では説明しませんでしたが、実はOnTriggerEnter や OnCollisionStay を使用したい場合、最低でもどちらかにはRigidbodyがアタッチされている必要があるというルールがあります。

​ 剣にRigidbodyをアタッチしてください。ただし重力や物理演算は不要なのでUse Gravity(重力を使用するか)はチェックを外し、Is Kinematic(物理演算を止めるか)はチェックを入れておいてください。ゲームの見た目的には変わりませんが、この措置がないと後ほど攻撃判定が取れないので注意しましょう。

 最後に剣にSwordスクリプトをアタッチしてください。

 これで剣で攻撃判定を取るための準備が完了しました。

​ ゲームを実行してEnterキーを押して攻撃してみましょう。コンソールに攻撃開始と攻撃終了のタイミングでログが出力されます。

 攻撃中でない時の剣がEnemyに触れても何も起きませんが、攻撃中の剣がEnemyに触れるとEnemyが消えます。確認してみましょう。

 これで剣で攻撃する処理は完成です。

​ サンプルでは攻撃するとEnemyが消えるだけでしたが、体力を実装したりエフェクトを追加することでより実際のゲームに近づけることができます。ぜひ挑戦してみてください。

 ボーンの子オブジェクトにすることで、簡単にオブジェクトに装飾を追加することができます。

​ 以下のサンプルでは剣の子オブジェクトにSphereを、ユニティちゃんの頭の子オブジェクトにCubeを追加しています。

 ボーンの子オブジェクトにすることで、ユニティちゃんがアニメーションしてもしっかり追尾するようになります。

 ボーンと親子関係を用いた追尾は色々な場所で役に立ちます。キャラクターをカスタムしたり、剣からエフェクトを発生させたりなど、ゲームの内容に応じて活用してみてください。

Unity Tips!

EX-2 ボールを投げる

 剣での攻撃を応用して、ボールを投げてみましょう。EX1で解説した内容は省略して、新しい要素だけ解説します。

​ まずはEX1でユニティちゃんに持たせた剣を削除して、使用するアニメーションを変更してください。IdleのMotionを「WAIT00」に変更してください。

 AttackのMotionを「Unarmed-Attack-L1」に変更してください。

​(本当はボールを投げるモーションを使いたかったのですが、ボールを投げるアニメーションがフリーで配布されていなかったのでサンプルではパンチモーションで代用します…)

 「Animation」フォルダ内にあるUnarmed-Attack-L1.animをダブルクリックして開いてください。

​ EX1と同じ流れでアニメーションイベントを設定してください。11フレーム目に「Throw」関数を呼び出すようにしましょう。

EX43

 投げるボールを実装します。

 「3D Object」→「Sphere」を追加して、Rigidbodyをアタッチしてください。大きさも調整しておきましょう。

​ 白色では見えにくいため、目立つ色のマテリアルを作成して貼っておくのがオススメです。

 ボールにBallタグを作成して設定してください。

​ これで事前準備は完了です。

 それでは、ボールを投げるために以下の処理を実装していきましょう。

1. ボールとプレイヤーが近くなったらボールを拾う

  ボールを持っている間、ボールは手の子オブジェクトにする

2. Throw関数でボールに対して前方に力を加える

 まずは1の「ボールとプレイヤーが近くなったらボールを拾う」処理を実装しましょう。

​ Playerスクリプトを開いて、赤い部分のコードを追加してください。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

 

public class Player : MonoBehaviour
{
    public float MoveSpeed = 0.01f;

    Animator m_animator;
 

    // 剣は使わないのでコメントアウト
    //public BoxCollider SwordCollider;   // 剣の当たり判定

 

    GameObject BallObject;      // ボール
    bool isBallGet = false;     // ボールを持っているかどうか
    bool isBallCanGet = false;  // ボールを持てるかどうか(投げた瞬間に拾えないように)
    public GameObject Hand;     // ボールの親オブジェクトとなる手

 

    void Start()
    {
        // 自分にアタッチされているAnimatorを取得
        m_animator = GetComponent<Animator>();
        // 剣の当たり判定を無効にする
        //SwordCollider.enabled = false;
        // ボールを取得

        BallObject = GameObject.FindGameObjectWithTag("Ball");
    }

 

    void Update()
    {
        // 移動速度を初期化
        Vector3 move = Vector3.zero;

 

        // 前後移動
        if (Input.GetKey(KeyCode.W))
        {
            move.z += MoveSpeed;
        }
        if (Input.GetKey(KeyCode.S))
        {
            move.z += -MoveSpeed;
        }
        // 左右移動
        if (Input.GetKey(KeyCode.D))
        {
            move.x += MoveSpeed;
        }
        if (Input.GetKey(KeyCode.A))
        {
            move.x += -MoveSpeed;
        }

        // 移動させる
        transform.localPosition += move;

 

        // 回転
        if (move.sqrMagnitude > 0.0f)
        {
            transform.rotation = Quaternion.LookRotation(move.normalized);
        }

 

        // ボールを持っている状態でエンターキーが押されたら攻撃(投げる)アニメーションを再生
        if (Input.GetKeyDown(KeyCode.Return) && isBallGet == true)
        {
            m_animator.SetTrigger("Attack");
        }

 

        // もしボールを持っていない かつボールを拾えるなら
        if (isBallGet == false && isBallCanGet == false)
        {

            // 自分とボールの距離が近いなら拾う
            if(Vector3.Distance(transform.position, BallObject.transform.position) <= 1.0f)
            {

                // ボール所持状態になる
                isBallGet = true;
                // ボールの親オブジェクトを自分の手にする
                BallObject.transform.parent = Hand.transform;
                // ボールのローカル座標を調整
                BallObject.transform.localPosition = Vector3.zero;
                // ボールの物理演算を止める
                BallObject.GetComponent<Rigidbody>().isKinematic = true;
            }

        }

    }

 

    // 攻撃開始
    void AttackStart()
    {
        // 当たり判定を有効にする
        //SwordCollider.enabled = true;

        // デバッグ
        Debug.Log("攻撃開始");
    }

 

    // 攻撃終了
    void AttackEnd()
    {
        // 当たり判定を無効にする
        //SwordCollider.enabled = false;
        // デバッグ

        Debug.Log("攻撃終了");
    }

}

【プログラムの解説】

​・Vector3.Distance 関数は第一引数の座標と第二引数の座標の距離を返す関数です。

​ 今回はプレイヤーとボールの距離を計算して、ボールの取得判定を行っています。

・GameObject.transform.parent でオブジェクトの親オブジェクトを取得、変更できます。

​・RigidbodyクラスのisKinematicはEX1で解説した通り、物理演算の有効/無効を切り替えるパラメータです。

 ボールの親オブジェクトとなるオブジェクトを指定しましょう。

​ ヒエラルキーで「unitychan」→「Character1_Reference」→「Character1_Hips」→「Character1_Spine」→「Character1_Spine1」→「Character1_Spine2」→「Character1_LeftShoulder」→「Character1_LeftArm」→「Character1_LeftForeArm」→「Character1_LeftHand」→「Character1_LeftHandThumb1」→「Character1_LeftHandThumb2」→「Character1_LeftHandThumb3」と開いて、PlayerコンポーネントのHandにドラッグ&ドロップしてください。

 これでボールを拾えるようになりました。​試しに実行して、ボールに近付いてみましょう。

 次に2の「Throw関数でボールに対して前方に力を加える」処理を実装します。先ほど追加したアニメーションイベントを使用しましょう

 Playerスクリプトを開いて、赤い部分のコードを追加してください。

~前略~

    // 攻撃終了
    void AttackEnd()
    {
        // 当たり判定を無効にする
        //SwordCollider.enabled = false;
        // デバッグ

        Debug.Log("攻撃終了");
    }

 

    // 投げる
    void Throw()
    {
        // ボールの親オブジェクトをなくす
        m_ballObject.transform.parent = null;
        // ボールの物理演算を再開
        m_ballObject.GetComponent<Rigidbody>().isKinematic = false;
        // ボールに力を加える
        m_ballObject.GetComponent<Rigidbody>().AddForce(transform.forward * 10.0f, ForceMode.Impulse);

 

        // しばらくボールを拾えない状態になる
        isBallGet = false;
        m_isBallCanGet = true;
        Invoke("BallCanGet", 1.0f);
    }

 

    // ボールを拾えるようにする
    void BallCanGet()
    {
        m_isBallCanGet = false;
    }


}

【プログラムの解説】

​​・Invoke関数は指定した関数を一定秒数後に呼び出すことができる関数です。k2Engineではタイマーを設定するなどしていたと思いますが、Unityでは単純に一定秒数後に呼び出すだけならInvoke関数を使うと便利です。第一引数に関数名、第二引数に待機時間を設定します。

 Invoke(呼び出す関数の名前,何秒後に呼び出すか);

※ 関数名で検索しているので名前間違いに注意!

​ これでボールを投げることができるようになりました。実行して確認してみましょう。

 EXの物理マテリアルを設定することでボールを弾ませることもできます。キャラクターに物を持たせたり、アニメーションイベントを使って様々な処理を実装してみてください。

【剣のモデル配布元】
https://assetstore.unity.com/packages/3d/props/weapons/long-sword-212082
【アニメーション配布元】
https://assetstore.unity.com/packages/3d/animations/rpg-character-mecanim-animation-pack-free-65284

河原電子ビジネス専門学校
​ゲームクリエイター科

bottom of page