虚幻C++ day5

角色状态的常见机制

创建角色状态设置到UI上

  • 在MainPlayer.h中新建血量,最大血量,耐力,最大耐力,金币变量,作为角色的状态
	//主角状态
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Playe Stats")
	float Health;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Playe Stats")
	float MaxHealth;
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Playe Stats")
	float Stamina;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Playe Stats")
	float  MaxStamina;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Playe Stats")
	int Coins;
//初始化角色状态
MaxHealth = 100.f;
Health = MaxHealth;
MaxStamina = 200.f;
Stamina = MaxStamina;
Coins = 0;
  • 然后我们需要去血量与耐力的UI蓝图中去实时更新present(这个是采用的比例)
  • 思路:因为这些UI都是用在MainPlayer中,我们在蓝图中获取MianPlayer的引用,然后实例化,这样我们每次用到这个UI的时候都会去实例化MainPlayer,然后present绑定个函数用来实时同步present值(状态值除以最大状态值)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • Stamina UI也是如此,运行结果
    在这里插入图片描述

添加状态更新函数

  • 添加三个函数,用来改变血量、耐力、金币的值

MainPlayer.h

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

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "MainPlayer.generated.h"

UCLASS()
class UEGAME_API AMainPlayer : public ACharacter
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	AMainPlayer();

	//新建一个SpringArm
	UPROPERTY(visibleAnywhere,BlueprintReadOnly)
	class USpringArmComponent* SpringArm;
	//新建一个Camera
	UPROPERTY(visibleAnywhere, BlueprintReadOnly)
	class UCameraComponent* FollowCamera;

	
	float BaseTurnRate;		//使用键盘X转向的速率
	float BaseLookUpRate;	//使用键盘Y转向的速率

	//主角状态
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Playe State")
	float Health;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Playe State")
	float MaxHealth;
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Playe State")
	float Stamina;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Playe State")
	float  MaxStamina;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Playe State")
	int Coins;



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

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

	//重新Character类中的Jump方法
	void Jump() override;

	void MoveForward(float value);
	void MoveRight(float value);

	void Turn(float Value);
	void LookUp(float Value);

	void TurnRate(float Rate);
	void LookUpRate(float Rate);

	//改变状态
	void AddHealth(float value);
	void AddStamina(float value);
	void AddCoin(float value);
};

MainPlayer.cpp

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


#include "MainPlayer.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/InputComponent.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/CharacterMovementComponent.h"

// Sets default values
AMainPlayer::AMainPlayer()
{
 	// Set this character to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
	SpringArm->SetupAttachment(GetRootComponent());
	//设置SPringArm无碰撞臂长
	SpringArm->TargetArmLength = 600.f;
	SpringArm->bUsePawnControlRotation = true;//硬编码SpringArm继承controlller旋转为真

	FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
	FollowCamera->SetupAttachment(SpringArm, NAME_None);
	FollowCamera->bUsePawnControlRotation = false;//硬编码FollowCamera继承controlller旋转为假

	//设置胶囊体的默认宽高
	GetCapsuleComponent()->SetCapsuleSize(35.f, 100.f);

	//对Character的Pawn进行硬编码
	bUseControllerRotationPitch = false;
	bUseControllerRotationYaw = false;
	bUseControllerRotationRoll = false;

	//硬编码orient Rotation to Movement,给个默认转向速率
	GetCharacterMovement()->bOrientRotationToMovement = true;
	GetCharacterMovement()->RotationRate = FRotator(0.f, 500.f, 0.f);

	//设置跳跃初始值与在空中的坠落时横向运动控制量
	GetCharacterMovement()->JumpZVelocity = 600.f;
	GetCharacterMovement()->AirControl = 0.15f;

	//给键盘控制转向的速率变量赋初值
	BaseTurnRate = 21.f;
	BaseLookUpRate = 21.f;

	//初始化角色状态
	MaxHealth = 100.f;
	Health = MaxHealth;
	MaxStamina = 200.f;
	Stamina = MaxStamina;
	Coins = 0;
}

// Called when the game starts or when spawned
void AMainPlayer::BeginPlay()
{
	Super::BeginPlay();
	
}

// Called every frame
void AMainPlayer::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

// Called to bind functionality to input
void AMainPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
	//检查PlayerInputComponent指针,check函数只能在这使用
	check(PlayerInputComponent);

	//绑定跳跃轴映射事件
	PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &AMainPlayer::Jump);//按下空格
	PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping);//抬起空格

	//绑定移动轴映射事件
	PlayerInputComponent->BindAxis("MoveForward", this, &AMainPlayer::MoveForward);
	PlayerInputComponent->BindAxis("MoveRight", this, &AMainPlayer::MoveRight);

	//绑定Controller控制器去管理视角旋转
	PlayerInputComponent->BindAxis("Turn", this, &AMainPlayer::Turn);
	PlayerInputComponent->BindAxis("LookUp", this, &AMainPlayer::LookUp);

	//绑定键盘鼠标轴映射事件
	PlayerInputComponent->BindAxis("TurnRate", this, &AMainPlayer::TurnRate);
	PlayerInputComponent->BindAxis("LookUpRate", this, &AMainPlayer::LookUpRate);

}

void AMainPlayer::Jump()
{
	//继承父类的方法
	Super::Jump();
}

void AMainPlayer::MoveForward(float value)
{
	if (Controller != nullptr && value != 0.f)
	{
		//获取到Control旋转
		FRotator Rotation = Controller->GetControlRotation();
		//转向只关注水平Yaw方向,因此置0防止影响
		FRotator YowRotation = FRotator(0.0f, Rotation.Yaw, 0.0f);
		//获取相机(鼠标控制器的朝向),并且朝这个轴的方向移动
		FVector Direction = FRotationMatrix(YowRotation).GetUnitAxis(EAxis::X);
		AddMovementInput(Direction, value);
	}

}

void AMainPlayer::MoveRight(float value)
{
	if (Controller != nullptr && value != 0.f)
	{
		//获取到Controller旋转
		FRotator Rotation = Controller->GetControlRotation();
		//转向只关注水平Yaw方向,因此置0防止影响
		FRotator YowRotation = FRotator(0.0f, Rotation.Yaw, 0.0f);
		//获取相机(鼠标控制器的朝向),并且朝这个轴的方向移动
		FVector Direction = FRotationMatrix(YowRotation).GetUnitAxis(EAxis::Y);
		AddMovementInput(Direction, value);
	}
}

