UEC++ day6

简易战斗系统

删除替换父类组件

  • 现在需要添加剑的组件,但是一般来说附着到蒙皮骨骼的东西,也是蒙皮骨骼,所以我们可以新建一个类重新编写也可以直接继承Interoperable类然后不管UStaticMeshComponent这个组件,新建USkeletalMeshComponent再或者直接更改UStaticMeshComponent,因为USkeletalMeshComponent与UStaticMeshComponent都继承自UMeshComponent类,我们把父类Interoperable类中的UStaticMeshComponent换成UMeshComponent类就可以在继承Interoperable的类中修改UStaticMeshComponent为USkeletalMeshComponent。
  • 我们在这里介绍第三种方法
  • 首先在interoperable类中更改UStaticMeshComponent为UMeshComponent。
    在这里插入图片描述
  • 然后在Weapon子类里面销毁DisplayMesh,重新创建DisplayMesh为USkeletalMeshComponent类型,注意这里的TEXT标识换一下名字,否则UE可能会崩溃,然后添加到根组件上
    在这里插入图片描述

添加武器外观与粒子效果

  • 创建Weapon蓝图添加SkeletonMesh与particle即可
    在这里插入图片描述

武器类添加需求

  • 毫无疑问父类的碰撞重叠那两个函数需要基础过来使用,然后定义剑被拾起的声音组件,是否保留被装备后的粒子效果bool变量,以及两个函数用来检测武器是否被拾起
  • Weapon.h
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

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

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

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon|Sound")
	class USoundCue* OnEquipSound;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon|Particle")
	bool bOnEquipParticle;

protected:
	virtual void BeginPlay()override;

public:
	virtual void OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)override;
	virtual void OnOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)override;
	
	void Equip();
	void UnEuip();
};
  • Weapon.cpp
// Fill out your copyright notice in the Description page of Project Settings.


#include "WeaponItem.h"
#include "Components/SkeletalMeshComponent.h"
AWeaponItem::AWeaponItem()
{
	//销毁
	if (DisplayMesh)
	{
		DisplayMesh->DestroyComponent();
		//UE_LOG(LogTemp, Warning, TEXT("delete succeed"));
	}
	else
	{
		//UE_LOG(LogTemp, Warning, TEXT("fail to delete"));
	}
	
	//因为TEXT具有唯一性,我们不知道什么时候销毁原UStaticMeshComponent* DisplayMesh;所以这里TEXT进行一下区分
	DisplayMesh=CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("DisplaySkeletalMesh"));
	DisplayMesh->SetupAttachment(GetRootComponent());
}

void AWeaponItem::BeginPlay()
{

}

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

}

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

}

void AWeaponItem::Equip()
{

}

void AWeaponItem::UnEuip()
{

}

给右手添加武器

  • 在人物骨骼里面新建一个socket,然后把武器插入预览调整到合适的大小及位置
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 名字最好不要有英文,因为到时候编码会用到这个插槽
    在这里插入图片描述

拾取武器机制

通知角色当前的靠近的武器

  • 设置一下轴事件映射
    在这里插入图片描述
  • 目的:当玩家走入触发剑的触发器,按F可以拾取这把剑,走到其他剑触发器时,可以交换两把剑
  • 我们需要在MainPlayer类中新建一个bool变量用来检测角色是否拿着武器,新建两个WeaponItem指针来标记当前装备的剑,和是否重叠了别的剑,然后在WeaponItem类中新建一个枚举类型用来标识可拾取与已经拾取状态,新建一个枚举类型变量用来表明状态
  • MainPlayer.h
	//检测是否持剑
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapon")
	bool bIsWeapon;

	//正在装备的武器
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapon")
	class AWeaponItem* EquipWeapon;
	//正在重叠的武器
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapon")
	AWeaponItem* OverlapWeapon;
  • Weapon.h
