PaperSloth’s diary

主にゲーム開発関連についての記事を書きます。

UE4 マウスカーソルを自作アイコンにする

環境

Unreal Engine 4.18.3


マウス座標の取得

Widgetでマウス座標を取得するには下記の2つのノードがあります。
・Get Mouse Position
・Get Mouse Position Scaled by DPI
f:id:PaperSloth:20180206195202p:plain

基本的にはGet Mouse Position Scaled by DPIで良いです。

画面解像度が固定でDPIスケールが1.0の場合や
DPIスケールの値が変わらないようにしている場合など
特殊な場合はGet Mouse Positionを使う場合もあると思います。

DPIスケールは画面解像度の変更に合わせてUIのサイズを変更してくれる機能です。
例えば、DPI Scaleが1.0の場合はGet Mouse Positionで問題ありませんが
初期設定では1280x720でWidgetを編集しており、DPI Scaleは約0.66です。
この値はProjectSettings -> Engine -> User Interfaceからカーブエディタで自由に編集できますが
基本的には初期設定のまま開発を行うでしょう。
初期値は1920x1080では1.0で3840x2160の場合は2.0が設定されています。
f:id:PaperSloth:20180206195021p:plain

この辺りの解像度の変化の違いをユーザー側が気にせず取得できるのが
Get Mouse Position Scaled by DPIとなっています。


マウスカーソルの変更

今回はマウスカーソルのみのWidget(WBP_MouseCursor)と
それを使用するWidget(WBP_MouseMove)の2つのWidgetを用意します。
f:id:PaperSloth:20180206195421p:plain

マウスカーソルのみのWidgetは下記のようにImageを1つ置いただけのWidget
Blueprint Graphには特に変更を加えません。
今回は特に素材を用意していないので、ポイントライトのテクスチャを使用しています。
f:id:PaperSloth:20180206195342p:plain

因みにマウスカーソルのみのWidget(WBP_MouseCursor)を作成してパーツとして分けていますが
今回の実装内容的にはWBP_MouseMoveにImageを配置する実装でも問題ありません。

もう一つのWidget(WBP_MouseMove)には先ほど作成したWidgetを配置します。
Palette内にUser Created -> WBP_MouseCursor(自作Widget名)があることを確認します。
f:id:PaperSloth:20180206195740p:plain

今回はマウスカーソルのアイコンがマウスの中心にきてほしいため
Allignmentには0.5. 0.5を代入してマウスカーソルの中心にアイコンがくるように補正しています。
f:id:PaperSloth:20180206195918p:plain
座標は画面外でもどこでもお好きなように。

続いてBlueprintの実装に移ります。
一旦は作成したWidgetを描画します。
ここでは確認のためにLevel Blueprintで行っています。
f:id:PaperSloth:20180206200024p:plain

続いて先ほど作成したWidgetのBlueprint Graphに移ります。
先ずは動作確認用に一旦マウスカーソルを表示することにします。
f:id:PaperSloth:20180206200141p:plain

ここまでで実行してアイコン画像とマウスカーソルが表示されていることを確認します。
f:id:PaperSloth:20180206200508p:plain


マウスカーソルの追従

マウスカーソルの座標を更新するには
アイコンの座標にMouse座標を代入するだけですね。

下記のようなノードになります。
f:id:PaperSloth:20180206200828p:plain

実行すると下記のようにアイコンがマウスの位置に合わせて動くことが確認できます。
※キャプチャーツールの都合上、マウスカーソルは表示されていません。


続いてマウスカーソルの位置に徐々に近づくような実装も紹介しておきます。
Interp Toノードで徐々に値を更新することで実現可能です。
f:id:PaperSloth:20180206201540p:plain

動画が分かりにくいのですが、実行してみるとこんな感じです。

今回は適当に5という値を入れましたが
変数に昇格させて場面によって速度を変えたりオプション画面から変更できると便利そうですね。

最後に動作確認ができたので、不要であればShow Mouse Cursorを削除しておきます。
以上で完成です。

参考資料

DPIスケールについて
https://docs.unrealengine.com/latest/JPN/Engine/UMG/UserGuide/DPIScaling/index.html

Mouse座標の取得について
[UE4] Input入門|株式会社ヒストリア


マウスカーソルの表示方法についてはPlayer Controllerを継承したBlueprintから変更する方法とBlueprintからアクセスする方法の2つがあります。

Blueprintから変更する方法について
http://kagring.blog.fc2.com/blog-entry-122.html

