CCS Unity入門記事 2020

 

こちらはCCS Advent Calendar 2020の23日目の記事です.

adventar.org

22日目の記事はこちら! VTuber たま〜に見ますが 良いですよねぇ(字余り) yoooomaruuuu.hatenablog.com

はじめに

今年はUnityを触っていることが多かったのでUnityについて書こうと思います.Unity勢が増えると嬉しいなあ...

Unityとは

Unityとはゲーム開発ツールです.PCからスマホ,ゲーム機,Web,VR/ARなど向けの開発に対応しています.2Dゲームも3Dゲームもこれで作れます!

読者の想定

  • CCSのC言語講座を受けた方 (or 何かしらのプログラミング言語の入門書を読んだことがあるくらいのレベルの方?)

  • C#に関する知識はなくてもOK

  • クラスベースのプログラミングをしたことがなくてもOK

とりあえず動かしてみるというよりは,応用が利きやすい内容を目指します.知っておくべきだと考えるUnityの仕様等のことを挟み周り道することがありますが,是非お付き合い下さい.

質問があればいつでも私に聞いて下さい!

この記事内でやること

  • 世界を作る!

  • ものを置く!

  • もの(プレイヤー)を操作できるようにする!

f:id:chinimuruhi:20201220165329p:plain

f:id:chinimuruhi:20201223180838g:plain

です.本当は敵を出したりしたかったけど...時間切れ...

この状態から敵を追加したり,というアレンジはしやすい内容を目指して書いているので是非その先も作りこんでみて欲しいですね...需要があれば続きを書くかも...?  

目次

 

 

Unityのインストール

Unityをダウンロード

unity3d.com

 こちらからダウンロードしていきましょう.「Unity Hub をダウンロード」をクリック. インストールが終わったらUnity Hubを起動しましょう.

Unity Hubのセットアップ

Unity本体をインストールしていきます.

左サイドメニューの「インストール」→右上の「インストール」を押します.  f:id:chinimuruhi:20201217235957p:plain

今回は記事を書いている時に推奨リリースとなっていた「Unity 2019.4.16f1(LTS)」を選びました.特に理由が無ければ推奨版か正式版の最新で良いと思います.

f:id:chinimuruhi:20201218005046p:plain

インストールするモジュールを選択します.PC向けゲームを作るならそのままの設定で良いと思います.スマホや他のOS向けにビルドしたい場合は〇〇Build Supportを追加するべきですが,こちらは後からでも追加できます.

 f:id:chinimuruhi:20201218005241p:plain

 Dev toolsの「Microsoft Visual Studio Community」はそこそこ重いです.

軽いのが良いという人はこちらのチェックを外して「Visual Studio Code」+拡張機能を導入すると良いと思います.以下を参考に.

UnityでVisual Studio Codeを使用できるようにするまでの手順 - Qiita

「次へ」を押してしばらく待てばインストール完了です.

 

プロジェクトの作成

 左サイドメニューの「プロジェクト」→「新規作成」を押します.

f:id:chinimuruhi:20201218010335p:plain

今回は3Dのゲームを作成しようと思うので「3D」を選択します. 

「プロジェクト名」と「保存先」を好きなように決め,「作成」を押します.

f:id:chinimuruhi:20201218010451p:plain

Editor画面の紹介

f:id:chinimuruhi:20201218012107p:plain プロジェクトの作成が終了するとEditor画面が表示されます.この画面について紹介していきます.

基本画面の紹介

Project

f:id:chinimuruhi:20201218012150p:plain

プロジェクトフォルダ内の情報です.デフォルトで「Assets」フォルダに「Scenes」フォルダが入っていることがわかります.

Hierarchy

f:id:chinimuruhi:20201218012337p:plain

この世界に存在しているオブジェクトが見れるところです. デフォルトで「Main Camera」と「Directional Light」がありますね.名前の通りカメラと照明です.

Game

f:id:chinimuruhi:20201218012433p:plain

ゲーム内でどのように見えているのか確認できる画面です.現在は「Main Camera」が映す映像がここに表示されるようになっています.

Scene f:id:chinimuruhi:20201218012506p:plain

世界の中を自由に見る・編集することができる画面です.

デフォルトだとGameの隣のタブにあります.

ゲーム感覚でオブジェクトを移動したりできます(後述).操作は以下の通りです.

  • 視点の移動:右クリックを押しながらマウスを動かす

  • 前進,後退:マウスのホイール

  • 左右に移動:マウスの真ん中のボタンを押しながらマウスを動かす

  • オブジェクトの選択:左クリック

Inspector