UENUM(BlueprintType)
enum class EWeaponState :uint8
{
	EWS_CanPickUp UMETA(DisplayName = "CanPickUp"),
	EWS_Equip UMETA(DisplayName = "Equip")
};
	//武器拾取状态
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapon")
	EWeaponState WeaponState;
  • 首先初始化武器是可拾取的状态,然后在开始重叠事件里面判断当前武器是否可拾取然后是不是角色进入触发器范围,如果是就告诉角色的正在重叠的武器是当前武器,在结束重叠事件里面判断,当前角色是否进入触发器范围以及当前的拾取的武器是不是当前武器,如果是就将重叠武器的指针变为空指针。
  • Weapon.cpp
AWeaponItem::AWeaponItem()
{
	//销毁
	if (DisplayMesh)
	{
		DisplayMesh->DestroyComponent();
		//UE_LOG(LogTemp, Warning, TEXT("delete succeed"));
	}
	else
	{
		//UE_LOG(LogTemp, Warning, TEXT("fail to delete"));
	}
	
	//因为TEXT具有唯一性,我们不知道什么时候销毁原UStaticMeshComponent* DisplayMesh;所以这里TEXT进行一下区分
	DisplayMesh=CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("DisplaySkeletalMesh"));
	DisplayMesh->SetupAttachment(GetRootComponent());

	//拾取武器后粒子效果默认关闭
	bOnEquipParticle = false;
	//默认状态武器是可拾取的
	WeaponState = EWeaponState::EWS_CanPickUp;
}

void AWeaponItem::BeginPlay()
{
	Super::BeginPlay();
}

void AWeaponItem::OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	Super::OnOverlapBegin(OverlappedComponent, OtherActor, OtherComp, OtherBodyIndex, bFromSweep, SweepResult);
	
	if (OtherActor && WeaponState == EWeaponState::EWS_CanPickUp)
	{
		AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
		if (Player)
		{
			//告诉角色正在重叠的武器是当前武器
			Player->OverlapWeapon = this;
		}
	}
}

void AWeaponItem::OnOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
	Super::OnOverlapEnd(OverlappedComponent, OtherActor, OtherComp, OtherBodyIndex);
	
	if (OtherActor)
	{
		AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
		//判断一开始是否拾起的武器是当前武器
		if (Player && Player->OverlapWeapon == this)
		{
			//告诉角色离开了武器触发器
			Player->OverlapWeapon = nullptr;
		}
	}
}

武器与角色的交互事件绑定

  • 新建一个与F事件绑定的函数接口
  • 绑定映射F事件
	PlayerInputComponent->BindAction("Interact", IE_Pressed, this, &AMainPlayer::InteractKeyDown);//按下F
  • 把WeaponItem类中的两个装备卸载函数传入MainPlayer指针类型参数
	void Equip(class AMainPlayer* Player);
	void UnEuip(AMainPlayer* Player);
  • 编写角色拾取武器逻辑
void AMainPlayer::InteractKeyDown()
{
	if (OverlapWeapon)
	{
		if (EquipWeapon)
		{
			//交换武器
			EquipWeapon->UnEuip(this);
			OverlapWeapon->Equip(this);
		}
		else
		{
			//装备武器
			OverlapWeapon->Equip(this);
		}
	}
	else
	{
		if (EquipWeapon)
		{
			//卸载武器
			EquipWeapon->UnEuip(this);
		}
	}
}

装备武器逻辑

  • 在Equip函数里面实现装备武器逻辑,我们首先将枚举状态改为已经装备武器,然后获取到右手的Socket,然后将武器添加上去,将bIsWeapon改为true,EquipWeapon改为true,OverlapWeapon为nullptr,播放音乐与关闭粒子组件
  • 头文件:#include "Engine/SkeletalMeshSocket.h"const USkeletalMeshSocket* RightHandSocker = Player->GetMesh()->GetSocketByName(TEXT("RightHandSocker"));
