Unity で テトリス風ゲームを作ってみる(アニメーション#2)
Unity Version 2022.3.4
前回の続き
前回はテトリミノの自由落下時にDOTween によるアニメーションを適用してみた。前回は触れていないが、アニメーションを適用する場合は状態管理と非同期処理について注意する必要がある。今回はそのあたりを整理する。
アニメーションの非同期処理
テトリミノの自由落下をアニメーション処理にすることで、落下判定の状態ずれが発生するケースがある。
アニメーションを適用しない場合、落下後の位置は自由落下処理を開始する段階で確定している。よって、テトリミノの落下前の位置から1 段下にブロックがあるかどうかを確認するだけでよい(当たり判定と実際の移動処理がシーケンシャルに同期的に行われるため)。
アニメーションを適用した場合、落下開始から落下終了までの移動処理が非同期的に処理されるため、その間に左右キーのイベント処理が発生する可能性がある。仮に落下中に右キーが押された場合、落下位置は落下開始時点から右に1 つズレた位置となる。つまり、落下開始時点では、落下後の位置を正確に特定できないことになる。
そこでまず、アニメーション処理を適用する場合には、アニメーション処理中の状態における各種発生イベントをどのように扱うか事前に決めておく必要がある。
落下アニメーション中は、左右キーイベントを無視する(落下アニメーション中は左右キーを受け付けない)ことも可能だが、それだとアニメーションを適用することによってゲーム性が落ちることになるので本末転倒。
落下アニメーション中にも左右キーイベントを有効とするため、イベントを受け取った時に左右にブロックがあるかの判定に加え、その下にもブロックがあるかの判定を行うように変更する。もし、落下アニメーション中の左右移動時の事前チェックで左右とその下のいずれかにブロックがある場合は、左右移動ができないものと判定する。
落下アニメーション中でない場合にも、左右移動時に下ブロックの当たり判定を追加してしまうと、左右が空いているにも関わらず左右に移動できない状態になる可能性がある。そこでアニメーション中かアニメーション中以外かでステートを分けて管理を行う。
このように、アニメーション処理を追加していくと、非同期処理が発生し、さらに非同期処理中に同時発生するイベントを処理する必要があり、ステート(状態管理)を増やして対応することになっていく。
今回は暫定処置として、テトリミノクラスの中でステート判定のif 分を追加することで処理を分けるが、状態が増えていく、もしくはイベントの数が増えていくとこの種のif 分が大量にソースコード上に増え続けるため、メンテナンス性が著しく低下する。
そこで、ゆくゆくはテトリミノのステートごとにクラスを分けて状態管理することを考える(デザインパターンのState パターン)
Tetrimino.cs の抜粋(ステート処理の追加)
DropAnimation ステートを追加
ステート分岐にDropAnimation ステートを追加。DropAnimation ステートで行う処理は、"今のところ" Drop ステートと同じ。
- void Update() {
- _totalDeltaTime += Time.deltaTime;
- switch (_state) {
- case State.Idle:
- break;
- case State.Drop:
- case State.DropAnimation:
- TickEventInDropState();
- break;
- case State.HighSpeedDrop:
- TickEventInHighSpeedDropState();
- break;
- case State.Lock:
- TickEventInLockState();
- break;
- }
- }
Tetrimino.cs の抜粋(アニメーションステートへ変更)
DOTween による落下アニメーション開始直前に、ステートをアニメーション中状態に変更する。
- private bool FreeFall () {
- var isCollision = false;
- foreach (var block in _blockPrefabs) {
- if (CollisionCheck (block.transform, Vector2.down)) {
- isCollision = true;
- break;
- }
- }
- if (isCollision) return true;
- // 以下をDOTweenで置き換える
- // this.transform.localPosition = new Vector3(
- // this.transform.localPosition.x,
- // this.transform.localPosition.y - 1.0f,
- // this.transform.localPosition.z);
- _state = State.DropAnimation;
- DropAnimation ();
- return false;
- }
Tetrimino.cs の抜粋(Dropステートに変更)
DOTween にはアニメーション完了を通知するOnComplete APIがある。OnComplete の引数にコールバック関数を指定すると、アニメーションが完了したときにコールバックさせる。今回はステートを変更するだけの処理のため、ラムダ式でコールバック関数を直接指定している。
- private void DropAnimation () {
- var pos = this.transform.localPosition;
- this.transform
- .DOLocalMoveY(pos.y - 1.0f, _fallTime*0.5f)
- .OnComplete( () => {
- _state = State.Drop;
- });
- }
Tetrimino.cs の抜粋(左右移動時の当たり判定変更)
左右移動処理にて、ステートがDropAnimation である場合は、左右の下にブロックがあるかも確認する。
- private bool OnMoveHorizontal (Vector2 direct) {
- var isCollision = false;
- foreach (var block in _blockPrefabs) {
- if (CollisionCheck(block.transform, direct)) {
- isCollision = true;
- break;
- }
- if (_state == State.DropAnimation) {
- if (CollisionCheck(
- new Vector2(
- block.transform.position.x+direct.x+0.5f, // 0.5fはブロックの中心座標にするため
- block.transform.position.y+direct.y+0.5f),// 0.5fはブロックの中心座標にするため
- Vector2.down,
- block.transform.lossyScale.x)) {
- isCollision = true;
- break;
- }
- }
- }
- if (isCollision) return false;
- transform.localPosition = new Vector3 (
- transform.localPosition.x + direct.x,
- transform.localPosition.y,
- transform.localPosition.z);
- return true;
- }
Tetrimino.cs の抜粋(当たり判定関数の別バージョンを追加)
当たり判定関数にスタートポジションと、向き、長さを指定できる引数バージョンを追加。当たり判定の方法はRaycast のまま。
- private bool CollisionCheck (Vector3 position, Vector2 direct, float length) {
- var start = position;
- var hit = Physics2D.Raycast (
- start, // レイの原点
- direct, // レイの方向
- length, // レイの長さ
- LayerMask.GetMask("Field") // チェックする対象を設定するフィルター
- );
- //Debug.DrawRay(start, direct * length, Color.green, 1f); // Debug用
- return (hit.collider != null)? true : false;
- }
上記を実装の結果がこちら。
次の記事