PlayerControllerを継承して変更する方法について
https://docs.unrealengine.com/ja/Resources/ContentExamples/MouseInterface/MouseControlSetup/index.html

おまけ

おかずさんがもっと便利な作成な方法について教えてくださいました。
https://twitter.com/pafuhana1213/status/960869841598013440?s=20

UE4 リフレクションに指定できる型について

環境

Unreal Engine 4.17.2

概要

Blueprintに公開可能な型と公開できない型を書いていきます。
他にも追記した方がいいことがあれば、コメントやTwitterで指摘いただけると助かります。
UPROPERTYで説明していますが、UFUNCTIONでも基本的に同様です。

enumUENUMを付ければ公開可能です。
structはUSTRUCTを付けてBlueprintTypeが指定されていれば公開可能です。

コードは下記クラスに追加していきます。
因みにUPROPERTYでCategoryを指定しない場合はクラス名がカテゴリ名になります。

#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ReflectionActor.generated.h"
 
UCLASS()
class PROJECTNAME_API AReflectionActor : public AActor
{
    GENERATED_BODY()
public:
    AReflectionActor();
private:
    // ここに変数を追加
};



Blueprintに公開可能な型

公開可能なプリミティブ型

UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
bool _bool;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
uint8 _uint;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
int32 _int;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
float _float;

f:id:PaperSloth:20180131234057p:plain


公開可能なUE4の型

ここではよく使う型だけを紹介します。
他にも挙げきれないくらい種類があります。
一部の構造体とUObjectを継承しているクラスポインタが公開可能です。

UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
FName name;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
FString string;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
FText text;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
FVector vector;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
FRotator rotator;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
FTransform transform;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
UObject* _object;

f:id:PaperSloth:20180131234302p:plain

UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
FMatrix matrix;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
FQuat quat;

Matrix, QuaternionはBlueprintへの公開は可能です。
f:id:PaperSloth:20180201005332p:plain
ですが、これらを操作するための関数はBlueprintにはほとんど公開されていません。

公開可能な構造体、列挙型

USTRUCT(BlueprintType)
struct FFoo
{
    GENERATED_USTRUCT_BODY()
 
    float _float;
 
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    int32 _int;
};
 
// 省略
 
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
FFoo foo;


この場合はUPROPERTYを公開設定で付与した_intのみがBlueprint上に公開されます。
f:id:PaperSloth:20180201005640p:plain

UENUM()
enum class EBar : uint8
{
    State1,
    State2,
    State3,
    Num        UMETA(Hidden)
};
 
UENUM()
namespace EBaz
{
    enum Type
    {
        State1,
        State2,
        State3,
        Num        UMETA(Hidden)
    };
}
 
// 省略
 
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
EBar bar;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
TEnumAsByte<EBaz::Type> baz;


enumの書き方を2種類紹介しましたが、Blueprintからの見え方は同じです。
f:id:PaperSloth:20180201005828p:plain
EBazのような古いenumの書き方を示したのには理由があります。
UE4のエンジンコードにはまだ古い書き方が残っているため
それらを利用する際にTEnumAsByteを使用します。
自身で列挙型を定義する際は上のEBarのような書き方をオススメします。
詳しくはこちら
papersloth.hatenablog.com

公開可能なcontainer

UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
TArray<int32> _array;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
TMap<int32, int32> map;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
TSet<int32> set;

ここではintを指定していますが
公開可能なプリミティブ型、構造体、クラスポインタは基本的に使用可能です。
f:id:PaperSloth:20180201010039p:plain

また、Map, SetのBlueprintでの扱いに関してですが
4.13以下では使用不可
4.14では実験的な機能
4.15から正式なサポートとなります。


Blueprintに公開不可能な型

これらはC++側では当然使用可能ですが、Blueprintへの公開はできません。
UPROPERTY, UFUNCTIONを付与するとエラーとなります。
Blueprintで使用したい場合はcastするなどの対応が必要です。
数が多いため、一部のみを抜粋しています。

プリミティブ型

UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
uint16 _uint16;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
uint32 _uint32;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
uint64 _uint64;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
int8 _int8;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
int16 _int16;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
int64 _int64;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
double _double;


UE4で定義された型、container

#include "Containers/StaticArray.h"
 
// 省略
 
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
FMatrix2x2 mat22;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
TStaticArray<int32, 10> staticArray;



コード全容

// Copyright(c) 2018 PaperSloth

#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Containers/StaticArray.h"
#include "ReflectionActor.generated.h"
 
USTRUCT(BlueprintType)
struct FFoo
{
    GENERATED_USTRUCT_BODY()
 
