虚幻C++ day5

news/2025/3/31 13:40:50/

角色状态的常见机制

创建角色状态设置到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 propertiesAMainPlayer();//新建一个SpringArmUPROPERTY(visibleAnywhere,BlueprintReadOnly)class USpringArmComponent* SpringArm;//新建一个CameraUPROPERTY(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 spawnedvirtual void BeginPlay() override;public:	// Called every framevirtual void Tick(float DeltaTime) override;// Called to bind functionality to inputvirtual 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 propertiesAInteroperableItem();//球形触发器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 spawnedvirtual void BeginPlay() override;public:	// Called every framevirtual 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 typeTSubclassOf<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 thisconst float ActualDamage = Super::TakeDamage(Damage, DamageEvent, EventInstigator, DamageCauser);// respond to the damageif (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);//按下shiftPlayerInputComponent->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){//如果耐力已经为0if (Stamina - StaminaConsumeRate * DeltaTime <= 0.f){//么我们需要内部编码把shift抬起,此时StaminaStatus状态转换为ExhaustedRecovering状态,然后设置移动状态为NormalLeftShiftUp();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状态为Normalif (Stamina + StaminaConsumeRate * DeltaTime >= MaxStamina * ExhaustedStamina){StaminaStatus = EPlayerStaminaStatus::EPSS_Normal;}//这状态值肯定是加定了Stamina += StaminaConsumeRate * DeltaTime;//抬起shiftLeftShiftUp();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 propertiesAMainPlayer();//新建一个SpringArmUPROPERTY(visibleAnywhere,BlueprintReadOnly)class USpringArmComponent* SpringArm;//新建一个CameraUPROPERTY(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 spawnedvirtual void BeginPlay() override;public:	// Called every framevirtual void Tick(float DeltaTime) override;// Called to bind functionality to inputvirtual 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;//默认没有按下shiftbLeftShiftDown = 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){//如果耐力已经为0if (Stamina - StaminaConsumeRate * DeltaTime <= 0.f){//么我们需要内部编码把shift抬起,此时StaminaStatus状态转换为ExhaustedRecovering状态,然后设置移动状态为NormalLeftShiftUp();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状态为Normalif (Stamina + StaminaConsumeRate * DeltaTime >= MaxStamina * ExhaustedStamina){StaminaStatus = EPlayerStaminaStatus::EPSS_Normal;}//这状态值肯定是加定了Stamina += StaminaConsumeRate * DeltaTime;//抬起shiftLeftShiftUp();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);//按下shiftPlayerInputComponent->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;}
}

http://www.ppmy.cn/news/1227407.html

相关文章

音视频项目—基于FFmpeg和SDL的音视频播放器解析(九)

介绍 在本系列&#xff0c;我打算花大篇幅讲解我的 gitee 项目音视频播放器&#xff0c;在这个项目&#xff0c;您可以学到音视频解封装&#xff0c;解码&#xff0c;SDL渲染相关的知识。您对源代码感兴趣的话&#xff0c;请查看基于FFmpeg和SDL的音视频播放器 如果您不理解本…

ffmpeg知识点整理

使用FFmepg进行视频转码、视频格式转换、图片提取等&#xff01;_ffmepg -c:v-CSDN博客 中文文档&#xff1a; ffmpeg 中文手册 (beandrewang.github.io) 笔记&#xff1a; 通用规则是&#xff0c;所有选项作用于其后边的第一个文件。因此&#xff0c;顺序是非常重要的&…

AUTODL云服务器使用大致步骤(适合本人版)

(一)在官网上创建一个服务器 (二)远程连接指令&#xff1a; 改为&#xff1a; (三)连接后&#xff0c;可在中进行代码运行 输入一些指令 python ......

038、语义分割

之——介绍与数据集 杂谈 语义分割&#xff0c;语义分割(Semantic Segmentation)方法-CSDN博客&#xff1a; 语义分割是计算机视觉领域的一项重要任务&#xff0c;旨在将图像中的每个像素分配到其对应的语义类别中。与物体检测或图像分类不同&#xff0c;语义分割不仅要识别图像…

iOS_折叠展开 FoldTextView

1. 显示效果 Test1&#xff1a;直接使用&#xff1a; Test2&#xff1a;在 cell 里使用&#xff1a; 2. 使用 2.1 直接使用 // 1.1 init view private lazy var mooFoldTextView: MOOFoldTextView {let view MOOFoldTextView(frame: .zero)view.backgroundColor .cyanvie…

Nodejs中net模块多次Socket.setTimeout无法覆盖之前函数,导致叠加执行问题解决

Hi, I’m Shendi Nodejs中net模块多次Socket.setTimeout无法覆盖之前函数&#xff0c;导致叠加执行问题解决 问题描述 在 Nodejs 中&#xff0c;net 模块的 Socket 的 setTimeout 函数是设置超时时间&#xff0c;如果多次设置&#xff0c;超时时间会是最后一次的时间&#xff…

pm2在Windows环境中的使用

pm2 进程管理工具可以Windows操作系统上运行&#xff0c;当一台Windows电脑上需要运行多个进程时&#xff0c;或者运维时需要运行多个进程以提供服务时。可以使用pm2&#xff0c;而不再是使用脚本。 1. 使用PM2管理进程 1.1. 启动PM2项目 1.1.1. 直接启动项目 参数说明&…

使用Spring Boot结合JustAuth实现支付宝、微信、微博扫码登录功能

使用Spring Boot结合JustAuth实现支付宝、微信、微博扫码登录功能 在使用Spring Boot结合JustAuth实现支付宝、微信、微博扫码登录功能之前&#xff0c;需要先确保已经配置好Spring Boot项目&#xff0c;并且添加了JustAuth的依赖。你可以在项目的pom.xml文件中添加如下依赖&a…