void AMainPlayer::Turn(float Value)
{
	if (Value != 0.f)
	{
		AddControllerYawInput(Value);
	}
	
}

void AMainPlayer::LookUp(float Value)
{
	//UE_LOG(LogTemp, Warning, TEXT("%f"), GetControlRotation().Pitch);
	// 
	//控制视角
	if (GetControlRotation().Pitch < 270.f && GetControlRotation().Pitch >180.f && Value > 0.f)
	{
		return;
	}
	else if (GetControlRotation().Pitch < 180.f && GetControlRotation().Pitch >45.f && Value < 0.f)
	{
		return;
	}
	AddControllerPitchInput(Value);
}

void AMainPlayer::TurnRate(float Rate)
{
	//要乘以一个DeltaTime这样就可以避免高帧底帧差值问题
	float Value = Rate * BaseTurnRate * GetWorld()->GetDeltaSeconds();
	if (Value != 0.f)
	{
		AddControllerYawInput(Value);
	}
}

void AMainPlayer::LookUpRate(float Rate)
{
	//要乘以一个DeltaTime这样就可以避免高帧底帧差值问题
	float Value = Rate * BaseLookUpRate * GetWorld()->GetDeltaSeconds();
	//控制视角
	if (GetControlRotation().Pitch < 270.f && GetControlRotation().Pitch >180.f && Value > 0.f)
	{
		return;
	}
	else if (GetControlRotation().Pitch < 180.f && GetControlRotation().Pitch >45.f && Value < 0.f)
	{
		return;
	}
	AddControllerPitchInput(Value);

}

void AMainPlayer::AddHealth(float value)
{
	Health = FMath::Clamp(Health + value, 0.f, MaxHealth);
}

void AMainPlayer::AddStamina(float value)
{
	Stamina = FMath::Clamp(Stamina + value, 0.f, MaxStamina);
}

void AMainPlayer::AddCoin(float value)
{
	Coins += value;
}

创建可交互物体的基类

  • 需求:这个可交互物体,应该可以播放粒子特效,所以我们需要粒子系统,然后有静态网格表示这个物体,作为基类,我们可以把所有的要求都写入里面,然后到时候创建子类继承来重写需求
  • 定义一个球形的触发器组件,静态网格,粒子组件与粒子系统,声音资源,进行多播委托事件,虚写多播委托函数,方便到时候子类继承重写
  • 可以自定义一下这个物体的旋转和碰撞类别
  • #include "Components/SphereComponent.h":球形碰撞器的头文件
  • #include "Particles/ParticleSystemComponent.h":粒子系统的头文件

InteroperableItem.h

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

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "InteroperableItem.generated.h"

UCLASS()
class UEGAME_API AInteroperableItem : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AInteroperableItem();

	//球形触发器
	UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
	class USphereComponent* TriggerVolume;

	UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
	class UStaticMeshComponent* DisplayMesh;

	//粒子组件
	UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
	class UParticleSystemComponent* ParticleEffectsComponent;

	//粒子资源
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interoperable Item|Particles")
	class UParticleSystem* Particle;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interoperable Item|Sounds")
	class USoundCue* Sound;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interoperable Item|Properties")
	bool bRotate;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interoperable Item|Properties")
	float RotationRate;  
protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	//自定义AddDynamic绑定的触发器函数
	UFUNCTION()
	virtual void OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
	UFUNCTION()
	virtual void OnOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
	
};

InteroperableItem.cpp

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


#include "InteroperableItem.h"
#include "Components/SphereComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Particles/ParticleSystemComponent.h"
// Sets default values
AInteroperableItem::AInteroperableItem()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
	TriggerVolume = CreateDefaultSubobject<USphereComponent>(TEXT("TriggerVolume"));
	RootComponent = TriggerVolume;

	//设置TriggerVolume碰撞的硬编码
	TriggerVolume->SetCollisionEnabled(ECollisionEnabled::QueryOnly);//设置碰撞类型
	TriggerVolume->SetCollisionObjectType(ECollisionChannel::ECC_WorldStatic);//设置对象移动时其应视为某种物体
	TriggerVolume->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);//设置所有的碰撞响应为忽略
	TriggerVolume->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);//设置Pawn碰撞响应为重叠


	DisplayMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("DisplayMesh"));
	DisplayMesh->SetupAttachment(GetRootComponent());

	ParticleEffectsComponent = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("ParticleEffects"));
	ParticleEffectsComponent->SetupAttachment(GetRootComponent());
	
	bRotate = true;
	RotationRate = 45.f;
}

// Called when the game starts or when spawned
void AInteroperableItem::BeginPlay()
{
	Super::BeginPlay();
	
	TriggerVolume->OnComponentBeginOverlap.AddDynamic(this, &AInteroperableItem::OnOverlapBegin);
	TriggerVolume->OnComponentEndOverlap.AddDynamic(this, &AInteroperableItem::OnOverlapEnd);

}

// Called every frame
void AInteroperableItem::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	if (bRotate)
	{
		FRotator rotator = GetActorRotation();
		rotator.Yaw += RotationRate * DeltaTime;
		SetActorRotation(rotator);
	}
}

void AInteroperableItem::OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
}

void AInteroperableItem::OnOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
}

创建可交互基类的子类爆炸物

  • 创建一个继承自Interoperable类的子类Explosive
  • 新建一个伤害值,重写OnOverlapBegin与OnOverlapEnd事件函数,在OnOverlapBegin中判断角色是否触发到炸弹,然后生成发射器播放粒子效果与声音,之后销毁
AExplosiveItem::AExplosiveItem()
{
	Damage = 20.f;
}
void AExplosiveItem::OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	Super::OnOverlapBegin(OverlappedComponent, OtherActor, OtherComp, OtherBodyIndex, bFromSweep, SweepResult);
	
	if (OtherActor)
	{
		AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
		if (Player)
		{
			//防御性编程
			if (Particle)
			{
				//生成发射器
				UGameplayStatics::SpawnEmitterAtLocation(this, Particle, GetActorLocation(), FRotator(0.f), true);
			}
			if (Sound)
			{
				UGameplayStatics::PlaySound2D(this, Sound);
			}
			//销毁
			Destroy();
		}
	}
}
  • 运行结果
    在这里插入图片描述
    在这里插入图片描述