void AWeaponItem::Equip(AMainPlayer* Player)
{
	if (Player)
	{
		//已装备武器
		WeaponState = EWeaponState::EWS_Equip;
		//获取Player的Socket
		const USkeletalMeshSocket* RightHandSocker = Player->GetMesh()->GetSocketByName(TEXT("RightHandSocker"));
		if (RightHandSocker)
		{
			//让武器附属到Socket上
			RightHandSocker->AttachActor(this, Player->GetMesh());
			Player->bIsWeapon = true;
			Player->EquipWeapon = this;
			Player->OverlapWeapon = nullptr;

			bRotate = false;//武器旋转关闭

			if (OnEquipSound)
			{
				//播放音乐
				UGameplayStatics::PlaySound2D(this, OnEquipSound);
			}
			if (!bOnEquipParticle)
			{
				//关闭粒子组件
				ParticleEffectsComponent->Deactivate();
				
			}
		}
	}
}
  • 回顾一下硬编码声音
  • 头文件 #include "UObject/ConstructorHelpers.h"
	static ConstructorHelpers::FObjectFinder<USoundCue> SoundCueAsset(TEXT("SoundCue'/Game/Assets/Audios/Blade_Cue.Blade_Cue'"));
	if (SoundCueAsset.Succeeded())
	{
		OnEquipSound = SoundCueAsset.Object;
	}

卸载武器逻辑

  • 我们首先需要明白的逻辑,当我在跳跃状态肯定是不能丢弃武器的也不能拾取武器,所以我们在判断时需要加上角色是否跳跃中,然后将其枚举变量武器状态切换成可装备武器状态,主角正在装备武器bool变量为false,装备武器为nullptr,然后我们需要判断武器重叠状态是否为空,如果是就将此时的重叠状态设置为Player自己,最后从Socket中丢弃武器,设置武器的旋转与大小位置
  • DetachFromActor(FDetachmentTransformRules::KeepWorldTransform);:分离当前WeaponItem Socket
void AWeaponItem::Equip(AMainPlayer* Player)
{
	if (Player && !Player->GetMovementComponent()->IsFalling())
	{
		//已装备武器
		WeaponState = EWeaponState::EWS_Equip;
		//获取Player的Socket
		const USkeletalMeshSocket* RightHandSocker = Player->GetMesh()->GetSocketByName(TEXT("RightHandSocket"));
		if (RightHandSocker)
		{
			//让武器附属到Socket上
			RightHandSocker->AttachActor(this, Player->GetMesh());
			Player->bIsWeapon = true;
			Player->EquipWeapon = this;
			Player->OverlapWeapon = nullptr;
			bRotate = false;//武器旋转关闭

			if (OnEquipSound)
			{
				//播放音乐
				UGameplayStatics::PlaySound2D(this, OnEquipSound);
			}
			//if (!bOnEquipParticle)
			//{
			//	//关闭粒子组件
			//	ParticleEffectsComponent->Deactivate();
			//	
			//}
		}
	}
}

void AWeaponItem::UnEuip(AMainPlayer* Player)
{
	if (Player && !Player->GetMovementComponent()->IsFalling())
	{
		WeaponState = EWeaponState::EWS_CanPickUp;
		Player->bIsWeapon = false;
		Player->EquipWeapon = nullptr;
		if (Player->OverlapWeapon == nullptr)
		{
			Player->OverlapWeapon = this;
		}

		//分离当前WeaponItem Socket
		DetachFromActor(FDetachmentTransformRules::KeepWorldTransform);
		SetActorRotation(FRotator(0.f));
		SetActorScale3D(FVector(1.f));
		bRotate = true;
	}
}

给武器添加PhysicsAsset与动态切换武器碰撞

  • 我们可以创建三把剑的PhysicsAsset设置碰撞胶囊
    在这里插入图片描述
    在这里插入图片描述
  • 新建两个函数用来激活碰撞与关闭碰撞
  • 注意虚幻4.23后的更新
    • PhysicsOnly:刚体、约束
    • QueryOnly:扫描、重叠、移动
    • QueryAndPhysics:上述全部
	//动态切换碰撞
	void ActiveDisplayMeshCollision();
	void DeactiveDisplayMeshCollision();
	
void AWeaponItem::ActiveDisplayMeshCollision()
{
	DisplayMesh->SetCollisionEnabled(ECollisionEnabled::PhysicsOnly);
	DisplayMesh->SetCollisionObjectType(ECollisionChannel::ECC_WorldStatic);
	DisplayMesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
	DisplayMesh->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Block);
}

void AWeaponItem::DeactiveDisplayMeshCollision()
{
	DisplayMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}

