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

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

UE4 のコンソールにユーザー定義のコマンドを追加する方法

テンプレプロジェクトでは @ キーででてくる便利なコンソールにユーザー定義のコマンドを追加する例:

// MyActor.h
// ...
public:
  // コンソールから呼びたい UFUNCTION の宣言
  UFUNCTION( BlueprintCallable ) void hoge();
// ...
AMyActor::AMyActor()
{
  // ...
  
  if ( !IsRunningCommandlet() )
  {
    IConsoleManager::Get().RegisterConsoleCommand
    ( TEXT( "hoge" ) // <-- コマンド名
    , TEXT( "hoging to log." ) // <-- コマンドの説明
    // ↓コマンド本体; 今回は UFUNCTION の this の "hoge" を呼ぶ
    , FConsoleCommandDelegate::CreateUFunction( this, TEXT( "hoge" ) )
    , ECVF_Default
    );
  }
}

void AMyActor::hoge()
{
  UE_LOG( LogTemp, Log, TEXT( "hoge!" ) )
}

↓プレイ中の @hoge コマンドが使えるようになり、

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

↓実行すると結果的に AMyActor::hoge() が実行されログに "hoge!" が出力される。

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

コマンドとして登録するファンクター(相当)のそれは UFUNCTION を放り込む FConsoleCommandDelegate::CreateUFunction に限らず、 lambda expression を放り込む FConsoleCommandDelegate::CreateLambda なども使える。

IConsoleManager::Get().RegisterConsoleCommand
( TEXT( "hoge" )
, TEXT( "hoging to log." )
// ↓ラムダも放り込めて便利。
, FConsoleCommandDelegate::CreateLambda( [ & ] { hoge(); } )
, ECVF_Default
);

C++er的にはとても簡単にコンソールに"オレオレコマンド"を追加できるので開発が捗りますね。

参考

地上を歩けて、空も飛べるキャラクター( ACharacter )の C++ な作り方

C++/TPSのテンプレプロジェクトを作ってソースを眺めると「地上を歩けるキャラクター」の作り方がわかります。そこで、追加要素として「この ACharacter を空も飛べるようにしたい」と思っても、エディターで “Can Fly” をチェックONするだけではまったく飛べないので、どうしたらよいか整理します。

歩く、飛ぶの切り替え方法

ACharacter::GetCharacterMovementUCharacterMovementComponent を取得して、 SetMovementMode に移動モードの定数を放り込む。

// --> 飛行移動モード
GetCharacterMovement()->SetMovementMode( MOVE_Flying );
// --> 歩行移動モード
GetCharacterMovement()->SetMovementMode( MOVE_Walking );

歩く、飛ぶの他に今回の記事でも使う落下なんかもある。他にも泳ぐとかあるけど今回は使わないので説明略。

// --> 落下移動モード
GetCharacterMovement()->SetMovementMode( MOVE_Falling );

ジャンプをトリガーにして飛ばす

  1. ジャンプ中にもう一度ジャンプ入力が来たら飛ばす。
  2. 飛行中にジャンプ入力が来たら落下させる。
  // AtpsCharacter::Jump をオーバーライド
  virtual void Jump() override;
void AtpsCharacter::Jump()
{
  // 1. 落下移動モードでジャンプ入力 --> 飛行移動モードへ移行
  if ( GetCharacterMovement()->IsFalling() )
    GetCharacterMovement()->SetMovementMode( MOVE_Flying );
  // 2. 飛行移動モードでジャンプ入力 --> 落下移動モードへ移行
  else if ( GetCharacterMovement()->IsFlying() )
    GetCharacterMovement()->SetMovementMode( MOVE_Falling );
  // その他の移動モード(歩行など)でジャンプ入力 --> ジャンプ
  else
    Super::Jump();
}

これで AtpsCharacter は最低限、歩いて飛べるキャラクターになる。

上昇/降下の入力を追加して飛ばす

  1. 飛行モードに対応したキャラクターは上昇/下降も入力で制御したい
  2. 歩行移動モードからジャンプしなくても、上昇の入力ですぐに飛行移動モードにしたい
  3. 落下中に上昇の入力でも飛行移動モードにしたい

