top of page

3Dアクションゲーム編

Lesson3 ステージを作ろう

 プレイヤーの体力を内部的に実装して、ステージを構成するギミックを実装していきます。全てのギミックが完成したら、プレハブ化してオリジナルのステージを作ってみましょう。

3-1

3-1 プレイヤーの体力

3-1 プレイヤーの体力

1

体力管理用の

​スクリプトを作成

 ここからはプレイヤーではなくステージを作っていきます

​ まずはプレイヤーに体力の要素を実装します。と言ってもまだUIがありませんので、内部的に値を用意するだけです。

​ 新しいスクリプト「PlayerHitPoint」を作成してください。

 PlayerHitPointスクリプトを以下のように入力してください。Start関数は削除して構いません。

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

 

public class PlayerHitPoint : MonoBehaviour
{
    public int MaxHitPoint = 3;
    public int HitPoint = 3;

 

    // ダメージ処理
    public void Damage(int damage)
    {
        // Mathf.Clampは値が範囲内に収まるようにする関数
        HitPoint = Mathf.Clamp(HitPoint - damage, 0, MaxHitPoint);
    }

 

    void Update()
    {

    }

}

2

スクリプトを

​アタッチ

 保存できたら、PlayerHitPointスクリプトをユニティちゃんにアタッチしてください。

​ ユニティちゃんのインスペクターにPlayerHitPointコンポーネントが表示されていたらOKです。今はこれだけですが、後でギミックを作る時に使用します。

3-2

3-2 星の実装

3-2 星の実装

1

星のモデルを追加

 最初のギミックである、星を実装しましょう。 

 星を全て集めることがこのゲームのクリア条件になります。

 「Model」→「Christmas-Presents」→「FBX」→「Star」​から、シーン上にスターをドラッグ&ドロップしてください。見覚えのある星が出現します​。

2

プレハブ化の解除

 星のプレハブ化を解除します。

 ヒエラルキー内のStarを右クリックして「Prefab」→「Unpack Completely」を選択してください。何をしているかよくわからないと思いますが、今はあまり気にしなくても大丈夫です。

3

大きさを調整

 星が大きすぎるのでScaleを調整します。X、Y、Z全てに0.5を入力してください。

 座標も好きな場所に動かして構いません。

4

当たり判定の​設定

 星の影が少しおかしい点が気になる方はMesh RenderのLightingからCast Shadowsを「Two Sided」に設定してください。これについては清原先生が記事を公開しているので詳しく知りたい方は読んでみてください。

Unity Tips!

 ユニティちゃんが星に近づいたら星を取得できるように、当たり判定(Collider)を設定しましょう。

 今回は球体の当たり判定、Sphere Colliderを使用します​。StarにSphere Colliderを設定してください。コライダーの追加方法を忘れた人は、1-4を参考にしてください。

​ コライダーを設定できたらRadius(半径)をお好みの大きさにして、Is Triggerにチェックを入れてください。

​ プレイヤーに得のある当たり判定は気持ち大きめの方が、ユーザーに優しいゲームになります。

5

星にタグをつける

 星にStarタグをつけておきます。1-6でGroundタグを作った時と同じように設定してみましょう。

6

星が消える処理を

​実装する

 それでは「ユニティちゃんがSphere Colliderに接触したら星を取得する」処理を実装します。

​ 新しいスクリプト「StarObject」を追加して、以下のように入力してください。

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

 

public class StarObject : MonoBehaviour
{
    
    void Start()
    {
        
    }

 

    void Update()
    {
        // Y軸周りに回転する
        transform.Rotate(0.0f, 0.2f, 0.0f);
    }

 

    private void OnTriggerEnter(Collider other)
    {
        // もし自身と衝突したオブジェクトのタグがPlayerだったら…
        if (other.CompareTag("Player"))
        {
            // ここで星を取得する処理を書く

 

            // 自身を削除する
            Destroy(gameObject);
        }
    }

 

}

7

星のカウント用

オブジェクト作成

【プログラムの解説】

・transform.Rotate関数は回転を加算する関数です。

 Rotate(X軸周りの加算量,Y軸周りの加算量,Z軸周りの加算量);

​ 今回はY軸周りに回転を加えています。

・OnTriggerEnter関数はOnTriggerStay関数と異なり、衝突した瞬間にだけ呼ばれる関数です。

・Destroyは引数に入力したものを削除する関数です。

 k2EngineのようにDestroy(this);と入力してしまうと、オブジェクトではなく自身の"コンポーネントが"削除されてしまいます。自身のオブジェクトを削除する際の引数はgameObjectです。間違えやすいので注意してください。

 

 コードの入力が終わったら、StarにStarObjectをアタッチするのを忘れないようにしてください。

 アタッチできたらゲームを実行して、ユニティちゃんが星に近づくと星が消えることを確認してください。

 これで見た目上は星の取得ができていますが、内部では獲得した星の数を数えられていません。

 次は // ここで星を取得する処理を書く という部分で獲得した星の数を増やしましょう。

​ 獲得した星の数を数えるために、新しい空オブジェクトを追加してください。

8

オブジェクト名を

​設定する

 オブジェクトの名前は「Game」にしておきます。この名前は後で使用するので間違えないように注意してください。

9

獲得した星の数を

​数える

 星を数えるためのスクリプトを作成します。

​ 新しいスクリプト「StarCount」を追加して、以下のように入力してください。

