【UQ1】ジップライン【BP・C++】

使用UE:Ver5.0.3

Unreal Quest1の中級ジップラインに挑戦しました。
キャラクターを直接SetActorLocationすると、他のプレイヤーから見たときに挙動がおかしくなりました。(ブルブル震える。)
そこでキャラクターをBoxに入れて、Boxを移動させることにしました。
当初想定していたものとは異なりますが、これで完成にします。

SplineMove

👇公式の解説動画です。

「アンリアルクエスト-グレイマンからの5つの挑戦状-」特別生放送!【前編】

Blueprint

BP_SplineBoxMove

Viewport

Rateという名前のComponentは、C++で自作したActorComponent。
詳しくは後の見出しで説明。

Construction Script

Box Collisionの位置などを設定。
Set Spline Meshの前のループのLast Indexは、Last Point Indexより1つ少ない。
これは、SplineMeshを、SplinePointの間に配置するからである。
点の数と、その間の数とを比べたとき、間の数は点の数より1つ少ない。

FirstPointとLastPoint変数に、最初と最初のSplinePointのTransformを格納。
StartLocationとEndLocationの変数には、上記のSplinePointから少しずらしたLocationを格納。
この位置にStartとEndのBoxCollisionを配置。
ずらす値はAjuctedVectorの変数で調整。
InstanceEditableにして3DWidgetで調整できるようにした。

SplineMeshをSplinePointの間に配置していく。

Event Graph

RunOnServerで実行。

Event BeginPlay
On Component Begin Overlap

動いているActorを、Actorsの配列に加えている。
動き終わったら、Actorsの配列から取り除いている。
そのActorsの配列を条件分岐に使っている。

ActorsとBoxActorsとRateStructsの配列に要素を追加。
Actorsは動かしたいキャラクターのObjectReferenceの配列。
BoxActorsは、ここでSpawnさせたBP_BoxのObjectReferenceの配列。
このBoxにキャラクターを入れて運ぶ。
RateStructsは、自作したRateComponentの配列である。
このMakeRateStructのノードで、CanPlayのブール値をtrueにしている。
それをきっかけにBoxが動き、その中のActorが運ばれる。

Event Tick

Splineを使って、BoxのTransformを決めている。

ConvertedRateを使用している。
ConvertedRateとはRateComponent内で定義されているもので、Rateを数式に入れて変換したものである。
これを使うことで、一定の速さの移動ではない移動が可能になる。
移動の仕方はInstance上で変更できるようにしている。

特定のIndexの要素を削除する関数である。
Boxが破壊されたときに呼んでいる。
中にいるキャラクターのReferenceも一緒に消えるので、このキャラクターはBeginOverlapで再び運動できるようになる。

GetStopのブール値は、Rateが0や1になったときにtrueになるものである。(Loopでないとき)
このとき同時に、CanPlayはfalseになるのでBoxの運動は止まる。
このBoxを破壊して、Referenceを消す。
(このBoxに対応するActorとRateStructのReferenceも一緒に消える。)

BP_Box

Class Defaults

ReplicatesとReplicate Movementをtrueにしておく。
ClientはReplicateで動かす。

Viewport

Geometryから作ったStaticMesh。
キャラクターを中に入れるため6方向を6つのSimpleCollisionで囲んだ。

Construction Script

CollisionのOverlapのEventによりSpawn時に一瞬で消えてしまう。
だからCollisionのCollisionは、はじめはNo Collision。
またBoxを生み出したActorのRoot位置に、一瞬BoxがSpawnしているように見える。
その対策として、はじめはBoxのVisibilityをfalseにしてCollisionもNo Collisionにしておく。

Event Graph

他のBoxに当たったとき、Boxの中からキャラクターが落ちたとき、Boxを破壊。
2つのBoxが当たったときに両方消すためのDelay。
たぶん、衝突したときに相手のReferenceをとる前に片方が消えてしまうのだと思われる。

Rate Component

頑張って自作したActorComponent。
複数のものを同時に動かすのに役立つ気がする。
Timelineの代わりとして使うために作った。

メンバ変数
RateStructsのみ。
(構造体FRateStructの配列)
FRateStructのメンバ変数
Rate
0から1の間の数をとるfloat型の変数。
bCanPlayがOnならば、Tickで数値が変化する。
Direction
型はERateDirection(ここで定義)。
ForwardならRateは0から1に変化。
Backwardなら、1から0に変化。
Duration
Rateが0から1に変わるまでの時間。
Type
RateからConvertedRateに変換する方法の種類。
型は、ここで定義したEnumのEConvertRateType。
Linear:変化は一定。(Rateのまま)
SlowQuick:初めは遅く、だんだん速く変化。
QuickSlow:初めは速く、だんだん遅く変化。
SlowQuickSlow:だんだん速くなり、その後だんだん遅くなる。
QuickSlowQuick:だんだん遅くなり、その後だんだん速くなる。
Power
int32型。数が大きいほど、遅いときと速いときの速度の差が大きい。
2未満の場合はLinearになる。
ConvertedRate
RateをConvertRateの関数で変換したもの。
bCanPlay
bool型。trueならRateが変化。
Rateが1を超えたり、0未満になったときにfalseになる。
bLoop
bool型。trueなら、Rateが変化し続ける。
DirectionがForwardなら、Rateが1を超えるとBackwardに変わる。
DirectionがBackwardなら、Rateが0未満になるとForwardに変わる。
bOnOneRate
bool型。bLoopがtrueのとき、Rateが1を超えるとtrueになる。
BlueprintImprementableEventの代わりとして作成。
bOnZeroRate
bool型。bLoopがtrueのとき、Rateが0未満になるとtrueになる。
BlueprintImprementableEventの代わりとして作成。
bOneStopRate
bool型。bLoopがfalseのとき、Rateが1を超えたり、0未満になるとtrueになる。
BlueprintImprementableEventの代わりとして作成。

