【Unity】2048スコアの実装

2019/10/18追記


いよいよ最終のスコア実装でっす。

スコアの実装なんて今までの処理に比べたら楽勝ρ(^^ )♭
と高を括っていたのですが…

適当に実装して、PLAYしたらとんでも無い事になりました。

移動後に残っているピースの点数を合計していったら、
あっと言う間に天文学的数字に ( ゜.゜) ポカーン

こんなスコアの入り方でしたっけ( ̄~ ̄;) ウーン

少し調べないと行けません。


Google先生にお伺いを立てたのですが、
どこにもスコア計算の情報が出ておりません。

更に調べると、どうやら結合したピースのみ得点するようです。

そら、全部のピースを合計したら天文学的数字になるはずでっす。
(~-~;)ヾ(-_-;) オイオイ

完全に舐めてました。


改めて実装していきます。

エディターでテキストのオブジェクトを設置から
2048㊱.jpg
もはや適当に作ってますが、こんな感じで良いでしょう。

このテキストをスクリプトから操作すれば無問題。

スクリプトを修正していきます。
GameManager

using UnityEngine.UI;

public class GameManager : MonoBehaviour
{
private int SCORE=0; //スコア
private int HI_SCORE = 0;             //ハイスコア
private int[] ScorList = { 2, 4, 8, 16, 32,
64, 128, 256, 512,
1024, 2048 }; //各ピースの点数

public GameObject SCOREText; //スコア表示用オブジェクト
public GameObject HI_SCOREText; //ハイスコア表示用オブジェクト
private Text scoreText; //スコアテキストコンポーネント取得
private Text HiscoreText; //ハイスコアテキストコンポーネント取得

各種変数を追加します。
テキストオブジェクトの編集をするので名前空間に
using UnityEngine.UI;を呼び出します。

続いて、Startでテキストコンポーネントを取得しておきます。

// Start is called before the first frame update
void Start()
{
//スコア系のテキストを取得してキャッシュする
scoreText = SCOREText.GetComponent<Text>();
HiScoreText = HI_SCOREText.GetComponent<Text>();

//初回ピース生成
SelectPosition();

//ピース生成のウェイト計算
waittime = width / PieceSpeed * fixedtime+proctime;
}

テキストの準備ができたのでスコアの計算を用意します。

スコアの加算はピース結合して出来たものが得点になるので、
CollisionEnterから生成したピースを加算しないといけないのですが、

SelectPosition()メソッドからもピース生成を行うので、
切り分けが必要です。

//ピースが結合した時の処理
public void CreatePiece(int x, int y, int p, bool c) ←ここに追加
{
GameObject Piece = (GameObject)Instantiate(PiecePrefab[p]);
Piece.transform.SetParent(PieceParent.transform, false);
Piece.transform.localPosition = new Vector3(PosNo[x], PosNo[Mathf.Abs(y - 3)], 0);
}

なので引数を増やします。
bool型変数cを引数に追加して識別します。

これに伴ってSelectPosition()と
OnCollisionEnter2Dの方も修正します。
PieceManager

//コラインダーが何かに衝突
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.tag == "No" + PieceNo)
{
Destroy(this.gameObject);
if (Move == false)
{
gameManager.CreatePiece(LineX, LineY, PieceNo,true); ←結合側はtrue
}
}
}

OnCollisionEnter2Dはtrueにします。
GameManager