​ このスクリプトは簡単なので一部穴埋め式になっています。青色の部分が抜けているコードです。書き方はC++と同じなので挑戦してみましょう。

 できるだけ答えを見ずに、過去のLessonを見て解いてください(答えはあくまで答え合わせ用です)

​【ヒント】② と ③ は「戻り値」を設定しています!戻り値についてはEXも参照

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

 

public class StarCount : MonoBehaviour
{
    private int m_nowStarCount = 0;   // 今持っている星の数
    private int m_maxStarCount = 0;   // 最大の星の数

 

    void Start()
    {
        // Starタグがついたオブジェクトを全て取得する
        GameObject[] stars = GameObject.FindGameObjectsWithTag("Star");
        m_maxStarCount = stars.Length;    // 要素数はオブジェクトの数
    }

 

    // 今持っている星の数を1増やす
    public void StarAdd()
    {
        // ① m_nowStarCountを1増やす
        (ここにコードを書く)
    }

 

    // 今持っている星の数を取得
    public int GetNowStarCount()
    {
        // ② m_nowStarCountを返す
        (ここにコードを書く)
    }

 

    // 最大の星の数を取得
    public int GetMaxStarCount()
    {
        // ③ m_maxStarCountを返す
        (ここにコードを書く)
    }

}

10

スクリプトを

​アタッチする

【プログラムの解説】

​・GameObject.FindGameObjectsWithTag(タグ名)で、引数に設定したタグを持つオブジェクトを全て取得することができます。今回はStarタグがついたオブジェクトを全て取得しています。

 戻り値は配列なので GameObject[] stars で受け取っています。

 Lesson2でGameObject.FindGameObjectWithTag というものが出ましたが、こちらは指定したタグを持つオブジェクト1つを取得する関数です。ちなみに対象となるオブジェクトが複数ある場合はどのオブジェクトが返ってくるかはわからないので、こちらはシーンに1つしかないオブジェクト(プレイヤーなど)を取得する時に使います。

​・取得した配列の要素数は、指定したタグがついたオブジェクトの数と等しくなります。

 今回は stars.Length でStarタグがついたオブジェクトの数(ステージにある星の数)を取得しています星の数を自動で数えるスクリプトを書くことで、後から星を追加しても問題なく動作するようになるため便利です。

​ コードが書けたら、先ほど作成した「Game」オブジェクトにStarCountをアタッチしておいてください。

 後はStarObjectスクリプトからStarCountのStarAdd関数を呼び出せば、獲得した星の数を数えることができます。

 今回はGameオブジェクトを名前で検索してStarCountコンポーネントを取得したいと思います。

​ 赤い部分のコードを追加してください。

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

 

public class StarObject : MonoBehaviour
{
    StarCount m_starCount;

 

    void Start()
    {
        // Gameという名前のオブジェクトを検索する
        GameObject gameObj = GameObject.Find("Game");
        // 検索したオブジェクトからStarCountコンポーネントを取得する
        m_starCount = gameObj.GetComponent<StarCount>();

    }

 

    void Update()
    {
       // Y軸周りに回転する
       transform.Rotate(0.0f, 0.2f, 0.0f);
    }

 

    private void OnTriggerEnter(Collider other)
    {
       // もし自身と衝突したオブジェクトのタグがPlayerだったら…
       if (other.CompareTag("Player"))
        {
           // ここで星を取得する処理を書く
            m_starCount.StarAdd();
           // 自身を削除する
           Destroy(gameObject);
        }
    }

 

}

評価テスト

3-3

【プログラムの解説】

・GameObject.Find(オブジェクト名) でシーン内に存在する同名オブジェクトを取得することができます。名前で検索するので、完全一致でないと取得できません。後からオブジェクト名を変更してしまうとうまく動作しないので注意しましょう。

(Findはタグで検索するより処理が重たいので、タグで検索できる時はFindGameObjectWithTagを使用することをオススメします)

​【評価テスト】

https://forms.gle/bVYdh4yR3WjmGVhH7

評価テスト
3-3 デバッグ

3-3 デバッグ

1

ログを出力

 3-2で星に触れると星が消えて獲得数が増加する処理を実装しました。

 ですが、実際に星の獲得数が増加しているか、今のままではわかりません。Unityのデバッグ機能を使って本当にm_nowStarCountが増加しているか確かめてみましょう。

 デバッグで使える機能はいくつかあるのですが、今回は2つ紹介します。

 まずはDebug.Logです。Debug.Logが実行されるとUnity上のコンソールにメッセージが出力されます。​例えばDebug.Log("Test");と入力すればコンソールウィンドウに「Test」と出力されます。

​ StarObjectに以下の赤い部分のコードを入力してください。

​~中略~

    private void OnTriggerEnter(Collider other)
    {
       // もし自身と衝突したオブジェクトのタグがPlayerだったら…
       if (other.CompareTag("Player"))
        {
           // ここで星を取得する処理を書く
            m_starCount.StarAdd();

 

            // デバッグ:獲得した星の数を表示
            Debug.Log(m_starCount.GetNowStarCount());

 

            // 自身を削除する
           Destroy(gameObject);
        }
    }

 

}

 入力できたら保存して、実行してみてください。

​ 星を取得すると、獲得した星の数がコンソールに表示されます。

​ Debug.Logの引数に変数を入力すると、その中身がコンソールに出力されます。

2

​ブレークポイント

 コンソールに出力された値をダブルクリックすると、プログラムのどこから出力されたかを確認することができます。デバッグに便利なので覚えておきましょう。

