技术标签: 虚幻4 虚幻 c++ ue4 unreal engine 4
情景:
Cpp类
Cpp类
要生成一个其他Cpp
或蓝图
类TSubclassOf<>
示例:
定义
private:
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
TSubclassOf<AActor> SpawnActorTest;
实现
void AActor_SpawnTest::BeginPlay()
{
Super::BeginPlay();
FActorSpawnParameters SpawnParameters;
SpawnParameters.Name = FName("SpawnActorTest");
SpawnParameters.Owner = this;
SpawnParameters.Instigator = nullptr;
GetWorld()->SpawnActor<AActor>(SpawnActorTest, GetActorTransform(), SpawnParameters);
}
说明:
定义中的SpawnActorTest
是私有变量,若要保持私有,其蓝图可以Get
, Set
和参数列表中显示继承中:
UPOPERTY()
中加入BlueprintReadWrite
,meta=(AllowPrivateAccess=true)
FActorSpawnParameters
是可以自定义生成参数,默认可以不写
GetActorTransform()
可以是GetActorLocation
和GetActorRotation
LineTraceSingleByChannel
情景:
Actor
需要发射检测射线Actor
上写,也可以通过组件SceneComponent
,ActorComponent
SceneComponent
Syntax:
bool LineTraceSingleByChannel(
struct FHitResult& OutHit,
const FVector& Start,
const FVector& End,
ECollisionChannel TraceChannel,
const FCollisionQueryParams& Params = FCollisionQueryParams::DefaultQueryParam,
const FCollisionResponseParams& ResponseParam = FCollisionResponseParams::DefaultResponseParam
) const;
示例:
定义
private:
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category="Trace", meta=(AllowPrivateAccess=true))
float TraceDistance;
实现
const FVector Start = GetOwner()->GetActorLocation();
const FVector End = Start + (GetForwardVector() * TraceDistance);
FHitResult HitResult;
const bool IsHit = GetWorld()->LineTraceSingleByChannel(HitResult, Start, End, ECC_Visibility);
DrawDebugLine(GetWorld(), Start, End, FColor::Red, false, 0.5f);
if (!IsHit){return;}
GEngine->AddOnScreenDebugMessage(-1, 1.f, FColor::Green,
FString::Printf(TEXT("Trace Hit: %s"), *HitResult.GetActor()->GetName()));
说明:
HitResult
会包含射线检测到的Actor
的信息情景:
Channel
检测单个物体Syntax:
static bool LineTraceSingle(
const UObject* WorldContextObject,
const FVector Start,
const FVector End,
ETraceTypeQuery TraceChannel,
bool bTraceComplex,
const TArray<AActor*>& ActorsToIgnore,
EDrawDebugTrace::Type DrawDebugType,
FHitResult& OutHit,
bool bIgnoreSelf,
FLinearColor TraceColor = FLinearColor::Red,
FLinearColor TraceHitColor = FLinearColor::Green,
float DrawTime = 5.0f
);
示例:
定义:
private:
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category="Trace", meta=(AllowPrivateAccess=true))
float TraceDistance;
实现:
const FVector Start = GetOwner()->GetActorLocation();
const FVector End = Start + (GetForwardVector() * TraceDistance);
FHitResult HitResult;
const TArray<AActor*> ActorsToIgnore;
const bool IsHit = UKismetSystemLibrary::LineTraceSingle(
this, Start, End, TraceTypeQuery1,
false, ActorsToIgnore, EDrawDebugTrace::ForDuration, HitResult, true,
FLinearColor::Red, FLinearColor::Green, 1.f);
if (!IsHit){return;}
GEngine->AddOnScreenDebugMessage(-1, 1.f, FColor::Green,
FString::Printf(TEXT("Trace Hit: %s"), *HitResult.GetActor()->GetName()));
说明:
ETraceTypeQuery
说明
TraceTypeQuery1
—— Visibility
TraceTypeQuery2
—— Camera
ProjectSettings
->Engine
->Collision
->Trace Channels
添加自定义情景:
Object Type
检测单个物体Syntax
static bool LineTraceSingleForObjects(
const UObject* WorldContextObject,
const FVector Start,
const FVector End,
const TArray<TEnumAsByte<EObjectTypeQuery> > & ObjectTypes,
bool bTraceComplex,
const TArray<AActor*>& ActorsToIgnore,
EDrawDebugTrace::Type DrawDebugType,
FHitResult& OutHit,
bool bIgnoreSelf,
FLinearColor TraceColor = FLinearColor::Red,
FLinearColor TraceHitColor = FLinearColor::Green,
float DrawTime = 5.0f
);
示例:
实现:
// 设置要检测的 Object Type
TArray<TEnumAsByte<EObjectTypeQuery> > ObjectTypes;
ObjectTypes.Add(EObjectTypeQuery::ObjectTypeQuery1);
//开始检测
bool bIsHit = UKismetSystemLibrary::LineTraceSingleForObjects(GetWorld(), BeginLoc, EndLoc, ObjectTypes, false, IgnoreActors, EDrawDebugTrace::ForDuration, HitResult, true);
if (bIsHit)
{
UKismetSystemLibrary::PrintString(GetWorld(), HitResult.GetActor()->GetName());
}
说明:
EObjectTypeQuery
对应 ObjectType
ObjectTypeQuery1
—— WorldStatic
ObjectTypeQuery2
—— WorldDynamic
ObjectTypeQuery3
—— Oawn
ObjectTypeQuery4
—— PhysicasBody
ObjectTypeQuery5
—— Vehicle
ObjectTypeQuery6
—— Destructible
ProjectSettings
->Engine
->Collision
->Object Channels
添加自定义情景:
Collision Preset
检测单个物体Syntax
static bool LineTraceSingleByProfile(
const UObject* WorldContextObject,
const FVector Start,
const FVector End,
FName ProfileName,
bool bTraceComplex,
const TArray<AActor*>& ActorsToIgnore,
EDrawDebugTrace::Type DrawDebugType,
FHitResult& OutHit,
bool bIgnoreSelf,
FLinearColor TraceColor = FLinearColor::Red,
FLinearColor TraceHitColor = FLinearColor::Green,
float DrawTime = 5.0f
);
示例:
实现:
bool bIsHit = UKismetSystemLibrary::LineTraceSingleByProfile(
GetWorld(), BeginLoc, EndLoc,TEXT("BlockAll"),
false, IgnoreActors, EDrawDebugTrace::ForDuration,
HitResult, true);
if (bIsHit)
{
UKismetSystemLibrary::PrintString(GetWorld(), HitResult.GetActor()->GetName());
}
说明:
ProfileName
对应 Collision Preset
的名称情景:
使用UWorld
生成
生成一个范围检测周边的Actor
示例:
定义:
private:
UPROPERTY(EditDefaultsOnly, Category="Trace", BlueprintReadWrite, meta=(AllowPrivateAccess=true))
float TraceRadius;
实现:
TArray<FHitResult> HitResults;
const FVector Start, End = GetOwner()->GetActorLocation();
const FCollisionShape CollisionShape = FCollisionShape::MakeSphere(TraceRadius);
const bool IsHit = GetWorld()->SweepMultiByChannel(
HitResults, Start, End, FQuat::Identity,
ECC_Visibility, CollisionShape);
DrawDebugSphere(GetWorld(), Start, TraceRadius, 50, FColor::Green, true);
if (!IsHit){return;}
for (const auto &result : HitResults)
{
GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Green,
FString::Printf(TEXT("Hit: %s"), *result.GetActor()->GetName()));
}
说明:
FQuat
四元数
Indentity
:无旋转情景:
Kismet
生成Actor
示例:
定义:
private:
UPROPERTY(EditDefaultsOnly, Category="Trace", BlueprintReadWrite, meta=(AllowPrivateAccess=true))
float TraceRadius;
实现:
const FVector Start, End = GetOwner()->GetActorLocation();
TArray<AActor*> ActorsToIgnore;
ActorsToIgnore.Add(GetOwner());
TArray<FHitResult> HitResults;
const bool IsHit = UKismetSystemLibrary::SphereTraceMulti(
this,
Start,
End,
TraceRadius,
TraceTypeQuery1,
false,
ActorsToIgnore,
EDrawDebugTrace::ForDuration,
HitResults,
true,
FLinearColor::Green,
FLinearColor::Red,
60.f);
if (!IsHit){return;}
for (const auto &result : HitResults)
{
GEngine->AddOnScreenDebugMessage(
-1, 60.f, FColor::Green,
FString::Printf(TEXT("Hit: %s"),
*result.GetActor()->GetName()));
}
情景:
示例:
准备:
定义:
class USpringArmComponent;
class UCameraComponent;
private:
UPROPERTY(VisibleDefaultsOnly, BlueprintReadWrite, Category="Player", meta=(AllowPrivateAccess=true))
USpringArmComponent *PlayerSpringArmComponent;
UPROPERTY(VisibleDefaultsOnly, BlueprintReadWrite, Category="Player", meta=(AllowPrivateAccess=true))
UCameraComponent *PlayerCameraComponent;
public:
UFUNCTION()
void MoveForward(float Value);
UFUNCTION()
void MoveRight(float Value);
实现:
ACharacterBase::ACharacterBase()
{
PrimaryActorTick.bCanEverTick = true;
GetCapsuleComponent()->InitCapsuleSize(36.f, 92.f);
PlayerSpringArmComponent = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
PlayerSpringArmComponent->SetupAttachment(RootComponent);
PlayerSpringArmComponent->bUsePawnControlRotation = true;
// 以下是详细配置
PlayerSpringArmComponent->SetRelativeLocation(FVector(0.f, 0.f, 90.f));
PlayerSpringArmComponent->TargetArmLength = 300.f;
PlayerSpringArmComponent->bEnableCameraLag = true;
PlayerSpringArmComponent->bEnableCameraRotationLag = true;
PlayerSpringArmComponent->CameraLagSpeed = 10.f;
PlayerSpringArmComponent->CameraRotationLagSpeed = 10.f;
PlayerSpringArmComponent->CameraLagMaxDistance = 100.f;
PlayerCameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
PlayerCameraComponent->SetupAttachment(PlayerSpringArmComponent, USpringArmComponent::SocketName);
PlayerCameraComponent->bUsePawnControlRotation = false;
GetCharacterMovement()->bOrientRotationToMovement = true;
// 以下是详细配置
GetCharacterMovement()->RotationRate = FRotator(0.f, 90.f, 0.f);
GetCharacterMovement()->GravityScale = 1.5f;
GetCharacterMovement()->MaxAcceleration = 980.f;
GetCharacterMovement()->JumpZVelocity = 600.f;
GetCharacterMovement()->AirControl = 0.2f;
bUseControllerRotationPitch = false;
bUseControllerRotationRoll = false;
bUseControllerRotationYaw = false;
}
void ACharacterBase::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
// Bind Axis => MoveForward, MoveRight
PlayerInputComponent->BindAxis(TEXT("MoveForward"), this, &ACharacterBase::MoveForward);
PlayerInputComponent->BindAxis(TEXT("MoveRight"), this, &ACharacterBase::MoveRight);
// Pawn, Character 已经写好了内部的鼠标输入
PlayerInputComponent->BindAxis(TEXT("PitchCamera"), this, &ACharacter::AddControllerPitchInput);
PlayerInputComponent->BindAxis(TEXT("YawCamera"), this, &ACharacter::AddControllerYawInput);
// Bind Action => Jump
PlayerInputComponent->BindAction(TEXT("Jump"), IE_Pressed, this, &ACharacter::Jump);
PlayerInputComponent->BindAction(TEXT("Jump"), IE_Released, this, &ACharacter::StopJumping);
}
void ACharacterBase::MoveForward(float Value)
{
// 这种方法有问题:人物应用摄像机旋转,摄像机应用控制器旋转时,鼠标完全朝上或朝下,将无法正常行走
//const FVector Direction = FRotationMatrix(GetController()->GetControlRotation()).GetScaledAxis(EAxis::X);
// 这个方法简单,通用
const FVector Direction = GetActorForwardVector();
AddMovementInput(Direction, Value);
###### 官方写法 ######
if ((Controller != nullptr) && (Value != 0.0f))
{
// find out which way is forward
const FRotator Rotation = Controller->GetControlRotation();
const FRotator YawRotation(0, Rotation.Yaw, 0);
// get forward vector
const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
AddMovementInput(Direction, Value);
}
}
void ACharacterBase::MoveRight(float Value)
{
// const FVector Direction = FRotationMatrix(GetController()->GetControlRotation()).GetScaledAxis(EAxis::Y);
const FVector Direction = GetActorRightVector();
AddMovementInput(Direction, Value);
###### 官方写法 ######
if ( (Controller != nullptr) && (Value != 0.0f) )
{
// find out which way is right
const FRotator Rotation = Controller->GetControlRotation();
const FRotator YawRotation(0, Rotation.Yaw, 0);
// get right vector
const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
// add movement in that direction
AddMovementInput(Direction, Value);
}
}
情景:
Pawn
去写非人型示例:
定义:
private:
UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category="Pawn", meta=(AllowPrivateAccess=true))
FVector MovementDirection;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category="Pawn", meta=(AllowPrivateAccess=true))
float MovementSpeed;
public:
UFUNCTION()
void MoveForward(float Value);
UFUNCTION()
void MoveRight(float Value);
实现:
void APawnBase::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (!MovementDirection.IsZero())
{
const FVector NewLocation = GetActorLocation() + (MovementDirection * DeltaTime * MovementSpeed);
SetActorLocation(NewLocation);
}
}
void APawnBase::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
// Bind Axis => MoveForward, MoveRight
PlayerInputComponent->BindAxis(TEXT("MoveForward"), this, &APawnBase::MoveForward);
PlayerInputComponent->BindAxis(TEXT("MoveRight"), this, &APawnBase::MoveRight);
}
void APawnBase::MoveForward(float Value)
{
MovementDirection.X = FMath::Clamp(Value, -1.f, 1.f);
}
void APawnBase::MoveRight(float Value)
{
MovementDirection.Y = FMath::Clamp(Value, -1.f, 1.f);
}
情景:
可移动
,开启模拟物理
的Actor
Actor
往射线方向添加脉冲力
示例:
定义:
private:
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
float TraceDistance;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
float ImpulseForce;
实现:
FHitResult HitResult;
const FVector Start = GetComponentLocation();
const FVector End = Start + (GetComponentRotation().Vector() * TraceDistance);
const bool IsHit = GetWorld()->LineTraceSingleByChannel(
HitResult,
Start,
End,
ECC_Visibility);
if (!IsHit){return;}
DrawDebugLine(GetWorld(), Start, End, FColor::Green, false, 0.1f);
UStaticMeshComponent *StaticMeshComponent = Cast<UStaticMeshComponent>(HitResult.GetActor()->GetRootComponent());
if (!StaticMeshComponent || !HitResult.GetActor()->IsRootComponentMovable()){return;}
StaticMeshComponent->AddImpulse(GetForwardVector() * ImpulseForce * StaticMeshComponent->GetMass());
说明:
HitResult.GetActor()->GetRootComponent()
是针对根组件是UStaticMeshComponent
HitResult.GetActor()->GetStaticMeshComponent()
可移动
,模拟物理
的Actor
都能收到影响GetStaticMeshComponent()
可在任意Actor
内实现此函数,返回UStaticMeshComponent*
情景:
Actor
添加力示例:
定义:
class UStaticMeshComponent;
private:
UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, meta=(AllowPrivateAccess=true))
UStaticMeshComponent *StaticMeshComponent;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
float Force;
实现:
void UAddForce_SComp::BeginPlay()
{
Super::BeginPlay();
StaticMeshComponent = Cast<UStaticMeshComponent>(GetOwner()->GetRootComponent());
}
void UAddForce_SComp::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
const FVector UpForce = GetUpVector();
StaticMeshComponent->AddForce(UpForce * Force * StaticMeshComponent->GetMass());
}
情景:
爆炸力
示例:
定义:
private:
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
float ImpulseRadius;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
float ImpulseForceStrength;
实现:
void URadiaImpulse_SComp::BeginPlay()
{
Super::BeginPlay();
TArray<FHitResult> HitResults;
const FVector Start, End = GetComponentLocation();
const bool IsHit = GetWorld()->SweepMultiByChannel(
HitResults,
Start,
End,
FQuat::Identity,
ECC_WorldStatic,
FCollisionShape::MakeSphere(ImpulseRadius));
DrawDebugSphere(
GetWorld(),
Start,
ImpulseRadius,
50,
FColor::Green,
true);
if (!IsHit){return;}
for (const auto &result : HitResults)
{
UStaticMeshComponent *MeshComponent = Cast<UStaticMeshComponent>(result.GetActor()->GetRootComponent());
if (!MeshComponent){continue;}
MeshComponent->AddRadialImpulse(
Start,
ImpulseRadius,
ImpulseForceStrength,
RIF_Linear,
true);
}
}
情景:
示例:
定义:
#define PrintScreen(String) GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Green, String)
private:
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
int32 CallTracker;
UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, meta=(AllowPrivateAccess=true))
FTimerHandle TimerHandle;
public:
UFUNCTION()
void TimerFunction();
实现:
void ATimerHandle_Actor::BeginPlay()
{
Super::BeginPlay();
GetWorldTimerManager().SetTimer(
TimerHandle,
this,
&ATimerHandle_Actor::TimerFunction,
1.f,
true,
1.f);
}
void ATimerHandle_Actor::TimerFunction()
{
CallTracker == 0 ?
PrintScreen("Timer End"), GetWorldTimerManager().ClearTimer(TimerHandle) :
PrintScreen(FString::Printf(TEXT("Timer: %d"), CallTracker));
--CallTracker;
}
注意:
TimerHandle
,需要用protected
修饰情景:
Actor
示例:
定义:
#define PrintScreen(String) GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Green, String)
private:
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
bool isOverrideTick = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
bool isAutoDisable = false;
public:
UFUNCTION()
void SetActive(bool isActive);
实现:
ADisableActor::ADisableActor()
{
PrimaryActorTick.bCanEverTick = true;
}
void ADisableActor::BeginPlay()
{
Super::BeginPlay();
isOverrideTick = !PrimaryActorTick.bCanEverTick;
if (isAutoDisable){SetActive(false);}
}
void ADisableActor::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
PrintScreen("Tick");
}
void ADisableActor::SetActive(bool isActive)
{
if (isOverrideTick)
{
SetActorTickEnabled(false);
}
else
{
SetActorTickEnabled(isActive);
}
SetActorHiddenInGame(!isActive);
SetActorEnableCollision(isActive);
}
说明:
isAutoDisable
进行勾选Actor
将会被禁用,不会作用于场景中情景:
Actor
和场景中的其他Actor
碰撞示例:
定义:
#define PrintScreen(String) GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Green, String)
class UBoxComponent;
private:
UPROPERTY(VisibleDefaultsOnly, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
UBoxComponent *HitBox;
public:
UFUNCTION()
void OnHitComp(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp,
FVector NormalImpulse, const FHitResult& Hit);
实现:
AHitEventActor::AHitEventActor()
{
PrimaryActorTick.bCanEverTick = false;
HitBox = CreateDefaultSubobject<UBoxComponent>(TEXT("Hit Box"));
}
void AHitEventActor::BeginPlay()
{
Super::BeginPlay();
HitBox->OnComponentHit.AddDynamic(this, &AHitEventActor::OnHitComp);
}
void AHitEventActor::OnHitComp(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp,
FVector NormalImpulse, const FHitResult& Hit)
{
PrintScreen(FString::Printf(TEXT("Hit: %s"), *OtherActor->GetName()));
}
说明:
HitBox
需要些设置,碰撞事件才能生效形状
渲染
碰撞
模拟生成命中事件
碰撞预设
情景:
示例:
定义:
class UStaticMeshComponent;
class UMaterialInterface;
class UMaterial;
class UMaterialInstance;
private:
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
UStaticMeshComponent *Mesh;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
UMaterialInterface *MaterialOne;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
UMaterialInterface *MaterialTwo;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
UMaterial *Material;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
UMaterialInstance *MaterialInstance;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
bool IsChooseOne = true;
实现:
ASetMaterial::ASetMaterial()
{
PrimaryActorTick.bCanEverTick = false;
Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh_Comp"));
RootComponent = Mesh;
MaterialOne = CreateDefaultSubobject<UMaterialInterface>(TEXT("Material_One"));
MaterialTwo = CreateDefaultSubobject<UMaterialInterface>(TEXT("Material_Two"));
}
void ASetMaterial::BeginPlay()
{
Super::BeginPlay();
Mesh->SetMaterial(0, IsChooseOne ? MaterialOne : MaterialTwo);
}
情景:
ScaleParam
,VectorParam
进行动态修改示例:
定义:
class UStaticMeshComponent;
class UMaterialInterface;
class UMaterialInstanceDynamic;
private:
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
UStaticMeshComponent *Mesh;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
UMaterialInterface *MaterialInterface;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
UMaterialInstanceDynamic *DynamicInstance;
实现:
ADynamicMaterial::ADynamicMaterial()
{
PrimaryActorTick.bCanEverTick = false;
Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MeshComp"));
RootComponent = Mesh;
}
void ADynamicMaterial::BeginPlay()
{
Super::BeginPlay();
MaterialInterface = Mesh->GetMaterial(0);
DynamicInstance = UMaterialInstanceDynamic::Create(MaterialInterface, this);
Mesh->SetMaterial(0, DynamicInstance);
DynamicInstance->SetScalarParameterValue(TEXT("EmissiveStrength"), 50.f);
DynamicInstance->SetVectorParameterValue(TEXT("Color"), FLinearColor::Yellow);
}
情景:
Actor
插值移动到Target
示例:
定义:
private:
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
AActor *Origin = nullptr;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
AActor *Target;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
float InterpSpeed = 3.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
float WaitTime = 1.f;
实现:
void UInterpTarget_SComp::BeginPlay()
{
Super::BeginPlay();
Origin = GetOwner();
}
void UInterpTarget_SComp::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
if (WaitTime > 0)
{
WaitTime -= DeltaTime;
return;
}
if (!Target || !Origin){return;}
Origin->SetActorLocation(
FMath::VInterpTo(
Origin->GetActorLocation(),
Target->GetActorLocation(),
DeltaTime,
InterpSpeed));
}
说明:
Origin = GetOwner();
写在构造函数里无效情景:
Lerp
Actor
的位置
和材质
示例:
定义:
class UMaterialInterface;
class UMaterialInstanceDynamic;
class UStaticMeshComponent;
private:
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
UMaterialInterface *MaterialInter;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
UMaterialInstanceDynamic *InstanceDynamic;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
AActor *OriginActor = nullptr;
UPROPERTY(VisibleDefaultsOnly, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
UStaticMeshComponent *Mesh;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
FVector StartLocation;
UPROPERTY(EditAnywhere, meta=(MakeEditWidget=true))
FVector TargetLocation;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
float TimeElapsed = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
float LerpDuration = 3.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
float WaitTime = 1.f;
实现:
#include "Lerp_SComp.h"
ULerp_SComp::ULerp_SComp()
{
PrimaryComponentTick.bCanEverTick = true;
Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MeshComp"));
}
void ULerp_SComp::BeginPlay()
{
Super::BeginPlay();
OriginActor = GetOwner();
StartLocation = GetComponentLocation();
MaterialInter = Mesh->GetMaterial(0);
InstanceDynamic = UMaterialInstanceDynamic::Create(MaterialInter, this);
Mesh->SetMaterial(0, InstanceDynamic);
InstanceDynamic->SetVectorParameterValue(TEXT("Color"), FLinearColor::Blue);
}
void ULerp_SComp::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
if (!OriginActor){return;}
if (WaitTime > 0){WaitTime -= DeltaTime; return;}
if (TimeElapsed < LerpDuration)
{
OriginActor->SetActorLocation(
FMath::Lerp(
StartLocation,
TargetLocation,
TimeElapsed / LerpDuration));
InstanceDynamic->SetVectorParameterValue(
TEXT("Color"),
FMath::Lerp(
FLinearColor::Blue,
FLinearColor::Red,
TimeElapsed / LerpDuration));
TimeElapsed += DeltaTime;
}
}
情景:
开启模拟物理的Actor
Actor
会被销毁示例:
Mat_BlackHole
VectorParameter
,设置RGBA(0, 0, 0, 1)
,连接基础颜色
0
, 连接其余的选项定义:
BlackHole_Actor.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "BlackHole_Actor.generated.h"
class UStaticMeshComponent;
class USphereComponent;
struct FTimerHandle;
UCLASS()
class FPSGAME_API ABlackHole_Actor : public AActor
{
GENERATED_BODY()
public:
ABlackHole_Actor();
virtual void BeginPlay() override;
/* My Code */
// Property
private:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="BlackHole", meta=(AllowPrivateAccess=true))
float BlackHoleActionRate;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="BlackHole", meta=(AllowPrivateAccess=true))
float BlackHoleStrength;
public:
FTimerHandle BlackHoleActionHandle;
// Component
private:
UPROPERTY(VisibleAnywhere, Category="A_Hole")
UStaticMeshComponent *BlackHoleStaticMeshComp;
UPROPERTY(VisibleAnywhere, Category="A_Hole")
USphereComponent *InnerSphereComp;
UPROPERTY(VisibleAnywhere, Category="A_Hole")
USphereComponent *OuterSphereComp;
public:
UFUNCTION()
void OverlapInnerSphere(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult &SweepResult);
UFUNCTION()
void OnBlackHoleAction();
};
实现
BlackHole_Actor.cpp
#include "BlackHole_Actor.h"
#include "Components/SphereComponent.h"
ABlackHole_Actor::ABlackHole_Actor()
{
PrimaryActorTick.bCanEverTick = false;
BlackHoleActionRate = 0.05f;
BlackHoleStrength = 10000.f;
BlackHoleStaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("BlackStaticMesh"));
BlackHoleStaticMeshComp->SetCollisionEnabled(ECollisionEnabled::NoCollision);
BlackHoleStaticMeshComp->CastShadow = false;
RootComponent = BlackHoleStaticMeshComp;
InnerSphereComp = CreateDefaultSubobject<USphereComponent>(TEXT("InnerSphereComp"));
InnerSphereComp->SetSphereRadius(100.f);
InnerSphereComp->SetupAttachment(BlackHoleStaticMeshComp);
OuterSphereComp = CreateDefaultSubobject<USphereComponent>(TEXT("OuterSphereComp"));
OuterSphereComp->SetSphereRadius(3000.f);
OuterSphereComp->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
OuterSphereComp->SetCollisionResponseToAllChannels(ECR_Overlap);
OuterSphereComp->SetupAttachment(BlackHoleStaticMeshComp);
}
void ABlackHole_Actor::BeginPlay()
{
Super::BeginPlay();
check(InnerSphereComp);
check(OuterSphereComp);
if (InnerSphereComp)
{
InnerSphereComp->OnComponentBeginOverlap.AddDynamic(this, &ABlackHole_Actor::OverlapInnerSphere);
}
if (GetWorld())
{
GetWorldTimerManager().SetTimer(
BlackHoleActionHandle,
this,
&ABlackHole_Actor::OnBlackHoleAction,
BlackHoleActionRate,
true
);
}
}
void ABlackHole_Actor::OverlapInnerSphere(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
if (!OtherActor){return;}
OtherActor->Destroy();
}
void ABlackHole_Actor::OnBlackHoleAction()
{
TArray<UPrimitiveComponent*> OverlappingComp;
if (!OuterSphereComp){return;}
OuterSphereComp->GetOverlappingComponents(OverlappingComp);
for (int i = 0; i < OverlappingComp.Num(); ++i)
{
if (OverlappingComp[i] && OverlappingComp[i]->IsSimulatingPhysics())
{
const float SphereRadius = OuterSphereComp->GetScaledSphereRadius();
OverlappingComp[i]->AddRadialForce(
GetActorLocation(),
SphereRadius,
-BlackHoleStrength,
RIF_Constant,
true);
}
}
}
情景:
Spectator
示例:
前提:
蒙太奇动画
启用自动混出
为false
代码:
定义
class UAnimMontage;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Montage", meta=(AllowPrivateAccess=true))
UAnimMontage *AnimMontage_Death;
UFUNCTION()
void OnDead();
实现
void ASTUCharacterBase::OnDead()
{
if (!AnimMontage_Death){return;}
PlayAnimMontage(AnimMontage_Death);
GetCharacterMovement()->DisableMovement();
if (GetController())
{
GetController()->ChangeState(NAME_Spectating);
}
GetCapsuleComponent()->SetCollisionResponseToAllChannels(ECR_Ignore);
SetLifeSpan(5.0f);
GetMesh()->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
GetMesh()->SetSimulatePhysics(true);
}
情景:
ACharacter
自带的功能实现
virtual void Landed(const FHitResult& Hit);
FLandedSignature LandedDelegate;
示例:
介绍:
ACharacter.h
/**
* Called upon landing when falling, to perform actions based on the Hit result. Triggers the OnLanded event.
* Note that movement mode is still "Falling" during this event. Current Velocity value is the velocity at the time of landing.
* Consider OnMovementModeChanged() as well, as that can be used once the movement mode changes to the new mode (most likely Walking).
*
* @param Hit Result describing the landing that resulted in a valid landing spot.
* @see OnMovementModeChanged()
*/
virtual void Landed(const FHitResult& Hit);
/**
* 落地时调用,根据命中结果执行动作。 触发 OnLanded 事件。
* 请注意,此活动期间移动模式仍为“下降”。 当前速度值是着陆时的速度。
* 还要考虑 OnMovementModeChanged(),因为一旦移动模式更改为新模式(很可能是步行),就可以使用它。
*
* @param Hit Result 描述了导致有效着陆点的着陆。
* @see OnMovementModeChanged()
*/
FLandedSignature LandedDelegate;
定义:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="FallingDamage", meta=(AllowPrivateAccess=true))
FVector2D LandedDamageVelocity;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="FallingDamage", meta=(AllowPrivateAccess=true))
FVector2D LandedDamage;
UFUNCTION()
void OnGroundLanded(const FHitResult& HitResult);
实现:
void ASTUCharacterBase::BeginPlay()
{
Super::BeginPlay();
LandedDelegate.AddDynamic(this, &ASTUCharacterBase::OnGroundLanded);
}
void ASTUCharacterBase::OnGroundLanded(const FHitResult& HitResult)
{
const float FallVelocityZ = -GetVelocity().Z;
if (FallVelocityZ < LandedDamageVelocity.X){return;}
const float FinalDamage = FMath::GetMappedRangeValueClamped(LandedDamageVelocity,
LandedDamage, FallVelocityZ);
TakeDamage(FinalDamage, FDamageEvent{}, nullptr, nullptr);
}
# FMath::GetMappedRangeValueClamped() --> UnrealMathUtility.h
// For the given Value clamped to the [Input:Range] inclusive, returns the corresponding percentage in [Output:Range] Inclusive
// 对于钳制到 [Input:Range] 的给定值,返回 [Output:Range] 包含的相应百分比
情景:
UAnimSequenceBase
中查找FAnimNotifyEvent
示例:
前提:
创建AnimNotifyEventUntility.h
#pragma once
class AnimNotifyEventUntility
{
public:
template<typename T>
static T* FindNotifyByClass(UAnimSequenceBase* AnimSequenceBase)
{
if (!AnimSequenceBase){return nullptr;}
const auto NotifyEvents = AnimSequenceBase->Notifies;
for (const auto& NotifyEvent : NotifyEvents)
{
if (const auto AnimNotify = Cast<T>(NotifyEvent.Notify))
{
return AnimNotify;
}
}
return nullptr;
}
};
创建AnimNotifyBase.h
#pragma once
#include "CoreMinimal.h"
#include "Animation/AnimNotifies/AnimNotify.h"
#include "STUAnimNotifyBase.generated.h"
class USkeletalMeshComponent;
DECLARE_MULTICAST_DELEGATE_OneParam(FOnNotifySignature, USkeletalMeshComponent*)
UCLASS()
class B_01_TPS_API UAnimNotifyBase : public UAnimNotify
{
GENERATED_BODY()
public:
virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) override;
/* My Code */
// Property
FOnNotifySignature OnNotify;
};
# Notify
// 是 AnimNotify 自带的函数,同时不建议 UE5 使用
实现AnimNotifyBase.cpp
#include "AnimNotifyBase.h"
void USTUAnimNotifyBase::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation)
{
Super::Notify(MeshComp, Animation);
OnNotify.Broadcast(MeshComp);
}
定义:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Weapon", meta=(AllowPrivateAccess=true))
UAnimMontage *EquipAnimMontage;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Weapon", meta=(AllowPrivateAccess=true))
bool IsEquipAnimInProgress;
UFUNCTION()
void InitAnimations();
UFUNCTION()
void OnEquipFinished(USkeletalMeshComponent* SkeletalMeshComponent);
实现:
#include "AnimNotify/AnimNotifyEventUntility.h"
void USTUWeaponActorComponent::BeginPlay()
{
Super::BeginPlay();
InitAnimations();
}
void USTUWeaponActorComponent::InitAnimations()
{
if (const auto EquipFinishedNotify =
AnimUtils::FindNotifyByClass<USTUAnimNotifyEquipFinished>(EquipAnimMontage))
{
EquipFinishedNotify->OnNotify.AddUObject(this, &USTUWeaponActorComponent::OnEquipFinished);
}
else
{
checkNoEntry();
}
}
void USTUWeaponActorComponent::OnEquipFinished(USkeletalMeshComponent* SkeletalMeshComponent)
{
const ASTUCharacterBase *Character = Cast<ASTUCharacterBase>(GetOwner());
if (!Character || !(Character->GetMesh() == SkeletalMeshComponent)){return;}
IsEquipAnimInProgress = false;
}
# USTUAnimNotifyEquipFinished
// 是AnimNotifyBase的子类
// 里面不用写东西
// 主要是用来:修改备注名,修改颜色,绑定事件
情景:
用指定的组件开始生成
BeginPlay()
时开始
指定要生成的class
以有需要附加的插槽``
将生成的对象附加到插槽上,并应用插槽的transform
EndPlay()
结束后,生成的Actor
也要销毁
示例:
定义:UWeaponActorComponent.h
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class B_01_TPS_API USTUWeaponActorComponent : public UActorComponent
{
GENERATED_BODY()
public:
USTUWeaponActorComponent();
protected:
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
/* My Code */
// Property
private:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Weapon", meta=(AllowPrivateAccess=true))
TSubclassOf<ASTUWeaponBase> WeaponClass;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Weapon", meta=(AllowPrivateAccess=true))
FName WeaponEquipSocketName;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Weapon", meta=(AllowPrivateAccess=true))
ASTUWeaponBase *CurrentWeapon;
// Function
public:
UFUNCTION()
void SpawnWeapons();
UFUNCTION()
void AttachWeaponToSocket(ASTUWeaponBase* Weapon, USceneComponent* SceneComponent, const FName& SocketName);
}
实现:
USTUWeaponActorComponent::USTUWeaponActorComponent()
{
PrimaryComponentTick.bCanEverTick = false;
WeaponEquipSocketName = TEXT("WeaponSocket");
WeaponClass = nullptr
CurrentWeapon = nullptr;
}
void USTUWeaponActorComponent::BeginPlay()
{
Super::BeginPlay();
SpawnWeapons();
}
void USTUWeaponActorComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
CurrentWeapon->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform);
CurrentWeapon->Destroy();
CurrentWeapon = nullptr;
Super::EndPlay(EndPlayReason);
}
void USTUWeaponActorComponent::SpawnWeapons()
{
if (!GetWorld()){return;}
ASTUCharacterBase *Character = Cast<ASTUCharacterBase>(GetOwner());
if (!Character){return;}
const auto Weapon = GetWorld()->SpawnActor<ASTUWeaponBase>(WeaponClass);
if (!Weapon){continue;}
CurrentWeapon = Weapon;
// 此处留一个伏笔,当前武器指定了 Onwer 是当前的玩家
CurrentWeapon->SetOwner(Character);
AttachWeaponToSocket(CurrentWeapon, Character->GetMesh(), WeaponEquipSocketName);
}
void USTUWeaponActorComponent::AttachWeaponToSocket(ASTUWeaponBase* Weapon, USceneComponent* SceneComponent,
const FName& SocketName)
{
if (!Weapon || !SceneComponent){return;}
const FAttachmentTransformRules AttachmentTransformRules(EAttachmentRule::SnapToTarget, false);
Weapon->AttachToComponent(SceneComponent, AttachmentTransformRules, SocketName);
}
情景:
UHealthActorComponent
中实现CameraShake
示例:
定义:
public:
USTUHealthActorComponent();
private:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="HPComp|Camera", meta=(AllowPrivateAccess=true))
TSubclassOf<UCameraShakeBase> CameraShake;
public:
UFUNCTION()
void PlayCameraShake();
实现:
#include "STUHealthActorComponent.h"
USTUHealthActorComponent::USTUHealthActorComponent()
{
PrimaryComponentTick.bCanEverTick = false;
CameraShake = nullptr;
}
void USTUHealthActorComponent::PlayCameraShake()
{
const auto Player = Cast<APawn>(GetOwner());
if (!Player){return;}
const auto Controller = Player->GetController<APlayerController>();
if (!Controller || !Controller->PlayerCameraManager){return;}
Controller->PlayerCameraManager->StartCameraShake(CameraShake);
}
情景:
前提:
ACharacterBase
UHealthComponent
结构分析:
OnDead
效果对应于生命组件类中的OnDead
CurrentHP
,MaxHP
,IsDead
TimerHandle
实现呼吸回血效果示例:
生命组件类
定义:
DECLARE_MULTICAST_DELEGATE(FOnDead);
DECLARE_MULTICAST_DELEGATE_OneParam(FOnHealthChanged, float);
struct FTimerHandle;
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class B_01_TPS_API USTUHealthActorComponent : public UActorComponent
{
GENERATED_BODY()
public:
USTUHealthActorComponent();
protected:
virtual void BeginPlay() override;
/* My Code */
// Property
private:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="HPComp", meta=(AllowPrivateAccess=true))
float Health;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="HPComp", meta=(AllowPrivateAccess=true))
float MaxHealth;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="HPComp|Heal", meta=(AllowPrivateAccess=true))
bool IsAutoHeal;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="HPComp|Heal",
meta=(AllowPrivateAccess=true, EditCondition="IsAutoHeal"))
float HealUpdateTime;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="HPComp|Heal",
meta=(AllowPrivateAccess=true, EditCondition="IsAutoHeal"))
float HealDelayTime;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="HPComp|Heal",
meta=(AllowPrivateAccess=true, EditCondition="IsAutoHeal"))
float HealModifierValue;
public:
FTimerHandle HealTimerHandle;
FOnDead OnDead;
FOnHealthChanged OnHealthChanged;
// Function
public:
UFUNCTION(BlueprintCallable)
FORCEINLINE float GetHP() const {return Health;}
UFUNCTION()
void OnTakeAnyDamage(AActor* DamagedActor, float Damage, const class UDamageType* DamageType,
class AController* InstigatedBy, AActor* DamageCauser);
UFUNCTION(BlueprintCallable)
bool IsDead() const {return FMath::IsNearlyZero(Health);}
UFUNCTION()
void HealUpdate();
UFUNCTION()
void SetHealth(float NewHealth);
};
实现:
#include "STUHealthActorComponent.h"
USTUHealthActorComponent::USTUHealthActorComponent()
{
PrimaryComponentTick.bCanEverTick = false;
Health = 0.f;
MaxHealth = 100.f;
IsAutoHeal = true;
HealUpdateTime = 1.f;
HealDelayTime = 4.f;
HealModifierValue = 10.f;
}
void USTUHealthActorComponent::BeginPlay()
{
Super::BeginPlay();
SetHealth(MaxHealth);
if (GetOwner())
{
GetOwner()->OnTakeAnyDamage.AddDynamic(this, &USTUHealthActorComponent::OnTakeAnyDamage);
}
}
/* My Code */
void USTUHealthActorComponent::OnTakeAnyDamage(AActor* DamagedActor, float Damage, const UDamageType* DamageType,
AController* InstigatedBy, AActor* DamageCauser)
{
if (Health <= 0.f || IsDead() || !GetWorld()){return;}
SetHealth(Health - Damage);
if (IsDead())
{
OnDead.Broadcast();
}
else if (IsAutoHeal)
{
GetWorld()->GetTimerManager().SetTimer(HealTimerHandle, this, &USTUHealthActorComponent::HealUpdate,
HealUpdateTime, IsAutoHeal, HealDelayTime);
}
}
void USTUHealthActorComponent::HealUpdate()
{
SetHealth(Health += HealModifierValue);
if (!FMath::IsNearlyEqual(Health, MaxHealth) || !GetWorld()){return;}
GetWorld()->GetTimerManager().ClearTimer(HealTimerHandle);
}
void USTUHealthActorComponent::SetHealth(float NewHealth)
{
Health = FMath::Clamp(NewHealth, 0.f, MaxHealth);
}
玩家类
定义:
class USTUHealthActorComponent;
public:
ASTUCharacterBase();
protected:
virtual void BeginPlay() override;
/* My Code */
// Component
private:
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Component", meta=(AllowPrivateAccess=true))
USTUHealthActorComponent *HealthActorComponent;
// Function
public:
UFUNCTION()
void OnDead();
实现:
#include "STUHealthActorComponent.h"
ASTUCharacterBase::ASTUCharacterBase()
{
PrimaryActorTick.bCanEverTick = false;
HealthActorComponent = CreateDefaultSubobject<USTUHealthActorComponent>(TEXT("HPComp"));
}
void ASTUCharacterBase::BeginPlay()
{
Super::BeginPlay();
check(HealthActorComponent);
HealthActorComponent->OnDead.AddUObject(this, &ASTUCharacterBase::OnDead);
}
void ASTUCharacterBase::OnDead()
{
// OnDead Event
}
情景:
Actor
获得其已添加的Component
模板工具类
示例:
定义:
#pragma once
class FH_Utility
{
public:
template<typename T>
FORCEINLINE static T* GetActorComponent(const AActor* Actor)
{
if (!Actor){return nullptr;}
const auto Component = Actor->GetComponentByClass(T::StaticClass());
T* ResultComponent = Cast<T>(Component);
if (!ResultComponent){return nullptr;}
return ResultComponent;
}
};
情景:
结构体
或Delegate
写进单独的类中示例:
定义:FH_CoreType.h
#pragma once
#include "FH_CoreType.generated.h" // 因为使用了Delegate 和 USTRUCT,这里要手动加入
class ASTUWeaponBase;
class UAnimMontage;
// Delegate 的定义可以写在这里
DECLARE_MULTICAST_DELEGATE(FOnDead);
DECLARE_MULTICAST_DELEGATE_OneParam(FOnHealthChanged, float);
// 可以定义结构体,USTRCUT() 内加入 BlueprintType, 蓝图就可以或这个结构体
USTRUCT(BlueprintType)
struct FAmmoData
{
GENERATED_USTRUCT_BODY() // 原:GENERATED_BODY() 需改为 GENERATED_USTRUCT_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Ammo", meta=(AllowPrivateAccess=true))
int32 Bullets = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Ammo",
meta=(AllowPrivateAccess=true, EditCondition="!IsInfinity"))
int32 Clips = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Ammo", meta=(AllowPrivateAccess=true))
bool IsInfinity = false;
};
USTRUCT(BlueprintType)
struct FWeaponData
{
GENERATED_USTRUCT_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="ReloadWeapon", meta=(AllowPrivateAccess=true))
TSubclassOf<ASTUWeaponBase> WeaponClass = nullptr;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="ReloadWeapon", meta=(AllowPrivateAccess=true))
UAnimMontage* ReloadAnimMontage = nullptr;
};
情景:
AI
绑定了AIPerceptionComponent
AISense
:例如–目光UAISense_Sight
GetCurrentlyPerceivedActors
获得目光内的有效Actor
Actor
获得其前面HealthComponent
,判断Actor
是否符合需求AI
最近的Actor
示例:
定义:
#pragma once
#include "CoreMinimal.h"
#include "Perception/AIPerceptionComponent.h"
#include "STUAIPerceptionComponent.generated.h"
UCLASS()
class B_01_TPS_API USTUAIPerceptionComponent : public UAIPerceptionComponent
{
GENERATED_BODY()
/* My Code */
// Function
public:
UFUNCTION()
AActor* GetClosetActor() const;
};
实现:
#include "STUAIPerceptionComponent.h"
#include "AIController.h"
#include "STUHealthActorComponent.h"
#include "B_01_TPS/Dev/STUUtils.h"
#include "Perception/AISense_Sight.h"
AActor* USTUAIPerceptionComponent::GetClosetActor() const
{
TArray<AActor*> PerceptionActors;
// AIPerceptionComponent自带的函数,获得所有已经被感知的Actor
GetCurrentlyPerceivedActors(UAISense_Sight::StaticClass(), PerceptionActors);
// 判断组件的拥有AI是否有AIController
if (PerceptionActors.Num() <= 0){return nullptr;}
const auto Controller = Cast<AAIController>(GetOwner());
// 判断当前AI是否有效
if (!Controller){return nullptr;}
const auto Pawn = Controller->GetPawn();
// 开始计算最近的Actor
if (!Pawn){return nullptr;}
// MAX_FLT 是 UnrealEngine 自带的 UrealMathUtility 中的 宏
// #define MAX_FLT 3.402823466e+38F
// 表示可虚幻引擎可表达的 最大浮点数
float NealDistance = MAX_FLT;
AActor* NealPawn = nullptr;
for (const auto& PerceptionActor : PerceptionActors)
{
// 此处使用了前面创建的 HealthComponent 和 FH_Utility
// 判断 感知到的Actor是否有生命组件,是否还活着
const auto HealthComp = STUUtils::GetSTUPlayerComponent<USTUHealthActorComponent>(PerceptionActor);
if (!HealthComp || HealthComp->IsDead()){return nullptr;}
const auto CurrentDistance = (PerceptionActor->GetActorLocation() - Pawn->GetActorLocation()).Size();
// 找出最近距离的Actor
if (CurrentDistance < NealDistance)
{
NealDistance = CurrentDistance;
NealPawn = PerceptionActor;
}
}
return NealPawn;
}
情景:
GameModeBase
进行初始化示例:
实现:
ASTUGameModeBase::ASTUGameModeBase()
{
DefaultPawnClass = ASTUCharacterBase::StaticClass();
PlayerControllerClass = ASTUPlayerController::StaticClass();
HUDClass = ASTUGameHUD::StaticClass();
}
情景:
PickUpBase
Actor
重叠检查Actor
产生BlockHit
TimerHandle
TimerHandle
Actor
执行指定的GivePickUpTo
,Virtual Function
示例:
定义:
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "STUBasePickUp.generated.h"
class USphereComponent;
class UStaticMeshComponent;
struct FTimerHandle;
UCLASS()
class B_01_TPS_API ASTUBasePickUp : public AActor
{
GENERATED_BODY()
public:
ASTUBasePickUp();
protected:
virtual void BeginPlay() override;
virtual void NotifyActorBeginOverlap(AActor* OtherActor) override;
/* My Code */
// Property
protected:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="PickUp", meta=(AllowPrivateAccess=true))
float RespawnTime;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="PickUp", meta=(ClampMin=0.0333, ClampMax=0.0083))
float RotationYawRate;
FTimerHandle RespawnHandle;
FTimerHandle RotationYawHandle;
// Component
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="PickUp", meta=(AllowPrivateAccess=true))
USphereComponent* CollisionComponent;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="PickUp", meta=(AllowPrivateAccess=true))
UStaticMeshComponent* StaticMeshComponent;
// Function
public:
UFUNCTION()
void PickUpWasTaken();
UFUNCTION()
void Respawn();
UFUNCTION()
void LoopRotationYawHandle();
UFUNCTION()
void BeginRotationYaw();
UFUNCTION()
virtual bool GivePickUpTo(APawn* PlayerPawn) { return false; }
};
# NotifyActorBeginOverlap(AActor* OtherActor)
/**
* Event when this actor overlaps another actor, for example a player walking into a trigger.
* For events when objects have a blocking collision, for example a player hitting a wall, see 'Hit' events.
* @note Components on both this and the other Actor must have bGenerateOverlapEvents set to true to generate overlap events.
*/
实现:
#include "STUBasePickUp.h"
#include "Components/SphereComponent.h"
ASTUBasePickUp::ASTUBasePickUp()
{
PrimaryActorTick.bCanEverTick = false;
RespawnTime = 5.0f;
RotationYawRate = 0.0333f;
CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("CollisionComp"));
RootComponent = CollisionComponent;
CollisionComponent->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
CollisionComponent->SetCollisionResponseToAllChannels(ECR_Overlap);
CollisionComponent->InitSphereRadius(50.f);
StaticMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MeshComp"));
StaticMeshComponent->SetupAttachment(RootComponent);
StaticMeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
StaticMeshComponent->SetCollisionResponseToAllChannels(ECR_Ignore);
}
void ASTUBasePickUp::BeginPlay()
{
Super::BeginPlay();
check(CollisionComponent);
check(StaticMeshComponent);
LoopRotationYawHandle();
}
void ASTUBasePickUp::NotifyActorBeginOverlap(AActor* OtherActor)
{
Super::NotifyActorBeginOverlap(OtherActor);
const auto Player = Cast<APawn>(OtherActor);
if (!GivePickUpTo(Player)){return;}
PickUpWasTaken();
}
/* My Code */
void ASTUBasePickUp::PickUpWasTaken()
{
if (!GetRootComponent()){return;}
CollisionComponent->SetCollisionResponseToAllChannels(ECR_Ignore);
GetRootComponent()->SetVisibility(false, true);
GetWorldTimerManager().SetTimer(RespawnHandle, this, &ASTUBasePickUp::Respawn, RespawnTime, false);
GetWorldTimerManager().ClearTimer(RotationYawHandle);
}
void ASTUBasePickUp::Respawn()
{
if (!GetRootComponent()){return;}
CollisionComponent->SetCollisionResponseToAllChannels(ECR_Overlap);
GetRootComponent()->SetVisibility(true, true);
GetWorldTimerManager().ClearTimer(RespawnHandle);
LoopRotationYawHandle();
}
void ASTUBasePickUp::LoopRotationYawHandle()
{
if (!StaticMeshComponent && !RespawnHandle.IsValid()){return;}
GetWorldTimerManager().SetTimer(
RotationYawHandle,
this,
&ASTUBasePickUp::BeginRotationYaw,
RotationYawRate,
true);
}
void ASTUBasePickUp::BeginRotationYaw()
{
StaticMeshComponent->AddRelativeRotation(FRotator(0, 1.f, 0.f));
}
注意:
# 示例中是通过 AActor 自带的 NotifyActorBeginOverlap(AActor* OtherActor) 实现交互
# 也可以通过 USphereComponent->UShapeComponent->UPrimitiveComponent 内的 FComponentHitSignature OnComponentHit; 实现交互
################## 委托名称 ###############
/**
* Event called when a component hits (or is hit by) something solid. This could happen due to things like Character movement, using Set Location with 'sweep' enabled, or physics simulation.
* For events when objects overlap (e.g. walking into a trigger) see the 'Overlap' event.
*
* @note For collisions during physics simulation to generate hit events, 'Simulation Generates Hit Events' must be enabled for this component.
* @note When receiving a hit from another object's movement, the directions of 'Hit.Normal' and 'Hit.ImpactNormal'
* will be adjusted to indicate force from the other object against this object.
* @note NormalImpulse will be filled in for physics-simulating bodies, but will be zero for swept-component blocking collisions.
*/
UPROPERTY(BlueprintAssignable, Category="Collision")
FComponentHitSignature OnComponentHit;
################### 委托绑定的函数参数列表 ##################
/**
* Delegate for notification of blocking collision against a specific component.
* NormalImpulse will be filled in for physics-simulating bodies, but will be zero for swept-component blocking collisions.
*/
DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_FiveParams( FComponentHitSignature, UPrimitiveComponent, OnComponentHit, UPrimitiveComponent*, HitComponent, AActor*, OtherActor, UPrimitiveComponent*, OtherComp, FVector, NormalImpulse, const FHitResult&, Hit );
情景:
AIPerception获得最近Actor
想关联AIPerceptionComponent
获取最近的Actor
TimerHandle
控制检测频率最近的Actor
AIControlller
控制AI
面向最近的Actor
AIController
获得自己绑定的AI
,执行有效的BehaviorTree
FName
手动指定获得黑板组件中指定的Key
示例:
定义:
#pragma once
#include "CoreMinimal.h"
#include "AIController.h"
#include "STUAIController.generated.h"
class USTUAIPerceptionComponent;
struct FTimerHandle;
UCLASS()
class B_01_TPS_API ASTUAIController : public AAIController
{
GENERATED_BODY()
public:
ASTUAIController();
protected:
virtual void OnPossess(APawn* InPawn) override;
virtual void BeginPlay() override;
/* My Code */
// Property
private:
UPROPERTY()
bool IsRunBehavior;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="AI", meta=(AllowPrivateAccess=true))
float CheckRate;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="AI", meta=(AllowPrivateAccess=true))
FName FocusOnKeyName;
public:
FTimerHandle CheckClosetEnemyTimerHandle;
// Component
private:
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="AI", meta=(AllowPrivateAccess=true))
USTUAIPerceptionComponent* AIPerceptionComponent;
// Function
public:
UFUNCTION()
void OnCheckClosetEnemy();
UFUNCTION()
AActor* GetFocusOnActor() const;
};
实现:
#include "STUAIController.h"
#include "STUAI.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "B_01_TPS/Component/STUAIPerceptionComponent.h"
ASTUAIController::ASTUAIController()
{
IsRunBehavior = false;
CheckRate = 0.1f;
// 此处指定要访问 黑板组件 的 keyName
FocusOnKeyName = "EnemyActor";
// 此处指定初始化 AIPerceptionComponent
AIPerceptionComponent = CreateDefaultSubobject<USTUAIPerceptionComponent>(TEXT("AIPerceptionComp"));
if (AIPerceptionComponent){SetPerceptionComponent(*AIPerceptionComponent);}
}
void ASTUAIController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
const auto STUCharacter = Cast<ASTUAI>(InPawn);
// 此处执行 AI绑定的 BehaviorTree
if (!STUCharacter || !STUCharacter->GetBehaviorTreeAsset()){return;}
IsRunBehavior = RunBehaviorTree(STUCharacter->GetBehaviorTreeAsset());
}
void ASTUAIController::BeginPlay()
{
Super::BeginPlay();
// 开始按频率 检测最近的Actor
if (GetWorld() && IsRunBehavior)
{
GetWorldTimerManager().SetTimer(
CheckClosetEnemyTimerHandle,
this,
&ASTUAIController::OnCheckClosetEnemy,
CheckRate,
true);
}
}
// 设置 AI面向 最近的 Actor
void ASTUAIController::OnCheckClosetEnemy()
{
const auto AimActor = GetFocusOnActor();
SetFocus(AimActor);
}
// 在黑板组件的指定key中 拿到以设置的 最近的Actor
AActor* ASTUAIController::GetFocusOnActor() const
{
if (!GetBlackboardComponent()){return nullptr;}
return Cast<AActor>(GetBlackboardComponent()->GetValueAsObject(FocusOnKeyName));
}
情景:
AI
AIController
已经可以面向最近的Actor
,但旋转没有过渡,需要实现过渡BehaviorTree
Dead
相关函数,方便通过AIcontroller
停止AI
的行为示例:
定义:
#pragma once
#include "CoreMinimal.h"
#include "B_01_TPS/Player/STUCharacterBase.h"
#include "STUAI.generated.h"
class UBehaviorTree;
UCLASS()
class B_01_TPS_API ASTUAI : public ASTUCharacterBase
{
GENERATED_BODY()
protected:
ASTUAI(const FObjectInitializer& ObjectInit);
/* My Code */
// Property
private:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="AI", meta=(AllowPrivateAccess=true))
UBehaviorTree* BehaviorTreeAsset;
public:
UFUNCTION()
FORCEINLINE UBehaviorTree* GetBehaviorTreeAsset() const {return BehaviorTreeAsset;}
virtual void OnDead() override;
};
实现:
#include "STUAI.h"
#include "BrainComponent.h"
#include "STUAIController.h"
#include "B_01_TPS/Component/STUAIWeaponActorComponent.h"
#include "B_01_TPS/Component/STUCharacterMovementComponent.h"
ASTUAI::ASTUAI(const FObjectInitializer& ObjectInit) :
Super(ObjectInit.SetDefaultSubobjectClass<USTUAIWeaponActorComponent>("WeaponActorComponent"))
{
// 初始化 AI 和 AIController
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
AIControllerClass = ASTUAIController::StaticClass();
// 利用 CharacterMovementComponent 设置 AI 旋转面向 最近Actor 的过渡效果
if (GetCharacterMovement())
{
bUseControllerRotationYaw = false;
GetCharacterMovement()->bUseControllerDesiredRotation = true;
GetCharacterMovement()->RotationRate = FRotator(0.f, 200.f, 0.f);
}
}
void ASTUAI::OnDead()
{
Super::OnDead();
// 通知 AIController 停止 AI行为
const auto STUController = Cast<AAIController>(Controller);
if (STUController && STUController->BrainComponent)
{
STUController->BrainComponent->Cleanup();
}
}
情景:
AI Task
的关键信息是AIController
和BlackBoardComp
AIController
获得绑定的AI Pawn
UNavigationSystemV1
获得场景的NavMesh
导航BlackBoardComp
获得和设置Key
值示例:
定义:
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/BTTaskNode.h"
#include "STUNextLocationTask.generated.h"
struct FBlackboardKeySelector;
UCLASS()
class B_01_TPS_API USTUNextLocationTask : public UBTTaskNode
{
GENERATED_BODY()
public:
USTUNextLocationTask();
protected:
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
/* My Code */
// Property
private:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="AI|Task", meta=(AllowPrivateAccess=true))
float Radius;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="AI|Task", meta=(AllowPrivateAccess=true))
FBlackboardKeySelector AimLocationKey;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="AI|Task", meta=(AllowPrivateAccess=true))
bool IsSelfCenter;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="AI|Task",
meta=(AllowPrivateAccess=true, EditCondition="!IsSelfCenter"))
FBlackboardKeySelector CenterActorKey;
};
实现:
#include "STUNextLocationTask.h"
#include "AIController.h"
#include "NavigationSystem.h"
#include "BehaviorTree/BlackboardComponent.h"
USTUNextLocationTask::USTUNextLocationTask()
{
Radius = 1000.f;
NodeName = "Next Location";
IsSelfCenter = true;
}
EBTNodeResult::Type USTUNextLocationTask::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
// 获得 AIController 和 BlackBoardComp
const auto Controller = OwnerComp.GetAIOwner();
const auto BlackBoard = OwnerComp.GetBlackboardComponent();
// 通过 AIController 获得 AI Pawn
if (!Controller || !BlackBoard){return EBTNodeResult::Failed;}
const auto Pawn = Controller->GetPawn();
// 获得场景中的 NavMeshSystem
if (!Pawn){return EBTNodeResult::Failed;}
const auto NavSys = UNavigationSystemV1::GetCurrent(Pawn);
// 先获得 AI 当前的位置
if (!NavSys){return EBTNodeResult::Failed;}
FNavLocation NavLocation;
auto Location = Pawn->GetActorLocation();
// 判断 AI 是否已经到达 随机的位置
// 通过 黑板组件获得 指定位置的 AI 的 key值
if (IsSelfCenter){return EBTNodeResult::Failed;}
const auto CenterActor = Cast<AActor>(BlackBoard->GetValueAsObject(CenterActorKey.SelectedKeyName));
// 但 AI到达随机位置,设置下个随机点的 位置
if (!CenterActor){return EBTNodeResult::Failed;}
Location = CenterActor->GetActorLocation();
const bool IsFound = NavSys->GetRandomReachablePointInRadius(Location, Radius, NavLocation);
// 设定好下个 随机位置,需要用 黑板组件 设置 新的位置 key值
if (!IsFound){return EBTNodeResult::Failed;}
BlackBoard->SetValueAsVector(AimLocationKey.SelectedKeyName, NavLocation.Location);
return EBTNodeResult::Succeeded;
}
情景:
AIPerceptionComponent
,设置新的目标Actor
AIPerceptionComponent
获得新目标,设置黑板组件
为例示例:
定义:
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/BTService.h"
#include "STUFindEnemyBTService.generated.h"
struct FBlackboardKeySelector;
UCLASS()
class B_01_TPS_API USTUFindEnemyBTService : public UBTService
{
GENERATED_BODY()
public:
USTUFindEnemyBTService();
protected:
virtual void TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
/* My Code */
// Property
private:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="AI", meta=(AllowPrivateAccess=true))
FBlackboardKeySelector EnemyActorKey;
};
实现:
#include "STUFindEnemyBTService.h"
#include "AIController.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "B_01_TPS/Component/STUAIPerceptionComponent.h"
#include "B_01_TPS/Dev/STUUtils.h"
USTUFindEnemyBTService::USTUFindEnemyBTService()
{
NodeName = "Find Enemy";
}
void USTUFindEnemyBTService::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds);
// 获得 黑板 和 AIController
const auto BlackBoard = OwnerComp.GetBlackboardComponent();
if (!BlackBoard){return;}
const auto Controller = OwnerComp.GetAIOwner();
// 获得 PerceptionComponent
if (!Controller){return;}
const auto PerceptionComponent = STUUtils::GetSTUPlayerComponent<USTUAIPerceptionComponent>(Controller);
// 从 PerceptionComponent 得到新的 Actort 设置到 黑板 的 key值
if (!PerceptionComponent){return;}
BlackBoard->SetValueAsObject(EnemyActorKey.SelectedKeyName, PerceptionComponent->GetClosetEnemy());
}
情景:
HUD
生成指定Widget
示例:
定义:
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/HUD.h"
#include "STUGameHUD.generated.h"
UCLASS()
class B_01_TPS_API ASTUGameHUD : public AHUD
{
GENERATED_BODY()
protected:
virtual void BeginPlay() override;
/* My Code */
// Function
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="UI", meta=(AllowPrivateAccess=true))
TSubclassOf<UUserWidget> PlayerHUDWidgetClass;
};
实现:
#include "STUGameHUD.h"
#include "Blueprint/UserWidget.h"
#include "Engine/Canvas.h"
void ASTUGameHUD::BeginPlay()
{
Super::BeginPlay();
const auto PlayerHUDWidget = CreateWidget<UUserWidget>(GetWorld(), PlayerHUDWidgetClass);
if (!PlayerHUDWidget){return;}
PlayerHUDWidget->AddToViewport();
}
情景:
HUD
生成静态准星示例:
定义:
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/HUD.h"
#include "STUGameHUD.generated.h"
UCLASS()
class B_01_TPS_API ASTUGameHUD : public AHUD
{
GENERATED_BODY()
protected:
virtual void DrawHUD() override;
public:
UFUNCTION()
void DrawCrossHair();
};
实现:
#include "STUGameHUD.h"
#include "Blueprint/UserWidget.h"
#include "Engine/Canvas.h"
void ASTUGameHUD::DrawHUD()
{
Super::DrawHUD();
DrawCrossHair();
}
void ASTUGameHUD::DrawCrossHair()
{
const TInterval<float> Center(Canvas->SizeX * 0.5f, Canvas->SizeY * 0.5f);
constexpr float HalfLineSize = 10.f;
constexpr float LineThickness = 2.f;
const FColor LineColor = FColor::Green;
DrawLine(
Center.Min - HalfLineSize,
Center.Max,
Center.Min + HalfLineSize,
Center.Max,
LineColor,
LineThickness);
DrawLine(
Center.Min,
Center.Max - HalfLineSize,
Center.Min,
Center.Max + HalfLineSize,
LineColor,
LineThickness);
}
情景:
C++
创建的Widget
蓝图可调用
函数示例:
定义:
UCLASS()
class B_01_TPS_API USTUPlayerHUDWidget : public UUserWidget
{
GENERATED_BODY()
/* My Code */
// Function
public:
UFUNCTION(BlueprintCallable)
float GetHealthPercent() const;
UFUNCTION(BlueprintCallable)
bool IsPlayerAlive() const;
UFUNCTION(BlueprintCallable)
bool IsPlayerSpectating() const;
};
实现:
#include "STUPlayerHUDWidget.h"
#include "B_01_TPS/Dev/STUUtils.h"
#include "B_01_TPS/Component/STUHealthActorComponent.h"
#include "B_01_TPS/Component/STUWeaponActorComponent.h"
float USTUPlayerHUDWidget::GetHealthPercent() const
{
const auto HealthComp = STUUtils::GetSTUPlayerComponent<USTUHealthActorComponent>(GetOwningPlayerPawn());
if (!HealthComp){return 0.f;}
return HealthComp->GetHPPercent();
}
bool USTUPlayerHUDWidget::IsPlayerSpectating() const
{
const auto Controller = GetOwningPlayer();
return Controller && Controller->GetStateName() == NAME_Spectating;
}
bool USTUPlayerHUDWidget::IsPlayerAlive() const
{
const auto HealthComp = STUUtils::GetSTUPlayerComponent<USTUHealthActorComponent>(GetOwningPlayerPawn());
return HealthComp && !HealthComp->IsDead();
}
情景:
private
:需要在蓝图中可读可写
,任意处编辑UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess=true))
以
bool IsTrue;
为判断标准,为false
时,编辑器处为不可编辑
状态UPROPERTY(EditAnywhere, meta=(EditCondition=“IsTrue”))
float HP;
为例,在编辑器中设置的值要符合一个固定的范围UPROPERTY(EditAnywhere, meta=(ClampMin=0, ClampMax=100))
情景:
需要在BeginPlay()
检测组件是否有效
check(GetMesh());
:如果GetMesh()
无效,程序会中断
还需要通过条件判断是否有效,同时打印指定语句到日志
以float HP;
为例,默认BeginPlay()
时,HP
应该大于0
checkf(HP > 0.f, TEXT("HP Shound Great 0"));
应用于不可到达的代码片段,当出现不应该出现的情况时,程序中断
void Function(AActor* Actor)
{
if (Actor)
{
....
}
else
{
checkNoEntry();
}
}
这次给大家带来配置H5的滚动条样式的,配置H5的滚动条样式的注意事项有哪些,下面就是实战案例,一起来看一下。本文介绍了配置H5的滚动条样式的示例代码,分享给大家,具体如下:/* 滚动条的滑轨背景颜色 */::-webkit-scrollbar-track {background-color: #b46868;}/* 滑块颜色 */::-webkit-scrollbar-thumb {backgro..._1671465600
java基础——集合类
Centos7部署PostgreSQL 12 主从流复制
Open Source VOIP applications, both clients and servers. Open source means all source code is available!! Do not post any "free but not open" software here! SIP Proxies Mini-SIP-Proxy A very tiny perl POE based SIP proxy MjServer cross-platform SIP proxy/r
1.首先通过Adobe Acrobat pro软件打开pdf文件(注:如果没有这个软件可以通过我的百度云下载,链接:http://pan.baidu.com/s/1pL2klzt)如果无法下载可以联系博主。2. 选择此处准备模板,添加角标类似上图,然后保存我是把模板上传到项目中,保存在doc目录下:以上是准备工作,下面进入代码阶段 1. 引入jar包依赖,我是maven项目 <dependency> <groupId>com.
解决方案: 点一下右下角的Event Log就好了
1.页面添加 一个验证码输入框 一个img标签用来存放验证码图写一个imgCode()点击事件,用来刷新验证码将获得的验证码放入redis缓存当中,登录的时候用来验证验证图形验证码是否正确``代码如下:controller层: @RequestMapping("toLogin") public String toLogin() { return "login"; } @RequestMapping("login") public Str
一.什么是代码片段(code snippets)代码片段:指用来存放程序执行代码的一块内存区域。诸如很多开发工具基本都集成了这一功能,可以说你在coding的时候经常都在使用它,或许你不知道你在使用的正是snippet的功能,比如你在开发工具中写代码的时候,用到if,for,while等循环体是,基本都不是自己把整块完整的写出来,而是通过编译器的提示自动填充剩余部分的代码。这也就是所谓的代
客户VPShttpd.exe进程占用100%CPU百度搜了下,很多文章:在网上也没有能够直接找到比较好的解决方法,后来在一个帖子上看到说,有可能是apache与其他的软件冲突了(参考http://topic.csdn.net/u/20080331/14/9a86ed60-285e-49b9-a11c-f42810dc64a5.html的35楼)。如果是冲突,他会一直写在apache/log
问:怎么每天看到这种文章?答:只需搜索公众号"51单片机学习网"免费关注一、红外通信原理红外遥控有发送和接收两个组成部分。发送端采用单片机将待发送的二进制信号编码调制为一系列的脉冲串信号,通过红外发射管发射红外信号。红外接收完成对红外信号的接收、放大、检波、整形,并解调出遥控编码脉冲。为了减少干扰,采用的是价格便宜性能可靠的一体化红外接收头(HS0038,它接收红外信号频率为38kH...
题目链接:https://www.luogu.org/problemnew/show/P1550思路:1:把地当做0节点,那么打井的费用,就是各节点到0节点的费用2:跑kruskal算法:1:kruskal#include <bits/stdc++.h>using namespace std;const int maxn=301;int n,par...
stm32存储方式为小端模式大端模式:数据高字节保存在内存低地址中,数据低字节保存在内存高地址中小端模式:数据高字节保存在内存高地址中,数据低字节保存在内存低地址中...