C++ ときどき ごはん、わりとてぃーぶれいく☆

USAGI.NETWORKのなかのひとのブログ。主にC++。

UE4: コンポーネントをアタッチする状況により潜む罠

今回紹介する「コンポーネントのアタッチ状況により潜む罠」は3つ以上のコンポーネントを"縦"にアタッチ(†)する場合に生じる可能性のある罠です。

例として、2つの USceneComponent と1つの UStaticMeshComponent を"縦"にアタッチする状況を幾つか挙げます。この様な状況は1つの AActorRootComponent(=USceneComponent)と他の USceneComponentUAudioComponent などでも同様です。

Note: この記事では煩雑を避ける目的でコード例から一般的な実装では組み込むがこの記事については直接関係しないエラーチェックなどを省きます。また、一般的には .cpp へ実装する cotr の実装も .h へ直接記述しています。

Case 1: すべて1つの ctor で CreateDefaultSubobject で生成する場合

次のコード例のように関連するコンポーネントがすべて1つの ctor で生成されるような基礎的な実装では「罠」は生じません。

UCLASS()
class AMyActor: AActor
{
  GENERATED_BODY()
public:
  AActor()
  {
    // Create; S2, M1
    S2 = CreateDefaultSubobject< USceneComponent >( "S2" );
    M1 = CreateDefaultSubobject< UStaticMeshComponent >( "M1" );
    static ConstructorHelpers::FObjectFinder< UStaticMesh > m( TEXT( "/Game/MyMesh.MyMesh" ) );
    M1->SetStaticMesh( m.Object );
    // Attach; RootComponent, S2, M1
    S2->SetupAttachment( RootComponent );
    M1->SetupAttachment( S2 );
  }
  USceneComponent* S2 = nullptr;
  UStaticMeshComponent* M1 = nullptr;
};

ちなみに、 RootComponentS2 をアタッチしてから S2M1 をアタッチしようと、あるいは S2M1 をアタッチしてから RootComponentS2 をアタッチしようと、コンポーネントSetStaticMesh など準備を先に済ませてからアタッチしようと、アタッチした後でコンポーネントの準備を行おうと、何れも ctor を抜けた後の動作には変わりは生じません。それは罠にならないので1つ安心しましょう。

Case 2: すべて ctor 以外で NewObject で生成する場合

次のコードのような場合も「罠」は"まだ"生じません。

// AMyActor.h
UCLASS()
class AMyActor: AActor
{
  GENERATED_BODY()
protected:
  void Tick( float ) override;
public:
  USceneComponent* S2 = nullptr;
  UStaticMeshComponent* M1 = nullptr;
};
// AMyActor.cpp
void AMyActor::Tick( float )
{
  if ( something_trigger )
  {
    // NewObject; S2, M1
    S2 = NewObject< USceneComponent >( this );
    S2->RegisterComponent();
    M1 = NewObject< UStaticMeshComponent >( this );
    M1->RegisterComponent();
    M1->SetStaticMesh( Cast< UStaticMesh >( StaticLoadObject( UStaticMesh::StaticClass(), nullptr, TEXT( "/Game/MyMesh.MyMesh" ) ) ) );
    // Attach; RootComponent, S2, M1
    S2->SetupAttachment( RootComponent );
    M1->SetupAttachment( S2 );
  }
};

Case 3: NewObjectCreateDefaultSubobject するコンポーネントを扱う場合

次の状況で「罠」が発生します。

  1. ctor で CreateDefaultSubobject により UStaticMeshComponent を生成し自らへアタッチする USceneComponent の派生クラス UMyScene のようなものがある。
  2. ctor 以外で NewObject により UMyScene のオブジェクト S2 を生成し、 S2RegisterComponent し、 RootComponent へアタッチする AActor の派生クラス AMyActor のようなものがある。

言葉で書くとやや混乱するかもしれませんが、わりと簡単/単純で "ありそう" な状況です。