Unity Tips!

 コンソールにDebug.Logが出力できていることが確認出来たらOKです。もし数値が増えていなかったら3-2を見返して星を取得するコードが間違っていないか確認しましょう。

​ このコードは残しておいても構いませんし、確認できたなら消しても構いません。

 2つ目はお馴染みのブレークポイントです。今までと同じように使うことができます​。

​ ブレークポイントを置いてから上部にある「Unityにアタッチ」を選択してください。

 Unityに戻るとUnityへのアタッチの許可を要請してきますので、OKしてください。

 「Unityにアタッチ」が機能している状態で星を取ると、ブレークポイントでプログラムが一時停止します。

 ブレークポイントで停止中にF10キーを押すとプログラムを1行進めることができます。m_starCountにマウスカーソルを合わせて、m_nowStarCountの値が変化することを確認してみてください。

 デバッグの方法はどちらを使ってもOKです。処理を継続するDebug.Logと処理を一時停止するブレークポイント、うまく使い分けてみてください。

3-4

3-4 トゲの実装

3-4 トゲの実装

1

トゲのモデルを

​追加

 次は触れるとダメージを受けるトゲを実装します。

 と言っても、星を実装した時と処理はあまり変わりません。

​ 「Model」→「Toge」からトゲのモデルをシーンにドラッグ&ドロップしてください。

2

プレハブ化を解除

 トゲのプレハブ化を解除します。

 ヒエラルキー内のTogeを右クリックして「Prefab」→「Unpack Completely」を選択してください。何をしているかよくわからないと思いますが、今はあまり気にしなくても大丈夫です。

3

当たり判定を追加

 ヒエラルキー内のTogeの横にある ▶ を押して開くと出てくるTogeModelを選択してください。

​ TogeModelにBoxColliderをアタッチしてください。コライダーのつけ方を忘れた人は1-4を参考にしてください。

 BoxColliderのIs Triggerにチェックを入れてください。

​ これがトゲに衝突した時のダメージ用当たり判定になります。

4

ダメージ処理の

実装

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

​ 青色の部分の穴埋めにも挑戦してみましょう。過去の実装を参考にしてみてください。

 特に②はUnityを扱う上で非常に重要なコードです。

【ヒント】① インスペクターに変数を表示したい時に必要な修飾子があったはず…

     ② StarObjectスクリプトのStart関数をチェック!

​     ③ 引数に注意しましょう!

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

 

public class DamageObject : MonoBehaviour
{
    // ① 触れた時のダメージ量を設定するint型の変数「DamagePower」
    //   後からインスペクターで調整できるようにする

    (ここにコードを書く)

 

    private void OnTriggerStay(Collider other)
    {
        // もし自身と衝突したオブジェクトのタグがPlayerだったら…
        if (other.CompareTag("Player"))
        {
            // ② 衝突したオブジェクトotherにアタッチされている
            //   PlayerHitPointコンポーネントを取得

            PlayerHitPoint playerHitPoint = (ここにコードを書く)

 

            // ③ playerHitPointを使用してダメージを与える
            //   ダメージ量は①で設定した変数「DamagePower」
            (ここにコードを書く)

        }

    }

}

 コードが書けたら保存して、TogeModelにDamageObjectをアタッチしてください。BoxColliderがアタッチされているものと同じオブジェクトにアタッチしないとOnTriggerStayが反応しないので注意してください。

​ 接触時のダメージ量を表すDamagePowerを1にしておきます。

5

ダメージ量の​設定

 設定できたらゲームを実行してトゲに触れてみてください。インスペクターで現在の体力を確認すると、体力がちゃんと減っていることが確認できます。

6

無敵時間の実装

 ですが、HitPointがとてつもない勢いで減少して一瞬で0になっています。OnTriggerStay関数はプレイヤーが触れている間は毎フレーム処理が呼ばれるので、このような現象が起こってしまいます。
 このままだとユニティちゃんはオーバーキルになってしまうので、無敵時間を実装しましょう。


 PlayerHitPointに赤い部分のコードを追加してください。

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

 

public class PlayerHitPoint : MonoBehaviour
{
    public int MaxHitPoint = 3;
    public int HitPoint = 3;

 

    public float InvincibleTime = 3.0f;         // 無敵時間
    private float m_invincibleTimer = 0.0f;     // 無敵時間のタイマー
    private bool m_invincibleFlag = false;      // 無敵フラグ

 

    // ダメージ処理
    public void Damage(int damage)
    {
        // 無敵時間中は実行されないようにする
        if (m_invincibleFlag)
        {
            return;
        }

 

        // Mathf.Clampは値が範囲内に収まるようにする関数
        HitPoint = Mathf.Clamp(HitPoint - damage, 0, MaxHitPoint);

        // 無敵時間開始
        m_invincibleFlag = true;
        // 無敵タイマーをセットする
        m_invincibleTimer = InvincibleTime;

    }

 

    void Update()
    {
        if (m_invincibleFlag)
        {
            // 無敵時間のタイマー減少
            m_invincibleTimer -= Time.deltaTime;

 

            if (m_invincibleTimer <= 0.0f)
            {
                m_invincibleFlag = false;     // 無敵時間終了
            }
        }

    }

 

}

【プログラムの解説】

