独習 Unity アプリ開発

独習でスマフォ向けアプリ開発を勉強中

Unity で テトリス風ゲームを作ってみる(ソースコードとか)

まとめ


ここまでで作成したソースコード、Unity Project の構造などの情報を掲載。

 

GameConrtol Object

 

Field Object

 

NextArea Object

 

GameTitle Object

 

StartButton Object

 

GamePlay Object

 

GameOver Object

 

RestartButton Object

 

Block Prefab

 

Tetrimino Prefab

 

InputAction Map

 

GameControl.cs

  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using UnityEngine;
  5. using Cysharp.Threading.Tasks;
  6. using DG.Tweening;
  7.  
  8. public class GameControl : MonoBehaviour, Field.IFieldEventListener, LevelScore.ILevelScoreEventListener {
  9.  
  10.   private enum State {
  11.     Title,
  12.     Droping,
  13.     GameOver,
  14.   }
  15.  
  16.   [SerializeField] Camera _camera;
  17.   [SerializeField] GameObject _gameTitlePanel;
  18.   [SerializeField] GameObject _gamePlayPanel;
  19.   [SerializeField] GameObject _gameOverPanel;
  20.   [SerializeField] Field _field;
  21.   [SerializeField] NextArea _nextArea;
  22.  
  23.   private LevelScore _levelScore;
  24.   private bool _gotFirstFrame = false;
  25.   private bool _readyTetrimino = false;
  26.   private State _state = State.Title;
  27.  
  28.   // IFieldEventListener Implementation
  29.   public void OnDeletedLine(int lines) {
  30.     if (lines > 0) {
  31.       _levelScore.AddScore(lines);
  32.     }
  33.     _readyTetrimino = true;
  34.   }
  35.  
  36.   // ILevelScoreEventListener Implementation
  37.   public void OnLevelUp (int level) {
  38.     _field.SetDropSpeed(level);
  39.   }
  40.  
  41.   // Button Event Callback
  42.   public void OnStartClick () {
  43.     _gameTitlePanel.SetActive(false);
  44.     _gamePlayPanel.SetActive(true);
  45.     _gameOverPanel.SetActive(false);
  46.     GameStart();
  47.   }
  48.  
  49.   public void OnRestartClick () {
  50.     _levelScore.Reset();
  51.     _field.DestroyAllBlocks();
  52.     _nextArea.DestroyNextTetrimino();
  53.  
  54.     PrepareNextTetrimino();
  55.     _gamePlayPanel.SetActive(true);
  56.     _gameOverPanel.SetActive(false);
  57.     _state = State.Droping;
  58.   }
  59.  
  60.   private void Init () {
  61.     UnityEngine.Random.InitState( (int) DateTime.Now.Ticks);
  62.     DOTween.Init();
  63.     CameraSetting();
  64.     _state = State.Title;
  65.   }
  66.  
  67.   private void GameStart () {
  68.     _field?.Init(this);
  69.     _nextArea?.Init();
  70.     CreateLevelScore();
  71.     PrepareNextTetrimino();
  72.     _state = State.Droping;
  73.   }
  74.  
  75.   private void PrepareNextTetrimino() {
  76.     _nextArea.CreateNextTetrimino();
  77.     PushTetrimino2Field();
  78.     _nextArea.CreateNextTetrimino();
  79.     _readyTetrimino = false;
  80.   }
  81.  
  82.   private void PushTetrimino2Field () {
  83.     var tetrimino = _nextArea.GetNextTetrimino();
  84.     var success = _field.PushTetrimino(tetrimino);
  85.     if (!success) {
  86.       _state = State.GameOver;
  87.       WaitGameOver();
  88.     }
  89.   }
  90.  
  91.   private void CameraSetting () {
  92.     _camera.orthographicSize = ScreenConfig.Instance.OrthographicSize;
  93.  
  94.     // change camera position so that left-bottom is (0,0).
  95.     _camera.transform.localPosition = new Vector3 (
  96.       ScreenConfig.Instance.OrthographicSize * ScreenConfig.Instance.ScreenAspect,
  97.       ScreenConfig.Instance.OrthographicSize,
  98.       -10);
  99.   }
  100.  
  101.   private void CreateLevelScore () {
  102.     _levelScore = new LevelScore();
  103.     _levelScore.Init(this);
  104.   }
  105.  
  106.   private void WaitGameOver () {
  107.     DOVirtual.DelayedCall(2, () => {
  108.       _gameOverPanel.SetActive(true);
  109.     });
  110.   }
  111.  
  112.   // Update is called once per frame
  113.   void Update() {
  114.     if (!_gotFirstFrame) {
  115.       // Initialize once after first frame.
  116.       Init();
  117.       _gotFirstFrame = true;
  118.     }
  119.  
  120.     if (_readyTetrimino && _state != State.GameOver) {
  121.       _readyTetrimino = false;
  122.       PushTetrimino2Field();
  123.       _nextArea.CreateNextTetrimino();
  124.     }
  125.   }
  126. }
  127.  

 

