6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Unreal Engine 4 (UE4) その2Advent Calendar 2020

Day 13

UE4でVR歩行デバイス KAT Loco(KAT WALK C)のネイティブ対応

Last updated at Posted at 2020-12-12

この投稿の概要

これは、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 Walk 日本代理店様202006101143544000.jpg

KAT LocoのほうもVR歩行デバイスですが、こちらはその場足踏みをするタイプです。(近日、新バージョンのKAT Loco Sが販売されます)
足のセンサーの他に、腰に1個センサーを付けます。足のセンサーは歩行速度やゼスチャーを読み取り、足と腰のセンサーの位置関係で歩行の方角を検出します。
場所を選ばず、すぐに使える最も手軽な歩行デバイスで、トラッカーを使用したフルトラと組み合わせたい場合、こちらのほうが良いかもしれません(KAT Walk Cのほうは仕組み上姿勢が制限されるので)。
あと、歩くときは意識的にその場足踏みをしていないと壁にぶつかるので注意です。

KAT Walk 日本代理店様555545456.jpg

今回、KAT Walk Cがまだ届いていないので、KAT Locoを使いますが、おそらくWalk CもLoco Sもほぼ同じSDKでしょう。

KAT Walk C / KAT Loco のドライバについて

まずKATのドライバをインストールしして、ユーティリティKAT Gatewayを起動できるようにしてください。
ドライバの詳しいことは、付属のマニュアルを読んでいただくとしまして、ここでは、この機器の入力をゲームで扱うためのモードについて補足します。
Steamなどの市販ゲームでは、主にゲーム側で「自由歩行モード(ワープではない)」に設定をして、KATのドライバからは各々のゲームの歩行コマンド(キー入力やハンドコントロローラーのスティックなど)をエミュレートして渡すような形になります。

一方、この記事で取り扱うのは、KATのSDKを用いて、機器の入力値を直接ゲームで取り扱い、キャラクタを移動させるネイティブ方式です。
この場合、KAT Gatewayを起動したら
image.png
のように、マッピングモードを「Global」側にしておいてください。

APIを知る

前述のように、これを書いている時点でKATのSDKは、まだUnity版しか公式には公開されていません。なのでまずAPIをUnity版のSDKのサンプルコードから知ることにします。

Unity用のSDKを入手します

日本代理店さんが公開しているこちら

のリンクから、SDKをダウンロードします。 **記事を書いている時点では、Unreal用は公開されておらず、これはUnity用です。**

ダウンロードしたら解凍します。
WalkerBase_Loco.dllを取り出します。
image.png

今回これを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を使用している箇所を探します。
image.png

ありました。この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というフォルダを作ります。
image.png
その中に、先程のWalkerBase_Loco.dllをコピーします。

UE4で使う

ここからの流れを書いておきます。

  • KAT APIを操作するクラスを作成
  • 上記クラスを使うコンポーネントを作成
  • 上記コンポーネントをプレイヤーのVRPawnに組み込み

KAT APIを操作するクラスを作成

UnrealEngineを起動して、ターゲットとなるVRプロジェクトを開きます。

コンテンツブラウザの新規追加から「新規C++クラス」を実行します。
image.png
親クラスを選択します。「なし」にします。
image.png
適当な名前をつけます。
image.png

先程の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++クラス」を実行します。
image.png

親クラスを選択します。例えばActor Componentにします。
image.png

名前をつけます。
image.png

クラスのソースコードが生成されるので、これを開きます。
image.png

こんな感じのヘッダーファイルと、ソースファイルが出来ています。
image.png

ヘッダとソースを、それぞれ例えば次のように書きます。

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を追加します。
image.png

Tick処理が来なかったので、Enableにしておきます
image.png

EventTickに移動処理を割り込ませます。ここでは関数にしました。
image.png

関数の中身は例えばこんな感じです(ちょっと適当です)
image.png

一方、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++コードのほうに移動処理を入れたものです。

おわりに

す、すみません。。今回アドベントカレンダー用の投稿となり、締切ギリギリになってしまい、途中から駆け足になってしまいました。
また、動作チェックも不十分なところがあるので、後ほど投稿を編集して、完全なコードをgithubにアップする予定です(・∀・;)

明日は、Naotsunさんの「最近作ったプラグインについて」です!

6
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?