・関数の途中で return を呼ぶと、処理を途中で中断できます。今回はダメージ関数の最初に無敵フラグを確認して、無敵状態ならその先の処理は不要なので return を使って中断しています。

 インスペクターにInvincibleTimeが追加されています。初期値は3秒ですが少なくすることで無敵時間が短くなり、難易度が上がります。自由に調整してOKです。

 ゲームを起動して、無敵時間が動作していることを確認してみてください。トゲに触れ続けても、しばらくはダメージを受けません。

​ これでトゲの実装は終了です。

3-5

3-5 大砲の実装

3-5 大砲の実装

1

大砲のモデルを

​追加

 次は大砲を実装します。

 一定間隔で弾が発射され、ユニティちゃんがぶつかるとダメージを受けます。

​ 「Model」→「Cannon」から大砲のモデルをシーン上にドラッグ&ドロップしてください。

2

弾となる球体を

​追加

 大砲から発射される弾も準備します。

 インスペクターから「3D Object」→「Sphere」を選択して球体を作成してください。

3

弾の大きさを調整

 Sphereの名前を「Bullet」に変更しておきます。

​ 大砲に対して弾が大きすぎるので、Scaleを全て0.5に調整しましょう。

4

マテリアルを作成

 白単色では弾として味気ないので、弾に色を設定しましょう。

​ Cannonフォルダ内にMaterialを作成してください。

 マテリアルとは物体の質感を表現するためのテクスチャや反射率などの設定をまとめたものです。

 マテリアルの名前はわかりやすい名前で大丈夫です。今回は「BulletMaterial」にしておきます。

5

マテリアルの色を

​設定

 インスペクターからBulletMaterialの設定をします。

​・Albedo(色)を灰色に変更してください。

・Metallic(反射率)を0に変更してください。

・Smoothness(滑らかさ)を0.8に変更してください。

※ この3つの要素はあくまで目安なので、好きに変更しても構いません

6

​マテリアルを貼る

 BulletMaterialの設定ができたら、インスペクター内のBulletにBulletMaterialをドラッグ&ドロップしてください。

​ これで真っ白で味気ない見た目から解放されました。

7

プレハブ化を解除

 シーン上にあるBulletに直接ドラッグ&ドロップしたり、インスペクターにドラッグ&ドロップしたりしても同じようにマテリアルを設定できます。​好きな方法でOKです。

Unity Tips!

 モデルの準備ができたので、実装の準備をしていきましょう。

​ まずはインスペクターのCannonを右クリックしてプレハブ化を解除してください。星やトゲを実装した時と同じです。

8

大砲に当たり判定を追加

 大砲に当たり判定を設定しましょう。

​ CannonにBoxColliderをアタッチしてください。

 BoxColliderの大きさや位置は以下のように設定するのがオススメです。

・Center X=-0.4 Y=0.7 Z=0

​・Size X=2.8 Y=1.6 Z=1

​ この当たり判定は貫通してほしくないので Is Triggerにチェックを入れていません。試しにプレイしてユニティちゃんが衝突することを確認してみてください。

9

大砲にタグを設定

 大砲や壁に衝突した際にカメラがガタガタする現象が気になる方は、新しくFixedUpdate関数を追加して、Playerスクリプト内のAction関数をFixedUpdate関数内に移動させてください。​そしてジャンプ関連の処理だけをUpdate関数内に移動させてください。

 FixedUpdate関数はUpdate関数と異なり、一定間隔で実行される関数です。また、Update関数より先に実行されるという特徴もあります。今まではプレイヤーの移動もカメラの移動もUpdateにありましたが、プレイヤーの移動をFixedUpdateに書くことで「プレイヤーが移動してからカメラが移動する」ようになります。

1_3_b
Unity Tips!

 大砲の上に乗ってもジャンプできるように、大砲にGroundタグをつけておきましょう。

10

Trigger判定にする

 Bulletには最初からSphereColliderがついていますが、これをTrigger判定にしておいてください。

11

弾を発射する

 それでは一定間隔で弾を発射するスクリプトを書きましょう。

 

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

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

 

public class CannonObject : MonoBehaviour
{
    public GameObject Bullet;       // 発射するオブジェクト

 

    public float CannonInterval = 3.0f; // 発射間隔
    public float CannonSpeed = 2.0f;    // 弾の速度
    public float CannonLimit = 5.0f;    // 弾の生存時間
    public float BulletAddY = 1.2f;     // 弾の発射位置を持ち上げる量

    public int DamagePower = 0;         // ダメージ量
 

    private float m_cannonTimer = 0.0f;
 

    void Update()
    {
        // タイマー減少
        m_cannonTimer -= Time.deltaTime;

 

        if (m_cannonTimer <= 0.0f)
        {
            // 発射座標を計算する
            Vector3 bulletPos = transform.position;
            bulletPos.y += BulletAddY;

 

            // 発射処理
            GameObject bullet = Instantiate(Bullet, bulletPos, Quaternion.identity);

 

            // ここに弾に移動速度を教える処理を書く!
 

            // タイマーを戻す
            m_cannonTimer = CannonInterval;
        }
    }

}

12

弾の移動処理を

​実装

【プログラムの解説】

​・Instantiate関数は第一引数に設定したゲームオブジェクトを生成する関数です。今回はBullet(大砲の弾)を生成しています。

  Instantiate(生成するオブジェクト, 座標, 回転);

 戻り値として生成したゲームオブジェクトを返します。

 弾に移動速度を教える処理は後で書きます。

 次は弾のスクリプトを実装しましょう。

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

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

 