Field.cs

  1. using System;
  2. using System.Linq;
  3. using System.Collections;
  4. using System.Collections.Generic;
  5. using UnityEngine;
  6. using UnityEngine.InputSystem;
  7.  
  8. public class Field : MonoBehaviour, Tetrimino.ILockedEventListner {
  9.  
  10.   public interface IFieldEventListener {
  11.     void OnDeletedLine (int lines);
  12.   }
  13.  
  14.   private IFieldEventListener _fieldEventListener;
  15.   private List<GameObject> _fieldBlocks = new List<GameObject>();
  16.   private List<GameObject> _blocks = new List<GameObject>();
  17.   private Tetrimino _tetrimino;
  18.  
  19.   // ILockedEventListner Implementation
  20.   public void OnLocked() {
  21.     var lines = CheckAndDeleteLines();
  22.     AddBlocks();
  23.     _fieldEventListener?.OnDeletedLine(lines);
  24.   }
  25.  
  26.   public void Init(IFieldEventListener listener) {
  27.     this.transform.localPosition = new Vector3(
  28.       ScreenConfig.Instance.FieldOffset.x,
  29.       ScreenConfig.Instance.FieldOffset.y,
  30.       0);
  31.  
  32.     // Create Field Area
  33.     // Bottom Wall
  34.     for (int x = 0; x < (ScreenConfig.FIELD_WIDTH+ScreenConfig.FIELD_SIDE_WIDTH*2); x++) {
  35.       CreateFieldBlock(new Vector2Int(x, 0));
  36.     }
  37.  
  38.     // Left and Right Wall
  39.     for (int y = 0; y < (ScreenConfig.FIELD_HEIGHT+ScreenConfig.FIELD_BOTTOM_WIDTH); y++) {
  40.       CreateFieldBlock(new Vector2Int(0, y));
  41.       CreateFieldBlock(new Vector2Int(ScreenConfig.FIELD_WIDTH+ScreenConfig.FIELD_SIDE_WIDTH, y));
  42.     }
  43.  
  44.     _fieldEventListener = listener;
  45.   }
  46.  
  47.   public bool PushTetrimino (Tetrimino tetrimino) {
  48.     _tetrimino = tetrimino;
  49.     _tetrimino.SetListner (this);
  50.     _tetrimino.transform.SetParent(this.transform);
  51.     _tetrimino.transform.localPosition = new Vector3(
  52.       ScreenConfig.START_POS_X + ScreenConfig.FIELD_SIDE_WIDTH,
  53.       ScreenConfig.START_POS_Y + ScreenConfig.FIELD_BOTTOM_WIDTH,
  54.       0);
  55.     
  56.     if (!_tetrimino.StartDrop()) {
  57.       // GameOver
  58.       return false;
  59.     }
  60.     return true;
  61.   }
  62.  
  63.   public void SetDropSpeed (int level) {
  64.     _tetrimino?.SetDropSpeed(level);
  65.   }
  66.  
  67.   // Add Blocks of Tetrimino to Field.
  68.   public void AddBlocks() {
  69.     var blocks = _tetrimino?.RemoveBlocks();
  70.  
  71.     foreach (var block in blocks) {
  72.       block.transform.SetParent(this.transform);
  73.       block.layer = ScreenConfig.FIELD_LAYER;
  74.     }
  75.  
  76.     _blocks.AddRange(blocks);
  77.     DestroyTetrimino();
  78.   }
  79.  
  80.   // Destroy All Block Objects include Field blocks.
  81.   public void DestroyAllBlocks () {
  82.     DestroyTetrimino();
  83.  
  84.     foreach (var block in _blocks) {
  85.       Destroy(block);
  86.     }
  87.     _blocks.Clear();
  88.   }
  89.  
  90.   // Destroy Block Objects on Field.
  91.   public void DestroyBlocks (List<GameObject> blocks) {
  92.     foreach (var block in blocks) {
  93.       _blocks.Remove(block);
  94.       Destroy(block);
  95.     }
  96.   }
  97.  
  98.   private void DestroyTetrimino () {
  99.     _tetrimino.DestroyBlocks();
  100.     Destroy(_tetrimino.gameObject);
  101.   }
  102.  
  103.   // Create Field Block Object from Prefab.
  104.   private void CreateFieldBlock (Vector2Int pos) {
  105.     var prefab = Resources.Load("Prefabs/Block") as GameObject;
  106.     var copyObject = Instantiate(prefab, this.transform);
  107.     copyObject.transform.localPosition = new Vector3(pos.x, pos.y, 0);
  108.     copyObject.layer = ScreenConfig.FIELD_LAYER;
  109.     _fieldBlocks.Add(copyObject);
  110.   }
  111.  
  112.   // Check Line whether it is filled or not at Tetrimino level.
  113.   private int CheckAndDeleteLines () {
  114.     var pos = _tetrimino.GetBlocksPosition();
  115.     var max = pos.Select(p => p.y).Max();
  116.     var min = pos.Select(p => p.y).Min();
  117.  
  118.     var deletedLineCount = 0;
  119.     for (float y = min; y <= max; y++) {
  120.       var deleteBlocks = CheckLine(y);
  121.       if (deleteBlocks != null) {
  122.         DestroyBlocks(deleteBlocks);
  123.         deletedLineCount++;
  124.         DropLines(y);
  125.       }
  126.     }
  127.  
  128.     return deletedLineCount;
  129.   }
  130.  
  131.   private List<GameObject> CheckLine (float y) {
  132.     var detectedBlocks = DetectBlocks(y);
  133.     if (detectedBlocks != null && detectedBlocks.Count() >= ScreenConfig.FIELD_WIDTH) {
  134.       return detectedBlocks;
  135.     }
  136.     return null;
  137.   }
  138.  
  139.   private List<GameObject> DetectBlocks (float y) {
  140.     ContactFilter2D contactFilter = new ContactFilter2D();
  141.     var start = new Vector2(
  142.       ScreenConfig.FIELD_SIDE_WIDTH + ScreenConfig.Instance.FieldOffset.x + 0.5f,
  143.       y + 0.5f
  144.     );
  145.  
  146.     var length = ScreenConfig.FIELD_WIDTH - 1f;
  147.     var hitResult = new List<RaycastHit2D>();
  148.     var hitCount = Physics2D.Raycast(
  149.       start, // レイの原点
  150.       Vector2.right, // レイの方向
  151.       contactFilter.NoFilter(), // チェックする対象を設定するフィルター
  152.       hitResult, // ヒットしたオブジェクトの情報
  153.       length // レイの長さ
  154.     );
  155.  
  156.     //Debug.DrawRay(start, Vector2.right * length, Color.red, 2f); // Debug用
  157.     
  158.     if (hitCount > 0) {
  159.       var detectedBlocks = new List<GameObject>();
  160.       foreach (var hit in hitResult) {
  161.         detectedBlocks.Add(hit.collider.gameObject);
  162.       }
  163.       return detectedBlocks;
  164.     }
  165.  
  166.     return null;
  167.   }
  168.  
  169.   private void DropLines (float deletedLineYPos) {
  170.     for (float y = deletedLineYPos+1; y < ScreenConfig.FIELD_HEIGHT; y++) {
  171.       var detectedBlocks = DetectBlocks(y);
  172.       if (detectedBlocks != null) {
  173.         DropLine(detectedBlocks);
  174.       }
  175.     }
  176.   }
  177.  
  178.   private void DropLine (List<GameObject> blocks) {
  179.     foreach (var block in blocks) {
  180.       block.transform.position += Vector3.down; // down is -1
  181.     }
  182.   }
  183. }
  184.  

 

