背景
Unity LearnのCreate with Codeで使ったコードを自分用にまとめています。
Unityでゲームを作り始めたけど、プログラミングが難しいと感じている方にとって、有用な記事になればと思います。
より正確な情報については公式マニュアルを参照ください。
コード
MonoBehaviour(基本クラス)
StartやUpdateなど、重要なイベントを使う際に継承する。
FixedUpdate()
固定フレームレートごとに呼び出される。
また、物理挙動の更新の直前に呼び出される。
(例)Rigidbodyに力を加える。
Update()
Physics (Collisionなど、Unityに備わっている物理関係の処理) の後、
マウスなどからの入力を受けた直後に呼び出される。
(私の理解: 固定フレームレートではないため、負荷が大きいフレームでは、最悪、呼び出されない)
LateUpdate()
Game logicの最後(物理計算、アニメーションなどが終了し、レンダリングの本当に直前)に呼び出される。
(例)プレイヤーに追従するカメラの位置の更新
※参考: イベント関数の実行順序
Collider.OnCollisionEnter(Collision)
衝突する2つのオブジェクトの両方とも、
collider/rigidbodyをアタッチしておく必要がある。
かつ、どちらか一方のrigidbodyのkinematicを無効にしておく必要がある。
(私の理解: Rigidbodyをアタッチされている物体同士の衝突の検出に使う)
敵を弾き飛ばすサンプルコード
private void OnCollisionEnter(Collision collision)
{
if(collition.gameObject.CompareTag("Enemy")){
Rigidbody enemyRigidbody = collision.gameObject.GetComponent<Rigidbody>();
Vector3 awayFromPlayer = (collision.gameObject.transform.position - transfrom.position);
enemyRigidbody.AddForce(awayFromPlayer, ForceMode.Impulse);
}
OnCollisionExit(Collision)
接している状態から離れるときに呼びたければ、こちらを使う
上下の壁で跳ね返るボールのサンプルコード
private void OnCollisionExit(Collision other)
{
var velocity = m_Rigidbody.velocity;
//after a collision we accelerate a bit
velocity += velocity.normalized * 0.01f;
//check if we are not going totally vertically as this would lead to being stuck, we add a little vertical force
if (Vector3.Dot(velocity.normalized, Vector3.up) < 0.1f)
{
velocity += velocity.y > 0 ? Vector3.up * 0.5f : Vector3.down * 0.5f;
}
//max velocity
if (velocity.magnitude > 3.0f)
{
velocity = velocity.normalized * 3.0f;
}
m_Rigidbody.velocity = velocity;
Collider.OnTriggerEnter(Collider)
衝突する2つのオブジェクトの両方とも、
Colliderコンポーネントをアタッチしておく必要がある。
かつ、どちらか一方はCollider.isTrigger
を有効にして、
さらに、rigidbodyをアタッチしておく必要がある。
(私の理解: インターフォンを押す人(Trigger)とインターフォンのように役割を分ける)
アイテムを取得するサンプルコード
private void OnTriggerEnter(Collider other)
{
if(other.CompareTag("Item")){
hasItem = true;
Destroy(other.gameObject);
{
}
OnMouseDown()
コライダーを持つオブジェクトの上でマウスのボタンが押されたときに呼ばれる
InvokeRepeating(〇〇〇, float time, float repeatRate)
設定した秒数timeでメソッドを呼び出し、repeatRate秒ごとにリピートする。
〇〇〇はメソッドの名前。
InvokeRepeatingで一定秒数ごとにスポーンさせる
void Start()
{
playerControllerScript = GameObject.Find("Player").GetComponent<PlayerController>();
InvokeRepeating("SpawnObstacle", startDelay, repeatRate);
}
void SpawnObstacle()
{
if (playerControllerScript.gameOver == false)
{
Instantiate(obstaclePrefab, spawnPos, obstaclePrefab.transform.rotation);
}
}
StartCoroutine(IEnumerator routine)
IEnumeratorに記述した処理を1フレームずつ進める(同期処理)。
反復子yeild
を使って、処理の継続(returnの後に反復処理の次の値)や終了(break)を指示する。
(例)処理の継続時間をWaitForSecondで秒数指定する
private void OnTriggerEnter(Collider other)
{
if(other.CompareTag("Powerup")){
hasPowerup = true;
Destroy(other.gameObject);
StartCoroutine(PowerupCountdownRoutine()}
}
IEnumerator PowerupCountdownRoutine()
{
yield return new WaitForSecond(7);
hasPowerup = false;
}
Transform (位置、回転などに関するクラス)
transform.Translate(〇〇〇, △△)
〇〇〇にはVector3が入る。
△△には座標系の指定(Space.SelfまたはSpace.World)が入り、省略するとローカル座標になる。
Ridgidbody
Rigidbody型の変数はStartで代入する。
※参照型
(例)playerRbという名前のRigidbody型の変数を宣言しておいて、
private void Start(){
playerRb = GetComponent<Rigidbody>();}
Rigidbody.AddRelativeForce("〇〇〇")
〇〇〇にはfloat型の値が入る。
ローカル座標に対して力を加える。
※AddForceはグローバル座標に対応
Collision(衝突したオブジェクトの情報を扱うクラス)
衝突した際に接した点、速度などが含まれる。
これらの情報はCollider.OnCollisionEnter
などのイベントに渡される。
Physics
Physics.SphereCast
球状のレイが任意のコライダーと交わる場合にtrue、それ以外はfalseを返す。
(例)groundLayersのレイヤーを使って接地判定する
//Ground check by Raycast
[SerializeField] float groundCheckRadius = 0.4f;
[SerializeField] float groundCheckOffsetY = 0.45f;
[SerializeField] float groundCheckDistance = 0.2f;
[SerializeField] LayerMask groundLayers = 0;
RaycastHit hit;
bool CheckGroundStatus()
{
return Physics.SphereCast(transform.position + groundCheckOffsetY * Vector3.up, groundCheckRadius, Vector3.down, out hit, groundCheckDistance, groundLayers, QueryTriggerInteraction.Ignore);
}
Physics.Raycast
左クリックした対象のコンポーネントを取得する
レイキャストが当たったオブジェクトがUnitクラスを持ち、かつ、`interface'で定義された情報をもつなら、情報を表示する
if (Input.GetMouseButtonDown(0))
{
var ray = GameCamera.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
//the collider could be children of the unit, so we make sure to check in the parent
var unit = hit.collider.GetComponentInParent<Unit>();
m_Selected = unit;
//check if the hit object have a IUIInfoContent to display in the UI
//if there is none, this will be null, so this will hid the panel if it was displayed
var uiInfo = hit.collider.GetComponentInParent<UIMainScene.IUIInfoContent>();
UIMainScene.Instance.SetNewInfoContent(uiInfo);
}
m_Selected.MovingBehaviour();
}
Input
Input.GetAxis("〇〇〇")
〇〇〇にはHorizontalやVerticalが入る。
Horizontalが何なのかなどをInput Managerで設定する。
(例)float型の変数に代入しておいて、Rotateの変更などで使う。
Input.GetKeyDown(KeyCode.〇〇〇)
〇〇〇にはSpaceなど、キーの名前が入る。
※戻り値はbool型
Input.GetMouseButtonDown(0)
左クリックをするとtrueが返ってくる。
Animator
※参照型
(例)Animatorで作っておいた変数の値をUpdateで変更する。
ジャンプした時に、空中にいる時のアニメーションが開始するようにする。
playerAnim.SetBool("Jump_b",true);
AudioSource
PlayOneShot()などでスクリプトからAudioClip
を再生できる。
コンポーネントをアタッチすると、
あらかじめPlay On Awakeが有効になっていることに注意する。
シーンを跨いで再生させ続けるなら、DontDestroyOnLoad(this)
などで対応する。
Camera
Camera.ScreenPointToRay(Input.mousePosition)
スクリーン上に入力した点から、
カメラを通してレイを飛ばす。
ParticleSystem
Play()
やStop()
でスクリプトから再生・停止できる。
VisualEffect
冒頭にusing UnityEngin.VFX;
と書いておく。
SendEvent("OnPlay")
やSendEvent("StopPlay")
でスクリプトから再生・停止できる。
UnityEngine.UI
UI関連のデータ型を使うときは、
スクリプトの冒頭にusing UnityEngine.UI
を書いておくといい。
InputField
ユーザーがキーボード入力可能なフィールドを扱う
(例)InputField.textで、ユーザーが入力した文字列を扱う
UnityEngine.SceneManagement
冒頭にusing UnityEngine.SceneManagement
を付ける。
シーンのロード、アンロードなどでシーンを管理する。
SceneManager.LoadScene()
ビルド設定のインデックスか名前を使ってシーンを読み込む。
SceneManager.GetActiveScene()
現在のアクティブなシーンを読み込む。
buildIndexを取得する例
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
UnityEditor
開発画面で使用する。
Exitボタンの挙動を開発専用にする例
#if UNITY_EDITOR
using UnityEditor;
#endif
public void Exit()
{
#if UNITY_EDITOR
EditorApplication.ExitPlaymode();
#else
Application.Quit(); //original code to quit Unity Player
#endif
}
概念
usingディレクティブ
スクリプトの冒頭でusing 名前空間;
と書くことで、「名前空間.クラス名」の「名前空間.」を省略できる。
※名前空間(namespace)とはクラスが属するグループ名のこと。
(例)using TMPro
と書いておくことで、
TextMeshProUGUI型の変数が使えるようになる。
参照型と値型
参照型 | 値型 | |
---|---|---|
メモリの情報 | 値がある場所 | 値そのもの |
データ型の例 | class、配列、コレクション(List型など)、string | struct(Vector3など)、int、float、bool |
実体 | new演算子でインスタンスを作成する* | インスタンスがなくても変数を代入できる (実体がある) |
内容 | 中身が変わる | 中身は固定 |
容量 | 軽い | 重い |
*stirngは特別で、インスタンスなしで変数を代入できる。
メソッドで引数を使うときの注意点
メソッドに変数を渡すとき、引数はコピーして渡される。
引数に変更を加えて、戻り値を得たい場合は以下に注意する。
参照型を引数として渡せば、呼び出し元の変数にも影響を与える。
一方で、値型を引数として渡しても、コピーが変更されるだけで、呼び出し元の変数に影響がない。
解決方法としては、メソッドを定義するときに、
引数の前にref
修飾子またはout
修飾子を付けることで、値型を参照渡しできる。
ref
は引数に値が入った変数を渡す必要があり、out
は引数の変数に値が入っていなくていい。
(例)ref修飾子を付けているので、関数の引数は参照渡しになり、2が表示される。
int x = 1;
void Update()
{
func(ref x);
Debug.Log(x);
}
void func(ref int x)
{
x = 2;
}
オブジェクトプール
オブジェクトをいくつか生成しておき、表示の切り替えで、あたかもオブジェクトを生成、削除しているように見せる。
シューティングゲームの球など、最適化の手段の1つ。
オブジェクトプーラーのサンプルコード
(例)オブジェクトプーラーを用意しておき、他のスクリプトから呼び出す。
GameObject pooledProjectile = ObjectPooler.SharedInstance.GetPooledObject();
if (pooledProjectile != null)
{
pooledProjectile.SetActive(true); // activate it
pooledProjectile.transform.position = transform.position; // position it at player
}
public class ObjectPooler : MonoBehaviour
{
public static ObjectPooler SharedInstance;
public List<GameObject> pooledObjects;
public GameObject objectToPool;
public int amountToPool;
void Awake()
{
SharedInstance = this;
}
// Start is called before the first frame update
void Start()
{
// Loop through list of pooled objects,deactivating them and adding them to the list
pooledObjects = new List<GameObject>();
for (int i = 0; i < amountToPool; i++)
{
GameObject obj = (GameObject)Instantiate(objectToPool);
obj.SetActive(false);
pooledObjects.Add(obj);
obj.transform.SetParent(this.transform); // set as children of Spawn Manager
}
}
public GameObject GetPooledObject()
{
// For as many objects as are in the pooledObjects list
for (int i = 0; i < pooledObjects.Count; i++)
{
// if the pooled objects is NOT active, return that object
if (!pooledObjects[i].activeInHierarchy)
{
return pooledObjects[i];
}
}
// otherwise, return null
return null;
}
}
カプセル化
クラス内のメンバーを隠して、利用してほしい機能だけを使えるようにする。
- プロパティを使う
プロパティの書き方
プロパティの名前の後ろに、{get; private set;]
を付ける。
これで、クラスの外から呼び出すことができ、クラス内でのみ値を設定できる。
これは自動実装プロパティと呼ばれる。
または、
public プロパティの型名 プロパティの名前(頭文字は大文字にするといい)
{
set{〇〇〇 = value;}
get{return 〇〇〇;}
}
-
private
とアクセサ(public
のメソッドでメンバーにアクセスする)の併用 -
public static
: 参照することができる。インスタンス化できない
(私の理解:実体返し(コピーを渡すこと)をするから、元の変数は変えられない) -
protected
:基本クラス、派生クラスからのみアクセス可能 -
[SerializeField]
: インスペクターに表示させる。
継承
基本(親)クラスの持つメンバー変数や関数を引き継ぐ形で、新たに派生(子)クラスを作成する。
例えば、MonoBehaviourを継承することで、Start()等の関数を使える。
ポリモーフィズム(多態性)
子クラスで、メソッドをオーバーライドすることで、親クラスとは異なるメソッドを使うことが出来る。
例えば、親クラスでpublic virtual void 関数()
としておき、子クラスでpublic override void 関数()
として中身を変える。
※下記のabstruct
と使い分ける。
抽象化
メソッドやクラスを使いやすい形で作成しておく。
例えば、AddForceメソッドを呼ぶだけで、(中身を知らなくても、)オブジェクトに力を加えることが出来る。
子クラスに継承する前提の、インスタンス化できないクラスを作成することも出来る。
抽象クラスと呼ばれ、abstract class
とする。
オーバーロード
メソッドを定義するとき、引数の個数や引数の型が異なっていれば、同じ名前のメソッドを複数定義することが出来る。
例えば、transform.Translateには4種類の引数の組み合わせが用意されている。
その他、つまづいたところ
-
配列は参照型で、new演算子でインスタンスを作成する。
int[] 〇〇 = new[]{△, △,...}
[]の中は要素数 -
クラスの中にあるクラス名と同じ名前のメソッドは、コンストラクタという。
クラスの初期化処理(値の代入など)を担い、インスタンス化するときに必然的に呼ばれる。
〇〇〇 ○○ = new 〇〇〇(); の 〇〇〇() がコンストラクタになっているから。 -
interface
はメソッドやプロパティのコレクションである。
(別にUIに関わるとも限らない。)
interface内のメソッドやプロパティは自動的にパブリックになる。
定義は宣言されない。
なぜなら、継承されてから定義されるから。
使いどころとしては、
①同じ機能は同じinterface
を使って実装しておくと、
後からこのグループをまとめて探すときに便利。
②interface
を継承していると、[SerializeReference]
を使用して、
シリアル化できる(?)。 -
$
文字はコードとして意味のない文字列を補間する。
例えば、return $"Producing at the speed of {m_ProductionSpeed}/s"; は{}内の変数だけを返す。 -
Vector3.Dot(Vector3, Vector3)
は内積 -
as
演算子はパターンマッチングに使う。
例えば、A = B as Typeでは、BがType型ならAをBにする。 -
?
は3項演算子という。
(式1? 式2:式3)の式1がtrueなら式2、falseなら式3を使う。Ifを使わない条件分岐。 -
switch
を使うと、If~elseを使わずに条件分岐できる。
swich文の例
switch (PointValue)
{
case 1 :
block.SetColor("_BaseColor", Color.green);
break;
case 2:
block.SetColor("_BaseColor", Color.yellow);
break;
case 5:
block.SetColor("_BaseColor", Color.blue);
break;
default:
block.SetColor("_BaseColor", Color.red);
break;
}
- あるスクリプトで別のスクリプトの関数や値を使いたい
1. 他のスクリプトをGetComponentで使う
スクリプトを取得する例
private PlayerController playerControllerScript;
void Start()
{
playerControllerScript = GameObject.Find("Player").GetComponent<PlayerController>();
}
2. シーンのコールバックを介してイベントの発生を伝える。UnityEvent
でシーンにイベントを保存する。
int型の引数を持つUnityEventを介してやりとりする例
public class Brick : MonoBehaviour
{
public UnityEvent<int> onDestroyed;
public int PointValue;
private void OnCollisionEnter(Collision other)
{
onDestroyed.Invoke(PointValue);
//slight delay to be sure the ball have time to bounce
Destroy(gameObject, 0.2f);
}
}
void Start(){
brick.onDestroyed.AddListener(AddPoint);
}
void AddPoint(int point)
{
m_Points += point;
ScoreText.text = $"Score : {m_Points}";
}
- シーンをまたいで変数を使いたい
1.静的(static
)クラスメンバーを使う
static classのメンバー変数はstaticである必要がある。
インスタンス変数がないため、静的クラスのメンバーにアクセスするには、クラス名自体を使用する。
例えば、Time.deltaTimeやVector3.forwardは静的クラスメンバーである。
(私の理解: クラスだけれど値型のように扱える。
インスタンス化する手間がないので、他のクラスから使う機会があるときに便利)
staticを使うサンプルコード
public static class GlobalVariables
{
public static string playerName = "nanashi";
public static string bestPlayerName;
public static int bestScore = 0;
}
private void Update()
{
GlobalVariables.playerName = inputField.text;
}
2.DontDestroyOnLoad
を使う
シーンをロード、アンロードするときにも、ゲームオブジェクトをメモリに保存しておくことが出来る。
シーンをまたぐマネージャーのサンプルコード
public static MainManager Instance;
private void Awake()
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
3.ゲームを中断・再開しても保持したければ、PlayerPrefsを使う。
GetString
やGetInt
で取得し、SetString
やSetInt
で値を入れた後にSave
で保存できる。
(私の理解: 例えば、PREFSファイルという拡張子はプリファレンス値に関する情報を格納する。同様に、PlayerPrefsはシーン開始時に優先してアクセスするべき情報を格納しておくことができる)
4.JSONを使う方法もある。
対象Version
Unity 2020 LTSおよびUnity 2022 LTS
参考文献
- 公式マニュアル
- Unity Learn
- 確かな力が身に付くC#「超」入門第2版 (北村愛美, 2020)
結び
少しでも役に立つ記事になっていれば幸いです。