f:id:chinimuruhi:20201218012823p:plain

選択されているオブジェクトの詳細を表示してくれるところです.

試しにHierarchy上の「Main Camera」をクリックしてみましょう. Inspector上に座標などいろいろな情報が表示されたと思います.

Play/Pause

f:id:chinimuruhi:20201218012858p:plain

上部中央の再生ボタンを押すと作成中のゲームを動かすことができます.また,一時停止ボタンを押せばゲームを一時停止することもできます.

Editor画面のカスタマイズ

 ウィンドウの表示/非表示,位置,レイアウト等は自由に弄れます.自分の使いやすいようカスタマイズしていくと良いでしょう. 絶対にやっておくべきカスタムが一つだけあるので紹介します.

実行時の画面の色の変更

Edit>Preferencesをクリック

f:id:chinimuruhi:20201218013234p:plain

Preferences>Color>Playmode tint の色を分かりやすい色に変えます. f:id:chinimuruhi:20201218013417p:plain

変更後,Playボタンを押してゲームを動かしてみましょう.ゲームの実行時の画面が先ほど指定した色に変わります.これにより,編集時/ゲーム実行時の区別がつきやすくなります. f:id:chinimuruhi:20201218013456p:plain

Q. 何故,編集時/ゲーム実行時の区別がつきやすくする必要がある?

A. ゲーム実行中に行った作業は(ほとんど)実行終了後に破棄されてしまう為.

ゲーム実行中だと気づかずにステージづくり等をしてしまうと...泣いてしまいます.

 

世界を作る

Unityでは「Scene」という世界の中にいろいろなオブジェクトを置いていきます.まずはこのSceneを作成します.

 

Assetsフォルダの整理

Projectウィンドウから作業していきます.基本的に使うのはAssetsフォルダ内です.

元々Assetsフォルダ内に「Scenes」というフォルダがありますが,削除します.

f:id:chinimuruhi:20201221204310p:plain
元々あったフォルダを消して空になったAssetsフォルダ

Assetsフォルダ直下は後々色々なものが置かれる可能性がある為,自分の作業用のフォルダを作ることをお勧めします.

Assetsフォルダのところで右クリック>Create>Folder 今回は自分の作業用のフォルダ名は「_GameFolder」としました.(_をつけたのは名前順にしたときに上の方に表示される為)

 f:id:chinimuruhi:20201221204651p:plain

Sceneを複数作ることが今後あるかも知れないので,Scene保存用のフォルダを作ります.

先ほどと同様に_GameFolderのところで右クリックからフォルダを作成.名前は「Scenes」としました.

f:id:chinimuruhi:20201221204958p:plain  

Sceneの作成

いよいよ世界(Scene)を作っていきます.

先ほど作成した「Scenes」フォルダのところで右クリック>Create>Scene

「Main」という名前にしました.

f:id:chinimuruhi:20201221205528p:plain

これにてSceneの作成は完了です.ダブルクリックして開いてみましょう.

