Unity

【初心者向け】Unityで「よく使う関数」や「よく使うコード」

ここではUnityで「よく使う関数」や「よく使うコード」を紹介します。
自分が初めてUnityを触った時、

[SerializeField] Player player;

void Start()
{
    Player player = GetComponent<Player>();
}

てんぷら
てんぷら
この2つってどっち使えばいいんだろう・・・

と戸惑ったので、今までの経験を元にベストな使い方もお話しします☆

初期化処理を行う

Start

public class Scene : MonoBehaviour
{
    void Start()
    {
        Debug.Log("シーン初期化");
    }
}

MonoBehaviourを継承すると、Startが呼ばれるようになります。
Startはオブジェクトが表示された時、1回呼ばれます。
初期化処理はStartに書くと、コンポーネントが分離しやすくなります。

Awake

public class Scene : MonoBehaviour
{
    void Awake()
    {
        Debug.Log("シーン初期化");
    }
}

MonoBehaviourを継承すると、Awakeが呼ばれるようになります。
Awakeはオブジェクトが表示された時、1回呼ばれます。
AwakeStartより先に呼ばれます。

それと細かいですが、Awakeはゲームオブジェクトが表示されててコンポーネントが非表示の状態でも呼ばれます(コンポーネントの表示状態を変える)。コンポーネント非表示時に行いたくない処理があるとまずいかもです(自分は問題になったことはないですが…)。

StartAwakeの使い分けですが、自分は

  • 基本Startに書く
  • どうしても優先したいものがある場合だけAwakeに書く

という感じにしてます。

あと重要な点としてStartAwakeなどMonoBehaviourから呼ばれるメソッドは、
コンポーネント間で呼び出し順が決まっていません。例えば

public class Scene : MonoBehaviour
{
    void Start()
    {
        Debug.Log("シーン初期化");
    }
}

public class Player : MonoBehaviour
{
    void Start()
    {
        Debug.Log("プレイヤー初期化");
    }
}

このScene.StartPlayer.Startはどっちが先に呼ばれるか分かりません。
なのでSceneをどうしても先に初期化したいという場合は

public class Scene : MonoBehaviour
{
    void Awake()
    {
        Debug.Log("シーン初期化");
    }
}

public class Player : MonoBehaviour
{
    void Start()
    {
        Debug.Log("プレイヤー初期化");
    }
}

Awakeに変えます。ただ他にもAwakeを使ってるコンポーネントがあった場合制御できないので、順番をちゃんとしたい場合はInitで作る方がいいです。

Init(自作メソッド)

public class Scene : MonoBehaviour
{
    [SerializeField] Player player;

    void Start()
    {
        Debug.Log("シーン初期化");
        player.Init();

    }
}

public class Player : MonoBehaviour
{
    public void Init()
    {
        Debug.Log("プレイヤー初期化");
    }
}

StartAwakeはコンポーネント間で呼び出し順が決まってないので、明確に初期化順を制御したい場合は自前メソッドで作ります。

ちなみにMonoBehaviourはコンストラクタが使えません(Unity側の都合)。
なので初期化メソッドを用意してます(MonoBehaviour以外ならコンストラクタでもOK)。

OnEnable

public class Scene : MonoBehaviour
{
    void OnEnable()
    {
        Debug.Log("シーンが有効になった");
    }
}

MonoBehaviourを継承すると、OnEnableが呼ばれるようになります。
OnEnableはオブジェクトが有効になる度、1回呼ばれます。
Startと違い

表示(OnEnable, Start)非表示表示(OnEnable)

でも呼ばれます。ただ自分はあまり使ってません(初期化のタイミングは単純ではないので)。

Reset

public class Scene : MonoBehaviour
{
    void Reset()
    {
        Debug.Log("シーンが追加やリセットされた");
    }
}

MonoBehaviourを継承すると、Resetが呼ばれるようになります(エディタでのみ呼ばれる)。
Resetコンポーネントを追加した時やエディタでResetボタンを押した時に呼ばれます。

名前で勘違いされやすいですが、コンポーネントを追加した時にも呼ばれるので、

public class Scene : MonoBehaviour
{
    [SerializeField] Player player;

    void Start()
    {
        player = GetComponentInChildren<Player>();
    }