// UMyScene.h
UCLASS()
class UMyScene: USceneComponent
{
  GENERATED_BODY()
  UMyScene()
  {
    // Create; M1
    M1 = CreateDefaultSubobject< UStaticMeshComponent >( "M1" );
    static ConstructorHelpers::FObjectFinder< UStaticMesh > m( TEXT( "/Game/MyMesh.MyMesh" ) );
    M1->SetStaticMesh( m.Object );
    // Attach; M1 -> this(≃S2)
    M1->SetupAttachment( this );
  } // Create; this(≃S2)
public:
  UStaticMeshComponent* M1 = nullptr;
};
// UMyActor.h
UCLASS()
class AMyActor: AActor
{
  GENERATED_BODY()
protected:
  void Tick( float ) override;
public:
  UMyScene* S2 = nullptr;
  UStaticMeshComponent* M1 = nullptr;
};
// ****** TRAPPED ******
// AMyActor.cpp;
void AMyActor::Tick( float )
{
  if ( something_trigger )
  {
    // NewObject; S2, M1
    S2 = NewObject< UMyScene >( this );
    S2->RegisterComponent();
    // Attach; RootComponent, S2, M1
    S2->SetupAttachment( RootComponent );
  }
};

このように実装した AMyActor の動作を確認すると、見えるはず、と意図したであろう UStaticMeshComponent は見えません。存在するのに存在しません。

この罠を抜け出すための"ヒント"を示します。

// ****** AVOID TRAP ( DIRTY ) ******
// AMyActor.cpp;
void AMyActor::Tick( float )
{
  if ( something_trigger )
  {
    // NewObject; S2, M1
    S2 = NewObject< UMyScene >( this );
    S2->RegisterComponent();
    // Attach; RootComponent, S2, M1
    S2->SetupAttachment( RootComponent );
    // ****** DIRTY IMPLEMENTATION ******
    S2->M1->RegisterComponent();
  }
};

状況と必要な対応がわかりやすいように "DIRTY" な応急対処を示しました。

  1. NewObject で生成されるオブジェクト S2 が、
  2. その S2 の ctor において CreateDefaultSubobject で生成したオブジェクト M1 がある場合、
  3. M1RegisterComponent されていない。(ので、そうならない設計にするか、対処可能な実装を施す必要が生じる事がある)

という「罠」です・x・

もう少し複雑な状況でうっかりはまって気付かない事があると精神がすり減る事もありそうです。

Appendix

(†): 「"縦"にアタッチ」は Component0 へ Component1 をアタッチ、 Component1 へ Component2 をアタッチするような、1つのコンポーネントへ複数のコンポーネントをアタッチする状況を"横"、1つのコンポーネントへ1つのコンポーネントをアタッチし、アタッチしたコンポーネントへまた1つのコンポーネントをアタッチする状況を"縦"と模して表現したものです。

References

UE4: 4.18 で追加された VSCode プロジェクト生成に少しだけ追加すると便利な事

1. launch.json<ProjectName>Editor (DebugGame)args-game を追加する

UE4Editor-game を追加して実行すると、 UE4EditorIDEユーザーインターフェース類を表示せずに擬似的に直接プロジェクトのバイナリーを実行したのと同様の状態でデバッグ実行を開始できます。

この時、既存の起動中の UE4Editor のプロセスとは別のプロセスで実行できるので、 C++ コードの実装中にたまにある「もしかしたら落ちるかも」の確認を起動中の UE4Editor とは別に安全に行いつつ、わざわざデバッグ実行のためにIDEユーザーインターフェースの表示などをスキップして実行できるので少し開発効率が良くなります。

2. launch.jsonAttach プロファイルを追加する

既存の起動中の UE4Editor にも VSCode からアタッチしてデバッグし、エディターの画面内でプレイ中に VSCode でブレークしてステップ実行するなどできます。以下のプロファイルを追加しておき、動作中の UE4Editor へアタッチしたい場合に使います。

{ "name": "Attach"
, "type": "cppvsdbg"
, "request": "attach"
, "processId": "${command:pickProcess}"
}

アタッチの場合は、既に UE4Editor が動いている状態でデバッグを即開始できるので、 -game オプション付きであれ別プロセスで起動するよりもデバッグ開始までの所要時間がかかりません。但し、メインでブループリントやマテリアルを編集している UE4Editor へアタッチしてデバッグ実行していても、アレなC++コードで落としてしまうとどうにもなりません。不安のあるコードや、落ちるバグをどうにかしたいデバッグ実行の際には(1)の -game 付きの別プロセス実行を行い、そうでもない場合には(2)で素早くアタッチしてデバッグすると捗るかもしれません。