(この際に現在開いているサンプルのSceneを保存するか聞かれる可能性がありますが,使わないので「Don't Save」でOKです.)

Hierarchyウィンドウの上部に「Main」と出ていれば正常に開けています. f:id:chinimuruhi:20201221205433p:plain

世界にモノを置いていく

さて,「Main」という世界(Scene)が作成できました.ここにモノ(オブジェクト)を置いてみましょう.

オブジェクトを置いてみる

Hierarchyウィンドウで右クリック>3D Object>Cube

f:id:chinimuruhi:20201218015601p:plain

すると,HierarchyウィンドウにCubeが追加されました. HierarchyウィンドウのCubeをクリックして,Inspectorウィンドウから詳細を見てみましょう.

f:id:chinimuruhi:20201218015647p:plain

Transform欄では,現在のCubeのPosition(座標),Rotation(角度),Scale(大きさ)が見られます.

f:id:chinimuruhi:20201218015943p:plain

オブジェクトのPositionの(X,Y,Z)が(0,0,0)ではない位置に生成されていることがあります.このような場合にはTransformの右側の3つの点のマーク>Resetを押して位置をリセットしましょう.

オブジェクトを見る

Sceneウィンドウから先ほど作成したCubeを確認してみましょう.

見つからないor見にくい場合にはHierarchyウィンドウから見たいオブジェクト「Cube」をダブルクリックしましょう.自動でCubeの前まで画面が移動してくれます.

f:id:chinimuruhi:20201218020108p:plain

画像のように生成されたCubeが灰色の場合,照明が機能していない可能性があります.Lightingウィンドウから「Auto Generate」のチェックを外し,「Generate Lighting」を押しましょう(私が忘れていたためしばらく灰色のCubeのまま作業が進行します...)

f:id:chinimuruhi:20201218020351p:plain

 

オブジェクトの編集

右上の赤丸で囲んだところから編集モードを選べます.左から順に紹介していきます.

f:id:chinimuruhi:20201218020055p:plain

Hand Tool:視点の移動に使えます.

Move Tool:オブジェクトの移動ができます.

Rotate Tool:オブジェクトの回転ができます.

Scale Tool:オブジェクトの大きさを変更できます.

Rect Tool:こちらもオブジェクトの大きさを変更できます.Scale Toolとの違いは触ってみればわかります.試してみよう!

Move, Rotate or Scale selected objects:Move Tool + Rotate Tool + Scale Toolの欲張りセット となっています.

まずはオブジェクトの移動をしたいので,Move Toolを選択しましょう.

Move Toolを選択したら,Sceneウィンドウ上(又はHierarchyウィンドウ上)からCubeを選択しましょう.

f:id:chinimuruhi:20201218020742p:plain

出てきた矢印をクリックして動かすことでオブジェクトを移動できます.

色々試してみよう

その他のツールも使ってオブジェクトを回転させたり,サイズを変えたりしてみましょう.結構感覚的に操作できます.

また,InspectorウィンドウのTransformからも操作できます.Inspector上の変更したい値(テキストボックスの外)をクリックして左右に動かすことでも値を変更できます

f:id:chinimuruhi:20201218020808g:plain

 

モノを動かしてみる

UnityではRigidbodyという物理エンジンを使うことが出来ます.

が,物理エンジンを使う前にまずはUnityのGameObjectの話をしていこうと思います.

GameObjectの話

先ほどSceneを作ってCubeを置いた通り,Sceneを(主に)色々なGameObjectから構成していきます.元々あった「Main Camera」や「Directional Light」,作成した「Cube」も全てGameObjectです.

f:id:chinimuruhi:20201218021106j:plain

GameObjectは様々なComponentから構成されています.GameObjectがどんなComponentを持っているかによって,性質が変わってきます.

f:id:chinimuruhi:20201218021132j:plain

例として先ほど作成したCubeをInspectorウィンドウから見てみましょう.「Transform」「Mesh Filter」「Mesh Renderer」「Box Collider」から構成されています.それぞれのComponentを軽く紹介します.

f:id:chinimuruhi:20201218021204p:plain

Transform:オブジェクトの座標,角度,大きさ等を保存

Mesh Filter:メッシュ(オブジェクトの形の情報)をMesh Rendererに渡す

Mesh Renderer:Mesh Filterに渡されたメッシュを描写する

Box Collider:直方体の形の当たり判定

つまり,Cubeは位置・角度・大きさの情報,オブジェクトの描写機能,当たり判定機能から構成されています.

これらのComponentを消したり,他のComponentをつけたりすることで求める性質を持つGameObjectへと変えていくことがUnityでのゲーム制作の根幹となります.

物理エンジンのComponentをつける

さて,このCubeが動くよう,物理エンジンのComponentを付けてみます.

CubeのInspector上でAddComponentを押す

f:id:chinimuruhi:20201218021753p:plain

「Rigidbody」と検索をかけ,出てきたものをクリック.

f:id:chinimuruhi:20201218021825p:plain

こうしてCubeに物理エンジン(Rigidbody)をつけることが出来ました.ここから質量や空気抵抗等のパラメータを変えられます.詳しい説明は割愛します.

f:id:chinimuruhi:20201218021840p:plain

実行してみる

上部中央の再生ボタンを押して,動作を確認してみましょう.

f:id:chinimuruhi:20201218021958g:plain

Cubeに重力が働き,落ちていきました.これだと寂しいのでもう一つCubeを作成して引き伸ばして傾けて,坂でも作ってあげましょう. 坂は作成したCubeの位置と角度と大きさを弄ってあげるだけでOKです,以下の2点の理由により.

  • 坂自体は動かない為物理エンジンが不要

  • 衝突判定を行うための「Box Collider」が元々ついている(衝突判定を行うにはぶつかるオブジェクトの少なくとも一方がRigidbodyコンポーネントを持っている必要があるので注意,今回は動くCubeが持っているのでOK)

f:id:chinimuruhi:20201218022047g:plain

良い感じに転がっていきましたね!神ゲームです.

 

 

ステージづくり

これから地面の上をプレイヤー(球)が動く,という動作を目指します.

f:id:chinimuruhi:20201220120759p:plain

まずは必要なオブジェクトを配置していきましょう.先ほどまで使用していたCubeは要らないので消してしまいます.

プレイヤーの配置

今回は球を配置してそれをプレイヤーとしましょう.Cubeを設置した時と同様に

Hierarchyウィンドウで右クリック>3D Object>Shpereを選択します.

Inspector上で名前を変更できます.分かりやすいように名前をつけておきましょう.また,今回はプレイヤーを物理エンジンを使って動かしたい為,「Rigidbody」コンポーネントも追加します.

f:id:chinimuruhi:20201220121953p:plain

ステージの配置

 ステージはいくつかのオブジェクト(地面,壁など)が組み合わさって構成される為,管理しやすいようにまとめておきます.

Hierarchyウィンドウで右クリック>Create Emptyを選択.空のGameObjectが作成されます.これをフォルダのように扱います.Inspector上から名前をStageとしておきました.

作成したStageで右クリック>3D Object>CubeとすることでStageの子要素にCubeを作成することができます.

f:id:chinimuruhi:20201220133508p:plain

Stageの子要素に壁と床をCubeで作成してみましょう.Sceneウィンドウからゲーム感覚で配置していくことができると思います.

f:id:chinimuruhi:20201220135632p:plain

Gameウィンドウでもステージが一覧できるようMain Cameraの位置も調整します.

f:id:chinimuruhi:20201220140611p:plain

ひとまずステージ完成.現在のHierarchyは以下のようになっています. f:id:chinimuruhi:20201220135828p:plain

 

Materialをつける

全てのオブジェクトが白だと見にくいので色をつけていきます.

Unityでは(見た目上の)材質を作成し,それをオブジェクト(のRenderer(描写用コンポーネント))に割り当てることでオブジェクトの見た目を変化させます.

Materialの作成

ProjectウィンドウからAssets/_GameFolder内に「Materials」という名前でフォルダを作成します.

作成したフォルダの中で右クリック>Create>Material

作成されたら好きな名前をつけましょう.

Materialの色の変更

作成したMaterialをクリックすると,InspectorにMaterialの詳細が表示されます.

Main MapsのAlbedoの色を好きな色に変更します.

f:id:chinimuruhi:20201220163736p:plain

他にもShaderを変えたりMetallic属性を変えたりしてガラスっぽい素材や金属感のある素材を作ることも出来ますが,今回は説明を割愛します.

Materialの割り当て

HierarchyウィンドウからPlayerのGameObjectを選択,InspectorからMeshRendererコンポーネント(オブジェクト描写用のコンポーネント)を見てみましょう.

Materialsという項目があります.こちらのElement0の欄に先ほど作成したMaterialをドラック&ドロップします.

f:id:chinimuruhi:20201220164504p:plain

 Playerオブジェクトの色が変わったことを確認しましょう.

同様にしてステージのオブジェクト用にもMaterialを作成し,色をつけて分かりやすくしました.ステージづくりはひとまずこれで終了です.

f:id:chinimuruhi:20201220165329p:plain

 

 

プレイヤーを動かす(Componentを作る)1

先ほど設置したPlayerが操作した通りに動くようにするには,「オブジェクトが自分の押したキーの方向の速度を持つようになるComponent」を追加すれば良いのです.しかしそんな都合の良いComponentが存在するはずもなく...(?)

みなさんお待ちかねプログラミングの時間です.自分でComponentを作りましょう!

Componentのソースファイルを作成

ProjectウィンドウからAssets/_GameFolder内に「Scripts」という名前でフォルダを作成します.

作成したフォルダの中で右クリック>Create>C# Script

名前を「PlayerMover」としました.一度Scriptファイルを作成したら極力ファイル名は変えない方が良いと思います.

もしファイル生成時に名前をつけ損ねて「NewBehaviourScript」という名前になってしまった場合,一度削除をして再度ファイルを作り直すことをお勧めします.

 

作成したファイルを開いてみる

作成したファイルをダブルクリックするとテキストエディタが起動します.デフォルトで以下のような内容が記述されていると思います.

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

public class PlayerMover : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
    }

    // Update is called once per frame
    void Update()
    {
    }
}