爆炸物对玩家的伤害施加

  • 我们采用UE中内置的直接伤害附加的功能,在Explosive类中调用UGameplayStatics::ApplyDamage()函数传递伤害值,在MainPlayer类中重写TakeDamage方法,进行接收伤害并对玩家施加
  • UGameplayStatics::ApplyDamage():源码函数分析
  • 该函数接收五个参数:
    • DamagedActor: 受损的 actor。
    • BaseDamage: 基础损害值。
    • EventInstigator: 事件发起者,通常是施加损害的对象。
    • DamageCauser: 直接导致损害的 actor。
    • DamageTypeClass: 损害类型的类。
    • 该函数首先检查 DamagedActor是否存在以及 BaseDamage是否不为零。如果是这样,则继续执行;否则,返回零表示未造成任何损害。 接下来,设置损害事件 (FDamageEvent)并传入有效的损害类型类 (TSubclassOf 类型)。如果提供了 DamageTypeClass参数,则使用该类;否则,默认使用 UDamageType类。 最后,调用 DamagedActor 的 TakeDamage方法,将基础损害值(BaseDamage) 和损害事件 (DamageEvent)传入,并返回造成的总损害。 整个函数最终实现的功能是将基础损害值施加到指定的受损actor上,并返回造成的总损害。
float UGameplayStatics::ApplyDamage(AActor* DamagedActor, float BaseDamage, AController* EventInstigator, AActor* DamageCauser, TSubclassOf<UDamageType> DamageTypeClass)
{
	if ( DamagedActor && (BaseDamage != 0.f) )
	{
		// make sure we have a good damage type
		TSubclassOf<UDamageType> const ValidDamageTypeClass = DamageTypeClass ? DamageTypeClass : TSubclassOf<UDamageType>(UDamageType::StaticClass());
		FDamageEvent DamageEvent(ValidDamageTypeClass);

		return DamagedActor->TakeDamage(BaseDamage, DamageEvent, EventInstigator, DamageCauser);
	}

	return 0.f;
}
  • TakeDamage():源码分析
  • 该方法接收四个参数:
    • Damage: 损害值。
    • DamageEvent: 损害事件。
    • EventInstigator: 事件发起者。
    • DamageCauser: 直接导致损害的 actor。
    • 方法首先调用 ShouldTakeDamage 成员方法以确定 pawn 是否应该受到损害。如果返回 false,则方法直接返回零表示未造成任何损害。 然后,方法调用基类 Super::TakeDamage 方法来处理损害,并将传入的所有参数都传递给它。在此过程中,损害值会被修改以反映抗性等因素的影响。 最后,方法响应所受损害。如果 event instigator存在并且与 pawn的控制器不同,则更新最后一个击中者为event instigator。此外,无论是否存在event instigator 或 event instigator是否与pawn控制器相同,都会进行进一步处理,以确保 pawn在遭受损害后能够做出相应的反应(如播放动画或发出声音)。
float APawn::TakeDamage(float Damage, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
	if (!ShouldTakeDamage(Damage, DamageEvent, EventInstigator, DamageCauser))
	{
		return 0.f;
	}

	// do not modify damage parameters after this
	const float ActualDamage = Super::TakeDamage(Damage, DamageEvent, EventInstigator, DamageCauser);

	// respond to the damage
	if (ActualDamage != 0.f)
	{
		if ( EventInstigator && EventInstigator != Controller )
		{
			LastHitBy = EventInstigator;
		}
	}

	return ActualDamage;
}
  • Explosive.cpp
void AExplosiveItem::OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	Super::OnOverlapBegin(OverlappedComponent, OtherActor, OtherComp, OtherBodyIndex, bFromSweep, SweepResult);
	
	if (OtherActor)
	{
		AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
		if (Player)
		{
			//防御性编程
			if (Particle)
			{
				//生成发射器
				UGameplayStatics::SpawnEmitterAtLocation(this, Particle, GetActorLocation(), FRotator(0.f), true);
			}
			if (Sound)
			{
				UGameplayStatics::PlaySound2D(this, Sound);
			}
			
			//传递伤害值
			UGameplayStatics::ApplyDamage(OtherActor, Damage, nullptr, this, DamageTypeClass);

			//销毁
			Destroy();
		}
	}
}
  • MainPlayer.h
//重写TakeDamage方法
float TakeDamage(float Damage, struct FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser) override;
  • MainPlayer.cpp
float AMainPlayer::TakeDamage(float Damage, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
	if (Health - Damage <= 0.f)
	{
		Health = FMath::Clamp(Health - Damage, 0.f, MaxHealth);
		//TODO Die();
	}
	else
	{
		Health -= Damage;
	}
	return Health;
}
  • 在蓝图中选择伤害类型为直接伤害
    在这里插入图片描述

创建可拾取道具的基类(基础自InteroperableItem)

  • 基本与Explosive子类差不多,只不过可拾取的基类目前只需要碰撞检测与一个提供拾取的共有接口OnPick(蓝图化),这样到时候需求就可以去蓝图中可视化操作

PickItem.h

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

#pragma once

#include "CoreMinimal.h"
#include "GamePlay/InteroperableItem.h"
#include "PickItem.generated.h"

/**
 * 
 */
UCLASS()
class UEGAME_API APickItem : public AInteroperableItem
{
	GENERATED_BODY()

public:
	APickItem();
public:
	
	void OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) override;
	void OnOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex) override;

	UFUNCTION(BlueprintImplementEvent, Category = "Pick")
	void OnPick(class AMainPlayer* Palyer);
};

PickItem.cpp

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


#include "PickItem.h"
#include "Kismet/GamePlayStatics.h"
#include "Characters/Player/MainPlayer.h"
#include "Sound/SoundCue.h"
APickItem::APickItem()
{
	
}

void APickItem::OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	if (OtherActor)
	{
		AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
		if (Player)
		{
			UGameplayStatics::SpawnEmitterAtLocation(this, Particle, GetActorLocation(), FRotator(0.f), true);
		}
		if (Sound)
		{
			UGameplayStatics::PlaySound2D(this, Sound);
		}
		OnPick(Player);//交给蓝图可视化去处理
		Destroy();
	}
}