    // ↓

    void Reset()
    {
        player = GetComponentInChildren<Player>();
    }
}

こんな感じでStartで初期化してるものをResetに持っていけば、ゲーム実行時の初期化コストを減らせます(重い初期化処理をエディタで事前に済ませてしまうとか)。

GetComponentInChildrenコンポーネントを取得する

RuntimeInitializeOnLoadMethod

public class Scene : MonoBehaviour
{
    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    static void Init()
    {
        Debug.Log("シーンロード前に初期化");
    }
}

通常は AwakeOnEnableResetStart の順に呼び出されますが、[RuntimeInitializeOnLoadMethod]を使えばそれより前に呼び出せます。

    RuntimeInitializeLoadType

  • AfterSceneLoad:シーンロード後
  • BeforeSceneLoad:シーンロード前
  • AfterAssembliesLoaded:全アセンブリロード後
  • BeforeSplashScreen:スプラッシュスクリーン表示前
  • SubsystemRegistration:サブシステムの登録に使用される(?)
public class AppManager : MonoBehaviour
{
    public static AppManager Instance { get; private set; }

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    static void Init()
    {
        Instance = Instantiate(Resources.Load<AppManager>("Prefabs/AppManager"));
        DontDestroyOnLoad(Instance.gameObject);
    }
}

ゲーム起動時、最初からあって欲しいものを作るとか。
ただどこから生まれたか分かりづらいので、乱用しない方がいいです。

まとめると

初期化処理を行う
  • 基本Startに書く
  • どうしても優先したいものがある場合だけAwakeに書く
  • 順番を制御したい場合は自分で作る(Init)
  • その他は必要に応じて使う

更新処理を行う

Update

public class Scene : MonoBehaviour
{
    void Update()
    {
        Debug.Log("シーン更新");
    }
}

MonoBehaviourを継承すると、Updateが呼ばれるようになります。
Updateはオブジェクトが表示されてる時、毎フレーム呼ばれます。
更新処理はUpdateに書くと、コンポーネントが分離しやすくなります。

【UnityBlog】Update()を10000回呼ぶ

上でUnity公式が言っているように、Updateは呼び出しにそれなりのコストがあります。
そこまで神経質になる必要はないですが、例えば大量のゲームオブジェクトから呼ばれてる場合は注意が必要です。

ちなみにUpdateメソッドを持っていたらUpdateが呼ばれます。なので

public abstract class SceneBase : MonoBehaviour
{
    public virtual void Update() { }
}

こういう空のUpdateであっても呼ばれます。不要な場合は削除した方がいいでしょう。

LateUpdate

public class Scene : MonoBehaviour
{
    void LateUpdate()
    {
        Debug.Log("シーン更新");
    }
}

MonoBehaviourを継承すると、LateUpdateが呼ばれるようになります。
LateUpdateはオブジェクトが表示されてる時、毎フレーム呼ばれます。
LateUpdateUpdateの後に呼ばれます。後にしたい処理はLateUpdateに書くといいです。

例えばプレイヤーと敵が移動した後、カメラを更新したい場合

public class Player : MonoBehaviour
{
    void Update()
    {
        transform.Translate(0, 0, 1);
    }
}

public class Enemy : MonoBehaviour
{
    void Update()
    {
        transform.Translate(0, 0, 1);
    }
}

public class Scene : MonoBehaviour
{
    [SerializeField] Player player;
    [SerializeField] Enemy  enemy;

    void Update()
    {
        UpdateCamera(player, enemy);
    }
}

Player.UpdateEnemy.UpdteScene.Update

を想定していますが、Updateは呼び出し順が決まっていないため、カメラの更新が正しく行えない可能性があります。こういう場合

Player.UpdateEnemy.UpdteScene.LateUpdate

としてやれば、正しくカメラを更新できるようになります。

FixedUpdate

public class Scene : MonoBehaviour
{
    void FixedUpdate()
    {
        Debug.Log("シーン更新");
    }
}

MonoBehaviourを継承すると、FixedUpdateが呼ばれるようになります。
FixedUpdateはオブジェクトが表示されてる時、一定間隔で呼ばれます。
FixedUpdateに関しては以下に詳しくまとまっています。