public class BulletObject : MonoBehaviour
{
    private Vector3 m_moveSpeed = Vector3.zero;     // 移動量
    private float m_timer = 0.0f;                   // 生存時間
    private int m_damage = 0;                       // ダメージ量

 

    private bool m_setFlag = false;                 // 初期化フラグ
 

    // 初期化
    public void Set(Vector3 move,float limit,int damage)
    {
        m_moveSpeed = move;
        m_timer = limit;
        m_damage = damage;

 

        m_setFlag = true;
    }


    // Fixedなので注意!

    void FixedUpdate()
    {
        // 未初期化なら実行しない
        if (m_setFlag == false) {
            return;
        }

 

        // 移動
        transform.Translate(m_moveSpeed * Time.deltaTime);

 

        // 生存時間
        m_timer -= Time.deltaTime;

        // 生存時間が終わったら自身を削除する
        if (m_timer <= 0.0f)
        {
            Destroy(gameObject);
        }

    }
 

    private void OnTriggerEnter(Collider other)
    {
        // もし自身と衝突したオブジェクトのタグがPlayerだったら…
        if (other.CompareTag("Player"))
        {
            // プレイヤーにダメージを与える
            PlayerHitPoint playerHitPoint = other.GetComponent<PlayerHitPoint>();
            playerHitPoint.Damage(m_damage);

            // 自身を削除する
            Destroy(gameObject);
        }

    }


}

【プログラムの解説】

​・Translate関数は引数に設定したベクトル分、座標を移動します。

 シンプルに移動するときに使えます。

13

弾に移動速度を

​教える

 プレイヤーを移動させる時には自身の座標(transform.position)に移動量を加算して移動させていました。今回使用したTranslate関数とは何が違うのでしょうか。

 Translate関数はローカル座標系を基準に移動します。

 例えば、

 transform.Translate(new Vector3(1.0f, 0.0f, 0.0f));

 と入力した場合、プレイヤーが回転しているとその回転の影響を受けて右へ移動します。

​ そして

 transform.position += new Vector3(1.0f, 0.0f, 0.0f);

 と入力した場合はワールド座標系を基準に移動します。プレイヤーが回転していても必ずX座標を+1するという訳です。

_ex2.png

​ ここまで読んで気付いた方もいるかもしれませんが、今回の大砲の弾の移動処理においては、弾が回転しないためどちらを使っても結果は変わりませんくまでこの話をするためだけにTranslate関数を使っただけです)

​ とはいえ、違いを気にせず使っていると突然うまく移動できなくなってしまう可能性があるので、それぞれの違いはしっかり覚えておきましょう。

Unity Tips!
Translate

・生存時間のタイマーが0になったら弾に自身を削除させています。弾のオブジェクトがシーン上に増え続けるとゲームが重くなってしまうので、適時削除するようにしましょう。

​・プレイヤーに触れた瞬間にダメージを与える処理の流れはトゲと同じですが、弾がプレイヤーを貫通すると不自然なので、衝突した瞬間に削除しています。オブジェクトを削除する処理は星の取得と同じです。

​ コードが書けたらCannonObjectスクリプトに戻って、移動速度を教える処理を実装しましょう。

​ 赤い部分のコードを追加してください。

~中略~
        if (m_cannonTimer <= 0.0f)
        {
            // 発射座標を計算する
            Vector3 bulletPos = transform.position;
            bulletPos.y += BulletAddY;

 

            // 発射処理
            GameObject bullet = Instantiate(Bullet, bulletPos, Quaternion.identity);

 

            // BulletObjectを取得する
            BulletObject bulletObject = bullet.GetComponent<BulletObject>();

 

            // 移動量を計算する(大砲の発射口が左にあるモデルなので左方向を取得)
            Vector3 move = -transform.right * CannonSpeed;
            // 移動速度、生存時間、ダメージ量を弾に教える
            bulletObject.Set(move, CannonLimit, DamagePower);

 

            // タイマーを戻す
            m_cannonTimer = CannonInterval;
        }
    }

}

14

Prefabフォルダを

​作成する

【プログラムの解説】

​・移動速度の計算では大砲の左方向に移動速度をかけた値を使用しています。

 自身の左方向を取得したい時は右方向にマイナスをかけることで取得できます。

 コードが書けたらCannonObjectを大砲に、BulletObjectを弾にアタッチしてください。

 後は大砲に発射させるオブジェクトを大砲に教えるだけです。

​ ここではプレハブという手法を使います。シーン上にあるオブジェクトをプロジェクト内に保存して、ハンコのように量産できるようにする仕組みです。

​ 物は試しなので大砲の弾をプレハブ化してみましょう。

​ 新しくPrefabフォルダを作成してください。

15

弾をプレハブ化

 Prefabフォルダ内にヒエラルキーからBulletをドラッグ&ドロップしてください。プロジェクト内にBulletが追加されたらプレハブ化完了です。

1_3_41

16

シーン上の弾を

​削除する

 プレハブ化できたらシーン上にあるBulletは必要ないので削除しましょう。オブジェクトを選択した状態でDeleteキーを押す(または右クリックして「Delete」を選択)ことで削除できます。

​ シーン上にある弾を削除してもプレハブ化された弾は消えません。オブジェクトを素材として保持できるのがプレハブの利点です。

1_3_42

17

発射する弾を

​登録する

 後はこのプレハブをCannonObjectに登録しましょう。

​ Cannonのインスペクターを開いて、Bulletに弾のプレハブをドラッグ&ドロップしてください。

 これで大砲が完成しました。

