Unity で テトリス風ゲームを作ってみる(ソースコードとか)
まとめ
ここまでで作成したソースコード、Unity Project の構造などの情報を掲載。
GameControl.cs
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- using Cysharp.Threading.Tasks;
- using DG.Tweening;
- public class GameControl : MonoBehaviour, Field.IFieldEventListener, LevelScore.ILevelScoreEventListener {
- private enum State {
- Title,
- Droping,
- GameOver,
- }
- [SerializeField] Camera _camera;
- [SerializeField] GameObject _gameTitlePanel;
- [SerializeField] GameObject _gamePlayPanel;
- [SerializeField] GameObject _gameOverPanel;
- [SerializeField] Field _field;
- [SerializeField] NextArea _nextArea;
- private LevelScore _levelScore;
- private bool _gotFirstFrame = false;
- private bool _readyTetrimino = false;
- private State _state = State.Title;
- // IFieldEventListener Implementation
- public void OnDeletedLine(int lines) {
- if (lines > 0) {
- _levelScore.AddScore(lines);
- }
- _readyTetrimino = true;
- }
- // ILevelScoreEventListener Implementation
- public void OnLevelUp (int level) {
- _field.SetDropSpeed(level);
- }
- // Button Event Callback
- public void OnStartClick () {
- _gameTitlePanel.SetActive(false);
- _gamePlayPanel.SetActive(true);
- _gameOverPanel.SetActive(false);
- GameStart();
- }
- public void OnRestartClick () {
- _levelScore.Reset();
- _field.DestroyAllBlocks();
- _nextArea.DestroyNextTetrimino();
- PrepareNextTetrimino();
- _gamePlayPanel.SetActive(true);
- _gameOverPanel.SetActive(false);
- _state = State.Droping;
- }
- private void Init () {
- UnityEngine.Random.InitState( (int) DateTime.Now.Ticks);
- DOTween.Init();
- CameraSetting();
- _state = State.Title;
- }
- private void GameStart () {
- _field?.Init(this);
- _nextArea?.Init();
- CreateLevelScore();
- PrepareNextTetrimino();
- _state = State.Droping;
- }
- private void PrepareNextTetrimino() {
- _nextArea.CreateNextTetrimino();
- PushTetrimino2Field();
- _nextArea.CreateNextTetrimino();
- _readyTetrimino = false;
- }
- private void PushTetrimino2Field () {
- var tetrimino = _nextArea.GetNextTetrimino();
- var success = _field.PushTetrimino(tetrimino);
- if (!success) {
- _state = State.GameOver;
- WaitGameOver();
- }
- }
- private void CameraSetting () {
- _camera.orthographicSize = ScreenConfig.Instance.OrthographicSize;
- // change camera position so that left-bottom is (0,0).
- _camera.transform.localPosition = new Vector3 (
- ScreenConfig.Instance.OrthographicSize * ScreenConfig.Instance.ScreenAspect,
- ScreenConfig.Instance.OrthographicSize,
- -10);
- }
- private void CreateLevelScore () {
- _levelScore = new LevelScore();
- _levelScore.Init(this);
- }
- private void WaitGameOver () {
- DOVirtual.DelayedCall(2, () => {
- _gameOverPanel.SetActive(true);
- });
- }
- // Update is called once per frame
- void Update() {
- if (!_gotFirstFrame) {
- // Initialize once after first frame.
- Init();
- _gotFirstFrame = true;
- }
- if (_readyTetrimino && _state != State.GameOver) {
- _readyTetrimino = false;
- PushTetrimino2Field();
- _nextArea.CreateNextTetrimino();
- }
- }
- }
Field.cs
- using System;
- using System.Linq;
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- using UnityEngine.InputSystem;
- public class Field : MonoBehaviour, Tetrimino.ILockedEventListner {
- public interface IFieldEventListener {
- void OnDeletedLine (int lines);
- }
- private IFieldEventListener _fieldEventListener;
- private List<GameObject> _fieldBlocks = new List<GameObject>();
- private List<GameObject> _blocks = new List<GameObject>();
- private Tetrimino _tetrimino;
- // ILockedEventListner Implementation
- public void OnLocked() {
- var lines = CheckAndDeleteLines();
- AddBlocks();
- _fieldEventListener?.OnDeletedLine(lines);
- }
- public void Init(IFieldEventListener listener) {
- this.transform.localPosition = new Vector3(
- ScreenConfig.Instance.FieldOffset.x,
- ScreenConfig.Instance.FieldOffset.y,
- 0);
- // Create Field Area
- // Bottom Wall
- for (int x = 0; x < (ScreenConfig.FIELD_WIDTH+ScreenConfig.FIELD_SIDE_WIDTH*2); x++) {
- CreateFieldBlock(new Vector2Int(x, 0));
- }
- // Left and Right Wall
- for (int y = 0; y < (ScreenConfig.FIELD_HEIGHT+ScreenConfig.FIELD_BOTTOM_WIDTH); y++) {
- CreateFieldBlock(new Vector2Int(0, y));
- CreateFieldBlock(new Vector2Int(ScreenConfig.FIELD_WIDTH+ScreenConfig.FIELD_SIDE_WIDTH, y));
- }
- _fieldEventListener = listener;
- }
- public bool PushTetrimino (Tetrimino tetrimino) {
- _tetrimino = tetrimino;
- _tetrimino.SetListner (this);
- _tetrimino.transform.SetParent(this.transform);
- _tetrimino.transform.localPosition = new Vector3(
- ScreenConfig.START_POS_X + ScreenConfig.FIELD_SIDE_WIDTH,
- ScreenConfig.START_POS_Y + ScreenConfig.FIELD_BOTTOM_WIDTH,
- 0);
- if (!_tetrimino.StartDrop()) {
- // GameOver
- return false;
- }
- return true;
- }
- public void SetDropSpeed (int level) {
- _tetrimino?.SetDropSpeed(level);
- }
- // Add Blocks of Tetrimino to Field.
- public void AddBlocks() {
- var blocks = _tetrimino?.RemoveBlocks();
- foreach (var block in blocks) {
- block.transform.SetParent(this.transform);
- block.layer = ScreenConfig.FIELD_LAYER;
- }
- _blocks.AddRange(blocks);
- DestroyTetrimino();
- }
- // Destroy All Block Objects include Field blocks.
- public void DestroyAllBlocks () {
- DestroyTetrimino();
- foreach (var block in _blocks) {
- Destroy(block);
- }
- _blocks.Clear();
- }
- // Destroy Block Objects on Field.
- public void DestroyBlocks (List<GameObject> blocks) {
- foreach (var block in blocks) {
- _blocks.Remove(block);
- Destroy(block);
- }
- }
- private void DestroyTetrimino () {
- _tetrimino.DestroyBlocks();
- Destroy(_tetrimino.gameObject);
- }
- // Create Field Block Object from Prefab.
- private void CreateFieldBlock (Vector2Int pos) {
- var prefab = Resources.Load("Prefabs/Block") as GameObject;
- var copyObject = Instantiate(prefab, this.transform);
- copyObject.transform.localPosition = new Vector3(pos.x, pos.y, 0);
- copyObject.layer = ScreenConfig.FIELD_LAYER;
- _fieldBlocks.Add(copyObject);
- }
- // Check Line whether it is filled or not at Tetrimino level.
- private int CheckAndDeleteLines () {
- var pos = _tetrimino.GetBlocksPosition();
- var max = pos.Select(p => p.y).Max();
- var min = pos.Select(p => p.y).Min();
- var deletedLineCount = 0;
- for (float y = min; y <= max; y++) {
- var deleteBlocks = CheckLine(y);
- if (deleteBlocks != null) {
- DestroyBlocks(deleteBlocks);
- deletedLineCount++;
- DropLines(y);
- }
- }
- return deletedLineCount;
- }
- private List<GameObject> CheckLine (float y) {
- var detectedBlocks = DetectBlocks(y);
- if (detectedBlocks != null && detectedBlocks.Count() >= ScreenConfig.FIELD_WIDTH) {
- return detectedBlocks;
- }
- return null;
- }
- private List<GameObject> DetectBlocks (float y) {
- ContactFilter2D contactFilter = new ContactFilter2D();
- var start = new Vector2(
- ScreenConfig.FIELD_SIDE_WIDTH + ScreenConfig.Instance.FieldOffset.x + 0.5f,
- y + 0.5f
- );
- var length = ScreenConfig.FIELD_WIDTH - 1f;
- var hitResult = new List<RaycastHit2D>();
- var hitCount = Physics2D.Raycast(
- start, // レイの原点
- Vector2.right, // レイの方向
- contactFilter.NoFilter(), // チェックする対象を設定するフィルター
- hitResult, // ヒットしたオブジェクトの情報
- length // レイの長さ
- );
- //Debug.DrawRay(start, Vector2.right * length, Color.red, 2f); // Debug用
- if (hitCount > 0) {
- var detectedBlocks = new List<GameObject>();
- foreach (var hit in hitResult) {
- detectedBlocks.Add(hit.collider.gameObject);
- }
- return detectedBlocks;
- }
- return null;
- }
- private void DropLines (float deletedLineYPos) {
- for (float y = deletedLineYPos+1; y < ScreenConfig.FIELD_HEIGHT; y++) {
- var detectedBlocks = DetectBlocks(y);
- if (detectedBlocks != null) {
- DropLine(detectedBlocks);
- }
- }
- }
- private void DropLine (List<GameObject> blocks) {
- foreach (var block in blocks) {
- block.transform.position += Vector3.down; // down is -1
- }
- }
- }
NextArea.cs
- using System;
- using System.Linq;
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- public class NextArea : MonoBehaviour {
- // Next Area Position and Size
- private const int POSITION_X = 5;
- private const int POSITION_Y = 15;
- private const float WIDTH = 4f;
- private const float HEIGHT = 4f;
- // Tetrimino Object
- private Tetrimino _next;
- public void Init () {
- _next = null;
- this.transform.localPosition = new Vector3(POSITION_X, POSITION_Y, 0f);
- }
- public void CreateNextTetrimino () {
- _next = CreateTetriminoByRandom();
- }
- public Tetrimino GetNextTetrimino () {
- return _next;
- }
- public void DestroyNextTetrimino () {
- if (_next != null) {
- _next.DestroyBlocks();
- Destroy(_next.gameObject);
- _next = null;
- }
- }
- private Tetrimino CreateTetriminoByRandom () {
- var prefab = Resources.Load("Prefabs/Tetrimino") as GameObject;
- var copyObject = Instantiate(prefab, this.transform);
- var next = copyObject.GetComponent<Tetrimino>();
- var type = (Tetrimino.Type)UnityEngine.Random.Range(0, (int)Tetrimino.Type.MAX);
- next.Init(type);
- // set position in Center of NextArea
- var width = next.GetWidth();
- var height = next.GetHeight();
- next.transform.localPosition = new Vector3(
- (WIDTH - width) * 0.5f,
- (HEIGHT - height) * 0.5f,
- 0);
- return next;
- }
- }
Tetrimino.cs
- using System;
- using System.Linq;
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- using UnityEngine.InputSystem;
- using UnityEngine.U2D;
- public class Tetrimino : MonoBehaviour {
- public interface ILockedEventListner {
- void OnLocked ();
- }
- private ILockedEventListner _lockedEventListner;
- public enum State {
- Idle,
- Drop,
- HighSpeedDrop,
- Lock,
- MAX
- };
- public enum Rotation {
- R0 = 0,
- R90,
- R180,
- R270,
- MAX
- };
- public enum Type {
- I = 0,
- L,
- J,
- T,
- S,
- Z,
- O,
- MAX
- };
- private readonly int [,,] TETRIMINO = new int [,,] {
- new int[,,] { // I
- {
- {0,0}, {1,0}, {2,0}, {3,0} // 0
- },{
- {1,0}, {1,1}, {1,2}, {1,3} // 90
- },{
- {0,0}, {1,0}, {2,0}, {3,0} // 180
- },{
- {1,0}, {1,1}, {1,2}, {1,3} // 270
- }
- },
- new int[,,] { // L
- {
- {0,0}, {1,0}, {2,0}, {2,1} // 0
- },{
- {1,0}, {1,1}, {1,2}, {0,2} // 90
- },{
- {0,0}, {0,1}, {1,1}, {2,1} // 180
- },{
- {0,0}, {1,0}, {0,1}, {0,2} // 270
- }
- },
- new int[,,] { // J
- {
- {0,0}, {1,0}, {2,0}, {0,1} // 0
- },{
- {0,0}, {1,0}, {1,1}, {1,2} // 90
- },{
- {0,1}, {1,1}, {2,1}, {2,0} // 180
- },{
- {0,0}, {0,1}, {0,2}, {1,2} // 270
- }
- },
- new int[,,] {
- // T
- {
- {0,0}, {1,0}, {2,0}, {1,1} // 0
- },{
- {1,0}, {0,1}, {1,1}, {1,2} // 90
- },{
- {1,0}, {0,1}, {1,1}, {2,1} // 180
- },{
- {0,0}, {0,1}, {0,2}, {1,1} // 270
- }
- },
- new int[,,] {
- // S
- {
- {0,0}, {1,0}, {1,1}, {2,1} // 0
- },{
- {1,0}, {0,1}, {1,1}, {0,2} // 90
- },{
- {0,0}, {1,0}, {1,1}, {2,1} // 180
- },{
- {1,0}, {0,1}, {1,1}, {0,2} // 270
- }
- },
- new int[,,] {
- // Z
- {
- {1,0}, {2,0}, {0,1}, {1,1} // 0
- },{
- {0,0}, {0,1}, {1,1}, {1,2} // 90
- },{
- {1,0}, {2,0}, {0,1}, {1,1} // 180
- },{
- {0,0}, {0,1}, {1,1}, {1,2} // 270
- }
- },
- new int[,,] {
- // O
- {
- {0,0}, {1,0}, {0,1}, {1,1} // 0
- },{
- {0,0}, {1,0}, {0,1}, {1,1} // 90
- },{
- {0,0}, {1,0}, {0,1}, {1,1} // 180
- },{
- {0,0}, {1,0}, {0,1}, {1,1} // 270
- }
- }
- };
- private readonly Color TETRIMINO_COLOR = new Color {
- new Color(0.0f, 1.0f, 1.0f), // I
- new Color(1.0f, 0.5f, 0.0f), // L
- new Color(0.0f, 0.0f, 1.0f), // J
- new Color(1.0f, 0.0f, 1.0f), // T
- new Color(0.0f, 1.0f, 0.0f), // S
- new Color(1.0f, 1.0f, 0.0f), // Z
- new Color(1.0f, 0.0f, 0.0f) // O
- };
- private const float HIGH_SPEED_RATIO = 0.05f; // 20 times of normal speed
- private const float DROP_LEVEL_RATIO = 0.95f; // 5% speed up per level
- [SerializeField] private List<GameObject> _blockPrefabs;
- private Type _type;
- private Rotation _rotation;
- private float _totalDeltaTime;
- private ContactFilter2D _contactFilter;
- private float _fallTime;
- private float _lockTime;
- private State _state;
- public void Init (Type type, ILockedEventListner lockedEventListner) {
- this.Init(type);
- _lockedEventListner = lockedEventListner;
- }
- public void Init (Type type) {
- _type = type;
- _rotation = Rotation.R0;
- LocateBlocks ();
- SetColor();
- _totalDeltaTime = 0f;
- _fallTime = 1.0f;
- _lockTime = 0.5f;
- _state = State.Idle;
- _contactFilter = new ContactFilter2D();
- _contactFilter.layerMask = LayerMask.GetMask("Field");
- _contactFilter.useLayerMask = true;
- }
- public void DestroyBlocks () {
- foreach (var block in _blockPrefabs) {
- Destroy(block);
- }
- _blockPrefabs.Clear();
- }
- public void SetListner (ILockedEventListner lockedEventListner) {
- _lockedEventListner = lockedEventListner;
- }
- public bool StartDrop () {
- var isCollision = false;
- foreach (var block in _blockPrefabs) {
- if (CollisionCheck(block.transform.position + new Vector3(0.5f, 0.5f, 0))) {
- isCollision = true;
- break;
- }
- }
- if (isCollision) {
- return false;
- }
- _state = State.Drop;
- return true;
- }
- public void SetDropSpeed (int level) {
- var falltime = 1.0f;
- for (var i = 0; i < level; i++) {
- falltime = falltime * DROP_LEVEL_RATIO;
- }
- _fallTime = falltime;
- }
- public void Rotate (int degree) {
- var rotation = RotateAngle(_rotation, degree);
- var positions = BlocksPosition(_type, rotation);
- foreach (var pos in positions)
- if (CollisionCheck(this.transform.position + new Vector3(pos.x, pos.y, 0))) return;
- _rotation = rotation;
- LocateBlocks();
- }
- public List<GameObject> GetBlocks() {
- return _blockPrefabs;
- }
- public List<GameObject> RemoveBlocks() {
- var blocks = new List<GameObject>();
- blocks.AddRange(_blockPrefabs);
- _blockPrefabs.Clear();
- return blocks;
- }
- public float GetWidth() {
- var pos = GetBlocksPosition();
- var max = pos.Select(p => p.x).Max();
- var min = pos.Select(p => p.x).Min();
- return max - min +1;
- }
- public float GetHeight() {
- var pos = GetBlocksPosition();
- var max = pos.Select(p => p.y).Max();
- var min = pos.Select(p => p.y).Min();
- return max - min +1;
- }
- public List<Vector2> GetBlocksPosition () {
- var pos = new List<Vector2>();
- foreach (var block in _blockPrefabs) {
- pos.Add(block.transform.position);
- }
- return pos;
- }
- public void OnMoveLeftEvent (InputAction.CallbackContext context) {
- if (!IsOperatable() || context.phase != InputActionPhase.Started)
- return;
- OnMoveHorizontal(Vector2.left);
- }
- public void OnMoveRightEvent (InputAction.CallbackContext context) {
- if (!IsOperatable() || context.phase != InputActionPhase.Started)
- return;
- OnMoveHorizontal(Vector2.right);
- }
- private bool OnMoveHorizontal (Vector2 direct) {
- var isCollision = false;
- foreach (var block in _blockPrefabs) {
- if (CollisionCheck(block.transform, direct)) {
- isCollision = true;
- break;
- }
- }
- if (isCollision) return false;
- transform.localPosition = new Vector3 (
- transform.localPosition.x + direct.x,
- transform.localPosition.y,
- transform.localPosition.z);
- return true;
- }
- public void OnMoveDownEvent (InputAction.CallbackContext context) {
- if (IsOperatable() && context.phase == InputActionPhase.Started) {
- _state = State.HighSpeedDrop;
- }
- }
- public void OnTurnLeftEvent (InputAction.CallbackContext context) {
- if (IsOperatable() && context.phase == InputActionPhase.Started) {
- Rotate(90);
- }
- }
- public void OnTurnRightEvent (InputAction.CallbackContext context) {
- if (IsOperatable() && context.phase == InputActionPhase.Started) {
- Rotate(-90);
- }
- }
- private Rotation RotateAngle (Rotation rotation, int degree) {
- var deg = degree % 360;
- var rot = ( (int) rotation * 90 + deg);
- if (rot < 0) rot = 360 + rot;
- rot = rot / 90;
- switch (rot) {
- case 0:
- return Rotation.R0;
- case 1:
- return Rotation.R90;
- case 2:
- return Rotation.R180;
- case 3:
- return Rotation.R270;
- default:
- return Rotation.R0;
- }
- }
- private bool CollisionCheck (Vector2 position) {
- var hitResult = new List<RaycastHit2D>();
- var start = position;
- var length = 0.1f;
- var direct = Vector2.down;
- var hitCount = Physics2D.Raycast(
- start, // レイの原点
- direct, // レイの方向
- _contactFilter, // チェックする対象を設定するフィルター
- hitResult, // ヒットしたオブジェクトの情報
- length // レイの長さ
- );
- //Debug.DrawRay(start, direct * length, Color.blue, 1f); // Debug用
- return (hitCount > 0)? true : false;
- }
- private bool IsOperatable () {
- return _state == State.Drop || _state == State.HighSpeedDrop || _state == State.Lock;
- }
- private void SetColor () {
- foreach (var block in _blockPrefabs) {
- block.GetComponent<SpriteRenderer>().color = TETRIMINO_COLOR[(int)_type];
- }
- }
- private void LocateBlocks () {
- var positions = BlocksPosition(_type, _rotation);
- var idx = 0;
- foreach (var block in _blockPrefabs) {
- block.transform.localPosition = new Vector2(positions[idx].x, positions[idx].y);
- idx++;
- }
- }
- private Vector2Int BlocksPosition (Type type, Rotation rotation) {
- var positions = new Vector2Int[4];
- for (var i = 0; i < 4; i++) {
- positions[i] = new Vector2Int(
- TETRIMINO[(int)type][(int)rotation, i, 0],
- TETRIMINO[(int)type][(int)rotation, i, 1]);
- }
- return positions;
- }
- // return true if collision is detected
- private bool FreeFall () {
- var isCollision = false;
- foreach (var block in _blockPrefabs) {
- if (CollisionCheck (block.transform, Vector2.down)) {
- isCollision = true;
- break;
- }
- }
- if (isCollision) return true;
- this.transform.localPosition = new Vector3(
- this.transform.localPosition.x,
- this.transform.localPosition.y - 1.0f,
- this.transform.localPosition.z);
- return false;
- }
- private bool CollisionCheck (Transform block, Vector2 direct) {
- var start = new Vector2 (
- block.position.x + block.lossyScale.x * 0.5f,
- block.position.y + block.lossyScale.y * 0.5f);
- var length = block.lossyScale.x; // ブロックは正方形なので、xとyの長さは同じ
- var hit = Physics2D.Raycast (
- start, // レイの原点
- direct, // レイの方向
- length, // レイの長さ
- LayerMask.GetMask("Field") // チェックする対象を設定するフィルター
- );
- //Debug.DrawRay(start, direct * length, Color.red, 1f); // Debug用
- return (hit.collider != null)? true : false;
- }
- private void TickEventInDropState () {
- if (_totalDeltaTime > _fallTime) {
- var isCollision = FreeFall();
- if (isCollision) {
- _state = State.Lock;
- }
- _totalDeltaTime = 0f;
- }
- }
- private void TickEventInHighSpeedDropState () {
- if (_totalDeltaTime > _fallTime*HIGH_SPEED_RATIO) {
- var isCollision = FreeFall();
- if (isCollision) {
- _state = State.Lock;
- }
- _totalDeltaTime = 0f;
- }
- }
- private void TickEventInLockState () {
- if (_totalDeltaTime > _lockTime) {
- _lockedEventListner?.OnLocked();
- _state = State.Idle;
- _totalDeltaTime = 0f;
- }
- }
- void Update() {
- _totalDeltaTime += Time.deltaTime;
- switch (_state) {
- case State.Idle:
- break;
- case State.Drop:
- TickEventInDropState();
- break;
- case State.HighSpeedDrop:
- TickEventInHighSpeedDropState();
- break;
- case State.Lock:
- TickEventInLockState();
- break;
- }
- }
- }
LevelScore.cs
- using System;
- using System.Linq;
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- using TMPro;
- public class LevelScore {
- public interface ILevelScoreEventListener {
- void OnLevelUp (int level);
- }
- private ILevelScoreEventListener _levelScoreEventListener;
- TextMeshProUGUI _scoreText;
- TextMeshProUGUI _levelText;
- private readonly int SCORE_RATE = new int[] { 10, 20, 40, 80 };
- private const int MAX_SCORE = 9999;
- private const int MAX_LEVEL = 99;
- private int _score = 0;
- private int _level = 1;
- private int _deletedLineCount = 0;
- public LevelScore () {
- }
- public void Init (ILevelScoreEventListener listener) {
- _score = 0;
- _level = 1;
- _deletedLineCount = 0;
- _levelScoreEventListener = listener;
- _scoreText = GameObject.Find("ScoreText").GetComponent<TextMeshProUGUI>();
- _levelText = GameObject.Find("LevelText").GetComponent<TextMeshProUGUI>();
- UpdateScoreText();
- UpdateLevelText();
- }
- public void Reset () {
- _score = 0;
- _level = 1;
- _deletedLineCount = 0;
- UpdateScoreText();
- UpdateLevelText();
- }
- public void AddScore (int deletedLineCount) {
- _deletedLineCount += deletedLineCount;
- _score += SCORE_RATE[deletedLineCount - 1];
- if (_score > MAX_SCORE) {
- _score = MAX_SCORE;
- }
- UpdateScoreText();
- UpdateLevel();
- }
- private void UpdateLevel () {
- var prev = _level;
- _level = _deletedLineCount / 10 + 1;
- if (_level > MAX_LEVEL) {
- _level = MAX_LEVEL;
- }
- if (prev != _level) {
- _levelScoreEventListener?.OnLevelUp(_level);
- UpdateLevelText();
- }
- }
- private void UpdateScoreText () {
- _scoreText.text = $"{_score:D4}";
- }
- private void UpdateLevelText () {
- _levelText.text = $"{_level:D2}";
- }
- }
ScreenConfig.cs
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- public class ScreenConfig {
- // Singleton
- public static ScreenConfig instance = null;
- public const float PIXEL_PER_UNIT = 128f; // Block Texture Pixel size per Unit
- public const int FIELD_WIDTH = 10;
- public const int FIELD_HEIGHT = 20;
- public const int FIELD_BOTTOM_WIDTH = 1;
- public const int FIELD_SIDE_WIDTH = 1;
- public const int START_POS_X = 3;
- public const int START_POS_Y = 16;
- public const float SCREEN_OFFSET_RATIO = 0.05f;
- public Vector2 FieldOffset {get; private set;} // field offset in Unit
- public float ScreenAspect {get; private set;} // screen aspect width/height
- public float OrthographicSize {get; private set;} // orthographic size in Unit
- public const int BLOCK_LAYER = 6;
- public const int FIELD_LAYER = 7;
- public const int TETRIMINO_LAYER = 8;
- // Singleton
- public static ScreenConfig Instance {
- get {
- if (instance == null) {
- instance = new ScreenConfig();
- }
- return instance;
- }
- }
- private ScreenConfig () {
- Init();
- }
- private void Init() {
- ScreenAspect = Screen.safeArea.width / Screen.safeArea.height;
- var offsetHight = Screen.safeArea.height * SCREEN_OFFSET_RATIO; // pixel
- OrthographicSize = (float)(FIELD_HEIGHT+FIELD_BOTTOM_WIDTH) * 0.5f
- + offsetHight / PIXEL_PER_UNIT; // Unit
- FieldOffset = new Vector2(
- OrthographicSize * ScreenAspect - (float)(FIELD_WIDTH+FIELD_SIDE_WIDTH*2f) * 0.5f,
- offsetHight / PIXEL_PER_UNIT
- );
- }
- }