【Unity】unity1week「あつめる」 GOLD RUSHの実装について 【unity1week】
unity1week「あつめる」が終わりました
お疲れ様です。ぐーるです。
終わりましたunity1week。
今回も難産でした。
お題は「あつめる」。
ちょろっと作ったカードダンジョンRPGを利用して
ゴールドを集めるゲームにしようと思い立ちました。
Twitter上でも非常に多くの方が注目してくれて
気恥ずかしい限りでしたが、非常に励みになりました。
自分の「あつめる」はストレートにゴールドを集めるにしました。
— ぐーるさん (@uuha_goul) July 3, 2019
ダンジョンに潜ってゴールドを集めながらどこまで潜れるかを競うイメージ。
RPGとパズルゲームを合わせた感じを出したい。#unity1week pic.twitter.com/Hx32pFZhs8
本当にありがとうございます。
今回も毎回お馴染みの実装について紹介したいと思います。
なるべくコンパクトにまとめたいのですが、毎回長文です…。
設計について
正直あんまり話すこと無い…w
前回のやつと比べるとすごい簡単。全部uGUIだし。
前回のなんてObject Pool , Preload System , Recycler
とメモリ管理の嵐でInstantiateを鬼の形相で排除してました。
これに比べたら全然言うことがない。
設計といえるほど工夫した点は特に無いんですが、
カードはもちろん継承して汎用的に処理をまとめて
どっからでも処理できるようにしてました。
UtilやExtension,NCMB,Effect,Soundなどゲームを補助するシステム
は省いてるけど基盤はこんな感じです。
(基本的にメモリに乗っかっててほしいので、1sceneです。
3Dじゃないし、リソースそんなに多くないのでめちゃくちゃ軽量だしね)
EnemyEngine?
この関連図を見ると異様なものが一個ある。そうEnemyEngineです。
これは敵キャラのカードを作った後、そのカードのステータスを決定するシステムです。
フロアによって出現する敵を制御したり、パラメータをセットしたり、画像リソースを持ってたりする。
これ、本当に製品にするなら、全てのカードに作るべきだと思った。
「GoldCard」を作る時は必ず「GoldEngine」を経由する とか
カードを生成する時、パラメータが状況によって変わってくる。
武器やゴールドは値しかないので、その値入れるだけなのだが、
その処理を外に出しておくと見通しが良くなって拡張性が高くなっていく
今回は面倒でEnemy以外はそのまま書いてしまった。反省。
ちゃんとやるなら全てのカードはEngine化する
画面について
左半分カードフィールド側
正直ちょっと失敗だったかも。
カード含めて全てuGUIでやってました。
が、カードの上に攻撃パーティクルを表示したくて
それを出すのにSortingLayerとか使って涙ぐましい努力してたりします。
SpriteRenderer + TextMeshProにしちゃって、3Dオブジェクトとして扱えばもっとラクに色々出来たかもしれない。
また、フロアが進むと3x3 , 5x5 , 7x7 ...とどんどんフィールドが広くなっていくので、
メモリ管理にどうなのかとちょっと不安ではありました。
今はいいですが20x20とかやろうとすると
同一カードのグルーピングをしてバッチング処理を考えた実装にしないと影響が出るかも知れません。
右半分ステータス側
また、値周りはUniRXのReactiveProperty使って値変えるだけで即座に反映されるようにしてました。
例えばゲーム一周してタイトルに戻った時に値を初期化するのですが、
その時にnewし直すと当たり前にsubscribeが外れるので、上手く回そう。
(newと初期値投入は別にするとかね。やっぱりInitializerクラスを作って別でやるべきだったかなぁと。
初期化処理は分散してますが、大量に処理があります。)
プログラムについて
特筆すべき所は…ありません!w
とか言うと本当に何にもなくなっちゃうので、むりやり捻出。
(コード的に難しい事は何にもしてないのでちょっと恥ずかしい)
ゲームログを表示している箇所はキューイングして複数のログを受け取ることが出来て、
一個ずつ遅延評価してます。
using UnityEngine; using System.Collections; using UnityEngine.UI; using System.Text; using System.Linq; using System.Text.RegularExpressions; using System.Collections.Generic; namespace CardDungeon { public class TextWriter : MonoBehaviour { [SerializeField] private Text _text; // 全部フィールド化して少しでもメモリ管理をラクにしてあげたい // 涙ぐましい努力 private int _length; private string _textString; private StringBuilder _stringBuilder; private bool _isAnimation = false; private const float ONE_TIME = 0.01f; private List<string> _messageQue = new List<string>(); public void Initialize() { _isAnimation = false; _text.text = ""; _length = 0; _textString = ""; _stringBuilder = null; _messageQue = new List<string>(); } public void Show(string txt) { _messageQue.Add(txt); } private void Update() { if (!_isAnimation && _messageQue != null && _messageQue.Count > 0) { StartCoroutine(textWriter(_messageQue[0])); } } IEnumerator textWriter(string text) { _isAnimation = true; _length = 0; _stringBuilder = new StringBuilder(); _text.text = _stringBuilder.ToString(); yield return null; while (_length < text.Length) { _textString = text.Substring(_length, 1); _stringBuilder.Append(_textString); _text.text = _stringBuilder.ToString(); _length++; yield return new WaitForSeconds(ONE_TIME); } _text.text = text; yield return new WaitForSeconds(0.25f); _isAnimation = false; _messageQue.RemoveAt(0); yield return true; } } }
またPopupする処理は得意のプーリング。
namespace CardDungeon { public class PopupManager : SingletonMonoBehavior<PopupManager> { [SerializeField] Dialog _dialog; [SerializeField] Dialog _yesnoDialog; [SerializeField] RectTransform _popupCanvas; [SerializeField] PopText _popTextPrefab; private List<PopText> _popList; private readonly int POP_TEXT_CNT = 10; private bool _isEnable; public bool IsEnable { get => _isEnable; set => _isEnable = value; } public void Initialize() { if (_popList != null) { foreach (var p in _popList) { Destroy(p); } _popList.Clear(); } // 予め生成しておいて非表示する _popList = new List<PopText>(); for (var i = 0; i < POP_TEXT_CNT; i++) { var p = Instantiate(_popTextPrefab, _popupCanvas); p.Initialize(); _popList.Add(p); } _dialog.Initialized(); _yesnoDialog.Initialized(); } // 以下略 public void ShowPopup(string valueString, RectTransform rect, Vector3 adjustPos) { if (!_isEnable) return; if (_popList != null) { // 使用中でないポップアップを使用する var p = _popList.FirstOrDefault(pop => !pop.IsUse); if (p != null) { p.Show(valueString, rect, adjustPos); } } } } }
簡単なコードだけどゲームには効果抜群です。
unity1weekとか出してるやつはほとんどやってそうですけど。
金土日の追い込みでやったこと
ここが一番つらく、楽しい時間です。
4時間睡眠 x 2です。(気絶するように寝る)
基本的なシステムは出来上がっていたが、
御粗末すぎるので、それを以下に製品に近づけるかの作業でした。
- オンラインランキング
- 敵エンジン
- グラフィックを仮素材から正式版に(UIまで出来ず)
- バグ取り(結局取り切れず。まれに同じ場所にカードが作られてしまう)
- タイトル、チュートリアル、ゲームオーバー画面などのUI全部
- その他細かい調整など
これは寝れない…。
寝たら死ぬぞ!と思いながらストロングゼロ呑んでました。
拾った画像なんですが、凄くお気に入りです。
グラフィック周りは本当にうつらうつらと…。
終わって
今回もすごいゲームが多いです。
ほんとみんな発想の鬼だなと。
とある漫画家の方がインタビューで言ってましたが、
「目についたものでネームを切ってみる練習をしている」
と。
普段から「これをゲームにしたらどうだろう?」と考えながら
生活しているような人は発想力が鍛えられているのではないでしょうか。
自分もそうありたいものです。
ゲーム紹介ページにも書きましたが、沢山の課題が残ってます。
それでも遊んでくださった方がいて凄く嬉しいです。
本当にありがとうございます。
次回はもう少しぶっ飛んだ感じのやつ作りたいなと思ってます。