UEC++ day8

伤害系统

给敌人创建血条

  • 首先添加一个UI界面用来显示敌人血条
  • 设置背景图像为黑色半透明
    在这里插入图片描述
  • 填充颜色
    在这里插入图片描述
  • 给敌人类添加两种状态表示血量与最大血量,添加一个UWidegtComponet组件与UProgressBar组件
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Enemy Stats")float Health;UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Enemy Stats")float MaxHealth;UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Enemy Stats")class UWidgetComponent* HealthBarWidgetComponent;UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Enemy Stats")class UProgressBar* HealthBar;MaxHealth = 100.f;
Health = MaxHealth;

设置WidgetComponent组件

  • 头文件:
    • #include “Components/WidgetComponent.h”:使用WidgetComponent就要使用
    • #include “Blueprint/UserWidget.h”:获取到User内部对象时需要使用
    • #include “Components/ProgressBar.h”:获取调用ProgressBar需要使用
  • 创建UWidgetComponent组件
	HealthBarWidgetComponent = CreateDefaultSubobject<UWidgetComponent>(TEXT("HealthBarWidgetComponent"));HealthBarWidgetComponent->SetupAttachment(GetRootComponent());HealthBarWidgetComponent->SetWidgetSpace(EWidgetSpace::Screen);HealthBarWidgetComponent->SetDrawSize(FVector2D(125.f, 10.f));HealthBarWidgetComponent->SetWorldLocation(FVector(0.f, 0.f, 50.f));
  • 获取到HealBar小组件
// Called when the game starts or when spawned
void ABaseEnemy::BeginPlay()
{Super::BeginPlay();ChaseVolume->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnChaseVolumeOverlapBegin);ChaseVolume->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnChaseVolumeOverlapEnd);AttackVolume->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnAttackVolumeOverlapBegin);AttackVolume->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnAttackVolumeOverlapEnd);//获取到HealBar小组件HealthBar = Cast<UProgressBar>(HealthBarWidgetComponent->GetUserWidgetObject()->GetWidgetFromName("HealthBar"));HealthBar->SetPercent(Health / MaxHealth);//拿到ControllerAIController = Cast<AAIController>(GetController());
}
  • 运行结果
    在这里插入图片描述
    在这里插入图片描述

血条进入敌人追逐区显示

  • 一开始敌人血条是不显示的,只有主角进入了敌人追逐区才开始显示血条,出了追逐区也不显示
	//获取到HealBar小组件HealthBar = Cast<UProgressBar>(HealthBarWidgetComponent->GetUserWidgetObject()->GetWidgetFromName("HealthBar"));HealthBar->SetPercent(Health / MaxHealth);HealthBar->SetVisibility(ESlateVisibility::Hidden);//一开始不显示血条void ABaseEnemy::OnChaseVolumeOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{if (OtherActor){AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);if (Player){//主角进入追逐范围显示血条HealthBar->SetVisibility(ESlateVisibility::Visible);MoveToTarget(Player);}}
}void ABaseEnemy::OnChaseVolumeOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{if (OtherActor){AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);if (Player){if (AIController){//主角出追逐范围不显示血条HealthBar->SetVisibility(ESlateVisibility::Hidden);//停止移动AIController->StopMovement();}}}
}

触发伤害思路

  • 我们可以在武器上加一个碰撞器,当这个碰撞器碰撞到敌人的时候就开启伤害,如果没有就躲避的伤害
  • 给每把剑的骨骼添加一个Socket
    在这里插入图片描述

触发伤害需求

  • 因为要使用UE中内置的直接伤害附加的功能,类似爆炸物那一节,所以我们要在武器类中新建一个盒子碰撞触发器,新建一个伤害值,新建一个伤害类型,新建一个伤害发起者
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapon|Attack")class UBoxComponent* AttackCollision;UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon|Attack")float Damage;UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon|Attack")TSubclassOf<UDamageType> DamageTyClass;UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Weapon|Attack")class AController* WeaponOwner;
  • 新建伤害开始触发事件与结束事件,两个动态切换碰撞函数,我只需要在挥剑的那个瞬间去切换需要在动画的notify中去切换的所以需要添加反射
	UFUNCTION()void OnAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);UFUNCTION()void OnAttackCollisionOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);//动态切换碰撞UFUNCTION(BlueprintCallable)void ActiveAttackCollision();UFUNCTION(BlueprintCallable)void DeactiveAttackCollision();
  • 初始化,创建BoxComponent需要头文件:#include “Components/BoxComponent.h”
	AttackCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("AttackCollision"));AttackCollision->SetupAttachment(DisplayMesh, "WeaponSocket");//将这个碰撞点附加到武器插槽上DeactiveAttackCollision();//关闭碰撞Damage = 25.f;//绑定
void AWeaponItem::BeginPlay()
{Super::BeginPlay();AttackCollision->OnComponentBeginOverlap.AddDynamic(this, &AWeaponItem::OnAttackCollisionOverlapBegin);AttackCollision->OnComponentEndOverlap.AddDynamic(this, &AWeaponItem::OnAttackCollisionOverlapEnd);
}void AWeaponItem::OnAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
}void AWeaponItem::OnAttackCollisionOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
}
//设置碰撞类型
void AWeaponItem::ActiveAttackCollision()
{AttackCollision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);AttackCollision->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);AttackCollision->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);AttackCollision->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);}void AWeaponItem::DeactiveAttackCollision()
{AttackCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
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"
#include "Components/BoxComponent.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();//设置碰撞AttackCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("AttackCollision"));AttackCollision->SetupAttachment(DisplayMesh, "WeaponSocket");//将这个碰撞点附加到武器插槽上DeactiveAttackCollision();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;Damage = 25.f;
}void AWeaponItem::BeginPlay()
{Super::BeginPlay();AttackCollision->OnComponentBeginOverlap.AddDynamic(this, &AWeaponItem::OnAttackCollisionOverlapBegin);AttackCollision->OnComponentEndOverlap.AddDynamic(this, &AWeaponItem::OnAttackCollisionOverlapEnd);
}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的Socketconst 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 SocketDetachFromActor(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);
}void AWeaponItem::OnAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
}void AWeaponItem::OnAttackCollisionOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
}void AWeaponItem::ActiveAttackCollision()
{AttackCollision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);AttackCollision->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);AttackCollision->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);AttackCollision->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);}void AWeaponItem::DeactiveAttackCollision()
{AttackCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}