メインの関数
PlayRate
Rateを変化させる関数。Tickの中に入れてある。
ConvertRate
Rateを変化する関数。
Break/Make関数
BreakRateStruct
NativeBreakFunc。RateStructをBreakする。
MakeRateStruct
NativeMakeFunc。RateStructをMakeする。
Get/Set/Toggle関数
Structを引数としたものと、ArrayのIndexを引数にしたものを作成した。

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "RateComponent.generated.h"


UENUM(BlueprintType)
enum class ERateDirection : uint8
{
	Forward,
	Backward
};

UENUM(BlueprintType)
enum class EConvertRateType : uint8
{
	Linear,
	SlowQuick,
	QuickSlow,
	SlowQuickSlow,
	QuickSlowQuick
};

USTRUCT(BlueprintType, meta = (HasNativeBreak = "UnrealQuest1_3.RateComponent.BreakRateStruct", HasNativeMake = "UnrealQuest1_3.RateComponent.MakeRateStruct"))
struct FRateStruct
{
	GENERATED_BODY()

	// Not less than 0 and not greater than 1.
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	float Rate = 0;

	// The Duration Rate go from 0 to 1.
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	float Duration = 1;

	// If this is Forward, Rate goes from 0 to 1. If Backward, Rate goes from 1 to 0.
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	ERateDirection Direction = ERateDirection::Forward;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ConvertRate")
	EConvertRateType Type = EConvertRateType::Linear;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ConvertRate")
	float Power = 2;

	UPROPERTY(BlueprintReadOnly, Category = "ConvertRate")
	float ConvertedRate;

	// If this is true, Rate can be played. If not, Rate cannot be played. 
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	bool bCanPlay = false;

	// If this is true, Rate doesn't stop. When Rate becomes greater than 1, the direction becomes Backward.
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	bool bLoop = false;

	// Becomes true, as soon as Rate becomes greater than 1.( If bLoop is true )
	UPROPERTY(BlueprintReadWrite)
	bool bOnOneRate = false;

	// Becomes true, as soon as Rate becomes less than 0.( If bLoop is true )
	UPROPERTY(BlueprintReadWrite)
	bool bOnZeroRate = false;

	// Becomes true, as soon as bCanPlay becomes false.
	UPROPERTY(BlueprintReadWrite)
	bool bOnStopRate = false;
};



UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class UNREALQUEST1_3_API URateComponent : public UActorComponent
{
	GENERATED_BODY()

public:	
	// Sets default values for this component's properties
	URateComponent();

protected:
	// Called when the game starts
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;

///////////////////////////////////////////////////////////////////////
//// Member Variable

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	TArray<FRateStruct> RateStructs;

///////////////////////////////////////////////////////////////////////
//// Main Function
	
	// Called in TickComponent.
	void PlayRate(float DeltaSec, UPARAM(ref) FRateStruct& Struct);

	// R means Rate, T means Type, P means Power. If Power is less than 2, Linear Rate is returned.
	UFUNCTION(BlueprintPure, meta = (P = "2"))
	float ConvertRate(float R, ERateDirection D, EConvertRateType T, float P);



///////////////////////////////////////////////////////////////////////
//// Break/Make Function

	UFUNCTION(BlueprintPure, meta = (NativeBreakFunc, AdvancedDisplay = 1))
	void BreakRateStruct(UPARAM(ref) FRateStruct& Struct, float& OutRate, float& OutDuration, ERateDirection& OutDirection, EConvertRateType& OutType, float& OutPower, float& OutConvertedRate, bool& OutCanPlay, bool& OutLoop, bool& OutOne, bool& OutZero, bool& OutStop);

	UFUNCTION(BlueprintPure, meta = (NativeMakeFunc, AdvancedDisplay = 1))
	void MakeRateStruct(FRateStruct& Struct, float InRate, float InDuration, ERateDirection InDirection, EConvertRateType InType, float InPower, float InConvertedRate, bool InCanPlay, bool InLoop, bool InOne, bool InZero, bool InStop);

///////////////////////////////////////////////////////////////////////
//// Get/Set/Toggle Function

	// Rate

	UFUNCTION(BlueprintPure, meta = (CompactNodeTitle = "Get Rate"))
	float GetRate(int32 Index);

	UFUNCTION(BlueprintPure, meta = (CompactNodeTitle = "Get Rate"))
	float GetRateFromStruct(FRateStruct Struct);

	UFUNCTION(BlueprintCallable, meta = (CompactNodeTitle = "Set Rate"))
	void SetRate(float NewRate, int32 Index);

	UFUNCTION(BlueprintCallable, meta = (CompactNodeTitle = "Set Rate"))
	void SetRateFromStruct(float NewRate, UPARAM(ref) FRateStruct& Struct);

	// Duration

	UFUNCTION(BlueprintPure, meta = (CompactNodeTitle = "Get Duration"))
	float GetDuration(int32 Index);

	UFUNCTION(BlueprintPure, meta = (CompactNodeTitle = "Get Duration"))
	float GetDurationFromStruct(FRateStruct Struct);

	UFUNCTION(BlueprintCallable, meta = (CompactNodeTitle = "Set Duration"))
	void SetDuration(float NewDuration, int32 Index);

	UFUNCTION(BlueprintCallable, meta = (CompactNodeTitle = "Set Duration"))
	void SetDurationFromStruct(float NewDuration, UPARAM(ref) FRateStruct& Struct);

	// Direction

	UFUNCTION(BlueprintPure, meta = (CompactNodeTitle = "Get Direction"))
	ERateDirection GetDirection(int32 Index);

	UFUNCTION(BlueprintPure, meta = (CompactNodeTitle = "Get Direction"))
	ERateDirection GetDirectionFromStruct(FRateStruct Struct);

	UFUNCTION(BlueprintCallable, meta = (CompactNodeTitle = "Set Direction"))
	void SetDirection(ERateDirection NewDirection, int32 Index);

	UFUNCTION(BlueprintCallable, meta = (CompactNodeTitle = "Set Direction"))
	void SetDirectionFromStruct(ERateDirection NewDirection, UPARAM(ref) FRateStruct& Struct);

	UFUNCTION(BlueprintCallable, meta = (CompactNodeTitle = "Toggle Direction"))
	void ToggleDirection(int32 Index);

	UFUNCTION(BlueprintCallable, meta = (CompactNodeTitle = "Toggle Direction"))
	void ToggleDirectionFromStruct(UPARAM(ref) FRateStruct& Struct);

	// Type

	UFUNCTION(BlueprintPure, meta = (CompactNodeTitle = "Get Type"))
	EConvertRateType GetType(int32 Index);

	UFUNCTION(BlueprintPure, meta = (CompactNodeTitle = "Get Type"))
	EConvertRateType GetTypeFromStruct(FRateStruct Struct);

	UFUNCTION(BlueprintCallable, meta = (CompactNodeTitle = "Set Type"))
	void SetType(EConvertRateType NewType, int32 Index);

	UFUNCTION(BlueprintCallable, meta = (CompactNodeTitle = "Set Type"))
	void SetTypeFromStruct(EConvertRateType NewType, UPARAM(ref) FRateStruct& Struct);

	// Power

	UFUNCTION(BlueprintPure, meta = (CompactNodeTitle = "Get Power"))
	float GetPower(int32 Index);

	UFUNCTION(BlueprintPure, meta = (CompactNodeTitle = "Get Power"))
	float GetPowerFromStruct(FRateStruct Struct);

	UFUNCTION(BlueprintCallable, meta = (CompactNodeTitle = "Set Power"))
	void SetPower(float NewPower, int32 Index);

	UFUNCTION(BlueprintCallable, meta = (CompactNodeTitle = "Set Power"))
	void SetPowerFromStruct(float NewPower, UPARAM(ref) FRateStruct& Struct);

	// ConvertedRate

	UFUNCTION(BlueprintPure, meta = (CompactNodeTitle = "Get Converted Rate"))
	float GetConvertedRate(int32 Index);

	UFUNCTION(BlueprintPure, meta = (CompactNodeTitle = "Get Converted Rate"))
	float GetConvertedRateFromStruct(FRateStruct Struct);

	// CanPlay

	UFUNCTION(BlueprintPure, meta = (CompactNodeTitle = "Get CanPlay"))
	bool GetCanPlay(int32 Index);

	UFUNCTION(BlueprintPure, meta = (CompactNodeTitle = "Get CanPlay"))
	bool GetCanPlayFromStruct(FRateStruct Struct);

	UFUNCTION(BlueprintCallable, meta = (CompactNodeTitle = "Set CanPlay"))
	void SetCanPlay(bool NewCanPlay, int32 Index);

	UFUNCTION(BlueprintCallable, meta = (CompactNodeTitle = "Set CanPlay"))
	void SetCanPlayFromStruct(bool NewCanPlay, UPARAM(ref) FRateStruct& Struct);

	UFUNCTION(BlueprintCallable, meta = (CompactNodeTitle = "Toggle CanPlay"))
	void ToggleCanPlay(int32 Index);

	UFUNCTION(BlueprintCallable, meta = (CompactNodeTitle = "Toggle CanPlay"))
	void ToggleCanPlayFromStruct(UPARAM(ref) FRateStruct& Struct);

	// Loop

	UFUNCTION(BlueprintPure, meta = (CompactNodeTitle = "Get Loop"))
	bool GetLoop(int32 Index);

	UFUNCTION(BlueprintPure, meta = (CompactNodeTitle = "Get Loop"))
	bool GetLoopFromStruct(FRateStruct Struct);

	UFUNCTION(BlueprintCallable, meta = (CompactNodeTitle = "Set Loop"))
	void SetLoop(bool NewLoop, int32 Index);

	UFUNCTION(BlueprintCallable, meta = (CompactNodeTitle = "Set Loop"))
	void SetLoopFromStruct(bool NewLoop, UPARAM(ref) FRateStruct& Struct);

	UFUNCTION(BlueprintCallable, meta = (CompactNodeTitle = "Toggle Loop"))
	void ToggleLoop(int32 Index);

	UFUNCTION(BlueprintCallable, meta = (CompactNodeTitle = "Toggle Loop"))
	void ToggleLoopFromStruct(UPARAM(ref) FRateStruct& Struct);

	// OnOneRate

	UFUNCTION(BlueprintPure, meta = (CompactNodeTitle = "Get OnOne"))
	bool GetOnOneRate(int32 Index);

	UFUNCTION(BlueprintPure, meta = (CompactNodeTitle = "Get OnOne"))
	bool GetOnOneRateFromStruct(FRateStruct Struct);

	// OnZeroRate

	UFUNCTION(BlueprintPure, meta = (CompactNodeTitle = "Get OnZero"))
	bool GetOnZeroRate(int32 Index);

	UFUNCTION(BlueprintPure, meta = (CompactNodeTitle = "Get OnZero"))
	bool GetOnZeroRateFromStruct(FRateStruct Struct);

	// OnStopRate

	UFUNCTION(BlueprintPure, meta = (CompactNodeTitle = "Get Stop"))
	bool GetOnStopRate(int32 Index);

	UFUNCTION(BlueprintPure, meta = (CompactNodeTitle = "Get Stop"))
	bool GetOnStopRateFromStruct(FRateStruct Struct);

		
};