【上級者向け】InputとFixedUpdateと物理演算の関係を整理しよう

  • 入力はUpdateで行う(FixedUpdateで行いたい場合はGetButtonを使う)
  • Rigidbody.AddForce(ForceMode.Force)FixedUpdateで使う
  • Rigidbody.AddForce(ForceMode.Impulse)Updateで使う

要はΔtが関わるものは物理演算の更新に合わせないといけないってことですね。
なんとなく理屈は分かりますが正直難しい…(Updateしか使ってこなかった)

Coroutine

public class Scene : MonoBehaviour
{
    IEnumerator coroutine;
    float time;

    void Start()
    {
        // コルーチン実行
        coroutine = Coroutine(1);
        StartCoroutine(coroutine);
        //StartCoroutine(Coroutine(1)); // こうも書ける(途中で停止を考えないならこれでいい)
    }

    void Update()
    {
        if(Input.GetMouseButtonUp(0))
        {
            // コルーチンを停止
            StopCoroutine(coroutine);
        }
        else if(Input.GetMouseButtonUp(1))
        {
            // 全コルーチンを停止
            StopAllCoroutines();
        }
    }

    IEnumerator Coroutine(int id)
    {
        // 1フレーム待つ
        yield return null;
        Debug.Log("1フレーム後の処理");

        // 1秒待つ
        time = 0;
        while(time < 1)
        {
            yield return null;
            time += Time.deltaTime;
        }
        Debug.Log("1秒後の処理");

        // 1秒待つ(WaitForSecondsでも書ける)
        yield return new WaitForSeconds(1);
        Debug.Log("1秒後の処理");

        // 条件を満たす間待つ(1秒待つ)
        time = 0;
        yield return new WaitWhile(() =>
        {
            time += Time.deltaTime;
            return (time < 1);
        });
        Debug.Log("1秒後の処理");

        // 条件を満たすまで待つ(1秒待つ)
        time = 0;
        yield return new WaitUntil(() =>
        {
            time += Time.deltaTime;
            return (time >= 1);
        });
        Debug.Log("1秒後の処理");

        // コルーチンを強制終了(以降の処理は行われない)
        yield break;

        // この処理は行われない
        Debug.Log("ホワイトファング!!");
    }
}

コルーチンを使うことで、更新処理を簡潔に書けるようになります。
コルーチンはIEnumeratorを戻り値にしてyield文で制御します。
MonoBehaviourを継承すると、コルーチンメソッドが呼べるようになります。

  • StartCoroutine:コルーチン実行
  • StopCoroutine:コルーチン終了
  • StopAllCoroutines:全コルーチン終了

例えば、1秒待ってからアニメーションを再生させたい場合

void Update()
{
    if(sq == 1)
    {
        // 1秒待つ
        time += Time.deltaTime;
        if(time >= 1)
        {
            // アニメーション再生
            animation.Play();
            sq = 2;
        }
    }
    else if(sq == 2)
    {
        // アニメーション再生中
        if(!animation.isPlaying)
        {
            Debug.Log("アニメーション終了!");
            sq = 0;
        }
    }
}

Updateで書くとこんな感じ。これをコルーチンで書くと

IEnumerator PlayAnimation()
{
    // 1秒待つ
    yield return new WaitForSeconds(1);

    // アニメーション再生
    animation.Play();

    // アニメーション再生中
    yield return new WaitWhile(() => animation.isPlaying);

    Debug.Log("アニメーション終了!");
}

こうなります。ぱっと見分かりやすいですよね。

コルーチンの本質は非同期処理です。ゲームのロジックが複雑になると制御するのが大変になります(いろんな処理が同時に動いているとコードを追うのが難しい)。
初心者はまずは、Updateを使いこなせるようになってから、コルーチンを試すことをオススメします(コルーチンで書いたコードはUpdateでも書けます)。

まとめると

更新処理を行う
  • 基本Updateに書く
  • 後にしたい処理はLateUpdateに書く(カメラとか)
  • 特定の物理演算はFixedUpdateに書いた方が良い
    Rigidbody.AddForce(ForceMode.Force)
  • 非同期処理は難しい

オブジェクトを取得する

ゲームオブジェクトを取得する

