【UE5 C++】Unreal Engine で テトリス風ゲームを作ってみる#3
前回のつづき
前回は、複数のブロックをまとめてテトリミノとして表示させるところまで確認した。今回は、テトリミノの落下動作とブロックとの衝突判定について確認する。
Unreal Engine Version 5.3.2
テトリミノの落下動作
Unity では、以前説明したとおり、Update / FixedUpdate コールバック関数にて経過時間を計測し、落下時刻が経過した場合は、テトリミノを一段下に移動するということで落下を実現可能した。
Unity で テトリス風ゲームを作ってみる#5 - 独習 Unity アプリ開発
Unreal Engine でも同様に、AActor::Tick というコールバック関数が毎フレームごとに呼ばれるため、このコールバックで経過時間を計測し、落下時刻が経過したことを検出する。テトリミノの落下移動は、AActor::SetActorLocation を使って1ブロック分下に移動させることで実現できる。
Tetrimino.cpp の抜粋
Tick 関数の引数DeltaTime は、前回の呼び出しからの差分を秒単位で表しているので、それを積算して落下時間(FALL_SEC)以上たった時に、落下処理を呼びだす。
- void ATetrimino::Tick(float DeltaTime)
- {
- Super::Tick(DeltaTime);
- TotalDeltaTime += DeltaTime;
- if (TotalDeltaTime > FALL_SEC) {
- FreeFall();
- TotalDeltaTime = 0.0f;
- }
- }
- void ATetrimino::FreeFall() {
- SetActorLocation(
- FVector(
- GetActorLocation().X,
- GetActorLocation().Y,
- GetActorLocation().Z - BLOCK_SIZE));
- }
自由落下の確認結果。(真ん中辺に差し掛かった時に●が見えるのは、DefaultSpawnの設定をちゃんとできていないためゴミが見えているため)
衝突判定
Unity では、テトリミノとブロックの衝突判定にRaycast とCollider の仕組みを利用した。
Unity で テトリス風ゲームを作ってみる#3 - 独習 Unity アプリ開発
Unreal Engine においても、Raycast に相当するLineTraceXXX というAPIがUWorld クラスに存在する。実際には、最初に検知したものを返すSingle とOverrap も含めて複数検知したものを返すMulti の2 種類に, 検出タイプを指定するChannel, Object Type, Profile の3 種類を掛け合わせて、6 つのAPI が定義されている。
ここで、コリジョン関係の用語がややこしいので、ちょっと整理する。
-
- Object Type
Object Typeは、自分自身のコリジョンの種別のこと。相手側のObject Type を表すときには、チャネルと呼ばれる(と思っている)。UPrimitiveComponent::SetCollisionObjectType APIでC++からも設定可能。BluePrint Editor 上では下記の項目で設定できる(Preset Custom の場合のみ指定可能)。ちなみにUnity と異なり、PaperSprite コンポーネント (Unity で言うところのSpriteRenderer) にCollision の機能がついている。これは、PapaerSprite コンポーネントが、UPrimitiveComponent クラスから派生したコンポーネントのため。
Object Type に設定できる値としては、WorldStatic, WorldDynamic, Pawn などがいくつか予め用意されている。それに加え、新しいObject Type を自分で追加することもできて、メニューの"Edit" → "Project Settings" → "Engine" → "Collision" の項目の、"Object Channels" → "New Object Channel" から追加できる。(Channel と言っているが Object Type でもある)
- Profile
Object Type(チャネル)別の衝突レスポンスのタイプを定義したもの。
衝突した相手のObject Type(チャネル)別に自分のレスポンスをどうするかを設定できる。レスポンスには ignore(無視する), Overlap(重なる), Block(ブロックする)の3つから選べる。ignore と Overlap の違いは、衝突時にイベントが発生するかしないかの差と思われる(?)。これによって例えば草のようなオブジェクトは無視するが、壁のようなオブジェクトには跳ね返ると言った挙動をさせることが可能になる。
下図の例では、設定がグレーアウトされているが、これは"Collision Presets" でBlockAll というプリセットを指定しているため。"Custom" を選択すれば、自由に変更することができる。独自のObject Type(チャネル)を作った場合には、一番したに表示されている。
-
- プリセット
予め定義されているプロファイル。
レスポンスに設定できるプロファイルは予めいくつか定義されており、No Collision, BlockAll, OverlapAll などいろいろとある。先に書いたように、プリセットを使わずにCustom を選択して、一つ一つのObject Type(チャネル)ごとに設定することも可能。
-
- LineTrance API
ここで再度LineTrace API の定義を見てみるとObject Type と Channel が別のAPIとなっているが、どちらも最終的にはChannel で指定できるようなIF になっており、2つ存在する理由がよくわからない。。。LineTrace API の末尾のbyXXX の部分は、LineTrace自身のObject Type, Channel, Profile を示している(衝突対象ではなく、レーザー自身)。よってLineTranceを使って何を検出するかのフィルタ指定ではないことに注意。
UWorld::LineTraceSingleByObjectType
Trace a ray against the world using object types and return the first blocking hit
UWorld::LineTraceSingleByChannel
Trace a ray against the world using a specific channel and return the first blocking hit
UWorld::LineTraceSingleByProfile
Trace a ray against the world using a specific profile and return the first blocking hit
今回は、テトリミノブロックと、フィールド上のブロックの2つのObject Type を追加して、衝突検出がうまくいくか確認してみる(メニューの"Edit" → "Project Settings" → "Engine" → "Collision" の項目の、"Object Channels" → "New Object Channel")
なお、自分で追加したObject Type(チャネル)は、C++上ではECollisionChannel enumのECC_GameTraceChannel1 ~ 18のいずれかに割り当てられる。何に割り当てられたかは、下記の.ini ファイルから確認が可能。LineTraceSingleByChannel の引数にこの値を指定して検出対象と当たり判定を行う。
(Unreal Engine Project folder path)/Config/DefaultEngine.ini 抜粋
BP_Block のParperSprite コンポーネントのCollision 設定は、下記のように"Collision Presets" を "Custom"に変更し、自分の"Object Type" を "OtherBlock" に変更。"Collision Response" は、"TetriminoBlock" と "OtherBlock" の両方に"Block"を設定。それ以外は"Ignore" とした。これで、LineTrance で "OtherBlock" か "TetriminoBlock" を指定すると衝突が検知できる(それ以外は無視される)。
Tetrimino.cpp の抜粋
テトリミノを構成するブロックは、LineTrace の判定にレスポンスしないように、UPrimitiveComponent::SetCollisionEnabled APIを使ってコリジョンを無効化(ECollisionEnabled::NoCollision)しておく。
CollisionCheck 関数は、テトリミノの各ブロックから下方向にLineTrace を打ち、ブロックがあるかどうかを判定する。考え方自体は、Unityのときと同じ。
Unity で テトリス風ゲームを作ってみる#3 - 独習 Unity アプリ開発
Unity で テトリス風ゲームを作ってみる(実装編)#2 - 独習 Unity アプリ開発
LineTraceの結果が、"true" の場合はブロックを検知したということなので落下しないようにしている。
- void ATetrimino::CreateBlocks(EType type) {
- UE_LOG(LogTemp, Display, TEXT("TetriminoComponent CreateBlocks()"));
- // Folder name should be Game instead of Content
- FString path =
- FString(TEXT("/Game/TetrisLikeTest1/BluePrints/BP_Block.BP_Block_C"));
- TSubclassOf<class AActor> blockbp =
- TSoftClassPtr<AActor>(FSoftObjectPath(*path)).LoadSynchronous();
- if (blockbp != nullptr) {
- for (int i = 0; i < BLOCK_NUM; i++) {
- AActor* pBlockActor = GetWorld()->SpawnActor(blockbp);
- pBlockActor->SetActorLocation(FVector(0, 0, 0));
- pBlockActor->AttachToActor(this, FAttachmentTransformRules::KeepRelativeTransform);
- TObjectPtr<UActorComponent> sp = pBlockActor->GetComponentByClass(UPrimitiveComponent::StaticClass());
- TObjectPtr<UPrimitiveComponent> primitive = Cast<UPrimitiveComponent>(sp);
- primitive->SetCollisionEnabled(ECollisionEnabled::NoCollision);
- BlockActors.Add (pBlockActor);
- }
- LocateBlocks(type);
- }
- }
- bool ATetrimino::CollisionCheck(FVector position) {
- FHitResult hitResult;
- FVector start = position + FVector(0, 0, 0); // Texture Pivot is Center
- FVector end = start + FVector(0, 0, -100);
- bool isHit = GetWorld()
- ->LineTraceSingleByChannel(hitResult, start, end, ECC_GameTraceChannel2);
- //DrawDebugLine(GetWorld(), start, end, FColor::Red, false, 1.0f, 0, 2.0f); // Debug
- return isHit;
- }
- void ATetrimino::FreeFall() {
- auto isHit = false;
- for (int i = 0; i < BLOCK_NUM; i++) {
- if (CollisionCheck(BlockActors[i]->GetActorLocation())) {
- isHit = true;
- break;
- }
- }
- if (!isHit) {
- SetActorLocation(
- FVector(
- GetActorLocation().X,
- GetActorLocation().Y,
- GetActorLocation().Z - BLOCK_SIZE));
- }
- }
実際に動作させた動画がこちら。
次の記事
Unreal Engine で テトリス風ゲームを作ってみる#4 - 独習 Unity アプリ開発