切换持剑移动动画

  • 新建一个BlendSpace1D,添加上动画
    在这里插入图片描述
  • 然后在动画蓝图里面移动的状态机嵌套状态机添加切换持剑移动与不持剑移动
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

蒙太奇插入攻击片段

  • 蒙太奇:可以理解为剪辑,我们可以在状态机里面插入蒙太奇去执行攻击动画
  • 创建蒙太奇,将攻击片段插入好
    在这里插入图片描述

在这里插入图片描述

使用Notifies添加挥剑音效

在这里插入图片描述
在这里插入图片描述

使用Notifies添加拖尾粒子特效

  • 注意这个粒子特效必须要支持拖尾,方可添加,不是任何一种粒子特效都可以添加上的
  • 添加两个插槽用来播放拖尾粒子的位置
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

分析攻击所需逻辑

  • 我们得新建三个变量,一个bool变量用来标识位是否攻击键按下,一个bool变量用来判断是否要攻击在蓝图中方便使用,和蒙太奇的引用变量,然后按键事件得绑定,需要两个函数用来绑定攻击按下与抬起,然后是开始攻击函数与攻击结束函数用来抒写主要攻击动画播放逻辑,攻击结束我们不知道什么时候结束,所以我们要把攻击结束函数添加反射在蓝图中去拿到攻击结束时间
	bool bAttackKeyDown;//是否按下攻击键

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Animation")
	bool bIsAttacking;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Animation")
	class UAnimMontage* AttackMontage;

//-------------------------------------------------------------------------
void AttackKeyDown();

FORCEINLINE void AttackKeyUp() { bAttackKeyDown = false; }

void AttackBegin();
	
UFUNCTION(BlueprintCallable)
void AttackEnd();
	//攻击
	PlayerInputComponent->BindAction("Attack", IE_Pressed, this, &AMainPlayer::AttackKeyDown);
	PlayerInputComponent->BindAction("Attack", IE_Released, this, &AMainPlayer::AttackKeyUp);
	
void AMainPlayer::AttackKeyDown()
{
}


void AMainPlayer::AttackBegin()
{
}

void AMainPlayer::AttackEnd()
{
}

攻击逻辑实现

  • 当我们按下攻击键时,将检测攻击键按下的bool变量为true,安全检测一下是否持剑,如果是就执行攻击函数,攻击函数里面就先判断是否攻击,如果是就将是否攻击先标记为true,然后拿到动画,安全检测动画与蒙太奇,设置播放速率与部分蒙太奇片段的名字,开启蒙太奇与播放指定判断,最后在攻击结束函数里面将是否攻击变量标记为false,判断攻击键是否按下,按下就形成闭环再执行攻击键按下函数。
  • 获取蒙太奇函数的头文件:#include "Animation/AnimInstance.h"
//拿到动画
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();

if (AnimInstance && AttackMontage)
{
	float PlayRate = FMath::RandRange(1.25f, 1.75f);
	FString SectionName = FString::FromInt(FMath::RandRange(1, 2));
			//指定片段播放
	AnimInstance->Montage_Play(AttackMontage, PlayRate);
	AnimInstance->Montage_JumpToSection(FName(*SectionName), AttackMontage);
}
  • 攻击逻辑
void AMainPlayer::AttackKeyDown()
{
	bAttackKeyDown = true;
	if (bIsWeapon)
	{
		AttackBegin();
	}

}


void AMainPlayer::AttackBegin()
{
	if (!bIsAttacking)
	{
		bIsAttacking = true;

		//拿到动画
		UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();

		if (AnimInstance && AttackMontage)
		{
			float PlayRate = FMath::RandRange(1.25f, 1.75f);
			FString SectionName = FString::FromInt(FMath::RandRange(1, 2));
			//指定片段播放
			AnimInstance->Montage_Play(AttackMontage, PlayRate);
			AnimInstance->Montage_JumpToSection(FName(*SectionName), AttackMontage);
		}
	}
}

void AMainPlayer::AttackEnd()
{
	bIsAttacking = false;
	//形成闭环
	if (bAttackKeyDown)
	{
		AttackKeyDown();
	}
}