public class Scene : MonoBehaviour
{
    // ① SerializeFieldでエディタから参照
    [SerializeField] GameObject player;

    void Start()
    {
        // ② コンポーネント自身のプロパティから取得
        GameObject scene = gameObject;

        // ③ GameObject.FindでHierarchyから検索(ゲームオブジェクトが非表示だと取得できない)
        GameObject player = GameObject.Find("Player");                 //全検索
        GameObject player = GameObject.Find("Scene/Player");           //パス指定
        GameObject player = GameObject.FindGameObjectWithTag("Player");//タグ指定
    }
}

  • 自身は②を使って
  • 他は①でエディタから参照する
  • ③は重たいので使わない

という感じです。

トランスフォームを取得する

public class Scene : MonoBehaviour
{
    // ① SerializeFieldでエディタから参照
    [SerializeField] Transform player;

    void Start()
    {
        // ② コンポーネント自身のプロパティから取得
        Transform scene = transform;

        // ③ 子のトランスフォームを検索(ゲームオブジェクトが非表示でも取得できる)
        Transform player = transform.Find("Player"); // 入れ子も可能(Player/●)
        for(var i = 0; i < transform.childCount; i++)
        {
            // 子を全検索(インデックスで取得)
            Transform child = transform.GetChild(i);
        }
        foreach(Transform child in transform)
        {
            // 子を全検索(実は回せる)
        }

        // ④ 親のトランフォームを取得
        Transform scene = player.parent;

        // ⑤ 一番上のトランフォームを取得
        Transform scene = player.root;
    }
}

  • 自身は②を使って
  • 他は①でエディタから参照する
  • 親子関係で処理したい場合は③④⑤を使う

という感じです。

コンポーネントを取得する

public class Scene : MonoBehaviour
{
    // ① SerializeFieldでエディタから参照
    [SerializeField] Player player;

    void Start()
    {
        // ② GetComponentで検索(ゲームオブジェクトが非表示だと取得できない)
        Scene    scene   = GetComponent<Scene>();             // 自身のコンポーネント
        Player   player  = GetComponentInChildren<Player>();  // 子も検索(最初に見つかったもの)
        Player[] players = GetComponentsInChildren<Player>(); // 子も検索(全部)
        Scene    scene   = GetComponentInParent<Scene>();     // 親も検索(最初に見つかったもの)
        Scene[]  scenes  = GetComponentsInParent<Scene>();    // 親も検索(全部)
        if(TryGetComponent<Scene>(out var scene)) { }         // nullチェックも行う
        
        // ③ FindObjectOfTypeでHierarchyから検索(ゲームオブジェクトが非表示だと取得できない)
        Player player = FindObjectOfType<Player>();
    }
}

  • 基本は①でエディタから参照する
  • 動的に取得したい場合は②
  • ③は重たいので使わない

という感じです。

SerializeField のメリット
SerializeField のデメリット
・エディタで値を変更できる
・AssetBundleで値を変更できる
・変数名を変えると値の参照がなくなってしまう
・使いすぎるとInspectorが長くなって見づらい
GetComponent のメリット
GetComponent のデメリット
・動的に取得できる(汎用化や大量取得に便利)
・コードを追いやすい
・検索コスト
・構造が変わった場合にコード修正が必要

どっちのやり方でも取得はできますが、自分はなるべく事前に値を入れておきたいのでSerializeFieldを優先してます。あと、エディタならプログラマー以外の人でも触れるので

  • プログラマー:設定を作る
  • 企画・デザイナー:設定を入れる

のような分業も可能です。

まとめると

オブジェクトを取得する
  • 基本SerializeFieldでエディタから参照する
  • gameObjectとtransformは自身のプロパティでも取得できる
  • 動的に取得したい場合はGetComponent

親を変える

public class Scene : MonoBehaviour
{
    [SerializeField] Player player;

    void Start()
    {
        // ルート階層へ(一番上へ)
        player.transform.SetParent(null);

        // Sceneの下へ
        player.transform.SetParent(transform);

        // worldPositionStays = false でローカルポジションが変わらない
        player.transform.SetParent(null, worldPositionStays: false);
    }
}

