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

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

Visual Studio 2017 を日本語でインストールした後で English に変更する方法(UE4の文字化け嫌だしー)

状況

Visual Studio 2017 の cl.exe のメッセージを English にしたい。

“googlability” や “stackoverflowability” や “ue4-answerhubability” 的な都合でも合理的だし、 UE4 の UE4Editor から C++ コードをビルドした際に問題が生じた場合に UE4Editor のログ表示で文字化けが起こり、具体的にどんな問題が起きてしまったのか読めず、適当な端末なり Visual Studio なりを起動して"よっこらせ"と再度ビルドして確認する事になるのを避けたいのがいちばんの理由。

方法

  1. Visual Studio Installer” を起動する
  2. “Modify” –> “Language Packs” タブ
  3. “English” –> checked –> Modify (右下のボタン)
  4. Visual Studio 2017” を起動する
  5. “ツール” –> “オプション” –> 「言語」などで絞り込み –> “環境/国際対応の設定” –> 言語: EnglishWindowsの言語設定を English にしている場合は「Microsoft Windows と同じ」でもOK)
  6. Visual Studio 2017” を終了する

これで以降は Visual StudioIDE や UE4Editor や端末で cl.exe を叩いた場合などの表示挙動が English になり無駄な事でいらいらする可能性がそれなり減ります♥

図解

Visual Studio Installer、インストール済みの Visual Studio 2017 の Modify を押すところ。これは Community の例だけど、たぶん Enterprise や Professional でも同じでしょう(執筆現在まだ仕事用の Professional とかで実際に試してないので断言はできませんが)。

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

Visual Studio Installer、 Language Packs を追加するところ。日本語を入れたまま English を追加しても問題は起きない。

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

Visual Studio 2017 の IDE から言語設定を買えるまでははじめにインストールした言語が規定になったままになっているので切り替えるまでは相変わらず CP932 が吐かれてこころが壊れるまま。

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

cl.exe のエラーメッセージの変化の様子

日本語動作のエラー表示:

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

English 動作のエラー表示:

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

UE4Editor で C++ コードにエラーがある状態で Compile しても cl.exe が English なエラーメッセージを吐く状態ならえんじにあの心が壊れずに快適に問題を解決して進捗できるようになる:

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

C++ 標準の promise / future / thread に対応する UE4 標準の TPromise / TFuture / FRunnableThread の使い方

C++ 標準の std::promise / std::future / std::thread

C++erにとっては基礎的なおさらい。

#include <iostream>
#include <future>

int main()
{
  using namespace std;
  
  promise< int > p;
  auto f = p.get_future();
  auto t = thread( [ =, p = move(p) ] () mutable { p.set_value( 123 ); } );
  cout << f.get();
  t.join();
}

C++ 標準では lambda-expression を std:thread へ放り投げるのが簡単なところが嬉しいポイント。

対応する UE4 標準の TPromise / TFuture / FRunnableThread

UE4 標準でコード上で同様の事を記述しようとすると少しめんどくさい。

#include "Runtime/Core/Public/Async/Future.h"

void your_something_function()
{
  // std::promise と同様
  TPromise< int > p;
  // std::future の std::promise からの取得と同様
  auto f = p.GetFuture();
  // ここがちょっとめんどくさい: std::thread( ... ) に相当するコード
  struct r_type: public FRunnable
  { TPromise<int> p;
    r_type( TPromise<int>&& p_ ) : p( std::move( p_ ) ) { }
    bool Init() override { return true; }
    uint32 Run() override { p.SetValue( 123 ); return 0; }
    void Stop() override { }
  } r( std::move( p ) );
  // ↓この2つのフラグは今回のミニマルコード例を応用する人がいた場合に間違えるととても危険なため目立つようにこのように書いておきました。
  constexpr auto bAutoDeleteSelf      = false;
  constexpr auto bAutoDeleteRunnable  = true;
  // ↓これに↑をやる lambda-expression をそのまま放り込めないので FRunnable の派生クラスを定義してもにょもにょ。
  FRunnableThread::Create( &r, TEXT( "promise-future tester"), bAutoDeleteSelf, bAutoDeleteRunnable, 0 );
  // std の例では cout していた部分を今回コード例では UE_LOG で代用しました。
  UE_LOG( LogTemp, Log, TEXT( "promise-future: %d" ), f.Get() );
}

