UE4勉強会ではBlueprintのみでブロック崩しを作っていました。
Blueprintの力の一端を見ることができたわけですが、Blueprintを使わなかったらどれだけ大変なのか?
他の参加者がBPを使ってブロック崩しを作成することは容易に想像がついたので、私はあえてBPを使わないで作成してみることにしました。
どういう部分をC++で作ればよいか、BPに対するメリット・デメリットはなんなのか、などの疑問には実際にやってみるのが一番だろう、というのも理由の1つです。
今回試したものについては以下の制約を加えました。
1.Blueprintは使わない
パーティクルエフェクトくらいは使おうかとも思ったのですが、結局使っていません
2.リソースの指定をC++コード内では行わない
レベルデザイナがレベルごとに設定できるようにしておくべきだろう、という理由からです
今回の記事は作成手順だけでなく、自分が嵌ったところや気を付けるべきところなども書いていこうと思います。
なお、プログラマ以外の方はちんぷんかんぷんかもしれませんがご了承ください。
では、まずはプロジェクトを作成しましょう。
今回もBlankプロジェクトではなく、BasicCodeプロジェクトを作成します。
名前は何でもいいでしょう。
作成が終わったらUE4勉強会の時と同じようにボールが反射する枠を作成します。
Brushで当たりをつけ、これをBlockingVolumeに変換、その後、そこに壁となるモデルを割り当てます。
枠ができたらBrushでパドル、ボール、ブロックのStaticMeshを作成します。
ここもUE4勉強会と同じです。
作成した3つのメッシュはレベルエディタから取り除いておきましょう。
UE4勉強会の模様はYoutubeにアップされていますので、そちらをご覧ください。
ここまでの手順はUE4勉強会で実際に行われています。
http://www.youtube.com/watch?v=roumUAQqVps
さて、ここからが勉強会と大きく違うところです。
まずはコードを追加します。
コードの追加は[File] -> [Add Code to Project]で行います。
今回作成するクラスはこの段階ですべて作成しておきます。
作成するのは以下の5つです。左側が作成するクラス名、右側が継承元クラス名です。
・MyWorldSettings : WorldSettings
・MyGameMode : GameMode
・MyBall : Actor
・MyBlock : Actor
・MyPaddle : Pawn
ここで注意点。
[Add Code to Project]で作成するクラス名を設定すると、同名の.h, .cppファイルが作成されます。
しかし、実際のクラス名は指定した名前の先頭にAがつくものになっています。
つまり、MyBallならAMyBallクラスになります。
このAという接頭辞はUE4ではActorを意味する接頭辞です。Actorを継承するクラスはすべてこの接頭辞がつきます。
ちなみに、Actorも正式なクラス名はAActorです。
今回作成したクラスはすべてActorを継承しているのでAの接頭辞がついています。
また、A以外ではUがついているクラスもありますが、こちらはActorを継承していないクラス全般のようです。
UStaticMeshやUSkeletalMeshComponentなどが接頭辞Uですね。
では、それぞれのクラスの役割を見ていきましょう。
MyWorldSettingsはLevelごとに必要な情報をC++側に伝えるために使用しています。
今回はパドルとボールに使用するStaticMeshコンテンツを指定するために使用しています。
ただし、このクラスを作成しただけではデフォルトのWorldSettingsから変更ができません。
[Edit] -> [Project Settings]で変更する必要があります。
赤で示している部分でWorldSettingsを変更できます。
MyWorldSettingsは.cpp側への変更はありません。
.hに2つのプロパティを設定しているだけです。
UCLASS() class AMyWorldSettings : public AWorldSettings { GENERATED_UCLASS_BODY() UPROPERTY(EditDefaultsOnly, Category = ActorAndMesh) UStaticMesh* BallMesh; UPROPERTY(EditDefaultsOnly, Category = ActorAndMesh) UStaticMesh* PaddleMesh; }; |
ボール用とパドル用のUStaticMeshを追加しています。
こうすると[World Settings]タブに以下のような項目が加わります。
ドロップダウンリストを利用してStaticMeshコンテンツを設定することが可能です。
存在しないコンテンツは選択できませんし、文字列で書かせるよりよっぽど間違いがなく安心です。
次はMyGameModeです。
こちらはゲーム開始時にカメラを設定し、パドルを生成するために作成しています。
つまりBeginPlay()を実装するために作成しているというわけです。
また、デフォルトのGameModeではDefaultPawnが生成されてしまうので、これをPawnが作成されるようにしたかった、というのもあります。
最終的にはこのPawnもパドルにとってかわられるわけですが…
MyGameModeの.hはBeginPlay()を宣言しているだけなので割愛します。
.cppは以下のように変更しています。
AMyGameMode::AMyGameMode(const class FPostConstructInitializeProperties& PCIP) : Super(PCIP) { DefaultPawnClass = APawn::StaticClass(); } void AMyGameMode::BeginPlay() { Super::BeginPlay(); UWorld* const world = GetWorld(); if (world) { AMyWorldSettings* settings = Cast<AMyWorldSettings>(world->GetWorldSettings()); if (settings) { // パドル設定 FVector location(0.0f, 0.0f, 80.0f); AMyPaddle* pawn = Cast<AMyPaddle>(world->SpawnActor(AMyPaddle::StaticClass(), &location)); pawn->SetStaticMesh(settings->PaddleMesh); world->GetFirstPlayerController()->ClientRestart(pawn); } // カメラ設定 for (TActorIterator<ACameraActor> Itr(world); Itr; ++Itr) { if (Itr->ActorHasTag("MainCamera")) { world->GetFirstPlayerController()->SetViewTargetWithBlend(*Itr); break; } } } } |
コンストラクタでDefaultPawnClassをPawnに変更しています。
MyPaddleに変更する方が正しいと思うのですが、PlayerPawnを変更する方法を試してみたかったのでこうしています。
BeginPlay()ではWorldSettingsをMyWorldSettingsにキャストして、そこからパドル情報を持ってくるようにしています。
WorldSettingsが変更されていない場合はパドルが生成されません。
パドルの初期位置は直値で指定していますが、本来はよろしくありませんので注意してください。
PlayerPawnの変更はAPlayerController::ClientRestart()を利用しています。
APlayerController::SetPawn()でもよかったのかもしれませんが、操作関係の初期化を行うためにClientRestart()を使いました。
ただ、ClientRestart()はカメラ設定も変更してしまうため、パドル設定を行った後にカメラ設定を行っています。
カメラ設定はLevelに配置されたメインカメラを取得してSetViewTargetWithBlend()で設定しています。
ブレンドの必要がなければSetViewTarget()を使ってもかまいませんし、今回はブレンドしてないので本来そっちの方がいいでしょう。
Levelに配置された特定の型のActorを取得したい場合はTActorIterator<>を使用します。
今回はACameraActorを列挙して、その中からMainCameraとタグ付けされたカメラを設定するようにしました。
タグはActorの実体に対して文字列として設定することができます。複数設定することも可能です。
Level Editorでは[Details]タブのActorパネルの隠し要素として存在しています。
Tagsの+ボタンを押すと順次追加することができます。
まだまだ説明が長くなりそうなので、今回はここまでで一旦切ります。
次回はパドル、ボール、ブロックの説明になりますが、長くなるようならそちらも分割します。