このファイル内の以下の箇所でこのコンポーネントがどういうものなのかを定義していきます.

public class PlayerMover : MonoBehaviour
{
    //省略
}

定義していく際にクラスというものを使用します.クラスについては後述.

動かしてみる

これから作る予定のコンポーネントの完成品が以下のものになります.説明の前に,どのような動作になるか先に一度動かしてみます.

PlayerMover.csに以下のように打ち込んで保存.

using UnityEngine;

public class PlayerMover : MonoBehaviour
{
    [SerializeField] float speed;

    Rigidbody rb;
    Vector3 velocity;

    void Awake()
    {
        rb = GetComponent<Rigidbody>(); //rbの参照先をRigidbodyの実体に
        velocity = new Vector3(0, 0, 0); //速度の初期値を0ベクトルに
    }

    void Update()
    {
        //キーの入力から速度を決定
        float dx = Input.GetAxisRaw("Horizontal"); //横方向のキー入力を取得
        float dy = Input.GetAxisRaw("Vertical"); //縦方向のキー入力を取得

        Vector3 direction = new Vector3(dx, 0, dy); //方向を決定
        velocity = speed * direction.normalized; //速度ベクトルを決定
    }

    void FixedUpdate()
    {
        rb.velocity = velocity; //物体の速度を書き換える
    }
}