void APickItem::OnOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{

}

创建可拾取道具基类的子类(Blueprint)

  • 创建PickItem蓝图进行MainPlayer的函数调用即可
  • 注意将之前MainPlayer中AddHealth这些函数加上反射
  • MainPlayer.h中的
	//改变状态
	UFUNCTION(BlueprintCallable,Category="Player|State")
	void AddHealth(float value);
	UFUNCTION(BlueprintCallable, Category = "Player|State")
	void AddStamina(float value);
	UFUNCTION(BlueprintCallable, Category = "Player|State")
	void AddCoin(float value);
  • 蓝图调用C++编写好的逻辑即可,UI会实时检测更新
    在这里插入图片描述
  • 运行结果
    在这里插入图片描述

冲刺行为需求

  • 思路1:当耐力值到达一个精疲力尽的状态时,就停止冲刺,所以我们需要在混合空间1D里面将Axis Setting最大速度值从600上升到900,添加9个关键帧将冲刺添加上去
    在这里插入图片描述
  • 思路2:新建四个变量来标识状态,耐力消耗速率,耐力的精疲力尽的状态,奔跑速度与冲刺速度
   //主角状态
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Playe State")
	float Health;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Playe State")
	float MaxHealth;
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Playe State")
	float Stamina;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Playe State")
	float MaxStamina;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Playe State")
	float StaminaConsumeRate;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Playe State", meta = (ClampMin = 0, ClampMax = 1))
	float ExhaustedStamina;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Playe State")
	int Coins;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Player State")
	float RunningSpeed;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Player State")
	float SprintSpeed;
	//初始化角色状态
	MaxHealth = 100.f;
	Health = MaxHealth;
	MaxStamina = 200.f;
	Stamina = MaxStamina;
	StaminaConsumeRate = 20.f;
	ExhaustedStamina = 0.167f;
	Coins = 0;
	RunningSpeed = 600.f;
	SprintSpeed = 900.f;

创建枚举类型进行标识

  • 创建枚举用来标识角色的状态
  • 枚举的声明规则
  • UENUM(BlueprintType):幻引擎中的一个宏,用于定义带有蓝图支持的枚举类型,使用此宏定义枚举时,它将在蓝图中可用
  • UMETA(DisplayName = "Normal"):是一个宏,用于设置枚举值的显示名称为 “Normal”。这是为了使枚举值在蓝图或编辑器界面中更加可读和直观。
UENUM(BlueprintType)
enum class 类名 : 变量类型
{
	变量 UMETA(DisplayName = "你调用时想要显示的名字"),
	....
	变量 UMETA(DisplayName = "你调用时想要显示的名字")
}
  • MainPlayer.h
//声明移动状态枚举
UENUM(BlueprintType)
enum class EPlayerMovementStatus :uint8
{
	EPMS_Normal UMETA(DisplayName = "Normal"),
	EPMS_Sprinting UMETA(DisplayName = "Sprinting"),
	EPMS_Dead UMETA(DisplayName = "Dead")
};

UENUM(BlueprintType)
enum class EPlayerStaminaStatus :uint8
{
	EPSS_Normal UMETA(DisplayName = "Normal"),
	EPSS_Exhausted UMETA(DisplayName = "Exhausted"),
	EPSS_ExhaustedRecovering UMETA(DisplayName = "ExhaustedRecovering")
};

// ```````````````````省略之前的
UPROPERTY(VisibleAnywhere,BlueprintReadWrite,Category="Player State")
EPlayerMovementStatus MovementStatus;
UPROPERTY(VisibleAnywhere,BlueprintReadWrite,Category="Player State")
EPlayerStaminaStatus StaminaStatus;
  • MainPlayer.cpp
	//初始化角色状态
// ```````````````````省略之前的
	MovementStatus = EPlayerMovementStatus::EPMS_Normal;
	StaminaStatus = EPlayerStaminaStatus::EPSS_Normal;

使用标识位检测shift状态

  • 新建一个bool变量来监测shift是否按下的状态,新建两个函数用来绑定shift事件映射状态用来判断是否按下shift状态。
  • MainPlayer.h
	bool bLeftShiftDown;
//短小精悍
FORCEINLINE void LeftShiftDown() { bLeftShiftDown = true; }
FORCEINLINE void LeftShiftUp() { bLeftShiftDown = false; }
  • MainPlayer.cpp
	//绑定跳跃轴映射事件
	PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &AMainPlayer::Jump);//按下空格
	PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping);//抬起空格

	PlayerInputComponent->BindAction("Sprint", IE_Pressed, this, &AMainPlayer::LeftShiftDown);//按下shift
	PlayerInputComponent->BindAction("Sprint", IE_Released, this, &AMainPlayer::LeftShiftUp);//抬起shift

