使用UE:Ver5.0.3
Unreal Quest1の中級ジップラインに挑戦しました。
キャラクターを直接SetActorLocationすると、他のプレイヤーから見たときに挙動がおかしくなりました。(ブルブル震える。)
そこでキャラクターをBoxに入れて、Boxを移動させることにしました。
当初想定していたものとは異なりますが、これで完成にします。
👇公式の解説動画です。
Blueprint
BP_SplineBoxMove
Viewport
Rateという名前のComponentは、C++で自作したActorComponent。
詳しくは後の見出しで説明。
![](https://japanese-rooster.com/wp-content/uploads/2022/10/image-89.png)
Construction Script
Box Collisionの位置などを設定。
Set Spline Meshの前のループのLast Indexは、Last Point Indexより1つ少ない。
これは、SplineMeshを、SplinePointの間に配置するからである。
点の数と、その間の数とを比べたとき、間の数は点の数より1つ少ない。
![](https://japanese-rooster.com/wp-content/uploads/2022/10/image-83.png)
Event Graph
RunOnServerで実行。
![](https://japanese-rooster.com/wp-content/uploads/2022/10/image-51.png)
![](https://japanese-rooster.com/wp-content/uploads/2022/10/image-46.png)
![](https://japanese-rooster.com/wp-content/uploads/2022/10/image-78.png)
BP_Box
Class Defaults
ReplicatesとReplicate Movementをtrueにしておく。
ClientはReplicateで動かす。
![](https://japanese-rooster.com/wp-content/uploads/2022/10/image-57.png)
Viewport
![](https://japanese-rooster.com/wp-content/uploads/2022/10/image-52.png)
Construction Script
CollisionのOverlapのEventによりSpawn時に一瞬で消えてしまう。
だからCollisionのCollisionは、はじめはNo Collision。
またBoxを生み出したActorのRoot位置に、一瞬BoxがSpawnしているように見える。
その対策として、はじめはBoxのVisibilityをfalseにしてCollisionもNo Collisionにしておく。
![](https://japanese-rooster.com/wp-content/uploads/2022/10/image-81.png)
Event Graph
![](https://japanese-rooster.com/wp-content/uploads/2022/10/image-80.png)
他のBoxに当たったとき、Boxの中からキャラクターが落ちたとき、Boxを破壊。
2つのBoxが当たったときに両方消すためのDelay。
たぶん、衝突したときに相手のReferenceをとる前に片方が消えてしまうのだと思われる。
![](https://japanese-rooster.com/wp-content/uploads/2022/10/image-56.png)
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 で円柱を作成。
![](https://japanese-rooster.com/wp-content/uploads/2022/10/image-70.png)
2.編集モードに移り、Ctrl + Rでループカット。
![](https://japanese-rooster.com/wp-content/uploads/2022/10/image-71.png)
3.分割数を決定。(ホイールを回すか、分割数を打ち込む。)
![](https://japanese-rooster.com/wp-content/uploads/2022/10/image-72.png)
完成。
![](https://japanese-rooster.com/wp-content/uploads/2022/10/image-73.png)
底面と側面に分けてマテリアルを設定。
UEに持っていく。
メッシュからマテリアルを外したら、設定していたマテリアルは綺麗に削除できた。
UVチャンネルの指定、タイリング回数の指定ができる。
ショートカット
Uキー
機能としては普通の関数だと思ってる。左端のアイコン以外の違いは分からない。
Break関数
![](https://japanese-rooster.com/wp-content/uploads/2022/10/image-58.png)
![](https://japanese-rooster.com/wp-content/uploads/2022/10/image-61.png)
Make関数
![](https://japanese-rooster.com/wp-content/uploads/2022/10/image-59.png)
![](https://japanese-rooster.com/wp-content/uploads/2022/10/image-60.png)
1.USTRUCTのメタタグを追加。
Break関数のメタタグ
HasNativeBreak="Module.Class.Function"
Make関数のメタタグ
HasNativeMake="Module.Class.Function"
👆この関数パス”Module.Class.Function”(モジュール名.クラス名.関数名)のところを間違えると、起動はできるがBreakPointにひっかかる。モジュール名のところはプロジェクトの名前を入れれば多分大丈夫。
![](https://japanese-rooster.com/wp-content/uploads/2022/10/image-62.png)
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;
}
結局のところ良く分かってないが、モジュール名を聞かれたら、プロジェクト名を答えることにする。
Modulesへのアクセスができないときが多い。理由が分からない。
👇この記事の下の方でModulesにおけるコンパイルの方法を説明。
👇理解したい。現時点の僕の知識がしょぼくて理解できない部分が多い。
![](https://japanese-rooster.com/wp-content/uploads/cocoon-resources/blog-card-cache/d76e643231555971d83ebab1f2116927.png)
関数にメタタグを追加。
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だけが表示。
![](https://japanese-rooster.com/wp-content/uploads/2022/10/image-61.png)
👇N=2。2番目のParameterであるOutRateまで表示。
![](https://japanese-rooster.com/wp-content/uploads/2022/10/image-65.png)
Make関数
void MakeRateStruct(FRateStruct& Struct, float InRate, float InDuration,
👇N=1。1番はじめのParameterであるStructのみ表示。
![](https://japanese-rooster.com/wp-content/uploads/2022/10/image-60.png)
👇N=2。2番目のParameterであるInRateまで表示。
![](https://japanese-rooster.com/wp-content/uploads/2022/10/image-64.png)
👇詳細ピンも表示している状態。
![](https://japanese-rooster.com/wp-content/uploads/2022/10/image-63.png)
詳細ピンとして設定されていても、他のノードとつながっているピンは表示される。
![](https://japanese-rooster.com/wp-content/uploads/2022/10/image-66.png)
下記のメタタグを追加。
変数の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で移動できる。
![](https://japanese-rooster.com/wp-content/uploads/2022/10/image-67.png)
下記のServer Portと関係がありそう。
![](https://japanese-rooster.com/wp-content/uploads/2022/10/image-30.png)
ちなみに上のServer PortはAdvanced Settings…から見れた。
![](https://japanese-rooster.com/wp-content/uploads/2022/10/image-68.png)
ポート番号について
![](https://japanese-rooster.com/wp-content/uploads/cocoon-resources/blog-card-cache/50d368e19c9a7fb558aba88706184dfa.png)
👇このスライドがなかったら一生OpenLevelできなかったと思う。それくらい行き詰ってた。
![](https://japanese-rooster.com/wp-content/uploads/cocoon-resources/blog-card-cache/0daa326fb15b7ba6edc5fa33422b6e0b.jpg)
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なんて作る必要なかったんだろうな。
↓
ダメだった。ブルブルする。終いには消える。なぜ。
参考
![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fcdn.qiita.com%2Fassets%2Fpublic%2Farticle-ogp-background-412672c5f0600ab9a64263b751f1bc81.png?ixlib=rb-4.0.0&w=1200&mark64=aHR0cHM6Ly9xaWl0YS11c2VyLWNvbnRlbnRzLmltZ2l4Lm5ldC9-dGV4dD9peGxpYj1yYi00LjAuMCZ3PTk3MiZoPTM3OCZ0eHQ9JUUzJTgwJTkwVUU0JUVGJUJDJTlBQyUyQiUyQiVFMyU4MCU5MUJQJUUzJTgxJUE3JUU5JTk2JUEyJUU2JTk1JUIwJUUzJTgyJTkyJUU1JTkxJUJDJUUzJTgxJUIzJUU1JTg3JUJBJUUzJTgxJTk5JUUzJTgxJUE4JUUzJTgxJThEJUUzJTgxJUFFJUUzJTgzJTg3JUUzJTgzJTk1JUUzJTgyJUE5JUUzJTgzJUFCJUUzJTgzJTg4JUU1JUJDJTk1JUU2JTk1JUIwJUUzJTgyJTkyJUU4JUE4JUFEJUU1JUFFJTlBJUUzJTgxJTk5JUUzJTgyJThCJnR4dC1hbGlnbj1sZWZ0JTJDdG9wJnR4dC1jb2xvcj0lMjMxRTIxMjEmdHh0LWZvbnQ9SGlyYWdpbm8lMjBTYW5zJTIwVzYmdHh0LXNpemU9NTYmcz1kNDVlYjdhODg2MzM0ZTc3MDkyNzRmZGM1ODhiMjkxOA&mark-x=142&mark-y=57&blend64=aHR0cHM6Ly9xaWl0YS11c2VyLWNvbnRlbnRzLmltZ2l4Lm5ldC9-dGV4dD9peGxpYj1yYi00LjAuMCZoPTc2Jnc9NzcwJnR4dD0lNDBzaGlyYXNheWEwMjAxJnR4dC1jb2xvcj0lMjMxRTIxMjEmdHh0LWZvbnQ9SGlyYWdpbm8lMjBTYW5zJTIwVzYmdHh0LXNpemU9MzYmdHh0LWFsaWduPWxlZnQlMkN0b3Amcz1mNmJkYTlkNTcwZDNmYWY0NTM1OTY1ZTgxZWZlOThlMA&blend-x=142&blend-y=486&blend-mode=normal&s=ee97430a3141eb427b9fc000d605c960)
![](https://japanese-rooster.com/wp-content/uploads/cocoon-resources/blog-card-cache/30a8c6f9dd04b80ed867f4e6317b315b.png)
![](https://japanese-rooster.com/wp-content/uploads/2022/01/Run.gif)
Twitterしてます
ブログの更新をお知らせ
コメント