在蒙太奇中添加攻击通知激活伤害碰撞

  • 在主角的蒙太奇中添加四个通知,跟当时添加攻击结束通知差不多
    在这里插入图片描述
  • 然后在动画蓝图中进行调用函数
    在这里插入图片描述
  • 将三把剑的盒子碰撞检测大小调整一下
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

给敌人添加触发器

  • 基本与上面给主角添加伤害需求的思路一致
  • 先给敌人添加两个骨骼
    在这里插入图片描述

触发伤害需求

  • 再来就是编辑敌人类的变量需求与基本逻辑了,两个触发盒子因为是两只腿的攻击,新建伤害值,伤害类型
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Attack")class UBoxComponent* LeftAttackCollision;UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Attack")UBoxComponent* RightAttackCollision;UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attack")float Damage;UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attack")TSubclassOf<UDamageType> DamageTyClass;
  • 不用说,现在是四个伤害触发事件的新建,四个动态切换碰撞的函数
	UFUNCTION()void OnLeftAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);UFUNCTION()void OnLeftAttackCollisionOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);UFUNCTION()void OnRightAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);UFUNCTION()void OnRightAttackCollisionOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);//动态切换碰撞UFUNCTION(BlueprintCallable)void ActiveLeftAttackCollision();UFUNCTION(BlueprintCallable)void DeactiveLeftAttackCollision();UFUNCTION(BlueprintCallable)void ActiveRightAttackCollision();UFUNCTION(BlueprintCallable)void DeactiveRightAttackCollision();
  • 现在就可以开始编写,初始化了
	LeftAttackCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("LeftAttackCollision"));LeftAttackCollision->SetupAttachment(GetMesh(), "LeftAttackSocket");DeactiveLeftAttackCollision();RightAttackCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("RightAttackCollision"));RightAttackCollision->SetupAttachment(GetMesh(), "RightAttackSocket");DeactiveRightAttackCollision();//赋值	