冲刺逻辑编写

  • 思路划分:
    • 一、首先我们新建一个设置移动枚举状态的函数处理冲刺与不冲刺的时候character的速度,用于到时候逻辑编写时的状态更换
    • 二、然后我们在Tick中来设计耐力枚举切换的逻辑
    • 三、在EPSS_Normal中判断是否按下shift键,然后判断是否进入了精疲力尽区,逻辑(总耐力 -耐力消耗值 *DeltaTime<=最大耐力值 *耐力消耗速率),这个逻辑就是每帧判断当前的耐力是否到达的精疲力尽区,到达了精疲力尽区首先改变StaminaStatus状态为Exhausted没有进入精疲力尽区Stamina要减去每帧的耐力消耗值,这是必定的,然后移动状态转为Sprint,判断没有进入精疲力尽区,那就恢复Stamina值(可以与耐力消耗值一样),然后把移动状态恢复成Normal状态
    • 四、在EPSS_Exhausted中判断是否还按着shift键,然后判断是否耐力已经到0了如果耐力已经为0,那么我们需要内部编码把shift抬起,此时StaminaStatus状态转换为ExhaustedRecovering状态。然后设置移动状态为Normal如果耐力没有为0,就直接减去当前帧消耗的耐力,没有按着shift键,那就直接将StaminaStatus状态转换为ExhaustedRecovering状态,恢复Stamina值,然后把移动状态恢复成Normal状态
    • 五、在EPSS_ExhaustedRecovering中判断当Stamina值已经大于精疲力尽区时,逻辑((总耐力 +耐力消耗值 *DeltaTime>=最大耐力值 *耐力消耗速率),将StaminaStatus设置为Normal状态,然后Stamina要加上每帧的耐力消耗值,这是必定的,然后抬起shift与移动状态设置为Normal。
      在这里插入图片描述
//一、首先我们新建一个设置```移动枚举状态的函数```处理冲刺与不冲刺的时候```character```的速度,用于到时候逻辑编写时的状态更换
void SetMovementStatus(EPlayerMovementStatus Status);
void AMainPlayer::SetMovementStatus(EPlayerMovementStatus Status)
{
	MovementStatus = Status;
	//切换状态的时候改变移动速度
	switch (MovementStatus)
	{
	case EPlayerMovementStatus::EPMS_Sprinting:
		GetCharacterMovement()->MaxWalkSpeed = SprintSpeed;
		break;
	default:
		GetCharacterMovement()->MaxWalkSpeed = RunningSpeed;
		break;
	}
}
//二、然后我们在```Tick```中来设计耐力枚举切换的逻辑
// Called every frame
void AMainPlayer::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	switch (StaminaStatus)
	{
	case EPlayerStaminaStatus::EPSS_Normal:
		//当Shift按下
		if (bLeftShiftDown)
		{
			if (Stamina - StaminaConsumeRate * DeltaTime <= MaxStamina * ExhaustedStamina)
			{
				StaminaStatus = EPlayerStaminaStatus::EPSS_Exhausted;
			}
			//无论是不是精疲力尽状态都要减去当前帧冲刺消耗的耐力
			Stamina -= StaminaConsumeRate * DeltaTime;
			SetMovementStatus(EPlayerMovementStatus::EPMS_Sprinting);
		}
		else
		{
			//当Shift没有按下,恢复耐力
			Stamina = FMath::Clamp(Stamina + StaminaConsumeRate * DeltaTime, 0.f, MaxStamina);
			SetMovementStatus(EPlayerMovementStatus::EPMS_Normal);
		}
		break;
	case EPlayerStaminaStatus::EPSS_Exhausted:
		if (bLeftShiftDown)
		{
			//如果耐力已经为0
			if (Stamina - StaminaConsumeRate * DeltaTime <= 0.f)
			{
				//么我们需要内部编码把shift抬起,此时StaminaStatus状态转换为ExhaustedRecovering状态,然后设置移动状态为Normal
				LeftShiftUp();
				StaminaStatus = EPlayerStaminaStatus::EPSS_ExhaustedRecovering;	
				SetMovementStatus(EPlayerMovementStatus::EPMS_Normal);
			}
			else
			{
				Stamina -= StaminaConsumeRate * DeltaTime;
			}
		}
		else
		{
			StaminaStatus = EPlayerStaminaStatus::EPSS_ExhaustedRecovering;
			Stamina = FMath::Clamp(Stamina + StaminaConsumeRate * DeltaTime, 0.f, MaxStamina);
			SetMovementStatus(EPlayerMovementStatus::EPMS_Normal);
		}
		break;
	case EPlayerStaminaStatus::EPSS_ExhaustedRecovering:
		//当恢复大于疲劳区时,StaminaStatus状态为Normal
		if (Stamina + StaminaConsumeRate * DeltaTime >= MaxStamina * ExhaustedStamina)
		{
			StaminaStatus = EPlayerStaminaStatus::EPSS_Normal;
		}
		//这状态值肯定是加定了
		Stamina += StaminaConsumeRate * DeltaTime;

		//抬起shift
		LeftShiftUp();
		SetMovementStatus(EPlayerMovementStatus::EPMS_Normal);
		break;
	default:
		break;
	}
}

耐力消耗殆尽的变色UI

  • 添加一个Appearance的绑定,在蓝图中将编写的Enum进行判断状态然后切换相应颜色
    在这里插入图片描述
    在这里插入图片描述
  • 运行结果
    在这里插入图片描述

冲刺转弯时角色抖动问题

  • 我们调整一下动画蓝图即可,设置一个区间,比如在0-100走路,500-600奔跑,700-900冲刺,这样设置区别可以避免插值
    在这里插入图片描述

MainPlayer.h

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

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "MainPlayer.generated.h"

//声明移动状态枚举
UENUM(BlueprintType)
enum class EPlayerMovementStatus :uint8
{
	EPMS_Normal UMETA(DisplayName = "Normal"),
	EPMS_Sprinting UMETA(DisplayName = "Sprinting"),
	EPMS_Dead UMETA(DisplayName = "Dead")
};

UENUM(BlueprintType)
enum class EPlayerStaminaStatus :uint8
{
	EPSS_Normal UMETA(DisplayName = "Normal"),
	EPSS_Exhausted UMETA(DisplayName = "Exhausted"),
	EPSS_ExhaustedRecovering UMETA(DisplayName = "ExhaustedRecovering")
};

UCLASS()
class UEGAME_API AMainPlayer : public ACharacter
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	AMainPlayer();

	//新建一个SpringArm
	UPROPERTY(visibleAnywhere,BlueprintReadOnly)
	class USpringArmComponent* SpringArm;
	//新建一个Camera
	UPROPERTY(visibleAnywhere, BlueprintReadOnly)
	class UCameraComponent* FollowCamera;

	
	float BaseTurnRate;		//使用键盘X转向的速率
	float BaseLookUpRate;	//使用键盘Y转向的速率

	//主角状态
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Playe State")
	float Health;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Playe State")
	float MaxHealth;
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Playe State")
	float Stamina;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Playe State")
	float MaxStamina;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Playe State")
	float StaminaConsumeRate;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Playe State", meta = (ClampMin = 0, ClampMax = 1))
	float ExhaustedStamina;
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Playe State")
	int Coins;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Player State")
	float RunningSpeed;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Player State")
	float SprintSpeed;
	UPROPERTY(VisibleAnywhere,BlueprintReadWrite,Category="Player State")
	EPlayerMovementStatus MovementStatus;
	UPROPERTY(VisibleAnywhere,BlueprintReadWrite,Category="Player State")
	EPlayerStaminaStatus StaminaStatus;

	bool bLeftShiftDown;

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

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

	//重新Character类中的Jump方法
	void Jump() override;

	void MoveForward(float value);
	void MoveRight(float value);

	void Turn(float Value);
	void LookUp(float Value);

	void TurnRate(float Rate);
	void LookUpRate(float Rate);

	//改变状态
	UFUNCTION(BlueprintCallable,Category="Player|State")
	void AddHealth(float value);
	UFUNCTION(BlueprintCallable, Category = "Player|State")
	void AddStamina(float value);
	UFUNCTION(BlueprintCallable, Category = "Player|State")
	void AddCoin(float value);

	//重写TakeDamage方法
	float TakeDamage(float Damage, struct FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser) override;

	//短小精悍
	FORCEINLINE void LeftShiftDown() { bLeftShiftDown = true; }
	FORCEINLINE void LeftShiftUp() { bLeftShiftDown = false; }

	void SetMovementStatus(EPlayerMovementStatus Status);
};