NextArea.cs

  1. using System;
  2. using System.Linq;
  3. using System.Collections;
  4. using System.Collections.Generic;
  5. using UnityEngine;
  6.  
  7. public class NextArea : MonoBehaviour {
  8.  
  9.   // Next Area Position and Size
  10.   private const int POSITION_X = 5;
  11.   private const int POSITION_Y = 15;
  12.   private const float WIDTH = 4f;
  13.   private const float HEIGHT = 4f;
  14.  
  15.   // Tetrimino Object
  16.   private Tetrimino _next;
  17.  
  18.   public void Init () {
  19.     _next = null;
  20.     this.transform.localPosition = new Vector3(POSITION_X, POSITION_Y, 0f);
  21.   }
  22.  
  23.   public void CreateNextTetrimino () {
  24.     _next = CreateTetriminoByRandom();
  25.   }
  26.  
  27.   public Tetrimino GetNextTetrimino () {
  28.     return _next;
  29.   }
  30.  
  31.   public void DestroyNextTetrimino () {
  32.     if (_next != null) {
  33.       _next.DestroyBlocks();
  34.       Destroy(_next.gameObject);
  35.       _next = null;
  36.     }
  37.   }
  38.  
  39.   private Tetrimino CreateTetriminoByRandom () {
  40.     var prefab = Resources.Load("Prefabs/Tetrimino") as GameObject;
  41.     var copyObject = Instantiate(prefab, this.transform);
  42.     var next = copyObject.GetComponent<Tetrimino>();
  43.     var type = (Tetrimino.Type)UnityEngine.Random.Range(0, (int)Tetrimino.Type.MAX);
  44.     next.Init(type);
  45.  
  46.     // set position in Center of NextArea
  47.     var width = next.GetWidth();
  48.     var height = next.GetHeight();
  49.     next.transform.localPosition = new Vector3(
  50.       (WIDTH - width) * 0.5f,
  51.       (HEIGHT - height) * 0.5f,
  52.       0);
  53.     return next;
  54.   }
  55. }
  56.  

 