    float _float;
 
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    int32 _int;
};
 
UENUM()
enum class EBar : uint8
{
    State1,
    State2,
    State3,
    Num        UMETA(Hidden)
};
 
UENUM()
namespace EBaz
{
    enum Type
    {
        State1,
        State2,
        State3,
        Num        UMETA(Hidden)
    };
}
 
UCLASS()
class PROJECTNAME_API AReflectionActor : public AActor
{
    GENERATED_BODY()
public:
    AReflectionActor();
private:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
    FFoo foo;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
    EBar bar;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
    TEnumAsByte<EBaz::Type> baz;
 
    UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
    bool _bool;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
    uint8 _uint;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
    int32 _int;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
    float _float;
 
    UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
    FName name;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
    FString string;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
    FText text;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
    FVector vector;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
    FRotator rotator;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
    FTransform transform;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
    UObject* _object;
 
    UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
    FMatrix matrix;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
    FQuat quat;
 
    UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
    TArray<int32> _array;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
    TMap<int32, int32> map;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
    TSet<int32> set;
 
#if 0
    UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
    uint16 _uint16;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
    uint32 _uint32;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
    uint64 _uint64;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
    int8 _int8;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
    int16 _int16;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
    int64 _int64;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
    double _double;
 
    UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
    FMatrix2x2 mat22;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
    TStaticArray<int32, 10> staticArray;
#endif
};

f:id:PaperSloth:20180201010405p:plain

UE4 ヒットストップの実装について

※1点修正があったため追記しました。(2018/01/31 1:22)
詳しくはページ下部をご覧ください。

環境

Unreal Engine 4.17.2

Time Dilationについて

ヒットストップの実装に関してはGlobal Time Dilationの値を変更するだけで簡単に実装ができます。
Time Dilationの説明について特に知る必要がないという人は読み飛ばしてください。

ここではTime Dilationの種類等について解説します。
Time Dilationは初期値が1.0です。
デフォルトの設定では
最小値は0.0001
最大値は20.0
となっています。
1を超える値を設定した場合、例えば2.0の場合は2倍速になることを表しています。
逆に1未満の場合、例えば0.5の場合は通常の1/2の速度になることを表しています。

基本的には下図のようなCustom Time DilationとGlobal Time Dilationを使用します。
f:id:PaperSloth:20180128231743p:plain

Custom Time Dilationとは

Custom Time DilationはActorに設定されたActorごとの速度を表します。
定義は下記のActor内に記述があります。
Engine/Source/Runtime/Engine/Classes/GameFramework/Actor.h
Engine/Source/Runtime/Engine/Private/Actor.cpp

Actorの更新呼び出しの中で
下図のように使用されています。

Target->TickActor(DeltaTime * Target->CustomTimeDilation, TickType, *this);	

Global Time Dilationとは

これは名前にGlobalとあるようにゲーム中の更新に関わります。
定義箇所は下記です。
Engine/Source/Runtime/Engine/Classes/GameFramework/WorldSettings.h
Engine/Source/Runtime/Engine/Private/WorldSetting.cpp

因みにC++側で使用する際はほとんどの場合では
代わりにGetEffectiveTimeDilationを使用してください。という警告があります。

先程最小値は0.0001で最大値は20.0と書きましたが、この設定は変更することが出来ます。
f:id:PaperSloth:20180128232843p:plain

World Setteingsタブの最下部に隠れたパラメーターとして
Min Global Time Dilation, Max Global Time Dilationがあります。
BlueprintからSet Global Time Dilationを呼び出した際にはこの値を参照してClampされています。
そのため、厳密には0を指定しても0.0001が設定されるため、完全に更新が停止するというわけではありません。

ヒットストップの実装

前置きが長くなりましたが、ヒットストップの実装は至って簡単です。
今回はMacroLibraryとして作成したものを例に紹介します。
親クラスにはActorを指定しています。

実装にはたった5つのノードを呼び出すだけの非常にシンプルな実装となっています。
f:id:PaperSloth:20180128233845p:plain

図に説明があるように
Global Time Dilationにどれくらいゆっくりにしたいかの値を設定します。
0以上1未満の値でゲーム全体をゆっくりにすることが出来ます。

次にDelayで元の早さに戻るまでの時間を設定します。

呼び出し例としては下記のような感じです。
ここではわかりやすくするためにDelayに渡す値をStop Timeという名前にしています。
通常の1/10の速度に変更し、0.15秒後に元の早さに戻るように設定しています。
※0.15秒ではなく、1.5秒後になります。
詳しくは追記したページ下部の説明をご参照ください。
f:id:PaperSloth:20180128234145p:plain