UE4: 4.18 で VSCode に UE4Editor が対応したのでロストテクノロジーをメモしておく

Note: この記事は UE4Editor の公式対応で VSCode では不要になるロストテクノロジーについてメモするものです。 UE4Editor の VSCode 対応については参考1、参考2をどうぞ。

私はこれまでもVSCodeに幾つかのコマンドラインをカスタム定義して与えてUE4のプロジェクトをVSCodeで書いていましたが、全自動で .vscode に必要な定義を作ってくれるようになって楽で良いですね。

参考としてVSCodeでもVimでもUE4Editorが対応しないエディター環境やシェル直でコマンドラインしたい場合は次のように仕込みor叩きます。とりあえず .bat 風に紹介。

environment.bat // 共通の変数

set UNREALVERSIONSELECTOR="C:\Program Files (x86)\Epic Games\Launcher\Engine\Binaries\Win64\UnrealVersionSelector.exe"
set UPROJECT="<path-to-uproject>.uproject"
set BUILD="C:\Program Files\Epic Games\UE_4.18\Engine\Build\BatchFiles\Build.bat"
set PROJECTNAME="<your-project-name>"
set PROJECTNAMEEDITOR="<your-project-name>Editor"

genrate.bat: // 新しいソースを手追加したりした時に使う

call environment.bat
%UNREALVERSIONSELECTOR% /projectfiles %UPROJECT%

build.debug.editor:

call environment.bat
%BUILD% %PROJECTNAME% Win64 DebugGame %UPROJECT% -waitmutex

build.development.editor: // ほか editor なしや shipping も同様

call environment.bat
%BUILD% %PROJECTNAMEEDITOR% Win64 Development %UPROJECT% -waitmutex

launch.json:

// Attach; 主に UE4Editor へアタッチして使う
{ "name": "Attach"
, "type": "cppvsdbg"
, "request": "attach"
, "processId": "${command:pickProcess}"
}
// Debug; VSCodeからデバッグ用に擬似的なスタンドアローン起動風にデバッグ実行したい場合はこんな感じ
"name":"Debug"
, "type": "cppvsdbg"
, "request": "launch"
, "program": "C:/Program Files/Epic Games/UE_4.18/Engine/Binaries/Win64/UE4Editor.exe"
, "args": [ "<path-to-uproject>.uproject", "-debug", "-game" ]
, "cwd": "${workspaceRoot}"
}

こういうのに相当するゴニョゴニョを VSCode でも UE4Editor 側で設定すれば手書きしなくても快適かつ必要に応じて更新してくれるようになった、という話。

手書きする必要が無くなるとロストテクノロジー化するので必要になった時に掘り出すのが面倒になりそうなので備忘録として、あるいはVimZshを使ってUE4したい人とかの参考にどうぞ。

参考

  1. https://www.unrealengine.com/en-US/blog/unreal-engine-4-18-released
  2. UE4 Visual StudioなしでVisual Studio CodeからC++プロジェクトを実行する - Let's Enjoy Unreal Engine

UE4: 4.18 で Static Mesh のコンテキストメニューから Create Destructible Mesh できなくなっていた件と解決方法

事象

4.17.2 ではコンテントブラウザーで任意の Static Mesh を右クリックして現れるコンテキストメニューにあった Create Destructible Mesh が 4.18.0 では消えてしまい、代わりに "Remove Vertex Colors" が出現しているように見える。

f:id:USAGI-WRP:20171027044351p:plain

原因

4.18.0 から Destructible Mesh はプラグインになった。

f:id:USAGI-WRP:20171027044902p:plain

解決

Apex Destruction プラグインを有効にして UE4Editor を再起動する。

f:id:USAGI-WRP:20171027045010p:plain

なお、 Remove Vertex Colors は関係の無い新機能で謎の現象により置き換わったわけではないので Create Destructible Mesh できるようになってもそれはそれでコンテキストメニューに居る。

f:id:USAGI-WRP:20171027045156p:plain

おまけ: タイムラインより

気づいた時にはバグかも?って思ったけどプラグイン化しただけだった。 Release Note に書いてくれよ感はある・x・