// Fill out your copyright notice in the Description page of Project Settings.


#include "RateComponent.h"

// Sets default values for this component's properties
URateComponent::URateComponent()
{
	// 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 URateComponent::BeginPlay()
{
	Super::BeginPlay();

	// ...
	
}


// Called every frame
void URateComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

	// ...
	
	int32 Num = RateStructs.Num();
	for (int32 i = 0; i < Num; i++)
	{
		PlayRate(DeltaTime, RateStructs[i]);
	}

}


///////////////////////////////////////////////////////////////////////
//// Main Function

void URateComponent::PlayRate(float DeltaSec, FRateStruct& Struct)
{
	if (Struct.bCanPlay == false) return;

	float DeltaRate = DeltaSec / Struct.Duration;

	if (Struct.Direction == ERateDirection::Forward)
	{
		Struct.Rate += DeltaRate;
	}
	else
	{
		Struct.Rate -= DeltaRate;
	}

	Struct.ConvertedRate = ConvertRate(Struct.Rate, Struct.Direction, Struct.Type, Struct.Power);

	if (Struct.Rate < 0.f)
	{
		if (Struct.bLoop)
		{
			Struct.Direction = ERateDirection::Forward;
			Struct.bOnZeroRate = true;
		}
		else
		{
			Struct.bCanPlay = false;
			Struct.bOnStopRate = true;
		}
	}
	else if (Struct.Rate > 1.f)
	{
		if (Struct.bLoop)
		{
			Struct.Direction = ERateDirection::Backward;
			Struct.bOnOneRate = true;
		}
		else
		{
			Struct.bCanPlay = false;
			Struct.bOnStopRate = true;
		}
	}
}

float URateComponent::ConvertRate(float R, ERateDirection D, EConvertRateType T, float P)
{
	if (P < 2) return R;

	switch (T)
	{
	case EConvertRateType::Linear: return R;
	case EConvertRateType::SlowQuick:
	{
		if (D == ERateDirection::Forward)
		{
			return FMath::Clamp(std::pow(R, P), 0, 1);
		}
		else
		{
			return FMath::Clamp(1 - std::pow((1 - R), P), 0, 1);
		}
		break;
	}
	case EConvertRateType::QuickSlow:
	{
		if (D == ERateDirection::Backward)
		{
			return FMath::Clamp(std::pow(R, P), 0, 1);
		}
		else
		{
			return FMath::Clamp(1 - std::pow((1 - R), P), 0, 1);
		}
		break;
	}
	case EConvertRateType::SlowQuickSlow:
	{
		if (R < 0.5)
		{
			return FMath::Clamp(std::pow(2, P - 1) * std::pow(R, P), 0, 1);
		}
		else
		{
			return FMath::Clamp(1 - std::pow(2, P - 1) * std::pow((1 - R), P), 0, 1);
		}
		break;
	}
	case EConvertRateType::QuickSlowQuick:
	{
		if (R < 0.5)
		{
			return FMath::Clamp(0.5 - std::pow(2, P - 1) * std::pow((0.5 - R), P), 0, 1);
		}
		else
		{
			return FMath::Clamp(std::pow(2, P - 1) * std::pow(R - 0.5, P) + 0.5, 0, 1);
		}
		break;
	}
	default: return R;
	}
}