Damage = 10.f;//绑定LeftAttackCollision->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnLeftAttackCollisionOverlapBegin);LeftAttackCollision->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnLeftAttackCollisionOverlapEnd);RightAttackCollision->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnRightAttackCollisionOverlapBegin);RightAttackCollision->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnRightAttackCollisionOverlapEnd);void ABaseEnemy::ActiveLeftAttackCollision()
{LeftAttackCollision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);LeftAttackCollision->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);LeftAttackCollision->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);LeftAttackCollision->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
}void ABaseEnemy::DeactiveLeftAttackCollision()
{LeftAttackCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}void ABaseEnemy::ActiveRightAttackCollision()
{RightAttackCollision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);RightAttackCollision->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);RightAttackCollision->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);RightAttackCollision->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
}void ABaseEnemy::DeactiveRightAttackCollision()
{RightAttackCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
BaseEnemy.cpp
// Fill out your copyright notice in the Description page of Project Settings.#include "BaseEnemy.h"
#include "Components/SphereComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/CapsuleComponent.h"
#include "AIController.h"
#include "Characters/Player/MainPlayer.h"
#include "Animation/AnimInstance.h"
#include "Kismet/KismetMathLibrary.h"
#include "Kismet/GameplayStatics.h"
#include "Components/WidgetComponent.h"
#include "Blueprint/UserWidget.h"
#include "Components/ProgressBar.h"
#include "Components/BoxComponent.h"
// Sets default values
ABaseEnemy::ABaseEnemy()
{// 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;ChaseVolume = CreateDefaultSubobject<USphereComponent>(TEXT("ChaseVolume"));ChaseVolume->SetupAttachment(GetRootComponent());ChaseVolume->InitSphereRadius(800.f);ChaseVolume->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);ChaseVolume->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);ChaseVolume->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);AttackVolume = CreateDefaultSubobject<USphereComponent>(TEXT("AttackVolume"));AttackVolume->SetupAttachment(GetRootComponent());AttackVolume->InitSphereRadius(100.f);AttackVolume->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);AttackVolume->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);AttackVolume->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);HealthBarWidgetComponent = CreateDefaultSubobject<UWidgetComponent>(TEXT("HealthBarWidgetComponent"));HealthBarWidgetComponent->SetupAttachment(GetRootComponent());HealthBarWidgetComponent->SetWidgetSpace(EWidgetSpace::Screen);HealthBarWidgetComponent->SetDrawSize(FVector2D(125.f, 10.f));HealthBarWidgetComponent->SetWorldLocation(FVector(0.f, 0.f, 50.f));LeftAttackCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("LeftAttackCollision"));LeftAttackCollision->SetupAttachment(GetMesh(), "LeftAttackSocket");DeactiveLeftAttackCollision();RightAttackCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("RightAttackCollision"));RightAttackCollision->SetupAttachment(GetMesh(), "RightAttackSocket");DeactiveRightAttackCollision();//避免摄像机被敌人给阻挡GetMesh()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Ignore);GetCapsuleComponent()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Ignore);//设置持有属性AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;//初始化默认移动状态EnemyMovementStatus = EEnemyMovementStatus::EEMS_Idle;InterpSpeed = 15.f;bInterpToPlayer = false;MaxHealth = 100.f;Health = MaxHealth;Damage = 10.f;
}// Called when the game starts or when spawned
void ABaseEnemy::BeginPlay()
{Super::BeginPlay();ChaseVolume->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnChaseVolumeOverlapBegin);ChaseVolume->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnChaseVolumeOverlapEnd);AttackVolume->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnAttackVolumeOverlapBegin);AttackVolume->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnAttackVolumeOverlapEnd);LeftAttackCollision->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnLeftAttackCollisionOverlapBegin);LeftAttackCollision->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnLeftAttackCollisionOverlapEnd);RightAttackCollision->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnRightAttackCollisionOverlapBegin);RightAttackCollision->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnRightAttackCollisionOverlapEnd);//获取到HealBar小组件HealthBar = Cast<UProgressBar>(HealthBarWidgetComponent->GetUserWidgetObject()->GetWidgetFromName("HealthBar"));HealthBar->SetPercent(Health / MaxHealth);HealthBar->SetVisibility(ESlateVisibility::Hidden);//一开始不显示血条//拿到ControllerAIController = Cast<AAIController>(GetController());
}// Called every frame
void ABaseEnemy::Tick(float DeltaTime)
{Super::Tick(DeltaTime);if (bInterpToPlayer){FRotator LookYaw(0.f, UKismetMathLibrary::FindLookAtRotation(GetActorLocation(), UGameplayStatics::GetPlayerPawn(this, 0)->GetActorLocation()).Yaw, 0.f);FRotator InterpRotation = FMath::RInterpTo(GetActorRotation(), LookYaw, DeltaTime, InterpSpeed);SetActorRotation(InterpRotation);}
}// Called to bind functionality to input
void ABaseEnemy::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{Super::SetupPlayerInputComponent(PlayerInputComponent);}void ABaseEnemy::OnChaseVolumeOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{if (OtherActor){AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);if (Player){//主角进入追逐范围显示血条HealthBar->SetVisibility(ESlateVisibility::Visible);MoveToTarget(Player);}}
}void ABaseEnemy::OnChaseVolumeOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{if (OtherActor){AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);if (Player){if (AIController){//主角出追逐范围不显示血条HealthBar->SetVisibility(ESlateVisibility::Hidden);//停止移动AIController->StopMovement();}}}
}void ABaseEnemy::OnAttackVolumeOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{if (OtherActor){AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);if (Player){Player->UpdataAttackTarget();bAttackVolumeOverlap = true;AttackBegin();}}
}void ABaseEnemy::OnAttackVolumeOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{if (OtherActor){AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);if (Player){bAttackVolumeOverlap = false;if (EnemyMovementStatus!=EEnemyMovementStatus::EEMS_Attacking){MoveToTarget(Player);}}}
}void ABaseEnemy::MoveToTarget(AMainPlayer* Player)
{EnemyMovementStatus = EEnemyMovementStatus::EEMS_MoveToTarget;if (AIController){FAIMoveRequest MoveRequest;MoveRequest.SetGoalActor(Player);//设置移动请求目标MoveRequest.SetAcceptanceRadius(10.f);	//设置移动半径FNavPathSharedPtr NavPath;//会返回路径AIController->MoveTo(MoveRequest, &NavPath);}
}void ABaseEnemy::AttackBegin()
{//攻击中关闭移动if (AIController){AIController->StopMovement();}if (EnemyMovementStatus != EEnemyMovementStatus::EEMS_Attacking){EnemyMovementStatus = EEnemyMovementStatus::EEMS_Attacking;bInterpToPlayer = true;UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();if (AnimInstance && AttackMontage){float PlayRate = FMath::RandRange(0.9f, 1.1f);FString SectionName = FString::FromInt(FMath::RandRange(1, 3));AnimInstance->Montage_Play(AttackMontage, PlayRate);AnimInstance->Montage_JumpToSection(FName(*SectionName), AttackMontage);}}
}void ABaseEnemy::AttackEnd()
{EnemyMovementStatus = EEnemyMovementStatus::EEMS_Idle;bInterpToPlayer = false;if (bAttackVolumeOverlap){AttackBegin();}
}void ABaseEnemy::OnLeftAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
}void ABaseEnemy::OnLeftAttackCollisionOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
}void ABaseEnemy::OnRightAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
}void ABaseEnemy::OnRightAttackCollisionOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
}void ABaseEnemy::ActiveLeftAttackCollision()
{LeftAttackCollision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);LeftAttackCollision->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);LeftAttackCollision->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);LeftAttackCollision->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
}void ABaseEnemy::DeactiveLeftAttackCollision()
{LeftAttackCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}void ABaseEnemy::ActiveRightAttackCollision()
{RightAttackCollision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);RightAttackCollision->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);RightAttackCollision->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);RightAttackCollision->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
}void ABaseEnemy::DeactiveRightAttackCollision()
{RightAttackCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}

在蒙太奇中添加攻击通知激活伤害碰撞

  • 首先修改攻击触发器的大小
    在这里插入图片描述
  • 在敌人的蒙太奇中添加通知,跟上面主角添加通知差不多
    在这里插入图片描述
  • 编辑动画蓝图即可
    在这里插入图片描述

设置击中敌人时的特效、音效与伤害

  • 给MainPlayer.h与BaseEnemy.h中添加一个粒子系统与声音组件
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hit Effect")class UParticleSystem* HitPaticles;UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hit Effect")class USoundCue* HitSound;
  • 在WeaponItem.cpp的攻击开始重叠事件中去添加声音特效与伤害,这个就和之前装备武器与爆炸物传递伤害逻辑差不多
  • 正好复习一下,获取插槽的时候必须加const,因为获取插槽的函数返回的是一个const值