Tetrimino.cs

  1. using System;
  2. using System.Linq;
  3. using System.Collections;
  4. using System.Collections.Generic;
  5. using UnityEngine;
  6. using UnityEngine.InputSystem;
  7. using UnityEngine.U2D;
  8.  
  9. public class Tetrimino : MonoBehaviour {
  10.  
  11.   public interface ILockedEventListner {
  12.     void OnLocked ();
  13.   }
  14.  
  15.   private ILockedEventListner _lockedEventListner;
  16.   
  17.   public enum State {
  18.     Idle,
  19.     Drop,
  20.     HighSpeedDrop,
  21.     Lock,
  22.     MAX
  23.   };
  24.  
  25.   public enum Rotation {
  26.     R0 = 0,
  27.     R90,
  28.     R180,
  29.     R270,
  30.     MAX
  31.   };
  32.  
  33.   public enum Type {
  34.     I = 0,
  35.     L,
  36.     J,
  37.     T,
  38.     S,
  39.     Z,
  40.     O,
  41.     MAX
  42.   };
  43.  
  44.   private readonly int [,,] TETRIMINO = new int [,,] {
  45.     new int[,,] { // I
  46.       {
  47.         {0,0}, {1,0}, {2,0}, {3,0} // 0
  48.       },{
  49.         {1,0}, {1,1}, {1,2}, {1,3} // 90
  50.       },{
  51.         {0,0}, {1,0}, {2,0}, {3,0} // 180
  52.       },{
  53.         {1,0}, {1,1}, {1,2}, {1,3} // 270
  54.       }
  55.     },
  56.     new int[,,] { // L
  57.       {
  58.         {0,0}, {1,0}, {2,0}, {2,1} // 0
  59.       },{
  60.         {1,0}, {1,1}, {1,2}, {0,2} // 90
  61.       },{
  62.         {0,0}, {0,1}, {1,1}, {2,1} // 180
  63.       },{
  64.         {0,0}, {1,0}, {0,1}, {0,2} // 270
  65.       }
  66.     },
  67.     new int[,,] { // J
  68.       {
  69.         {0,0}, {1,0}, {2,0}, {0,1} // 0
  70.       },{
  71.         {0,0}, {1,0}, {1,1}, {1,2} // 90
  72.       },{
  73.         {0,1}, {1,1}, {2,1}, {2,0} // 180
  74.       },{
  75.         {0,0}, {0,1}, {0,2}, {1,2} // 270
  76.       }
  77.     },
  78.     new int[,,] {
  79.       // T
  80.       {
  81.         {0,0}, {1,0}, {2,0}, {1,1} // 0
  82.       },{
  83.         {1,0}, {0,1}, {1,1}, {1,2} // 90
  84.       },{
  85.         {1,0}, {0,1}, {1,1}, {2,1} // 180
  86.       },{
  87.         {0,0}, {0,1}, {0,2}, {1,1} // 270
  88.       }
  89.     },
  90.     new int[,,] {
  91.       // S
  92.       {
  93.         {0,0}, {1,0}, {1,1}, {2,1} // 0
  94.       },{
  95.         {1,0}, {0,1}, {1,1}, {0,2} // 90
  96.       },{
  97.         {0,0}, {1,0}, {1,1}, {2,1} // 180
  98.       },{
  99.         {1,0}, {0,1}, {1,1}, {0,2} // 270
  100.       }
  101.     },
  102.     new int[,,] {
  103.       // Z
  104.       {
  105.         {1,0}, {2,0}, {0,1}, {1,1} // 0
  106.       },{
  107.         {0,0}, {0,1}, {1,1}, {1,2} // 90
  108.       },{
  109.         {1,0}, {2,0}, {0,1}, {1,1} // 180
  110.       },{
  111.         {0,0}, {0,1}, {1,1}, {1,2} // 270
  112.       }
  113.     },
  114.     new int[,,] {
  115.       // O
  116.       {
  117.         {0,0}, {1,0}, {0,1}, {1,1} // 0
  118.       },{
  119.         {0,0}, {1,0}, {0,1}, {1,1} // 90
  120.       },{
  121.         {0,0}, {1,0}, {0,1}, {1,1} // 180
  122.       },{
  123.         {0,0}, {1,0}, {0,1}, {1,1} // 270
  124.       }
  125.     }
  126.   };
  127.  
  128.   private readonly Color TETRIMINO_COLOR = new Color {
  129.     new Color(0.0f, 1.0f, 1.0f), // I
  130.     new Color(1.0f, 0.5f, 0.0f), // L
  131.     new Color(0.0f, 0.0f, 1.0f), // J
  132.     new Color(1.0f, 0.0f, 1.0f), // T
  133.     new Color(0.0f, 1.0f, 0.0f), // S
  134.     new Color(1.0f, 1.0f, 0.0f), // Z
  135.     new Color(1.0f, 0.0f, 0.0f) // O
  136.   };
  137.  
  138.   private const float HIGH_SPEED_RATIO = 0.05f; // 20 times of normal speed
  139.   private const float DROP_LEVEL_RATIO = 0.95f; // 5% speed up per level
  140.  
  141.   [SerializeField] private List<GameObject> _blockPrefabs;
  142.   private Type _type;
  143.   private Rotation _rotation;
  144.   private float _totalDeltaTime;
  145.   private ContactFilter2D _contactFilter;
  146.   private float _fallTime;
  147.   private float _lockTime;
  148.   private State _state;
  149.  
  150.   public void Init (Type type, ILockedEventListner lockedEventListner) {
  151.     this.Init(type);
  152.     _lockedEventListner = lockedEventListner;
  153.   }
  154.  
  155.   public void Init (Type type) {
  156.     _type = type;
  157.     _rotation = Rotation.R0;
  158.     LocateBlocks ();
  159.     SetColor();
  160.     _totalDeltaTime = 0f;
  161.     _fallTime = 1.0f;
  162.     _lockTime = 0.5f;
  163.     _state = State.Idle;
  164.     _contactFilter = new ContactFilter2D();
  165.     _contactFilter.layerMask = LayerMask.GetMask("Field");
  166.     _contactFilter.useLayerMask = true;
  167.   }
  168.  
  169.   public void DestroyBlocks () {
  170.     foreach (var block in _blockPrefabs) {
  171.       Destroy(block);
  172.     }
  173.     _blockPrefabs.Clear();
  174.   }
  175.  
  176.   public void SetListner (ILockedEventListner lockedEventListner) {
  177.     _lockedEventListner = lockedEventListner;
  178.   }
  179.  
  180.   public bool StartDrop () {
  181.     var isCollision = false;
  182.     foreach (var block in _blockPrefabs) {
  183.       if (CollisionCheck(block.transform.position + new Vector3(0.5f, 0.5f, 0))) {
  184.         isCollision = true;
  185.         break;
  186.       }
  187.     }
  188.  
  189.     if (isCollision) {
  190.       return false;
  191.     }
  192.   
  193.     _state = State.Drop;
  194.     return true;
  195.   }
  196.  
  197.   public void SetDropSpeed (int level) {
  198.     var falltime = 1.0f;
  199.     for (var i = 0; i < level; i++) {
  200.       falltime = falltime * DROP_LEVEL_RATIO;
  201.     }
  202.     _fallTime = falltime;
  203.   }
  204.  
  205.   public void Rotate (int degree) {
  206.     var rotation = RotateAngle(_rotation, degree);
  207.     var positions = BlocksPosition(_type, rotation);
  208.     foreach (var pos in positions)
  209.       if (CollisionCheck(this.transform.position + new Vector3(pos.x, pos.y, 0))) return;
  210.     _rotation = rotation;
  211.     LocateBlocks();
  212.   }
  213.  
  214.   public List<GameObject> GetBlocks() {
  215.     return _blockPrefabs;
  216.   }
  217.  
  218.   public List<GameObject> RemoveBlocks() {
  219.     var blocks = new List<GameObject>();
  220.     blocks.AddRange(_blockPrefabs);
  221.     _blockPrefabs.Clear();
  222.     return blocks;
  223.   }
  224.  
  225.   public float GetWidth() {
  226.     var pos = GetBlocksPosition();
  227.     var max = pos.Select(p => p.x).Max();
  228.     var min = pos.Select(p => p.x).Min();
  229.     return max - min +1;
  230.   }
  231.  
  232.   public float GetHeight() {
  233.     var pos = GetBlocksPosition();
  234.     var max = pos.Select(p => p.y).Max();
  235.     var min = pos.Select(p => p.y).Min();
  236.     return max - min +1;
  237.   }
  238.  
  239.   public List<Vector2> GetBlocksPosition () {
  240.     var pos = new List<Vector2>();
  241.     foreach (var block in _blockPrefabs) {
  242.       pos.Add(block.transform.position);
  243.     }
  244.     return pos;
  245.   }
  246.  
  247.   public void OnMoveLeftEvent (InputAction.CallbackContext context) {
  248.     if (!IsOperatable() || context.phase != InputActionPhase.Started)
  249.       return;
  250.  
  251.     OnMoveHorizontal(Vector2.left);
  252.   }
  253.  
  254.   public void OnMoveRightEvent (InputAction.CallbackContext context) {
  255.     if (!IsOperatable() || context.phase != InputActionPhase.Started)
  256.       return;
  257.  
  258.     OnMoveHorizontal(Vector2.right);
  259.   }
  260.  
  261.   private bool OnMoveHorizontal (Vector2 direct) {
  262.     var isCollision = false;
  263.     foreach (var block in _blockPrefabs) {
  264.       if (CollisionCheck(block.transform, direct)) {
  265.         isCollision = true;
  266.         break;
  267.       }
  268.     }
  269.  
  270.     if (isCollision) return false;
  271.  
  272.     transform.localPosition = new Vector3 (
  273.       transform.localPosition.x + direct.x,
  274.       transform.localPosition.y,
  275.       transform.localPosition.z);
  276.  
  277.     return true;
  278.   }
  279.  
  280.   public void OnMoveDownEvent (InputAction.CallbackContext context) {
  281.     if (IsOperatable() && context.phase == InputActionPhase.Started) {
  282.       _state = State.HighSpeedDrop;
  283.     }
  284.   }
  285.  
  286.   public void OnTurnLeftEvent (InputAction.CallbackContext context) {
  287.     if (IsOperatable() && context.phase == InputActionPhase.Started) {
  288.       Rotate(90);
  289.     }
  290.   }
  291.  
  292.   public void OnTurnRightEvent (InputAction.CallbackContext context) {
  293.     if (IsOperatable() && context.phase == InputActionPhase.Started) {
  294.       Rotate(-90);
  295.     }
  296.   }
  297.  
  298.   private Rotation RotateAngle (Rotation rotation, int degree) {
  299.     var deg = degree % 360;
  300.     var rot = ( (int) rotation * 90 + deg);
  301.     if (rot < 0) rot = 360 + rot;
  302.     rot = rot / 90;
  303.  
  304.     switch (rot) {
  305.       case 0:
  306.         return Rotation.R0;
  307.       case 1:
  308.         return Rotation.R90;
  309.       case 2:
  310.         return Rotation.R180;
  311.       case 3:
  312.         return Rotation.R270;
  313.       default:
  314.         return Rotation.R0;
  315.     }
  316.   }
  317.  
  318.   private bool CollisionCheck (Vector2 position) {
  319.     var hitResult = new List<RaycastHit2D>();
  320.     var start = position;
  321.     var length = 0.1f;
  322.     var direct = Vector2.down;
  323.     var hitCount = Physics2D.Raycast(
  324.       start, // レイの原点
  325.       direct, // レイの方向
  326.       _contactFilter, // チェックする対象を設定するフィルター
  327.       hitResult, // ヒットしたオブジェクトの情報
  328.       length // レイの長さ
  329.       );
  330.  
  331.     //Debug.DrawRay(start, direct * length, Color.blue, 1f); // Debug用
  332.     return (hitCount > 0)? true : false;
  333.   }
  334.  
  335.   private bool IsOperatable () {
  336.     return _state == State.Drop || _state == State.HighSpeedDrop || _state == State.Lock;
  337.   }
  338.  
  339.   private void SetColor () {
  340.     foreach (var block in _blockPrefabs) {
  341.       block.GetComponent<SpriteRenderer>().color = TETRIMINO_COLOR[(int)_type];
  342.     }
  343.   }
  344.  
  345.   private void LocateBlocks () {
  346.     var positions = BlocksPosition(_type, _rotation);
  347.     var idx = 0;
  348.     foreach (var block in _blockPrefabs) {
  349.       block.transform.localPosition = new Vector2(positions[idx].x, positions[idx].y);
  350.       idx++;
  351.     }
  352.   }
  353.  
  354.   private Vector2Int BlocksPosition (Type type, Rotation rotation) {
  355.     var positions = new Vector2Int[4];
  356.     for (var i = 0; i < 4; i++) {
  357.       positions[i] = new Vector2Int(
  358.         TETRIMINO[(int)type][(int)rotation, i, 0],
  359.         TETRIMINO[(int)type][(int)rotation, i, 1]);
  360.     }
  361.     return positions;
  362.   }
  363.   
  364.   // return true if collision is detected
  365.   private bool FreeFall () {
  366.     var isCollision = false;
  367.     foreach (var block in _blockPrefabs) {
  368.       if (CollisionCheck (block.transform, Vector2.down)) {
  369.         isCollision = true;
  370.         break;
  371.       }
  372.     }
  373.  
  374.     if (isCollision) return true;
  375.  
  376.     this.transform.localPosition = new Vector3(
  377.       this.transform.localPosition.x,
  378.       this.transform.localPosition.y - 1.0f,
  379.       this.transform.localPosition.z);
  380.  
  381.     return false;
  382.   }
  383.  
  384.   private bool CollisionCheck (Transform block, Vector2 direct) {
  385.     var start = new Vector2 (
  386.       block.position.x + block.lossyScale.x * 0.5f,
  387.       block.position.y + block.lossyScale.y * 0.5f);
  388.     var length = block.lossyScale.x; // ブロックは正方形なので、xとyの長さは同じ
  389.  
  390.     var hit = Physics2D.Raycast (
  391.       start, // レイの原点
  392.       direct, // レイの方向
  393.       length, // レイの長さ
  394.       LayerMask.GetMask("Field") // チェックする対象を設定するフィルター
  395.       );
  396.  
  397.     //Debug.DrawRay(start, direct * length, Color.red, 1f); // Debug用
  398.     return (hit.collider != null)? true : false;
  399.   }
  400.  
  401.   private void TickEventInDropState () {
  402.     if (_totalDeltaTime > _fallTime) {
  403.       var isCollision = FreeFall();
  404.       if (isCollision) {
  405.         _state = State.Lock;
  406.       }
  407.       _totalDeltaTime = 0f;
  408.     }
  409.   }
  410.  
  411.   private void TickEventInHighSpeedDropState () {
  412.     if (_totalDeltaTime > _fallTime*HIGH_SPEED_RATIO) {
  413.       var isCollision = FreeFall();
  414.       if (isCollision) {
  415.         _state = State.Lock;
  416.       }
  417.       _totalDeltaTime = 0f;
  418.     }
  419.   }
  420.  
  421.   private void TickEventInLockState () {
  422.     if (_totalDeltaTime > _lockTime) {
  423.       _lockedEventListner?.OnLocked();
  424.       _state = State.Idle;
  425.       _totalDeltaTime = 0f;
  426.     }
  427.   }
  428.  
  429.   void Update() {
  430.     _totalDeltaTime += Time.deltaTime;
  431.     switch (_state) {
  432.       case State.Idle:
  433.         break;
  434.       case State.Drop:
  435.         TickEventInDropState();
  436.         break;
  437.       case State.HighSpeedDrop:
  438.         TickEventInHighSpeedDropState();
  439.         break;
  440.       case State.Lock:
  441.         TickEventInLockState();
  442.         break;
  443.     }
  444.   }
  445. }
  446.  

 

