げーむ開発徒然日記~怠惰のために勤勉~

Unreal Engineや3DCG制作について学んだことを記事にしていきます

対象Actorの速度を監視するAbilityTask

概要

GASにてダッシュアビリティ(GA_Run)を実装した際、ダッシュのアクション入力をすると止まっているにも関わらずGA_Runの持つState.RunningのようなGameplayTagが付与されてしまうのが気になっていました。

AbilityTaskですが、どうやらTick動作させることができるみたいなので、それを利用してActorの速度を監視するAbilityTaskを作りました。

AbilityTaskの作り方については、あいす氏の記事でわかりやすく解説されていますのでそちらをご覧ください。 また、公式の詳しい作成手順はAbilityTask.h内に示されています。

[UE] AbilityTask のつくりかた - Qiita

使用UEバージョン:5.0.3

作成するAbilityTask

今回作ったものは以下のようなものです。

AbilityTaskを実行するとTargetに接続したActorの速度(高さ方向は無視)を監視し続け、Threshold(閾値)を超えるとOnStartMovingデリゲートが発行され、再度Threshold以下となるとOnStopMovingデリゲートが発行されるといった非常にシンプルな構成になっています。

実装

DECLARE_DYNAMIC_MULTICAST_DELEGATE(FWaitGroundMovingDelegate);

UCLASS()
class YOURPROJECT_API UAbilityTask_WaitGroundMoving : public UAbilityTask
{
    GENERATED_BODY()

    UPROPERTY(BlueprintAssignable)
    FWaitGroundMovingDelegate OnStartMoving;
    UPROPERTY(BlueprintAssignable)
    FWaitGroundMovingDelegate OnStopMoving;

    virtual void TickTask(float DeltaTime) override;