void AWeaponItem::OnAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{if (OtherActor){ABaseEnemy* BaseEnemy = Cast<ABaseEnemy>(OtherActor);if (BaseEnemy){if (BaseEnemy->HitPaticles){//获取WeaponSocket插槽const USkeletalMeshSocket* WeaponScoket = ((USkeletalMeshComponent*)DisplayMesh)->GetSocketByName("WeaponSocket");if (WeaponScoket){FVector SocketLocation = WeaponScoket->GetSocketLocation((USkeletalMeshComponent*)DisplayMesh);UGameplayStatics::SpawnEmitterAtLocation(this, BaseEnemy->HitPaticles, SocketLocation, FRotator(0.f), true);}if (BaseEnemy->HitSound){UGameplayStatics::PlaySound2D(this, BaseEnemy->HitSound);}if (DamageTyClass){UGameplayStatics::ApplyDamage(BaseEnemy, Damage, WeaponOwner, this, DamageTyClass);}}}}
}
  • 将攻击类型添加到每把剑上,人物添加上粒子与音效

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

设置击中玩家时的特效、音效与伤害

  • 基本与上面一样,在BaseEnemy.cpp的左右攻击事件中添加粒子音效与伤害
void ABaseEnemy::OnLeftAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{if (OtherActor){AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);if (Player){if (Player->HitPaticles){const USkeletalMeshSocket* AttackScoket = GetMesh()->GetSocketByName("LeftAttackSocket");if (AttackScoket){FVector SocketLocation = AttackScoket->GetSocketLocation(GetMesh());UGameplayStatics::SpawnEmitterAtLocation(this, Player->HitPaticles, SocketLocation, FRotator(0.f), true);}if (Player->HitSound){UGameplayStatics::PlaySound2D(this, Player->HitSound);}if (DamageTyClass){UGameplayStatics::ApplyDamage(Player, Damage, AIController, this, DamageTyClass);}}}}
}void ABaseEnemy::OnLeftAttackCollisionOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
}void ABaseEnemy::OnRightAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{if (OtherActor){AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);if (Player){if (Player->HitPaticles){const USkeletalMeshSocket* AttackScoket = GetMesh()->GetSocketByName("RightAttackSocket");if (AttackScoket){FVector SocketLocation = AttackScoket->GetSocketLocation(GetMesh());UGameplayStatics::SpawnEmitterAtLocation(this, Player->HitPaticles, SocketLocation, FRotator(0.f), true);}if (Player->HitSound){UGameplayStatics::PlaySound2D(this, Player->HitSound);}if (DamageTyClass){UGameplayStatics::ApplyDamage(Player, Damage, AIController, this, DamageTyClass);}}}}
}
  • 将攻击类型添加到敌人上,添加上粒子与音效
    在这里插入图片描述

敌人受伤死亡分析

  • 敌人与主角类中添加死亡函数
  • 在敌人类中重写TakeDamage方法接收伤害,注意的是因为MainPlayer的血量UI是写在UI绑定事件蓝图里面在,而敌人血量UI是在C++中编码跟随,所以最后返回写完逻辑返回血量前,要更新一下血条
	//重写TakeDamage方法float TakeDamage(float Damage, struct FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser) override;void Die();
  • 伤害逻辑
float ABaseEnemy::TakeDamage(float Damage, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{if (Health - Damage <= 0.f){Health = FMath::Clamp(Health - Damage, 0.f, MaxHealth);Die();}else{Health -= Damage;}HealthBar->SetPercent(Health / MaxHealth);//更新UI血条return Health;
}

按布尔类型播放死亡动画

  • 思路:我们还是可以使用notify来通知调用死亡动画,首先在MainPlayer与BaseEnemy中新建一个用于信息通知的函数
UFUNCTION(BlueprintCallable)
void DeathEnd();
  • 然后编辑MainPlayer死亡函数的逻辑,首先主角状态设置为死亡,判断是否持剑,持剑就关闭武器所有碰撞
void AMainPlayer::Die()
{SetMovementStatus(EPlayerMovementStatus::EPMS_Dead);if (EquipWeapon){EquipWeapon->DeactiveAttackCollision();EquipWeapon->DeactiveDisplayMeshCollision();}
}
  • 编辑BaseEnemy死亡函数逻辑,首先设置状态为死亡 ,关闭所有的碰撞,因为敌人已死亡更新主角攻击目标
void ABaseEnemy::Die()
{EnemyMovementStatus = EEnemyMovementStatus::EEMS_Dead;DeactiveLeftAttackCollision();DeactiveRightAttackCollision();ChaseVolume->SetCollisionEnabled(ECollisionEnabled::NoCollision);AttackVolume->SetCollisionEnabled(ECollisionEnabled::NoCollision);GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);//敌人死亡,主角要更新攻击目标Cast<AMainPlayer>(UGameplayStatics::GetPlayerPawn(this, 0))->UpdataAttackTarget();
}
  • 编辑他们的动画蓝图,添加Blend Poses by bool播放死亡动画,Enemy状态为死亡时播放死亡动画,否则就正常播放其他动画,记得将死亡的循环播放动画关闭
    在这里插入图片描述
    在这里插入图片描述

限制玩家死亡后的一切活动状态

  • 思路:我们在MainPlayer与BaseEnemy中新建一个专门用来判断玩家是否存活状态的函数,如果玩家不存活就停止一切活动
FORCEINLINE bool IsAlive() { return MovementStatus != EPlayerMovementStatus::EPMS_Dead; }
  • 敌人的状态中还得检测一下玩家是否死亡
FORCEINLINE bool IsAlive() { return EnemyMovementStatus != EEnemyMovementStatus::EEMS_Dead; }
bool HasValidTarget();//------------------------------------------------------------------------------------------------
bool ABaseEnemy::HasValidTarget()
{return Cast<AMainPlayer>(UGameplayStatics::GetPlayerPawn(this, 0))->MovementStatus != EPlayerMovementStatus::EPMS_Dead;
}

MainPlayer.cpp

  • 然后在MainPlayer中添加判断禁止死亡后的一切活动,也就是都加上IsAlive判断