実際にヒットストップを使用した感じは下記のような感じになります。
ここでは敵の撃破時に使用しています。
容量の都合上、10FPSのGifで分かりにくいですが、こんな動きになります。
f:id:PaperSloth:20180130223753g:plain

※1点誤りがあったため、追記です。
ブログ公開後に指摘をいただきましたように
Delayノードも時間の進みが遅くなります。

これは最初、気付かずに実装すると少しハマってしまうので注意が必要です。
Global Time Dilationの影響を受けるノード
・Delay
Global Time Dilationの影響を受けないノード
・Set Timer by Event


以上のように5つのノードだけで簡単にヒットストップが実装できます。
先程の例のようにMacroLibraryにしておくと便利です。
その時はActor以上を継承したものを選択しましょう。
また、FunctionLibraryでは実装できません。
理由としてはDelayなどのLatentノードの呼び出しができないからです。
ゲームのコンボや撃破時の演出など活用できる場面は幾つかあると思います。
是非試してみてください。

UE4 文字列変換について

目次

環境

Unreal Engine 4.17.2
Visual Studio Community 2015

FStringとstd::stringの変換について

プラグイン開発を行っているとネイティブのC/C++で書かれたプラグインとのやり取りが発生することがあると思います。
その時にUE4側のstring(FString)とC/C++のstring(std::string)の変換が必要になることがあります。

よくド忘れするので、簡単に個人用メモとしてまとめます。

FString -> std::string

この変換にはTCHAR_TO_UTF8マクロを使います。

FString ustr = "UnrealString";
std::string cstr = TCHAR_TO_UTF8(*ustr);

std::string -> FString

この変換にはc_strを呼び出します。

std::string cstr = "CppString";
FString ustr = cstr.c_str();

TCHAR_TO_UTF8の他にも幾つかマクロが存在します。
それらの定義は下記に記述があります。
Engine/Source/Runtime/Core/Public/Containers/StringConv.h

今回紹介したものがUTF8_TO_TCHARでしたが、他にも

TCHAR_TO_ANSI(str);
ANSI_TO_TCHAR(str);
TCHAR_TO_UTF8(str);

などがあります。


Blueprintに値を公開したい場合はFStringでなければなりません。
そのため、下記のようなものはエラーとなります。

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "String")
std::string FooName;

UFUNCTION(BlueprintCallable, Category = "String")
void ConvertString(const std::string str);

UFUNCTION(BlueprintCallable, Category = "String")
std::string ConvertString();

UPROPERTYもUFUNCTIONもFStringに変換しなければ使用することはできません。
ですが、std::stringが引数、戻り値として使用できないというわけではないため
Unreal C++側での呼び出しに限定される場合は問題ありません。

UE4 Editorのショートカットキーのカスタマイズについて

目次

環境

Unreal Engine 4.18.3

エディタのショートカットキー一覧

Editor Preferences - General - Keyboard Shortcuts以下に設定項目があります。
f:id:PaperSloth:20180128161608p:plain

PauseとFrameSkipにショートカットキーを割り当てておくと
デバッグ作業がしやすくなります。
f:id:PaperSloth:20180128162146p:plain

確認のたびにEjectしてからPauseだと止めたいタイミングで止めるのが難しいですからね。

他にもショートカットキーを設定可能な項目がたくさんあります。
お好みで設定してみてください。

また、既存のショートカットキーがどんなものがあるのかを確認するためにも一通り確認しておくと作業が楽になります。

UE4 ローカルマルチプレイについて

目次

環境

Unreal Engine 4.18.3

概要

ローカルマルチプレイの基本についてはalweiさんのブログが参考になります。
UE4 簡単なローカルマルチプレイヤーゲームを作ってみる - Let's Enjoy Unreal Engine
今回はこれに少し追記したような内容になります。


画面の分割設定について

画面を分割しない設定や縦に分割、横に分割の設定が可能です。
Project Settings - Maps & Modes - Local Multiplayer以下に設定項目があります。
f:id:PaperSloth:20180125213049p:plain


・Use SplitScreen
画面分割を行うか否かです。
デフォルトではオンになっているため、通常はこのままで良いでしょう。

・Two Player Splitscreen Layout
2人プレイ時の画面分割設定です。
Horizontal - 上画面、下画面に分割されます。デフォルトの設定です。
f:id:PaperSloth:20180125213434p:plain
Vertical - 右画面、左画面に分割されます。
f:id:PaperSloth:20180125213351p:plain