参考

  1. https://answers.unrealengine.com/questions/711506/no-destructible-meshes-in-418.html

UE4: 柄の付いた道具の当たり判定

1. 導入

例として「つるはし」の当たり判定を考えてみる。

f:id:USAGI-WRP:20171020041221p:plain

岩につるはしが当たって効果がある部分は"柄"(一般に木製の棒; stick)ではなく、その先に装着した"頭"(一般に鋼鉄製; head)の部分だけ。便宜上のゲーム表現では柄は当たり判定なし、頭だけ当たり判定ありで、「キャラクターがつるはしを振るうアクション」を行うとそんな当たり判定のつるはしオブジェクトがプレイヤーに合わせて出現するような実装をしたい場合がままあります。

リアリティーを求める場合は柄の当たり判定も実装して力のかかり具合と材質から柄がダメージを受け破壊されてしまう要求もあるかもしれませんが、今回はそこまでせず、古典的なアクションゲームにありがちな便宜上の当たり判定の実装をしたい事にします。

2. 前提となる状態

  1. つるはしの見た目のメッシュデータ Tsuruhashi を UE4 でインポートできている
  2. Tsuruhashi をメッシュとして設定した StaticMeshActor の Tsuruhashi_Blueprint を配置できている

例示の簡単のためモデルデータをレベルエディターへドラッグアンドドロップすれば生成される状態で紹介します。 C++ コードで実装する場合、実行中に動的にオブジェクトを生成する場合も本質的には同様です。

3. つるはしの頭の部分にだけ当たり判定を付ける

  1. Tsuruhashi_Blueprint オブジェクトの RootComponent の StaticMeshComponent に +Add Component で Cube を追加する
  2. 追加した Cube の Tsuruhashi_Blueprint オブジェクト内での Location と Scale を適当に設定して当たり判定としたい空間を占めるように設定する
  3. 追加した Cube のプロパティー Collision の
    1. Generate Overlap Events が ON になっている事を確認(デフォルトでON)
    2. Collision PresetsOverlapAllDynamics に設定
  4. Tsuruhashi_Blueprint オブジェクトの RootComponent の StaticMeshComponent のプロパティー Collision の
    1. Generate Overlap Events を OFF に設定
    2. Collision PresetsNoCollision に設定
  5. Tsuruhashi_Blueprint オブジェクトを Edit Blueprint して Event Graph に Event ActorBeginOverlap に当たり判定に伴う処理を実装する
    • note: 動作確認目的では Event ActorBeginOverlapOther Actor から Get Object Name して Print String すれば十分
  6. 動作確認が済んだら Cube のプロパティー Rendering の Visible を OFF に設定

以上のように当たり判定用に Cube あるいは必要に応じて Sphere や Cone などをコンポーネントとして追加して組み立てると一部分に当たり判定を持つ「つるはし」のような道具の実装は簡単にできる。

f:id:USAGI-WRP:20171020051120p:plain

f:id:USAGI-WRP:20171020051006p:plain

f:id:USAGI-WRP:20171020051203p:plain

4. 応用

  • プレイ中の視認によるデバッグ目的や、エンドユーザーへ当たり判定の表示機能を提供したい場合は §3 で追加した Cube の Visible を適当なトリガーで ON/OFF できるようにし、マテリアルも当たり判定の可視化用に適当な半透明の黄色ベタなどを設定するとよい。
  • 当たり判定の可視化が UE4Editor の開発環境の表示機能だけで十分でメッシュとマテリアルによる表示が不要な場合は StaticMeshComponent の Cube や Sphere ではなく Box CollisionSphere CollisionCapsule Collisionコンポーネントとして追加するとよい。
    • note: C++ コードの場合は UBoxComponentUSphereComponentUCapsuleComponent を ctor 等で生成して SetupAttachment する。
  • アクター単位でなくコンポーネント単位で当たり判定を行いたい場合は Event Graph で Event ActorBeginOverlap を定義せずに Bind Event to OnComponentBeginOverlap を定義し、その TargetCube コンポーネントを与え、 EventOnComponenBeginOverlap_Cube などお好みのユーザー定義イベントを束縛させるとよい。
    • note: C++ コードの場合は OnComponentBeginOverlap.AddDynamic で実装する。

f:id:USAGI-WRP:20171020050834p:plain