// 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"
#include "Characters/Enemy/BaseEnemy.h"
#include "Kismet/KismetMathLibrary.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 = 400.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;InterpSpeed = 15.f;bInterpToEnemy = 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);//不存活就不执行其他活动了if (!IsAlive()){return;}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;}//进行转向插值if (bInterpToEnemy && AttackTarget){//只需要AttackTarget的Yaw转向FRotator LookAtYaw(0.f, UKismetMathLibrary::FindLookAtRotation(GetActorLocation(), AttackTarget->GetActorLocation()).Yaw, 0.f);FRotator InterpRotation = FMath::RInterpTo(GetActorRotation(), LookAtYaw, DeltaTime, InterpSpeed);SetActorRotation(InterpRotation);}
}// 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->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()
{//继承父类的方法if (IsAlive()){Super::Jump();}}void AMainPlayer::MoveForward(float value)
{if (Controller != nullptr && value != 0.f && !(bIsAttacking) && IsAlive()){//获取到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) && IsAlive()){//获取到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 && IsAlive()){AddControllerYawInput(Value);}}void AMainPlayer::LookUp(float Value)
{//UE_LOG(LogTemp, Warning, TEXT("%f"), GetControlRotation().Pitch);if (IsAlive()){//控制视角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 && IsAlive()){AddControllerYawInput(Value);}
}void AMainPlayer::LookUpRate(float Rate)
{//要乘以一个DeltaTime这样就可以避免高帧底帧差值问题if (IsAlive()){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)
{if (IsAlive()){MovementStatus = Status;//切换状态的时候改变移动速度switch (MovementStatus){case EPlayerMovementStatus::EPMS_Sprinting:GetCharacterMovement()->MaxWalkSpeed = SprintSpeed;break;default:GetCharacterMovement()->MaxWalkSpeed = RunningSpeed;break;}}
}void AMainPlayer::InteractKeyDown()
{if (OverlapWeapon && IsAlive()){if (EquipWeapon){//交换武器EquipWeapon->UnEuip(this);OverlapWeapon->Equip(this);}else{//装备武器OverlapWeapon->Equip(this);}}else{if (EquipWeapon){//卸载武器EquipWeapon->UnEuip(this);}}
}void AMainPlayer::AttackKeyDown()
{if (IsAlive()){bAttackKeyDown = true;if (bIsWeapon){AttackBegin();}}}void AMainPlayer::AttackBegin()
{if (!bIsAttacking && IsAlive()){bIsAttacking = true;bInterpToEnemy = 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;bInterpToEnemy = false;//形成闭环if (bAttackKeyDown && IsAlive()){AttackKeyDown();}
}void AMainPlayer::UpdataAttackTarget()
{TArray<AActor*> OVerlappingActors;GetOverlappingActors(OVerlappingActors,EnemyFilter);//判断列表里面是否为空,为空就无攻击目标if (OVerlappingActors.Num() == 0){AttackTarget = nullptr;return;}ABaseEnemy* ClosestDistance = nullptr;float MinDistance = 1000.f;FVector Loation = GetActorLocation();for (auto Actor : OVerlappingActors){ABaseEnemy* Enemy = Cast<ABaseEnemy>(Actor);if (Enemy && Enemy->EnemyMovementStatus != EEnemyMovementStatus::EEMS_Dead){float DistanceToActor = (Enemy->GetActorLocation() - Loation).Size();//记录当前位置Enemy距离MainPlayer位置if (DistanceToActor < MinDistance){MinDistance = DistanceToActor;ClosestDistance = Enemy;}}}AttackTarget = ClosestDistance;
}void AMainPlayer::Die()
{SetMovementStatus(EPlayerMovementStatus::EPMS_Dead);if (EquipWeapon){EquipWeapon->DeactiveAttackCollision();EquipWeapon->DeactiveDisplayMeshCollision();}
}void AMainPlayer::DeathEnd()
{
}

限制敌人死亡后的一切活动状态

  • 基本跟限制玩家差不多,只不过有些位置得多一个玩家是否存活的判断

BaseEnemy.cpp

// Fill out your copyright notice in the Description page of Project Settings.#include "BaseEnemy.h"
#include "Components/SphereComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/CapsuleComponent.h"
#include "AIController.h"
#include "Characters/Player/MainPlayer.h"
#include "Animation/AnimInstance.h"
#include "Kismet/KismetMathLibrary.h"
#include "Kismet/GameplayStatics.h"
#include "Components/WidgetComponent.h"
#include "Blueprint/UserWidget.h"
#include "Components/ProgressBar.h"
#include "Components/BoxComponent.h"
#include "Sound/SoundCue.h"
#include "Engine/SkeletalMeshSocket.h"
#include "Characters/Player/MainPlayer.h"
// Sets default values
ABaseEnemy::ABaseEnemy()
{// 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;ChaseVolume = CreateDefaultSubobject<USphereComponent>(TEXT("ChaseVolume"));ChaseVolume->SetupAttachment(GetRootComponent());ChaseVolume->InitSphereRadius(800.f);ChaseVolume->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);ChaseVolume->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);ChaseVolume->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);AttackVolume = CreateDefaultSubobject<USphereComponent>(TEXT("AttackVolume"));AttackVolume->SetupAttachment(GetRootComponent());AttackVolume->InitSphereRadius(100.f);AttackVolume->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);AttackVolume->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);AttackVolume->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);HealthBarWidgetComponent = CreateDefaultSubobject<UWidgetComponent>(TEXT("HealthBarWidgetComponent"));HealthBarWidgetComponent->SetupAttachment(GetRootComponent());HealthBarWidgetComponent->SetWidgetSpace(EWidgetSpace::Screen);HealthBarWidgetComponent->SetDrawSize(FVector2D(125.f, 10.f));HealthBarWidgetComponent->SetWorldLocation(FVector(0.f, 0.f, 50.f));LeftAttackCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("LeftAttackCollision"));LeftAttackCollision->SetupAttachment(GetMesh(), "LeftAttackSocket");DeactiveLeftAttackCollision();RightAttackCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("RightAttackCollision"));RightAttackCollision->SetupAttachment(GetMesh(), "RightAttackSocket");DeactiveRightAttackCollision();//避免摄像机被敌人给阻挡GetMesh()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Ignore);GetCapsuleComponent()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Ignore);//设置持有属性AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;//初始化默认移动状态EnemyMovementStatus = EEnemyMovementStatus::EEMS_Idle;InterpSpeed = 15.f;bInterpToPlayer = false;MaxHealth = 100.f;Health = MaxHealth;Damage = 5.f;
}// Called when the game starts or when spawned
void ABaseEnemy::BeginPlay()
{Super::BeginPlay();ChaseVolume->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnChaseVolumeOverlapBegin);ChaseVolume->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnChaseVolumeOverlapEnd);AttackVolume->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnAttackVolumeOverlapBegin);AttackVolume->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnAttackVolumeOverlapEnd);LeftAttackCollision->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnLeftAttackCollisionOverlapBegin);LeftAttackCollision->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnLeftAttackCollisionOverlapEnd);RightAttackCollision->OnComponentBeginOverlap.AddDynamic(this, &ABaseEnemy::OnRightAttackCollisionOverlapBegin);RightAttackCollision->OnComponentEndOverlap.AddDynamic(this, &ABaseEnemy::OnRightAttackCollisionOverlapEnd);//获取到HealBar小组件HealthBar = Cast<UProgressBar>(HealthBarWidgetComponent->GetUserWidgetObject()->GetWidgetFromName("HealthBar"));HealthBar->SetPercent(Health / MaxHealth);HealthBar->SetVisibility(ESlateVisibility::Hidden);//一开始不显示血条//拿到ControllerAIController = Cast<AAIController>(GetController());
}// Called every frame
void ABaseEnemy::Tick(float DeltaTime)
{Super::Tick(DeltaTime);if (bInterpToPlayer && HasValidTarget() && IsAlive()){FRotator LookYaw(0.f, UKismetMathLibrary::FindLookAtRotation(GetActorLocation(), UGameplayStatics::GetPlayerPawn(this, 0)->GetActorLocation()).Yaw, 0.f);FRotator InterpRotation = FMath::RInterpTo(GetActorRotation(), LookYaw, DeltaTime, InterpSpeed);SetActorRotation(InterpRotation);}
}// Called to bind functionality to input
void ABaseEnemy::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{Super::SetupPlayerInputComponent(PlayerInputComponent);}void ABaseEnemy::OnChaseVolumeOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{if (OtherActor && IsAlive()){AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);if (Player){//主角进入追逐范围显示血条HealthBar->SetVisibility(ESlateVisibility::Visible);MoveToTarget(Player);}}
}void ABaseEnemy::OnChaseVolumeOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{if (OtherActor && IsAlive()){AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);if (Player){if (AIController){//主角出追逐范围不显示血条HealthBar->SetVisibility(ESlateVisibility::Hidden);//停止移动AIController->StopMovement();}}}
}void ABaseEnemy::OnAttackVolumeOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{if (OtherActor && IsAlive()){AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);if (Player){Player->UpdataAttackTarget();bAttackVolumeOverlap = true;AttackBegin();}}
}void ABaseEnemy::OnAttackVolumeOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{if (OtherActor && IsAlive()){AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);if (Player){bAttackVolumeOverlap = false;if (EnemyMovementStatus!=EEnemyMovementStatus::EEMS_Attacking){MoveToTarget(Player);}}}
}void ABaseEnemy::MoveToTarget(AMainPlayer* Player)
{if (IsAlive()){EnemyMovementStatus = EEnemyMovementStatus::EEMS_MoveToTarget;if (AIController){FAIMoveRequest MoveRequest;MoveRequest.SetGoalActor(Player);//设置移动请求目标MoveRequest.SetAcceptanceRadius(10.f);	//设置移动半径FNavPathSharedPtr NavPath;//会返回路径AIController->MoveTo(MoveRequest, &NavPath);}}
}void ABaseEnemy::AttackBegin()
{if (HasValidTarget() && IsAlive()){//攻击中关闭移动if (AIController){AIController->StopMovement();}if (EnemyMovementStatus != EEnemyMovementStatus::EEMS_Attacking){EnemyMovementStatus = EEnemyMovementStatus::EEMS_Attacking;bInterpToPlayer = true;UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();if (AnimInstance && AttackMontage){float PlayRate = FMath::RandRange(0.9f, 1.1f);FString SectionName = FString::FromInt(FMath::RandRange(1, 3));AnimInstance->Montage_Play(AttackMontage, PlayRate);AnimInstance->Montage_JumpToSection(FName(*SectionName), AttackMontage);}}}
}void ABaseEnemy::AttackEnd()
{bInterpToPlayer = false;if (HasValidTarget() && IsAlive()){EnemyMovementStatus = EEnemyMovementStatus::EEMS_Idle;if (bAttackVolumeOverlap && HasValidTarget() && IsAlive()){AttackBegin();}}
}void ABaseEnemy::OnLeftAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{if (OtherActor && IsAlive()){AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);if (Player){if (Player->HitPaticles){const USkeletalMeshSocket* AttackScoket = GetMesh()->GetSocketByName("LeftAttackSocket");if (AttackScoket){FVector SocketLocation = AttackScoket->GetSocketLocation(GetMesh());UGameplayStatics::SpawnEmitterAtLocation(this, Player->HitPaticles, SocketLocation, FRotator(0.f), true);}if (Player->HitSound){UGameplayStatics::PlaySound2D(this, Player->HitSound);}if (DamageTyClass){UGameplayStatics::ApplyDamage(Player, Damage, AIController, this, DamageTyClass);}}}}
}void ABaseEnemy::OnLeftAttackCollisionOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
}void ABaseEnemy::OnRightAttackCollisionOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{if (OtherActor && IsAlive()){AMainPlayer* Player = Cast<AMainPlayer>(OtherActor);if (Player){if (Player->HitPaticles){const USkeletalMeshSocket* AttackScoket = GetMesh()->GetSocketByName("RightAttackSocket");if (AttackScoket){FVector SocketLocation = AttackScoket->GetSocketLocation(GetMesh());UGameplayStatics::SpawnEmitterAtLocation(this, Player->HitPaticles, SocketLocation, FRotator(0.f), true);}if (Player->HitSound){UGameplayStatics::PlaySound2D(this, Player->HitSound);}if (DamageTyClass){UGameplayStatics::ApplyDamage(Player, Damage/2, AIController, this, DamageTyClass);}}}}
}void ABaseEnemy::OnRightAttackCollisionOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
}void ABaseEnemy::ActiveLeftAttackCollision()
{LeftAttackCollision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);LeftAttackCollision->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);LeftAttackCollision->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);LeftAttackCollision->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
}void ABaseEnemy::DeactiveLeftAttackCollision()
{LeftAttackCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}void ABaseEnemy::ActiveRightAttackCollision()
{RightAttackCollision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);RightAttackCollision->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);RightAttackCollision->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);RightAttackCollision->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
}void ABaseEnemy::DeactiveRightAttackCollision()
{RightAttackCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}float ABaseEnemy::TakeDamage(float Damage, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{if (Health - Damage <= 0.f){Health = FMath::Clamp(Health - Damage, 0.f, MaxHealth);Die();}else{Health -= Damage;}HealthBar->SetPercent(Health / MaxHealth);//更新UI血条return Health;
}void ABaseEnemy::Die()
{EnemyMovementStatus = EEnemyMovementStatus::EEMS_Dead;DeactiveLeftAttackCollision();DeactiveRightAttackCollision();ChaseVolume->SetCollisionEnabled(ECollisionEnabled::NoCollision);AttackVolume->SetCollisionEnabled(ECollisionEnabled::NoCollision);GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);//敌人死亡,主角要更新攻击目标Cast<AMainPlayer>(UGameplayStatics::GetPlayerPawn(this, 0))->UpdataAttackTarget();
}void ABaseEnemy::DeathEnd()
{
}bool ABaseEnemy::HasValidTarget()
{return Cast<AMainPlayer>(UGameplayStatics::GetPlayerPawn(this, 0))->MovementStatus != EPlayerMovementStatus::EPMS_Dead;
}