    /** TargetActorのXY速度が閾値Thresholdを上回る、又は、Threshold以下となるのを待つ */
    UFUNCTION(BlueprintCallable, Category = "Ability|Custom|Tasks", meta = (DisplayName= "WaitGroundMoving", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"))
    static  UAbilityTask_WaitGroundMoving* CreateWaitGroundMoving(UGameplayAbility* OwningAbility, AActor* Target, float Threshold);

    virtual void Activate() override;

protected:
    UPROPERTY()
    AActor* TargetActor;
    
    float Threshold;

private:
    bool bIsMoving;
};
#include "Kismet/KismetMathLibrary.h"

void UAbilityTask_WaitGroundMoving::TickTask(float DeltaTime)
{
    if (TargetActor)
    {
        float GroundSpeed = UKismetMathLibrary::VSizeXY(TargetActor->GetVelocity());

        if (GroundSpeed > Threshold)
        {
            if (!bIsMoving)
            {
                if (ShouldBroadcastAbilityTaskDelegates())
                {
                    OnStartMoving.Broadcast();
                }
                bIsMoving = true;
            }
        }
        else
        {
            if (bIsMoving)
            {
                if (ShouldBroadcastAbilityTaskDelegates())
                {
                    OnStopMoving.Broadcast();
                }
                bIsMoving = false;
            }
        }
    }
    else
    {
        ABILITY_LOG(Warning, TEXT("UAbilityTask_WaitGroundMoving ticked without a valid Actor. ending."));
        EndTask();
    }
}

UAbilityTask_WaitGroundMoving* UAbilityTask_WaitGroundMoving::CreateWaitGroundMoving(
    UGameplayAbility* OwningAbility, AActor* InTarget, float InThreshold)
{
    auto* MyObj = NewAbilityTask<ThisClass>(OwningAbility);

    MyObj->bTickingTask = true;  //Tick動作に必要
    MyObj->TargetActor = InTarget;
    MyObj->Threshold = InThreshold;

    return MyObj;
}

void UAbilityTask_WaitGroundMoving::Activate()
{
    SetWaitingOnRemotePlayerData();
}

MetaSoundでお手軽ラジオボイス風表現

タイトル通りです。

なんとなく「外部ソフトを使用せずにUE5内でプロシージャルにラジオ風音声に変換したいな」と思ったので紹介します。

※MetaSoundの基本的な使い方は省略します。使用するにはプラグインをONにしてください。

また、音声サンプルはありません。

使用バージョン:UE5.0.3

フィルタを使ってこもったような音声にする

まだMetaSoundをほとんど触ったことがなく、機能を把握しきれていないので今回は単純にLPF(Low-pass filter)とHPF(High-pass filter)+αを使っていきます。もちろんBPF(Band-pass filter)でもできるはずです。

LPFは、所定の周波数以上の成分をカットするフィルターで、HPFはその逆です。

手順は以下の通りです。

入力ノードOn PlayピンからWave PlayerノードPlayピンに接続して音声アセットを再生できるようにします。

Wave PlayerノードOut Leftピンから、ワンポールハイパスフィルタノード(HPF)のInピンに接続します。

下図の例では、カットオフ周波数を"3000"に設定しているので、3000Hz以上の周波数成分を通します(数値は割と適当なので調節してください)。

なお、今回はデフォルト設定のモノラル音声のまま進めるので、Wave PlayerノードのOut Rightピンは使用しません。

ワンポールハイパスフィルタノードOutピンからワンポールローパスフィルタノード(LPF)のInピンに接続します。

再生してみるとわかりますが、元の音声に比べてこもったような感じになっていると思います。

音質を悪くする

先ほどまでの状態でこもった音声にはなりますが、元の音声の品質が高いとまだまだラジオっぽくなりません。

そこで、Bitcrusherノードなるものを、フィルタを通した先に接続します。

これは、ノードの説明にもあるように「入力オーディオ信号をダウンサンプルし、ビット深度も下げる」ものみたいなので使えそうだなと思いました。

サンプルレートとビット深度をお好みに合わせて調節してください。

これで再生してみると、音質が低下したことで更にラジオっぽくなっていると思います。

仕上げにノイズを追加する

先ほどまでで声のいじくりは終わったので、最後は仕上げにノイズを乗せることでよりラジオっぽくしていきますが、ノイズを追加するかどうかは必要に応じて選択してください。

手順は以下の通りです。

ノイズノードを出して、出力をPink NoiseかWhite Noiseかを選択する。

②今回はモノラル音声なのでMono Mixer (2)ノードで声とノイズをミックスする。

③声とノイズのゲインを調節する。

いかがでしょうか。

元の音声と比べるとだいぶラジオっぽくなっていると思います。

これで、わざわざ外部ソフトで加工しなくてもUE5内で完結できちゃいますね。また、パラメータを変数化して元の音声ファイルを残したままプロシージャルに調節可能なのもMetaSoundの大きな利点だと思います。

使い方を調べながら検証するつもりが思ったよりも使いやすかったため、一瞬で記事まで書けてしまいました。

 

つまり、MetaSoundは素晴らしい!

 

以上

CC3キャラクターの表情シェイプキーとフェイスリグを残し、Blender Auto-Rig Proのボディリグに統合する

 今回の記事はタイトル通りです。

 今回ご紹介させていただく方法は、Reallusion公式のチュートリアル動画やAuto-Rig ProとUE4との連携についての先人様のブログをいくつか参考にまとめたものです。

 こちらの方法を使うことによって、下記メリットが得られると考えられます。

・CC3キャラクターをBlenderにインポートしてもリギングされていないためすぐにアニメーション作成に取り掛かれないが、Auto-Rig Proの自動リグ生成によってこれを解消できる。iCloneなどよりBlenderでのアニメーション制作のほうが慣れている場合は特に推奨。

・フェイシャルリグはAuto-Rig Proの生成機能を使わず、CC3の表情シェイプキーとフェイシャルリグを統合させるため、Reallusionの強力なLive Faceアプリなどは引き続き使える(はず)。

・Auto-Rig ProはUE4やUnityなどのゲームエンジン向けのエクスポートに対応しており、これによってUE4やUnity標準スケルトンとほぼ同様の命名に自動変換してくれるので地味に嬉しい。

 ゲームエンジン側で自動でマテリアルなどを設定してくれるプラグイン(Auto-Setupプラグイン)はあるが、これを使っても標準スケルトンに沿った命名はなされない。ただしマテリアル設定に関してはAuto-Setupプラグインを使うのがよいと思われます。今回の手順はあくまでもスケルトンの入れ替えと、アニメーション作成などのためのみです。

・CC3で作成したヒト型キャラクターやそれ以外のソフト(Blenderなど)で作成したヒト型キャラクターを、Auto-Rig Proで作成したアーマチュアに統一することでリターゲティングが不要になったり簡単になる場合が多い。

CC3でのキャラクター作成~Blenderでのインポートと準備

CC3からエクスポートし、Blenderでインポートする

 ここは特に難しいことはしません。以下の設定でCC3からFBXエクスポートします。

f:id:raksul_01:20211218142514p:plain

 エクスポートされたFBXをBlenderにインポートします。

インポート時の設定はデフォルトのままでOKです。

f:id:raksul_01:20211218142837p:plain

Auto-Rig Proのための事前準備

 テンキーの1を押して正面から見ると、おそらく足がZ=0の面に設置していないことになっているので、アーマチュアを選択した状態で、足がZ=0にくるようにします

(X軸を示す赤色の線に合わせる)。

f:id:raksul_01:20211218143046p:plain

 その後、Aキーでアーマチュアとすべてのメッシュを選択した状態でCtrl+A→「適用:全トランスフォーム」をします。

 また、このままだとメッシュ内部に骨があって見えづらいので、以下のように「最前面」にチェックを入れます。

f:id:raksul_01:20211218143619p:plain

 次に、後で誤って操作しないようにするために各パーツのメッシュ(身体、服、靴、眼球、口、舌、髪の毛などすべて)それぞれに対して、頂点グループをすべてロックします。ちなみに、左クリックした状態でドラッグすると一気にロックできます。あるいは、下向きの「>」を押すと「すべてロック」というボタンがあります。

f:id:raksul_01:20211218144950p:plain

Auto-Rig Proによるアーマチュア・リグ生成とスキニング

アーマチュアの自動生成と修正

 キャラクターのメッシュをすべて選択した状態で、Auto-Rig ProのメニューからGet Selected Objectsを押します。

f:id:raksul_01:20211219114651p:plain

 すると、元のアーマチュアが非表示になりますが、後の作業のためにアーマチュアを再度表示します。

f:id:raksul_01:20211219115401p:plain

 ここからはAuto-Rig ProのSmart機能を使って、アーマチュアを生成します。Smart機能を使う際、首、アゴ、肩、手首、背骨のルート、足首のアタリをつけていくことになります。

 先ほどAuto-Rig Pro:Smart内のGet Selected Objectsボタンを押した後、メニュー内がAdd Neckなどのボタンに変わっていると思うので、そちらを押すとアタリをつけるためのマーカーが追加されるので、再表示した元のCC3アーマチュアをもとにアタリをつけていきます

f:id:raksul_01:20211219121522p:plain

 配置し終えたらGoボタンを押します。Auto-Rig Proのバージョンによってはこのとき、フェイシャルリグを生成するか聞かれますが今回はCC3のフェイシャルリグを統合することが目的なので生成しないようにします。

f:id:raksul_01:20211219121803p:plain

 すると、アーマチュアが自動生成されますが、元のCC3アーマチュアに対してずれているので各ボーンの位置を修正していきます。なお、手足などは左右対称に位置が移動されるので、片側のみでOKです。

f:id:raksul_01:20211219125709p:plain

足の3か所(leg_ref.l, foot_ref.l, toes_ref.l)

f:id:raksul_01:20211219130225p:plain

ひざ(thigh_ref.l)

重要!!必ずしもCC3アーマチュアの位置に合わせる必要はなく、間違った方向にまがらないようにすること。ひざはCC3アーマチュアよりもすこし前に出すような感じにするとよい。

f:id:raksul_01:20211219130457p:plain

腕(shoulder_ref.l, arm_ref.l)※正面視でも調整すること。

 ここもひじ関節に注意。画像よりも後ろ(画像上方向)にずらすとよいかもしれません。ボーン位置が適切でないと後で問題が発生しますが、後述するように修正することもできます。

 他のボーンと同様に、指のボーン位置も合わせた方がよいです。

UE4オプション)UE4標準スケルトンに合わせる