(1)の実装には先だってエディターでプロジェクトの設定の入力から適当なAxis(例えば "MoveTop")とキーなどを割り当てておく。その上で、 void AtpsCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) 内で、

PlayerInputComponent->BindAxis( "MoveTop", this, &AtpsCharacter::MoveTop);

としつつ、 AtpsCharacter::MoveTop も実装する:

// for tpsCharacter.h
  void MoveTop( float Value );
// for tpsCharacter.cpp
// TPSテンプレプロジェクトで始めれば既に定義済みのはずの MoveRight, MoveForward を参考にほぼそのまま軸の向きだけ変えればよい
void AtpsCharacter::MoveTop( float Value )
{
  if ( ( Controller != NULL ) && ( Value != 0.0f ) )
  {
    // TODO: このあとここに (2), (3) 用のコードを追加

    // find out which way is right
    const FRotator Rotation = Controller->GetControlRotation();
    const FRotator YawRotation( 0, Rotation.Yaw, 0 );

    // get right vector 
    const FVector Direction = FRotationMatrix( YawRotation ).GetUnitAxis( EAxis::Z );
    // add movement in that direction
    AddMovementInput( Direction, Value );
  }
}

ここまでで飛行中に上昇/下降は可能になる。続けて、↑の MoveTop の実装を少し工夫して (2), (3) も実現できる。

    // TODO: このあとここに (2), (3) 用のコードを追加

この部分を次のように実装する。

    // 2. 歩行移動モードからジャンプしなくても、上昇の入力ですぐに飛行移動モードにしたい
    // 3. 落下中に上昇の入力でも飛行移動モードにしたい
    if ( Value > 0 && !GetCharacterMovement()->IsFlying() )
      GetCharacterMovement()->SetMovementMode( MOVE_Flying );

飛行中に足場に接触したら歩行移動モードにする

この機能を実装しようとすると嵌りそうなちょっとした罠がある。

  • TickGetCharacterMovement()->IsFlying()GetCharacterMovement()->IsMovingOnGround() をチェックして・・・
    • IsMovingOnGroundMODE_Walking では意図通りに機能するけど MODE_Flying では常に false になるので使えません。
  • ACharacter::Landed( const FHitResult& )override して GetCharacterMovement()->IsFlying() なら・・・
    • MODE_Flying では Landed(ブループリント的には OnLanded ) がそもそも発生しないので使えません。

と、言うわけで、この機能の実装は素直な当たり判定で処理する事になる。

  // ACharacter::NotifyHit を override
  virtual void NotifyHit
  ( class UPrimitiveComponent* MyComp
  , AActor* Other
  , class UPrimitiveComponent* OtherComp
  , bool bSelfMoved
  , FVector HitLocation
  , FVector HitNormal
  , FVector NormalImpulse
  , const FHitResult& Hit
  ) override;
// 実装
void AtpsCharacter::NotifyHit
( class UPrimitiveComponent* MyComp
, AActor* Other
, class UPrimitiveComponent* OtherComp
, bool bSelfMoved
, FVector HitLocation
, FVector HitNormal
, FVector NormalImpulse
, const FHitResult& Hit
)
{
  Super::NotifyHit( MyComp, Other, OtherComp, bSelfMoved, HitLocation, HitNormal, NormalImpulse, Hit );

  // 飛行移動モードかつ接触した物体から受ける法線が上向きから45[deg]未満ならば歩行移動モードへ移行する
  if ( GetCharacterMovement()->IsFlying() && acosf( FVector::DotProduct( HitNormal, FVector::UpVector ) ) < PI / 4 )
    GetCharacterMovement()->SetMovementMode( MOVE_Walking );
}

ちなみに、この機能を実装しないと、地上に接しても空中飛行中と同じように移動しても構わないのでは?と思うかもしれない。それはそれで摩擦などを与えれば問題無いのかもしれないけれど、素直に地表では歩行モードに切り替えた方が自然な挙動となる場合が多いと思います。床がツルッツルの濡れた氷上という設定ならいいかもしれないけどね。

また、もし、固定角度ではなく、歩行移動モードで歩行可能な角度の場合にしたければ、 PI / 4 の部分を FMath::DegreesToRadians( GetCharacterMovement()->GetWalkableFloorAngle() ) にします。