MainPlayer.cpp

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


#include "MainPlayer.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/InputComponent.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/CharacterMovementComponent.h"

// Sets default values
AMainPlayer::AMainPlayer()
{
 	// Set this character to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
	SpringArm->SetupAttachment(GetRootComponent());
	//设置SPringArm无碰撞臂长
	SpringArm->TargetArmLength = 600.f;
	SpringArm->bUsePawnControlRotation = true;//硬编码SpringArm继承controlller旋转为真

	FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
	FollowCamera->SetupAttachment(SpringArm, NAME_None);
	FollowCamera->bUsePawnControlRotation = false;//硬编码FollowCamera继承controlller旋转为假

	//设置胶囊体的默认宽高
	GetCapsuleComponent()->SetCapsuleSize(35.f, 100.f);

	//对Character的Pawn进行硬编码
	bUseControllerRotationPitch = false;
	bUseControllerRotationYaw = false;
	bUseControllerRotationRoll = false;

	//硬编码orient Rotation to Movement,给个默认转向速率
	GetCharacterMovement()->bOrientRotationToMovement = true;
	GetCharacterMovement()->RotationRate = FRotator(0.f, 500.f, 0.f);

	//设置跳跃初始值与在空中的坠落时横向运动控制量
	GetCharacterMovement()->JumpZVelocity = 600.f;
	GetCharacterMovement()->AirControl = 0.15f;

	//给键盘控制转向的速率变量赋初值
	BaseTurnRate = 21.f;
	BaseLookUpRate = 21.f;

	//初始化角色状态
	MaxHealth = 100.f;
	Health = MaxHealth;
	MaxStamina = 200.f;
	Stamina = MaxStamina;
	StaminaConsumeRate = 20.f;
	ExhaustedStamina = 0.167f;
	Coins = 0;
	RunningSpeed = 600.f;
	SprintSpeed = 900.f;
	MovementStatus = EPlayerMovementStatus::EPMS_Normal;
	StaminaStatus = EPlayerStaminaStatus::EPSS_Normal;

	//默认没有按下shift
	bLeftShiftDown = false;
}

// Called when the game starts or when spawned
void AMainPlayer::BeginPlay()
{
	Super::BeginPlay();
}

// Called every frame
void AMainPlayer::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	switch (StaminaStatus)
	{
	case EPlayerStaminaStatus::EPSS_Normal:
		//当Shift按下
		if (bLeftShiftDown)
		{
			if (Stamina - StaminaConsumeRate * DeltaTime <= MaxStamina * ExhaustedStamina)
			{
				StaminaStatus = EPlayerStaminaStatus::EPSS_Exhausted;
			}
			//无论是不是精疲力尽状态都要减去当前帧冲刺消耗的耐力
			Stamina -= StaminaConsumeRate * DeltaTime;
			SetMovementStatus(EPlayerMovementStatus::EPMS_Sprinting);
		}
		else
		{
			//当Shift没有按下,恢复耐力
			Stamina = FMath::Clamp(Stamina + StaminaConsumeRate * DeltaTime, 0.f, MaxStamina);
			SetMovementStatus(EPlayerMovementStatus::EPMS_Normal);
		}
		break;
	case EPlayerStaminaStatus::EPSS_Exhausted:
		if (bLeftShiftDown)
		{
			//如果耐力已经为0
			if (Stamina - StaminaConsumeRate * DeltaTime <= 0.f)
			{
				//么我们需要内部编码把shift抬起,此时StaminaStatus状态转换为ExhaustedRecovering状态,然后设置移动状态为Normal
				LeftShiftUp();
				StaminaStatus = EPlayerStaminaStatus::EPSS_ExhaustedRecovering;	
				SetMovementStatus(EPlayerMovementStatus::EPMS_Normal);
			}
			else
			{
				Stamina -= StaminaConsumeRate * DeltaTime;
			}
		}
		else
		{
			StaminaStatus = EPlayerStaminaStatus::EPSS_ExhaustedRecovering;
			Stamina = FMath::Clamp(Stamina + StaminaConsumeRate * DeltaTime, 0.f, MaxStamina);
			SetMovementStatus(EPlayerMovementStatus::EPMS_Normal);
		}
		break;
	case EPlayerStaminaStatus::EPSS_ExhaustedRecovering:
		//当恢复大于疲劳区时,StaminaStatus状态为Normal
		if (Stamina + StaminaConsumeRate * DeltaTime >= MaxStamina * ExhaustedStamina)
		{
			StaminaStatus = EPlayerStaminaStatus::EPSS_Normal;
		}
		//这状态值肯定是加定了
		Stamina += StaminaConsumeRate * DeltaTime;

		//抬起shift
		LeftShiftUp();
		SetMovementStatus(EPlayerMovementStatus::EPMS_Normal);
		break;
	default:
		break;
	}
}

// Called to bind functionality to input
void AMainPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
	//检查PlayerInputComponent指针,check函数只能在这使用
	check(PlayerInputComponent);

	//绑定跳跃轴映射事件
	PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &AMainPlayer::Jump);//按下空格
	PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping);//抬起空格

	PlayerInputComponent->BindAction("Sprint", IE_Pressed, this, &AMainPlayer::LeftShiftDown);//按下shift
	PlayerInputComponent->BindAction("Sprint", IE_Released, this, &AMainPlayer::LeftShiftUp);//抬起shift


	//绑定移动轴映射事件
	PlayerInputComponent->BindAxis("MoveForward", this, &AMainPlayer::MoveForward);
	PlayerInputComponent->BindAxis("MoveRight", this, &AMainPlayer::MoveRight);

	//绑定Controller控制器去管理视角旋转
	PlayerInputComponent->BindAxis("Turn", this, &AMainPlayer::Turn);
	PlayerInputComponent->BindAxis("LookUp", this, &AMainPlayer::LookUp);

	//绑定键盘鼠标轴映射事件
	PlayerInputComponent->BindAxis("TurnRate", this, &AMainPlayer::TurnRate);
	PlayerInputComponent->BindAxis("LookUpRate", this, &AMainPlayer::LookUpRate);

}

