Unity

【Unity超入門】障害物を避けてゴールを目指す3Dゲーム作り【前後左右の移動とジャンプを操作する】

本記事内には、アフィリエイトリンクを含む場合があります

Unityは、ゲーム開発をアシストする非常に優れたソフトウェアサービスです。

直感的な操作でゲームを構築できる一方で 
プログラミング知識が必要な部分があります。

Vtuber技術やAR/VRサービス、スマホアプリ開発など
できることが幅広すぎるため(無限の可能性)、
逆に混乱を生み、
初心者にとってハードルが高い、とも言われています。

そこで、本講座では、Unity初心者に向けて 
●簡単な3Dゲームを作る方法
●基礎的なプログラミングによってゲームを動かす方法
を学べるような内容になっています。

少しボリュームはありますが、
最初の登竜門として、取り組んでいただくと良いと思います^^

 

0.Unityの導入

まだUnityを触ったことがない方は
Unity IDの作成(メールアドレスとPWの登録だけでOK)と
必要なソフトウェアを
公式サイト(Personalの無料プランで)からインストールしましょう。

インストールするソフトウェアは、
Unity Hub(プロジェクトファイルを管理するモノ)と
Unity Editor(プロジェクトファイルを作成・編集するモノ)の
2つです。

手順としては、まずUnity Hubをインストールし、
それを起動すると、Unity IDへのサインインが求められます。
初めての場合は、ここでアカウントを作成します(Googleアカとの連携で作成可能)。
ID作成が完了すると、プランを選ぶことになります。
この時はStudent or hobbitを選択すると、無料でUnityを利用することができます(ゲームによる収入が$100Kを超えると、有料プランになるという条件つきで)。

その後に、Hub内メニューの「installs」からUnity Editorをインストールします。

Unity Editorは、日々最新のバージョンがリリースされていきます。
しかし、必ずしも最新版である必要はありません。
機能が最新のモノで揃ってなくとも、使い勝手の関係で昔のバージョンを使っている場合もあります。

ちなみに、私は2019.4をずっと使っているので、
本記事は、2019.4のEditor画面で解説しています。
2022などのバージョンによって、少し作業内容が違ってくることもありますので、その点をご了承ください。

 

Editorをインストールできたら、3Dのプロジェクト作成から、ゲームを作っていきましょう。

1. シーンにオブジェクトを配置しよう

Unityのプロジェクト作成の際に、3Dを選びましょう。

Unity Editorという図の操作画面に開かれます。
図のような要領で、「+」から
「Capsule」、「Plane」を作成します。


*ちなみに、点線で囲ったタブはよく使う機能なので、図のように配置させるのがオススメ

 

各オブジェクトに対して、図のようにInspectorを設定します。

<Capsule(図の左側)>
・「Capsule」に”Player”と名づける
・Positionを変更する
・「Add Component」から「Rigidbody」コンポーネントをアタッチする

<Plane(図の右側)>
・「Plane」に”Ground”と名づける
・Scaleを変更する

ちょっと解説!

「Scene」というタブと「Game」というタブがあります。

「Scene」タブはオブジェクトを配置したりと、作業用の画面で、
「Game」タブはゲーム画面を示し、確認用の画面です。

動作確認するために、
ゲームの実行「▶︎」をクリックしてみましょう。

現段階では、Playerが地面に落下するのが見られたと思います。
ゲームの実行によって、ゲーム世界の時間が進み出したというわけです。

なぜ落下したかというと
PlayerがRigidbodyコンポーネントという機能をもつため、
重力がはたらくように挙動したのです。

2. Camera(便利なCinemachine)の実装

「Game」タブのゲーム画面は
今現在SceneにあるMainCameraが映し出すモノを反映させています。

このままMainCameraを使っても良いのですが、
ここでは、Cinemachineというカメラを使いましょう。

CinemachineのVirtual Cameraを使うことで、
複数のカメラを設置して、視点変更が簡単にできるようになったり
カメラの動きを滑らかにすることができるようになります。
(今後ゲーム開発してくのに、かなりの確率で使います)

使っているUnityのバージョンにもよりますが、
「2019.4」はCinemachineを使うために、「window」タブから「Package Manager」を開き、「Cinemachine」をインストールする必要があります。
(「Add Component」からCinemachineがない方はインストールしましょう)