こうして作った AtpsCharacter のプレイ動画

youtu.be

あとは飛行中にそれっぽいメッシュやスケルタルアニメーションに切り替えたり、用途に併せて細かな挙動付けや調整を施せば実用できると思います。

参考

  1. UCharacterMovementComponent::SetMovementMode | Unreal Engine API Reference UCharacterMovementComponent | Unreal Engine API Reference

  2. ACharacter::Landed | Unreal Engine API Reference
  3. ACharacter::OnLanded | Unreal Engine API Reference
  4. AActor::NotifyHit | Unreal Engine API Reference
  5. GetWalkableFloorAngle | Unreal Engine Blueprint API Reference
  6. Making character class fly? - UE4 AnswerHub

UProceduralMeshComponent などの UMeshComponent のマテリアルを実行時にC++で変更する方法

特に UProceduralMeshComponent を使う場合など UMeshComponent 系のメッシュ mesh のマテリアルを実行時に変更したい場合は以下のようにする。

mesh->SetMaterial
( 0 // Note: マテリアルをセットしたいメッシュ内のセクション番号
, Cast<UMaterial> // Note: 今回はスターターコンテントの草原っぽいマテリアルをロードして与えている
  ( StaticLoadObject
    ( UMaterial::StaticClass()
    , nullptr
    , TEXT( "/Game/StarterContent/Materials/M_Ground_Grass.M_Ground_Grass" )
    )
  )
);

参考

  1. UMeshComponent::SetMaterial | Unreal Engine API Reference
  2. UMaterial | Unreal Engine API Reference

UProceduralMeshComponent::UpdateMeshSection_LinearColor で一部の引数(頂点属性)だけ更新する方法

以下のコードの結果は同じです・w・

// code-1
  ...
mesh->CreateMeshSection_LinearColor
( 0
, vertices
, indices
, normals
, texcoords0
, vertex_colors
, tangents
, true
);
// code-2
  ...
mesh->CreateMeshSection_LinearColor
( 0
, vertices
, indices
, normals
, decltype(texcoords0)()
, vertex_colors
, tangents
, true
);
mesh->UpdateMeshSection_LinearColor
( 0
, decltype(vertices)()
, decltype(normals)()
, texcoords0
, decltype(vertex_colors)()
, decltype(tangents)()
);

と、言うわけで、生成済みの頂点バッファーの一部の引数(頂点属性)だけ更新したい場合には、更新したくない引数部分に要素が空の TArray を投げると、要素の詰まった引数が渡された頂点属性のみスリムに更新できます。

具体的な用途としては、頂点アニメーション、テクスチャーアニメーション、法線やタンジェントなしで生成したメッシュに法線を後から計算して追加する、などが一般的なシチュエーションと思います。

UE4: ProceduralMeshComponent 入門

はてなブログよ、わたしは帰ってきた・w・ と、いうわけで早速本題に入りましょ。

UE-4.14 から導入された事になっている ProcedualMeshComponent ですが、いまひとつ公式ドキュメントでも最低限のエグザンプルとブループリントの情報しかまだ整理されていないようなので、C++による日本語の入門まとめを書いておこうと思います。

必要最小限の使い方: C++

↓とりあえずこんなのを出すエグザンプルとtips。

cppf:id:USAGI-WRP:20170526231022p:plain

1. MyProject.Build.cs

  1. PublicDependencyModuleNames.AddRange"ProceduralMeshComponent" を追加。

2. MyActor.h

一般的に Actor 系のヘッダーで UProceduralMeshComponentポインター型を使いたいだけならクラスの宣言だけ AActor クラスの宣言の手前に書いておけば十分。

class UProceduralMeshComponent;

class AActor で保持して使いたい事が多いと思うので UProceduralMeshComponent * のメンバー変数を宣言しておく。

UProceduralMeshComponent* mesh;

3. MyActor.cpp

とりあえず必要最小限、"さんかく1枚を実行時に作る"実装例。

ProceduralMeshComponent の中身を使うので素直にヘッダーを include

#include "ProceduralMeshComponent.h"