void AMainPlayer::Jump()
{
	//继承父类的方法
	Super::Jump();
}

void AMainPlayer::MoveForward(float value)
{
	if (Controller != nullptr && value != 0.f)
	{
		//获取到Control旋转
		FRotator Rotation = Controller->GetControlRotation();
		//转向只关注水平Yaw方向,因此置0防止影响
		FRotator YowRotation = FRotator(0.0f, Rotation.Yaw, 0.0f);
		//获取相机(鼠标控制器的朝向),并且朝这个轴的方向移动
		FVector Direction = FRotationMatrix(YowRotation).GetUnitAxis(EAxis::X);
		AddMovementInput(Direction, value);
	}

}

void AMainPlayer::MoveRight(float value)
{
	if (Controller != nullptr && value != 0.f)
	{
		//获取到Controller旋转
		FRotator Rotation = Controller->GetControlRotation();
		//转向只关注水平Yaw方向,因此置0防止影响
		FRotator YowRotation = FRotator(0.0f, Rotation.Yaw, 0.0f);
		//获取相机(鼠标控制器的朝向),并且朝这个轴的方向移动
		FVector Direction = FRotationMatrix(YowRotation).GetUnitAxis(EAxis::Y);
		AddMovementInput(Direction, value);
	}
}

void AMainPlayer::Turn(float Value)
{
	if (Value != 0.f)
	{
		AddControllerYawInput(Value);
	}
	
}

void AMainPlayer::LookUp(float Value)
{
	//UE_LOG(LogTemp, Warning, TEXT("%f"), GetControlRotation().Pitch);

	//控制视角
	if (GetControlRotation().Pitch < 270.f && GetControlRotation().Pitch >180.f && Value > 0.f)
	{
		return;
	}
	else if (GetControlRotation().Pitch < 180.f && GetControlRotation().Pitch >45.f && Value < 0.f)
	{
		return;
	}
	AddControllerPitchInput(Value);
}

void AMainPlayer::TurnRate(float Rate)
{
	//要乘以一个DeltaTime这样就可以避免高帧底帧差值问题
	float Value = Rate * BaseTurnRate * GetWorld()->GetDeltaSeconds();
	if (Value != 0.f)
	{
		AddControllerYawInput(Value);
	}
}

void AMainPlayer::LookUpRate(float Rate)
{
	//要乘以一个DeltaTime这样就可以避免高帧底帧差值问题
	float Value = Rate * BaseLookUpRate * GetWorld()->GetDeltaSeconds();
	//控制视角
	if (GetControlRotation().Pitch < 270.f && GetControlRotation().Pitch >180.f && Value > 0.f)
	{
		return;
	}
	else if (GetControlRotation().Pitch < 180.f && GetControlRotation().Pitch >45.f && Value < 0.f)
	{
		return;
	}
	AddControllerPitchInput(Value);

}

void AMainPlayer::AddHealth(float value)
{
	Health = FMath::Clamp(Health + value, 0.f, MaxHealth);
}

void AMainPlayer::AddStamina(float value)
{
	Stamina = FMath::Clamp(Stamina + value, 0.f, MaxStamina);
}

void AMainPlayer::AddCoin(float value)
{
	Coins += value;
}

float AMainPlayer::TakeDamage(float Damage, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
	if (Health - Damage <= 0.f)
	{
		Health = FMath::Clamp(Health - Damage, 0.f, MaxHealth);
		//TODO Die();
	}
	else
	{
		Health -= Damage;
	}
	return Health;
}