攻击逻辑蓝图实现

  • 首先在蒙太奇中新建两个结束攻击的通知
    在这里插入图片描述
    在这里插入图片描述

  • 在动画蓝图与角色蓝图中添加蒙太奇
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

  • 正在攻击的时候就不能卸载武器
    在这里插入图片描述
  • 移动中不可攻击
    在这里插入图片描述

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;
	
	//检测是否持剑
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapon")
	bool bIsWeapon;

	//正在装备的武器
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapon")
	class AWeaponItem* EquipWeapon;

	//正在重叠的武器
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapon")
	AWeaponItem* OverlapWeapon;

	bool bAttackKeyDown;//是否按下攻击键

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Animation")
	bool bIsAttacking;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Animation")
	class UAnimMontage* AttackMontage;

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);

	void InteractKeyDown();

	void AttackKeyDown();

	FORCEINLINE void AttackKeyUp() { bAttackKeyDown = false; }

	void AttackBegin();
	
	UFUNCTION(BlueprintCallable)
	void AttackEnd();
};

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"
#include "GamePlay/WeaponItem.h"
#include "Animation/AnimInstance.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->BindAction("Interact", IE_Pressed, this, &AMainPlayer::InteractKeyDown);//按下F

	//攻击
	PlayerInputComponent->BindAction("Attack", IE_Pressed, this, &AMainPlayer::AttackKeyDown);
	PlayerInputComponent->BindAction("Attack", IE_Released, this, &AMainPlayer::AttackKeyUp);

	//绑定移动轴映射事件
	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 && !(bIsAttacking))
	{
		//获取到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 && !(bIsAttacking))
	{
		//获取到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;
	}
}

void AMainPlayer::InteractKeyDown()
{
	if (OverlapWeapon)
	{
		if (EquipWeapon)
		{
			//交换武器
			EquipWeapon->UnEuip(this);
			OverlapWeapon->Equip(this);
		}
		else
		{
			//装备武器
			OverlapWeapon->Equip(this);
		}
	}
	else
	{
		if (EquipWeapon)
		{
			//卸载武器
			EquipWeapon->UnEuip(this);
		}
	}
}

void AMainPlayer::AttackKeyDown()
{
	bAttackKeyDown = true;
	if (bIsWeapon)
	{
		AttackBegin();
	}

}


void AMainPlayer::AttackBegin()
{
	if (!bIsAttacking)
	{
		bIsAttacking = true;

		//拿到动画
		UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();

		if (AnimInstance && AttackMontage)
		{
			float PlayRate = FMath::RandRange(1.25f, 1.75f);
			FString SectionName = FString::FromInt(FMath::RandRange(1, 2));
			//指定片段播放
			AnimInstance->Montage_Play(AttackMontage, PlayRate);
			AnimInstance->Montage_JumpToSection(FName(*SectionName), AttackMontage);
		}
	}
}

void AMainPlayer::AttackEnd()
{
	bIsAttacking = false;
	//形成闭环
	if (bAttackKeyDown)
	{
		AttackKeyDown();
	}
}

WeaponItem.h

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

#pragma once

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

/**
 * 
 */
UENUM(BlueprintType)
enum class EWeaponState :uint8
{
	EWS_CanPickUp UMETA(DisplayName = "CanPickUp"),
	EWS_Equip UMETA(DisplayName = "Equip")
};
UCLASS()
class UEGAME_API AWeaponItem : public AInteroperableItem
{
	GENERATED_BODY()
public:
	AWeaponItem();

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon|Sound")
	class USoundCue* OnEquipSound;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon|Particle")
	bool bOnEquipParticle;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapon")
	EWeaponState WeaponState;

protected:
	virtual void BeginPlay()override;

public:
	virtual void OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)override;
	virtual void OnOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)override;
	
	void Equip(class AMainPlayer* Player);
	void UnEuip(AMainPlayer* Player);

	//动态切换碰撞
	void ActiveDisplayMeshCollision();
	void DeactiveDisplayMeshCollision();
};

WeaponItem.cpp

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


#include "WeaponItem.h"
#include "Components/SkeletalMeshComponent.h"
#include "Characters/Player/MainPlayer.h"
#include "Engine/SkeletalMeshSocket.h"
#include "Kismet/GameplayStatics.h"
#include "Sound/SoundCue.h"
#include "Particles/ParticleSystemComponent.h"
#include "UObject/ConstructorHelpers.h"
#include "GameFramework/CharacterMovementComponent.h"