Unityの画面に戻り,HierarchyウィンドウからPlayerを選択,PlayerのInspectorからAddComponent>PlayerMover

f:id:chinimuruhi:20201223181148p:plain

 PlayerMoverコンポーネントが追加されたら,Speedを好きな値に設定しましょう.

f:id:chinimuruhi:20201223181306p:plain

再生ボタンを押して実行してみます.WASDや矢印キーでプレイヤーを動かせました.

f:id:chinimuruhi:20201223180838g:plain

さて,ここからはこのスクリプトの作成方法を説明していきます.

クラスの話

一度Unityから離れてクラスについて説明します.一応知らなくても動くものは作れるので,とりあえず動かしたい!という人はスキップしてもOKです. 予期せぬ動作を防ぐために知っておくべきだと思う点について書きます.

以下のソースコードはUnity上でも動きますが動作の確認が面倒な為,眺めて貰えればOKです.  

クラスとは

クラスとは自分で作る新しい型です(int型やfloat型とか言うあの型).既存の型の変数や関数を組み合わせて作ります.

クラスを使うには,次の2段階のステップを踏みます.

  1. クラスを定義する(どのような型なのかを書く)

  2. 作った型(クラス)をインスタンス化(実体化)する

例として,Human型(クラス)を作り,インスタンス化(実体化)してtaroと名前をつけ,使用してみます.

クラスの定義

身長と体重のデータと,BMIを取得する機能からなる型(クラス)を定義すると以下のようになります.

public class Human
{
    public float Height; //身長
    public float Weight; //体重
    
    //BMIを取得する関数
    public float GetBMI(){
        return Weight / ( Height * Height );
    }
}

Human型をfloat型の変数のHeightとWeight,関数GetBMI()から構成しました.クラスの中に含まれる変数や関数をそれぞれ「メンバ変数」,「メンバ関数」と言います.

インスタンス化,使用

クラスの定義では型がどういうものなのかを決めただけなので,使用するにはその型の実体(インスタンス)を作る必要があります.

先ほど人間というのがどういう型なのかを決めたので,使用するために太郎を作ります.

「new 型名()」によってHuman型の実体(インスタンス)を生成し,taroという名前に結び付けます.

Human taro =  new Human();

クラスの中のメンバ変数には「.(ドット)」でアクセスできます.「taro.Height」と書いたらtaroの中のHeightという意味になります. 太郎の身長と体重を代入してみます.

taro.Height = 2.26; //身長を代入
taro.Weight = 105.3; //体重を代入

クラスの中のメンバ関数も同様に「.(ドット)」でアクセスできます.「taro.GetBMI()」と書けばtaroの中のGetBMI関数を呼ぶことができます.

float bmi = taro.GetBMI(); //BMIを計算してbmiという変数に入れる
Componentのクラスのインスタンス

Humanクラスでは「new 型名()」によって新しい実体を作成していましたが,Unity上でのComponentのクラス(MonoBehaviourを継承するクラス(後述))は「new 型名()」によって実体を作成することはできません.

f:id:chinimuruhi:20201223224303j:plain

その代わり,GameObjectにComponentとして追加される際に実体化されます.Inspector上でAdd Componentをする際にクラスそのものをつけているのではなく,クラスを基にインスタンス化したものをつけています.

f:id:chinimuruhi:20201223230441p:plain

アクセス修飾子

「public float Height;」のように変数や関数の前,クラスの定義部分に書いていた「public」をアクセス修飾子と言います.

全てのメンバ変数・メンバ関数にアクセス出来てしまうと困ることがあるので,このアクセス修飾子によって利用できる範囲を制御します.

代表的なものを2つ紹介します.

public:どこからでもアクセス可能(クラスの外,他のファイルなどからでもOK)