 UE4に持っていく場合は、標準スケルトンの仕様上、背骨の数を4つにしたほうがよいです。これをするにはいずれかの背骨(spine)を選択した状態で、Auto-Rig ProのメニューからLimb Optionsボタンを押して数を4にします。これはGoボタンでアーマチュアを生成する際にも設定できるのでそこで4つに設定していれば変更する必要はありません。

 さらに、ツイストボーンも追加した方がよいそうです。これをするには、腕のボーンを選択した状態で、Limb Optionsボタンを押してTwist Bonesを2~4にします。脚に関してはよっぽど左右のねじれが激しくなければ増やさなくてもよいそう。

(備考)Secondary Controllersについて

 なん氏によるブログ記事(AutoRigProの解説:その2 Smart編 - 当たったらどうすんだよ)より、Limb Optionsのほかにもセカンダリコントローラ設定ができますが、TwistコントローラはBlender独自仕様であり、ゲームエンジンにそのままエクスポートはできないためAdditiveのままでもよいそう。

リグの自動生成

 ここまでできたら、Auto-Rig Proのアーマチュアを選択した状態で、Match to Rigボタンを押します。するとリグがアーマチュアに従って自動で生成されます。

f:id:raksul_01:20211219131307p:plain

リグが上手く生成されない場合

 生成されたリグを見ると、脚は真っすぐですが、上腕や下腕のリグはねじれているように見えます。このようなときは肘関節のボーン位置などが原因のことが多いので、 Match to Rigボタンの上のほうにあるEdit Reference Bonesボタンを押してボーンを再度位置修正し、Match to Rigボタンを押してリグを再生成、という工程を繰り返します。

f:id:raksul_01:20211219133209p:plain

f:id:raksul_01:20211219132759p:plain

上は修正前(リグのねじれが激しい)、下は修正後。

自動スキニングする

  所望のリグが生成されたら、オブジェクトモードで最初にアウトライナーからメッシュをすべて選択し、最後にリグをShiftを押しながら3Dビュー上で選択した状態で、スキンメニューにあるBindボタンを押してしばらく待ちます。すると、Auto-Rig Proのリグに従ってメッシュが自動でスキニングされます。なお、Mesh Binding設定にてスキニングのメソッドをHeat MapかVoxelizedから選択することも可能です。

 ゲームエンジンに持っていく場合は「体積を維持」のチェックは外す方がよい。

f:id:raksul_01:20211219141459p:plain

 リグを動かしてみるとAuto-Rig Proのリグに従ってスキニングされていることが確認できますが、CC3のフェイシャルリグは動作しなくなっています。なので、これらを統合する作業に入ります。

Auto-Rig ProのリグとCC3のフェイシャルリグの統合

 まずはCC3のアーマチュアから不要なボーンを削除します。オブジェクトモードでCC3のアーマチュアを選択し、編集モードに切り替えます。

 サークル選択などを使って顔以外のボーンを選択して、Xキー→ボーンを削除します(胸にあるボーンも忘れずに削除)。

f:id:raksul_01:20211219142221p:plain

 オブジェクトモードに戻り、CC3の残ったアーマチュア(フェイシャルリグ)を選択し、Shiftを押しながらAuto-Rig Proのリグを選択し、Ctrl+Jでリグを統合します。統合後、フェイシャルリグがメッシュの内部に隠れますが問題ありません。

 次に、Auto-Rig Proの頭部のリグにCC3のフェイシャルリグが追従するようにします。ワイヤーフレーム表示に切り替えてCC_Base_Headボーン(CC_Base_Facialに隠れてます)を選択し、ボーン設定からc_head.xをペアレントに設定します。

ウェイト修正する

 今回は、髪の毛や服などすべて自動スキニングしたため高確率で修正が必要になります。そもそも髪の毛や服を自動スキニングしない方法もありますが、今回はすべて自動スキニングしたものとして修正の仕方の例を挙げておきます。

 前提として知っておくことは、Auto-Rig Proの命名規則として、プレフィックスのないものは基本、できあがりのスケルトンのボーンとなるもの、"c_"のつくものはコントローラです。なので、ウェイトはプレフィックスのないものにつけていくことになります。

・頂点グループhead.xで首以外を選択してウェイト1.0で割り当て

・首の頂点グループにまつげが含まれていたりするので首以外の部分をウェイト削除

・体メッシュ以外は、それぞれのメッシュに対して頂点グループメニューから「未ロックグループを削除」でOK

※基本的には、最初にロックしたCC3ですでにスキニングされたものだけを残すイメージです。なのでhead.xもすべてウェイト削除でよいかもしれません。

(備考)服のスキニングの別の方法