​ 時間経過で弾が発射されて、弾に触れるとダメージを受けることを確認してみてください。

 例のごとくCannonのパラメータはpublicになっているので自由に変更できます。CannonInterval(発射間隔)やCannonSpeed(移動速度)を変更してどう変わるか確認してみてください。

 プロジェクト内にあるプレハブをダブルクリックするとプレハブを編集することができます。後から弾の大きさを変更したくなったり、新しいスクリプトをアタッチしたくなった時はここから編集することで、全ての弾に反映させることができます。

Unity Tips!

3-6

3-6 移動床の実装

3-6 移動床の実装

1

​床を作成

 最後のギミックとして移動する床を実装しましょう。

 開始地点と目標地点を自動で往復し、プレイヤーが乗って移動することができます。

​ まずはCubeを作成してください。

2

タグと

Transformを設定

 名前をMoveCubeにして、ジャンプできるようにGroundタグをつけます。

​ 座標や大きさは好みで調整してください。

3

マテリアル用の
​フォルダを作成

 真っ白な箱のままだと普通の床と区別がつかないので、マテリアルを貼りましょう。

 今回は箱に画像を貼ります。​新しくMaterialフォルダを追加してください。

4

テクスチャを用意

 Materialフォルダ内に使いたい画像をドラッグ&ドロップしてください。好きな画像でOKです。

 使いたい画像が特にない場合は以下の画像を使用してください。

5

マテリアルを
作成&適用

 大砲の弾と同じように新しいMaterialを作成してください。名前は何でもOKですが、教材ではMoveMaterialにしています。

 MaterialのAlbedoに使用したい画像をドラッグ&ドロップし、パラメータを好みに調整してください。

 設定ができたらMoveCubeにマテリアルをドラッグ&ドロップして貼りつけてください。

6

移動床の
​スクリプトを書く

 移動床の準備ができたのでプログラムを実装していきましょう。

​ MoveObjectスクリプトを作成して、以下のように入力してください。少し複雑な処理ですが、全てを理解する必要はありません。

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

 

public class MoveObject : MonoBehaviour
{
    // 目標となる2つの座標
    Vector3 m_startPosition = Vector3.zero; // 開始地点
    public Vector3 TargetPosition = Vector3.zero;   // 目標地点

 

    // スピード
    public float MoveSpeed = 1.0f;                  // 移動速度
    float m_nowTime = 0.0f;                 // 線形補間用の経過時間

 

    // 移動状態
    bool m_moveFlag = false;                // 目的地に向かっているか帰っているか

 

    // ウェイト
    float m_waitTimer = 0.0f;               // 待機時間のタイマー
    public float WaitTime = 2.0f;                   // 移動後の待機時間
    bool m_waitFlag = false;                // 待機状態

 

    void Start()
    {
        // 開始地点を保存
        m_startPosition = transform.position;
    }

 

    // ↓「Update」ではないので注意!
    void FixedUpdate()
    {
        // 待ち状態かどうか
        if (m_waitFlag == false)
        {
            // 移動状態
            if (m_moveFlag == false)
            {
                Move(m_startPosition, TargetPosition);
            }
            else
            {
                Move(TargetPosition, m_startPosition);
            }
        }
        else
        {
            Wait();
        }
    }

 

    // 移動中の処理
    void Move(Vector3 Start, Vector3 Target)
    {
        // 現在の位置
        m_nowTime += Time.deltaTime * MoveSpeed;

        if (m_nowTime > 1.0f)
        {
            m_nowTime = 1.0f;
        }

 

        // オブジェクトの移動
        transform.position = Vector3.Lerp(Start, Target, m_nowTime);

 

        // もし目的地に着いていたら状態遷移
        if (transform.position == Target)
        {
            m_waitTimer = WaitTime;
            m_waitFlag = true;
        }
    }

 

    // 待機中の処理
    void Wait()
    {
        m_waitTimer -= Time.deltaTime;

        if (m_waitTimer <= 0.0f)
        {
            m_waitFlag = false;
            m_moveFlag = !m_moveFlag;   // フラグを反転させる
            m_nowTime = 0.0f;
        }
    }

 

}

7

スクリプトを
​アタッチする

【プログラムの解説】

・Lerp関数は線形補間で、2点間の座標を0.0~1.0の範囲で補間してくれる関数です。

​  Vector3.Lerp(開始地点, 目標地点, 補間量);

​ 例えば第三引数に0.0を入れると、開始地点の座標を返します。

 そして第三引数に0.5を入れると、開始地点と目標地点のちょうど中間の座標を返します。

 プログラムが書けたら保存して、移動床にMoveObjectスクリプトをアタッチしてください。​

​ 移動先の座標であるTargetPositionはpublicにしてあるのでインスペクターから編集することができます。

 移動先の座標を設定してください。値は自由です。移動速度や移動の待機時間も変更して構いません。

8

移動床の処理を
​修正する

 これで移動床が開始地点と目標地点を往復するようになりました。しかし、プレイヤーが乗ってもプレイヤーは床に置いて行かれてしまいます。このままではかなり遊びにくいギミックになってしまいます…

 これを解決する方法として"直前座標との差をプレイヤーに教える"方法があります。プレイヤーが乗っている間は前フレームとの座標の差をプレイヤーの座標に加算します。

​ MoveObjectスクリプトに以下の赤い部分のコードを追加してください。

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

 

public class MoveObject : MonoBehaviour
{
    // 目標となる2つの座標
    Vector3 m_startPosition = Vector3.zero; // 開始地点
    public Vector3 TargetPosition = Vector3.zero;   // 目標地点

 

