前回は3Dテトリスのスコアと何のブロックが落ちてくるかのUI作成を行いました。
前回の記事:
今回の記事ではゲームの流れとなるスタート・ゲームオーバー処理を作成し、ゲームを完成させます。
開始ボタンの作成
まず、開始ボタンを押してからゲームを開始するようにしていきましょう!
「TetrisSceneManager」スクリプトを次のように修正してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
// TetrisSceneManager.cs 開始処理の追加 //次のものを修正 void Start() { StartTitle(); } //次のものを追加 public GameObject TitleUI; Coroutine _currentCoroutine; public void StartGame() { if(_currentCoroutine != null) { StopCoroutine(_currentCoroutine); } Score = 0; TitleUI.SetActive(false); ScoreText.transform.parent.parent.gameObject.SetActive(true); _currentCoroutine = StartCoroutine(MainLoop()); } public void StartTitle() { if (_currentCoroutine != null) { StopCoroutine(_currentCoroutine); } TitleUI.SetActive(true); ScoreText.transform.parent.parent.gameObject.SetActive(false); Score = 0; _currentCoroutine = StartCoroutine(TitleLoop()); } IEnumerator TitleLoop() { while(true) { yield return null; } } |
GameObjectの追加
スクリプト側の準備ができましたので次は必要なGameObjectを追加していきます。
次の手順を行ってください。
- メニューのGameObject > UI > CanvasをクリックしGameObjectをシーンに追加
- 追加したGameObjectの名前は「TitleUI」にする
作成した「TitleUI」は次のGameObject階層にしてください。
- TitleUI : UI > Canvas
- – Title : UI > Text
- – StartButton : UI > Button
GameObject階層を作成したら、次のように設定してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
// TitleUIの設定内容 ## TitleUI : UI > Canvas - CanvasScalerコンポーネント UIScaleMode : ScaleWithScreenSize ReferenceResolution : X=800, Y=600 ## TitleUI > Title : UI > Text - RectTransformコンポーネント Anchors Min: 0, 0.5 Anchors Max: 1, 1 Pivot : 0.5, 0.5 Left=0, Right=0, Top=0, Bottom=0 - Textコンポーネント Text : 3D Tetris Alignment : 横=中央揃え, 縦=中央揃え BestFit : true, MinSize=10, MaxSize=72 Color :白色 ## TitleUI > StartButton : UI > Button - RectTransformコンポーネント Anchors Min: 0.5, 0.25 Anchors Max: 0.5, 0.25 Pivot : 0.5, 0.5 PosX=0, PosY=0, Width=100, Height=40 - Button コンポーネント OnClick : シーンの「Tetris」の「TetrisSceneManager」コンポーネントの「StartGame()」メソッドを設定 ## TitleUI > StartButton > Text : UI > Button - Textコンポーネント Text : Start Alignment : 横=中央揃え, 縦=中央揃え BestFit : true, MinSize=10, MaxSize=32 Color :黒色 |
GameObjectの配置および設定ができたら、「TetrisSceneMangaer」コンポーネントに次のものを設定してください。
1 2 |
// TetrisSceneManagerコンポーネントの設定内容 TitleUI : シーンの「TitleUI」 |
ここまでできたら再生して動作を確認してみてください。
次の画像のようになっていればOKです。
【学歴不問・高卒、元ニートでも挑戦できる】
ゲームオーバー処理の作成
次にゲームオーバー処理を作成していきます。
「TetrisSceneManager」コンポーネントを次のように修正してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
// TetrisSceneManager.cs ゲームオーバー処理の追加 //次のものを追加 public GameObject GameOverUI; public float WaitGameOverSeconds = 3f; public void GameOver() { if (_currentCoroutine != null) { StopCoroutine(_currentCoroutine); } GameOverUI.SetActive(true); ScoreText.transform.parent.parent.gameObject.SetActive(false); StartCoroutine(GameOverLoop()); } IEnumerator GameOverLoop() { yield return new WaitForSeconds(WaitGameOverSeconds); GameOverUI.SetActive(false); StartTitle(); } //次のものを修正 IEnumerator MainLoop() { Grid.ClearMass(); var rnd = new System.Random(); var nextTemplate = DropBlocks[rnd.Next(DropBlocks.Length)]; while (true) { yield return null; var block = Object.Instantiate(BlockPrefab, transform); block.SetTemplate(nextTemplate); block.Pos = DropStartPos; nextTemplate = DropBlocks[rnd.Next(DropBlocks.Length)]; NextBlockImage.sprite = nextTemplate.Thumbnail; while (!block.IsTouch(Grid, Vector3Int.zero)) { float t = 0f; while (t < DropSpeed) { t += Time.deltaTime; var offset = Vector3Int.zero; if (Input.GetKeyDown(KeyCode.LeftArrow)) offset.x += 1; else if (Input.GetKeyDown(KeyCode.RightArrow)) offset.x -= 1; if (Input.GetKeyDown(KeyCode.DownArrow)) offset.z += 1; else if (Input.GetKeyDown(KeyCode.UpArrow)) offset.z -= 1; block.Move(Grid, offset); if (Input.GetKeyDown(KeyCode.Q)) block.RotateForward(Grid, true); else if (Input.GetKeyDown(KeyCode.E)) block.RotateForward(Grid, false); if (Input.GetKeyDown(KeyCode.A)) block.RotateUp(Grid, true); else if (Input.GetKeyDown(KeyCode.D)) block.RotateUp(Grid, false); yield return null; } block.DropMove(1); } { var pos = block.Pos; pos.y++; block.Pos = pos; } block.FillMass(Grid); Object.Destroy(block.gameObject); if (block.MaxPos.y >= Grid.Size.y) { GameOver(); break; } else { CheckGridDepth(); } } } |
上の修正内容には「Grid」コンポーネントにマスの状態をクリアーするメソッドを追加しているのでそちらも作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// Grid.cs マスの状態をクリアーするメソッドの追加 //次のものを追加 public void ClearMass() { for(var y=0; y<Size.y; ++y) { for (var z = 0; z < Size.z; ++z) { for (var x = 0; x < Size.x; ++x) { this[x, y, z].Type = MassType.Empty; } } } } |
GameObjectの作成
GameObjectの配置ができましたら、「TetrisSceneMangaer」スクリプトに必要なGameObjectを追加していきます。
次の手順を行ってください。
- メニューのGameObject > UI > CanvasをクリックしGameObjectをシーンに追加
- 追加したGameObjectの名前は「GameOverUI」にする
作成した「GameOverUI」は次のGameObject階層にしてください。
- GameOverUI : UI > Canvas
- – Text : UI > Text
GameObject階層を作成したら、次のように設定してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// GameOverUIの設定内容 ## GameOverUI : UI > Canvas - CanvasScalerコンポーネント UIScaleMode : ScaleWithScreenSize ReferenceResolution : X=800, Y=600 GameOverUIは再生前にエディター状で非アクティブにしてください。 ## GameOverUI > Text : UI > Text - RectTransformコンポーネント Anchors Min: 0, 0 Anchors Max: 1, 1 Pivot : 0.5, 0.5 Left=0, Right=0, Top=0, Bottom=0 - Textコンポーネント Text : Game Over… Alignment : 横=中央揃え, 縦=中央揃え BestFit : true, MinSize=10, MaxSize=72 Color :白色 |
GameObjectの設定ができましたら、シーンの「TetrisSceneManager」コンポーネントを次のように設定してください。
1 2 |
// TetrisSceneManagerコンポーネントの設定内容 GameOverUI : シーンの「GameOverUI」 |
ここまでできたら再生して動作を確認してみてください。
次の画像のようになっていればOKです。
まとめ
今回の記事ではゲームの流れを作成しました。
まとめると次のようになります。
- 開始ボタンを押すとゲームを始めるようにした
- ゲームオーバー処理を作成
今回までで3Dテトリスの実装ができました。ですが、まだまだ改善点がたくさんあります。
ここから先は読者の方がご自身で改良してみてください。
ここまでご覧いただきありがとうございました。
3Dテトリスゲームスクリプトまとめ
ここまでの講座の内容で出てきたスクリプト全体をまとめます。
Block.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 |
using System.Collections; using System.Collections.Generic; using UnityEngine; enum Direction { Left, Right, Up, Down, Forward, Back, } enum RotateDirection { Minus, None, Plus, } static class DirectionUtils { static readonly Dictionary<Direction, Vector3> dirVectorDict = new Dictionary<Direction, Vector3>() { { Direction.Left, Vector3.left}, { Direction.Right, Vector3.right}, { Direction.Up, Vector3.up}, { Direction.Down, Vector3.down}, { Direction.Forward, Vector3.forward}, { Direction.Back, Vector3.back}, }; public static Vector3 GetDirVec(Direction dir) { return dirVectorDict[dir]; } public static Vector3Int Rotate(Vector3Int p, float deg, Direction axis) { var mat = Matrix4x4.Rotate(Quaternion.AngleAxis(deg, DirectionUtils.GetDirVec(axis))); var newPos = mat.MultiplyVector(p); newPos.x += newPos.x > 0 ? 0.5f : -0.5f; newPos.y += newPos.y > 0 ? 0.5f : -0.5f; newPos.z += newPos.z > 0 ? 0.5f : -0.5f; return new Vector3Int((int)newPos.x, (int)newPos.y, (int)newPos.z); } } class Block : MonoBehaviour { [SerializeField] GameObject MassPrefab; [SerializeField] BlockTemplate BlockTemplate; [SerializeField] Vector3Int _pos; public Vector3 MassSize = new Vector3(1, 1, 1); public Vector3Int Size { get => Instance?.Size ?? Vector3Int.zero; } public Vector3Int MinPos { get => Instance != null ? Pos + Instance.MinPos : Pos; } public Vector3Int MaxPos { get => Instance != null ? Pos + Instance.MaxPos : Pos; } [SerializeField] Direction _forward = Direction.Forward; [SerializeField] Direction _up = Direction.Up; BlockInstance Instance; public Vector3Int Pos { get => _pos; set { _pos = value; transform.localPosition = new Vector3( MassSize.x * _pos.x, MassSize.y * _pos.y, MassSize.z * _pos.z ); } } public Direction Forward { get => _forward; } public Direction Up { get => _up; } public void SetTemplate(BlockTemplate template) { BlockTemplate = template; CreateBlockGameObjects(); } void RemoveAllMass() { while (0 < transform.childCount) { var child = transform.GetChild(transform.childCount - 1); Object.Destroy(child.gameObject); } } void CreateBlockGameObjects() { RemoveAllMass(); if (BlockTemplate == null) return; Instance = BlockTemplate.CreateBlockInstance(); if (MassPrefab == null) return; for (var y = 0; y < Size.y; ++y) { for (var z = 0; z < Size.z; ++z) { for (var x = 0; x < Size.x; ++x) { var obj = Object.Instantiate(MassPrefab, transform); obj.transform.localPosition = new Vector3( MassSize.x * (x - Instance.Origin.x), MassSize.y * (y - Instance.Origin.y), MassSize.z * (z - Instance.Origin.z) ); var type = Instance[x, y, z]; obj.SetActive(type == MassType.Mass); } } } } private void Awake() { Pos = Pos; transform.localRotation = Instance?.rotaMat.rotation ?? Quaternion.identity; } public void FillMass(Grid grid, bool doRemove = false) { var pos = Vector3Int.zero; for (pos.y = 0; pos.y < Size.y; ++pos.y) { for (pos.z = 0; pos.z < Size.z; ++pos.z) { for (pos.x = 0; pos.x < Size.x; ++pos.x) { var p = Instance.ToRotateCoordination(pos) + Pos; if (grid.Contains(p) && Instance.Contains(pos) && Instance[pos] == MassType.Mass) { grid[p].Type = doRemove ? MassType.Empty : MassType.Mass; } } } } } public bool IsTouch(Grid grid, Vector3Int offset) { var minPos = MinPos; if (minPos.y + offset.y < 0) return true; var pos = Vector3Int.zero; for (pos.y = 0; pos.y < Size.y; ++pos.y) { for (pos.z = 0; pos.z < Size.z; ++pos.z) { for (pos.x = 0; pos.x < Size.x; ++pos.x) { var p = Instance.ToRotateCoordination(pos) + Pos + offset; if (Instance.Contains(pos) && Instance[pos] == MassType.Mass && grid.Contains(p) && grid[p].Type == MassType.Mass) { return true; } } } } return false; } public void Move(Grid grid, Vector3Int move) { if (IsTouch(grid, move)) return; var minPos = MinPos; var maxPos = MaxPos; if (minPos.x + move.x < 0) move.x -= (minPos.x + move.x); if (minPos.y + move.y < 0) move.y -= (minPos.y + move.y); if (minPos.z + move.z < 0) move.z -= (minPos.z + move.z); if (maxPos.x + move.x > grid.Size.x) move.x -= (maxPos.x + move.x) - grid.Size.x; if (maxPos.z + move.z > grid.Size.z) move.z -= (maxPos.z + move.z) - grid.Size.z; var pos = Pos + move; pos.x = Mathf.Clamp(pos.x, 0, grid.Size.x - 1); pos.z = Mathf.Clamp(pos.z, 0, grid.Size.z - 1); Pos = pos; } public void DropMove(int moveY) { var pos = Pos; pos.y -= moveY; Pos = pos; } public void RotateForward(Grid grid, bool isLeft) { Instance.RotateForward(isLeft); if (IsTouch(grid, Vector3Int.zero)) { Instance.RotateForward(!isLeft); } else { _forward = Instance.Forward; } transform.localRotation = Instance.rotaMat.rotation; } public void RotateUp(Grid grid, bool isLeft) { Instance.RotateUp(isLeft); if (IsTouch(grid, Vector3Int.zero)) { Instance.RotateUp(!isLeft); } else { _up = Instance.Up; } transform.localRotation = Instance.rotaMat.rotation; } } |
BlockInstance.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 |
using System.Collections; using System.Collections.Generic; using UnityEngine; class BlockInstance { public BlockTemplate Template { get; private set; } public Vector3Int Origin { get; private set; } public Vector3Int Size { get; private set; } public Vector3Int MinPos { get; private set; } public Vector3Int MaxPos { get; private set; } public Direction Forward { get; private set; } = Direction.Forward; public Direction Up { get; private set; } = Direction.Up; MassType[,,] _data; public Matrix4x4 rotaMat = Matrix4x4.identity; public MassType this[int x, int y, int z] { get { return _data[x, y, z]; } set { _data[x, y, z] = value; } } public MassType this[Vector3Int pos] { get => this[pos.x, pos.y, pos.z]; set { this[pos.x, pos.y, pos.z] = value; } } public bool Contains(int x, int y, int z) { return 0 <= x && x < _data.GetLength(0) && 0 <= y && y < _data.GetLength(1) && 0 <= z && z < _data.GetLength(2); } public bool Contains(Vector3Int pos) => Contains(pos.x, pos.y, pos.z); public (int x, int y, int z) ToRotateCoordination(int localX, int localY, int localZ) { var p = rotaMat.MultiplyPoint(new Vector3(localX, localY, localZ) - Origin); p.x += p.x > 0 ? 0.5f : -0.5f; p.y += p.y > 0 ? 0.5f : -0.5f; p.z += p.z > 0 ? 0.5f : -0.5f; return ((int)p.x, (int)p.y, (int)p.z); } public Vector3Int ToRotateCoordination(Vector3Int pos) { var p = ToRotateCoordination(pos.x, pos.y, pos.z); return new Vector3Int(p.x, p.y, p.z); } public BlockInstance(BlockTemplate template) { Template = template; CreateData(template); } (Vector3Int size, Vector3Int min, Vector3Int max) CalcSize(BlockTemplate template) { var minPos = Vector3Int.zero; var maxPos = Vector3Int.zero; maxPos.y = Template.Slices.Length; for (var y = 0; y < maxPos.y; ++y) { var lines = Template.Slices[y].Split('\n'); maxPos.z = Mathf.Max(maxPos.z, lines.Length); for (var z = 0; z < lines.Length; ++z) { var line = lines[z]; maxPos.x = Mathf.Max(maxPos.x, line.Length); } } return (maxPos, minPos, maxPos); } void CreateData(BlockTemplate template) { (Size, MinPos, MaxPos) = CalcSize(template); _data = new MassType[Size.x, Size.y, Size.z]; var origin = Vector3Int.zero; for (var y = 0; y < Size.y; ++y) { var lines = Template.Slices[y].Split('\n'); for (var z = 0; z < lines.Length; ++z) { var line = lines[z]; for (var x = 0; x < line.Length; ++x) { MassType mass; switch (line[x]) { case '#': mass = MassType.Mass; break; case 'o': origin = new Vector3Int(x, y, z); mass = MassType.Mass; break; default: mass = MassType.Empty; break; } _data[x, y, z] = mass; } } } Origin = origin; MinPos -= Origin; MaxPos -= Origin; } public void RotateForward(bool isLeft) { switch (Forward) { case Direction.Forward: switch (Up) { case Direction.Left: Forward = isLeft ? Direction.Down : Direction.Up; break; case Direction.Right: Forward = isLeft ? Direction.Up : Direction.Down; break; case Direction.Up: Forward = isLeft ? Direction.Left : Direction.Right; break; case Direction.Down: Forward = isLeft ? Direction.Right : Direction.Left; break; } break; case Direction.Back: switch (Up) { case Direction.Left: Forward = isLeft ? Direction.Up : Direction.Down; break; case Direction.Right: Forward = isLeft ? Direction.Down : Direction.Up; break; case Direction.Up: Forward = isLeft ? Direction.Right : Direction.Left; break; case Direction.Down: Forward = isLeft ? Direction.Left : Direction.Right; break; } break; case Direction.Left: switch (Up) { case Direction.Up: Forward = isLeft ? Direction.Back : Direction.Forward; break; case Direction.Down: Forward = isLeft ? Direction.Forward : Direction.Back; break; case Direction.Forward: Forward = isLeft ? Direction.Up : Direction.Down; break; case Direction.Back: Forward = isLeft ? Direction.Down : Direction.Up; break; } break; case Direction.Right: switch (Up) { case Direction.Up: Forward = isLeft ? Direction.Forward : Direction.Back; break; case Direction.Down: Forward = isLeft ? Direction.Back : Direction.Forward; break; case Direction.Forward: Forward = isLeft ? Direction.Down : Direction.Up; break; case Direction.Back: Forward = isLeft ? Direction.Up : Direction.Down; break; } break; case Direction.Up: switch (Up) { case Direction.Left: Forward = isLeft ? Direction.Forward : Direction.Back; break; case Direction.Right: Forward = isLeft ? Direction.Back : Direction.Forward; break; case Direction.Forward: Forward = isLeft ? Direction.Right : Direction.Left; break; case Direction.Back: Forward = isLeft ? Direction.Left : Direction.Right; break; } break; case Direction.Down: switch (Up) { case Direction.Left: Forward = isLeft ? Direction.Back : Direction.Forward; break; case Direction.Right: Forward = isLeft ? Direction.Forward : Direction.Back; break; case Direction.Forward: Forward = isLeft ? Direction.Left : Direction.Right; break; case Direction.Back: Forward = isLeft ? Direction.Right : Direction.Left; break; } break; } var deg = isLeft ? -90 : 90; var minPos = DirectionUtils.Rotate(MinPos, deg, Up); var maxPos = DirectionUtils.Rotate(MaxPos - Vector3Int.one, deg, Up); MinPos = Vector3Int.Min(minPos, maxPos); MaxPos = Vector3Int.Max(minPos, maxPos) + Vector3Int.one; rotaMat = Matrix4x4.Rotate(Quaternion.AngleAxis(deg, DirectionUtils.GetDirVec(Up))) * rotaMat; } public void RotateUp(bool isLeft) { switch (Up) { case Direction.Left: switch (Forward) { case Direction.Up: Up = isLeft ? Direction.Forward : Direction.Back; break; case Direction.Down: Up = isLeft ? Direction.Back : Direction.Forward; break; case Direction.Forward: Up = isLeft ? Direction.Down : Direction.Up; break; case Direction.Back: Up = isLeft ? Direction.Up : Direction.Down; break; } break; case Direction.Right: switch (Forward) { case Direction.Up: Up = isLeft ? Direction.Back : Direction.Forward; break; case Direction.Down: Up = isLeft ? Direction.Forward : Direction.Back; break; case Direction.Forward: Up = isLeft ? Direction.Up : Direction.Down; break; case Direction.Back: Up = isLeft ? Direction.Down : Direction.Up; break; } break; case Direction.Up: switch (Forward) { case Direction.Left: Up = isLeft ? Direction.Back : Direction.Forward; break; case Direction.Right: Up = isLeft ? Direction.Forward : Direction.Back; break; case Direction.Forward: Up = isLeft ? Direction.Left : Direction.Right; break; case Direction.Back: Up = isLeft ? Direction.Right : Direction.Left; break; } break; case Direction.Down: switch (Forward) { case Direction.Left: Up = isLeft ? Direction.Forward : Direction.Back; break; case Direction.Right: Up = isLeft ? Direction.Back : Direction.Forward; break; case Direction.Forward: Up = isLeft ? Direction.Right : Direction.Left; break; case Direction.Back: Up = isLeft ? Direction.Left : Direction.Right; break; } break; case Direction.Forward: switch (Forward) { case Direction.Left: Up = isLeft ? Direction.Up : Direction.Down; break; case Direction.Right: Up = isLeft ? Direction.Down : Direction.Up; break; case Direction.Up: Up = isLeft ? Direction.Right : Direction.Left; break; case Direction.Down: Up = isLeft ? Direction.Left : Direction.Right; break; } break; case Direction.Back: switch (Forward) { case Direction.Left: Up = isLeft ? Direction.Down : Direction.Up; break; case Direction.Right: Up = isLeft ? Direction.Up : Direction.Down; break; case Direction.Up: Up = isLeft ? Direction.Left : Direction.Right; break; case Direction.Down: Up = isLeft ? Direction.Right : Direction.Left; break; } break; } var deg = isLeft ? -90 : 90; var minPos = DirectionUtils.Rotate(MinPos, deg, Forward); var maxPos = DirectionUtils.Rotate(MaxPos - Vector3Int.one, deg, Forward); MinPos = Vector3Int.Min(minPos, maxPos); MaxPos = Vector3Int.Max(minPos, maxPos) + Vector3Int.one; rotaMat = Matrix4x4.Rotate(Quaternion.AngleAxis(deg, DirectionUtils.GetDirVec(Forward))) * rotaMat; } } |
BlockTemplate.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; [CreateAssetMenu(menuName = "Block Template")] public class BlockTemplate : ScriptableObject { [TextArea(3, 5)] public string[] Slices; public Sprite Thumbnail; internal BlockInstance CreateBlockInstance() { return new BlockInstance(this); } } |
Camera.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Camera : MonoBehaviour { public GameObject Target; public Vector3 Offset = new Vector3(0, 0, 0); public Vector3 Dir = new Vector3(0, 1, 0); public float Distance = 10; // Start is called before the first frame update IEnumerator Start() { yield return null; while (true) { transform.position = Target.transform.position + Dir.normalized * Distance + Offset; transform.LookAt(Target.transform); yield return null; } } } |
Grid.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
using System.Collections; using System.Collections.Generic; using UnityEngine; enum MassType { Empty, Mass, } class Mass { public GameObject Obj { get; set; } MassType _type; public MassType Type { get => _type; set { _type = value; Obj.SetActive(_type == MassType.Mass); } } } class Grid : MonoBehaviour { public GameObject MassPrefab; public Vector3Int Size = new Vector3Int(5, 10, 5); public Vector3 MassSize = new Vector3(1, 1, 1); Mass[,,] _data; public Mass this[int x, int y, int z] { get => _data[x, y, z]; set => _data[x, y, z] = value; } public Mass this[Vector3Int pos] { get => this[pos.x, pos.y, pos.z]; set => this[pos.x, pos.y, pos.z] = value; } public bool Contains(int x, int y, int z) { return 0 <= x && x < _data.GetLength(0) && 0 <= y && y < _data.GetLength(1) && 0 <= z && z < _data.GetLength(2); } public bool Contains(Vector3Int pos) => Contains(pos.x, pos.y, pos.z); public void BuildGrid() { _data = new Mass[Size.x, Size.y, Size.z]; for (var y = 0; y < Size.y; ++y) { var depth = new GameObject($"Depth{y}"); depth.transform.localPosition = Vector3.up * y; depth.transform.SetParent(transform); for (var z = 0; z < Size.z; ++z) { for (var x = 0; x < Size.x; ++x) { var mass = Object.Instantiate(MassPrefab); mass.transform.localPosition = new Vector3( MassSize.x * x, MassSize.y * y, MassSize.z * z ); mass.transform.SetParent(depth.transform); this[x, y, z] = new Mass { Obj = mass, Type = MassType.Empty }; } } } } private void Awake() { BuildGrid(); } public bool DoFillDepth(int depth) { if (depth < 0 || Size.y <= depth) return false; for (var z = 0; z < Size.z; ++z) { for (var x = 0; x < Size.x; ++x) { if (this[x, depth, z].Type == MassType.Empty) return false; } } return true; } public bool RemoveDepth(int depth) { if (depth < 0 || Size.y <= depth) return false; for (var z = 0; z < Size.z; ++z) { for (var x = 0; x < Size.x; ++x) { this[x, depth, z].Type = MassType.Empty; } } for (var y = depth + 1; y < Size.y; ++y) { for (var z = 0; z < Size.z; ++z) { for (var x = 0; x < Size.x; ++x) { this[x, y - 1, z].Type = this[x, y, z].Type; this[x, y, z].Type = MassType.Empty; } } } return true; } public void ClearMass() { for (var y = 0; y < Size.y; ++y) { for (var z = 0; z < Size.z; ++z) { for (var x = 0; x < Size.x; ++x) { this[x, y, z].Type = MassType.Empty; } } } } } |
TetrisScenemanager.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; class TetrisSceneManager : MonoBehaviour { public Grid Grid; public Block BlockPrefab; public BlockTemplate[] DropBlocks; public float DropSpeed = 1f; public Vector3Int DropStartPos = new Vector3Int(0, 11, 0); [SerializeField] int _score; public Text ScoreText; public Image NextBlockImage; public GameObject TitleUI; Coroutine _currentCoroutine; public GameObject GameOverUI; public float WaitGameOverSeconds = 3f; public void StartGame() { if (_currentCoroutine != null) { StopCoroutine(_currentCoroutine); } Score = 0; TitleUI.SetActive(false); ScoreText.transform.parent.parent.gameObject.SetActive(true); _currentCoroutine = StartCoroutine(MainLoop()); } public void StartTitle() { if (_currentCoroutine != null) { StopCoroutine(_currentCoroutine); } TitleUI.SetActive(true); ScoreText.transform.parent.parent.gameObject.SetActive(false); Score = 0; _currentCoroutine = StartCoroutine(TitleLoop()); } IEnumerator TitleLoop() { while (true) { yield return null; } } public int Score { get => _score; set { _score = value; ScoreText.text = _score.ToString(); } } void Start() { StartTitle(); } IEnumerator MainLoop() { Grid.ClearMass(); var rnd = new System.Random(); var nextTemplate = DropBlocks[rnd.Next(DropBlocks.Length)]; while (true) { yield return null; var block = Object.Instantiate(BlockPrefab, transform); block.SetTemplate(nextTemplate); block.Pos = DropStartPos; nextTemplate = DropBlocks[rnd.Next(DropBlocks.Length)]; NextBlockImage.sprite = nextTemplate.Thumbnail; //block.FillMass(Grid, false); while (!block.IsTouch(Grid, Vector3Int.zero)) { float t = 0f; while (t < DropSpeed) { t += Time.deltaTime; var offset = Vector3Int.zero; if (Input.GetKeyDown(KeyCode.LeftArrow)) offset.x += 1; else if (Input.GetKeyDown(KeyCode.RightArrow)) offset.x -= 1; if (Input.GetKeyDown(KeyCode.DownArrow)) offset.z += 1; else if (Input.GetKeyDown(KeyCode.UpArrow)) offset.z -= 1; block.Move(Grid, offset); if (Input.GetKeyDown(KeyCode.Q)) block.RotateForward(Grid, true); else if (Input.GetKeyDown(KeyCode.E)) block.RotateForward(Grid, false); if (Input.GetKeyDown(KeyCode.A)) block.RotateUp(Grid, true); else if (Input.GetKeyDown(KeyCode.D)) block.RotateUp(Grid, false); yield return null; } block.DropMove(1); } { var pos = block.Pos; pos.y++; block.Pos = pos; } block.FillMass(Grid); Object.Destroy(block.gameObject); if (block.MaxPos.y >= Grid.Size.y) { GameOver(); break; } else { CheckGridDepth(); } } } void CheckGridDepth() { for (var y = 0; y < Grid.Size.y; ++y) { if (Grid.DoFillDepth(y)) { Grid.RemoveDepth(y); y--; Score += 100; } } } public void GameOver() { if (_currentCoroutine != null) { StopCoroutine(_currentCoroutine); } GameOverUI.SetActive(true); ScoreText.transform.parent.parent.gameObject.SetActive(false); StartCoroutine(GameOverLoop()); } IEnumerator GameOverLoop() { yield return new WaitForSeconds(WaitGameOverSeconds); GameOverUI.SetActive(false); StartTitle(); } } |
コメント