OwnerComp.GetAIOwner()->GetPawn():通过 AI 控制器获取它当前附身的 Pawn;
Character->Shoot():调用写好的射击逻辑。
五、第三人称射击游戏角色类
代码实现功能:集成战斗、生存与高阶机动逻辑,通过动态生成与销毁 Actor 的方式构建了支持可扩展的多武器循环切换系统,利用重写的伤害处理函数与 GameMode 深度协作以管理角色的生命周期与死亡判定,同时通过多重射线检测技术实现了一个具备防穿墙和地形自适应能力的'智能瞬移'技能,从而在保证游戏物理稳定性的前提下赋予了角色灵活的战术位移手段。
BP_MyCharacter.h
#pragma once #include"CoreMinimal.h" #include"GameFramework/Character.h" #include"BP_MyCharacter.generated.h"classAGun;
UCLASS()
classCRYPTRAIDER_API ABP_MyCharacter : public ACharacter {
GENERATED_BODY()
public:
// Sets default values for this character's propertiesABP_MyCharacter();
protected:
// Called when the game starts or when spawnedvirtualvoidBeginPlay()override;
public:
voidDash();
UFUNCTION(BlueprintPure) boolIsDead()const;
UFUNCTION(BlueprintPure) floatGetHealthPercent()const;
// Called every framevirtualvoidTick(float DeltaTime)override;
// Called to bind functionality to inputvirtualvoidSetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)override;
virtualfloatTakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)override;
voidShoot();
voidSwitchWeapon();
private:
voidMoveForward(float AxisValue);
voidMoveRight(float AxisValue);
voidLookUpRate(float AxisValue);
UPROPERTY(EditAnywhere) float RotationRate =10;
/*UPROPERTY(EditDefaultsOnly) TSubclassOf<AGun> GunClass;*/UPROPERTY(EditDefaultsOnly, Category = "Combat") TArray<TSubclassOf<AGun>> GunClasses;
int32 CurrentGunIndex = 0;
UPROPERTY(EditDefaultsOnly) AGun* Gun;
UPROPERTY(EditDefaultsOnly) float MaxHealth = 100;
UPROPERTY(EditDefaultsOnly) float Health = 0;
// 重置冷却的回调函数voidResetDash();
// 瞬移距离 (默认 15 米)UPROPERTY(EditAnywhere, Category = "Abilities") float DashDistance = 500.0f;
// 冷却时间UPROPERTY(EditAnywhere, Category = "Abilities") float DashCooldown = 5.0f;
// 瞬移后的悬空高度 (增加落地感,建议 50-80)UPROPERTY(EditAnywhere, Category = "Abilities|Dash") float DashHeightOffset = 60.0f;
// 是否可以使用 (冷却标志位)bool bCanDash = true;
// 定时器句柄 (用于计算冷却)
FTimerHandle DashTimerHandle;
// 瞬移时的特效 (可选)UPROPERTY(EditDefaultsOnly, Category = "Abilities") UParticleSystem* DashEffect;
UPROPERTY(EditDefaultsOnly, Category = "Abilities") USoundBase* DashSound;
};
BP_MyCharacter.cpp
#include"Gun.h" #include"BP_MyCharacter.h" #include"Components/CapsuleComponent.h" #include"SimpleShooterGameModeBase.h" #include"GameFramework/GameSession.h" #include"Kismet/GameplayStatics.h"// Sets default values
ABP_MyCharacter::ABP_MyCharacter() {
// 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;
}
// Called when the game starts or when spawnedvoidABP_MyCharacter::BeginPlay(){
Super::BeginPlay();
Health = MaxHealth;
// 隐藏原本模型的骨骼(保持不变)GetMesh()->HideBoneByName(TEXT("weapon_r"), EPhysBodyOp::PBO_None);
//初始化生成第一把枪(如果有的话)
CurrentGunIndex = 0;
if (GunClasses.Num() > 0) {
Gun = GetWorld()->SpawnActor<AGun>(GunClasses[CurrentGunIndex]);
if (Gun) {
Gun->AttachToComponent(GetMesh(), FAttachmentTransformRules::KeepRelativeTransform, TEXT("WeaponSocket"));
Gun->SetOwner(this);
}
}
}
// Called every framevoidABP_MyCharacter::Tick(float DeltaTime){
Super::Tick(DeltaTime);
}
// Called to bind functionality to inputvoidABP_MyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent){
Super::SetupPlayerInputComponent(PlayerInputComponent);
PlayerInputComponent->BindAxis(TEXT("MoveForward"), this , &ABP_MyCharacter::MoveForward);
PlayerInputComponent->BindAxis(TEXT("LookUp"), this, &APawn::AddControllerPitchInput);
PlayerInputComponent->BindAxis(TEXT("LookUpRate"), this, &ABP_MyCharacter::LookUpRate);
PlayerInputComponent->BindAxis(TEXT("MoveRight"), this, &ABP_MyCharacter::MoveRight);
PlayerInputComponent->BindAxis(TEXT("LookRight"), this, &APawn::AddControllerYawInput);
PlayerInputComponent->BindAction(TEXT("Jump"), EInputEvent::IE_Pressed,this,&ACharacter::Jump);
PlayerInputComponent->BindAction(TEXT("Shoot"), EInputEvent::IE_Pressed, this, &ABP_MyCharacter::Shoot);
PlayerInputComponent->BindAction(TEXT("SwitchWeapon"), EInputEvent::IE_Pressed, this, &ABP_MyCharacter::SwitchWeapon);
PlayerInputComponent->BindAction("Dash", IE_Pressed, this, &ABP_MyCharacter::Dash);
}
floatABP_MyCharacter::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser){
float DamageToApplied = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
DamageToApplied = FMath::Min(Health , DamageToApplied);
Health -= DamageToApplied;
UE_LOG(LogTemp, Warning, TEXT("Health is %f"), Health);
if (IsDead()) {
ASimpleShooterGameModeBase* GameMode = GetWorld()->GetAuthGameMode<ASimpleShooterGameModeBase>();
if (GameMode != nullptr) {
GameMode->PawnKilled(this);
}
DetachFromControllerPendingDestroy();
GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
return DamageToApplied;
}
boolABP_MyCharacter::IsDead()const{
return Health <= 0;
}
voidABP_MyCharacter::MoveForward(float AxisValue){
AddMovementInput(GetActorForwardVector() * AxisValue);
}
voidABP_MyCharacter::MoveRight(float AxisValue){
AddMovementInput(GetActorRightVector() * AxisValue);
}
voidABP_MyCharacter::LookUpRate(float AxisValue){
AddControllerPitchInput(AxisValue * RotationRate * GetWorld()->GetDeltaSeconds());
}
voidABP_MyCharacter::Shoot(){
if (Gun != nullptr) {
Gun->PullTrigger();
}
}
floatABP_MyCharacter::GetHealthPercent()const{
return Health / MaxHealth;
}
voidABP_MyCharacter::SwitchWeapon(){
//如果没配置枪,或者只有一把枪,就不切换if (GunClasses.Num() <= 1) return;
//销毁当前手中的枪if (Gun) {
Gun->Destroy();
Gun = nullptr;
}
//计算下一个索引
CurrentGunIndex = (CurrentGunIndex + 1) % GunClasses.Num();
//生成新枪if (GunClasses[CurrentGunIndex]) {
Gun = GetWorld()->SpawnActor<AGun>(GunClasses[CurrentGunIndex]);
if (Gun) {
Gun->AttachToComponent(GetMesh(), FAttachmentTransformRules::KeepRelativeTransform, TEXT("WeaponSocket"));
Gun->SetOwner(this);
// UE_LOG(LogTemp, Warning, TEXT("Switched to weapon index: %d"), CurrentGunIndex);
}
}
}
voidABP_MyCharacter::Dash(){
if (!bCanDash) return;
FVector StartLocation = GetActorLocation();
FVector ForwardVector = GetActorForwardVector();
FVector TargetLocation = StartLocation + (ForwardVector * DashDistance);
FVector FinalLocation = TargetLocation;
UWorld* World = GetWorld();
if (!World) return;
// 防止卡墙检测
FHitResult WallHit;
FCollisionQueryParams Params;
Params.AddIgnoredActor(this);
bool bHitWall = World->LineTraceSingleByChannel(
WallHit, StartLocation, TargetLocation, ECollisionChannel::ECC_Visibility, Params
);
if (bHitWall) {
// 如果撞墙,停在墙壁前 50 单位处,防止模型穿插
FinalLocation = WallHit.Location - (ForwardVector * 50.0f);
}
FVector UpVector = FVector(0, 0, 1);
FVector CeilingCheckStart = FinalLocation;
// 检查头顶上方
FVector CeilingCheckEnd = FinalLocation + (UpVector * 100.0f);
FHitResult CeilingHit;
bool bHitCeiling = World->LineTraceSingleByChannel(
CeilingHit, CeilingCheckStart, CeilingCheckEnd, ECollisionChannel::ECC_Visibility, Params
);
// 只有头顶没撞到东西,才执行'抬高'操作if (!bHitCeiling) {
FinalLocation.Z += DashHeightOffset;
}
if (DashEffect) {
UGameplayStatics::SpawnEmitterAtLocation(World, DashEffect, StartLocation, GetActorRotation());
}
TeleportTo(FinalLocation, GetActorRotation());
if (DashEffect) {
UGameplayStatics::SpawnEmitterAtLocation(World, DashEffect, FinalLocation, GetActorRotation());
}
if (DashSound) {
UGameplayStatics::PlaySoundAtLocation(this, DashSound, StartLocation);
}
bCanDash = false;
World->GetTimerManager().SetTimer(DashTimerHandle, this, &ABP_MyCharacter::ResetDash, DashCooldown, false);
}
voidABP_MyCharacter::ResetDash(){
bCanDash = true;
}