Unity で スイカ風ゲームを作ってみる#1
Unityでゲームを作る
Unity でスイカ風ゲームを作る!
Unity Version 2022.3.16
仕様の確認
まずはスイカゲームの仕様確認から始める。テトリス風ゲームを作ったときと同様にWiki で仕様を調べてみる。
- フルーツの種類は全部で11種
- フルーツは物理演算で転がる
- 同じ種類のフルーツ同士が触れると、接触点位置で一段階大きなフルーツに変化する
- 一段階大きなフルーツになるとそのフルーツに従って得点が入る
- フルーツごとの配点は、ここの記載を参照
- プレーヤーが落下させるフルーツは、柿以下の5種類からランダムで選ばれる
- 積みあがったフルーツが箱から出るとゲームオーバー
ざっと調べた感じは以上だが、実際のゲームプレイ動画(ドズルさんがかなりアップしている)をみると、
- 次に落ちてくるフルーツが1 つ表示される(テトリスと同様)
- フルーツの進化ガイドが表示される(固定のテクスチャ?)
- 現在のスコアと今までのハイスコアが表示される
という要素も必要そう。
各フルーツの大きさや、重さの情報は見つからなかったが、ドズルさんの動画から推測しながら調整することにする。
次の記事
Unity で スイカ風ゲームを作ってみる(はじめに)
Unityでゲームを作る
Unity でスイカ風ゲームを作る!
Unity を使ってゲームを開発する流れを理解するために、最近はやり(もう終わったかもですが)のスイカ風ゲームを作ってみます。スイカゲームを知らない人は、ドズルさんの動画を参照してみてください。
Unity Version 2022.3.16
目次
- まずは仕様の確認
- ScriptableObject を使ってフルーツデータの管理
- RigidBody2D/Collider を使ってフルーツの落下と結合
- InputSystem を使ってプレーヤーの操作
- 全体のクラス構造
- UnityEvent を使ってイベント通知
- 物理パラメータを調整して仕上げ
プロト
WebGL版のプロト(PCブラウザのみ、スマフォはNG)
その他
Unity でゲームを作る!
作るシリーズ!
テトリス風ゲーム WebGL版(PCブラウザのみ/スマフォはNG)
スイカ風ゲーム WebGL版(PCブラウザのみ/スマフォはNG)
8番出口風ゲーム WebGL版(PCブラウザのみ/スマフォはNG)
Unity 備忘録
- 異なる画サイズでUI Layout を調整する
- 非アクティブなGameObject の検索
- Parent scale と local positionの関係
- Parent のSprite が 1Uではない場合
- Unreal Engine シリーズはこちら
Unity で テトリス風ゲームを作ってみる(Particle)
Unity Version 2022.3.4
パーティクル(ParticleSystem)
前回まで、よりゲームらしくするためにテトリミノのアニメーション処理を実装してきた。今回からさらにゲームらしい演出を加えるため、パーティクル(ParticleSystem)を適用していく。
ライン削除時のパーティクル演出
ライン削除が発生した時に、削除するブロック位置に横方向へ雷が走るみたいな演出をパーティクルを使って実現する。
雷のパーティクルを作成する時に参考にしたサイトはこちら。
【Unity】雷(サンダー)のエフェクトの作り方 - おーみんブログ
- パーティクルの作成
Hierarchy タブの"+"ボタンから"Effects"→"Particle System" を選択してHierarchy 上にParticle オブジェクトを作成する。
作成したParticle Object の名前を"ExplosionParticle" に変更して、予め作成しておいた"Prefab" フォルダにDrag & Drop してPrefab 化する。その後 Hierarchy 上のParticle Object は削除しておく。
- パーティクルの設定
作成したParticle のPrefab を選択して、Inspector からパーティクルを設定して雷のエフェクトを作成する。パーティクルの設定項目はかなり多いので注意が必要。Unity のマニュアルでもかなりの量があることがわかる。デフォルトからの変更点を以下に記載する。
-
- Duration
- 0.5 : パーティクル全体の実行期間
- Loop
- Disable(チェックを外す): 実行期間が終了後、再度初めから再開し続けるかどうかの設定
- Start LifeTime
- 0.5 : 個々のパーティクル要素の生存期間
- Start Speed
- 100(Min)/ 150(Max): 各パーティクル要素の初速。デフォルトでは固定値を1つだけ入力できるが、右側の"▼"ボタンを押して"Random Between Two Constatns" を選択すると、最初値と最大値を設定できるようになる。この二つの値の範囲でランダム値となる。
- Start Size
- 0.1(Min)/ 0.5(Max): 各パーティクル要素のサイズ。こちらも"Random Beween Two Constatns" を選択して、最初値と最大値を設定。
- Gravity Source
- 2D Physics : 重力のソース。今回重力は使わないので変更しなくてもよさそうだが、念のため。
- Gravity Modifier
- 0 : Physics で設定している重力値に掛ける倍率。0 は重力を使わないという意味。
- Play On Awake
- Disable(チェックを外す): パーティクルオブジェクトが生成(Awake)したときに、パーティクルの再生を開始するするかどうかの設定。今回はスクリプトから開始トリガーをかけるためDisable にしておく。
- Stop Action
- Destroy : パーティクルの実行期間が終了したときに起こすアクションの選択。Destroy の場合はパーティクルオブジェクトが削除される。コールバックをもらいたい場合は、"Callback" を選択する。この場合、パーティクルオブジェクトにアタッチしたスクリプトの"OnParticleSystemStopped" メソッドが呼ばれることになる。
- Duration
-
- Emission Rate Over Time
- 0 : 単位時間ごとに生成されるパーティクル要素の数。"Bursts" の項目で制御するので、ここは"0" を設定。
- Emission Bursts(パーティクル要素の生成を時間ことにトリガーできる)
- Time 0 : パーティクル要素を生成する時間
- Count 5 : 時間が来た時に1 度に生成するパーティクル要素の数
- Cycles 1 : 時間が来た時に何回繰り返し生成するかの回数
- Interval 0.010 : Cycle をどれくらいの間隔で実行するかの時間
- Probability 1.0 : パーティクル要素生成時の生成確率。"1" だと必ず生成される。
- Shape Circle : パーティクル要素を生成するジェネレータの形。イメージとしては、水道の水がパーティクル要素としたら、蛇口の形。
- Emission Rate Over Time
-
- Color over Lifetime
- (下図参照): パーティクル要素の色を時間経過に伴って変更する際のグラデーションの値。左が生成時、右が終了時。
- Noise Strength
- 10(Min)/ 20(Max): 各パーティクル要素の動きに与えるノイズ効果の強度
- Noise Frequency
- 10 : ノイズを加える頻度
- Noise Scroll Speed
- 2 : (よくわかならない。マニュアル参照)
- Noise Octaves
- 4 : ノイズのレイヤー数。ノイズをどの程度複雑化するかの設定。
- Noise Octaves Multiplier
- 1 : ノイズのレイヤーを重ねる際の強度の減衰量(割合)。値を大きくすると(最大1) ノイズが細かくなるみたい。
- Noise Octaves Scale
- 2 : ノイズのレイヤーを重ねる際のノイズ周波数の減衰量。ローパスフィルターのようなものか?よくわかない。。。
- Color over Lifetime
正直ノイズの設定項目は、Preview 画像と実際の実行結果を見ながら適当に調整するしかなさそう。よくわかない。
-
- Trails Color Over Trail
- (下図参照): トレイルの色の設定。トレイルとは、パーティクル要素に対して尾ひれのように(弾道のように)移動の軌跡を表現するために使われるもの。
- Renderer Mode
- Streched Billboard :
- Speed Scale
- 0.1 : パーティクル要素の速度に応じて、パーティクル画像を引き延ばす
- Material
- Default-ParticleSystem : パーティクル要素のレンダリングに使用するマテリアル
- Trail Material
- Default-ParticleSystem : トレイル要素のレンダリングに使用するマテリアル
- Trails Color Over Trail
Particle の設定が完了したので、スクリプトからPerticle Prefab を生成して再生する部分を作る。
Field.cs の抜粋
Field クラスのメンバにParticlePrefab GameObject のメンバを追加。
- private GameObject _explosionParticlePrefab;
Filed クラスの初期化メソッド(Init)で、ParticlePrafab アセットリソースをロード。
- public void Init(IFieldEventListener listener) {
- // 中略
- // Load Particle Prefab
- _explosionParticlePrefab = Resources.Load("Prefabs/ExplosionParticle") as GameObject;
ライン削除時に、ロードしておいたParticlePrefab をインスタンス化し、Y 座標を削除したラインの高さ、X 座標をフィールド中央の位置に移動。Particle オブジェクトからParticleSystem コンポーネントを取得して、Play メソッドで再生を開始する。Particle 設定にて、Stop Action をDestroy にしているので、削除処理は不要(のはず)。
- public void DestroyBlocks (List<GameObject> blocks) {
- float y = blocks[0].transform.localPosition.y;
- foreach (var block in blocks) {
- _blocks.Remove(block);
- Destroy(block);
- }
- var particleObject = Instantiate(_explosionParticlePrefab, this.transform);
- particleObject.transform.localPosition =
- new Vector3(ScreenConfig.FIELD_WIDTH*0.5f, y, 0);
- var particle = particleObject.GetComponent<ParticleSystem>();
- particle.Stop();
- particle.Play();
- }
上記を実装した結果がこちら。
次の記事
(準備中)
Unity で テトリス風ゲームを作ってみる(アニメーション#3)
Unity Version 2022.3.4
前回の続き
前回は、テトリミノ落下時の移動を単純なTranform.localPositionを使った移動から、DoTween を使ったアニメーション処理に変更した。それに伴ってアニメーション処理中を表現するステートを追加した。今回は、前回の対応だけでは不十分な点を補足していく。
ステートとイベントの再整理
前回、落下処理をアニメーション化(非同期化)したことにより、新たなステートState.DropAnimation を追加したが、このステート中に受け取るイベントの処理が実はまだ足りていない。
全体像を把握するため、再度テトリミノのステート遷移図を書き直したのが下図。
DropAnimation ステート中に下キーイベントを受信した場合は、高速落下HighSpeedDrop ステートに遷移する必要がある。そこでTetrimino クラスのOnMoveDownEvent メソッドにて、ステート判定分を追加してDropAnimation ステート中の場合は、DoTween アニメーションをキャンセル(Kill)して、HighSpeedDrop ステートに遷移するように変更する。また合わせて高速落下処理自体もDoTween アニメーションを利用するように変更した。
Tetrimino.cs の抜粋
- public void OnMoveDownEvent (InputAction.CallbackContext context) {
- if (IsOperatable() && context.phase == InputActionPhase.Started) {
- if (_state == State.HighSpeedDrop || _state == State.Lock)
- return;
- if (_state == State.DropAnimation && _tweenerOfDropAnimation != null)
- _tweenerOfDropAnimation.Kill();
- _tweenerOfDropAnimation = DropAnimation (ScreenConfig.FIELD_BOTTOM_WIDTH, DropUpdate);
- ChangeState(State.HighSpeedDrop);
- }
- }
高速落下もアニメーション対応した結果がこちら。
ちなみにアニメーション対応する前はこちら。
DropAnimation のキャンセルと、高速落下アニメーションに対応するため、アニメーション実行処理も少し修正した。
DoTween のアニメーションを途中でキャンセルするには、DOxxx メソッドを呼び出したときに戻り値として取得できるTweener オブジェクトに対し、Kill メソッドを呼び出すことで途中終了させることができる(DOTween - Documentation のKill の項目を参照)。DropAnimation ステート中に下キーを押された時は、Kill を呼び出してDropAnimation をキャンセルした後に、高速落下のアニメーションを開始してステートをHighSpeedDropAnimation に遷移させる。
DOTween アニメーションのキャンセル
Tweener tweenerObj = transform.DOLocalMoveY (,,,,略,,,,);
〰なんやかんや〰
tweenerObj.Kill()
高速落下のアニメーションは、Y座標をフィールドのボトム位置にして実行する。当然、ボトム位置に到達するまえにブロックがある場合は、その時点でアニメーションを止める必要がある。DOTween の OnUpdate を使うとアニメーション処理のUpdate ごとのタイミングでコールバックメソッドを呼び出すことができるので、そのコールバックでブロック検出を実行する。もし、ブロックを検知した場合は、アニメーションをキャンセル(Kill)して、ステートをLock に変更する。
OnMoveDownEvent からのDropAnimation メソッド呼び出し時は、TweenCallback を指定して呼び出すようにしておく。
OnMoveDownEvent
_tweenerOfDropAnimation =
DropAnimation (ScreenConfig.FIELD_BOTTOM_WIDTH, DropUpdate);
Tetrimino.cs の抜粋
- private Tweener DropAnimation (float dest, TweenCallback callback) {
- var pos = this.transform.localPosition;
- return this.transform
- .DOLocalMoveY(dest, _fallTime*0.5f)
- .OnUpdate(callback)
- .OnComplete( () => {
- ChangeState(_prevState);
- _tweenerOfDropAnimation = null;
- });
- }
- // Callback from DOTween
- private void DropUpdate() {
- var isCollision = false;
- foreach (var block in _blockPrefabs) {
- if (CollisionCheck (block.transform, Vector2.down)) {
- isCollision = true;
- break;
- }
- }
- if (isCollision) {
- _tweenerOfDropAnimation.Kill();
- AdjustPosition();
- ChangeState(State.Lock);
- _tweenerOfDropAnimation = null;
- }
- }
今回、ステートの再整理を行ったので、Update メソッド内の各Tick イベント処理についても再度整理を行った。それぞれのステートごとにTick イベント処理関数を呼び出すように変更。今のところ実際に処理があるのはDrop ステートと Lock ステートのみ。
Tetrimino.cs の抜粋
- private void TickEventIdleState () {
- _totalDeltaTime = 0f;
- }
- private void TickEventInDropState () {
- if (_totalDeltaTime > _fallTime) {
- var isCollision = FreeFall();
- if (isCollision) {
- ChangeState(State.Lock);
- }
- _totalDeltaTime = 0f;
- }
- }
- private void TickEventInDropAnimationState () {
- _totalDeltaTime = 0f;
- }
- private void TickEventInHighSpeedDropState () {
- _totalDeltaTime = 0f;
- }
- private void TickEventInLockState () {
- if (_totalDeltaTime > _lockTime) {
- _lockedEventListner?.OnLocked();
- ChangeState(State.Idle);
- _totalDeltaTime = 0f;
- } else {
- FreeFall(false);
- }
- }
- void Update() {
- _totalDeltaTime += Time.deltaTime;
- switch (_state) {
- case State.Idle:
- TickEventIdleState();
- break;
- case State.Drop:
- TickEventInDropState();
- break;
- case State.DropAnimation:
- TickEventInDropAnimationState();
- break;
- case State.HighSpeedDrop:
- TickEventInHighSpeedDropState();
- break;
- case State.Lock:
- TickEventInLockState();
- break;
- }
- }
TickEventInLockState メソッドについて、この処理も今まで考慮すべき点がもれていたので修正した。Lock ステート中も左右キーが有効であるため、テトリミノが左右に移動した後に、下にブロックがなければ落下する必要がある。そこで、Tick イベント処理にて、FreeFall メソッドをアニメーションなしで呼び出し、落下できる場合は落下するように変更を加えた。
以上を実装した結果がこちら。
次の記事
【UE5 C++】Unreal Engine で テトリス風ゲームを作ってみる#9
前回のつづき
前回は、レベルとスコア、UIについて確認した。今回はテトリミノの高速落下とステート管理について確認する。
Unreal Engine Version 5.3.2
テトリミノのステート管理
ステート管理方法は、Unity での実現方法と差分はない。Enum でステートを定義して、Actor::Tick メソッドや、入力イベントメソッドの中で場合分けして動作を変えればよいだけだ。結局はロジックで作成する部分はUnity でも Unreal Engine でも考え方は同じでよい。
- Tetrimino のステート定義
各ステートの定義はUnity の時と同じ。
enum class EState : int { IDLE, DROP, FASTDROP, LOCKED, MAX};
- TetriminoのTickメソッド
Unity だとMonobehaviour のUpdate メソッドの中で振り分けていたが、Unreal Engine では、Tick メソッドの中でステート処理を振り分ける。
- void ATetrimino::Tick(float DeltaTime)
- {
- Super::Tick(DeltaTime);
- TotalDeltaTime += DeltaTime;
- switch (State)
- {
- case EState::IDLE:
- TickEventIdleState();
- break;
- case EState::DROP:
- TickEventInDropState();
- break;
- case EState::FASTDROP:
- TickEventInFastDropState();
- break;
- case EState::LOCKED:
- TickEventInLockedState();
- break;
- default:
- break;
- }
- }
高速落下
高速落下状態(EState::FASTDROP)に入るイベントはDown キー入力イベントで、そこでステートを変更し、高速落下中は落下時間をFAST_SPEED_RATE 分だけ短縮して速度を上げる。
- // Down キーイベント
- void ATetrimino::OnMoveDown()
- {
- State = EState::FASTDROP;
- }
- // 高速落下状態処理
- void ATetrimino::TickEventInFastDropState()
- {
- if (TotalDeltaTime > FallSec*FAST_SPEED_RATE)
- {
- auto isDrop = FreeFall();
- TotalDeltaTime = 0.0f;
- if (!isDrop)
- {
- State = EState::LOCKED;
- if (Listener != nullptr)
- Listener->OnLocked();
- }
- }
- }
高速落下を実装したプロトがこちら。
次の記事
(準備中)
【UE5 C++】Unreal Engine で テトリス風ゲームを作ってみる#8
前回のつづき
前回は、テトリミノ着地後のライン削除処理について確認する。今回は、レベルとスコア、UIについて確認する。
Unreal Engine Version 5.3.2
UMG(Unreal Motion Graphics)
レベルとスコアを計算するロジック部分の実装は、Unity とUnreal Engine でそれほど差分はない。問題はUI である。
Unity ではuGUI というUI システムがあり、Hierarchy ウィンドウ上から各種UI コンポーネントを生成することで、比較的簡単にUI を実装できる。例えば、テキストコンポーネントの文字列をスクリプトから変更したい場合などは、"SerializeField" を使って事前に該当のコンポーネントオブジェクトを設定しておけば、実行時にスクリプトからテキストコンポーネントのテキストメンバに直接アクセスして文字列を書き換えることができる。
ゲーム画面上でレベルやスコアを実現するには、少なくとも表示しているUI のテキストコンポーネントにC++ クラスからアクセスして文字列を書き換えるということが実現できなければならない。
Unreal Engine でもUMG というUI システムを使ってUI を実現する。UMG は、Widget Blueprint クラスと、Widget と呼ばれる各種UI コンポーネントから構成される。簡単に言うと、Widget Blueprint を使って各UI Widget を配置し、C++ クラスからActor Blueprint と同様にインスタンスを生成して画面に描画する。また、生成したインスタンスからUI Widget インスタンスを取得して、そのメンバを書き換えることで、UI 上のテキストなどをC++ クラスから変更することができる。
では、まずは、Widget Blueprint クラスを作成する。Content Browser 上で、"UMG" というフォルダを作成し、作成した空のフォルダ上で右クリックから"User Interface"→"Widget Blueprint" を選択する。"Widget Blueprint" クラスが作成されるので、作成したWidget Blueprint の名前を"WBP_LevelScore"に変更する。Widget Blueprint クラスのプリフィックスは"WBP_"
次に作成した"WBP_LevelScore" のアイコンをダブルクリックして、Widget Blueprint Editor を起動する。Widget Blueprint Editor では、各種UI Widget を視覚的にレイアウトすることができる。
今回は、Level とScore を表示するUIを作成する。まずはUI Widget の下地になる"Canvas Panel" をPalette のリスト(画面右側)から選択して、そのまま画面中央のグリッドにDrag & Dropする(緑の枠線がCanvas Panel)。
今度は、"Canvas Panel" 上に、"Horizontal Box"、その中に"Vertical Box"、その中に"Text" を入れ子状に上図のように配置する。うまくいかないときは、画面左下にある "Hierarchy" を使って入れ子状態を整理する。UI レイアウトが完成したら、"Compile" して "Save" するのを忘れずに実行し、Blueprint Editor を閉じる。
次にLevel とScore を管理するC++ クラスを作成する。このクラスを直接画面上に配置するわけはないのでActor ではなくUObject の派生クラスとして生成する。
メニューから"Tools"→"New C++"を選択し、"All Class" ボタンを押して、すべての派生元クラス一覧を出す。その中から"Object" を選択してクラスを作成する。(Object が UObject のこと)。
を使ってWidget と呼ばれる各種UI コンポーネント(UWidget クラスから派生したクラス)をEditor 上から配置し、Blueprint を使って制御することができる。もちろん、Actor Blueprint クラスのように、C++ クラスからWidget Blueprint のインスタンスをロードして、制御することも可能。
LevelScore.cpp の抜粋
まずは、Widget Blueprint クラスのインスタンスを生成するところは、Actor Blueprint と同様。Blueprint のパスをFSoftObjectPath に入れて、TSoftClassPtr のLoadSynchronous メソッドでロードする。ロードできたクラスをUUserWidget::CreateWidgetInstance メソッドを使ってインスタンス化し、できたUUserWidget インスタンスのAddToViewPort を呼び出して画面に表示する。ちなみにWidget Blueprint でレイアウトしたUI Widget は、UUserWidget クラスとして定義される。UUserWidget も Widget Blueprint でレイアウトした各UI Widget もUWidget クラスが親クラスになっている。
- void ULevelScore::Init()
- {
- // Load Widget
- auto path =
- FString(TEXT("/Game/TetrisLikeTest2/UMG/WBP_LevelScore.WBP_LevelScore_C"));
- auto widgetClass =
- TSoftClassPtr<UUserWidget>(FSoftObjectPath(*path)).LoadSynchronous();
- auto userWidget = UUserWidget::CreateWidgetInstance(*GetWorld(), widgetClass, NAME_None);
- userWidget->AddToViewport(0);
- LevelTextBlock = Cast<UTextBlock>(userWidget->GetWidgetFromName(TEXT("LevelText")));
- ScoreTextBlock = Cast<UTextBlock>(userWidget->GetWidgetFromName(TEXT("ScoreText")));
- }
最後に、UUserWidget のインスタンから、TextBlockのインスタンスを取得する。これには、UUserWidget::GetWidgetFromName メソッドを使って、Widgetの名前から取得することができる。Widget の名前は、Widget Blueprint Editor 上で名前を付けたいUI Widget を選択して右側にあるDetails タブで自由に変更することができる。
今回、Level の値を表示するText Widget には、"LevelText"。Socre の値を表示するText Widget には、"ScoreText" と名前を付けておく。
最後は、Text Widget の文字列をC++ クラスから変更する処理について。
LevelScore.cpp の抜粋
上記で取得しておいたTextBlock のインスタンから、SetText メソッドを使えば、指定した文字列に変更することができる。この辺りの考え方はUnity と同様である。
- void ULevelScore::UpdateLevelText()
- {
- LevelTextBlock->SetText(FText::FromString(FString::Printf(TEXT("%02d"), Level)));
- }
- void ULevelScore::UpdateScoreText()
- {
- ScoreTextBlock->SetText(FText::FromString(FString::Printf(TEXT("%06d"), Score)));
- }
ここまでプロトした結果がこちら。テトリスの腕がいまいちなので、Level とScore が上がるのに時間がかかって申し訳ないが、一応UI の更新もできていることが確認できる。
次の記事