Unityはゲーム開発をアシストする非常に優れた開発エンジンです。
直感的に作成していく部分と
プログラミングが必要な部分があります。
特にプログラミングで作っていく部分は、
プログラミングの概念を知らない方には当然とっつきにくいものでしょう。
本講座は、Unity初心者に向けて
●3Dゲームを作る方法
●ゲームに基礎的なプログラミングを組み込む方法
を学べるように内容になっています。
最初の登竜門的な講座として、取り組んでみてください^^
1. シーンにオブジェクトを配置しよう
Unityのプロジェクト作成では3Dを選びます。
SampleSceneが開かれているはずなので、
図のように、「+」から「Capsule」、「Cube」、「Plane」を作成します。
*ちなみに、点線で囲ったタブはよく使いますし、図のように配置させると開発を進めやすい
各オブジェクトに対して、図のようにInspectorを設定します。
<Capsule(図の左側)>
・「Capsule」に”Player”と名づける
・Positionを変更する
・「Add Component」から「Rigidbody」コンポーネントをアタッチする
<Plane(図の右側)>
・「Plane」に”Ground”と名づける
・Scaleを変更する

ここで少し解説!
「Scene」というタブと「Game」というタブがあります。
「Scene」タブはオブジェクトを配置するなど、開発向けの画面で、
「Game」タブは実際のゲーム実行時の画面です。
ゲーム実行時とは・・・?
「▶︎」をクリックしてみましょう。
Playerが地面に落下するのが見られたと思います。
ゲームが実行されて、ゲーム世界の時間が進み出したというわけです。
落下した理由は
PlayerのRigidbodyコンポーネントにより
Playerに重力がはたらいたのです。
2. Camera(便利なCinemachine)の実装
ゲームの画面(「Game」タブ)は
今現在SceneにあるMainCameraが映し出す画面を反映させています。
ここでMainCameraを使っても良いのですが、
ここではCinemachineというカメラを使いましょう。
CinemachineのVirtual Cameraを使うことで、
複数のカメラを設置し、視点変更が簡単にできるようになったり
カメラの動きを滑らかにしたりできます。
使っているUnityのversionにもよりますが、
私が使っている「2019.4」はCinemachineを使うために、「window」タブから「Package Manager」を開き、「Cinemachine」をインストールする必要があります。
(Add ComponentからCinemachineがない方はインストールしましょう)
インストールしたら、コンポーネントからCinemachineの機能を使えるようになります。
さて、ここで作るゲームは、
キャラクターを背後から追従するような視点のゲームです。
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"と名付けて、次のコードを書きましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
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();<br /> //スペースキーによるジャンプ if (Input.GetKeyDown(KeyCode.Space)) {<br /> if(isGrounded)<br /> {<br /> isGrounded = false; rb.AddForce(Vector3.up * jumpSpeed, ForceMode.Impulse);<br /> } } }<br /><br /> //地面を検知する関数<br /> bool CheckGrounded()<br /> {<br /> var ray = new Ray(transform.position + Vector3.up * 0.01f, Vector3.down);<br /> var distance = 1.5f;<br /> return Physics.Raycast(ray, distance);<br /> } } |
オブジェクトを移動させる方法は、色々とありますが、
今回は、最もシンプルなtransformのTranslateを採用しました。
なお、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」が生成されます。
「Text」には、"ResultText"と名付けます。
Inspectorは次のように設定してします。
画面中央にこのTextが表示されるようになればOK。

ちなみにシーンのどこにオブジェクトがあるかわからなくなったら、
Hierarchyのオブジェクトネームをダブルクリックしましょう^^;
「Button」には、"Retry Button"と名付けます。
Buttonオブジェクトはクリックすると、
関数(メソッド)を呼び出せるできるオブジェクトです。

メソッドって何?となると思いますが、
スクリプトに書いた特定の処理です。
とりあえず、ResultTextの下あたりに配置しておきます。
それでは、お待ちかねステージクリアのスクリプトを作っていきます。
Scriptsフォルダ内に、StageClearと名付けたC#Scriptを作り、Goalオブジェクトにアタッチします。
内容は次の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
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")) { 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」を表示させること。
そして、ゲームで進行する時間を「Time.timeScale = 0」で止め、プレイヤーを操作しなくしています。
また、「OnClickRetryBtn」という関数も書いてます。
これはRetryButtonをクリックしたときに呼ぶ関数です。
SceneManagerを用いて、このゲームシーンをリロードする処理です。
SceneManagerを用いる場合、「using SceneManagement」を書いておく必要があります。
ここでは()内の引数はシーンの名前を記載することもできますが、Sceneに当てられた番号を記載しても機能します。
ステージクリアして、次のステージに遷移させたいときは、
ここを変更すれば、よいのです。
ゲームシーンをリロードしたら、「Time.timeScale = 1」でゲームを再進行させます。
さて、スクリプトが作成できたら、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オブジェクトの上あたりに配置することができました。
それでは、ゴールに当たって、クリア処理がうまく機能するか、
ボタンによりゲームをリトライできるか、確認してみましょう。
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オブジェクトを全て選択した状態で、設定しましょう。
(一挙に反映されて作業軽減です)
スクリプトの内容は次の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
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; } } } |
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)の実装にもチャレンジしてみてください^^
数学的なコードで動く障害物を作る記事↓
https://youdoyou-motto.com/?p=5673
技術が身につき、独創的なゲームが作れるようになると、
まるで芸術作品を作るようで、ゲーム開発がもっと面白くなると思います!
これから新たな趣味として、ゲーム開発をはじめてみましょう^^