private:クラスの内部でのみアクセス可能

例としてこんなクラスを作ってみます

public class Test{
    int a; //アクセス修飾子を省略するとprivate
    private int b; //privateメンバ
    public int c; //pulicメンバ

    private int Func1(){
        return b + c; //クラス内なのでbが使用可能
    }

    public int Func2(){
        int num = c + Func1(); //クラス内なのでfunc1が使用可能
        return num;
    }

    public void SetB(int _b){
        b = _b; //クラス内なのでbは使用可能
    }

    public int GetB(){
        return b; //クラス内なのでbは使用可能
    }

}

アクセス修飾子を省略するとprivate扱いになります.クラス内ではpublic,private両方の変数・関数の使用が可能です.

インスタンス化して使ってみます.どのメンバにアクセスできるのか,考えてみましょう.

int num;
Test test = new Test();
test.a = 10; //NG.aはprivate
test.b = 5; //NG.bはprivate
test.c = 3; //OK!cはpublicメンバ
num = test.Func1(); //NG.Func1はprivateメンバ
num = test.Func2(); //OK!Func2はpublicメンバ
tets.SetB(226); //OK! SetB関数の中でtest.bに226が代入される
num = test.GetB(); //OK! ↑でtest.bが226になっているためnumは226になる

動作についてはソースコード内にコメントで書き込んだ通りです.クラスの外からはprivateメンバにはアクセス出来ず,publicメンバにだけアクセスできます.

メンバがどこからでも使用可能という状態は危険であるため,出来る限りのメンバはprivateにしておくのが良いと思います.

値型と参照型

C#の型は大きく分けて2つの型に分けられます.

値型:変数に直接値が格納されるもの.数値型(int, float等),bool型,enum型,構造体など.

参照型:変数が持っているのは参照情報(実体が存在する場所の情報)だけのもの.クラス配列,文字列型など.

使用している型がどちらのタイプかにより,振る舞いが変わってきます.特に代入時(〇〇 = △△;)の振る舞いが大きく変わります.以下に例を示します.

まずは値型であるint型で以下のようなコードを考えます.

int a, b; 
a = 10;
b = a;
b = 53;

値型であるintにおいてはaとbはそれぞれ異なる実体を持ちます.また,「b=a;」とした時はaが持つ実体の中身をbにコピーするという動作をします.

aとbは異なる実体を持つため,bを書き換えてもaが書き換わることはありません.

f:id:chinimuruhi:20201223155122p:plain

続いて,参照型であるHumanクラスで以下のようなコードを考えます.

Human a, b; 
a = new Human();
a.Height = 2.26;
b = a;
b.Height = 1.53;

参照型の変数はそのままでは実体を持ちません.インスタンス化された実体への参照を持たせることができます.「b=a;」とした時にはbがaの指している実体を指すようになります(aの参照情報をbにコピーする).よって,aとbが同じ実体を指しているため,bの内容を変更するとaも変更されます.

f:id:chinimuruhi:20201223163443p:plain

値型と参照型を区別せずに使っていると変数の内容の意図しない変更を行ってしまう可能性があります.代入や関数との受け渡しには特に注意しましょう.

プレイヤーを動かす(Componentを作る)2

Unityの話に戻ります.プレイヤーを動かすComponentのクラスを作成していきます.

MonoBehaviourの話

まず,Componentを作るためのクラスについて説明します.

先ほど作成したソースファイル内で,クラスを定義している部分

public class PlayerMover : MonoBehaviour
{
    //省略
}

の「 : MonoBehaviour」はMonoBehaviourクラスの派生したもの,という意味です.つまり,ここでは「MonoBehaviour」クラスの機能を引き継いだクラスである「PlayerMover」クラスを定義しようとしています.(派生したクラスの作成をクラスの継承と言います.クラスの継承について詳しくは割愛します.)

MonoBehaviourとは?

Componentを作るための素だと思ってもらえば大丈夫です.

MonoBehaviour(を継承したクラス)はデフォルトで用意されたイベント関数(Unity側で勝手に呼んでくれる関数)が沢山あります.その中のいくつかを紹介します.

イベント関数の紹介

void Awake():ゲームが起動された後,オブジェクトが生成された瞬間一度呼ばれます.

void Start():ゲームが起動された後,最初のフレームの前に一度呼ばれます.

void Update():毎フレーム呼ばれます.入力の処理等はここに書くと良い.

void FixedUpdate():固定時間につき一回呼ばれます.物理挙動関連の処理はここに書くと良い.

これらのフローチャートは以下のような感じです.