AWeaponItem::AWeaponItem()
{
	//销毁
	if (DisplayMesh)
	{
		DisplayMesh->DestroyComponent();
		//UE_LOG(LogTemp, Warning, TEXT("delete succeed"));
	}
	else
	{
		//UE_LOG(LogTemp, Warning, TEXT("fail to delete"));
	}
	
	//因为TEXT具有唯一性,我们不知道什么时候销毁原UStaticMeshComponent* DisplayMesh;所以这里TEXT进行一下区分
	DisplayMesh=CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("DisplaySkeletalMesh"));
	DisplayMesh->SetupAttachment(GetRootComponent());
	ActiveDisplayMeshCollision();//设置碰撞

	static ConstructorHelpers::FObjectFinder<USoundCue> SoundCueAsset(TEXT("SoundCue'/Game/Assets/Audios/Blade_Cue.Blade_Cue'"));
	if (SoundCueAsset.Succeeded())
	{
		OnEquipSound = SoundCueAsset.Object;
	}

	//拾取武器后粒子效果默认关闭
	bOnEquipParticle = false;
	//默认状态武器是可拾取的
	WeaponState = EWeaponState::EWS_CanPickUp;
}

void AWeaponItem::BeginPlay()
{
	Super::BeginPlay();
}

void AWeaponItem::OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	Super::OnOverlapBegin(OverlappedComponent, OtherActor, OtherComp, OtherBodyIndex, bFromSweep, SweepResult);
	
	if (OtherActor && WeaponState == EWeaponState::EWS_CanPickUp)
	{
		AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
		if (Player)
		{
			//告诉角色正在重叠的武器是当前武器
			Player->OverlapWeapon = this;
		}
	}
}

void AWeaponItem::OnOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
	Super::OnOverlapEnd(OverlappedComponent, OtherActor, OtherComp, OtherBodyIndex);
	
	if (OtherActor)
	{
		AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);
		//判断一开始是否拾起的武器是当前武器
		if (Player && Player->OverlapWeapon == this)
		{
			//告诉角色离开了武器触发器
			Player->OverlapWeapon = nullptr;
		}
	}
}

void AWeaponItem::Equip(AMainPlayer* Player)
{
	if (Player && !Player->GetMovementComponent()->IsFalling())
	{
		//已装备武器
		WeaponState = EWeaponState::EWS_Equip;
		DeactiveDisplayMeshCollision();//关闭碰撞
		//获取Player的Socket
		const USkeletalMeshSocket* RightHandSocker = Player->GetMesh()->GetSocketByName(TEXT("RightHandSocket"));
		if (RightHandSocker)
		{
			//让武器附属到Socket上
			RightHandSocker->AttachActor(this, Player->GetMesh());
			Player->bIsWeapon = true;
			Player->EquipWeapon = this;
			Player->OverlapWeapon = nullptr;
			bRotate = false;//武器旋转关闭

			if (OnEquipSound)
			{
				//播放音乐
				UGameplayStatics::PlaySound2D(this, OnEquipSound);
			}
			//if (!bOnEquipParticle)
			//{
			//	//关闭粒子组件
			//	ParticleEffectsComponent->Deactivate();
			//	
			//}
			
		}
	}
}

void AWeaponItem::UnEuip(AMainPlayer* Player)
{
	if (Player && !Player->GetMovementComponent()->IsFalling() && !Player->bIsAttacking)
	{
		WeaponState = EWeaponState::EWS_CanPickUp;
		ActiveDisplayMeshCollision();//开启碰撞
		Player->bIsWeapon = false;
		Player->EquipWeapon = nullptr;
		if (Player->OverlapWeapon == nullptr)
		{
			Player->OverlapWeapon = this;
		}

		//分离当前WeaponItem Socket
		DetachFromActor(FDetachmentTransformRules::KeepWorldTransform);
		SetActorRotation(FRotator(0.f));
		SetActorScale3D(FVector(1.f));
		bRotate = true;	
	}
}