transform.SetParentを使えば、ゲームオブジェクトの親を変えられます。
基本はあらかじめHierarchyで階層を作っておきますが、スクリプトで親を変えたい場合に便利です(プレイヤーを別シーンに移動させたい、とか)。

通常は親が変わっても見た目が変わらないようにワールドポジションが維持されますが、worldPositionStays = falseでローカルポジションが変わらなくなります(基本指定しなくてもいいとは思います)。

オブジェクトの表示状態を変える

ゲームオブジェクトの表示状態を変える

public class Scene : MonoBehaviour
{
    [SerializeField] Player player;

    void Start()
    {
        // Sceneを非表示(Playerも非表示になる)
        gameObject.SetActive(false);

        // Playerだけ非表示
        player.gameObject.SetActive(false);
    }
}

gameObject.SetActiveを使えば、ゲームオブジェクトの表示状態を変えられます。
Hierarchy見てわかる通り、子にも影響します(Scene非表示でPlayerも非表示になる)。
また非表示になると、全てのコンポーネントが無効になり、StartUpdateなどが呼ばれなくなります(Scene非表示でScene.UpdatePlayer.Updateなどが呼ばれなくなる)。

表示状態の確認はgameObject.activeSelfgameObject.activeInHierarchyを使います。

public class Scene : MonoBehaviour
{
    [SerializeField] Player player;

    void Start()
    {
        // Sceneを非表示
        gameObject.SetActive(false);

        // 自身の表示状態を確認
        Debug.Log($"[Player]activeSelf = {player.gameObject.activeSelf}");

        // Hierarchyの表示状態を確認
        Debug.Log($"[Player]activeInHierarchy = {player.gameObject.activeInHierarchy}");
    }
}

Sceneを非表示にした場合、Player

  • activeSelf = true
  • activeInHierarchy = false

です。親(Scene)を非表示にしても子(Player)の表示設定は維持されています。

public class Scene : MonoBehaviour
{
    [SerializeField] Player player;

    void Start()
    {
        // Playerを非表示
        player.gameObject.SetActive(false);

        // 自身の表示状態を確認
        Debug.Log($"[Player]activeSelf = {player.gameObject.activeSelf}");

        // Hierarchyの表示状態を確認
        Debug.Log($"[Player]activeInHierarchy = {player.gameObject.activeInHierarchy}");
    }
}

Playerを非表示にすると

  • activeSelf = false
  • activeInHierarchy = false

になります。このことから

  • Hierarchyの表示状態を見たい場合はactiveInHierarchy
  • 厳密に自身の表示状態を見たい場合はactiveSelf

ということになります。ただ自分はほぼactiveSelfを使ってます(親子の表示状態で処理するって分かりづらいので)。

コンポーネントの表示状態を変える

public class Scene : MonoBehaviour
{
    [SerializeField] Player player;

    void Start()
    {
        // Sceneを非表示(Sceneだけ非表示になり、他には影響しない)
        enabled = false;

        // Playerを非表示
        player.enabled = false;
    }
}

コンポーネントのenabledを使えば、コンポーネントの表示状態を変えられます。
また非表示になると、コンポーネントが無効になり、StartUpdateなどが呼ばれなくなります(Scene非表示でScene.Updateなどが呼ばれなくなる)。

SetActiveenabledの使い分けですが

  • とにかくゲームオブジェクトを止めたい → SetActive
  • ゲームオブジェクトの一部機能を止めたい → enabled

という感じです。ゲームオブジェクトを止める方が負荷はかかりますが、自分はSetActiveを使うことが多いです。単純にゲームオブジェクトを止める機会の方が多いからです。

まとめると

オブジェクトの表示状態を変える
  • とにかくゲームオブジェクトを止めたい → SetActive
  • ゲームオブジェクトの一部機能を止めたい → enabled

ゲームオブジェクトの名前を変える

public class Scene : MonoBehaviour
{
    void Start()
    {
        // SceneをGameにリネーム
        name = "Game";

        // ゲームオブジェクトを作ると Player(Clone) のような名前になるのでリネーム
        var player  = Instantiate(Resources.Load<Player>("Prefabs/Player"));
        player.name = "Player";
    }
}