ゲーム内容に合わせてお好みでお選びください。

・Three Player Splitscreen Layout
3人プレイ時の画面分割設定です。
Favor Top - 上画面が大きく表示されます。デフォルトの設定です。
f:id:PaperSloth:20180125213537p:plain
Favor Bottom - 下画面が大きく表示されます。
f:id:PaperSloth:20180125213605p:plain

プレイヤーを指定の位置に配置したい場合について

PlayerStartを削除し、Level上に直接Pawn(Character)を配置する方法が楽です。

しかし、配置しただけではどのキャラクターも操作できないため操作設定を行います。
配置したキャラクターのPawn - Auto Possess Playerに指定したいPlayer番号を設定します。
0からカウントされるため、Player 0が1P、Player 1が2Pとなります。
f:id:PaperSloth:20180125213708p:plain

4人配置したイメージ
f:id:PaperSloth:20180125213838p:plain


2人、4人の対戦や協力ゲームだと同じキャラクターの色違いを用意したりするため簡単ですね。
Level上に直接Pawn(Character)を置くため、Pawn(Character)の種類はバラバラでも構いません。
例えば3人プレイのゲームで1対2で1人が巨大なモンスターというのも面白そうですね。
f:id:PaperSloth:20180125214328p:plain

まとめ

さて、比較的簡単にローカルマルチプレイゲームが作れますね。
私自身もUE4ぷちコンゲームジャムで3人構成のチームでこんなゲームを作りました。
www.nicovideo.jp


ですが、幾つか課題もあるためそれらが解決できそうかどうかも考えつつ挑んでみてください。

・4人プレイの場合そもそもコントローラーが用意できるか、繋げるか
 どちらも物理的な問題ですが、繋げるかは結構重要ですね。
 ノートPCのUSBポートの空き数と相談してハブを用意するなどしましょう。

・個々のプレイヤーの入力を上手くさばけそうか
 そこそこ簡単な問題ですが、肥大化したプロジェクトを
 いきなりマルチ対応したりするとぐちゃぐちゃになります。
 どうやったら綺麗に入力を処理できるかのイメージを考えましょう。

・個々のプレイヤー情報をどうやって取得するか
 TickでGet All Actors Of Classで取得するみたいな方法は極力避けたいですね。
 個々のプレイヤーの情報にアクセスしたりチェックしたりは必ず必要になるので、
 最初の段階で考えておくと良さそうです。

・リスポーン処理をどうするか
 例えば対戦ゲーム等でリスポーンがある場合に
 他のプレイヤーと同じ地点にリスポーンしてしまうのは避けたいですね。

以上のようにいざ作ってみると詰まってしまうことがあると思いますが1人プレイより盛り上がります!
Blueprintにちょっと慣れてきた方は作ってみてください。

Lumberyard ScriptCanvasで簡単なゲームロジック作成

この記事はLumberyard Advent Calendar 2017の25日目の記事です。
Lumberyard Advent Calendar 2017 - Qiita

環境

Lumberyard 1.12.0.0


参考資料

プレイヤーが移動するまでは下記2つを追ってもらえれば同じようなのができます!
【Lumberyard】Script Canvas入門 - ユーセンブログ
Script Canvas Tutorials - Lumberyard


空のレベルの作成

ScriptCanvasに入る前に適当にレベルを作成しておきます。
プロジェクトは新規作成したものでも、既存のものでも問題ありません。
f:id:PaperSloth:20171224120529p:plain

新規プロジェクト作成方法はこちら
papersloth.hatenablog.com
もし、Defaultで作成できなかったという時はEmptyも試してみてください。


プレイヤーの作成

続いてPlayerを作成していきましょう。

Entitiy OutlinerかViewport上で右クリックして
Create entity
f:id:PaperSloth:20171224121650p:plain

名前を変更したいので、Entity OutlinerかEntity Inspectorから
名前をPlayerに変更
f:id:PaperSloth:20171224121832p:plain


このままだと空のEntityなので、見た目を追加します。
Entity InspectorからAdd Compoenent
f:id:PaperSloth:20171224121959p:plain

RenderingカテゴリのMeshを追加
f:id:PaperSloth:20171224122104p:plain

まだ見た目がないので、設定していきましょう。
MeshComponentのMesh assetを選択
f:id:PaperSloth:20171224122424p:plain

Engine > EngineAssets > Objects > Default.cgfを選択
f:id:PaperSloth:20171224122548p:plain
この時に面、頂点、LOD、マテリアルの情報とプレビューが表示されるのは見やすいですね。