(詳細なフローチャートを見たい方は→イベント関数の実行順序 - Unity マニュアル)

f:id:chinimuruhi:20201223191634p:plain

これらの関数を記述しておけばUnity側で勝手に呼び出してくれます.この関数の中に必要な処理を書いていくことでコンポーネントの性質を決めていきます.

他にも衝突が起こった時に呼ばれるイベント関数等様々なものがありますので,必要に応じてググりましょう.

スクリプトの作成

行いたい動作

ここで一度行いたい動作を整理します.必要な処理は以下のようになります.

  1. 入力した方向を取得する

  2. 入力した方向で,指定した大きさの速度ベクトルを作成する

  3. プレイヤーの物理エンジンの速度を書き換える

イベント関数を書く

PlayerMoverクラスの中に必要なイベント関数を作成します.初期化用のAwakeと毎フレーム呼ばれるUpdate,固定時間毎に呼ばれるFixedUpdateを作成しました.

using UnityEngine;

public class PlayerMover : MonoBehaviour
{
    void Awake()
    {
    }

    void Update()
    {

    }

    void FixedUpdate()
    {
    }
}
メンバ変数の作成

今回必要なメンバ変数は

です.以下のように作成します.

using UnityEngine;

public class PlayerMover : MonoBehaviour
{
    [SerializeField] float speed; //移動スピード

    Rigidbody rb; //Rigidbodyコンポーネントの参照を保存する変数
    Vector3 velocity; //速度ベクトル

     //省略
}

変数の前に"[SerializeField]"とつけることでUnityの画面上から値や参照を変えられるようになります.

f:id:chinimuruhi:20201223181306p:plain

speedを[SerializeField]指定しました.

[SerializeField] float speed;

物理エンジンのRigidbodyコンポーネントの型はRigidbodyです.Rigidbody型で変数を作成しました.

Rigidbody rb;

Vector3はUnityにある3次元ベクトルの型です.これを用いて算出した速度を保存しておくためのベクトルvelocityを作成しました.

Vector3 velocity;
Awakeの中身

続いてAwakeの中身です.Awakeでは今回はメンバ変数の初期化を行います.

speedはUnityの画面上で入力した値で初期化されるため,Awake内で初期化する必要があるのはrbとvelocityです. 以下のように初期化します.

using UnityEngine;

public class PlayerMover : MonoBehaviour
{
    [SerializeField] float speed; //移動スピード

    Rigidbody rb; //Rigidbodyコンポーネントの参照を保存する変数
    Vector3 velocity; //速度ベクトル


    void Awake()
    {
        rb = GetComponent<Rigidbody>(); //rbの参照先をRigidbodyの実体に
        velocity = new Vector3(0, 0, 0); //速度の初期値を0ベクトルに
    }

     //省略
}

細かい箇所について説明していきます.

 rb = GetComponent<Rigidbody>();

「GetComponent<型名>()」で自身と同じGameObjectに付いているComponentの参照を取得できます.GetComponent関数はそこそこ重いため,沢山呼ばれるようなUpdate関数やFixedUpdate関数の中で呼ばないようにすべきです.その為,初期化の段階で呼んで予め参照を取得しておきます.

今回は自身と同じGameObjectに付いているRigidbodyの参照を取得し,rbに入れました.

velocity = new Vector3(0, 0, 0);

初期の速度を0ベクトルにします.「new Vector3()」で新しいベクトルを作成しvelocityにコピー.この際,「new Vector3(0, 0, 0)」とすることでx成分,y成分,z成分それぞれに0を入れることができます.

(Vector3は値型(構造体)であり,変数作成時のデフォルトの値が(0, 0, 0)なのでこの処理は無くても動きます.)

Updateの中身

続いてUpdateの中身です.ここで行うべきことは入力の処理等であるので,

  1. 入力した方向を取得する

  2. 入力した方向で,指定した大きさの速度ベクトルを作成する

を記述します.以下のようになります.

using UnityEngine;

public class PlayerMover : MonoBehaviour
{
    //省略

    void Update()
    {
        //キーの入力から速度を決定
        float dx = Input.GetAxisRaw("Horizontal"); //横方向のキー入力を取得
        float dy = Input.GetAxisRaw("Vertical"); //縦方向のキー入力を取得

        Vector3 direction = new Vector3(dx, 0, dy); //方向を決定
        velocity = speed * direction.normalized; //速度ベクトルを決定

    }

     //省略
}

それぞれ細かい箇所について説明していきます.

