Skip to content

C#コーディングノウハウ

現時点で私が得たコードをきれいにしたり軽量化したりするためのポイントを書いてみます。

〇シングルトン

ゲーム制作だとオーディオ関連でよく使う。

  • 例:インスタンス化を使ったオーディオシステムの実装

    csharp
    using Cysharp.Threading.Tasks;
    using System.Collections;
    using System.Collections.Generic;
    using System.Xml.Serialization;
    using Unity.VisualScripting;
    using UnityEngine;
    
    public class AudioManager : MonoBehaviour
    {
        private static AudioManager instance;
    
        //インスタンスの設定
        public static AudioManager Instance;
    
        private BGMManager bgmManager;
        private SEManager seManager;
    
        //ミキサーの名前
        const string m_VolumeMaster = "VolumeMaster";
        const string m_VolumeBGM = "VolumeBGM";
        const string m_VolumeSE = "VolumeSE";
    
        [SerializeField] SoundSystemSO m_soundSystemSO;
    
        public SoundSystemSO soundSystemSO => m_soundSystemSO;
    
        private void Awake()
        {
            if (instance == null)
            {
                instance = this.gameObject.GetComponent<AudioManager>();
                DontDestroyOnLoad(gameObject);
                Instance = instance;
            }
            else 
            {
                Debug.Log("nullじゃないよ");
                Destroy(gameObject);
            }
        }
    
        private void Start()
        {
            bgmManager = BGMManager.Instance;
            seManager = SEManager.Instance;
    
            ApplyVolumeSettings();
    
            AudioEvent.ChangeBGM += bgmManager.PlayBGM;
            AudioEvent.PlaySE += seManager.PlaySE;
        }
    
        private async void GetSoundManager()
        {
            while (bgmManager == null)
            {
                await UniTask.DelayFrame(1);
            }
        }
    
        //音量設定
        public void SetAllAudio(float value)
        {
            soundSystemSO.MasterVolume = value;
            float volume = ConverttoDecibel(value);
            m_soundSystemSO.AudioMixer.SetFloat(m_VolumeMaster, volume);
        }
    
        public void SetBGMAudio(float value)
        {
            soundSystemSO.BGMVolume = value;
            float volume = ConverttoDecibel(value);
            m_soundSystemSO.AudioMixer.SetFloat(m_VolumeBGM, volume);
        }
        
        public void SetSEAudio(float value)
        {
            soundSystemSO.SEVolume = value;
            float volume = ConverttoDecibel(value);
            m_soundSystemSO.AudioMixer.SetFloat (m_VolumeSE, volume);
        }
    
        //初期音量のセット
        public void ApplyVolumeSettings()
        {
            SetAllAudio(soundSystemSO.MasterVolume);
            SetBGMAudio(soundSystemSO.BGMVolume);
            SetSEAudio(soundSystemSO.SEVolume);
        }
    
        //View=>mixer
        private float ConverttoDecibel(float value)
        {
            //Sliderに入力された値(0~10)を0~1に正規化する
            float dB;
            if(value > 0)
            {
                dB = Mathf.Log10(value) * 20;
                
            }
            else 
            {
                dB = -80f;
            }
            return dB;
        }
    
        //mixer=>View
        public static float ConverttoSlider(float decibelVolume)
        {
            return Mathf.Pow(10, decibelVolume / 20.0f);
        }
    
        public void ChangeBGM(BGMSoundData.BGM bgmType)
        {
            Debug.Log("audio changeBGM");
            AudioEvent.ChangeBGM?.Invoke(bgmType);
        }
    }
csharp
public static HogeHoge Instance{get; private set;}

    private void Awake()
    {
        if (instance == null)
        {
            instance = this.gameObject.GetComponent<HogeHoge>();
            //シーン共通して使うなら↓
            DontDestroyOnLoad(gameObject);
            Instance = instance;
        }
        else 
        {
            Debug.Log("nullじゃないよ");
            Destroy(gameObject);
        }
    }

Instanceを先頭で宣言。ほかから呼び出すときは、 HogeHoge.Instanceで呼び出しができる。

シングルトンの良いところは以下。

利点説明
どこからでもアクセスできるInstance を介して簡単に呼び出せる(例:ButtonSelectManager.Instance.SelectManage(...))。
オブジェクトの一元管理複数のscriptが同じマネージャを共有して動作する。状態を集中管理できる。
データの整合性を保つ同じデータ(ボタンの状態や選択情報)を複数インスタンスで競合させない。
グローバル変数より安全private set により外部から勝手に上書きされない。
設計がシンプルマネージャ系(UI管理・入力管理・サウンド管理)で頻繁に利用される。

また、軽量化の観点からしても、FindObjectOfType<HogeHoge>();は探索処理として重くなりがちなので、マネージャ系統として一つにまとめてInstanceで取れれば高速に動く。さらに、状態の集中管理も可能であるため、更新処理も最小限にできる。

〇Factoryパターン・ObjectPool

ObjectPoolは、使い終わったオブジェクトを破棄せずに再利用する仕組み。

InstantiateとDestroyを繰り返すと重くなってしまうので、エネミーなど、繰り返し生成を繰り返すようなものはObjectPoolを使うとよい。

  • 例:エネミー生成

    jsx
    using UnityEngine;
    
    public class EnemyFactory
    {
        private readonly EnemyPool _pool;
        private readonly float _defaultSpeed = 2f;
    
        public EnemyFactory(Enemy prefab, int initialCount, Transform parent = null)
        {
            _pool = new EnemyPool(prefab, initialCount, parent);
        }
    
        public Enemy Create(Vector3 position)
        {
            var enemy = _pool.Get();
            enemy.transform.position = position;
            enemy.Initialize(_defaultSpeed, OnEnemyDeath);
            return enemy;
        }
    
        private void OnEnemyDeath(Enemy enemy)
        {
            _pool.Release(enemy);
        }
    }
    jsx
    using System.Collections.Generic;
    using UnityEngine;
    
    public class EnemyPool
    {
        private readonly Queue<Enemy> _pool = new();
        private readonly Enemy _prefab;
        private readonly Transform _parent;
    
        public EnemyPool(Enemy prefab, int initialCount, Transform parent = null)
        {
            _prefab = prefab;
            _parent = parent;
    
            for (int i = 0; i < initialCount; i++)
            {
                var enemy = Object.Instantiate(_prefab, _parent);
                enemy.gameObject.SetActive(false);
                _pool.Enqueue(enemy);
            }
        }
    
        public Enemy Get()
        {
            if (_pool.Count > 0)
            {
                return _pool.Dequeue();
            }
    
            // 足りない場合は新規生成(必要最低限)
            return Object.Instantiate(_prefab, _parent);
        }
    
        public void Release(Enemy enemy)
        {
            enemy.gameObject.SetActive(false);
            _pool.Enqueue(enemy);
        }
    }

そもそもFactoryパターンの利点:

項目内容
生成ロジックの分離newを直接書かず、生成処理をFactoryに任せる。クラスの依存関係を減らせる。
拡張が容易新しい型を追加しても、呼び出し元を修正せずに済む。
コードの可読性・保守性複雑な初期化処理(パラメータ設定、マテリアル付与など)を一箇所にまとめられる。
依存性の軽減呼び出し側が具体クラスを知らなくて済むため、差し替え・テストが容易。

FactoryパターンとObjectPoolの相性が良い。


Author: 松崎 | Source: 松崎\C#コーディングノウハウ 2a7aba435ee780a5bc79ca458b2613ef.md