ども、ゴコーです。
よくWeb記事などで「変数は public にしない方がいいので」という文を見ることがあると思います。その理由は何でしょうか?
この記事は
- 変数を public にしない方がいい理由
- [SerializeField] はどういう場合につけるのか
を知らない方向けの記事です。
変数を public にするとどうなるか
public にすると以下のように扱えます。
- シリアライズされて、インスペクターに表示できる
- どのクラスからも中身を見られる
- どのクラスからも中身を書き換えられる
以下のようなクラスを用意して ObjectA に張り付けると、
1 2 3 4 5 6 7 |
using UnityEngine; public class ClassA : MonoBehaviour { public int intField; } |
そのオブジェクトのインスペクターは、この画像のようになります。
このように、シリアライズとかは知らないけど変数をインスペクターに表示したい時は public にするわーという初心者の方も多いんじゃないかと思います。
次に先ほどの ClassA にアクセスするクラスを作ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
using UnityEngine; public class ClassB : MonoBehaviour { void Start() { // ClassA のインスタンスを見つける. ClassA classA = FindObjectOfType<ClassA>(); int i = classA.intField; // 読み取りできる. classA.intField = 15; // 書き込みもできる. } } |
public にするということは、このように他のクラスからも自由に読み取り、書き込みアクセスが自由にできるということです。当たり前ですね。
変数を public にすると何が困るのか
ぼくは他のクラスから自由に書き込めることだと思ってます。
具体的にいうと、次のようなケースに対処するのが面倒です。
- 不正な値の書き込み
- 変数の扱い方が変わった場合への対処
それぞれ具体例を出してみます。
まず、ゲームのスコアを管理するスコアマネージャー(ScoreManager)、アイテム(ScoreItem) の2クラスを用意します。
スコアマネージャーには Score という public 変数を用意し、アイテムクラスは取得時に一定のスコアを足すものとします。
1 2 3 4 5 6 7 |
using UnityEngine; public class ScoreManager : MonoBehaviour { public int Score; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
using UnityEngine; public class ScoreItem : MonoBehaviour { // 取得時にスコアマネージャーに加算するスコア. public int AddScore; // スコアマネージャーを張り付けたゲームオブジェクトをインスペクターから設定する. public ScoreManager scoreManager; void GetItem() { scoreManager.Score += AddScore; } } |
本来はスコアマネージャーなどのマネージャークラスはインスペクターから設定するのは得策ではありません。しかし本題ではないので今回は割愛します。
最初の例と同じく、 Score は public 変数なので自由に数字を足すことも、引くこともできます。
困るケース1:不正な値の書き込み
ScoreItem のスコアはオブジェクト毎に自由に設定できるようにします。
さて、最初はスコアを増やすアイテムしかなかったのですが、仕様変更がありスコアを減らすアイテムも作ることになりました。しかしスコアの合計がマイナスになるのはおかしいのでチェックするようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
using UnityEngine; public class ScoreItem : MonoBehaviour { // 取得時にスコアマネージャーに加算するスコア. public int AddScore; // スコアマネージャーを張り付けたゲームオブジェクトをインスペクターから設定する. public ScoreManager scoreManager; void GetItem() { scoreManager.Score += AddScore; // 0 を下回らないようにチェックする if (scoreManager.Score < 0) scoreManager.Score = 0; } } |
ほんの少しの修正ですが例えばさらに仕様変更があり、敵を倒したときにスコアが増える、味方を倒したときはスコアが減るという要素が増えた場合はどうでしょうか。スコアを変更するクラスの数だけ 0 のチェックをしないといけなくなります。
数値だけではなく「本来ありえないことの回避策」をすべての場所で行うのはとても面倒、というかありえません。
困るケース2:変数の扱い方が変わった場合への対処
さらに仕様変更が入ってしまいました。
連続してアイテムを取ったり敵を倒すとテンションが上がり、スコアの増加にプラス補正がかかることになりました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
using UnityEngine; public class ScoreManager : MonoBehaviour { // テンション. public enum Tension { Normal, High } public Tension tension; // テンションが高い場合のスコア倍率. public float HighRate = 1.2f; public int Score; } |
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 33 34 35 36 37 38 39 |
using UnityEngine; public class ScoreItem : MonoBehaviour { // 取得時にスコアマネージャーに加算するスコア. public int AddScore; // スコアマネージャーを張り付けたゲームオブジェクトをインスペクターから設定する. public ScoreManager scoreManager; void GetItem() { if(AddScore > 0) { // 増加時のみ補正をかける. scoreManager.Score += (int)(AddScore * GetRate()); } else { scoreManager.Score += AddScore; } // 0 を下回らないようにチェックする if (scoreManager.Score < 0) scoreManager.Score = 0; } float GetRate() { switch(scoreManager.tension) { case ScoreManager.Tension.Normal: return 1.0f; case ScoreManager.Tension.High: return scoreManager.HighRate; } return 1.0f; } } |
先ほどよりもがっつりとした変更が入りました。
ではこの変更をスコアを扱うすべてのクラスに施しましょう!……というのは苦行すぎるのでやめましょう。
また、もしもスコアに関するバグが発生した場合スコアを書き換える処理を書いているすべてのクラスを調べなければいけません。すごいですね(白目
処理はできる限りクラス内で収めるべき
このように、 public 変数を公開して他のクラスから自由に書き換えられるようにすると何かと非常にめんどくさいことになります。複数人での開発ともなると地獄しか待っていません。
ではどうするかというと…以下のようにします。
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
using UnityEngine; public class ScoreManager : MonoBehaviour { // テンション. private enum Tension { Normal, High } // クラス内でのみ扱う. private Tension tension; // テンションが高い場合のスコア倍率. [SerializeField] private float HighRate = 1.2f; private int score { get { return _score; } set { if (value > 0) { _score = (int)(value * getRate()); } else { _score = Mathf.Max(value, 0); } } } private int _score; public void AddScore(int addScore) { score += addScore; } float getRate() { switch (tension) { case Tension.Normal: return 1.0f; case Tension.High: return HighRate; } return 1.0f; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
using UnityEngine; public class ScoreItem : MonoBehaviour { // 取得時にスコアマネージャーに加算するスコア. [SerializeField] private int score; // スコアマネージャーを張り付けたゲームオブジェクトをインスペクターから設定する. [SerializeField] private ScoreManager scoreManager; void GetItem() { scoreManager.AddScore(score); } } |
変数を private に変更して [SerializeField] 属性を付け、 スコアへの干渉は AddScore からのみできるようにしました。
get set などが分からないかもしれません。これはプロパティといい、値にアクセスされた瞬間処理が実行されます。詳しくは次回ということで。
結果 ScoreManager は大きくなりましたが、 ScoreItem は同じだけ短くなりました。
この形式の方が良い理由をざっくりまとめると
- クラスの中身を外部から自由に書き換えられない
- 不正な値に対処できている
- バグ発生時チェックしなければいけない箇所が減る
というところです。要求が増えた場合もスコアマネージャーに処理を追加するようにしましょう。
[SerializeField] はどういう場合につけるのか
先ほどのスクリプトを変更する際、 public な変数を private に変更して Serialize 属性をつけました。
Unity は変数をインスペクターに表示する際シリアライズということをするのですが、その方法は2つあります。
- 変数を public として定義する
- [SerializeField] を付ける
public にはしたくない場合、 [SerializeField] を付けるしかないということになります。
次回はプロパティについて勉強してみましょう。今回の記事について分かりにくい部分等ありましたらコメントで教えてくださいませー。
では、また。
コメント
RT @backlight1144: 新しい記事です: 【初心者向け】変数は public にしない方がいい理由 https://t.co/IVgymajg6M by Unity道しるべ
RT @backlight1144: 新しい記事です: 【初心者向け】変数は public にしない方がいい理由 https://t.co/IVgymajg6M by Unity道しるべ
RT @backlight1144: 新しい記事です: 【初心者向け】変数は public にしない方がいい理由 https://t.co/IVgymajg6M by Unity道しるべ