void AWeaponItem::ActiveDisplayMeshCollision()
{
	DisplayMesh->SetCollisionEnabled(ECollisionEnabled::PhysicsOnly);
	DisplayMesh->SetCollisionObjectType(ECollisionChannel::ECC_WorldStatic);
	DisplayMesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
	DisplayMesh->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Block);
}

void AWeaponItem::DeactiveDisplayMeshCollision()
{
	DisplayMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}

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

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

相关文章

00后如何组织双十一大促看这一篇就够了! | 京东云技术团队

引言 大家好&#xff0c;我是王蒙恩&#xff0c;一名“整顿职场”的00后。作为一名去年刚刚加入京东的校招生&#xff0c;我有幸成为本次CDP平台的11.11备战负责人。虽然早在实习的时候就经历过大促&#xff0c;但是真正组织整个部门的备战还是很难忘的。于是提起笔&#xff0…

APP外包开发需要注意的问题

在进行APP外包开发时&#xff0c;有一些关键问题需要注意&#xff0c;以确保项目的顺利进行和最终交付满足预期的应用。以下是一些在APP外包开发中需要关注的问题&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎…

基于C#实现字符串相似度

一、概念 对于两个字符串 A 和 B&#xff0c;通过基本的增删改将字符串 A 改成 B&#xff0c;或者将 B 改成 A&#xff0c;在改变的过程中我们使用的最少步骤称之为“编辑距离”。比如如下的字符串&#xff1a;我们通过种种操作&#xff0c;痉挛之后编辑距离为 3&#xff0c;不…

虹科分享 | PEAK版本升级,看看有没有你关注的新功能?

号外号外&#xff01;近期PEAK进行了重要的版本升级&#xff0c;这次升级带来了许多令人兴奋的功能优化&#xff0c;助力您的工作流程更加便捷高效。为了帮助您更好地了解PEAK新版本&#xff0c;我们提供了详细的说明和指导&#xff0c;快来看看有没有你关注的新功能&#xff1…

如何为视频添加旁白,有哪些操作技巧?

简而言之&#xff0c;画外音是视频的旁白&#xff0c;在教程视频中添加旁白可以使视频更加有趣&#xff0c;并向观看者传达更多的信息。 如果您是视频制作人&#xff0c;想要为视频添加旁白&#xff0c;可阅读以下文章&#xff0c;可以帮助您更好地进行配音。 制作配音的技巧…

ubuntu20.04蓝牙连接airpods

ubuntu20.04蓝牙连接airpods 解禁蓝牙安装blueman设置模式连接上没有声音的问题 解禁蓝牙 sudo rmmod btusb sleep 1 sudo modprobe btusb sudo /etc/init.d/bluetooth restart安装blueman sudo apt install blueman sudo apt-get install pulseaudio-module-bluetooth sudo …

球幕投影有哪些常见的物理表现形式?

近年来&#xff0c;投影技术不断发展完善&#xff0c;给内容的表达方式带来了突破&#xff0c;使其展示形式不再局限于平面&#xff0c;即使在弧面、球面等异形幕墙上&#xff0c;也能呈现出令人惊叹的视觉画面。其中球幕投影备受关注&#xff0c;它以半球形屏幕将图像投影到球…

pytest

pytest test_one.py pytest的执行

十倍增量的海外客户开发新方式来了!外贸企业可直接照做

外贸和B2大C型&#xff08;汽车、房产、保险、教育等&#xff09;企业出海过程中&#xff0c;除了常见的数字营销&#xff08;投放&#xff09;、平台营销、活动营销&#xff08;线下展会&#xff09;和内容营销&#xff0c;还有一个批量化可快速复制起量的营销方式&#xff1a…

大厂秋招真题【单调栈】Bilibili2021秋招-大鱼吃小鱼

文章目录 题目描述与示例题目描述输入描述输出描述示例一输入输出说明 示例二输入输出说明 解题思路代码PythonJavaC时空复杂度 华为OD算法/大厂面试高频题算法练习冲刺训练 题目描述与示例 题目描述 小明最近喜欢上了俄罗斯套娃、大鱼吃小鱼这些大的包住小的类型的游戏。 于…

磁钢的居里温度和工作温度