とりあえずプレイヤーの見た目は一旦これで終了です。
f:id:PaperSloth:20171224123128p:plain


ですが、このままだと何も起こらないので次は物理を追加しましょう。
再びAdd Componentから
Physics > Rigid Body Physicsを追加しました。
f:id:PaperSloth:20171224123410p:plain
Unityユーザーには馴染みがあるかもしれませんね。


次に入力を取れるようにします。
このあたりはtachi1427さんの解説と重複している箇所があるため
読み飛ばしていただいても問題ありません。
【Lumberyard】Script Canvas入門 - ユーセンブログ


Lumberyardでは空のEntityでは入力の取得ができない仕組みになっています。
Add Componentから Gameplay > Input
f:id:PaperSloth:20171224123728p:plain

入力の設定ファイルを作成します。
Input to event binding内にアーケードコントローラーみたいなボタンを押します。
f:id:PaperSloth:20171224124207p:plain

するとAsset Editor Windowが開くので、
File > New > Input bindingを選択します。
f:id:PaperSloth:20171224124342p:plain

(ProjectName) > inputbinding内に
player.inputbindingsを追加しました。
f:id:PaperSloth:20171224124531p:plain


作成するとこんな感じになります。
f:id:PaperSloth:20171224124753p:plain
もし、反映されていなければ
File > Openから探して開きましょう。

続いて入力イベントを登録していきます。
Input Event Groupの+ボタンを押下します。
f:id:PaperSloth:20171224125548p:plain

MoveX(左右)とMoveY(前後)を登録しました。
LumberyardはZ-upなので、Yが前後になりますね。
f:id:PaperSloth:20171224130043p:plain

続いてMoveX, MoveYにそれぞれどのキーを割り当てるかを登録していきます。
Event Generators > +
f:id:PaperSloth:20171224130729p:plain

Inputクラス作成のダイアログが出るのでOKを押します。
f:id:PaperSloth:20171224130900p:plain

するとEvent Generatorに登録され
Input Device TypeやInput Nameが設定可能になりました。
f:id:PaperSloth:20171224131035p:plain

とりあえずは前後移動のWSキーを登録しました。
左右移動のADキーも同様に追加します。
f:id:PaperSloth:20171225133423p:plain

全て追加したらFile > Saveを選択して保存します。

作成はされましたが、登録はされていないので登録します。
Playerを選択してInput > Input to event bindingsに作成したplayer.inputbindingsを追加します。
f:id:PaperSloth:20171225133839p:plain
f:id:PaperSloth:20171225133932p:plain

一旦はこれでプレイヤー周りのコンポーネントの追加、作成は完了です。
いよいよScript Canvasに入っていきます。


Playerの処理の追加

さぁ、ようやくScript Canvasの記述です。
先ずはScript Canvasを開きましょう。
Tools > Script Canvas(PREVIEW)です。
f:id:PaperSloth:20171225134223p:plain

Script Canvasを開いたらFile > New ScriptかCtrl + Nで新規作成します
f:id:PaperSloth:20171225134311p:plain

一旦ここで保存します。
File > Save(Ctrl + S)かFile > Save as...(Ctrl + Shift + S)です。
Lumberyard/1.12.0.0/dev/(ProjectName)/ScriptCanvasにPlayerController.scriptcanvasで保存しました。
f:id:PaperSloth:20171225134630p:plain


早速処理を書いていきましょう。
操作はUE4のBlueprintを触ったことがある人は入りやすいかもしれません。
先ずは入力の取得から。
Script Canvas上で右クリックし、inputと入力すると下記のようになります。
Input Handlerを選択しましょう。
f:id:PaperSloth:20171225140330p:plain

MoveXとMoveYとLogを下図のように繋いで先ずは入力が取れているか確認します。
f:id:PaperSloth:20171225140638p:plain

playerを選択してAdd ComponentでScript Canvasを追加します。
f:id:PaperSloth:20171225140805p:plain

Script Canvas Assetの参照も追加します。
f:id:PaperSloth:20171225140908p:plain

先程作成したplayercontroller.scriptcanvasを割り当てたら
実行してWASDキーを押下して、WDで1.0、ASで-1.0が出力されることを確認します。
f:id:PaperSloth:20171225141130p:plain


入力が取れていることが確認できたら移動処理を書きます。
移動にはMove Entityノードを使います。
Sourceは移動ターゲット
Directionは移動方向ですね。
f:id:PaperSloth:20171225141835p:plain