LevelScore.cs

  1. using System;
  2. using System.Linq;
  3. using System.Collections;
  4. using System.Collections.Generic;
  5. using UnityEngine;
  6. using TMPro;
  7.  
  8. public class LevelScore {
  9.  
  10.   public interface ILevelScoreEventListener {
  11.     void OnLevelUp (int level);
  12.   }
  13.  
  14.   private ILevelScoreEventListener _levelScoreEventListener;
  15.   
  16.   TextMeshProUGUI _scoreText;
  17.   TextMeshProUGUI _levelText;
  18.   private readonly int SCORE_RATE = new int[] { 10, 20, 40, 80 };
  19.   private const int MAX_SCORE = 9999;
  20.   private const int MAX_LEVEL = 99;
  21.   private int _score = 0;
  22.   private int _level = 1;
  23.   private int _deletedLineCount = 0;
  24.  
  25.   public LevelScore () {
  26.   }
  27.   
  28.   public void Init (ILevelScoreEventListener listener) {
  29.     _score = 0;
  30.     _level = 1;
  31.     _deletedLineCount = 0;
  32.     _levelScoreEventListener = listener;
  33.     _scoreText = GameObject.Find("ScoreText").GetComponent<TextMeshProUGUI>();
  34.     _levelText = GameObject.Find("LevelText").GetComponent<TextMeshProUGUI>();
  35.     UpdateScoreText();
  36.     UpdateLevelText();
  37.   }
  38.  
  39.   public void Reset () {
  40.     _score = 0;
  41.     _level = 1;
  42.     _deletedLineCount = 0;
  43.     UpdateScoreText();
  44.     UpdateLevelText();
  45.   }
  46.  
  47.   public void AddScore (int deletedLineCount) {
  48.     _deletedLineCount += deletedLineCount;
  49.     _score += SCORE_RATE[deletedLineCount - 1];
  50.     if (_score > MAX_SCORE) {
  51.       _score = MAX_SCORE;
  52.     }
  53.     UpdateScoreText();
  54.     UpdateLevel();
  55.   }
  56.  
  57.   private void UpdateLevel () {
  58.     var prev = _level;
  59.     _level = _deletedLineCount / 10 + 1;
  60.     if (_level > MAX_LEVEL) {
  61.       _level = MAX_LEVEL;
  62.     }
  63.  
  64.     if (prev != _level) {
  65.       _levelScoreEventListener?.OnLevelUp(_level);
  66.       UpdateLevelText();
  67.     }
  68.   }
  69.  
  70.   private void UpdateScoreText () {
  71.     _scoreText.text = $"{_score:D4}";
  72.   }
  73.  
  74.   private void UpdateLevelText () {
  75.     _levelText.text = $"{_level:D2}";
  76.   }
  77. }
  78.  

 