f:id:USAGI-WRP:20171020051220p:plain

5. 参考

  1. UShapeComponent | Unreal Engine API Reference
  2. Unreal Engine | イベント
  3. Event ActorBeginOverlap | Unreal Engine Blueprint API Reference
  4. Unreal Engine | 5.オーバーラップしているアクタをテストする
  5. OnComponentBeginOverlap | Unreal Engine API Reference
  6. Unreal Engine | 動的デリゲート
  7. UE4: C++ クラスで実装したアクターのコンポーネントの OnComponentBeginOverlap.AddDynamic 等がうまく動作しない時に確認すること - C++ ときどき ごはん、わりとてぃーぶれいく☆

スマートフォンが落下などにより画面を使用不能に陥った場合の対処方法

Zenfone Go がトイレへ駆け込む際にトイレットペーパーを補充しようとしている間に棚から落ちて画面が死にました。

f:id:USAGI-WRP:20171018204134j:plain

まあ、操作できません。しかし、幸いな事にこの Android 端末は USB デバッグ機能が ON に設定されています。そこで Vysor を PC の Chrome へインストールし、 USB ケーブルでこの画面の死んだ Zenfone Go を PC に接続します。

f:id:USAGI-WRP:20171018203615p:plain

接続すると Chrome の Vysor が自動的に Android 側に必要なパッケージのインストールを行い、 PC からポインティングデバイスで操作可能な状態になります。キーボードは International を長押ししてインストールされている Vysor に切り替えると日本語入力含め使用できますが、 Vysor が死ぬことがしばしばありましたので、ポインティングデバイスだけでぽちぽち操作するのが安全なようです。

一部のアプリケーションでは新しいスマートフォンを購入した後にアプリ専用のデータの移行方法が必要などありますから重宝します。

だそく

さて、やはり CAT S60 が欲しいのですが日本では公式な取り扱いがありません。 S40 は安いけど機能、性能的にしょぼいし。 Zenfone のツルツルデザインは滑って落ちやすい事が今回の件以外でもしばしば不便に思っていましたので Zenfone4 など買うかは少々微妙な心境です。

参考

  1. Vysor

UE4: C++ クラスで実装したアクターのコンポーネントの OnComponentBeginOverlap.AddDynamic 等がうまく動作しない時に確認すること

C++ でアクターを実装し、 StaticMeshComponent のオーバーラップイベントを受けたい、と思ったがなぜか StaticMeshComponentOnComponentBeginOverlap.AddDynamic した thisTriggerEnter が呼ばれない。」

そんな時には UFUNCTIONTriggerEnter 等についているか確認するとあっさり問題が解決するかもしれません。

// my_actor.h
public:
  // ↓ この UFUNCTION() をうっかり定義し忘れると期待動作しない。
  UFUNCTION()
  void TriggerEnter(class UPrimitiveComponent* HitComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult);

他には、より単純に bGenerateOverlapEventsSetCollisionProfileName に適切な値を放り込んでいない場合もありますが、これはうっかりという事も少ないとは思います。コード備忘録も兼ねて一応紹介。

// my_actor.cpp

AMyActor::AMyActor()
{
  auto mesh_component = CreateDefaultSubobject< UStaticMeshComponent >( TEXT( "SomethingMesh" ) );
  if ( mesh_component )
  {
    // オーバーラップさせたいのにオーバーラップしないプロファイルではもちろん期待動作しない。
    mesh_component->SetCollisionProfileName( TEXT( "OverlapAllDynamic" ) );
    // オーバーラップイベントが欲しいのにイベント発生フラグをONにしていなければもちろん期待動作しない。
    mesh_component->bGenerateOverlapEvents = true;
    // 宣言にUFUNCTIONが付いている事を確認しつつオーバーラップイベントにファンクター(UE的にはデリゲート)を放り込む。
    mesh_component->OnComponentBeginOverlap.AddDynamic( this, &AMyActor::TriggerEnter );
    
    RootComponent = mesh_component;
  }
}

参考

  1. OnComponentBeginOverlap | Unreal Engine API Reference
  2. bGenerateOverlapEvents | Unreal Engine API Reference
  3. UPrimitiveComponent::SetCollisionProfileName | Unreal Engine API Reference