void AMainPlayer::SetMovementStatus(EPlayerMovementStatus Status)
{
	MovementStatus = Status;
	//切换状态的时候改变移动速度
	switch (MovementStatus)
	{
	case EPlayerMovementStatus::EPMS_Sprinting:
		GetCharacterMovement()->MaxWalkSpeed = SprintSpeed;
		break;
	default:
		GetCharacterMovement()->MaxWalkSpeed = RunningSpeed;
		break;
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/152104.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

最新PS 2024 虎标正式版来啦,附带AI神经滤镜(支持win/mac)

软件简介 文件名称 PS2024 虎标正式版 支持系统 windows、Mac 获取方式 文章底部 分享形式 百度网盘 小伙伴们&#xff0c;下午好&#xff01;今天给大家的是PS 2024 25.0虎标正式版。 PS 2024 25.0 正式版介绍 经历了多次Photoshop 2023 Beta 测试之后&#xff0c;今天…

01.智慧商城——项目介绍与初始化

智慧商城 - 授课大纲 接口文档&#xff1a;https://apifox.com/apidoc/shared-12ab6b18-adc2-444c-ad11-0e60f5693f66/doc-2221080 演示地址&#xff1a;http://cba.itlike.com/public/mweb/#/ 01. 项目功能演示 1.明确功能模块 启动准备好的代码&#xff0c;演示移动端面…

Mahony 滤波算法参数自动调节方法 11

Mahony 滤波算法参数自动调节方法 1. 基于无阻尼自由频率设计设置Kp、Ki参数[^1]2.基于时间常数设置Kp&#xff0c; Ki参数[^2][^3] 1. 基于无阻尼自由频率设计设置Kp、Ki参数1 2.基于时间常数设置Kp&#xff0c; Ki参数23 Gain-Scheduled Complementary Filter Design for a M…

Vue3:给表格的单元格增加超链接功能(点击单元格可以跳转到新的页面)

一、目的 在Vue3项目中&#xff0c;给表格某个字段下的全部单元格添加超链接功能&#xff0c;点击对应的单元格可以进入对应的页面 二、定义单元格内容 使用ElementPlus的el-table组件来实现表格 1、代码 <template> <el-table :data"dataAll"> &…

成都瀚网科技有限公司抖音带货正规么

近年来&#xff0c;随着抖音等短视频平台的兴起&#xff0c;越来越多的企业和个人选择在抖音上进行带货。成都瀚网科技有限公司&#xff08;以下简称瀚网科技&#xff09;也提供抖音带货服务&#xff0c;那么&#xff0c;瀚网科技的抖音带货正规吗&#xff1f; 首先&#xff0c…

达索系统SOLIDWORKS Electrical机电一体化协同设计

一秒读懂 SOLIDWORKS Electrical 问题点 电气、机械设计各自为政数据传递困难&#xff0c;存在设计错误 CHALLENGE电气设计面临挑战 问题点&#xff1a;电气、机械设计各自为政数据传递困难&#xff0c;存在设计错误 原理图绘制完毕后&#xff0c;再绘制接线图,人工统计BOM&a…

【新版Bing】集成chatGpt4的搜索引擎

具体步骤 打开Chrome浏览器&#xff0c;进入无痕模式。 访问bing.com/new;&#xff08;需要kexueshangwang&#xff0c;评论区有具体方法&#xff09; 注册账号或使用老帐号登录&#xff08;我是直接用的老帐号&#xff09; 注册界面 登录成功 点击在Microsoft Edge中…

windows usbip(瑞芯微windows开发)

Rockchip RK3588 windows开发 安装usbipd 除usbipd之外&#xff0c;还有一个usbip仓库可以参考usbip-win&#xff0c;但是相对麻烦一些 windows install winget install usbipdShare Devices usbipd --help usbipd list usbipd bind --busid<BUSID>Remote Connectin…

金融行业如何数字化转型?_光点科技

金融行业的数字化转型涉及技术创新的引入、客户体验的改善、内部流程的优化、安全和合规性的加强以及员工技能和企业文化的转变。 技术创新 包括云计算、人工智能、大数据分析和区块链技术的采用。云计算增强数据处理的灵活性&#xff0c;AI和机器学习在风险评估和欺诈检测方面…

YOLOv8-Seg改进: 捕捉空间上的局部关系和全局关系的CoordAttention注意力 | 分割注意力系列篇

🚀🚀🚀本文改进:CoordAttention注意力,引入到YOLOv8-seg,CoordAttention在计算注意力时,不仅会考虑输入的特征信息,还会考虑每个像素点的位置信息,从而更好地捕捉空间上的局部关系和全局关系。 🚀🚀🚀Context Aggregation小目标分割&复杂场景首选,实现…

二叉树的创建与遍历

目录 前言&#xff1a; 二叉树的概念与结构 二叉树的链式存储 二叉树的创建 二叉树的销毁 二叉树结点个数计算 二叉树叶子结点个数计算 二叉树第k层节点个数的计算 二叉树高度的计算 二叉树查找值为x的结点 二叉树的遍历 二叉树的前序遍历 二叉树的中序遍历 二叉树…

还在为忘记BIOS密码担心?至少有五种方法可以重置或删除BIOS密码

忘记密码是一个我们都非常熟悉的问题。虽然在大多数情况下,只需单击“忘记密码”选项,然后按照几个简单的步骤即可恢复访问权限,但情况并非总是如此。忘记BIOS密码(通常为避免进入BIOS设置或避免个人计算机启动而设置的密码)意味着你将无法完全启动系统。 幸运的是,就像…

oracle初步学习

先了解几个登录的方式 1.直接在cmd输入 sqlplus scott/tiger //登陆普通用户scott 2.输入sqlplus sys as sysdba 之后 紧接着让你输入口令&#xff0c;直接输入sys就行了 3.先输入sqlplus/nolog 在输入conn system/managerorcl 先在cmd窗口输入sqlplus/nolog &#x…

免费分享一套基于Springboot+Vue的医院管理系统,挺漂亮的

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的SpringbootVue的医院管理系统&#xff0c;分享下哈。 项目视频演示 【免费】springbootvue医院管理系统 Java毕业设计_哔哩哔哩_bilibili【免费】springbootvue医院管理系统 Java毕业设计项目来自互联网&a…

使用IDEA 将Eclipse java工程转为maven格式

使用IDEA 将Eclipse java工程转为maven格式 ①使用idea打开项目&#xff0c;在项目根目录下右键选择 Add Framework Support 选择 maven &#xff0c;引入maven ②找到项目中的.classpath文件或者lib目录 根据.classpath文件或者lib目录中列举的jar包名&#xff0c;将其依次手…

【架构师】的修炼之道都需要学习哪些?看看这些就够了

&#x1f468;‍&#x1f393;博主简介 &#x1f3c5;云计算领域优质创作者   &#x1f3c5;华为云开发者社区专家博主   &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社区&#xff1a;运维交流社区 欢迎大家的加入&#xff01; &#x1f40b; 希望大家多多支…

React父组件怎么调用子组件的方法

调用方法&#xff1a;1、类组件中的调用可以利用React.createRef()、ref的函数式声明或props自定义onRef属性来实现&#xff1b;2、函数组件、Hook组件中的调用可以利用useImperativeHandle或forwardRef抛出子组件ref来实现。 【程序员必备开发工具推荐】Apifox一款免费API管理…

nginx关闭重启和配置检查

优雅关闭Nginx 找出nginx的进程号&#xff1a;ps -ef | grep nginx 执行命令&#xff1a;kill -QUIT 主pid 注意&#xff1a; 其中pid是主进程号的pid&#xff08;master process&#xff09;&#xff0c;其他为子进程pid&#xff08;worker process&#xff09; 这种关闭方式…

计算机视觉基础(9)——相机标定与对极几何

前言 本节我们将学习相机标定和对极几何两部分的内容。 在相机标定部分&#xff0c;我们将学习直接线性变换&#xff08;Direct Linear Transform, DL&#xff09;,张正友标定法&#xff08;Zhang’s Method&#xff09;和 Perspective-n-Point (PnP) 这三种方法。 在对极几何部…

赢麻了……腾讯1面核心9问,小伙伴过了提42W offer

说在前面 在40岁老架构师尼恩的&#xff08;50&#xff09;读者社群中&#xff0c;经常有小伙伴&#xff0c;需要面试腾讯、美团、京东、阿里、 百度、头条等大厂。 下面是一个小伙伴成功拿到通过了腾讯面试&#xff0c;并且最终拿到offer&#xff0c;一毕业就年薪42W&#x…