オブジェクトのnameを使えば、ゲームオブジェクトの名前を変えられます。
ゲームオブジェクトをスクリプトから作ると(Clone)のような文字列が付いてくるので、リネームすると綺麗になります。
ただし文字列はメモリを食いやすいので少しだけ注意です(大量に長い名前を付けるとか)。

ゲームオブジェクトを作る

public class Scene : MonoBehaviour
{
    // ① SerializeFieldでHierarchyのゲームオブジェクトを参照
    [SerializeField] GameObject playerOrg;

    // ② SerializeFieldでProjectのゲームオブジェクトを参照
    [SerializeField] GameObject enemyOrg;

    void Start()
    {
        // ①② SerializeFieldで参照して作成(Instantiateで作成)
        GameObject player = Instantiate(playerOrg, transform); // 親の下に作る
        GameObject enemy  = Instantiate(enemyOrg);             // 親なしでルート階層
        player.name       = "Player2";
        enemy.name        = "Enemy";

        // ③ Resources.LoadでResourcesフォルダから取得して作成
        GameObject npcOrg = Resources.Load<GameObject>("Prefabs/NPC");
        GameObject npc    = Instantiate(npcOrg);
        npc.name          = "NPC";

        // ④ GameObjectのnewや、GameObject.CreatePrimitiveで作成
        GameObject go   = new GameObject("GameObject");
        GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
    }
}

  • ⓪ エディタでシーンのゲームオブジェクトはあらかじめ作っておく
  • Hierarchyのゲームオブジェクトを複製したい場合は①
  • プレファブを作成したい場合は②③
  • ④は特殊な事情がない限り使わない(重たい)

という感じです。

ゲームオブジェクトは可能な限りエディタであらかじめ作っておいた方がいいです。
ただ動的に作らないといけないものは②③になってきます(①はあんまり使わない)。

②③の使い分けですが、Unity公式ではResourcesフォルダのベストプラクティスは使用しない事である」と書かれています。以下の記事がわかりやすいです。

Resourcesフォルダのベストプラクティス【Unity】【最適化】

Resourcesに入れると自動でアプリに含まれてしまうのでアプリ容量やメモリ管理に注意、ということだと思います。ただ自分はガンガンResourcesを使ってます。

  • Resources = アプリに含めるリソース」なので、それを考慮してメモリ管理する
  • AssetBundleは使うのに一手間いる(Resourcesならフォルダに入れるだけ)
  • 大量のオブジェクトをSerializeFieldで参照させるのは大変

ちゃんとメモリ管理すれば、問題ないかと思います。
大規模アプリなどアプリ容量に気を遣う場面では、Resourcesは最低限にした方がいいかと思います。

ゲームオブジェクトを削除する

public class Scene : MonoBehaviour
{
    [SerializeField] GameObject player;

    void Start()
    {
        // 指定のゲームオブジェクトを削除(子も一緒に削除される)
        Destroy(player);

        // 自身を削除
        Destroy(gameObject);

        // 子を削除
        Destroy(transform.Find("Player"));
        for(var i = 0; i < transform.childCount; i++)
        {
            // 子を全削除
            Destroy(transform.GetChild(i));
        }
        foreach(Transform child in transform)
        {
            // 子を全削除
            Destroy(child);
        }

        // 親から削除
        Destroy(player.transform.parent); // 1つ上
        Destroy(player.transform.root);   // 一番上

        // コンポーネントを削除
        Destroy(player.GetComponent<Player>());

        // DestroyImmediateだと削除が遅延しないらしい(ただしエディタ拡張専用)
        DestroyImmediate(player);
    }
}

オブジェクトのDestroyを使えば、ゲームオブジェクトを削除できます。
コンポーネントを渡せば、コンポーネントを削除できます。

ゲームオブジェクトの削除は重い処理なので、非表示の方が速く処理できるかと思います。
あとシーン遷移でまとめて削除した方が無難です。

ゲームオブジェクトが削除されないようにする

public class Scene : MonoBehaviour
{
    void Start()
    {
        // シーン遷移で削除されないようにする(子も削除されなくなる)
        DontDestroyOnLoad(gameObject);
    }
}

オブジェクトのDontDestroyOnLoadを使えば、ゲームオブジェクトをシーン遷移で削除されないようにできます。

