この投稿の概要
これは、VR方向デバイスKAT Walk C、およびKAT Locoを、Unreal Engineで作成したVRプロジェクトでネイティブ対応させる手順のまとめです。KATのAPIをC++で取り扱います。
背景
そろそろVR用歩行デバイスKAT Walk Cは日本の購入者にも届き始めている頃かもしれません。しかし、私がこれを書いている時点では、まだKAT Walk Cは私の手元には届いてなく、あるのは簡易版のKAT Locoのみです。
また、KAT LocoのSDKは(公式では)Unity用しか公開されていなく、Unreal用は出回っていません。
ここでは、KAT Loco SDK for UnityのDLLを使いUE4で使う手順をまとめてみました。
(追記)KAT Walk CはKAT Locoとまったく同じSDK(DLL)でした。この投稿の手法はそのままKAT Walk Cで使えます。
(追記2)KAT Walk CのUE4版SDKが日本総代理店様より公開されました。ここのページの下の方のリンクから入手できます。
VR歩行デバイスについて
VR歩行デバイスは何がうれしい?
今現在、歩行デバイスを使わないVR空間内での移動は、
- コントロラーのスティックやパッドによる直接移動
- コントロラーのスティックやパッドでターゲット位置を指定してワープする移動
- ルームスケールでの実際の歩行
が、一般的です。
しかし、直接移動ではVR酔いをしやすく、ワープ移動は現実味を損ない、ルームスケールは現実の部屋の広さに制限される、といった弱点があります。
歩行デバイスを使うと、現実の肉体は一箇所に固定されたまま、仮想空間内を無限に歩くことができて、かつ、歩くという動作のおかげでVR酔いが軽減されるというメリットがあります。(デメリットは、疲れることです)
KAT Walk C、KAT Locoとは?
KAT Walk Cは、KAT VR社が先日リリースした家庭用のVR歩行デバイスです。
一般販売は来年初頭に出荷、クラウドファンディングではそろそろ国内のバッカーにも届き始めている頃かと思います。
仕組みは、滑りやすい特製の靴を履き、滑りやすい皿の内側をムーンウォークするタイプです。
腰を柱に固定し(腰サポートは上下に動く)、体の方向を変えると、柱ごと回転します。
靴にはセンサーが付いていて、これは足の動きを読み取り、歩行速度とゼスチャー(後述)を検出します。
歩行の方角は柱の回転位置から読み取ります(靴から読み取るような設定も可能)。
これにより、視線(HMDの方向)とは独立して、体の向きに歩くことができます。
KAT LocoのほうもVR歩行デバイスですが、こちらはその場足踏みをするタイプです。(近日、新バージョンのKAT Loco Sが販売されます)
足のセンサーの他に、腰に1個センサーを付けます。足のセンサーは歩行速度やゼスチャーを読み取り、足と腰のセンサーの位置関係で歩行の方角を検出します。
場所を選ばず、すぐに使える最も手軽な歩行デバイスで、トラッカーを使用したフルトラと組み合わせたい場合、こちらのほうが良いかもしれません(KAT Walk Cのほうは仕組み上姿勢が制限されるので)。
あと、歩くときは意識的にその場足踏みをしていないと壁にぶつかるので注意です。
今回、KAT Walk Cがまだ届いていないので、KAT Locoを使いますが、おそらくWalk CもLoco Sもほぼ同じSDKでしょう。
KAT Walk C / KAT Loco のドライバについて
まずKATのドライバをインストールしして、ユーティリティKAT Gatewayを起動できるようにしてください。
ドライバの詳しいことは、付属のマニュアルを読んでいただくとしまして、ここでは、この機器の入力をゲームで扱うためのモードについて補足します。
Steamなどの市販ゲームでは、主にゲーム側で「自由歩行モード(ワープではない)」に設定をして、KATのドライバからは各々のゲームの歩行コマンド(キー入力やハンドコントロローラーのスティックなど)をエミュレートして渡すような形になります。
一方、この記事で取り扱うのは、KATのSDKを用いて、機器の入力値を直接ゲームで取り扱い、キャラクタを移動させるネイティブ方式です。
この場合、KAT Gatewayを起動したら
のように、マッピングモードを「Global」側にしておいてください。
APIを知る
前述のように、これを書いている時点でKATのSDKは、まだUnity版しか公式には公開されていません。なのでまずAPIをUnity版のSDKのサンプルコードから知ることにします。
Unity用のSDKを入手します
日本代理店さんが公開しているこちら
のリンクから、SDKをダウンロードします。 **記事を書いている時点では、Unreal用は公開されておらず、これはUnity用です。**KATloco開発キッド(SDK)
— 【公式】 -KAT VR 日本総代理店- (@KATVRJP_TAKUMI) July 31, 2020
unity版公開のお知らせ
unity版のSDKが公開されましたのでリンクを公開致します。
よろしくお願いします。https://t.co/LFDT55L8Xm
ダウンロードしたら解凍します。
WalkerBase_Loco.dllを取り出します。
今回これをUnrealで使う、という話になります。
ちなみに、このWalkerBase_Loco.dllは、ファイルマッピングでドライバ側とプロセス間通信を行っているようです。ドライバには現時点でKat Loco以外にも既にKAT Loco S(新verのLoco)やKat Walk C用のDLLも含まれていますので、後に公開されるWalkerBase_C.dllが入手できれば同じ方法でKAT Walk Cのネイティブ対応が可能になります。 (KAT Walk CはLocoと同じDLLを使用することがわかりました。)
Unityでサンプルスクリプトを読む
APIの関数をUnityのサンプルコードから読み取ります。この作業は、みなさんが新規に行う必要はありません。
Unityで適当な新規プロジェクトをつくり、先程のSDKに入っていたKAT_unity_loco_xxx.unitypackageをインポートします。
このようなサンプルコードが入っていますので、DLLを使用している箇所を探します。
ありました。この3つの関数がKAT LocoのAPIのようです。コメントが中国語ですが、なんとなく意味はわかりますね。
この後、これを参考にしてC++からアクセスします。
#region Interface
/// <summary>
/// 初始化函数
/// </summary>
/// <returns>
/// -1:内存开辟失败
/// -2:线程启动失败
/// 0:初始化成功
/// 1:已初始化
/// </returns>
[DllImport("WalkerBase_Loco.dll", CallingConvention = CallingConvention.Cdecl)]
public extern static int Loco_Init();
/// <summary>
/// 获取行走数据
/// </summary>
/// <param name="Bodyyaw">角度</param>
/// <param name="WalkPower">速度</param>
/// <param name="MoveDirection">方向 -1:前进/1:后退</param>
/// <param name="IsMoving">是否在行走:1:是 0:否</param>
/// <param name="Distance">总路程(暂不生效)</param>
/// <returns>
/// -1:未初始化
/// 0:获取成功
/// </returns>
[DllImport("WalkerBase_Loco.dll", CallingConvention = CallingConvention.Cdecl)]
public extern static int Get_Loco_Data(ref int Bodyyaw, ref double WalkPower, ref int MoveDirection, ref int IsMoving, ref int Distance);
/// <summary>
/// 获取特殊动作数据
/// </summary>
/// <param name="Action">-1:左平移 1:右平移 0:其他</param>
/// <returns>
/// -1:未初始化
/// 0:获取成功
/// </returns>
[DllImport("WalkerBase_Loco.dll", CallingConvention = CallingConvention.Cdecl)]
public extern static int Get_Loco_Action(ref int Action);
#endregion
Unreal Engineで使う
UE4で使う準備
Unreal Engineで既存のVRプロジェクトを開くか、新規作成します。
ターゲットとなるUE4のプロジェクトの.uprojectファイルのある場所に、Pluginsというフォルダを作ります。
その中に、先程のWalkerBase_Loco.dllをコピーします。
UE4で使う
ここからの流れを書いておきます。
- KAT APIを操作するクラスを作成
- 上記クラスを使うコンポーネントを作成
- 上記コンポーネントをプレイヤーのVRPawnに組み込み
KAT APIを操作するクラスを作成
UnrealEngineを起動して、ターゲットとなるVRプロジェクトを開きます。
コンテンツブラウザの新規追加から「新規C++クラス」を実行します。
親クラスを選択します。「なし」にします。
適当な名前をつけます。
先程のC#のサンプルコードを参考に、C++でAPI関数の定義を書くと、次のようになります。これを.cppに追加してください。
typedef int(*Loco_Init)();
typedef int(*Get_Loco_Data)( int& Bodyyaw, double& WalkPower, int& MoveDirection, int& IsMoving, float& Distance );
typedef int(*Get_Loco_Action)( int &Action );
Loco_Init LocoInit;
Get_Loco_Data GetLocoData;
Get_Loco_Action GetLocoAction;
各関数の仕様は、次のようになります。
Loco_Init()
デバイスを初期化します
返り値0で初期化失敗
Get_Loco_Data( int& Bodyyaw, double& WalkPower, int& MoveDirection, int& IsMoving, float& Distance )
デバイスの現在の状態を取得します。
-
Bodyyaw
体(足)の向いている方角 -
WalkPower
歩行速度 -
MoveDirection
前進/後退フラグ。前進は-1、後退は1が取得されるようです -
IsMoving
動いているかフラグ。1で動いている、0で動いていない -
Distance
総距離?(現在は無効で0が取得されるようです)
Get_Loco_Action( int &Action )
デバイスの現在の状態(アクション)を取得します。
- Action
アクション(ゼスチャー)フラグ。-1は左移動、1は右移動、アクション無しは0
たぶんクルーズモード(自動移動ゼスチャー)は2とか返って来ます(未確認)
KAT APIを操作するクラスを作成(全コード)
上記を踏まえて、コードを書きます。例えば次のような感じになります。
ここでは、使いやすいようにシングルトンにしてみました。
ヘッダー
#pragma once
#include "CoreMinimal.h"
class HOGEVR_API KATController {
public:
static KATController& GetInstance();
void UpdateData();
float BodyYaw;
double WalkPower;
int32 MoveDirection;
int32 IsMoving;
int32 Action;
float Distance;
private:
KATController();
~KATController();
void Initialize();
void Dispose();
};
ソース
#include "KATController.h"
namespace
{
typedef int (*Loco_Init)();
typedef int (*Get_Loco_Data)( int& Bodyyaw, double& WalkPower, int& MoveDirection, int& IsMoving, float& Distance );
typedef int (*Get_Loco_Action)( int& Action );
Loco_Init LocoInit;
Get_Loco_Data GetLocoData;
Get_Loco_Action GetLocoAction;
void* s_DLL = nullptr;
}
KATController& KATController::GetInstance()
{
static KATController instance;
return instance;
}
KATController::KATController()
{
Initialize();
}
KATController::~KATController()
{
Dispose();
}
void KATController::Dispose()
{
LocoInit = nullptr;
GetLocoData = nullptr;
GetLocoAction = nullptr;
if ( s_DLL != nullptr ) {
FPlatformProcess::FreeDllHandle( s_DLL );
s_DLL = nullptr;
}
}
void KATController::Initialize()
{
Dispose();
FString hoge = FPaths::ProjectPluginsDir();
FString filePath = FPaths::Combine( *FPaths::ProjectPluginsDir(), TEXT( "WalkerBase_Loco.dll" ) );
if ( FPaths::FileExists( filePath ) ) {
s_DLL = FPlatformProcess::GetDllHandle( *filePath );
if ( s_DLL == nullptr ) return;
LocoInit = nullptr;
FString procName = "Loco_Init";
FString procLog = "";
LocoInit = (Loco_Init)FPlatformProcess::GetDllExport( s_DLL, *procName );
GetLocoData = nullptr;
procName = "Get_Loco_Data";
GetLocoData = (Get_Loco_Data)FPlatformProcess::GetDllExport( s_DLL, *procName );
GetLocoAction = NULL;
procName = "Get_Loco_Action";
GetLocoAction = (Get_Loco_Action)FPlatformProcess::GetDllExport( s_DLL, *procName );
Loco_Init();
}
}
void KATController::UpdateData()
{
if ( s_DLL == nullptr ) return;
int bodyYaw = 0, moveDirection = 0, isMoving = 0, action = 0;
double walkPower = 0;
float distance = 0;
int retData = GetLocoData( bodyYaw, walkPower, moveDirection, isMoving, distance );
int retAction = GetLocoAction( action );
if ( retData != 0 || retAction != 0 ) return;
BodyYaw = ((float)bodyYaw / 1024.0f) * 360.0f; // 方向
WalkPower = (float)walkPower; // 歩き強度→速さへ
MoveDirection = moveDirection; // 前進/後退フラグ
IsMoving = isMoving; // 歩いているか停止してるかフラグ
Distance = distance; // 歩幅
Action = action; // 特別ゼスチャー(横移動、クルーズモードなど)
}
ざっくりとした説明ですが、
Initialize()で、先程plugin下に配置したWalkerBase_Loco.dllの存在確認をして、定義したAPI関数と結びつけ、その後Locoの初期化をします。
そしてフレーム毎に呼ぶUpdateData()では、Locoの状態を取得してメンバ変数に格納します。
コンポーネントの作成
コンテンツブラウザの新規追加から「新規C++クラス」を実行します。
親クラスを選択します。例えばActor Componentにします。
こんな感じのヘッダーファイルと、ソースファイルが出来ています。
ヘッダとソースを、それぞれ例えば次のように書きます。
TreadmillComponent.h
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "TreadmillComponent.generated.h"
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class HOGEVR_API UTreadmillComponent : public UActorComponent {
GENERATED_BODY()
public:
// Sets default values for this component's properties
UTreadmillComponent();
protected:
// Called when the game starts
virtual void BeginPlay() override;
public:
// Called every frame
virtual void TickComponent( float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction ) override;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
float BodyYaw;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
float WalkPower;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
int32 MoveDirection;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
int32 IsMoving;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
int32 Action;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
float Distance;
};
TreadmillComponent.cpp
#include "TreadmillComponent.h"
#include "KATController.h"
// Sets default values for this component's properties
UTreadmillComponent::UTreadmillComponent()
{
// Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
// off to improve performance if you don't need them.
PrimaryComponentTick.bCanEverTick = true;
// ...
}
// Called when the game starts
void UTreadmillComponent::BeginPlay()
{
Super::BeginPlay();
// ...
}
// Called every frame
void UTreadmillComponent::TickComponent( float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction )
{
Super::TickComponent( DeltaTime, TickType, ThisTickFunction );
KATController& kat = KATController::GetInstance();
kat.UpdateData();
BodyYaw = kat.BodyYaw;
WalkPower = kat.WalkPower;
MoveDirection = kat.MoveDirection;
IsMoving = kat.IsMoving;
Action = kat.Action;
Distance = kat.Distance;
}
ここでは、C++とブループリントを橋渡しするクラスを作りました。
先程作成したKATControllerから毎フレーム数値を読み取り、ブループリントからアクセスできる変数に入れています。
直接C++でキャラクターを動かす場合は、このクラス内などに移動処理を書いても良いと思います。
コンポーネントをポーンに組み込む
対象となるポーンに、作成したTreadmillComponentを追加します。
EventTickに移動処理を割り込ませます。ここでは関数にしました。
一方、C++コードで処理する場合は、こんな感じでしょうか
// Called every frame
void UTreadmillComponent::TickComponent( float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction )
{
Super::TickComponent( DeltaTime, TickType, ThisTickFunction );
AActor* owner = GetOwner();
if ( owner == nullptr ) return;
APawn* pawn = Cast<APawn>( owner );
if ( pawn == nullptr ) return;
KATController& kat = KATController::GetInstance();
kat.UpdateData();
FRotator rot( 0.0f, kat.BodyYaw, 0.0f ) ;
FVector location = owner->GetActorLocation();
location += rot.Vector() * 10.0f;
location.Z += 10.0f ;
if ( kat.IsMoving ) {
// 歩かせる
float speed = kat.WalkPower* 0.0002f ;
if ( speed > 1.0f ) speed = 1.0f;
else if ( speed < -1.0 ) speed = -1.0;
pawn->AddMovementInput( rot.Vector(), speed ); // 入力換算
}
}
動いてみた動画
下のリンクの動画のようになります。これはC++コードのほうに移動処理を入れたものです。
KAT LocoのDLLで歩いてみた動画😄 #KATLoco #UE4 #vive pic.twitter.com/9P5CAAprVx
— オンドレラ (@ondorela) November 8, 2020
おわりに
す、すみません。。今回アドベントカレンダー用の投稿となり、締切ギリギリになってしまい、途中から駆け足になってしまいました。
また、動作チェックも不十分なところがあるので、後ほど投稿を編集して、完全なコードをgithubにアップする予定です(・∀・;)
明日は、Naotsunさんの「最近作ったプラグインについて」です!