//キーの入力から速度を決定
float dx = Input.GetAxisRaw("Horizontal"); //横方向のキー入力を取得
float dy = Input.GetAxisRaw("Vertical"); //縦方向のキー入力を取得

Input.GetAxisRaw();で方向キーの入力を取得,横方向と縦方向それぞれの成分をdxとdyに入れています.

Vector3 direction = new Vector3(dx, 0, dy); //方向を決定

取得した方向キーのデータから進行方向のベクトルを作成するため,「new Vector3()」で新しいベクトルを作成.この際,「new Vector3(dx, 0, dy)」とすることでx成分にdx,z成分にdyを入れることができます.

dyをz成分に入れた理由はUnityの座標軸が以下のようになっている為です. f:id:chinimuruhi:20201223220555p:plain

x:左右方向,右が正

y:上下方向,上が正

z:奥手前方向,奥が正

velocity = speed * direction.normalized; //速度ベクトルを決定

(Vector3の変数).normalizedで長さを1にしたベクトルが取得できます.「direction.normalized」(長さを1にした方向ベクトル)にspeedを掛けることで,入力された向きで大きさがspeedの速度ベクトルを算出しています.

FixedUpdateの中身

最後にFixedUpdateです.ここで行うべきことは物理挙動関連の処理であるので,

  • 速度の書き換え

を行います.

using UnityEngine;

public class PlayerMover : MonoBehaviour
{
    //省略

    void FixedUpdate()
    {
        rb.velocity = velocity; //物体の速度を書き換える
    }
}

FixedUpdate内部では以下のようにして速度を書き換えています.

  1. Awakeで取得しておいたRigidbodyの参照(rb)から速度にアクセス(rb.velocity).

  2. 速度をUpdateで算出しておいたvelocityに書き換える(rb.velocity = velocity)

以上で「PlayerMover」の完成です.

AwakeとStartの使い分け

今回のPlayerMoverでは初期化にAwakeのみを使いましたが,先ほど紹介した通り初期化のための関数は2つあります(AwakeとStart).それぞれ以下のような役割で使い分けると良いと思います.

void Awake():メンバ変数の初期化,他のオブジェクトの参照の取得等

void Start():他のオブジェクトの値の取得等

このような使い分けを行うことで,以下の問題を回避できます.

Unityでは,以下のような順番でイベント関数が呼ばれて行きます.

  1. 全てのオブジェクトのAwake

  2. 全てのオブジェクトのStart

  3. 全てのオブジェクトのFixedUpdate

  4. 全てのオブジェクトのUpdate

...

「どの種類のイベント関数から呼ばれるか」という順番は固定ですが,「どのオブジェクトから呼ばれるかは未定」です.

例えば,以下の画像のような場合,AとBのどちらのStartが先に呼ばれるかによって結果が変わってしまいます.

B内のStartにおいて,A内のNumを値を使用します.この時,

  • Aが先の場合,Num = 10として処理が進む

  • Bが先の場合,A内のStartが呼ばれていない為,Num = 0(デフォルト値)として処理が進む

f:id:chinimuruhi:20201223221311p:plain

このような事態を避けるため,先に呼ばれるAwakeでメンバ変数の初期化や他のオブジェクトの参照の取得を行い,他のオブジェクトの値の取得は後に呼ばれるStartで行うべきだと考えます.

f:id:chinimuruhi:20201223222354p:plain

今回のPlayerMoverでは必要な処理がメンバ変数の初期化と他のオブジェクトの参照の取得のみであったため,Startが必要なかったという訳です.以上でPlayerMoveコンポーネントスクリプトの説明を終わります.

さいごに

もし手元にUnityを開いてここまでの作業をして貰えていたら嬉しすぎておじさん泣いちゃいます.Unityの便利な機能の紹介があまりできなかったのが心残りです.是非使いながら色々調べてみて欲しい.

ゲームを作れるようになりたいなら実践が一番だと思います.ドンドン作ってみて,必要に応じて調べるというスタンスで良いのかなあと個人的には思います.

この記事を一度読んで全てのことを理解するのは難しいと思いますが,「あらゆるComponentをつけたり,消したりすることで求める性質を持つGameObjectへと変えていく」ということが抑えられていればゲームの制作,アレンジもスムーズに行えるようになるのではないかと思います.

敵の追加も「プレイヤーを追いかけてくるコンポーネント」だったり「敵にぶつかったら自身が消滅するコンポーネント」などを作成すれば実現できそうです.

また,全てのComponentを自分で作る必要はありません.「Assets Store」で色々DLできたり,ググると色々出てきたりします.個人でもクオリティ上げやすいのがUnityの良いところの一つだと思います.