你知道吗&#xff0c;磁体在超过一定温度时会永久的失磁&#xff0c;不同的磁体能够承受的最大工作温度是不同的&#xff0c;那么与温度相关的指标有哪些&#xff1f;如何根据工作温度来选择合适的磁钢&#xff1f;今天我们就来解答一下这些问题。 居里温度 说到温度与磁性关…

Python武器库开发-flask篇之error404(二十七)

flask篇之error404(二十七) 首先&#xff0c;我们先进入模板的界面创建一个404的html页面 cd templates vim 404.html404.html的内容如下&#xff1a; <h1>error!!!</h1>在 Flask 应用程序中&#xff0c;当用户访问一个不存在的页面的时候&#xff0c;会出现 4…

LeetCode【32】最长的有效括号

题目&#xff1a; 思路&#xff1a; 括号字符串依次入栈&#xff0c;删除匹配的成对括号。最后栈中留下的都是无法匹配的断点。这些断点的差值减一就是断点间有效括号串的长度&#xff0c;取这些长度的最大值即可。 例如括号字符串 “)()((())(”&#xff0c;最后留在栈中的…

2023初中生古诗文大会复赛12月2日举行,来做做全真在线模拟题吧

2023年11月19日日&#xff0c;上海市古诗文大会主办方通过官微发布了2023上海中学生古诗文大会&#xff08;初中组&#xff09;复选将于12月2日举行的通知&#xff0c;就初中生古诗文大会复赛&#xff08;复选&#xff09;的相关安排做了说明&#xff0c;六分成长已经为您把通知…

ASUS华硕ROG幻13笔记本电脑GV301QE原厂Windows10系统

链接&#xff1a;https://pan.baidu.com/s/1aPW0ctRXRNAhE75mzVPdTg?pwdds78 提取码&#xff1a;ds78 华硕玩家国度幻13笔记本电脑锐龙版Ryzen 7 5800HS,显卡3050 3050Ti,3060,3060Ti,3070,3070Ti 原厂W10系统自带所有驱动、出厂主题壁纸、系统属性专属LOGO标志、Office办…

横向扩展统一存储备份解决方案的特点与优势

Infortrend 使企业能够实现高效和可靠的数据备份&#xff0c;确保业务不间断的运行&#xff0c;保护有价值的业务信息。用户可以依靠我们的存储解决方案实现恢复时间目标&#xff08;RTO&#xff09;和恢复点目标&#xff08;RPO&#xff09;&#xff0c;用于广泛的备份应用场景…

6.10二叉树的所有路径(LC257-E,不太会)

算法&#xff1a; 前序遍历&#xff1a; 因为要让父节点指向孩子节点&#xff0c;才能输出路径。 递归与回溯相辅相成&#xff0c;只要有递归&#xff0c;就一定有回溯。 举个例子理解一下&#xff1a; 中&#xff1a;先push入1 左&#xff1a;再Push入2 右&#xff1a;再…

MES管理系统与ERP系统的实施顺序与决策

在现今的数字化时代&#xff0c;制造企业纷纷寻求通过先进的系统来提升运营效率。其中&#xff0c;ERP管理系统与MES管理系统被誉为是数字化转型的两大利器。然而&#xff0c;在推进这两个系统时&#xff0c;企业常常面临一个关键问题&#xff1a;究竟应该先实施哪一个系统&…

BetterDisplay Pro v2.0.11(显示器颜色校准软件)

BetterDisplay Pro是一款为Mac电脑设计的屏幕亮度调节软件&#xff0c;旨在提高显示器的色彩和亮度表现。它可以根据用户的需求和显示器的特性&#xff0c;自动调整显示器的亮度、色温、对比度等参数&#xff0c;以获得更加真实、舒适的视觉效果。 这款软件拥有智能调节功能&a…

基于C#实现最长公共子序列

一、作用 最长公共子序列的问题常用于解决字符串的相似度&#xff0c;是一个非常实用的算法&#xff0c;作为码农&#xff0c;此算法是我们的必备基本功。 二、概念 举个例子&#xff0c;cnblogs 这个字符串中子序列有多少个呢&#xff1f;很显然有 27 个&#xff0c;比如其…
最新文章