インストールしたら、「Add Component」からCinemachineの機能を使えるようになります。

さて、今回作る3Dゲームは、
キャラクターを背後から追従するような視点のゲームです。

PUBGやフォートナイトでも使われている、サードパーソンシューティング(TPS)形式^^

ーーそれでは、追従するカメラを実装していきます。

MainCameraに「CinemachineBrain」というコンポーネントをアタッチします。

次にパッケージをインストールしたら追加される「Cinemachine」タブから
「Create Virtual Camera」をクリックします。

すると、Cinemachine Virtual Cameraとして「CM vcam1」が生成されます。
"PlayerCamera"と名付け、
PositionとRotationを調整(少し見下ろすぐらいに設定)します。

そして、追従するオブジェクトとして、
「Follow」に「Player」オブジェクトを紐付けます(ドラッグ&ドロップで)。

MainCameraの「CinemachineBrain」にある
「Live Camera」には「PlayerCamera」オブジェクトが紐づいていることが確認できるでしょう。

ゲームを実行してみて、「PlayerCamera」の視点となり、
「Player」の落下に追従して「PlayerCamera」が動けば、実装完了です。

ちなみに、Cinemachine Virtual Cameraを使うと、
カメラがオブジェクトを追従する滑らかさを簡単に調整することができます。
「X, Y, Z Damping」はデフォルト1ですが、値を大きくすると、滑らかさが増します。

3. Player操作の実装

Playerをパソコンのキーボードで操作できるようにしていきます。

Playerは、前後左右に動かせるようにします。
Sceneの次元で言うと、x軸が左右、z軸が前後となります。

Playerに次のようなスクリプトをコンポーネントとしてアタッチします。
ProjectタブのAssetsフォルダ内にScriptsというフォルダを作り、
そこで、右クリックから「Create」の「C# Script」をクリックすれば
スクリプトを作成できます。

"PlayerController"と名付けて、次のコードを書きましょう。

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

public class PlayerController: MonoBehaviour
{
    [SerializeField] float moveSpeed = 10f;
    [SerializeField] float jumpSpeed = 5f;
    Rigidbody rb;
    bool isGrounded;
 
    // Start is called before the first frame update
    void Start()
    {
        rb = GetComponent<Rigidbody>();
    }
 
    // Update is called once per frame
    void Update()
    {
        //方向キーによる移動
        float x = Input.GetAxis("Horizontal") * Time.deltaTime * moveSpeed;
        float z = Input.GetAxis("Vertical") * Time.deltaTime * moveSpeed;
        transform.Translate(x,0,z);
 
        isGrounded = CheckGround();
        //スペースキーによるジャンプ
        if (Input.GetKeyDown(KeyCode.Space))
        {
            if(isGrounded)
            {
                isGrounded = false;
                rb.AddForce(Vector3.up * jumpSpeed, ForceMode.Impulse);
            }
        }
    }

    //地面を検知する関数
    bool CheckGround()
    {
        var ray = new Ray(transform.position + Vector3.up * 0.01f, Vector3.down);
        var distance = 1.5f;
        return Physics.Raycast(ray, distance);
    }
}

オブジェクトを移動させる方法は、色々とありますが、
今回は、最もシンプルなtransformのTranslateを採用しました。

Translateは連続的なテレポートで移動する方式です。
その他の方式として、オブジェクトのRigidbodyにアプローチする方式があります。
物理挙動をより反映したゲームでは、後者を多用した開発がオススメです。
リアルな挙動の反面、制御しにくくなります^^;

なお、Unity標準では
InputGetAxisのVerticalは、↑↓キーまたはWSキーの入力値に対応しています。
そして、
InputGetAxisのHorizontalは、←→キーまたはADキーの入力値に対応しています。

それらキーによる入力値に、Time.deltaTimeと任意の値(moveSpeed)を掛けて移動する値(float x,z)を決めています
Time.deltaTimeは、フレームとフレームの差分(秒)で、これを掛けることでデバイスによって挙動の差がないようにします。

Translateの(x軸、y軸、z軸)に移動する値を入力して、移動処理の出来上がりです。

