前回試しにゲーム作成をしました。 今回は前回にさらに様々な改善を施し二つ目のゲームを作成したので記録に残します。
ゲーム内容
作ったゲームの内容を以下に示します。
- 舞台は宇宙。星々が輝く
- なぜか大きな隕石が降ってくる
- クリックすると弾が重力無視して高速に飛んでいく。隕石に当たると隕石破壊
- 隕石が落ちるとHP減少。HPは3
宇宙背景
skyboxで宇宙のものを選んで選択。 星空が輝いているほうにカメラを向けます。
隕石落下
BlenderのRock generatorで作成。 高めの位置にしておとすだけ。
星のような隕石がどんどん大きくなり高速で横切らせたかったので、高めの位置に。 しかしそうすると重力の影響が少ないと落ちてくる時間が結構かかります。 なのでEdit->Project Setting ->Physics ->Gravitiyの値を-98.1にするなどかなり大きい値に設定しました。
隕石の増殖
Create Object関数作成し、ランダムにX,Yを選択して配置するようにしました。 配置の間隔をあける(*50)しているのは、隕石同士が衝突してはねとんだり、隕石同士の衝突し続けで遅延が発生することを防ぐためです。
void CreateObject() { // ゲームオブジェクトを生成します。 GameObject obj = Instantiate(prefabObj, Vector3.zero, Quaternion.identity); // ゲームオブジェクトの親オブジェクトを設定します。 obj.transform.SetParent(parentTran); int x = Random.Range(-20, 20); int z = Random.Range(-20, 20); // ゲームオブジェクトの位置を設定します。 obj.transform.localPosition = new Vector3(x*50, height, z*50);
ゲームの終了
qをおすかHPが0になるとtimescaleを止めて、終了画面(pauseUI)を表に出すようにします。
if (Input.GetKeyDown("q") | ((HP-N_destroy)<=0)) { if (pauseUIInstance == null) { pauseUIInstance = GameObject.Instantiate(pauseUIPrefab) as GameObject; Time.timeScale = 0f; stop_mode = 1; pauseUI.SetActive(!pauseUI.activeSelf); }
最初にpauseUIを非表示にするのはInspectorのチェックを外すと可能です。
Update関数が呼ばれることで何か処理され続けることの対処は 以下のようにtime scaleが0ただっらとすることで停止可能でした。
void Update() { if (Mathf.Approximately(Time.timeScale, 0f)) { return; }
リトライ
リトライはボタンUIを配置して、そのon click関数でretry関数が呼ばれるようにしました。 リトライ内容はシーンを読み込むのと、timescaleなどをもとに戻しています。
public void retry() { SceneManager.LoadScene(SceneManager.GetActiveScene().name); Time.timeScale = 1f; stop_mode = 0; pauseUI.SetActive(!pauseUI.activeSelf); }
範囲外のオブジェクト削除
隕石は、カメラの下に広い平面を配置し、その当たり判定でデリートするようにしています。 合わせて、HP計算もしています。 しかしここ処理の無駄な気がするので、単純にyが0以下のときに衝突にしておけばよかったと思います。
void OnCollisionEnter(Collision other) { // 衝突した相手を闇の彼方に消し去ります。 Destroy(other.gameObject); if (other.gameObject.tag == "Rock") { N_destroy += 1; }
ball側はそれに気が付いたのでy軸座標で判定しています。
void Update() { if (transform.position.y > 5000) { Destroy(gameObject); } }
パフォーマンスについて
隕石がひたすら増え続けると重くなります。
なので隕石の数が50個以上存在しているとそれ以上作成しないようにしました。 具体的には以下Rockタグでオブジェクトをひっかけてレングス長で存在している数確認してます。
また、
GameObject[] tagObjects = GameObject.FindGameObjectsWithTag("Rock"); if((stop_mode == 0)&(tagObjects.Length<50)) { count += 1; if (count > 60) { for (int i = 0; i < (1 + N_stone / 10); i++) { CreateObject(); N_stone += 1; } count = 0; } }
スコア表示
以下のようなスクリプトをText(Legacy)にわりあてれば一応スコア表示可能でした。 課題は外部コンポーネントとのIFが増えてしまうという。。 良い方法はないのか。
void Update() { if (Time.frameCount % 60 == 0) { // オブジェクトからTextコンポーネントを取得 Text score_text = score_object.GetComponent<Text>(); int N_rock = game_object.GetComponent<PrefabInstantiateSample>().N_stone; int N_destroy = damage_object.GetComponent<ObjectEraser>().N_destroy; // テキストの表示を入れ替える score_text.text = "Score(Time):" + score_num; score_text.text += "\n"; score_text.text += "Num of Rock(Live):" + (N_rock - N_destroy); score_text.text += "\n"; score_text.text += "HP: " + (HP - N_destroy); score_num += 1; // とりあえず1加算し続けてみる }
あとがき
というわけで様々な技術を駆使ししてゲームを作成することができました。
課題はたくさんあります。
そもそもこのゲームは恐らく30秒生きるのすら難しい内容なっています。 隕石が結構離れていて、中心に向かってくるわけではないので、当てずらくなってくるんですよね。。 さらにはスマホだとクリックする指で弾が隠れて、隕石にあてずらいです。爆発も恐らく見えないでしょう。 前回と違い敵の数が増え続けるので達成感がないのも問題です。 以下のような単純な隕石以外もあるとゲーム性増すのだとも思いました。
- 二発当てないと壊れない硬そうな隕石
- パズルのような隕石
- 砕ける大きな隕石。
- 玉を打ってくるてきキャラ
また個人的に一番の課題はクラス分け、インターフェイスです。 適当につなげてスパゲッティになっていて、正直どうなっているのかという具合です。 デザインが必要と強く思いました。リファクタリングしないとです。
というわけで次回は以下に気を付けようと思います。
- 1分は遊べる内容にしよう
- 遊んでいて爽快感、達成感があるものにしよう。
- デザインを学ぼう。
- パフォーマンスも理解しよう