ScreenConfig.cs

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4.  
  5. public class ScreenConfig {
  6.   // Singleton
  7.   public static ScreenConfig instance = null;
  8.  
  9.   public const float PIXEL_PER_UNIT = 128f; // Block Texture Pixel size per Unit
  10.   public const int FIELD_WIDTH = 10;
  11.   public const int FIELD_HEIGHT = 20;
  12.   public const int FIELD_BOTTOM_WIDTH = 1;
  13.   public const int FIELD_SIDE_WIDTH = 1;
  14.   public const int START_POS_X = 3;
  15.   public const int START_POS_Y = 16;
  16.   public const float SCREEN_OFFSET_RATIO = 0.05f;
  17.   public Vector2 FieldOffset {get; private set;} // field offset in Unit
  18.   public float ScreenAspect {get; private set;} // screen aspect width/height
  19.   public float OrthographicSize {get; private set;} // orthographic size in Unit
  20.   public const int BLOCK_LAYER = 6;
  21.   public const int FIELD_LAYER = 7;
  22.   public const int TETRIMINO_LAYER = 8;
  23.   
  24.   // Singleton
  25.   public static ScreenConfig Instance {
  26.     get {
  27.       if (instance == null) {
  28.         instance = new ScreenConfig();
  29.       }
  30.       return instance;
  31.     }
  32.   }
  33.  
  34.   private ScreenConfig () {
  35.     Init();
  36.   }
  37.  
  38.   private void Init() {
  39.     ScreenAspect = Screen.safeArea.width / Screen.safeArea.height;
  40.  
  41.     var offsetHight = Screen.safeArea.height * SCREEN_OFFSET_RATIO; // pixel
  42.  
  43.     OrthographicSize = (float)(FIELD_HEIGHT+FIELD_BOTTOM_WIDTH) * 0.5f
  44.                          + offsetHight / PIXEL_PER_UNIT; // Unit
  45.  
  46.     FieldOffset = new Vector2(
  47.       OrthographicSize * ScreenAspect - (float)(FIELD_WIDTH+FIELD_SIDE_WIDTH*2f) * 0.5f,
  48.       offsetHight / PIXEL_PER_UNIT
  49.     );
  50.   }
  51. }