//空きマスからランダムに2カ所を選ぶ
public void SelectPosition()
{


//ピースをnum回生成する
while (rot < num)
{
int pos = list.GetAndRemoveAtRandom();
int x = pos % frame;
int y = pos / frame;

CreatePiece(x, y, 0,false); ←移動後の生成はfalse
rot++;
}

メソッドの中のwhile (rot < num)で
CreatePiece()を呼び出しているので、こちらはfalseにしておきます。

切り分けが出来たのでCreatePiece()を修正します。

//ピースが結合した時の処理
public void CreatePiece(int x, int y, int p, bool c)
{
//ピース結合していれば加算する
if (c)
{
SCORE += ScorList[p]; ←pはピースナンバーです
}

GameObject Piece = (GameObject)Instantiate(PiecePrefab[p]);
Piece.transform.SetParent(PieceParent.transform, false);
Piece.transform.localPosition = new Vector3(PosNo[x], PosNo[Mathf.Abs(y - 3)], 0);
}

引数にしているピースナンバーを、スコア用の配列ScorList[]に代入
これで点数がわかるので、変数SCOREに加算します。

これで、スコアの計算が出来るようになりました。
あとは表示のタイミングです。

ピース移動が完了して、生成後ぐらいがいいかと思うので、

//空きマスからランダムに2カ所を選ぶ
public void SelectPosition()
{
List<int> list = new List<int>(); //空きマスを取得する変数
int num = 2; //ピースの生成回数
int rot = 0; //ピースの生成カウント
int limit=0; //空マスのカウント

for(int i = 0; i < 16; i++)
{
if (Flag[i] == false)
{
list.Add(i);
limit++;
}
}

//空きマスが一つならnumを修正する
if (list.Count == 1)
{
num = list.Count;
}

//ピースをnum回生成する
while (rot < num)
{
int pos = list.GetAndRemoveAtRandom();
int x = pos % frame;
int y = pos / frame;

CreatePiece(x, y, 0,false);
rot++;
}

//スコアの表示
scoreText.text = "SCORE:"+SCORE.ToString();      ←ここで表示
HiscoreText.text = "HI-SCORE:" + HI_SCORE.ToString();

//空マスが2つ以下なら移動できるか判定する
if (limit<=2)
{
StartCoroutine("waitJudge");
}
create = false;
}

ハイスコアの表示もできるようにしておきます。

スコアの処理は、これで問題ないと思います。
続いて、ハイスコアの処理を作成します。

ハイスコアは保存が必要になるので、PlayerPrefsを使います。
データの読込みはStartで行い、
セーブはゲームオーバー時に行います。

読込み側

// Start is called before the first frame update
void Start()
{
//スコア系のテキストを取得してキャッシュする
scoreText = SCOREText.GetComponent();
HiscoreText = HI_SCOREText.GetComponent();

//保存されたハイスコアを呼び出す
HI_SCORE = PlayerPrefs.GetInt("HI_SCORE", 0); ←ここで読み込む
HiscoreText.text = HI_SCORE.ToString();    

//初回ピース生成
SelectPosition();

//ピース生成のウェイト計算
waittime = width / PieceSpeed * fixedtime+proctime;
}

PlayerPrefsは、呼び出し時にセーブ用のキーを設定するので、
キーの名前で呼び出し、変数に代入します。
設定したキーに値が無い場合、エラーが掛からないように0を設定します。
PlayerPrefs.GetInt("HI_SCORE", 0)
今回は”HI_SCORE”をキーにしています。
HI_SCOREで呼び出して、なければ0を代入となります。

キーが存在するとキー内のデータを呼び出してくれます。

これでハイスコアが読み込めるので、
ハイスコアtextを更新しておきます。

保存側

//ピースの移動が出来るか判定する
void EndJudge()
{
//ピースナンバー仮取得する変数XY
int tempX=0;
int tempY=0;

for(int i = 0; i < 4; i++)
{
for(int a = 0; a < 4; a++)
{
}
tempX = 0;
tempY = 0;
}
//ゲームオーバーなら呼び出す
EndTxet.SetActive(true);

//ハイスコアを保存する
PlayerPrefs.SetInt("HI_SCORE", HI_SCORE); ←ここで保存
}

ループ内の処理が多いので端折ります。
ゲームオーバーの後にキーを"HI_SCORE"にして保存すれば問題ないかと。

同じ文字を使ってるので、分かりにくいのですが、
前の"HI_SCORE"がキーで、後ろが変数HI_SCOREでっす。

これで、ハイスコアの保存・読込みができたので、
PLAY中の更新処理を作成します。


//ピースが結合した時の処理
public void CreatePiece(int x, int y, int p, bool c)
{
//ピース結合していれば加算する
if (c)
{
SCORE += ScorList[p];

//ハイスコアが更新されたらスコアを代入する
if (HI_SCORE <= SCORE)
{
HI_SCORE = SCORE;
}
}

GameObject Piece = (GameObject)Instantiate(PiecePrefab[p]);
Piece.transform.SetParent(PieceParent.transform, false);
Piece.transform.localPosition = new Vector3(PosNo[x], PosNo[Mathf.Abs(y - 3)], 0);
}

単純です。上回れば更新ですね。

あとは、TextオブジェクトをGameManagerにアタッチすればOKです。

早速PLAYしてみます。

ハイスコアが最初から入っていますが、
更新されたのが分かりやすいように前回データを残しておきました。

ハイスコアが更新された所からプレイ中のスコアがハイスコアに
なって行くようすが分かります。

ゲームオーバーまでPLAYすれば、ハイスコアも自動的に保存されます。


長々と2048の記事を書いてきましたが、
一応、完成までたどり着けました。

ただ、作ったコードを振り返ると
・無駄が多い
・分かりにくい

このあたりは、今後の課題ですね。
更に勉強しないと…

アセットストアに公開されている2048のモデルがあるのですが、
スクリプトを見てもチンプンカンプンでした。

でも、今回チャレンジして何となくですが、
モデルのスクリプトが理解できた気がします。

無駄の多い部分は、モデルと比較すると
勉強になりそうですね~

これからも精進、精進

新しいネタができたらブログを更新します。
お楽しみにして下さいませ。

それでは(o・・o)/~マタネェ



追記
Start時の注意点があったのを書き忘れてました。

TextをGetComponentしていますが、ピース生成前にしています。
これは、ピースの生成が先に来るとピース側のStartメソッドが働く為、
Textを取得できなくなります。

Textの取得が出来ないので、ハイスコアの表示もエラーが出ます。
なので、ピース生成より先に呼び出しています。


ちなみに、TextのGetComponentなどを優先したい場合は、
Awake()メソッドを使うのも一つです。

Awake()とStart()は、共に起動時に呼び出されるメソッドなのですが、
Awake()の方が少しだけ先に呼び出されるそうです。

なので、複数のスクリプトを同時に動かす時は、先に読み込ませたい
物などをAwake()で起動しておくと良いかと思います。

詳しく知りたい場合は、GoogleでStartとAwakeの違いで検索すると
情報が出てくると思います。

この記事へのコメント