///////////////////////////////////////////////////////////////////////
//// Break/Make Function


void URateComponent::BreakRateStruct(FRateStruct& Struct, float& OutRate, float& OutDuration, ERateDirection& OutDirection, EConvertRateType& OutType, float& OutPower, float& OutConvertedRate, bool& OutCanPlay, bool& OutLoop, bool& OutOne, bool& OutZero, bool& OutStop)
{
	OutRate = Struct.Rate;
	OutDuration = Struct.Duration;
	OutDirection = Struct.Direction;
	OutType = Struct.Type;
	OutPower = Struct.Power;
	OutConvertedRate = Struct.ConvertedRate;
	OutCanPlay = Struct.bCanPlay;
	OutLoop = Struct.bLoop;
	OutOne = Struct.bOnOneRate;
	OutZero = Struct.bOnZeroRate;
	OutStop = Struct.bOnStopRate;
}



void URateComponent::MakeRateStruct(FRateStruct& Struct, float InRate, float InDuration, ERateDirection InDirection, EConvertRateType InType, float InPower, float InConvertedRate, bool InCanPlay, bool InLoop, bool InOne, bool InZero, bool InStop)
{
	Struct.Rate = InRate;
	Struct.Duration = InDuration;
	Struct.Direction = InDirection;
	Struct.Type = InType;
	Struct.Power = InPower;
	Struct.ConvertedRate = InConvertedRate;
	Struct.bCanPlay = InCanPlay;
	Struct.bLoop = InLoop;
	Struct.bOnOneRate = InOne;
	Struct.bOnZeroRate = InZero;
	Struct.bOnStopRate = InStop;
}

///////////////////////////////////////////////////////////////////////
//// Get/Set/Toggle Function


// Rate
float URateComponent::GetRate(int32 Index)
{
	return RateStructs[Index].Rate;
}

float URateComponent::GetRateFromStruct(FRateStruct Struct)
{
	return Struct.Rate;
}

void URateComponent::SetRate(float NewRate, int32 Index)
{
	RateStructs[Index].Rate = NewRate;
}

void URateComponent::SetRateFromStruct(float NewRate, FRateStruct& Struct)
{
	Struct.Rate = NewRate;
}

// Duration
float URateComponent::GetDuration(int32 Index)
{
	return RateStructs[Index].Duration;
}

float URateComponent::GetDurationFromStruct(FRateStruct Struct)
{
	return Struct.Duration;
}

void URateComponent::SetDuration(float NewDuration, int32 Index)
{
	RateStructs[Index].Duration = NewDuration;
}

void URateComponent::SetDurationFromStruct(float NewDuration, FRateStruct& Struct)
{
	Struct.Duration = NewDuration;
}


// Direction
ERateDirection URateComponent::GetDirection(int32 Index)
{
	return RateStructs[Index].Direction;
}

ERateDirection URateComponent::GetDirectionFromStruct(FRateStruct Struct)
{
	return Struct.Direction;
}

void URateComponent::SetDirection(ERateDirection NewDirection, int32 Index)
{
	RateStructs[Index].Direction = NewDirection;
}

void URateComponent::SetDirectionFromStruct(ERateDirection NewDirection, FRateStruct& Struct)
{
	Struct.Direction = NewDirection;
}

void URateComponent::ToggleDirection(int32 Index)
{
	if (RateStructs[Index].Direction == ERateDirection::Forward)
	{
		RateStructs[Index].Direction = ERateDirection::Backward;
	}
	else
	{
		RateStructs[Index].Direction = ERateDirection::Forward;
	}	
}

void URateComponent::ToggleDirectionFromStruct(FRateStruct& Struct)
{
	if (Struct.Direction == ERateDirection::Forward)
	{
		Struct.Direction = ERateDirection::Backward;
	}
	else
	{
		Struct.Direction = ERateDirection::Forward;
	}
}

// Type
EConvertRateType URateComponent::GetType(int32 Index)
{
	return RateStructs[Index].Type;
}

EConvertRateType URateComponent::GetTypeFromStruct(FRateStruct Struct)
{
	return Struct.Type;
}

void URateComponent::SetType(EConvertRateType NewType, int32 Index)
{
	RateStructs[Index].Type = NewType;
}

void URateComponent::SetTypeFromStruct(EConvertRateType NewType, FRateStruct& Struct)
{
	Struct.Type = NewType;
}

float URateComponent::GetPower(int32 Index)
{
	return RateStructs[Index].Power;
}

float URateComponent::GetPowerFromStruct(FRateStruct Struct)
{
	return Struct.Power;
}

void URateComponent::SetPower(float NewPower, int32 Index)
{
	RateStructs[Index].Power = NewPower;
}

void URateComponent::SetPowerFromStruct(float NewPower, FRateStruct& Struct)
{
	Struct.Power = NewPower;
}

// Converted Rate
float URateComponent::GetConvertedRate(int32 Index)
{
	return RateStructs[Index].ConvertedRate;
}

float URateComponent::GetConvertedRateFromStruct(FRateStruct Struct)
{
	return Struct.ConvertedRate;
}

