前回(9回)は弓関連のUIを追加し、レベルアップや売却が実際に出来るようになりました。
前回の記事↓
今回はついにこのタワーディフェンス講座も最終回。
タイトルやゲームクリア・ゲームオーバーなどの「ゲームループ」部分を主に作って完成させていきます。
また、これまで作ってきたC#スクリプト全文やゲームをより面白くするレベルデザインについても触れています。
EnemyManagerUIの作成
ゲームループの前に、EnemyManagerの情報表示を作りましょう。
第8回で作ったPlayerStatusUI オブジェクトを複製(CTRL+D)して作られるPlayerStatusUI (1) を EnemyManagerUIと名前を変更し、同じく複製されているPlayerStatusUIスクリプトは削除(右クリックしてRemoveComponent)します。
位置も重なってしまっているので Pos Yを下に下げます。 -500ぐらいが適当です。
そのまま複製され子要素の Text(HP)と Text(GOLD)も名前をText(WAVE)とText(ENEMY)に変更します。
EnemyManagerUI に AddComponent→New Script → EnemyManagerUI と選択し、EnemyManagerUIスクリプトを作成し、下記のように編集します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class EnemyManagerUI : MonoBehaviour { public EnemyManager enemyManager; public Text waveText; public Text enemyText; void Update() { waveText.text = $"WAVE:{enemyManager.wave + 1}/{enemyManager.waves.Length}"; enemyText.text = $"ENEMY:{enemyManager.EnemyCnt}"; } } |
メンバ変数は
- どのEnemyManagerの情報を表示するか(Inspectorで設定)
- EnemyManagerのWave情報を表示するためのText(Inspectorで設定)
- EnemyManagerのEnemy数情報を表示するためのText(Inspectorで設定)
の3つです。 Update関数の中では UIのTextにWave情報と敵の数情報をセットしています。
ただ、この
1 |
enemyText.text = $"ENEMY:{enemyManager.EnemyCnt}"; |
のenemyManager.EnemyCnt
はまだEnemyManagerスクリプトに無いので EnemyCnt
プロパティを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class EnemyManager : MonoBehaviour { public Wave[] waves; public int wave; public float time; //まだこのwaveで出現してない敵+画面上の敵の数 public int EnemyCnt => waves[wave].patterns.Count + FindObjectsOfType<Enemy>().Length; ~~以下略~~ |
まだ現在のwaveで出現していない敵の数は waves[wave].patterns.Count
になります。
そして、既に出現している敵(Enemy)は FindObjectsOfType<Enemy>().Length
で取得し、それを足す事で敵残数を取得できるプロパティにしています(=>
を使ったgetプロパティの省略記法です)。
注意点としては waves[wave].patterns
はListなのでCount
で要素数を取得し、FindObjectsOfType<Enemy>()
は配列なのでLength
で要素数を取得しています。
2スクリプトを修正しましたので、忘れずに2つとも保存をしましょう。
UnityEditorのInspectorでEnemyManagerUIのEnemyManager、Text(WAVE)、Text(GOLD)をセットします。
それではプレイボタンを押して、確認してみましょう。
GOLDの獲得とダメージ処理
大分ゲームが完成に近づいてきましたが、まだ
- 敵を倒してもGOLDが増えない
- 敵がゴール(本拠地)に到着してもHPが減らない
ので、そこもやってしまいましょう。
敵を倒したらGOLDを増やす
矢(Arrow)スクリプトで敵(Enemy)を倒した時に処理を追加します。
23 24 25 26 27 28 |
targetEnemy.hp -= 1; if (targetEnemy.hp <= 0) { Destroy(targetEnemy.gameObject); FindObjectOfType<Player>().gold += targetEnemy.gold; } |
FindObjectOfType<Player>()
でPlayerオブジェクトを探し、gold
を倒した敵の金額(targetEnemy.gold
)分増やしています。
- Arrowを発射するBowに設置をしたPlayerオブジェクトを保持させ、バケツリレーのように渡していく
- Playerオブジェクトは1つしかないので Playerオブジェクトをstatic を使ったシングルトンオブジェクトにする
- Find系関数を使ってPlayerオブジェクトを探す
等々があるのですが、今回はFind系関数である FindObjectOfType<探したい型>() を使っていますが深い理由はありません(強いて言えば修正するスクリプトが少ない方が混乱が少ないという講座制作上の理由になります)他の方法を使っても問題ありません。
敵がゴール(本拠地)に到着したらHPを減らす
次に敵(Enemy)スクリプトを修正します。
Waveの追加・レベルデザイン
これで完成!! と言いたいところですが、この例だとWaveが一つしかないですね。
最後にWaveの追加方法と、さらなるレベルデザイン例を提示してこのタワーディフェンス制作講座の締めくくりとしたいと思います。(既にWaveを自分で量産している方は読み流してください)
敵のWaveを管理しているのは、EnemyManagerオブジェクトでしたね(タワーディフェンス講座 6/10参照)。
今は、Waves の Size が 1 となっているので、1waveで終わってしまいます。
なので Element 0 となっているところを右クリックし、「Dupulicate Array Element」を選択して複製することで、Element 1が増えます。
このWaveの複製を繰り返すことで何Wave構成にもできます。
なお、Element 0 の中にあった Patterns の中身もElement 1に複製されるため、多数Waveの複製をした状態で再生をして確認をすると全てのwaveが全く同じ敵構成の内容ということになります。
もちろんそれでは面白みが無いので、削除したり内容を変更していきます。
Wave2以降も作ってみたら、実際に敵の動きを確認してみたいですよね。
しかし、毎回Wave1から開始して敵を倒してやっとWave2の実際の動きを確認・・・では辛いです。
そんな時は EnemyManager のInscpectorビューで Wave を テストしたいwave番号に変更しましょう。指定Waveから開始することが出来るため時間を短縮できます。(もちろん通しでWaveをプレイするのも大事です)
テストが終わったら0に戻すのを忘れないようにしましょう。
レベルデザインの一例
例として簡単に5wave構成のレベルデザインを考えてみましょう。
- wave1~3 は1wave内では各敵1種類だけ出現させ、「この色の敵は、このような特徴がある」というのをプレイヤーに示す、自己紹介waveとし、
- wave4 で、複数の敵が混合で来ることで、難易度をちょっと上げていき、
- wave5 で、計画的に弓を配置していないとゲームオーバーになってしまうような敵の量、経路で敵を配置。
という感じでしょうか。
もちろんもっとWaveを多くしても良いですし、ブロックの配置やゴール、経路も好きに変えてしまいましょう。
もっと硬いEnemyオブジェクトを新規で作っても良いです(ちょっと大きくして、最終Waveにボスとして出すというのも良いですね。)
Enemyの落とすGOLDの量を調整する事も必要かもしれません。
Playerの初期HPと所持金も非常に重要なレベルデザインになります。
ちょっと変則ですが、旗(敵からみたゴール)が複数あるステージも考えられます。
是非工夫を凝らしたステキなレベルデザインをしてみてください。
これは敵の速度やHP、GOLDを調整してみたレベルデザイン例になります。(ボスっぽい大型Enemyも作ってみました)
参考になれば幸いです。
おさらい
これにて、ゲームは完成し10回に渡ってお付き合いしていただいたタワーディフェンス制作講座も(ひとまず)終わりになります。
ここで終わりにさせず、別シーンでタイトル画面を作ったり、ステージセレクトを追加したり、効果音や演出を適切に入れる事でよりゲームとして完成されていくと思います。
(追記:例としてゲームルールにマイナーチェンジを加えたサンプルゲームも作ってみました。)
このタワーディフェンス作成講座をベースに素敵なゲームが完成した際には是非一報頂けると嬉しいです。 ここまで読んでいただき誠にありがとうございます。
タワーディフェンスゲームの作り方講座に戻る↓
コメント
一応完成しました!
内容もわかりやすくまとめていてとても理解しやすかったです!
これからUnityでゲームを作っていく上で参考にしていきたいと思います!
ありがとうございました!
おお!完成おめでとうございます!
完成報告いただけてとてもうれしいです。
これからも新しい講座どんどん作っていくのでまたサイト見に来てもらえたらうれしいです。
こちらこそ最後まで読んでいただきありがとうございました!^^
ここ5日くらい、このブログを人生で初めてゲーム作りを完遂させました! 今までも何度か挑戦しようとしては挫折してを繰り返していたので、完成出来てめちゃくちゃ嬉しかったです!
せっかくなのでこのままUnityゲームジャムに何かしらのゲームを投稿してみようかと思います!
すばらしいコンテンツをありがとーございました!
おおー!初のゲーム完成おめでとうございます!^^
この講座作った甲斐がありました。コメントもいただけてとてもうれしいです。
ちょうどunityゲームジャムも始まりましたもんね!
おこめさんの作るゲーム楽しみにしてます。
次回作の講座ももうすぐ出来上がるのでまた読んでやってください(*’ω’*)
講座ありがとうございました!
3週間くらいかけてじっくり読み込みながら作りました
プログラムの書きかたがとても分かりやすく
挫折せずに開発まで作りあげるが出来ました
せっかく作ったので、忘れないうちにオリジナルのタワーディフェンス製作を
やってみたいと思います!!
目指せ! Kingdom Rush 超え!!(大きく出たな
この講座がなければやってみようと思いませんでした
本当に分かりやすい講座でした
ありがとうございます!!
うれしいコメントありがとうございます!^^
最後まで挫折せずに作れたとのことで講座制作者冥利に尽きます!
プログラムの書き方なども今後もさらにブラッシュアップさせながら講座制作続けていきます。
ぜひオリジナルゲーム完成まで進めていただければと思います。
ゲーム完成したらまたコメント欄などでいつでも教えてくださいね!
こちらこそ最後まで読んでいただき、実際に手を動かして作っていただきありがとうございました!
いただいた声を励みにこれからもいろんなゲームプログラミング講座作っていきます!
最後まで行くことは出来ましたが、敵が弓矢の射程に入ったら弓矢が飛んでいく矢ごと消えてしまいます。VECTOR3 position関連なのでで三次元座標の値がおかしいのだと思いますが講座通りにプログラムを打ち込んでるのに治らないので、解決方法を教えていただけると幸いです。よろしくお願いします
既に完成させている方も出てきてますし、ちゃんと作ればそうはならないはずですね。
TETSUさんのコードがどこか間違えてるんでしょうね。
デバッグもゲームプログラミングにつきものですからね。
コードやUnityのオブジェクトへのアタッチの過程にミスがないか確認してやってみてください。
ファイトです!
C#、Unityの初学者です。
ばこさんのtwitterを拝見し、こちらの講座を受講して最後まで完成させる事ができました!
自分のPC上でゲームが遊べる事に感動しました。本当にありがとうございます。
1点、疑問がありまして、タワーディフェンスの挙動でよく見る「一番ゴールに近い敵を優先で狙う」方法についてです。
OverlapCircleのLayerMask.GetMask(“Enemy”)取得時に「自分の総合移動値」などをEnemyに持たせておいて条件を付ける事ができれば、やりたい事が可能かな…と想像してしたのですが、実現する方法が分からず、質問させて頂きました。
よく見る挙動ですし、何かお決まりのプログラムでもあるのでしょうか…?
長文失礼しました。
他の講義も受講させて頂きます!
タワーディフェンス制作講座を担当したすずきかつーきと申します。完成おめでとうございます!
一番ゴールに近い敵を優先で狙う
なるほど、確かによくある処理ですね。 そこは丁寧に作ると非常に難解になってしまうので、あえて雑にしてある箇所でした。
色々な方法がありますが、まず現状の
Physics2D.OverlapCircle
では、範囲内のEnemyの「どれか」しか返却しないため、実際に複数のEnemyが範囲内に居た場合、どのEnemyを狙うのかはランダムになってしまっています(厳密にはUnity内部のルールに従っているとは思いますが)
それを解消するには、まず
Physics2D.OverlapCircle
の使用をやめて、代わりにPhysics2D.OverlapCircleAll
を使う必要があります。こちらのメソッドは、範囲内のオブジェクト(正確にはCollider2D)「すべて」を配列にして返却してくれるため、そこからおっしゃる通り「最も移動距離が長いEnemy」を一つ取得するのは良い手だと思います。
ただ、Enemyスクリプトのデータを扱いたい場合は
GetComponent()
などして、Collider2DからEnemyを取得する必要が出てきますので注意してください。コメント欄に書ききれないのであまり具体的な事が書けなくて申し訳ないですが、頑張ってみてください!!
ご返答ありがとうございます!
なるほど~ Physics2D.OverlapCircleAllといったメソッドがあるのですね
こちらをヒントに思った挙動に改造できるか試してみます!
ありがとうございました。
なんとか最後まで作れました!
とても勉強になる講座をありがとうございます。これからじっくり復習していきます!
よろしければ、今後は『キャラクターにスキルを実装する』や『配置するキャラクターを選択できるようにする』といった機能を学べるとありがたいです。
中・上級編の講座がでたらまた購入いたしますので、ご検討お願いします。
最後まで講座取り組んでいただき、またコメントいただきありがとうございます!
なるほど!
『キャラクターにスキルを実装する』、
『配置するキャラクターを選択できるようにする』といった機能
↑これら、タワーディフェンスではないですが、次回作のSRPG講座で同種の機能を実装する方法を掲載予定です(まだ執筆中なので必ずしもご希望のものになってるかはわからないですが)。
今執筆中のものなどがある程度まとまったら初心者向け講座のブースターパックみたいなのを書いてみるのも面白そうだなと思いました。
ご意見ありがとうございます!
一昨日にこのサイトが気になって購入してしまいました。
非常に丁寧な説明でわかりやすいですね。
完成まで「なるほど、そうゆうことか!!」と疑問を残すことなく気持ちのいい講座でした。
すぐに忘れてしまうので復習のため、また最初から読み直しますね。
このサイトはすごく気に入りました。
他の講座もやらせていただこうかと思います
嬉しいコメント&講座購入&お褒めの言葉ありがとうございます!
気に入っていただけてよかったです^^
まだまだ至らぬところも多いので今後とも精進してまいります。
他講座も気に入っていただければ幸いです。
以前、こちらの講座で学んだ内容を応用して、クオリティはともかく、ようやくオリジナルゲームを1本リリースできました。作ったのはアクションゲームですが、ループ処理の実装方法や武器のレベルアップ処理など、多くの要素について、こちらの講座内容を応用させていただきました。アクション講座とSRPG講座は購入しておりますので、次回はこちらの内容も活かしつつ
スマホアプリのリリース目指します。今後とも、よろしくお願いします。
おおー!完成&リリースおめでとうございます!
Unity入門の森の講座が実際にリリースされたゲームに役立ったとのこと、とても嬉しいです^^
報告いただきありがとうございました。
スマホアプリ化に関してもアクションゲーム講座やSRPG講座が役立つかと思います。
こちらこそ今後ともよろしくお願いいたします!
講座を購入させていただき、完成させることができました。
とても丁寧な説明でわかりやすく、大変参考になりました。
一点、まだ理解できていないところがあり、完成したゲームでは、
「Gameビュー」上は問題なくゲーム画面が表示されているのですが
「Sceneビュー」ではGameFieldに対してCanvasが非常に大きく配置されています。
1. わざとこのように配置したのか
2. なぜこうなっているのにGameビューでは問題ないのか
が気になっています。
また、このくらいの規模のゲームでもきちんと「設計」しないとゲームを
完成させるのは難しいんだな、と感じました。ですが、実際にどんな設計書を
書けばいいのかまではイメージできておらず、何か参考情報でも教えて
いただけますと幸いです。
今後も他の講座も購入させていただきながら勉強していこうと思っています。
よろしくお願いいたします。
講座購入&完成報告ありがとうございます!
ゲーム完成したときはとても嬉しいですよね。
解説もお褒めいただきありがとうございます。
Canvasの大きさに関してはCanvasのRender ModeのScreen SpaceをOverlayからCameraに変更すればちょうど良いサイズに変更されます。
手順に関してタワーディフェンスゲームの作り方講座の第2回目の記事に追記しました。
詳しい解説は別講座の「クリッカーゲームの作り方講座」の第2回にあるので読んでみてください。
https://feynman.co.jp/unityforest/game-create-lesson/clicker-game/making-ui-create-sheep/
ゲームの設計に関しては難しいところですね。
設計に関しては自分もまだまだといった感じですしプロの方も日々開発物に応じて良い形を模索してる印象があります。
ゲームの場合、最初は小さな遊びの体験を作り、そこから肉付けしていく形で行うのが最初は良いと思います。
慣れてきたら最初からある程度複雑なゲームシステムも作れるかもしれませんが、
まずはどんな遊びを作りたいかを考えていくと良いのかなと。
設計に閉じた本ではないですが、ゲームを形にする上でおすすめの書籍としては、
「気持ちいい」から考えるゲームアイデア講座が良かったです。
また、ゲームに限らず一般的なプログラミングを行う際の設計であればUMLモデリングの本などを見てみるのも良いかもしれません。
ご返信ありがとうございました。
追記と別講座や書籍のご紹介ありがとうございました。後ほど確認させていただきます。
設計に関しては日々良い形の模索をしていこうと思います。
今後ともよろしくお願いいたします。
なんとか最後まで作ることができました!!
丁寧でわかりやすい講座でした。
ありがとうございます。
これは、本当はGAME_PLAY状態の時だけ敵を生成しないようにしなければいけないところで
ここは生成するようにの誤字なのではないですか
コメントありがとうございます!
“生成するようにしなければいけない”
が正しいですね。訂正しておきました!
最新版のunityでも完成できました!
途中何回か検索したり、ユニティ側のUIが違うため試行錯誤ありましたが何とか出来ました!
そのままリリースはしたくないのでアレンジしてリリース目指してみます。
完全初心者向けではなく、一度無料でも何か学習した方向けかもしれないですね。特に困ったときは自分で検索して自分で解決するという学習法が無いと厳しいかなと思いました。
ありがとうございました。
他の講座も受けるかもしれないです。よろしくお願いいたします。
本講座学習中のUnity、C#初心者です。
EnemyManagerUIの作成 の箇所ですが、
内容通りにEnemyManagerUI、EnemyManagerスクリプトともに修正し、
EnemyManagerUIのインスペクター上でEnemyManager、Text(WAVE)、Text(GOLD)それぞれセットしてUIの動作確認しているのですが、動作はするもののエラーが出てしまいました。。
エラー内容:
NullReferenceException: Object reference not set to an instance of an object
EnemyManagerUI.Update () (at Assets/Scripts/EnemyManagerUI.cs:14)
UIの動作はウェーブ数も表示され、敵の数の表示の方も
敵オブジェクトを倒したり、ゴールされて敵オブジェクトが消えることで
正しい値が表示はされており、UIの動作に問題自体はなさそうなのですが。。
EnemyManagerUI、EnemyManagerスクリプトのコード等の見直しをしても
改善点がよくわからず、このまま次の項目を学習しても良いか分からない状態です。
恐縮ですが、ご助言をいただけますでしょうか。。
何卒よろしくお願いいたします。
エラーの原因は、NullReferenceExceptionが示している通り、どこかの参照がnullになっていることです。NullReferenceExceptionはアタッチミスなどで値やオブジェクトが入っているべきところに入っていないと出てくるエラーです。
エラーはEnemyManagerUI.Update()の14行目で発生しているとのことですが、該当するコードがwaveText.textやenemyText.textを更新する部分です。
以下の手順に従って、問題の原因を特定し、解決策を探りましょう。
1. インスペクターの確認
まずは、インスペクター上で必要な参照が正しく設定されていることを確認します。
EnemyManagerUIスクリプトをアタッチしているGameObjectを選択します。
EnemyManagerフィールドに正しいEnemyManagerオブジェクトが設定されていることを確認します。
waveTextフィールドとenemyTextフィールドに正しいTextオブジェクトが設定されていることを確認します。
public class EnemyManagerUI : MonoBehaviour
{
public EnemyManager enemyManager;
public Text waveText;
public Text enemyText;
void Update()
{
if (enemyManager == null)
{
Debug.LogError(“EnemyManager is not set in the Inspector”);
return;
}
if (waveText == null)
{
Debug.LogError(“WaveText is not set in the Inspector”);
return;
}
if (enemyText == null)
{
Debug.LogError(“EnemyText is not set in the Inspector”);
return;
}
waveText.text = $”WAVE:{enemyManager.wave + 1}/{enemyManager.waves.Length}”;
enemyText.text = $”ENEMY:{enemyManager.EnemyCnt}”;
}
}
このスクリプトを保存して再度実行し、コンソールに表示されるログを確認してください。エラーメッセージが表示された場合、そのフィールドがnullであることがわかります。
3. スクリプトの確認
EnemyManagerクラスが正しく設定されているか確認します。特に、waveとwaves、およびEnemyCntプロパティが存在し、正しく初期化されていることを確認してみてください。
EnemyManagerUIの作成に関するご助言、誠にありがとうございます。
EnemyManagerUIのスクリプトがアタッチしているオブジェクトの方で、
設定ミスがあったことを確認できました。
初歩的なミスでした・・大変申し訳ございません。
ゲームの作成完了後、本講座全体の流れの復習も兼ねて、
本講座をベースに再度ゲームを作成してみます!
やっと10章全部完了してキッチリ動きました!
が、9章10章あたりのC#で何を書いているか分からなくなってきたので
今のこのある程度分かった状態で、もう一回第一章から同じ手順を追いかけつつ
Unity C#プログラミング入門講座も触りながら勉強を続けます。
ありがとうございます。引き続きもうちょっと頑張ります!