 こちらのツイートの方法を使えば、服は自動スキニングせずに身体メッシュから転送して対応できそうです。

UE4オプション)UE4-Aポーズにする

 CC3でもエクスポート時にAポーズを指定していますが、ここでいうAポーズとは同じAポーズでもUE4標準に近いAポーズを指します。

 厳密にいえば、Blenderでのリグ&アーマチュア再生成&CC3表情シェイプキー統合(要するに今回の方法)を用いないのであれば、CC3でエクスポートする際にUE4-Aposeを指定することができますが、今回の方法はBlenderで、かつ、AutoRig Proを使うことになるためUE4-Aposeでエクスポートは行わず、通常のAポーズでエクスポートしていることになります。そのため、AutoRig Proの機能を使ってUE4のAポーズに近づける必要があります。
 Set PoseでAポーズを選んでApply Pose at Rest Poseボタンを押した後に、Match to Rigボタンを押すことでUE4のAポーズに近いものとなります(Auto-Rig ProのAポーズはUE4のAポーズに非常に近い)。

(オプション)rootの子ボーンの追加

 rootに子ボーンを追加したい場合は、c_trajにペアレントすれば上手くいった。

 

GASでダッシュアビリティを実装しよう!(アビリティにキー入力を関連付ける)

 らくするです。
 今回は、いまアツいGameplayAbilitiesプラグインを使ったダッシュアビリティ(ダッシュ以外にも幅広く使える!)の実装について紹介したいと思います。
 本記事で紹介する方法はダッシュ以外にも色々と使い道があるため、GASの導入時についでに実装しておくといいかも…なんて思ったり。
※本記事の実装方法を用いて生じたいかなる問題に対しても責任を負えませんのでご了承ください。

 今回の記事を書くに至った経緯としては、GameplayAbilitySystemによるダッシュ(スプリント)を実装しようとしたとき、GameplayAbilityクラスを1つだけ使ってShiftキーのPress/Releaseの判定をするように実装するのがブループリンターには少し厄介だったためです。
 例えば、GameplayAbilityクラスを「歩行」と「走行」の2つのアビリティを用意して、インプットアクションによるイベントを使ってShiftキーを押したら「走行」アビリティを発動し、離したら「歩行」アビリティが発動するように仕組めば可能ではあるのですが、あまりスマートじゃないように思えます。

 GameplayAbilityごとにキー入力を関連付けるというのはかなり多用しそうな処理であるにも関わらず、紹介や解説の記事・動画はあまりないようでしたので、自分なりにまとめてみようと思います。

 誤っている部分やよろしくない記述が散見されるかもしれませんので、もしよろしければコメントやDMでご指摘いただけますと幸いです。

 今回の実装にあたって、GASShooterのコードの一部の他に、Twitterでのあいす氏(@koorinonaka)からの下記リプライを参考にいたしました。ご助言ありがとうございました。

GASの導入

 今回使用したのはUE5EAです。
 GASをプロジェクトに導入するにはC++を使った初期セットアップが必要ですが、これについては、他のブログ様などでもよくご紹介されている
おかわりはくまい氏によるセットアップ方法の紹介記事が非常に参考になります。
 当該記事についてはタイトルだけ下記しておきます。

・GameplayAbilitiesの使い方(セットアップ編) - おかわりのアンリアルなメモ

ダッシュの実装

前置き

 話を振り出しに戻しますが、なぜGameplayAbilityクラスを1つでダッシュを実装しようとすると面倒なのかについて少しだけ…。

 今回、肝要となるのが以下の、WaitInputPress/WaitInputRelease(特に後者)というキー入力のPress/Releaseの監視をしてくれるAbilityTaskになります。
f:id:raksul_01:20211226225749p:plain

 え、そいつら使えばすぐできるんじゃ…?

と思われるかもしれませんが、これらのAbilityTaskはすぐには使えません。
各GameplayAbilityをキー入力に紐づける必要があり、そのためにはプロジェクト設定のアクションマッピングだけでなく、C++を使って紐づける必要があるためです。

GameplayAbilityにキー入力を紐づける

 本記事では、おかわりはくまい氏によるセットアップ記事の通りにセットアップしたものを改変して実装していきます。

EnumでインプットIDを定義する

 定義する場所はどこが最適なのかわからないのでプロジェクトのヘッダーに定義しました。
 DisplayNameは適当でも大丈夫ですが、各アクション名はプロジェクトのアクションマッピングと同じ名前にしておきましょう。
 開発を進むにつれてインプットアクションを増やす場合はその都度、EAbilityInputIDも増やしていけば良いだけです。

プロジェクト名.h

#pragma once

#include "CoreMinimal.h"

UENUM(BlueprintType)
enum class EAbilityInputID : uint8
{
	// 0 None
	None				UMETA(DisplayName = "None"),
	// 1 Confirm
	Confirm				UMETA(DisplayName = "Confirm"),
	// 2 Cancel
	Cancel				UMETA(DisplayName = "Cancel"),
	// 3 Sprint
	Sprint				UMETA(DisplayName = "Sprint")
};

※今回は参考にしたGASShooterにConfirmやCancelといったInputIDが定義されていたので同様に追加しております。
これはおそらく、WaitForCancelInput / WaitForConfirmInputといったAbilityTaskで用いるものと思われますが、詳しく調べてないためここでは説明無しです。

