• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

UE4功能

武飞扬头像
FanghSpace
帮助1

UE4 功能整理

1. SpawnActor

情景:

  • 我有一个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()中加入BlueprintReadWritemeta=(AllowPrivateAccess=true)
  • FActorSpawnParameters是可以自定义生成参数,默认可以不写

  • GetActorTransform()可以是GetActorLocationGetActorRotation

2. LineTrace

  • Trace模式
    • TraceSingle 单个结果
    • TraceMulti 多个结果
  • Trace 的检测依据
    • ByChanne
    • ByObjectType
    • ByProfile
2.1 UWorld

LineTraceSingleByChannel

情景:

  • 场景中的某个Actor需要发射检测射线
  • 可以直接在Actor上写,也可以通过组件SceneComponentActorComponent
  • 示例采用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的信息
2.2 Kismet
2.2.1 LineTraceSingle

情景:

  • 根据 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 添加自定义
2.2.2 LineTraceSingleForObjects

情景:

  • 根据 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 添加自定义
2.2.3 LineTraceSingleByProfile

情景:

  • 根据 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 的名称

3. SweepTrace

情景:

  • 使用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:无旋转

4. SphereTrace

情景:

  • 使用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()));
    }
    
    学新通

5. Character

情景:

  • 第三人称
  • 自由视角
  • 角色朝向鼠标输入和控制器指向

示例:

  • 准备:

    1. 编辑 >> 项目设置 >> 引擎 >> 输入
    2. 添加操作映射
      • Jump == 空格键
    3. 添加轴映射
      • MoveForward
        • W == 1.0
        • S == -1.0
      • MoveRight
        • D == 1.0
        • A == -1.0
      • PitchCamera
        • 鼠标Y == -1.0
      • YawCamera
        • 鼠标X == 1.0
  • 定义:

    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);
    	}
    }
    
    学新通

6. Pawn

情景:

  • 使用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);
    }
    
    学新通

7. Impulse Force

情景:

  • 反射一条射线,朝向可移动,开启模拟物理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*

8. Add Force

情景:

  • 不发射射线
  • 给可以模拟物理的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());
    }
    

9. Radia Impulse Force

情景:

  • 实现爆炸力

示例:

  • 定义:

    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);
    	}
    }
    
    学新通

10. TimerHandle

情景:

  • 需要使用定时器

示例:

  • 定义:

    #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修饰

11. Disable Actor

情景:

  • 编辑场景中的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将会被禁用,不会作用于场景中

12. Hit Event

情景:

  • 通过一个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需要些设置,碰撞事件才能生效
  • 配置:
    • 方便查看:
      • 设置形状
      • 设置渲染
    • 产生事件:设置碰撞
      • 打开模拟生成命中事件
      • 设置碰撞预设

13. Set Material

情景:

  • 材质的创建
  • 材质的使用

示例:

  • 定义:

    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);
    }
    
    学新通

14. Dynamic Material

情景:

  • 有一个基本的材质
  • 通过创建材质实例
  • 对其中的ScaleParamVectorParam进行动态修改

示例:

  • 定义:

    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);
    }
    
    学新通

15. Interp Target

情景:

  • 指定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();写在构造函数里无效

16. Lerp

情景:

  • 需要使用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;
    	}
    }
    
    学新通

18. 黑洞

情景:

  • 一个球,可以在一定范围内吸引开启模拟物理的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);
		}
	}
}
学新通

19. 玩家死亡后进入观察

情景:

  • 但玩家死亡后
  • 播放死亡动画
  • 禁用玩家输入
  • 控制器切换控制到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);
      }
      
      学新通

20. 高处坠落伤害

情景:

  • 高处下落的速度达到指定值
  • 玩家受到相应的伤害
  • 使用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] 包含的相应百分比
    
    学新通

21. FindAnimNotifyByClass

情景:

  • 按类型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的子类
    // 里面不用写东西
    // 主要是用来:修改备注名,修改颜色,绑定事件
    
    学新通

22. 类生成附加到插槽

情景:

  • 用指定的组件开始生成

  • 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);
    }
    
    学新通

23. CameraShake

情景:

  • 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);
    }
    
    学新通

24. HealthComponent

情景:

  • 设计一个角色的生命组件
  • 有生命值
  • 有伤害处理
  • 呼吸回血效果
  • 有处理玩家死亡效果

前提:

  • 定义玩家类:ACharacterBase
  • 定义生命组件类:UHealthComponent

结构分析:

  • 玩家类中的OnDead效果对应于生命组件类中的OnDead
  • 生命组件类:
    • 可以通过蓝图获得CurrentHPMaxHPIsDead
    • 通过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
    }
    
    学新通

25. GetActorComponent

情景:

  • 需要通过已有的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;
    	}
    };
    
    学新通

26. 设计结构体和委托

情景:

  • 将一类结构体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;
    };
    
    学新通

27. AIPerception获得最近Actor

情景:

  • 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;
    }
    
    学新通

28. GameMode

情景:

  • 通过GameModeBase进行初始化

示例:

  • 实现:

    ASTUGameModeBase::ASTUGameModeBase()
    {
    	DefaultPawnClass = ASTUCharacterBase::StaticClass();
    	PlayerControllerClass = ASTUPlayerController::StaticClass();
    	HUDClass = ASTUGameHUD::StaticClass();
    }
    

29. 拾取物-PickUp

情景:

  • 创建PickUpBase
  • 可被放置在场景中
  • 可以Actor重叠检查
  • 不与Actor产生BlockHit
  • 拾取后不销毁,隐藏起来,一段时间重生,TimerHandle
  • 物品默认旋转,拾取后关闭旋转,重生后开启旋转,TimerHandle
  • 被拾取后,让拾取的Actor执行指定的GivePickUpToVirtual 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 );
    
    学新通

30. AIController

情景:

  • 与前面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));
    }
    
    学新通

31. AI初始化和过渡旋转

情景:

  • 初始化一个基本的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();
    	}
    }
    
    学新通

32. BeHaviorTree Task

情景:

  • AI Task的关键信息是AIControllerBlackBoardComp
  • 通过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;
    }
    
    学新通

33. BehaviorTree Service

情景:

  • 利用前面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());
    }
    
    学新通

34. HUD生成Widget

情景:

  • 设置自己的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();
    }
    

35. HUD绘制准星

情景:

  • 直接通过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);
    }
    
    学新通

36. Widget创建蓝图可用函数

情景:

  • 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();
    }
    
    学新通

37. UPROPERTY()

情景:

private:需要在蓝图中可读可写,任意处编辑

UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess=true))

bool IsTrue;为判断标准,为false时,编辑器处为不可编辑状态

UPROPERTY(EditAnywhere, meta=(EditCondition=“IsTrue”))

float HP;为例,在编辑器中设置的值要符合一个固定的范围

UPROPERTY(EditAnywhere, meta=(ClampMin=0, ClampMax=100))

38. check-checkf-checkNoEntry

情景:

  • 需要在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();
        }
    }
    

39. UFUNCTION

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgabbaa
系列文章
更多 icon
同类精品
更多 icon
继续加载