ですが、InputのValueとMoveEntityのDirectionは接続できません。
Number型とVector3型でScript Canvasでは自動でキャストしてくれないからですね。
f:id:PaperSloth:20171225142031p:plain

Constructノードを使ってNumberからVector3を生成します。
f:id:PaperSloth:20171225142233p:plain
同様にMoveYも組んでみると上下左右に移動できると思います。
ノードの複製はCtrl + Dで行えます。

これで動かしてみるとすごく高速移動します
f:id:PaperSloth:20171225142602g:plain


続いて速度の補正を加えることにします。
Multiplyノードで0.1をかけ、1/10の速度にします。
f:id:PaperSloth:20171225142946p:plain

どちらも同じ値なのでとりあえず変数にまとめちゃいます。
Create Variable > Numberで作成します。
f:id:PaperSloth:20171225150732p:plain

Speedという名前で保存しました。
f:id:PaperSloth:20171225150810p:plain

これで1/10の速度で動いてだいぶいい感じになりました。
f:id:PaperSloth:20171225161510g:plain

カメラの追加

動画を見ての通り、カメラ処理がないのでカメラを追加していきます。

先ずはカメラの作成から。
Create Entityして、Camera Componentをつけるだけ!
f:id:PaperSloth:20171225153248p:plain

ですが、これだと動かしてもカメラの位置が変わらないので
Playerの子にすることで追従します。
この辺はUnityと同じですね。
f:id:PaperSloth:20171225153637p:plain

また、位置も調整して少し斜め後ろから見下ろすようにしました。
f:id:PaperSloth:20171225153844p:plain

動かすと分かりますが、少し酔いますね。
f:id:PaperSloth:20171225162442g:plain


カメラの追従を緩やかにする。

このままでもカメラは追従しますが、動きが早いと酔ってしまうので緩やかにする追従するようにします。
UnityだとVector3.MoveTowards, UE4だとVInterpToでサクッと実装できますが
Lumberyardでそれに該当する関数が見つけられなかったので、自作していきます。
CameraRig Componentを活用すればもっと楽に実装できる気もします。

CameraにScript Canvasを追加し、新規にCamera Managerという名前で追加しました。
f:id:PaperSloth:20171225170022p:plain

Camera側でPlayerを参照するには
Playerを選択した状態でReference Selected Entityを選択します。
UE4のLevel BlueprintでLevel上のActorを取得する時と同じようなイメージですね。
f:id:PaperSloth:20171225170238p:plain

先ずはCameraとPlayerの距離ベクトルをDistanceという変数を作成し、保存します。
On Graph StartノードはUnityでいうStart, UE4でいうBeginPlayです。
f:id:PaperSloth:20171225183410p:plain

次は長いので、小分けに説明していきます。
OnTickノードはUnityでいうUpdate, UE4でいうTickです。
InterpSpeed, Offsetという変数を作成しました。
InterpSpeedで追従の速度を調整することになります。
f:id:PaperSloth:20171225183752p:plain

次にPlayerの位置に先程のプレイヤーからカメラの距離を加算します。
この値をカメラの座標にセットすると親子階層にした時と同様に固定距離でべったり貼り付くカメラになりますね。
f:id:PaperSloth:20171225184153p:plain

計算がもう少しだけ続きます。
Playerにカメラとの距離を加算した値からカメラの位置を引きます。
その値に先程求めたOffsetを掛けます。
f:id:PaperSloth:20171225185644p:plain

最後にその値をカメラ座標に足してやれば完成です。
f:id:PaperSloth:20171225185842p:plain

全体像が見えるように折り返して写しました。
中でやってる計算はUE4のVInterpToの簡素なバージョンです。
f:id:PaperSloth:20171225190035p:plain

また、このままでは正常に動作しないため
CameraをPlayerの子階層から外しておきましょう。
f:id:PaperSloth:20171225190430p:plain

計算が多いのはノードエディタと相性が悪いため、LuaC++で書いた方がいいですね。
関数の作成方法や独自のノードの作成方法が分かればリファクタリングしたいところです。

とりあえず動きとしてはよくなってきました。
f:id:PaperSloth:20171225190743g:plain


弾を撃つ

続いて攻撃処理を作っていきます。
移動できて、弾が撃てて敵が倒せればゲームが作れそうですよね。

先ずは弾となるEntityを作成します。
Create Entityをし、Projectileと命名しました。
f:id:PaperSloth:20171225200422p:plain

Meshを追加し、SampleProject > Objects > default > primitive_sphereを使用します。
サイズも大きいので、Scaleを0.2にしておきます。
f:id:PaperSloth:20171225200628p:plain