// CanPlay
bool URateComponent::GetCanPlay(int32 Index)
{
	return RateStructs[Index].bCanPlay;
}

bool URateComponent::GetCanPlayFromStruct(FRateStruct Struct)
{
	return Struct.bCanPlay;
}

void URateComponent::SetCanPlay(bool NewCanPlay, int32 Index)
{
	RateStructs[Index].bCanPlay = NewCanPlay;
}

void URateComponent::SetCanPlayFromStruct(bool NewCanPlay, FRateStruct& Struct)
{
	Struct.bCanPlay = NewCanPlay;
}

void URateComponent::ToggleCanPlay(int32 Index)
{
	RateStructs[Index].bCanPlay = !RateStructs[Index].bCanPlay;
}

void URateComponent::ToggleCanPlayFromStruct(FRateStruct& Struct)
{
	Struct.bCanPlay = !Struct.bCanPlay;
}

bool URateComponent::GetLoop(int32 Index)
{
	return RateStructs[Index].bLoop;
}

bool URateComponent::GetLoopFromStruct(FRateStruct Struct)
{
	return Struct.bLoop;
}

void URateComponent::SetLoop(bool NewLoop, int32 Index)
{
	RateStructs[Index].bLoop = NewLoop;
}

void URateComponent::SetLoopFromStruct(bool NewLoop, FRateStruct& Struct)
{
	Struct.bLoop = NewLoop;
}

void URateComponent::ToggleLoop(int32 Index)
{
	RateStructs[Index].bLoop = !RateStructs[Index].bLoop;
}

void URateComponent::ToggleLoopFromStruct(FRateStruct& Struct)
{
	Struct.bLoop = !Struct.bLoop;
}

bool URateComponent::GetOnOneRate(int32 Index)
{
	return RateStructs[Index].bOnOneRate;
}

bool URateComponent::GetOnOneRateFromStruct(FRateStruct Struct)
{
	return Struct.bOnOneRate;
}

bool URateComponent::GetOnZeroRate(int32 Index)
{
	return RateStructs[Index].bOnZeroRate;
}

bool URateComponent::GetOnZeroRateFromStruct(FRateStruct Struct)
{
	return Struct.bOnZeroRate;
}

bool URateComponent::GetOnStopRate(int32 Index)
{
	return RateStructs[Index].bOnStopRate;
}

bool URateComponent::GetOnStopRateFromStruct(FRateStruct Struct)
{
	return Struct.bOnStopRate;
}

知識・考察

1.オブジェクトモードにおいて、Shift + A で円柱を作成。

作成後現れる、左下のWidgetで編集できる。

2.編集モードに移り、Ctrl + Rでループカット。

3.分割数を決定。(ホイールを回すか、分割数を打ち込む。)

完成。

底面と側面に分けてマテリアルを設定。
UEに持っていく。
メッシュからマテリアルを外したら、設定していたマテリアルは綺麗に削除できた。

UVチャンネルの指定、タイリング回数の指定ができる。
ショートカット
Uキー

機能としては普通の関数だと思ってる。左端のアイコン以外の違いは分からない。

Break関数

検索結果
ノード

Make関数

検索結果
ノード

1.USTRUCTのメタタグを追加。
Break関数のメタタグ

HasNativeBreak="Module.Class.Function"

Make関数のメタタグ

HasNativeMake="Module.Class.Function"

👆この関数パス”Module.Class.Function”(モジュール名.クラス名.関数名)のところを間違えると、起動はできるがBreakPointにひっかかる。モジュール名のところはプロジェクトの名前を入れれば多分大丈夫。

PythonGenerationIssue。BreakPointだと思ってる。

2.関数を作成。メタタグを追加。
Break関数のメタタグ

NativeBreakFunc

Make関数のメタタグ

NativeMakeFunc

関数の作り方は他のものと同じ。BlueprintPureじゃないとダメかもしれないけど。
構造体の全てのメンバ変数を使う必要はない。一部分だけでも使用できた。

以下はRateComponentにおける該当部分の抜粋。
RateComponent.h

USTRUCT(BlueprintType, meta = (HasNativeBreak = "UnrealQuest1_3.RateComponent.BreakRateStruct", HasNativeMake = "UnrealQuest1_3.RateComponent.MakeRateStruct"))
struct FRateStruct
{
// 構造体の定義
};

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class UNREALQUEST1_3_API URateComponent : public UActorComponent
{
	GENERATED_BODY()

public:
///////////////////////////////////////////////////////////////////////
//// Break/Make Function

	UFUNCTION(BlueprintPure, meta = (NativeBreakFunc, AdvancedDisplay = 1))
	void BreakRateStruct(UPARAM(ref) FRateStruct& Struct, float& OutRate, float& OutDuration, ERateDirection& OutDirection, EConvertRateType& OutType, float& OutPower, float& OutConvertedRate, bool& OutCanPlay, bool& OutLoop, bool& OutOne, bool& OutZero, bool& OutStop);

	UFUNCTION(BlueprintPure, meta = (NativeMakeFunc, AdvancedDisplay = 1))
	void MakeRateStruct(FRateStruct& Struct, float InRate, float InDuration, ERateDirection InDirection, EConvertRateType InType, float InPower, float InConvertedRate, bool InCanPlay, bool InLoop, bool InOne, bool InZero, bool InStop);
};