GameplayAbilityクラスを拡張する

 BPでアビリティ毎のバインド先の変更が簡単にできるよう、MyGameplayAbilityクラスを作成します。

MyGameplayAbility.h

#pragma once

#include "CoreMinimal.h"
#include "プロジェクト名/プロジェクト名.h"
#include "Abilities/GameplayAbility.h"
#include "MyGameplayAbility.generated.h"

UCLASS()
class プロジェクト名_API UMyGameplayAbility : public UGameplayAbility
{
	GENERATED_BODY()
	
public:
	UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Ability")
		EAbilityInputID AbilityInputID = EAbilityInputID::None;
};
MyCharacterクラスを改変する

(変更点)
・インクルード対象にMyGameplayAbilityクラスおよびプロジェクトのヘッダーを追加する。
・AbilityListをMyGameplayAbilityクラスの配列にする。
・InputIDに対してキー入力をバインディングするためのBindASCInput()を宣言。

MyCharacter.h

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "プロジェクト名/プロジェクト名.h"
#include "AbilitySystemInterface.h"
#include "MyGameplayAbility.h"
#include "MyCharacter.generated.h"

UCLASS()
class プロジェクト名_API AMyCharacter : public ACharacter
{
	GENERATED_BODY()

・・・

public:
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Abilities, meta = (AllowPrivateAccess = "true"))
		class UAbilitySystemComponent* AbilitySystem;
	UAbilitySystemComponent* GetAbilitySystemComponent() const
	{
		return AbilitySystem;
	};

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Abilities)
		TArray<TSubclassOf<class UMyGameplayAbility>> AbilityList;
};

protected:
	void BindASCInput();

	bool bASCInputBound;

(変更点)
・InputIDを削除。
・MyGameplayAbilityクラスを継承した各アビリティのBPクラスで設定したAbilityInputID != 0であればその値をInputIDに、それ以外はInputIDに-1が割り当てられるように。
・BindASCInput()の処理を記述。

MyCharacter.cpp

・・・

void AMyCharacterBase::BeginPlay()
{
	Super::BeginPlay();
	if (AbilitySystem)
	{
		if (HasAuthority() && AbilityList.Num() > 0)
		{
			for (auto Ability : AbilityList)
			{
				if (Ability)
				{
					if (static_cast<int32>(Ability.GetDefaultObject()->AbilityInputID) != 0)
					{
						AbilitySystem->GiveAbility(FGameplayAbilitySpec(Ability.GetDefaultObject(), 1, static_cast<int32>(Ability.GetDefaultObject()->AbilityInputID), this));
					}
					else
					{
						AbilitySystem->GiveAbility(FGameplayAbilitySpec(Ability.GetDefaultObject(), 1, -1, this));
					}
				}
			}
		}
		AbilitySystem->InitAbilityActorInfo(this, this);
	}
}

void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    Super::SetupPlayerInputComponent(PlayerInputComponent);

・・・

    BindASCInput();
}

void AMyCharacter::BindASCInput()
{
	if (!bASCInputBound && IsValid(AbilitySystem) && IsValid(InputComponent))
	{
		AbilitySystem->BindAbilityActivationToInputComponent(InputComponent, FGameplayAbilityInputBinds(FString("ConfirmTarget"),
			FString("CancelTarget"), FString("EAbilityInputID"), static_cast<int32>(EAbilityInputID::Confirm), static_cast<int32>(EAbilityInputID::Cancel)));

		bASCInputBound = true;
	}
}

①GiveAbilityで渡すFGameplayAbilitySpecにInputIDを指定することで、各GameplayAbilityとInputIDとを紐づけます。
②BindASCInput()内のAbilitySystem->BindAbilityActivationToInputComponentによって、先ほどEnumで定義したInputIDに対応するキーがPress/Releaseされた際にインプットアクションが発行され、さらに①で紐づけられたInputIDに対応するGameplayAbilityがActivateされるようになっています。

 なお、AbilitySystem->BindAbilityActivationToInputComponentの実行タイミングはSetupPlayerInputComponentの後にしていますが、
 おそらくネットワーク対応ゲームの場合はOnRep_PlayerStateのときも含めて二回とすることが望ましいです(InputComponentが存在しない可能性があるため)。
※bASCInputBoundは二重にBindASCInput()が実行されないようにするための値なので、ネットワーク対応でない場合は不要。

ダッシュのGameplayAbilityクラスを作成する

 MyGameplayAbilityクラスをBP継承したGA_Sprintを作成します。
 作成したら、クラスのデフォルト設定にてAbilityInputIDをSprintにしておきます。
ちなみに、上記②のようにインプットアクションをGameplayAbilityにバインディングしているため、キャラクターのBP内で「インプットアクション○○」→「TryActivateBy~~」などを記述しておく必要はありません。
 つまり、ダッシュ以外のGameplayAbilityを追加した際は、
・EAbilityInputIDと、プロジェクトのアクションマッピングに同名のアクションを追加する。
・追加したアビリティBPの設定から、EAbilityInputIDを選択する。
これだけを行うだけで、キャラクターのBP内でアビリティ発動の処理を記述する必要がなくなるというわけです。

f:id:raksul_01:20220104044647p:plain

 中身は必要最低限で以下のような感じにしました。
Start/Stop Sprintingの中身はキャラクターのMovement Componentを取得してSetMaxWalkSpeedで移動速度を変更しているだけです。

f:id:raksul_01:20220104044924p:plain