    // スピード
    public float MoveSpeed = 1.0f;                  // 移動速度
    float m_nowTime = 0.0f;                 // 線形補間用の経過時間

 

    // 移動状態
    bool m_moveFlag = false;                // 目的地に向かっているか帰っているか

 

    // ウェイト
    float m_waitTimer = 0.0f;               // 待機時間のタイマー
    public float WaitTime = 2.0f;                   // 移動後の待機時間
    bool m_waitFlag = false;                // 待機状態


    Vector3 m_beforePosition = Vector3.zero;  // 直前の座標
 

    void Start()
    {
        // 開始地点を保存
        m_startPosition = transform.position;
    }

 

    // ↓「Update」ではないので注意!
    void FixedUpdate()
    {
        // 移動前の座標を保存しておく
        m_beforePosition = transform.position;


        // 待ち状態かどうか
        if (m_waitFlag == false)
        {
            // 移動状態
            if (m_moveFlag == false)
            {
                Move(m_startPosition, TargetPosition);
            }
            else
            {
                Move(TargetPosition, m_startPosition);
            }
        }
        else
        {
            Wait();
        }
    }

    // 移動中の処理
    void Move(Vector3 Start, Vector3 Target)
    {
        // 現在の位置
        m_nowTime += Time.deltaTime * MoveSpeed;

        if (m_nowTime > 1.0f)
        {
            m_nowTime = 1.0f;
        }

 

        // オブジェクトの移動
        transform.position = Vector3.Lerp(Start, Target, m_nowTime);

 

        // もし目的地に着いていたら状態遷移
        if (transform.position == Target)
        {
            m_waitTimer = WaitTime;
            m_waitFlag = true;
        }
    }

 

    // 待機中の処理
    void Wait()
    {
        m_waitTimer -= Time.deltaTime;

        if (m_waitTimer <= 0.0f)
        {
            m_waitFlag = false;
            m_moveFlag = !m_moveFlag;   // フラグを反転させる
            m_nowTime = 0.0f;
        }
    }


    void OnCollisionStay(Collision collision)
    {
        // 接触しているオブジェクトのタグが"Player"のとき
        if (collision.gameObject.CompareTag("Player"))
        {
            // 直前の座標との変化量をプレイヤーに加算する
            collision.gameObject.transform.position += transform.position - m_beforePosition;
        }
    }

 

}

【プログラムの解説】

​・OnCollisionStayはOnTriggerStayとは異なり、IsTriggerにチェックが入っていないオブジェクトの衝突判定に使います。使い方はあまり変わりませんが、この2つの関数を間違えるとうまく動作しないので注意してください。

 今まで様々な当たり判定の関数を紹介してきましたが、これらの関数は適切に使い分けないと期待通りの判定が取れないので、少しずつ覚えていきましょう。関数名は以下の法則で決まっています。

・OnCollision→ Is Triggerにチェックなし OnTrigger→ Is Triggerにチェックあり

​・Enter→触れた瞬間 Stay→触れている間 Exit→離れた瞬間

・最後に2Dとついているものは2D用

​ これで移動床が完成しました。実際に乗って、ユニティちゃんが床と一緒に移動することを確認してみてください。

 他にも「移動床に触れている間だけ、プレイヤーを移動床の子オブジェクトにする」という手法でも移動床の問題を解決できます。

 こちらの手法を取るメリットとしては

・移動床が下に高速で移動した際のガタつきを抑制する

・移動床の回転にも対応できる

 デメリットとしては

​・プレイヤーが特定のオブジェクトの子オブジェクトになることで、移動の際などで都合が悪いことがある

 などがあります。

 プレイヤーの親オブジェクトは、SetParnet関数を使うことで変更することができます。

 SetParnet関数の第一引数に親オブジェクトにしたいオブジェクトのtransformを指定してください。

 下記のサンプルを参考にして、こちらの方法もぜひ実装してみてください。

Unity Tips!

評価テスト

3-7

評価テスト
3-7 落下処理の実装

3-7 落下処理の実装

1

落下処理の
​スクリプトを書く

 ステージとはあまり関係がないのですが、実装するタイミングがないのでここで実装します。

​ ユニティちゃんが落下するとダメージを受けて初期位置に戻るようにしましょう。

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

 今回はほとんど穴埋めです。青い部分を埋めてみてください。コメントや過去の処理を参考にしてみましょう。

【ヒント】自分の座標は transform.position で取得できます!

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

 

public class PlayerFall : MonoBehaviour
{
    // ① 落下判定になるY(低さ)を決めるためのpublicなfloat型変数
   
(ここに入力)

    // ② 初期座標を保存するためのprivateなVector3型変数
    (ここに入力)

    // ③ 自身のPlayerHitPointを保持するためのprivateな変数
    (ここに入力)

    // ④ 落下時のダメージを指定するpublicなint型変数
    (ここに入力)
 

    void Start()
    {
        // ⑤ 自分の初期座標を②で宣言した変数に代入

        (ここに入力)

        // ⑥ 自身にアタッチされているPlayerHitPointコンポーネントを取得して③に代入
        (ここに入力)

    }
 

    void Update()
    {
        // ⑦ もし自分のY座標が①で指定したボーダー以下になったら
        //   ダメージを受けて初期座標に戻る
        //  (ダメージを与える方法はDamageObjectを参照!)

        (ここに入力)

    }

}

 完成したらユニティちゃんにアタッチしてください。

 試しに落下してみて、ボーダーラインまで落下したらダメージを受けて初期座標に戻ることを確認してみましょう。