これらは、Update関数に入れるので、毎フレーム読まれる処理となります。
毎フレーム、キー入力によって、移動する値があるか検知することになります。

 

さらに、

前後左右(x軸z軸)の動きでは、"見た目だけ3Dゲーム"と言われかねません。
なので、今回は「ジャンプ」もつけました。
(isGroundedが絡んでくるあたり)

ジャンプはSpaceキーでy軸方向に力を作用させます。

今、Rigidbodyコンポーネントを持たせることで
y軸方向に重力に相当する力がはたらいています。

その力の影響を受けながらジャンプさせたいので、
ジャンプはRigidbodyのAddForceという方式で動かします。

ジャンプのisGroundedコードに関しては、説明すると少し長くなるので、
こちらの記事で解説します。

簡単にいうと、
地面を検知できた時のみ、ジャンプが機能するようにしています。
(でないと、空中浮遊できてしまう^^;)

スクリプトをPlayerオブジェクトにアタッチして、
moveSpeedやjumpSpeedの値を適宜変えてみましょう。
(おすすめは、moveSpeed:5、jumpSpeed:6)

また、RigidbodyにあるConstraintsのFreeze Rotation X,Y,Zにチェックを入れて、
回転したり倒れないようにします。

それでは、ゲームを実行し、キーボード操作で
Playerが動くか確認してみましょう!

4. Goal地点の実装

ゲームには、ミッションなり、目的が必要です。
プレイヤーを動かして、ゴール地点に辿り着かせるゲームを設けます。

シーンに「Cylinder」を配置し、"Goal"と名づけます。
そのInspectorの「CapsuleCollider」において「IsTrigger」にチェックを入れます。

これは当たり判定を有効にするためです。

当たった物体(オブジェクト)がPlayerならば、
ステージクリアの処理を走らせたいところ・・・

クリアの処理はスクリプトで設計しますが、後ほど。

Cylinderオブジェクトの他、テキストといったUIを作っていきます。

「+」から「UI」の「Text」および「Button」を作成します。
自動的に「Canvas」というオブジェクト、その配下に「Text」が生成されます。

2021以降のEditorでは、
UIの「Text」ではなく「Text Mesh Pro」に変わってします。
その場合は、「Text Mesh Pro」を生成してください。

そのとき下のようなウィンドウが出るので、


「Import TMP Essentials」だけクリックして、
「Text Mesh Pro」を使えるようにしましょう。

「Text」には、"ResultText"と名付けます。
Inspectorは図のように設定してします。

画面中央にこのTextが表示されるようになればOK。

ちなみにシーンのどこにオブジェクトがあるかわからなくなったら、
Hierarchyのオブジェクトネームをダブルクリックしましょう^^;

 

「Button」には、"Retry Button"と名付けます。

Buttonオブジェクトはクリックすると、
関数(メソッド)を呼び出すことができる機能をもっています。

関数(メソッド)って何?となると思いますが、
スクリプトに書いた特定の処理です。
void ●●(){ }
で区切られるものです。PlayerController.csにたくさん出てきましたね^^

とりあえず、RetryButtonはResultTextの下あたりに配置しておきます。

 

それでは、お待ちかねステージクリアのスクリプトを作っていきます。

Scriptsフォルダ内に、StageClearと名付けたC#Scriptを作り、Goalオブジェクトにアタッチします。

内容は次の通り。

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

public class StageClear : MonoBehaviour
{
    [SerializeField] GameObject clearText;
    [SerializeField] GameObject retryBtn;

    void OnTriggerEnter(Collider other)
    {
 
        if(other.CompareTag("Player")) //Playerタグのオブジェクトの衝突した場合
        {
            clearText.SetActive(true);
            retryBtn.SetActive(true);
            Time.timeScale = 0; //時間の経過が止まる
        }
    }

    public void OnClickRetryBtn()
    {
        SceneManager.LoadScene(0);
        Time.timeScale = 1; //時間の経過を元に戻す
    }
}

 

OnTriggerEnterという関数は、
アタッチしたオブジェクトの「OnTrigger」にチェックしたColliderが
当たり判定を検知したときに処理されるものです。