主角死亡与敌人死亡后延时销毁

  • 首先设置敌人血条在敌人死亡后就隐藏
void ABaseEnemy::Die()
{EnemyMovementStatus = EEnemyMovementStatus::EEMS_Dead;HealthBar->SetVisibility(ESlateVisibility::Hidden);DeactiveLeftAttackCollision();DeactiveRightAttackCollision();ChaseVolume->SetCollisionEnabled(ECollisionEnabled::NoCollision);AttackVolume->SetCollisionEnabled(ECollisionEnabled::NoCollision);GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);//敌人死亡,主角要更新攻击目标Cast<AMainPlayer>(UGameplayStatics::GetPlayerPawn(this, 0))->UpdataAttackTarget();
}
  • 然后去主角的死亡动画中添加DeathEnd的通知,并在动画蓝图中调用
    在这里插入图片描述
    在这里插入图片描述
  • 编写DeathEnd死亡逻辑,首先关闭动画,添加定时器,玩家死后一秒重启游戏
void AMainPlayer::DeathEnd()
{GetMesh()->bPauseAnims = true;GetMesh()->bNoSkeletonUpdate = true;FTimerHandle DeathTimerHandle;auto Lambda = [](){//TODO RestartLevel();};GetWorldTimerManager().SetTimer(DeathTimerHandle, FTimerDelegate::CreateLambda(Lambda), 1.0, false);
}
  • 敌人这边也是一样添加通知动画蓝图中调用,然后编写死亡消失逻辑
    在这里插入图片描述
    在这里插入图片描述
  • 死亡一秒后销毁
void ABaseEnemy::DeathEnd()
{GetMesh()->bPauseAnims = true;GetMesh()->bNoSkeletonUpdate = true;FTimerHandle DeathTimerHandle;auto Lambda = [this](){Destroy();};GetWorldTimerManager().SetTimer(DeathTimerHandle, FTimerDelegate::CreateLambda(Lambda), 1.0, false);
}

玩家死亡重新开始游戏

  • 将上面遗留的RestartLevel函数进行新建然后编写,用UGameplayStatics中的方法进行重新开始游戏
void AMainPlayer::DeathEnd()
{GetMesh()->bPauseAnims = true;GetMesh()->bNoSkeletonUpdate = true;FTimerHandle DeathTimerHandle;auto Lambda = [this](){RestartLevel();};GetWorldTimerManager().SetTimer(DeathTimerHandle, FTimerDelegate::CreateLambda(Lambda), 1.0, false);}void AMainPlayer::RestartLevel()
{FString LevelName = UGameplayStatics::GetCurrentLevelName(this);UGameplayStatics::OpenLevel(this, FName(*LevelName));
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

浏览器没收到返回,后端也没报错,php的json_encode问题bug

今天网站遇到个问题&#xff0c;后端返回异常&#xff0c;但是浏览器状态码200&#xff0c;但是看不到结果。经过排查发现&#xff0c;我们在返回结果的时候使用了json_encode返回给前端&#xff0c;结果里面的字符编码异常&#xff0c;导致json_encode异常&#xff0c;但是php…

禁止linux shell 终端显示完整工作路径,如何让linux bash终端不显示当前工作路径

在操作linux时&#xff0c;默认安装的linux终端会显示当前完整的工作目录&#xff0c;如果目录比较短还是可以接收&#xff0c;如果目录比较长&#xff0c;就显得比较别扭&#xff0c;操作起来不方便&#xff0c;因此需要关闭这种功能。 要关闭这个功能&#xff0c;请按如下步骤…

生命周期评估(LCA)与SimaPro碳足迹分析

SimaPro提供最新的科学方法和数据库以及丰富的数据&#xff0c;使您可以收集和评估产品和流程的环境绩效。通过这种方式&#xff0c;您可以将改变公司产品生命周期的想法提交给您的同事&#xff0c;以便阐明您的业务未来。 SimaPro软件的特点和功能&#xff1a; 完全控制产品生…

供应链和物流的自动化新时代

今天&#xff0c;当大多数人想到物流自动化时&#xff0c;他们会想到设备。机器人、无人机和自主卡车运输在大家的谈话中占主导地位。全自动化仓库的视频在网上流传&#xff0c;新闻主播们为就业问题绞尽脑汁。这种炒作是不完整的&#xff0c;它错过了供应链和物流公司的机会。…

基于安卓android微信小程序的刷题系统

项目介绍 面试刷题系统的开发过程中&#xff0c;采用B / S架构&#xff0c;主要使用jsp技术进行开发&#xff0c;中间件服务器是Tomcat服务器&#xff0c;使用Mysql数据库和Eclipse开发环境。该面试刷题系统包括会员、答题录入员和管理员。其主要功能包括管理员&#xff1a;个…

selenium 简单案例 <批量下载文件> <网页自动化点击上报>

一、批量下载文件 网页分析 点击跳转到下载页面 from selenium import webdriver import timedef get_link_list():# 创建浏览器对象driver webdriver.Chrome(executable_pathrC:\Users\nlp_1\Desktop\chromedriver\chromedriver-win32\chromedriver.exe)url https://www…

几款Java源码扫描工具(FindBugs、PMD、SonarQube、Fortify、WebInspect)

说明 有几个常用的Java源码扫描工具可以帮助您进行源代码分析和检查。以下是其中一些工具&#xff1a; FindBugs&#xff1a;FindBugs是一个静态分析工具&#xff0c;用于查找Java代码中的潜在缺陷和错误。它可以检测出空指针引用、资源未关闭、不良的代码实践等问题。FindBu…

从Github登录的双因子验证到基于时间戳的一次性密码:2FA、OTP与TOTP

Github于2023-03-09推出一项提高软件安全标准的措施&#xff0c;所有在Github上贡献过代码的开发人员在年底前必须完成 2FA&#xff08;Two-factory authentication&#xff0c;双因子认证&#xff09;。初听此事之时&#xff0c;不以为意&#xff0c;因为自己之前就知道双因子…

再探MDG cloud-ready模式!看未来MDG的发展路线

紧跟上一篇博客&#xff0c;我们将更加深入探讨一些MDG Cloud-Ready模式的相关内容。 背景 在2021年9月&#xff0c;Harald Kuck&#xff0c;SAP ABAP Platform老大&#xff0c;介绍了未来ABAP的发展路线&#xff0c;并最终在一年后正式推出了ABAP Cloud。他在会上是这么说的…

担忧CentOS停服?KeyarchOS系统来支撑

担忧CentOS停服&#xff1f;KeyarchOS系统来支撑 近年发生的“微软黑屏门”、“微软操作系统停更”等安全事件&#xff0c;敲响了我国 IT 产业的警钟&#xff0c;建立由我国主导的 IT 产业生态尤为迫切。对此&#xff0c;我国信息技术应用创新行业乘势而起&#xff0c;旨在通过…

基于单片机的光伏发电并网系统设计(论文+源码)

1.系统设计 片作为主控制器。由于太阳能板本身的能量输出受到负载影响&#xff0c;因此需要在太阳能板后面加入一级DC/DC电路&#xff0c;来实现最大功率跟踪&#xff0c;以提高整个系统的效率。接着&#xff0c;由于光伏逆变器需要产生220V的交流电给居民使用&#xff0c;因此…

子虔与罗克韦尔自动化合作 进博会签约自动化净零智造联创中心

11月6日进博会现场&#xff0c;漕河泾罗克韦尔自动化净零智造联创中心合作协议签约暨合作伙伴&#xff08;第一批&#xff09;授牌仪式举办&#xff0c;子虔科技作为联创中心合作伙伴签约&#xff0c;携手共建智能制造&#xff0c;引领行业可持续发展。 图示&#xff1a;子虔科…

BMS基础知识:BMS基本功能,铅酸和锂电池工作原理,电池系统的重要概念!

笔者有话说&#xff1a; 作为BMS从业者来讲&#xff0c;目前接触的BMS系统并不是很高大尚&#xff0c;但基础功能都是有的。 关于BMS的基本功能&#xff0c;工作原理&#xff0c;运行逻辑等&#xff0c;在此做一个梳理&#xff0c;讲一些最基础的扫盲知识&#xff0c;可以作为…

计算机网络:数据链路层

0 本节主要内容 问题描述 解决思路 1 问题描述 数据链路层主要面临四个问题&#xff1a; 封装成帧&#xff1b;透明传输&#xff1b;差错检测&#xff1b;实现相邻节点之间的可靠通信。 1.1 子问题1&#xff1a;封装成帧 怎么知道数据从哪里开始&#xff1f;到哪里结束&a…

校园圈子论坛,交友,帖子内短视频,二手市场,APP小程序H5三端交付,源码交付,支持二开

校园圈子论坛&#xff0c;交友频道&#xff0c;商城&#xff0c;二手市场&#xff0c;活动专区&#xff0c;短视频&#xff0c;从校园生活的方方面面展现出了充满活力和创造力的镜头。这个频道是一个让学生们相互交流、结识新朋友的平台&#xff0c;不仅有交友功能&#xff0c;…

如何使用无代码系统搭建软件平台?有哪些开源无代码开发平台?

无代码是什么 无代码开发&#xff0c;也称为零代码&#xff08;Zero Code&#xff09;开发&#xff0c;是一种技术概念。无代码开发无需代码基础&#xff0c;适合业务人员、IT开发及其他各类人员使用。他们通过无代码开发平台快速构建应用&#xff0c;并适应各种需求变化&#…

抖音汽车租赁小程序技术指南:开发高效便捷的租赁系统

为了更好地满足用户需求&#xff0c;抖音汽车租赁小程序成为一个备受关注的技术解决方案。本文将深入探讨开发高效便捷的汽车租赁系统所需的技术要点&#xff0c;为开发者提供一份实用的技术指南。 小程序架构选择 在搭建抖音汽车租赁小程序时&#xff0c;选择合适的小程序架构…

Arthas 监听 Docker 部署的java项目CPU占比高的信息

1、Linux上安装Arthas wget https://alibaba.github.io/arthas/arthas-boot.jar2、docker ps 查看目标项目的容器ID 3、copy Arthas 到目标容器中 (注意有 &#x1f615; ) docker cp arthas-boot.jar d97e8666666:/4、进入到目标容器目录中 docker exec -it d97e8666666 /b…

根据商品链接获取拼多多商品详情数据接口|拼多多商品详情价格数据接口|拼多多API接口

拼多多&#xff0c;作为中国最大的社交电商之一&#xff0c;为卖家提供了丰富的商品详情接口。这些接口可以帮助卖家快速获取商品信息&#xff0c;提高销售效率。本文将详细介绍如何使用拼多多商品详情接口&#xff0c;以及它的优势和注意事项。 一、拼多多商品详情接口概述 …

centos7中通过minikube安装Kubernetes

minikube是一款开源的Kubernetes集群管理器&#xff0c;它可以帮助您在本地计算机上轻松部署和管理Kubernetes集群。以下是minikube的安装和使用步骤&#xff1a; 安装Docker&#xff1a;如果您还没有安装Docker&#xff0c;可以从Docker官方网站上下载并安装适合您操作系统的…