完了

 うまくいけば、ダッシュのキーを押すとアビリティがActivateされてStart Sprintingによってスピードが上がり、キーを離すとWaitInputReleasedのOnReleaseから続くEndAbilityが実行され、Stop Sprintingによってスピードが下がって歩行に戻る といった挙動が実現しているはずです。

 無駄な部分やより良い方法があればぜひ教えていただけると幸いです。

「オーストラリアの自然」アセットで使われている道路マテリアルの中身を見てみる(1)

※この記事は、シェーダーに精通していない私の勉強のための備忘録みたいなものです。そのため、間違った部分がある場合がございます。

こんにちは、らくするです。

すっかりクリスマスシーズンですね。

今回は、永続無料コンテンツである「オーストラリアの自然」で使われている道路マテリアルの中身がどうなっているのか、ざっくりと見ていきたいと思います。

www.unrealengine.com

なぜ中身を見ようと思ったのかと言いますと、この道路、単純なマテリアルではなく地面と馴染ませるようにされていたり(馴染ませる部分の詳細についてはまた後日ですが…)、その他いろいろと工夫されている点が多い(らしい?)ので一度見てみたかったからです。

道路のメッシュについて

f:id:raksul_01:20211213105242p:plain

道路メッシュ

道路のメッシュは画像のように、おおよそ長さ3500cm,幅897cmとなっています。

緑色のフレームは単純コリジョンです。

f:id:raksul_01:20211213110240p:plain

ベースカラーテクスチャ

また、ベースカラーテクスチャは上のように4096*1024となっており、メッシュのUVはV座標0→1までぴっちり配置なので長さ方向に対して若干縮められますね。

マテリアルについて

それでは中身を見ていきます。全体は以下の画像の通りです。

f:id:raksul_01:20211213110644p:plain

道路マテリアル(全体)

これでは少しわかりづらいので、一旦下側の部分と最後のContactShadows部分を排除します。ちなみに排除した下側部分は、ランドスケープの地面マテリアルとより馴染ませるための部分、ContactShadowsは陰影の調整部分です(内部でワールドポジションオフセットとピクセル深度オフセットを調整している)。

f:id:raksul_01:20211213152208p:plain

今回見る部分以外を排除したマテリアル

大まかな内容は画像中にコメントで入れておきました。

以下、備考です。

・(ベースカラーについて)

 中央上のオーバーレイについてはみつまめ杏仁さんが以下のツイートで非常にわかりやすく図説されていますが、思った以上に計算コストが高そうです。

・(PDOについて)

 道路メッシュは略半円筒形状になっており、ピクセル深度オフセット(PDO)によって、メッシュのぶっ刺し感を低減しています。PDOが低いほどカメラ奥行方向の深度が深い(奥にある)と見做されるやつで、ディザ抜きと併用した馴染ませ用途なんかでもよく使われてます。

 このマテリアルではベースのPDOと詳細のPDOとを乗算していますが、実際には今回簡易化のために排除してしまった部分が馴染ませの肝となる部分なので、ここまでではたぶん見栄え…というかぶっ刺し感の低減には大きく影響はしません(大事なところが抜けていてごめんなさい)。

・(法線の合成について)

 上の画像中ではだいぶ割愛して書きましたが、法線の合成はおそらく慣用的な手法で計算しているものと思われます。合成計算について気になる方は是非調べてみてください。BlendAngleCorrectedNormalsなんかでもいけるのではないでしょうか。

 また、MF_ReconstructZ中で使われているDeriveNormalZは、二軸の成分から残りの成分を計算するためのものです。わかりやすさのために、FP_GunのノーマルマップからRG成分だけを抜き出して、法線の再構築(B成分の計算)を行いました。

f:id:raksul_01:20211213153325p:plain

DeriveNormalZによる法線の再構築

 このように、法線情報は2軸成分があれば残り1軸の成分は計算により求められるので、通常のノーマルマップのBチャンネルに別の情報を入れることもできます。これはメモリ節約術として時々見かけることがあります。圧縮形式などなどが変わってくるのでテクスチャの品質に影響したりするかも…。

 

ストアのコンテンツは本当に教科書ですね。

次回、今回排除した部分について見ていけたらと思います。

第16回UE4ぷちコン振り返り その③~カーソルを筆に変更する(筆先の動きもつける)~

ぷちコンの審査結果発表会も終わりましたが、ありがたいことに賞をいただくことができたのでこのまま逃亡しようとしていた(嘘です)振り返り記事を再開します。

今回紹介するものはこちら↓

f:id:raksul_01:20211005011523g:plain

概要

前回の記事で紹介させていただいたペイントシステムでは、カーソルがそのままデフォルトのものなのでそれを自前の筆に置き換えよう!っていう内容です。

また、上の動画をご覧の通り、筆の移動に合わせて筆先も動いていますね。

単純にカーソルを画像に置き換えるだけなら

Set Mouse Cursorを使う方法

・UIに配置したimageをTickでマウス位置に追従させる方法

が一般的だと思いますが、これらの方法では筆先の動きをつけることができません。やっていることはかなり後者の方法に近いですが、少し工夫して実装することにしました。

要約

実装方法を簡単にまとめると

・筆スケルタルメッシュをシーンキャプチャーでテクスチャに描画(常に更新)し、そのテクスチャをUIのimageに設定

・筆スケルタルメッシュのアニメーションBPにマウスの移動量を与える

です。

他にも、筆スケルタルメッシュを直接カメラに映し出すといった方法もあるかもしれませんが個人的には上記の方法のほうが手っ取り早いと感じたのでこちらを採用しました(ほかにも理由はありますが後述します)。