さらに、if文で条件分岐を行います。
当たったオブジェクトが"Player"ならば、if文内の処理を行います。
("Player"であるかどうかはTagで判断させます)

TagはPlayerのInspectorで設定します。

処理の内容は、「ResultText」と「RetryButton」を表示させること(SetActive(true))。
そして、ゲームで進行する時間を「Time.timeScale = 0」で止め、キャラクターを操作できなくしています。

また、「OnClickRetryBtn」という関数も書いてます。
これはRetryButtonをクリックしたときに呼ぶ関数です。

このゲームシーンをリロードする処理は、SceneManagerを用います。
SceneManagerを用いる場合、「using SceneManagement」を書いておく必要があ流ので忘れないようにしましょう。
ここでは()内の引数はシーンの名前を記載することもできますが、
Sceneに当てられた番号を記載しても機能します。
(BuildSettingでSceneに割り当てられた番号を確認できます)

ステージクリアして、次のステージに遷移させたいときは、
この部分を変更していけばよいということです。

 

ゲームシーンをリロードしたら、「Time.timeScale = 1」でゲームを再進行させます。

 

「StageClear」スクリプトを作成できたら、Goalオブジェクトにアタッチして、Inspectorを設定していきます。

Goalオブジェクトの「StageClear」にある「ResultText」および「RetryBtn」の項目に、該当するオブジェクトを紐付けます。

クリアしてはじめて、それらオブジェクトは表示される(SetActive(true)が読まれる)ので、
デフォルトでは、それらオブジェクトは非表示にしておきます。

 

RetryButtonのOnClick「+」をクリックし、そこにGoalオブジェクトを紐付け、クリックで呼び出す関数「OnClickRetryBtn()」を選択します。

さらに、SceneManagerよりシーンを読み込めるように、一旦このSampleSceneを保存し、「File」タブのBuild Settingにて、SampleSceneをドラッグ&ドロップで追加します。

ゲームシーンとして作ってきたSampleSceneも、ここらで保存しましょう。
Hierarchyあたりをクリックしてから、
「Ctrl+S」でSampleSceneの上書き保存ができます。

 

Goalはどこ?

現段階では、Goalがどこかわからないので、Goalを示すTextを配置しましょう。
先程のようにCanvas上に配置させる方法もなくはないですが、
テキストを3Dオブジェクトと同じ次元に配置する方が簡単です。
(Goal見つけた!となる)

したがって、「UI」から、改めて「Canvas」を作ったら、
"WorldCanvas"と名付けて
Render Modeの種別を”World Space”に変更します。

そして、このWorldCanvasをGoalオブジェクトの配下に置きます。
InspectorでPosX, Y, Zは0にします。

その配下に「Text」を作成します。

なお、World SpaceのCanvasに置くTextは、
Scaleをとても小さくする必要があります(図では0.04)。

Textの内容をGOALとして、作成完了です。

シーンで見ればわかる通り、
GOALという文字がGoalオブジェクトの上あたりに配置させることができました。


それでは、ゴールに当たって、クリア処理がうまく機能するか、
RetryButtonでゲームをリトライできるか、確認してみましょう。

5. ゲームオーバーの実装

ゲームオーバー、つまりステージクリアできない条件と
その時のUI(テキスト表示)を設定します。

ここでは、障害物に当たるとゲームオーバーとします。

障害物として「Cube」を、Plane上にいくつか配置しましょう。
"Obstacle (1)"と名付けます。
複数作るので、"Obstacle (1)"を選択して、「Ctrl+D」で複製すれば、名前も連番に生成されて楽です。
Obstacle (2)、Obstacle (3)....のように。

Groundオブジェクトから落下した場合もゲームオーバーにするために、
Obstacle (4)を作り、そのX,ZのScaleを大きくして、Groundオブジェクトの下に配置します。

作るゲームは、これらの障害物に当たると1発アウトにします(鬼畜)。

まず当たり判定を有効に(IsTriggerにチェック)、
そして、Obstacleというスクリプトを作り、アタッチします。

これらコンポーネントの設定は、
作ったObstacleオブジェクトを全て選択した状態で、設定しましょう。
(一挙に反映されて作業軽減できます)

スクリプトの内容は次の通り。

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