とりあえず、という事なら以下の実装を AMyActor::AMyActor でまとめて行っても「はじめてのさんかく」は表示できる。

AMyActor::AMyActor()
{
  // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
  PrimaryActorTick.bCanEverTick = true;
  
  // mesh の中身を作る。
  mesh = CreateDefaultSubobject<UProceduralMeshComponent>( TEXT( "generated-mesh" ) );
  // とりあえず RootComponent として mesh をつっこむ。
  RootComponent = mesh;
  
  // ここから ProceduralMeshComponent のオブジェクトにメッシュの中身のデータを作らせるコード
  // 頂点群
  TArray<FVector> vertices;
  // インデックス群
  TArray<int32> indices;
  // 法線群(今回は空っぽのまま)
  TArray<FVector> normals;
  // テクスチャー座標群
  TArray<FVector2D> texcoords0;
  // 頂点カラー群(今回は空っぽのまま)
  TArray<FLinearColor> vertex_colors;
  // 接線群(今回は空っぽのまま)
  TArray<FProcMeshTangent> tangents;

  // 「はじめてのさんかっけい」に必要な3点の頂点座標を生成。
  vertices.Emplace( 0.0e+0, 0.0e+0, 0.0e+0 );
  vertices.Emplace( 0.0e+0, 1.0e+2, 0.0e+0 );
  vertices.Emplace( 1.0e+2, 0.0e+0, 0.0e+0 );

  // UE は「左手座標系Z-top」の「反時計回り面生成」なので↑の頂点をこの順序で繋ぐと+Z向きに三角面1つを生成できる。
  indices.Emplace( 0 );
  indices.Emplace( 1 );
  indices.Emplace( 2 );

  // テクスチャー座標を設定しておけばこんなエグザンプルでも適当なマテリアルをセットして模様出し確認はできます。
  texcoords0.Emplace( 0, 0 );
  texcoords0.Emplace( 0, 0 );
  texcoords0.Emplace( 0, 0 );

  // UProceduralMeshComponent::CreateMeshSection_LinearColor でメッシュを生成。
  // 第1引数: セクション番号; 0, 1, 2, ... を与える事で1つの UProceduralMeshComponent に複数のメッシュを内部的に同時に生成できます。
  // 第2引数: 頂点群
  // 第3引数: インデックス群
  // 第4引数: 法線群
  // 第5引数: テクスチャー座標群
  // 第6引数: 頂点カラー群
  // 第7引数: 法線群
  // 第8引数: コリジョン生成フラグ
  mesh->CreateMeshSection_LinearColor( 0, vertices, indices, normals, texcoords0, vertex_colors, tangents, true );

実用化にあたって手始めにすると良いかもしれないこと

  1. CreateMeshSection_LinearColor 周りのメッシュ生成コードを generate_mesh メンバー関数に分離。
  2. AActor::AActor ではなく AActor::PostLoad AActor::PostActorCreated などを override して↑の generate_mesh を呼ぶようにする。
  3. 適当なイベントまたは Tick などでアニメーションさせてみる。

執筆現在の残念なところ

  1. インデックスバッファーを TRIANGLES でしか扱えないところ。
    • せめて TRIANGLE-STRIP には対応して欲しいなー。

その他 tips

  1. CreateMeshSection_LinearColor の他に CreateMeshSection でも同様にメッシュ生成できる。
    • CreateMeshSection_LinearColor: 第6引数 を TArray<FLinearColor> で与える版
    • CreateMeshSection: 第6引数を TArray<FColor> で与える版
  2. ここまでの知識だけでも実行時に頂点やインデックスが複雑に変形するメッシュは作れる。
    1. AActor::Tickoverride してアニメーションを定義可能にし、
    2. 毎フレーム目的の状態のメッシュを CreateMeshSection_LinearColor 等で生成する。
  3. UProceduralMeshComponent の応用機能(上記までの「必要最小限の使い方」で紹介していない、という意味の)
    1. UpdateMeshSection_LinearColor または UpdateMeshSection
      • トポロジーの変更を伴わないメッシュの更新」をするための機能。
      • アニメーションを実現したい場合は Create 系に代えて Update 系を使った方が高速に動作する。
    2. ClearMeshSection: セクション番号を指定してメッシュを消す。
    3. ClearAllMeshSections: すべてのメッシュを消す。
    4. SetMeshSectionVisible: セクション番号のメッシュの仮死状態の boolean な切り替えを行う。
      • この機能を応用すると単純な表示/非表示として使うほか、予め複数のセクションに生成したメッシュを順に切り替えるアニメーションなども実装できる。いちおう。
    5. IsMeshSectionVisible: セクション番号のメッシュの可視状態を取得できる。
    6. GetNumSections: メッシュが生成されているセクションの数を取得できる。
    7. AddCollisionConvexMesh: 凸形状コリジョンを追加できる。
    8. ClearCollisionConvexMeshes: 凸形状コリジョンを消せる。
    9. SetCollisionConvexMeshes: 凸形状コリジョン群を TArray<TArray<FVector>> で一度にまとめて設定(既存の設定群とは置き換え)できる。
    10. ProcMeshBodySetup: コリジョンデータ。
    11. GetProcMeshSection: セクション番号のメッシュの内部データを取得する。
    12. SetProcMeshSection: セクション番号のメッシュの内部データを直接設定する。
    13. Interface_CollisionDataProvider から継承した機能群: GetPhysicsTriMeshData ContainsPhysicsTriMeshData WantsNegXTriMesh
    14. bUseComplexAsSimpleCollision: シンプルな凸形状コリジョンを使う場合は false 、複雑なジオメトリーを使う場合は true を設定するフラグ。
    15. UPrimitiveComponent から継承した機能群: CreateSceneProxy GetBodySetup GetMaterialFromCollisionFaceIndex
    16. UMeshComponent から継承した機能群: GetNumMaterials
    17. UObject から継承した機能: PostLoad

参考

  1. Procedural Mesh Component in C++:Getting Started - Epic Wiki
    • 「さいしょのさんかっけい」の原型
  2. GitHub - SiggiG/ProceduralMeshes: Plugin with example procedural mesh actors and components
    • ProceduralMeshComponent の応用実装の参考になる
  3. ProceduralMeshComponent | Unreal Engine API Reference
    • ブループリントのリファレンスだけどC++erにも無いよりまし
  4. https://github.com/EpicGames/UnrealEngine/tree/release/Engine/Plugins/Runtime/ProceduralMeshComponent
    • 結局ソース読むのがいちばんはやい(特に執筆現在まだ丁寧なC++er向けの解説は無いようなものなので)

C++ tips: Boost で demangling

RTTIで型情報を対話的に可視化したいと思うと、C++ではデマングルしないと割と読めません。extern "C"しておくとか邪悪臭いですし。

ところがデマングリングは処理系依存に触れる事になるので、ユーザーコードで扱いたいものではありません。そこで処理系依存のデマングルをラップしたライブラリーレベルでユーザーコードからは使いたいと思うのが一般的なプログラマーの思考かと思います。

Boost.Units にあるよ・w・

例えば、

#include <boost/units/detail/utility.hpp>
using boost::units::detail::demangle;
auto demangled_name = demangle(typeid(0).name());

こんな感じで使うと、翻訳時に0の型はintになるのでdemangled_nameには"int"なるstd::stringが入る事になります。

もうちょっとゴチャゴチャ見てみましょう・w・

魔黒は便利なC++機能です。死者が出ない程度に有効に使いましょう。と、それはさておき、この例の実行結果は著者環境では次の通りです。

i --> int
Ss --> std::string
St6vectorIhSaIhEE --> std::vector<unsigned char, std::allocator<unsigned char> >
1XIiIfdjbDnEE --> X<int, float, double, unsigned int, bool, decltype(nullptr)>
PFivE --> int (*)()

著者環境は g++-4.7.1 / openSUSE-12.2 です。テンプレートが絡まない型はマングリングされた状態でも読めなくも…いやどうなんだ…程度な事もありますが、テンプレートが絡んでるともう型名読めません、デマングル必須です・w・

と、言う訳で今回は「処理系依存のdemangleとか直接書きたくないでござる」という場合にBoostの一角に含まれたデマングリング機能を使っちゃう方法の紹介でした。