実装方法

スケルタルメッシュを用意する(blender

今回は、グレイマン人体改造ロボット改造したものを筆としました。

※意外にも審査員の方たちは初見で気づかなかったようですが…。その①の記事でも述べた通り、当初作成予定だったウケ狙いゲームの名残ですw

blenderを使って、インポートしたグレイマンのデフォルトのスケルトンを削除し、引き延ばした頭に新しいスケルトンを作成した至極雑な作りです。

f:id:raksul_01:20211005001656p:plain

これにSubstance Painterでペペっとテクスチャを作りました。

アニメーションBPを用意する

筆スケルタルメッシュができたら、UE4にインポートします。

筆スケルタルメッシュのスケルトンをベースにアニメーションBPを新規に作成し、AnimGraph内でCCDIKを使用して筆先がいい感じに動くようにしました。

f:id:raksul_01:20211005004357g:plain

アニメーションBPは以下のように組んでいます。

f:id:raksul_01:20211005004534p:plain

後述するキャンバスウィジェット(前回記事のWB_TestCanvas)からTarget Effector Location X/Yに値が代入され、実際のCCDIKにはTarget Effector Location X/YにFInterp Toで徐々に値が近づいていくEffector Location X/Yが入力として与えられています。

※BPにいっさい処理を組まず、WB_TestCanvasから直接Effector Location X/Yに値を代入しても大丈夫ですが筆先の動きが固くなってしまいます。

筆アクターとシーンキャプチャーをレベルに配置する

Actorクラスを継承したBP_GrayBrushを新規作成し、Skeletal Mesh Component(筆スケルタルメッシュ)を追加し、上記で作成したアニメーションBPを設定しておきます。

このBP_GrayBrushをレベルに配置し、さらにScene Capture 2Dがうまい具合にBP_GrayBrushをテクスチャ内に収めるように配置します。

※応募作品ではサブレベルに配置しました。

f:id:raksul_01:20211005010135p:plain

Scene Capture 2Dの設定

ここで、投影方法は平行投影にしました。なぜ今回の実装方法を採用したのかという理由の2つ目(要約のところでほかの理由と述べたやつです)は、筆の投影を平行投影にしたかったためです。

→この記事を書いている途中で透視投影に変更してみたらそっちのほうがいい感じになりましたw

上手くできるとこんな感じでキャプチャしてくれます。テクスチャレンダーターゲットTRT2D_Brushのサイズは480*480にしました。

f:id:raksul_01:20211005010614p:plain

ここまでできたら、新規マテリアルM_TRT2D_Brushを作成します。

TRT2D_Brushのままでは筆の部分だけがアルファの値が0という情報を持っているので、反転させます。

f:id:raksul_01:20211005012716p:plain

キャンバスウィジェットに追加処理を組む

最後に、前回記事で作成したWB_TestCanvasを若干変更していきます。

まず、ウィジェットに新規のイメージ(BrushのImageにM_TRT2D_Brushを設定)を追加します。

f:id:raksul_01:20211005012136p:plain

上の画像のようにAlignmentなども調整しておきます。

また、テクスチャサイズが先ほど述べた通り480*480に対して、ウィジェット上では600*600となっていますが、解像度より負荷軽減を優先しました。

で、前回記事にてオーバーライド処理を施した関数On Mouse Moveの途中に以下の処理を追加します。

f:id:raksul_01:20211005175535p:plain

マウスの移動量を筆スケルタルメッシュのアニメーションBPに与える前にノーマライズして値をベクトルの大きさが1になるように抑えています。2.0を乗算しているのは調整用です。

あ、それとコンストラクトイベントなどで筆アクターのAnim Instanceを取得しておくこともお忘れなく

他にも細かい処理(例えば、描いてる時より描いてない時のほうが筆の位置が高くしたり、音を鳴らしたり)を色々追加してますが記事の本題から逸れるので説明は省略します。

→完成!

最後に

実装方法は簡単ですが、私は思いつくのにだいぶ時間がかかってしまいました…。

しかしペイント中に筆が現れるようにするだけで、クオリティや描き心地がずいぶん上がったように感じたのでやりがいがありました!

次回の記事その④(ラスト)では「描いた線にそって道を生成する方法」について紹介します。

→力尽きて断念しました。。

 

 

第16回UE4ぷちコン振り返り その②~画面上にペイントする~

f:id:raksul_01:20210921175136p:plain

今回は、第16回UE4ぷちコンで実装した画面上へのペイントシステムをどのように実装したかなどを紹介したいと思います。

 

★目次★

 

概要

作るもののイメージはこちら↓

マウスの左ボタンを押している間は黒いインクが塗れるようなものになっています。

さて、やり方としてはUE4を触っている方であれば

TextureRenderTarget2D(以下、TRT2D)を用意し、ウィジェットに描画。マウス座標に応じてDraw TextureあるいはDraw Materialを極短い間隔(Tickなど)で使用することによってTRT2Dに対して疑似的にペイント。

といった方法が通常は思いつくかなと思います。

しかし、今回は少し違ったアプローチで実装してみました。というのも、上記の動画をご覧の通り、インクが滲んで広がるような表現をしたかったためです(大神の筆しらべもよく見ると広がっていますね)。

※上述したような通常の方法で、DrawTextureを用いて同じ座標にサイズを徐々に上げたテクスチャを描画するような手法を使ってみたところ、TRT2Dの更新頻度が上がって負荷が半端なくなりました(当然そうなるのはわかっていたことですが)。

また、以下の前回記事で紹介した、大神の筆しらべをUnityで再現したものUE4で言うTRT2Dのようなものを使用しており、インクが広がるような表現までは再現していませんでした

要約

使用したUE4のバージョンは4.26となります。

今回のアプローチを簡単にまとめると、

  • ウィジェット上にNiagaraエミッタをマウス座標にフレーム毎に更新
  • 当該Niagaraエミッタからスプライトパーティクル(TRT2Dを使用するやり方で描画するTextureの代わり)をスポーン

という手法になります。

Niagaraパーティクルをウィジェットに描画するにあたって、下記のプラグインを使用しました。無料です。

 使い方はプラグイン制作者様のチュートリアルを見た方が早いので、本記事では割愛します。

実装方法

ナイアガラシステムの準備

それでは実装方法に入りますが、UE4(特にウィジェットブループリント)の基礎知識があるものとして細かいところは省略しつつ進めます。

本記事の真似をするだけではできないかもしれませんのでご容赦ください。また、名付けは適当なので任意でお願いします。

まずは、ナイアガラシステムNS_Brushを作成します。エミッタとしてSimpleSpriteBurstを追加しておくとやりやすいと思います。

f:id:raksul_01:20210921185717p:plain

今回、最低限下記のエミッタ設定を行いました。

  • Emitter StateのLife Cycle ModeをSelf, Loop BehaviorをInfinite
    →パーティクルのスポーンが止まらないように
  • Particle StateのKill Particles When Lifetime Has Elapsedのチェックを外す
    →パーティクルが消えないように
  • Spawn Rateを750以上
  • パーティクル更新にScale Sprite Sizeを追加して、1秒で1.2倍くらいのサイズになるようScale Vector 2DBy Curveを設定

あとはスプライトに適用するマテリアルですが、以下のような適当なものを用意しました。

f:id:raksul_01:20210921191908p:plain

ナイアガラエディタでのプレビュー用マテリアル(M_BrushParticle)

これは、あくまでもナイアガラエディタ上でプレビューするためのマテリアルです。

使用しているプラグインNiagara UI Rendererの都合上、マテリアルドメインUser Interfaceにしたマテリアルも別途用意しておいてください。

結局、応募作品もこの雑なマテリアルのままにしてしまいました。

f:id:raksul_01:20210921192015p:plain
後述するNiagara System Widgetで使うマテリアル(M_BrushParticleUI)

ウィジェットの準備

キャンバス用に新規ウィジェットWB_TestCanvasを作成し、パレットからNiagara System Widgetを追加します(NS_BrushInkと名付けました)。

また、Borderを最上層に追加します(試したところ、これがないと後のOn Mouse Button Downなどが動作しないみたいです)。

f:id:raksul_01:20210921184916p:plain
f:id:raksul_01:20210921210629p:plain

そして、NS_BrushInkを選択した状態で詳細タブから以下のように設定します(マテリアルの名前が誤字ってますが気にしないでください)。

f:id:raksul_01:20210921192728p:plain

Material Remap Listはナイアガラエディタでスプライトに適用していたM_BrushParticle(左)をウィジェットへの描画用にM_BrushParticleUI(右)に置換する設定です。

また、Auto Activateのチェックを外さないとマウスのボタンを押してなくても常に描かれてしまいますので外しておきます。

次に、On Mouse Button Down関数をオーバーライドして以下のようにノードを組みます。

f:id:raksul_01:20210921193231p:plain

変数PointsVector 2Dの配列で、今回は使いませんが後に「描いた線に沿った道の生成」の紹介記事で使うことになります。

ちなみにこの処理は、左ボタンを押したポイントのみにインクを塗るためのものであり、このまま連続して線を描くときには次に説明する処理が必要となります。

この処理のために、次はOn Mouse Move関数をオーバーライドして以下のようにノードを組みます。

f:id:raksul_01:20210921194005p:plain

始めの部分でエミッタの位置をカーソル位置に更新しています。

f:id:raksul_01:20210921194140p:plain

マウス位置にエミッタ位置を更新

その後のブランチ処理は、左ボタン押下状態かつマウスが一定距離移動した場合にのみナイアガラシステムをアクティベートするものです。これがないとエミッタの設定上、カーソルを動かさなくても同じ場所に永遠にパーティクルがスポーンされ続けてしまいPCが爆発します

f:id:raksul_01:20210921194502p:plain
マウスが一定距離移動したらナイアガラシステムをアクティベート

最後の処理(拡大画像無し)は、先ほどと同様、Pointsにアイテムを追加するものです。

ポーンの用意

適当なポーンを用意して、インプットアクションやキー入力でWB_TestCanvasをビューポートに表示する処理を追加します。

また、必要に応じてSet Show Mouse CursorやSet Input Mode Game And UIなども入れる必要もあります。

f:id:raksul_01:20210921211429p:plain

※上記のようなFlip Flopの使用はバグの元となりやすいため推奨しません。真面目に作るならbool値を使いましょう。

---セットアップ完了!---

上手くいけば、Hキーを押せばペイントできるようになり、再びHキーを押すことでキャンバスが排除されるようなシステムができているはずです。

最後に

  • 実際の応募作品ではBPC_MagicBrushというコンポーネントウィジェット作成だとかスペシャルスキルだとかほとんどの機能を入れて、どのポーンに対してもペイント&道生成&スキルを簡単に実装できるようにしていました。
    f:id:raksul_01:20210921212107p:plain
    絵筆の機能をまとめてコンポーネント化したもの

以上、間違いなどがあればDMやコメント等で教えてください!