RateComponent.cpp

///////////////////////////////////////////////////////////////////////
//// Break/Make Function


void URateComponent::BreakRateStruct(FRateStruct& Struct, float& OutRate, float& OutDuration, ERateDirection& OutDirection, EConvertRateType& OutType, float& OutPower, float& OutConvertedRate, bool& OutCanPlay, bool& OutLoop, bool& OutOne, bool& OutZero, bool& OutStop)
{
	OutRate = Struct.Rate;
	OutDuration = Struct.Duration;
	OutDirection = Struct.Direction;
	OutType = Struct.Type;
	OutPower = Struct.Power;
	OutConvertedRate = Struct.ConvertedRate;
	OutCanPlay = Struct.bCanPlay;
	OutLoop = Struct.bLoop;
	OutOne = Struct.bOnOneRate;
	OutZero = Struct.bOnZeroRate;
	OutStop = Struct.bOnStopRate;
}



void URateComponent::MakeRateStruct(FRateStruct& Struct, float InRate, float InDuration, ERateDirection InDirection, EConvertRateType InType, float InPower, float InConvertedRate, bool InCanPlay, bool InLoop, bool InOne, bool InZero, bool InStop)
{
	Struct.Rate = InRate;
	Struct.Duration = InDuration;
	Struct.Direction = InDirection;
	Struct.Type = InType;
	Struct.Power = InPower;
	Struct.ConvertedRate = InConvertedRate;
	Struct.bCanPlay = InCanPlay;
	Struct.bLoop = InLoop;
	Struct.bOnOneRate = InOne;
	Struct.bOnZeroRate = InZero;
	Struct.bOnStopRate = InStop;
}

関数にメタタグを追加。

AdvancedDisplay=N

Nに自然数を入れて使用。N=1だと最初のParameterだけ常に表示。N=2だと2番目のParameterまで常に表示。
それ以降のParameterは詳細ピンとして設定され、たたまれた状態では隠される。この順番に入力ピンか、出力ピンかは関係ない。
このメタタグをBreak/Make関数に追加した。ピンの数が多くても、それを隠してスッキリさせることができる。
Break関数