ミニマルコード例として C++ 標準の promise / future / thread ( / lambda-expression ) に対応する事を優先して UE4 標準のコードを書くとこうなります。( FRunnableThread::Create には引数が少し簡単なオーバーロードもあり、 r_typenew して bAutoDeleteSelf 相当のフラグを true として構わない状況ではそちらを使った方が楽な事もあるかもしれません。 )

実用上はこのような例では PROMISE-FUTURE-THREAD 系の低レベルの記述をせず、 C++ 標準なら std::async 、 UE4 標準なら Async へそれぞれ lambda-expression を放り投げた方がよい場合が多いと思いますが、ときおり諸事情により PROMISE-FUTURE 系の低レベルのコード記述が欲しい事があります。例えば既存のライブラリーが非同期処理のコールバックパターンの API を提供しているところで、外で PROMISE と FUTURE を作って非同期処理を呼びつつコールバックに PROMISE を std::move で投げておいてコールバック内で PROMISE に値をセットさせ API の呼び出し元では FUTURE から値の取得を行ったり FUTURE の状態を監視するなどしたい場合とか。

参考

UE4 プロジェクトを git で管理する際の remote origin の選択について

あちこちのサービスを試す事はあっても、結果的には git リポジトリーの remote origin は基本的にはなんでも GitHub を使っていました。しかし、 GitHub だと 100 MB を超えるファイルを少なくともそのままでは放り投げられないのでちょっと工夫とか面倒がありました。

それで、久しぶりに VSTS ( MicrosoftVisual Studio Team Services ) を使ってみる事にしました。きっかけは TwitterFacebook のタイムラインの観測範囲で VSTS がいい感じに進化しているらしいという話がちらほら見えたので。

VSTSの日本語の入り口ページ

www.visualstudio.com

↑を UE4 プロジェクトの git の remote origin にしてしばらくお試ししてみた感想を整理します。

  • 良いと感じた事
    1. 遠慮なく Content/ も git の通常の管理に含められるようになった。らく。
    2. プロジェクトの管理単位が team/project となっていて整理しやすいと感じた。
      • 例えば Hoge プロジェクトがあるとき、メインの hoge リポジトリーの他に prototype-x とか fuga-tool とか補助的な単位があると便利なこともままあるので、事務的な意味でのプロジェクトの管理単位を VSTS の team として、そのシリーズの中でのソース管理単位を VSTS の project として整理して使えて便利が良いです。
    3. push/pull がはやい。(通信の経路にも寄ると思いますが、わがやの場合は github より応答性が良いと感じました。)
    4. モダンな開発プロセスを考慮したリポジトリー運用ルールやレビュープロセスを標準でしれっと使える状態になっている。(一人でやる分にはあんまり関係ないけど)
  • 改善して欲しいと感じた事
    1. ssh 接続に ECDSA 公開鍵を使えなかった事( RSA は使えるけど)
    2. GitHub における public リポジトリー相当の使い方もできるようになると嬉しい(UE4プロジェクトではあんまり必要ない事だけど)

これまで Content/ だけ別管理していたので、それが必要なくなるだけでも嬉しい変更でした。ちなみに、 BitBucket はモッサリ感が開発者のストレス要因になる気配の漂うレベルなので選択肢にふだんはあがりません。総合的に使い倒せれば BitBucket, Atlassian, JIRA 関連のサービス群はもちろん強力だけど、それ相応のちょっとお高い維持費も必要になっちゃうしね・w・

おまけ // git remote の tips

既に GitHub を origin にしているローカルリポジトリーを新たに vsts をデフォルトに変更したい場合はこんな感じ。

git remote add vsts ssh://<YOUR-TEAM>@<YOUR-TEAM>.visualstudio.com:22/_git/<YOUR-PROJECT>
git push -u vsts master

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 を投げると、要素の詰まった引数が渡された頂点属性のみスリムに更新できます。

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