public class AppManager : MonoBehaviour
{
    public static AppManager Instance { get; private set; }

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    static void Init()
    {
        Instance = Instantiate(Resources.Load<AppManager>("Prefabs/AppManager"));
        DontDestroyOnLoad(Instance.gameObject);
    }
}

マネージャークラスなどアプリ起動時からずっと使いまわすものに使用するといいです。
ただしメモリが解放されなくなるので常駐オブジェクトは限定した方がいいかと思います。

【宴】DontDestroyOnLoadは非推奨?

気になる記事を見かけました。どうやらUnity公式はDontDestroyOnLoadではなくシーンの方で管理して欲しいようです(わからなくはない)。ですが、個人的には気にする必要はないかと思います(DontDestroyOnLoadの方が楽)。

当たり判定

衝突した位置を知りたい

public class Player : MonoBehaviour
{
    // 衝突した時に呼ばれる
    void OnCollisionEnter(Collision collision)
    {
        // collisionに衝突情報が入ってくる
        foreach(ContactPoint contact in collision.contacts)
        {
            GameObject target   = collision.gameObject;
            Vector3    position = contact.point;
            Debug.Log($"target = {target.name}, position = {position}");
        }
    }

    // 衝突している間呼ばれる
    void OnCollisionStay(Collision collision) { }

    // 衝突から離れた時に呼ばれる
    void OnCollisionExit(Collision collision) { }

    // 2Dも考え方は一緒(多少パラメータの違いはあり)
    void OnCollisionEnter2D(Collision2D collision) { }
    void OnCollisionStay2D(Collision2D  collision) { }
    void OnCollisionExit2D(Collision2D  collision) { }
}

  • ColliderRigidbodyが付いている
  • ColliderIsTriggerにチェックが入ってない

これでMonoBehaviourOnCollisionメソッドが呼ばれるようになります。
2DはRigidbody2DCollider2Dのように後ろに2D記載があるものを使います。
(3Dと2Dで当たり判定は別になることに注意)

衝突した位置を知りたい場合はこちらを使います。

  • 攻撃が当たった位置にエフェクトを出す
  • 弱点に当たったら大ダメージ(ブレワイ)

衝突したかだけ分かればいい

public class Player : MonoBehaviour
{
    // 衝突した時に呼ばれる
    void OnTriggerEnter(Collider collider)
    {
        // colliderは衝突対象
        GameObject target = collider.gameObject;
        Debug.Log($"target = {target.name}");
    }

    // 衝突している間呼ばれる
    void OnTriggerStay(Collider collider) { }

    // 衝突から離れた時に呼ばれる
    void OnTriggerExit(Collider collider) { }

    // 2Dも考え方は一緒(多少パラメータの違いはあり)
    void OnTriggerEnter2D(Collider2D collider) { }
    void OnTriggerStay2D(Collider2D  collider) { }
    void OnTriggerExit2D(Collider2D  collider) { }
}

  • ColliderRigidbodyが付いている
  • ColliderIsTriggerにチェックが入っている

これでMonoBehaviourOnTriggerメソッドが呼ばれるようになります。
2DはRigidbody2DCollider2Dのように後ろに2D記載があるものを使います。
(3Dと2Dで当たり判定は別になることに注意)

衝突したかだけ分かればいい場合はこちらを使います。

  • 攻撃がどこでもいいので当たったらダメージ
  • ビタロックのカーソルが物体に合ったら停止!(ブレワイ)

最後に

【渋谷ほととぎす通信】Unity基本編

てんぷら
てんぷら
初心者はこの人の記事もオススメです!

以前から有用な記事書いてるなぁ、とは思ってたのですが、めちゃくちゃ分かりやすくまとめられてますね。しかも的を得てる。

パワーくん
パワーくん
世の中すごい人がいるな
【エビでもわかる】オセロプログラミング
〜オセロを作りながらゲームのプログラムを学ぼう〜
「Unityで初めてゲームを作ってみたい!」

そんな人のためにこの本を作りました。
オセロを一から作りながら実践形式でプログラムを学べる本です。
すでに完成したプロジェクトを説明するのではなく、実際に作りながら説明していきます。
一緒に手を動かしながら、プログラムを覚えていきましょう🌟