次にコリジョンを追加します。
Add Component > Rigid Body Physics, Primitive Colliderを追加します。
f:id:PaperSloth:20171225220259p:plain

Primitive ColliderのAdd Required Componentから
Sphere Shapeを選択します。
Primitive ColliderのSource Typeにはmat_defaultを設定します。
f:id:PaperSloth:20171225220501p:plain


続いて弾の移動処理を追加します。
ProjectileにScript Canvasを追加して、Projectileという名前のScript Canvasを追加しました。
f:id:PaperSloth:20171225201801p:plain

処理は開始時にY軸方向に100倍した値を速度として渡しています。
因みにこのY軸の値は必ずしも1.0ではありません。
f:id:PaperSloth:20171225220943p:plain

最後に弾の消滅処理を書きます。
DelayとDestroyを使うわけですが、Script Canvasは少しおもしろい書き方ができます。
削除は下図の形でつなぎます。
Delayで3秒後に自身をDestroyします。
f:id:PaperSloth:20171225221314p:plain

Script Canvasでは1つのOutピンから複数の入力ピンにつなげることができます。
UE4ではSequenceノードがその役割を果たしていますね。

弾の処理に関しては一旦これで終了です。


弾をSliceとして登録する。

スライスと動的スライス - Lumberyard
SliceとはUnityでいうところのPrefabです。
UE4ではClass Blueprintが近いですね。
f:id:PaperSloth:20171225221704p:plain

Lumberyard/1.12.0.0/dev/(ProjectName)/slicesに保存しました。
f:id:PaperSloth:20171225221821p:plain

Sliceとして保存するとEntity Outliner上で青く表示されます。
f:id:PaperSloth:20171225221920p:plain


ドキュメントを読めば書いてあるのですがこのスライスは実行時に生成ができません。
そのため、動的スライスとして登録します。
先程作成したSliceをAsset Browser上から探し、右クリックでSet Dynamic Sliceを選択します。
f:id:PaperSloth:20171225222341p:plain

これで動的スライス(DynamicSlice)として登録され、実行時の生成が可能になりました。
実行して、弾が無事に飛んでいくことを確認すれば、Entitiy OutlinerからProjectileを削除します。


プレイヤーから弾を撃つ

弾が登録できたので、次はプレイヤーから撃ちます。

Player EntityにSpawnerを追加します。
f:id:PaperSloth:20171225222741p:plain

Dynamic Sliceに先程登録したProjectileを設定します。
f:id:PaperSloth:20171225222915p:plain


次に発射ボタンの登録を行います。
player.inputbindingsを開き、Fire Eventとしてマウスの左ボタンを追加しました。
f:id:PaperSloth:20171225223108p:plain
この時、保存を忘れないようにしましょう。

PlayerControllerのScript Canvasを開き、先ずはログで入力の確認。
左クリックで-1が出力されれば入力が取得できています。
f:id:PaperSloth:20171225223328p:plain

弾の発射処理はこれだけです。
SpawnがSelfになっていることに違和感を感じた方もいるかもしれませんが、
これはSpawnerに登録されているDynamic SliceをSpawnするという意味なのでこれで問題ありません。
f:id:PaperSloth:20171225224347p:plain


ちょっと見た目を変えたりしておしまい。
f:id:PaperSloth:20171225230436g:plain


まとめ

なんとなくゲームを作る雰囲気が見えてきたでしょうか。

幾つかやり残したことがあって次回の課題としたいと思います。
・カメラの回転
 カメラ追従にこったりせずにこっちを実装すればよかったと今更思います。
・エフェクトの発生
 弾を撃つ時、弾を当てた時のマズルフラッシュと爆発エフェクト
・サウンドの再生
 発射音、ヒット音の再生
・敵の追加
 AIを作るには時間が足りないのですが、当たり判定の解説用に作ればよかったところです。
・UIの作成
 Hit Pointや制限時間などなど
・キャラクターのアニメーション
 これに関しては資料というかサンプルが豊富なので、なんとかなりそうです。
 1.12からシステムが変わったようなので、時間を作って解析したいところ。
・Levelの遷移
 Level遷移は少し資料を探すのが大変そうで難しいかなと思っている点です。
 Forumを探したら意外と直ぐに見つかる気もします。

一先ずゲームを完成させた!というにはまだまだ解説する要素が足りなかったですね。
ですが、Lumberyardは2018年で大きく変わるようですので、本当に楽しみです。

来年のAdvent Calendarをもっともっと盛り上げていければと思います。
最後までお付き合いいただき、ありがとうございました!