3-8

3-8 タイマーの実装

3-8 タイマーの実装

1

落下処理の
​スクリプトを書く

 こちらもステージ本体とは関係ないのですが、クリアタイムを計測するために内部的にタイマーを設定します。

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

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

 

public class GameTimer : MonoBehaviour
{
    float m_second;   // 秒
    int m_minute;     // 分

 

    // 分と秒を返す関数
    public float GetSecond()
    {
        return m_second;
    }
    public int GetMinute()
    {
        return m_minute;
    }

 

    void Update()
    {
        // タイマー加算
        m_second += Time.deltaTime;
        if (m_second > 60.0f)
        {
            // 1分経過
            m_minute++;
            m_second -= 60.0f;
        }
    }
}

 1フレームごとの経過時間を加算していき、1分経過したらm_minuteを増やすスクリプトです。

 

 入力できたらGameオブジェクトにアタッチしておいてください。変数はprivateなのでインスペクターからは値を確認できませんが、デバッグ機能で確認することができます。

 タイマー機能は今後のLessonで出番がありますが、今はこれだけでOKです。

3-9

3-9 ステージの制作

3-9 ステージの制作

1

ギミックを
​プレハブ化する

 それでは今まで作ったギミックを配置してステージを作ってみましょう。

 星、トゲ、大砲、移動床をプレハブ化します。4つのオブジェクトをPrefabフォルダ内にドラッグ&ドロップしてください。

2

プレハブを使って
​ステージを作る

 プレハブ化したオブジェクトをステージに配置して、自由にステージを作ってみてください。

​・ユニティちゃんはGroundタグがついたオブジェクトが足元にないとジャンプできないので注意!

・クリア条件となる星を1つ以上配置してください!

​・地面に画像を貼ったり、新しいギミックを実装できるとGOOD!

・親子関係(1-6参照)を使うことで移動床と他ギミックを組み合わせることができます。

 今まで学んだ内容を応用することで、様々なギミックを作ることができるはずです。

 ここではオススメのギミックと実装のヒントを紹介します。ぜひ挑戦してみましょう!

【トランポリン】

 プレイヤーが上に乗った時に、そのプレイヤーに対して上方向に大きく力を加えてみましょう。接触判定はトゲの処理が、飛ばす処理はジャンプの処理が参考になります。

 他にもプレイヤーが触れている間だけ一定方向へ移動させることで「ベルトコンベア」、プレイヤーが触れると下へ移動する「落ちる足場」など、応用することで様々なギミックを作れます。

【回復アイテム】

 触れると体力が回復するアイテムを作ってみましょう。オブジェクトを削除する処理は星のスクリプトを確認しましょう。

【チェックポイント】

 触れると復活する場所が変化するオブジェクトを作ってみましょう。PlayerFallスクリプトを改造して、落下時に復活する座標を外部から変更できるようにする必要があります。

 教材では使っていませんが「3D Object」→「Cylinder」で円柱を作成できるので、Cubeと組み合わせて旗を作ることができます。チェックポイントのモデルとして使ってみましょう(好きなモデルでもOK)

 後はチェックポイントに触れた際にm_startPosを更新する関数を呼び出せばOKです。

【スイッチ】

 触れた瞬間に足場を出現させたり、消去したりすることで謎解きのギミックを作ってみましょう。

 場を出現させたり、消したりしたい時はオブジェクトのアクティブ/非アクティブを切り替えることでスムーズに処理できます。SetActive関数を使うことでオブジェクトのアクティブ状態を変更することができます。非アクティブにしたオブジェクトは表示されず、当たり判定も無効になります。

 インスペクター内の名前の左側にあるチェックマークがオブジェクトのアクティブ状態を示しています。最初は非表示にしておきたいオブジェクトがある場合はチェックマークを外しておきましょう

Unity Tips!

 ステージが完成したらLesson3は終了です。

​ 次のLessonではUIを実装して、獲得した星の数や体力を画面上に表示できるようにしましょう。

まとめ

まとめ

Destroy(gameObject); で自身のオブジェクトを削除できる

 k2Engineのように Destroy(this); と入力してしまうとコンポーネントだけが消える(オブジェクトは残り続ける)ので注意!

・GameObject.FindGameObjectsWithTag("Star") でStarタグが設定されているオブジェクトを全て検索して配列で取得することができる

 Objects で複数形になっている点に注目しよう

・GameObject.Find("Player") でPlayerという名前のオブジェクトを検索して取得できる

 文字列での検索は重いので、特別な事情がない限りはタグでの検索や、変数をインスペクターに表示して直接指定する方法がオススメ

・Debug.Log("Test"); と入力することで、コンソールに「Test」と出力できる

 変数を出力することも可能

 変数の値の変化を確認する時などに便利

・UnityはVisual Studioにあるブレークポイントの機能を使用することもできる

Instantiate(生成するオブジェクト); と入力することで、オブジェクトを生成できる

 第二引数に生成座標、第三引数に初期回転を指定できる

 生成したオブジェクトが戻り値として返ってくる

・オブジェクトをアセットとして保持して、ハンコのように量産できるプレハブという機能がある

 プレハブを基に配置したオブジェクトは、プレハブ側を編集することでその変更を一括で反映することができる

評価テスト

評価テスト

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

bottom of page