void BreakRateStruct(UPARAM(ref) FRateStruct& Struct, float& OutRate,

👇N=1。1番はじめの引数のStructだけが表示。

詳細ピンが隠された状態。

👇N=2。2番目のParameterであるOutRateまで表示。

Make関数

void MakeRateStruct(FRateStruct& Struct, float InRate, float InDuration,

👇N=1。1番はじめのParameterであるStructのみ表示。

詳細ピンが隠された状態。

👇N=2。2番目のParameterであるInRateまで表示。

👇詳細ピンも表示している状態。

詳細ピンとして設定されていても、他のノードとつながっているピンは表示される。

N=2。

下記のメタタグを追加。

変数のParameter名 = "初期値"

Parameterの型によって設定の仕方が違うみたいだから注意。
とりあえずfloatの場合はこれでいける。

👇ReteComponentにおける該当部分

UFUNCTION(BlueprintPure, meta = (P = "2"))
float ConvertRate(float R, ERateDirection D, EConvertRateType T, float P);

Server側のOpenLevelのLevelNameに「Map名?Listen」と入力。
たとえばThirdPersonMapの場合は下記のNameを入力。

ThirdPersonMap?Listen

Client側のOpenLevelのLevelNameにポート番号を入力。

127.0.0.1:7777

とりあえず、下記のBPで移動できる。

AuthorityはServerが実行する処理、RemotehaClientが実行する処理。

下記のServer Portと関係がありそう。

ちなみに上のServer PortはAdvanced Settings…から見れた。

ポート番号について

ポート番号とは|「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典
「ポート番号」の意味を何となく説明しています。

👇このスライドがなかったら一生OpenLevelできなかったと思う。それくらい行き詰ってた。

UE4/5におけるオンラインマルチプレイ(導入編) | ドクセル
ドクセルはスライドやPDFをかんたんに共有できるサイトです

InstanceEditableの変数を使って、ConstructionScriptだけで切り替えるのはやめた方が良さそう。
一度NoCollisionにした後、QueryOnlyに戻すことができなかった。理由は分からない。
BeginPlayの場合は問題なく切り替えることができた。

👇ここくらいから作ってる最中にメモしたことなど。まとまりはない。

BP_BoxのConstructionScriptのところで少し説明している問題。
理由が良く分からないが仕様なのだろうか。むしろ、この謎のSpawnによって、OverlapEventが引き起こされているのではと推測している。

Tangentが、どの角度のTangentなのか気になる。
おそらくSplineの形を決めるときの、各点において形の調整をしているもののことを指しているのだと思う。
Tangentは2次元の関数で考えると傾きになるし、3次元でも傾きなのだろう。定義は分からないが。
各成分を微分したものかな。微分するパラメータには、Splineに沿った始点からの距離が使えそう。
おそらく、ある点と、そこから微小に移動した場所との差をとって、向きを変えずに長さを1になおしたものとかだろう。
それで、StartとEndのLocationとTangentの4つのベクトルの情報から、その間を補間してるんだろう。
要は、Splineの接線の進行方向への単位ベクトルだと思う。

いや、違うなぁ。初期状態では点の間の補間なんてされてなくて線になってないのだから、微分なんてできない。

とりあえずTangentとは、線の間を補間する方法を決める数値ってことでいいや。
その数値をつかって滑らかな曲線をつくるための数式に突っ込んだだけ。

👇結局使わなかったが、Operatorなどを設定してみた。
理解は少し深まったと思うが、使いどころを見つけられてない。

USTRUCT(BlueprintType)
struct FRateVar
{
	GENERATED_BODY()

	FORCEINLINE FRateVar();
	explicit FORCEINLINE FRateVar(float InRate);
	explicit FORCEINLINE FRateVar(bool InCanPlay, ERateDirection InDirection, float InRate);

	// Probably const means pure
	FRateVar operator+(const FRateVar& V) const;
	FRateVar operator-(const FRateVar& V) const;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
		bool bCanPlay = false;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
		ERateDirection Direction = ERateDirection::Forward;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
		float Rate = 0;
};

FORCEINLINE FRateVar::FRateVar()
{
}

FORCEINLINE FRateVar::FRateVar(float InRate) : bCanPlay(true), Direction(ERateDirection::Forward), Rate(InRate)
{
}

FORCEINLINE FRateVar::FRateVar(bool InPlay, ERateDirection InDirection, float InRate) : bCanPlay(InPlay), Direction(InDirection), Rate(InRate)
{
}

FORCEINLINE FRateVar FRateVar::operator+(const FRateVar& V) const
{
	return FRateVar(bCanPlay, Direction, Rate + V.Rate);
}

FORCEINLINE FRateVar FRateVar::operator-(const FRateVar& V) const
{
	return FRateVar(bCanPlay, Direction, Rate - V.Rate);
}

浅い経験なりの感想。
・使いまわしが楽。
・EnumやStructの設定が楽。
コピペで使いまわせる。BPの場合、変数とか、Enumとか、Structとか、いちいち作るの面倒。Migrateも上手くいかないこと多い。知識がなくて何が悪いのか分からない。

・処理の流れが直感的に分かりやすい。(計算は例外。)
・感覚で何となく作れる。検索が優秀。

(現時点の考え)
基本的にBPで作る。他のプロジェクトでも使いまわせそうなものをC++でつくる。
関数、Enum、StructをBlueprintFunctionLibraryなどにまとめておく。
ものによっては、Componentにまとめる。
どのようにまとめるべきかは、良く分からないので作りながら考える。
シンプルな処理になるように工夫をすれば、良い方法が見えてくる気がする。
一度作ったものと同じようなものを再度つくるときに、大幅に楽ができるように考える。
自分にとって使いやすい道具をつくる。
プログラミングの常識を知らないので、情報収集も適度にする。

BP_SplineMoveでもRunOnServerできた。〇✖の時に、CubeのActorでRunOnServerができなかったことを記憶しているが、PlayerControllerのクリックで操作していたのが影響していたのだろう。

変数の型がvoidではないときは、switchのdefaultは必要みたい。全てのPathで返り値を設定しないといけないから。

サーバーで、Clientのプレイヤーのコントローラーを使えば、PlayerStateにアクセスできる。キャラクターからClientのコントローラーを得るには、GetController。GetPlayerControllerではない。インデックス変えれば行けるかもだけど。GetControllerしたものをカスタムイベント経由で送り、そのカスタムイベントをRunOnServerで実行すればOK。そのコントローラーからGetPlayerStateする。そこからとったPlayerStateは、そのキャラクターを操るクライアントのもの。

SetActorLcationや、SetActorRotationが上手くいかないのは所有権の問題かも。
ほとんどのActorはServerのいいなりだけど、Clientが操っているControllerやPawnはClientが所有権を持っているので、Serverの命令に従わない。
やっぱりRotationを動かそうとしても、操っているClientの画面ではブルブルしてる。
Serverからの命令と所有者としての矜持との間での葛藤があるのだろう。
BPIなどで、各自に動いてもらうのが良いと思われる。
そう考えると、Boxなんて作る必要なかったんだろうな。

ダメだった。ブルブルする。終いには消える。なぜ。

参考

メタデータ指定子
Unreal Engine および Editor の様々な側面での動作を指定するために UClasses、 UFunctions、UProperties、 UEnums、および UInterfaces を宣言する場合に使用するメタデータ キーワード

マルチプレイヤー ゲームでの移動
マルチプレイヤー ゲームにおける移動方法の概要

【UE4:C++】BPで関数を呼び出すときのデフォルト引数を設定する - Qiita
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context", sample = "-1"))void SetSample(UObje…

脱初心者!他人や未来の自分が読みやすいプログラムの書き方を解説します【ひろはす】

murashun.jp
404 Page Not Found

Twitterしてます

ブログの更新をお知らせ

コメント

タイトルとURLをコピーしました