本投稿は2024年9月時点の内容になります。アップデートにより変更となる場合があります。また環境によって違いがあると思いますのであくまで参考として、ご了承ください。
様々な書籍、ブログや動画を参考にさせていただきました。多すぎて一つ一つは紹介できませんが感謝です。
初心者の自分がUnity ソフトウエアでゲームを作ってみました。とりあえずシンプルなものということでモグラたたきに挑戦です。ゲーム作ってみるかという感じになったときに、いいタイミングで某ゲームのイベントシナリオ内ミニゲームにモグラたたきが実装されていたのでUIとかエフェクトとか、諸々の仕様をぱくって参考にして作ってみましたよ。様々なHowToの中の選択肢のひとつとして、同じ初心者さんの参考になればよいです。
\ チェック /
目次
Prefabプレハブ化とスポーンをやっていきます
前回までに敵キャラゲームオブジェクトのアニメーションとロジックがほぼ出来ました。
今回は敵キャラのプレハブPrefab化とスポーンを解説、初心者がハマるポイントと対処法を解説していきます。
本記事のポイント
- ゲームオブジェクトのプレハブ化
- プレハブをスポーンする方法
- 初心者が必ずハマるプレハブのアサイン外れに関して解説
ゲームオブジェクトのプレハブ化します
プレハブて何?
Unity の Prefab (プレハブ) システムでは、1 つのゲームオブジェクトをそのすべてのコンポーネント、プロパティ値、子ゲームオブジェクトとまとめて、再利用可能なアセットとして作成、設定、保存することができます。
Unity 公式マニュアル
ざっくりと再利用可能なゲームプロジェクトのパッケージって感じでね。
ゲームオブジェクトのプレハブ化
ゲームオブジェクトをプレハブ化する
- 整理用にプロジェクトに「Prefabs」フォルダーを作ります。
- そこにヒエラルキー上からプレハブ化したいゲームオブジェクトをドラッグアンドドロップします。今回はゲームオブジェクト「Unitychan」をプレハブ化しました。
- 今後「Unitychan」はスクリプトからスポーンするので、ヒエラルキー上にある「Unitychan」は削除してしまいましょう。
これだけです。簡単ですね。
プレハブを編集したいときは?
プロジェクトのプレハブをダブルクリック、もしくは選択してインスペクターの「開く」をクリックすると、プレハブモードになって編集できます。
戻るときはヒエラルキー上で「<」をクリック。
プレハブをゲームシーンにスポーンしよう
準備
- スクリプト「EnemyManager」を作成
- ヒエラルキー上に「EnemyManager」の名前で「空のオブジェクト」を作成
- 「EnemyManager」ゲームオブジェクトに「EnemyManager」スクリプトをアタッチ
スクリプトを書いていきます。
public class EnemyManager : MonoBehaviour
{
[SerializeField]GameObject enemyPrefab;
// Start is called before the first frame update
void Start()
{
//プレハブenemyPrefab、(0,0,0)にQuaternion.identity(回転なし)でインスタンス化
Instantiate(enemyPrefab, new Vector3(0, 0, 0), Quaternion.identity);
}
// Update is called once per frame
void Update()
{
}
}
スクリプトざっくり解説
- (3)フィールドenemyPrefab
- (8)「enemyPrefab」を座標(0,0,0)にQuaternion.identity(回転なし)でインスタンス化。
- enemyPrefabにアセットにあるプレハブをアサインします。
ゲーム再生します。スポーンはされましたがコンソールにエラーがいっぱい出てますね。
まずはログ「指定された名前がステータスリストに見当たりません」から対処します。CharacterStatusDBから該当する名前での検索が失敗してるようです。
原因はインスタンス化したゲームオブジェクトの名前の後ろに「(Clone)」が付いているためです。
「UnitStatusDB」クラスのGetStatusByNameメソッドを修正します。
public UnitStatusData GetStatusByName(string name)
{
name = name.Replace("(Clone)", "");
//UnitStatusDataのフィールドnameと引数nameが一致するデータをresultに代入
var result=UnitStatusList.Find(UnitStatusData => UnitStatusData.name == name);
//一致するものがなかったらコンソールに出力
if(result == null)
{
Debug.Log("指定された名前がステータスリストに見当たりません");
}
//resultを返します
return result;
}
スクリプトざっくり解説
- (3)で検索前に文字列から問題のあった「(Clone)」を取り除いています。
再度ゲーム再生してみましょう。「指定された名前がステータスリストに見当たりません」エラーは解消されましたが、まだいくつかエラーが出てると思います。
「NullReferenceException」は参照が見当たらない、必要なアサインがされてないときによく出るエラーです。
アセットにある「Unitychan」プレハブを選択してインスペクターを確認してみます。「EnemyCotroller」の「BattleManager」のアサインが見事に外れてますね。
これがマケイヌ的Unity 初心者泣かせの「プレハブのアタッチ外れ(アサイン外れ)問題」です。
プレハブのアサイン外れ問題を解決する
どうしてプレハブ化するとアサインが外れてしまうのか
プレハブ化した時にアサインが外れるのは、ゲームオブジェクトがヒエラルキー上で実体化しているときは情報を渡せるが、プロジェクト上にプレハブ化したときに、その関係性が切れるためらしいです(Unity の詳しい処理はわからないですが)。
下記のようにイメージするとわかりやすいかな?どうでしょう。
ざっくりイメージ解説
- プレハブはシーンにあるスクリプト(道具)を使いたい
- シーン(ヒエラルキー)は実体化がある現世(現実世界)。
- プレハブがいるプロジェクト(アセット)は幽世(あっちの世界)。
- アタッチとは現世でゲームオブジェクトに道具の場所を教える(地図を渡す)こと
- ただしプレハブは幽世にいるので現世のどこに道具があるか教えることができない(アサイン外れ)。
- 現世の召喚士(シーン上のスクリプト)が召喚魔法(Instantiate)を唱えるとプレハブは現世に実体化される。
イメージわきましたか?
このイメージで考えるとアタッチ外れの解決策も見えてきます。
アサイン外れの解決策
- プレハブは現世に召喚(インスタンス化)されるまで道具の場所を知ることができません。
- 召喚されたとき(インスタンス化した時)道具の場所を知る方法は2つです。
- 召喚された実体化したプレハブが自分で探しに行く(GameObject.Findを使う)
- 召喚士(Instantiateしたスクリプト)がプレハブに道具のある場所を教える(publicなメソッド、プロパティを使って渡す)
それぞれの方法でプレハブのアサイン外れを解決します。
解決方法その1 GameObject.Findをつかう
「EnemyController」クラスを変更します
public class EnemyController : MonoBehaviour, IPointerDownHandler//インターフェースを実装したクラスであると宣言
{
//省略
[SerializeField]BattleManager battleManager;
//省略
private void Start()
{
battleManager = GameObject.Find("BattleManager").GetComponent<BattleManager>();
}
}
スクリプトざっくり解説
- (9)GameObject.Findで対象のゲームオブジェクト「BattleManager」を探して、GetComponentで「BattleManager」クラスのスクリプトを取得
- (4)アサインはしないのでSerializeFieldha削除してもよい(今回は確認のためつけています)
ゲーム再生してインスペクターを確認するとアサインが外れていたフィールドが対象のスクリプトを取得出来ているのがわかります。
解決方法その2 メソッド(もしくはプロパティ)をつかう
「Enemy」クラスを変更します
public class EnemyController : MonoBehaviour, IPointerDownHandler//
{
//省略
[SerializeField] BattleManager battleManager;
[SerializeField] Animator enemyMove;
//省略
public void SetManager(BattleManager battleManager)
{
this.battleManager = battleManager;
}
}
スクリプトざっくり解説
- (7-10)publicなメソッドを使って設定
- (4)アサインはしないのでSerializeFieldha削除してもよい(今回は確認のためつけています)
「EnemyManager」クラスも変更します
public class EnemyManager : MonoBehaviour
{
[SerializeField]GameObject enemyPrefab;
[SerializeField] BattleManager battleManager;
// Start is called before the first frame update
void Start()
{
GameObject enemyObj= Instantiate(enemyPrefab, new Vector3(0, 0, 0), Quaternion.identity);
EnemyController enemyController = enemyObj.GetComponent<EnemyController>();
enemyController.SetManager(battleManager);
}
}
スクリプトざっくり解説
- (4)フィールド プレハブをインスタンス化する役割の「EnemyManager」が、同じヒエラルキー上にある「BattleManager」を「battleManager」としてを取得
- (8,9)プレハブがインスタンス化したゲームオブジェクトからスクリプト「EnemyController」を取得
- (10)「EnemyController」のフィールド「battleManager」に「SetManager」メソッドで「_battleManager」を渡す。プロパティのsetterで渡してもOKです。
- 「BattleManager」はアサインします。
ゲーム再生してインスペクターを確認すると、こちらもアサインが外れていたフィールドが対象のスクリプトを取得出来ているのがわかります。
解決法1は毎回シーンを探しに行くので、マケイヌ的には解決法2の方がおすすめです。ただし、解決法1は「EnemyController」の中だけで処理をおこなえる利点もあるので、あとは好みと適材適所て感じでしょうか。
ちょっと長くなったので本格的なスポーンの実装は次に続きます
まとめ
まとめ
- プレハブのインスタンス化(スポーン)は「Instantiate」で出来る
- プレハブ化のアサイン外れは ①GameObject.Findを使って探すか ②publicなメソッド(もしくはプロパティ)を使ってフィールドに渡してあげることで 解決できる
\ Unityのスクリプトを書くのに役立ちます /
もっと早く教えてほしかった!Unity C#入門
MARU
おすすめ記事
Unity ソフトウエアでゲーム制作#1モグラたたき編(28.ゲームに効果音…
Unity ソフトウエアでゲーム制作#1モグラたたき編(27.エフェクト2パ…
Unity ソフトウエアでゲーム制作#1モグラたたき編(26.エフェクトその1)
Unity ソフトウエアでゲーム制作#1モグラたたき編(25.プレハブバリア…
Unity ソフトウエアでゲーム制作#1モグラたたき編(24.Buttonで…
Unity ソフトウエアでゲーム制作#1モグラたたき編(23.シンプルなアニ…