※画像クリックでリンクが開きます
Unityの教科書 Unity 2018完全対応版 2D&3Dスマートフォンゲーム入門講座
#はじめに
上記書籍4章の、 車をゴールのギリギリ手前で止める「寸止めゲーム」
のサンプルを作ってみました。
こんな感じです。
いざ作ってプレイしてみると、ゲームとして以下の欠点があることに気付きました。
(サンプルなので当たり前なのですが、、)
###欠点?
1.一見何をすれば良いのかわからない
2.スワイプで車が動くが、何度でもスワイプが有効
3.ゴールしてもしなくてもゲームとしての終わりが無い
もう少しゲームっぽくしたいなと思い、このサンプルに以下の仕様を追加したいと思います。
###改修!
1.ゲーム開始時にゲームの目的を提示する
2.スワイプは1回のみの一発勝負
3.プレイを評価して終わりを表現する
初心者ですので、サンプルのコードをベースに仕様を追加します。
「良いやり方がある」や「間違ってる」等御座いましたら、コメントを頂ければ幸いです。
#実践
##改修後に想定するゲーム仕様
※各画像ファイルは自作画像に差し替えてます
ゲーム仕様
・スワイプで車を動かし、ゴールのギリギリ手前で止める「寸止めゲーム」
→ 車が動かせるのは1ゲームで1回 ※仕様追加1
→ 車がゴールを越えてしまったらゲームオーバーを表示し、自動的にリトライ ※仕様追加2
→ 車がゴールから遠ざかった場合は自動的にリトライ ※仕様追加3
→ いつでもリトライ出来るボタンを設置 ※仕様追加4
・画面中央のテキストで、ゲーム進行を表現する
→ ゲーム開始時の案内 ※仕様追加5
→ ゴールまでの距離
→ 停止位置に応じて評価する ※仕様追加6
##スクリプトを改修する
このゲームは下記スクリプトが実装されており、これらに仕様を追加する。
1.CarController
:スワイプによる車の動作に関するスクリプト
2.GameDirector
:テキストの表示などUIに関するスクリプト
##CarController
###CarControllerで追加する仕様
→ 車が動かせるのは1ゲームで1回 ※仕様追加1
CarController
は車の動作に関するコードなので、動作したかどうかの判定のみ実装。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CarController : MonoBehaviour {
float speed = 0;
Vector2 startPos;
void Start() {
}
void Update() {
// スワイプの長さを求める
if(Input.GetMouseButtonDown(0)) {
// クリックした座標
this.startPos = Input.mousePosition;
} else if(Input.GetMouseButtonUp(0)) {
// 離した座標
Vector2 endPos = Input.mousePosition;
float swipeLength = endPos.x - this.startPos.x;
// スワイプの長さを初速度に変換する
this.speed = swipeLength / 500.0f;
}
transform.Translate(this.speed, 0, 0);
this.speed *= 0.98f;
}
}
改修↓
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CarController : MonoBehaviour {
bool movedFlg;
float speed;
Vector2 startPos;
// Use this for initialization
void Start(){
}
// Update is called once per frame
void Update()
{
// ポイント1_1
// 1度目のスワイプのみ有効
if (this.movedFlg == false)
{
// スワイプの長さを求める
if (Input.GetMouseButtonDown(0))
{
// クリックした座標
this.startPos = Input.mousePosition;
}
else if (Input.GetMouseButtonUp(0))
{
// 離した座標
Vector2 endPos = Input.mousePosition;
// ポイント2
// タップの場合は動作させない
float swipeLength = endPos.x - this.startPos.x;
if (swipeLength > 0 || swipeLength < 0)
{
// 初速度設定
this.speed = swipeLength / 500.0f;
// ポイント1_2
this.movedFlg = true;
}
}
}
// 移動速度設定
transform.Translate(this.speed, 0, 0);
this.speed *= 0.98f;
}
}
###ポイント1
・ポイント1_1 boolのmovedFlg
を設け1度のみ操作可能にする。 →仕様追加1
// ポイント1_1
// 1度目のスワイプのみ有効
if (this.movedFlg == false)
・ポイント1_2 初速度が決まり、車が動いたタイミングでtrueに
// 初速度設定
this.speed = swipeLength / 500.0f;
// ポイント1_2
this.movedFlg = true;
###ポイント2
タップではmovedFlg
がtrueにならないよう、スワイプしたかどうかの判定を追加。
// ポイント2
// タップの場合は動作させない
float swipeLength = endPos.x - this.startPos.x;
if (swipeLength > 0 || swipeLength < 0)
##GameDirector
###GameDirectorで追加する仕様
→ 車がゴールを越えてしまったらゲームオーバーを表示し、自動的にリトライ ※仕様追加2
→ 車がゴールから遠ざかった場合は自動的にリトライ ※仕様追加3
→ ゲーム開始時の案内 ※仕様追加5
→ 停止位置に応じて評価する ※仕様追加6
GameDirector
はゲームの監督役なので、ゲームの進行を全て一任する。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GameDirector : MonoBehaviour {
GameObject car;
GameObject flag;
GameObject distance;
void Start() {
this.car = GameObject.Find("car");
this.flag = GameObject.Find("flag");
this.distance = GameObject.Find("Distance");
}
void Update() {
float length = this.flag.transform.position.x - this.car.transform.position.x;
this.distance.GetComponent<Text>().text = "ゴールまで" + length.ToString("F2") + "m";
}
}
改修↓
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class GameDirector : MonoBehaviour
{
GameObject car;
GameObject flag;
GameObject distance;
string sceneName;
float defaultLength;
float length;
bool finishFlg;
// Use this for initialization
void Start()
{
this.car = GameObject.Find("car");
this.flag = GameObject.Find("flag");
this.distance = GameObject.Find("Distance");
// ポイント1_1
this.sceneName = SceneManager.GetActiveScene().name;
// ポイント2_1
this.defaultLength = this.flag.transform.position.x - this.car.transform.position.x - 1.53f;
}
// Update is called once per frame
internal void Update()
{
this.length = this.flag.transform.position.x - this.car.transform.position.x - 1.53f;
if (this.finishFlg == false)
{
// ポイント2_2
// 車が停止中のテキスト表示
if (this.length.Equals(this.defaultLength))
{
this.distance.GetComponent<Text>().text = "スワイプでゴールを目指せ!\nゴールを越えたらゲームオーバー";
}
// ポイント2_3
// 逆走したらリトライ
else if (this.length > this.defaultLength)
{
// ポイント1_3
Invoke("Retry", 1);
}
// 車が動作中のテキスト表示
else if (this.length >= 0)
{
this.distance.GetComponent<Text>().text = "ゴールまで" + this.length.ToString("F2") + "m";
// ポイント3_2
Invoke("GetScore", 10);
}
// ポイント2_4
// ゴールを超えたらゲームオーバー
else
{
this.distance.GetComponent<Text>().text = "ゲームオーバー";
// ポイント1_3
Invoke("Retry", 1);
}
}
}
// ポイント3_1
void GetScore()
{
this.finishFlg = true;
if (this.length < 0.5)
{
this.distance.GetComponent<Text>().text = "perfect!!!";
}
else if (this.length < 1)
{
this.distance.GetComponent<Text>().text = "great!!";
}
else if (this.length < 1.5)
{
this.distance.GetComponent<Text>().text = "good!";
}
else
{
this.distance.GetComponent<Text>().text = "too bad...";
}
// ポイント1_3
Invoke("Retry", 2);
}
// ポイント1_2
public void Retry()
{
SceneManager.LoadScene(this.sceneName);
}
}
###ポイント1 リトライの実装
・ポイント1_1 ゲーム開始時のシーンを取得しておく。
※using UnityEngine.SceneManagement;
を宣言する必要有り
// ポイント1_1
this.sceneName = SceneManager.GetActiveScene().name;
・ポイント1_2 ポイント1_1
で取得したゲーム開始時のシーンを実行させるメソッドを作成する。
※後述するRetryボタン(仕様追加4)からも呼び出したいのでメソッドはpublicに
// ポイント1_2
public void Retry(){
SceneManager.LoadScene(this.sceneName);
}
・ポイント1_3 リトライさせたい時に引数に指定した秒数後にポイント1_2
で作成したメソッドを実行させるように実装。
// ポイント1_3
Invoke("Retry", 1);
###ポイント2 テキストUIを変化させる
・ポイント2_1 ゲーム開始時の車とゴールの位置を取得しておく。
// ポイント2_1
this.defaultLength = this.flag.transform.position.x - this.car.transform.position.x -1.53f;
・ポイント2_2 車とゴールの距離が初期値から変わっていない場合にゲーム開始時の案内を表示する。 →仕様追加5
// ポイント2_2
// 車が停止中のテキスト表示
if (this.length.Equals(this.defaultLength))
{
this.distance.GetComponent<Text>().text = "スワイプでゴールを目指せ!\nゴールを越えたらゲームオーバー";
}
・ポイント2_3 車とゴールの距離が離れたら(車がゴールから遠ざかった場合は)1秒後にリトライ。 →仕様追加3
// ポイント2_3
// 逆走したらリトライ
else if (this.length > this.defaultLength)
{
// ポイント1_3
Invoke("Retry", 1);
}
・ポイント2_4 車とゴールの位置関係がマイナスの値になったらゲームオーバーを表示し、1秒後にリトライ。 →仕様追加2
// ポイント2_4
// ゴールを超えたらゲームオーバー
else
{
this.distance.GetComponent<Text>().text = "ゲームオーバー";
// ポイント1_3
Invoke("Retry", 1);
}
###ポイント3 評価する
・ポイント3_1 距離に応じて評価するメソッドを作成する。1秒後にリトライ。
// ポイント3_1
void GetScore()
{
this.finishFlg = true;
if (this.length < 0.5)
{
this.distance.GetComponent<Text>().text = "perfect!!!";
}
else if (this.length < 1)
{
this.distance.GetComponent<Text>().text = "great!!";
}
else if (this.length < 1.5)
{
this.distance.GetComponent<Text>().text = "good!";
}
else
{
this.distance.GetComponent<Text>().text = "too bad...";
}
// ポイント1_3
Invoke("Retry", 2);
}
・ポイント3_2 車が動作して10秒後にポイント3_1
で作成したメソッドを実行させるように実装。 →仕様追加6
// 車が動作中のテキスト表示
else if (this.length >= 0)
{
this.distance.GetComponent<Text>().text = "ゴールまで" + this.length.ToString("F2") + "m";
// ポイント3_2
Invoke("GetScore", 10);
}
##Retryボタンを設置する
→ いつでもリトライ出来るボタンを設置 ※仕様追加4
###配置位置を決める
・下記キーを押下しながら指定すると、その位置を基準に再計算してくれる。
→ポジションやサイズはこれを行ってから微修正する
mac:option + shift
win:Alt + Shift
###スクリプトを割り当てる
・クリック時にGameDirector
のRetry
を呼び出すように指定。 →仕様追加4
※呼び出すメソッドはpublicにしておく必要あり
#所感
// ポイント3_2
Invoke("GetScore", 10);
評価メソッドGetScore()
を呼び出す際、Invoke()
を用いて秒数指定しておりますが、ここは車が止まっているかどうかで判断したかったです。
Rigidbody
を使うとIsSleeping()
を使ってオブジェクトが動いているかどうかを判定できるようなので色々試しましたが、
今回はオブジェクトの操作を物理特性ではなく計算式で設定しているので、実現できませんでした。
PC上のテストプレイとスマートフォンの動作に乖離があったり躓くこともありますが、まだまだスタート地点なので勉強を続けていきたいと思います。