public class Obstacle : MonoBehaviour
{
    [SerializeField] GameObject gameoverText;
    [SerializeField] GameObject retryBtn;

    void OnTriggerEnter(Collider other)
    {
 
        if(other.CompareTag("Player"))
        {
            gameoverText.GetComponent<Text>().text = "GAME OVER";
            gameoverText.GetComponent<Text>().color = new Color(0,0,255);
            gameoverText.SetActive(true);
            retryBtn.SetActive(true);
            Time.timeScale = 0;
        }
    }
}

※「Text Mesh Pro」を使っている場合は、
using TMPro;
を冒頭に書き、
●●.GetComponent<Text>().text = "〜〜";
の代わりに
●●.GetComponent<TextMeshProUGUI>().text = "〜〜";
と書きましょう。

StageClear.csと同じようにPlayerとの衝突判定を行います。

ただし、ResultTextのTextコンポーネントにアクセスし、
文字列(text)を"GAME OVER"に変更する他、
文字色(color)をRGBの数値で変更します。
その後に、SetActiveで表示させる内容としています。

なお、TextのようなUIオブジェクト特有のコンポーネントを扱う際は、スクリプト冒頭で「using UnityEngine.UI」が必要となるので、忘れないようにしましょう。

スクリプトができたので、
Inspectorから各項目を紐付けます。
Obstacle (1)~(4)を選択した上で、Inspectorの設定を行うと全適用されて作業が楽です。

さて、ゲームを実行し、Obstacleにぶつかったときに
GAME OVERとなるか確認してみましょう。

6.マテリアルで色をつける

ゲームの仕組み自体はできました。

最後に、見た目を整えましょう。
最も簡単な方法で。
(ここまで微妙な灰色ばかり見てきて、テンションが上がらなかったかもしれません^^;)

CapsuleやCubeなどの3Dオブジェクトは、
「マテリアル」というコンポーネントをアタッチして、見た目を変えていきます。

ProjectウィンドウでMaterialsというフォルダを作ったら、
その中にCreateから「Material」を選び、Materialファイルを作成します。

1つのMaterialに対して色(またはパターン)は1種類です。

Player、Ground、Goal、Obstacle (1)~(3)、Obstacle (4)のために作成します。
(PlayerMat、GroundMat、GoalMat、ObstacleMat1、ObstacleMat2と名付けます)

アタッチしたら、Albedoから色を変更します。

ここで、例えば半透明な色にしたい場合、
そのMaterialのRenderingModeを「Transparent」にします。
すると、色の要素RGBAのA(不透明度)が反映されるようになります。
(図ではGoalMatを半透明なピンクにしてます)

Unityでは、
このような見た目(ライティングやシェーダーなど)の設定も非常に奥深く、
ゲーム開発の中でも、デザイン面において高いスキルを持っておくと
そのプロフェッショナルとして、需要があるでしょう。

 

次はUIのオブジェクトですが、
これらはMaterialをつけなくても色をつけることができます。
(すでにGAME OVERやSTAGE CLEARで色を変えました)

残すは、GOALと表示したTextオブジェクトとRetryButtonです。

TextはColor、
ButtonはButtonコンポーネントのNormal Color、
Button配下のTextオブジェクトのColor、
から色を調整できます。

 

 

というわけで
Unity入門講座はここまで。

障害物にぶつからず、ゴールを目指す基本的なゲームができたはずです!

 

余裕、余裕!、まだ物足りない!と言う方は
動く障害物(Obstacle)の実装にもチャレンジしてみてください^^

数学的なコードで動く障害物を作る

技術が身につき、独創的なゲームが作れるようになると、
アート作品を作る感覚で、ゲーム開発がもっと面白くなると思います!

新たな自己表現として、ゲーム開発にハマってみるのもありですね!^^

ABOUT ME
いなも@システマライフハッカー
”仙豆”を開発することを夢見て、健康食品会社で働いていたものの、2016年に出会ったロシアの武術”システマ”こそ、その糸口があると感銘し、勝手にシステマ普及活動を始める。 一方で、クリエイティブなモノ作りが好きで、DX社会で楽しみを見出せる"Unity”を活かして、”スマートかつ快適な暮らし”のヒントを発信している。

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA