• UE4 C++ ActionRoguelike开发记录


     

    目录

    角色类:SCharacter

    设置摄像机

    弹簧臂和摄像机设置

    设置人物移动和动作绑定

    移动与朝向:MoveForward/MoveRight、Turn/LoopUp

    为人物绑定设置子弹类:SpawnProjectile适配器类

    设置攻击动画以及攻击特效

    普通攻击

    黑洞攻击

    闪现攻击

    ❗为角色添加属性组件:USAttributeComponent

    为角色添加交互组件接口:USInteractionComponent* InteractionComp;

    子弹类:SProejctileBase

    胶囊碰撞体:USphereComponent* SphereComp;

    子弹移动组件:UProjectileMovementComponent* MoveComp;

    粒子系统组件(平A效果):UParticleSystemComponent* EffectComp;

    粒子系统组件(爆炸效果):UParticleSystem* ImpactVFX;

    子弹爆炸音效组件:USoundCue* ImpactSound;

    ?子弹播放声音组件:UAudioComponent* AudioComp;

    子弹击中画面震动效果:TSubclassOf ImpactShake;

    ⭐碰撞事件&爆炸(虚函数):void Explode();(BlueprintNativeEvent)

    普通攻击:SMagicProjectile

    设置并重写OnActorOverlap逻辑:

    设置子弹伤害:DamageAmount

    ❗闪现:SDashProjectile

    重写BeginPlay:设置子弹存活时间

    重写爆炸逻辑Explode_Implementation

    实现闪现传送效果:TeleportInstigator

    设置定时器:FTimerHandle

    黑洞攻击(蓝图):Proj_BlackHole

    径向力Force Strength

    可影响物体Object Types to Affect

    吸进黑洞后要杀掉物体:

    ❗接口类:USGameplayInterface

    USInteractionComponent

    世界中的物体

    爆炸油桶:ASExplosiveBarrel

    设置StaticMesh,径向力

    初始化Actor组件(添加碰撞事件)

    宝箱(实现交互接口):ASItemChest

    血包(实现交互接口):ASPowerupActor

    血包有很多种,因此接口实现逻辑由子类实现:

    吃掉后隐藏血包(计时器):

    血包子类:ASPowerup_HealthPotion

    UI:Widget

    十字准星

    创建Widget并设置:

    游戏开始时添加到屏幕上

    人物血条、Credits、游戏进行时间:Main_HUD

    血条

    Credits:

    游戏进行时间:

    AI

    UE中的AI系统

    行为树:

    ​编辑

    添加导航网格体:NavMeshBoundsVolume​编辑

    游戏AI角色:SAICharacter

    游戏AI控制器:SAIController

    设置行为树

    为SAICharacter设置控制器

    游戏AI逻辑-范围内攻击:USBTService_CheckAttackRange(UBTService)

    范围查询逻辑

    在行为树添加此Service

    游戏AI范围内攻击任务结点:USBTTask_RangedAttack

    AI朝向玩家:Set default focus

    ​编辑 

    环境查询系统:

    创建EQS

    在玩家周围半径内设置EQS生成移动目的地

    AI死亡


    角色类:SCharacter

    设置摄像机

    弹簧臂和摄像机设置

    1. UPROPERTY(VisibleAnywhere)
    2. USpringArmComponent* SpringArmComp;
    3. UPROPERTY(VisibleAnywhere)
    4. UCameraComponent* CameraComp;

    在构造函数中创建这两个类,并设置依赖关系:

    弹簧臂附着在RootComponent,摄像机附着在弹簧臂上

    1. SpringArmComp = CreateDefaultSubobject("SpringArmComp");
    2. SpringArmComp->bUsePawnControlRotation = true;
    3. SpringArmComp->SetupAttachment(RootComponent);
    4. CameraComp = CreateDefaultSubobject("CameraComp");
    5. CameraComp->SetupAttachment(SpringArmComp);
    6. GetCharacterMovement()->bOrientRotationToMovement = true;
    7. bUseControllerRotationYaw = false;

    注意 bUseControllerRotationYaw =false;其效果为:

    bUseControllerRotationYaw=true;

    bUseControllerRotationYaw=false; 

    应该设置为false, 

    同时注意GetCharacterMovement()->bOrientRotationToMovement = true;

    GetCharacterMovement()->bOrientRotationToMovement = false;如下

     GetCharacterMovement()->bOrientRotationToMovement = true;如下

    类似黑暗之魂这种第三人称的移动朝向方式。

    设置人物移动和动作绑定

    1. void ASCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
    2. {
    3. Super::SetupPlayerInputComponent(PlayerInputComponent);
    4. PlayerInputComponent->BindAxis("MoveForward", this, &ASCharacter::MoveForward);
    5. PlayerInputComponent->BindAxis("MoveRight", this, &ASCharacter::MoveRight);
    6. PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput);
    7. PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput);
    8. PlayerInputComponent->BindAction("PrimaryAttack", IE_Pressed, this, &ASCharacter::PrimaryAttack);
    9. // Used generic name 'SecondaryAttack' for binding
    10. PlayerInputComponent->BindAction("SecondaryAttack", IE_Pressed, this, &ASCharacter::BlackHoleAttack);
    11. PlayerInputComponent->BindAction("Dash", IE_Pressed, this, &ASCharacter::Dash);
    12. PlayerInputComponent->BindAction("PrimaryInteract", IE_Pressed, this, &ASCharacter::PrimaryInteract);
    13. PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);
    14. }

    移动与朝向:MoveForward/MoveRight、Turn/LoopUp

    1. void ASCharacter::MoveForward(float Value)
    2. {
    3. FRotator ControlRot = GetControlRotation();
    4. ControlRot.Pitch = 0.0f;
    5. ControlRot.Roll = 0.0f;
    6. AddMovementInput(ControlRot.Vector(), Value);
    7. }
    8. void ASCharacter::MoveRight(float Value)
    9. {
    10. FRotator ControlRot = GetControlRotation();
    11. ControlRot.Pitch = 0.0f;
    12. ControlRot.Roll = 0.0f;
    13. // X = Forward (Red)
    14. // Y = Right (Green)
    15. // Z = Up (Blue)
    16. FVector RightVector = FRotationMatrix(ControlRot).GetScaledAxis(EAxis::Y);
    17. AddMovementInput(RightVector, Value);
    18. }

     其中Turn和LookUp绑定的函数是由APawn中已经写好的内容。

    1. &APawn::AddControllerYawInput
    2. &APawn::AddControllerPitchInput

    这里附上Yaw、Roll、Pitch的内容,结合代码理解:

    UE是左手坐标系:拇指X,食指Y,中指Z(中指向上摆放)
    X:Roll翻滚角
    Y:Pitch俯仰角
    Z:Yaw偏航角

    动图:

    1.俯仰:(Pitch)

    2.翻滚:(Roll)

    3.偏航:(Yaw)

    所以,MoveForward和MoveRight只与Yaw有关,控制偏航角度;Turn也和Yaw有关,控制偏航角度;而LoopUp与Pitch有关,控制上下移动视角。

    为人物绑定设置子弹类:SpawnProjectile适配器类

    • 生成位置:手部Socket(HandLocation)
    • Actor生成:设置碰撞,instigator为角色自身
    • 碰撞预设:
    • 子弹轨迹:起点为手部,终点为十字准星方向
    1. void ASCharacter::SpawnProjectile(TSubclassOf ClassToSpawn)
    2. {
    3. if (ensureAlways(ClassToSpawn))
    4. {
    5. FVector HandLocation = GetMesh()->GetSocketLocation(HandSocketName);
    6. FActorSpawnParameters SpawnParams;
    7. SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
    8. SpawnParams.Instigator = this;
    9. FCollisionShape Shape;
    10. Shape.SetSphere(20.0f);
    11. // Ignore Player
    12. FCollisionQueryParams Params;
    13. Params.AddIgnoredActor(this);
    14. FCollisionObjectQueryParams ObjParams;
    15. ObjParams.AddObjectTypesToQuery(ECC_WorldDynamic);
    16. ObjParams.AddObjectTypesToQuery(ECC_WorldStatic);
    17. ObjParams.AddObjectTypesToQuery(ECC_Pawn);
    18. FVector TraceStart = CameraComp->GetComponentLocation();
    19. // endpoint far into the look-at distance (not too far, still adjust somewhat towards crosshair on a miss)
    20. FVector TraceEnd = CameraComp->GetComponentLocation() + (GetControlRotation().Vector() * 5000);
    21. FHitResult Hit;
    22. // returns true if we got to a blocking hit
    23. if (GetWorld()->SweepSingleByObjectType(Hit, TraceStart, TraceEnd, FQuat::Identity, ObjParams, Shape, Params))
    24. {
    25. // Overwrite trace end with impact point in world
    26. TraceEnd = Hit.ImpactPoint;
    27. }
    28. // find new direction/rotation from Hand pointing to impact point in world.
    29. FRotator ProjRotation = FRotationMatrix::MakeFromX(TraceEnd - HandLocation).Rotator();
    30. FTransform SpawnTM = FTransform(ProjRotation, HandLocation);
    31. GetWorld()->SpawnActor(ClassToSpawn, SpawnTM, SpawnParams);
    32. }
    33. }

    设置攻击动画以及攻击特效

    1. void ASCharacter::StartAttackEffects()
    2. {
    3. PlayAnimMontage(AttackAnim);
    4. UGameplayStatics::SpawnEmitterAttached(CastingEffect, GetMesh(), HandSocketName, FVector::ZeroVector, FRotator::ZeroRotator, EAttachLocation::SnapToTarget);
    5. }

    普通攻击

    1. void ASCharacter::PrimaryAttack()
    2. {
    3. StartAttackEffects();
    4. GetWorldTimerManager().SetTimer(TimerHandle_PrimaryAttack, this, &ASCharacter::PrimaryAttack_TimeElapsed, AttackAnimDelay);
    5. }
    6. void ASCharacter::PrimaryAttack_TimeElapsed()
    7. {
    8. SpawnProjectile(ProjectileClass);
    9. }

    黑洞攻击

    1. void ASCharacter::BlackHoleAttack()
    2. {
    3. StartAttackEffects();
    4. GetWorldTimerManager().SetTimer(TimerHandle_BlackholeAttack, this, &ASCharacter::BlackholeAttack_TimeElapsed, AttackAnimDelay);
    5. }
    6. void ASCharacter::BlackholeAttack_TimeElapsed()
    7. {
    8. SpawnProjectile(BlackHoleProjectileClass);
    9. }

    闪现攻击

    1. void ASCharacter::Dash()
    2. {
    3. StartAttackEffects();
    4. GetWorldTimerManager().SetTimer(TimerHandle_Dash, this, &ASCharacter::Dash_TimeElapsed, AttackAnimDelay);
    5. }
    6. void ASCharacter::Dash_TimeElapsed()
    7. {
    8. SpawnProjectile(DashProjectileClass);
    9. }

    ❗为角色添加属性组件:USAttributeComponent

    为角色添加交互组件接口:USInteractionComponent* InteractionComp;

    子弹类:SProejctileBase

    1. ASProjectileBase::ASProjectileBase()
    2. {
    3. SphereComp = CreateDefaultSubobject("SphereComp");
    4. SphereComp->SetCollisionProfileName("Projectile");
    5. SphereComp->OnComponentHit.AddDynamic(this, &ASProjectileBase::OnActorHit);
    6. RootComponent = SphereComp;
    7. EffectComp = CreateDefaultSubobject("EffectComp");
    8. EffectComp->SetupAttachment(RootComponent);
    9. AudioComp = CreateDefaultSubobject("AudioComp");
    10. AudioComp->SetupAttachment(RootComponent);
    11. MoveComp = CreateDefaultSubobject("ProjectileMoveComp");
    12. MoveComp->bRotationFollowsVelocity = true;
    13. MoveComp->bInitialVelocityInLocalSpace = true;
    14. MoveComp->ProjectileGravityScale = 0.0f;
    15. MoveComp->InitialSpeed = 8000;
    16. ImpactShakeInnerRadius = 0.0f;
    17. ImpactShakeOuterRadius = 1500.0f;
    18. }

    胶囊碰撞体:USphereComponent* SphereComp;

    • 设置名称
    • 设置碰撞预设
    • 设置碰撞触发事件
    • 将RootComponent设为碰撞体

    子弹移动组件:UProjectileMovementComponent* MoveComp;

    • 初始化相关移动属性,比如速度

    粒子系统组件(平A效果):UParticleSystemComponent* EffectComp;

    在cpp中构造函数进行初始化,只需要设置名称与挂载RootComponent。

    因为子弹效果必然要跟随着子弹,故需要附着在RootComponent上。

    粒子系统组件(爆炸效果):UParticleSystem* ImpactVFX;

    在编辑中设置。

    因为爆炸效果只需要在碰撞点触发,故不需要附着在RootComponent上。

    子弹爆炸音效组件:USoundCue* ImpactSound;

    ?子弹播放声音组件:UAudioComponent* AudioComp;

    声音较小,在黑洞攻击时有体现

    子弹击中画面震动效果:TSubclassOf ImpactShake;

    1. UPROPERTY(EditDefaultsOnly, Category = "Effects|Shake")
    2. TSubclassOf ImpactShake;
    3. UPROPERTY(EditDefaultsOnly, Category = "Effects|Shake")
    4. float ImpactShakeInnerRadius;
    5. UPROPERTY(EditDefaultsOnly, Category = "Effects|Shake")
    6. float ImpactShakeOuterRadius;

    ⭐碰撞事件&爆炸(虚函数):void Explode();(BlueprintNativeEvent)

    1. // BlueprintNativeEvent = C++ base implementation, can be expanded in Blueprints
    2. // BlueprintCallable to allow child classes to trigger explosions
    3. // Not required for assignment, useful for expanding in Blueprint later on
    4. UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
    5. void Explode();

    在cpp中实现逻辑:

    • 在爆炸处播放粒子特效ImpactVFX
    • 在爆炸处播放爆炸音效ImpactSound
    • 在爆炸处触发画面震动效果ImpactShake
    1. void ASProjectileBase::OnActorHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
    2. {
    3. Explode();
    4. }
    1. // _Implementation from it being marked as BlueprintNativeEvent
    2. void ASProjectileBase::Explode_Implementation()
    3. {
    4. // Check to make sure we aren't already being 'destroyed'
    5. // Adding ensure to see if we encounter this situation at all
    6. if (ensure(!IsPendingKill()))
    7. {
    8. UGameplayStatics::SpawnEmitterAtLocation(this, ImpactVFX, GetActorLocation(), GetActorRotation());
    9. UGameplayStatics::PlaySoundAtLocation(this, ImpactSound, GetActorLocation());
    10. UGameplayStatics::PlayWorldCameraShake(this, ImpactShake, GetActorLocation(), ImpactShakeInnerRadius, ImpactShakeOuterRadius);
    11. Destroy();
    12. }
    13. }

    普通攻击:SMagicProjectile

    依赖于USAtrributeComponent,人物属性组件,因为要影响人物的血量

    设置并重写OnActorOverlap逻辑:

    构造函数初始化并设置碰撞体触发事件:

    1. ASMagicProjectile::ASMagicProjectile()
    2. {
    3. SphereComp->SetSphereRadius(20.0f);
    4. SphereComp->OnComponentBeginOverlap.AddDynamic(this, &ASMagicProjectile::OnActorOverlap);
    5. DamageAmount = 20.0f;
    6. }

    重写事件逻辑:

    • 更改属性组件AttributeComp(HP)数值
    • 不能伤害自身:OtherActor != GetInstigator()
    1. void ASMagicProjectile::OnActorOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
    2. {
    3. if (OtherActor && OtherActor != GetInstigator())
    4. {
    5. USAttributeComponent* AttributeComp = Cast(OtherActor->GetComponentByClass(USAttributeComponent::StaticClass()));
    6. if (AttributeComp)
    7. {
    8. // minus in front of DamageAmount to apply the change as damage, not healing
    9. AttributeComp->ApplyHealthChange(GetInstigator(), -DamageAmount);
    10. // Only explode when we hit something valid
    11. Explode();
    12. }
    13. }
    14. }

    设置子弹伤害:DamageAmount

    ❗闪现:SDashProjectile

    重写BeginPlay:设置子弹存活时间

    1. void ASDashProjectile::BeginPlay()
    2. {
    3. Super::BeginPlay();
    4. GetWorldTimerManager().SetTimer(TimerHandle_DelayedDetonate, this, &ASDashProjectile::Explode, DetonateDelay);
    5. }

    从Begin之后,DetonateDelay秒之后,调用一次ASDashProjectile。

    虚幻引擎中的Gameplay定时器 | 虚幻引擎5.0文档 (unrealengine.com)

    重写爆炸逻辑Explode_Implementation

    • 清除计时器
    • 爆炸依然播放粒子效果
    • 爆炸后取消粒子效果
    • 爆炸后子弹立即停止移动
    • 爆炸后子弹立即取消碰撞
    • 重新设置定时器,计时触发传送
    1. void ASDashProjectile::Explode_Implementation()
    2. {
    3. // Clear timer if the Explode was already called through another source like OnActorHit
    4. GetWorldTimerManager().ClearTimer(TimerHandle_DelayedDetonate);
    5. UGameplayStatics::SpawnEmitterAtLocation(this, ImpactVFX, GetActorLocation(), GetActorRotation());
    6. EffectComp->DeactivateSystem();
    7. MoveComp->StopMovementImmediately();
    8. SetActorEnableCollision(false);
    9. FTimerHandle TimerHandle_DelayedTeleport;
    10. GetWorldTimerManager().SetTimer(TimerHandle_DelayedTeleport, this, &ASDashProjectile::TeleportInstigator, TeleportDelay);
    11. // Skip base implementation as it will destroy actor (we need to stay alive a bit longer to finish the 2nd timer)
    12. //Super::Explode_Implementation();
    13. }

    实现闪现传送效果:TeleportInstigator

    APawn* Instigator为人物本身,调用actor的TeleportTo函数实现传送AActor::TeleportTo | Unreal Engine Documentation

    1. void ASDashProjectile::TeleportInstigator()
    2. {
    3. AActor* ActorToTeleport = GetInstigator();
    4. if (ensure(ActorToTeleport))
    5. {
    6. // Keep instigator rotation or it may end up jarring
    7. ActorToTeleport->TeleportTo(GetActorLocation(), ActorToTeleport->GetActorRotation(), false, false);
    8. }
    9. }

    设置定时器:FTimerHandle

    FTimerHandle TimerHandle_DelayedDetonate;

    黑洞攻击(蓝图):Proj_BlackHole

    由SProjectileBase继承而来,为蓝图。

    添加了RadialForce径向力,并设置为负数,因为要实现一种黑洞的吸附能力

    径向力Force Strength

    可影响物体Object Types to Affect

    要排除自身

    吸进黑洞后要杀掉物体:

    ❗接口类:USGameplayInterface

    USInteractionComponent

    利用射线检测实现交互 

    1. void ASCharacter::PrimaryInteract()
    2. {
    3. if (InteractionComp)
    4. {
    5. InteractionComp->PrimaryInteract();
    6. }
    7. }
    1. void USInteractionComponent::PrimaryInteract()
    2. {
    3. //碰撞预设
    4. FCollisionObjectQueryParams ObjectQueryParams;
    5. ObjectQueryParams.AddObjectTypesToQuery(ECC_WorldDynamic);
    6. //获取自身
    7. AActor* MyOwner = GetOwner();
    8. //设置碰撞检测位置参数,从眼部开始
    9. FVector EyeLocation;
    10. FRotator EyeRotation;
    11. MyOwner->GetActorEyesViewPoint(EyeLocation, EyeRotation);
    12. FVector End = EyeLocation + (EyeRotation.Vector() * 1000);
    13. //FHitResult Hit;
    14. //bool bBlockingHit = GetWorld()->LineTraceSingleByObjectType(Hit, EyeLocation, End, ObjectQueryParams);
    15. TArray Hits;
    16. float Radius = 30.f;
    17. //射线检测形状
    18. FCollisionShape Shape;
    19. Shape.SetSphere(Radius);
    20. bool bBlockingHit = GetWorld()->SweepMultiByObjectType(Hits, EyeLocation, End, FQuat::Identity, ObjectQueryParams, Shape);
    21. //射线颜色
    22. FColor LineColor = bBlockingHit ? FColor::Green : FColor::Red;
    23. for (FHitResult &Hit : Hits)
    24. {
    25. AActor* HitActor = Hit.GetActor();
    26. if (HitActor)
    27. {
    28. //执行击中物体所实现的接口
    29. if (HitActor->Implements())
    30. {
    31. APawn* MyPawn = Cast(MyOwner);
    32. ISGameplayInterface::Execute_Interact(HitActor, MyPawn);
    33. break;
    34. }
    35. }
    36. DrawDebugSphere(GetWorld(), Hit.ImpactPoint, Radius, 32, LineColor, false, 2.0f);
    37. }
    38. DrawDebugLine(GetWorld(), EyeLocation, End, LineColor, false, 2.0f, 0, 2.0f);
    39. }

    世界中的物体

    爆炸油桶:ASExplosiveBarrel

    设置StaticMesh,径向力

    1. ASExplosiveBarrel::ASExplosiveBarrel()
    2. {
    3. MeshComp = CreateDefaultSubobject("MeshComp");
    4. MeshComp->SetSimulatePhysics(true);
    5. RootComponent = MeshComp;
    6. ForceComp = CreateDefaultSubobject("ForceComp");
    7. ForceComp->SetupAttachment(MeshComp);
    8. // Leaving this on applies small constant force via component 'tick' (Optional)
    9. ForceComp->SetAutoActivate(false);
    10. ForceComp->Radius = 750.0f;
    11. ForceComp->ImpulseStrength = 2500.0f; // Alternative: 200000.0 if bImpulseVelChange = false
    12. // Optional, ignores 'Mass' of other objects (if false, the impulse strength will be much higher to push most objects depending on Mass)
    13. ForceComp->bImpulseVelChange = true;
    14. // Optional, default constructor of component already adds 4 object types to affect, excluding WorldDynamic
    15. ForceComp->AddCollisionChannelToAffect(ECC_WorldDynamic);
    16. // Binding either in constructor or in PostInitializeComponents() below
    17. //MeshComp->OnComponentHit.AddDynamic(this, &ASExplosiveBarrel::OnActorHit);
    18. }

    初始化Actor组件(添加碰撞事件)

    AActor::PostInitializeComponents | Unreal Engine Documentation

    ‎允许Actor在初始化所有组件后在C++端初始化自己,仅在游戏过程中调用‎ 

    1. void ASExplosiveBarrel::PostInitializeComponents()
    2. {
    3. // Don't forget to call parent function
    4. Super::PostInitializeComponents();
    5. MeshComp->OnComponentHit.AddDynamic(this, &ASExplosiveBarrel::OnActorHit);
    6. }

     其实有效果的就是第一句:

    1. void ASExplosiveBarrel::OnActorHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
    2. {
    3. ForceComp->FireImpulse();
    4. UE_LOG(LogTemp, Log, TEXT("OnActorHit in Explosive Barrel"));
    5. // %s = string
    6. // %f = float
    7. // logs: "OtherActor: MyActor_1, at gametime: 124.4"
    8. UE_LOG(LogTemp, Warning, TEXT("OtherActor: %s, at game time: %f"), *GetNameSafe(OtherActor), GetWorld()->TimeSeconds);
    9. FString CombinedString = FString::Printf(TEXT("Hit at location: %s"), *Hit.ImpactPoint.ToString());
    10. DrawDebugString(GetWorld(), Hit.ImpactPoint, CombinedString, nullptr, FColor::Green, 2.0f, true);
    11. // Detailed info on logging in ue4
    12. // https://nerivec.github.io/old-ue4-wiki/pages/logs-printing-messages-to-yourself-during-runtime.html
    13. }

    宝箱(实现交互接口):ASItemChest

     实现交互后,打开/关闭宝箱盖子 ,同时可以在蓝图中设置粒子效果

    1. ASItemChest::ASItemChest()
    2. {
    3. BaseMesh = CreateDefaultSubobject(TEXT("BaseMesh"));
    4. RootComponent = BaseMesh;
    5. LidMesh = CreateDefaultSubobject(TEXT("LidMesh"));
    6. LidMesh->SetupAttachment(BaseMesh);
    7. TargetPitch = 110;
    8. }
    9. void ASItemChest::Interact_Implementation(APawn* InstigatorPawn)
    10. {
    11. LidMesh->SetRelativeRotation(FRotator(TargetPitch, 0, 0));
    12. }

    在蓝图中的Class Settings里可以看到继承了接口:

    实现交互开关宝箱、设置粒子效果

    血包(实现交互接口):ASPowerupActor

    老规矩,首先是简单的构造函数中的初始化

    1. ASPowerupActor::ASPowerupActor()
    2. {
    3. SphereComp = CreateDefaultSubobject("SphereComp");
    4. SphereComp->SetCollisionProfileName("Powerup");
    5. RootComponent = SphereComp;
    6. RespawnTime = 10.0f;
    7. }

    血包有很多种,因此接口实现逻辑由子类实现:

    1. void ASPowerupActor::Interact_Implementation(APawn* InstigatorPawn)
    2. {
    3. // logic in derived classes...
    4. }

    吃掉后隐藏血包(计时器):

    1. void ASPowerupActor::HideAndCooldownPowerup()
    2. {
    3. SetPowerupState(false);
    4. GetWorldTimerManager().SetTimer(TimerHandle_RespawnTimer, this, &ASPowerupActor::ShowPowerup, RespawnTime);
    5. }
    6. void ASPowerupActor::SetPowerupState(bool bNewIsActive)
    7. {
    8. SetActorEnableCollision(bNewIsActive);
    9. // Set visibility on root and all children
    10. RootComponent->SetVisibility(bNewIsActive, true);
    11. }

    血包子类:ASPowerup_HealthPotion

    1. void ASPowerup_HealthPotion::Interact_Implementation(APawn* InstigatorPawn)
    2. {
    3. if (!ensure(InstigatorPawn))
    4. {
    5. return;
    6. }
    7. USAttributeComponent* AttributeComp = USAttributeComponent::GetAttributes(InstigatorPawn);
    8. // Check if not already at max health
    9. if (ensure(AttributeComp) && !AttributeComp->IsFullHealth())
    10. {
    11. // Only activate if healed successfully
    12. if (AttributeComp->ApplyHealthChange(this, AttributeComp->GetHealthMax()))
    13. {
    14. HideAndCooldownPowerup();
    15. }
    16. }
    17. }

    UI:Widget

    十字准星

    创建Widget并设置:

    游戏开始时添加到屏幕上

    create widget:

    人物血条、Credits、游戏进行时间:Main_HUD

    血条

    创建对应组件UI

    为text绑定health事件

    Credits:

    游戏进行时间:

    AI

    UE中的AI系统

    • Navigation Mesh:AI寻路,可达区域
    • Behavior Tree:行为树,AI的大脑
    • Blackboard:AI数据存储
    • Environment Query System:空间查询
    • PawnSensing&AIPerception:感知
    • Debug相关

    行为树:

    设置黑板 

    添加导航网格体:NavMeshBoundsVolume

    游戏AI角色:SAICharacter

    游戏AI控制器:SAIController

    设置行为树

    1. class UBehaviorTree;
    2. /**
    3. *
    4. */
    5. UCLASS()
    6. class ACTIONROGUELIKE_API ASAIController : public AAIController
    7. {
    8. GENERATED_BODY()
    9. protected:
    10. UPROPERTY(EditDefaultsOnly, Category = "AI")
    11. UBehaviorTree* BehaviorTree;
    12. virtual void BeginPlay() override;
    13. };
    1. void ASAIController::BeginPlay()
    2. {
    3. Super::BeginPlay();
    4. if (ensureMsgf(BehaviorTree, TEXT("Behavior Tree is nullptr! Please assign BehaviorTree in your AI Controller.")))
    5. {
    6. RunBehaviorTree(BehaviorTree);
    7. }
    8. // APawn* MyPawn = UGameplayStatics::GetPlayerPawn(this, 0);
    9. // if (MyPawn)
    10. // {
    11. // GetBlackboardComponent()->SetValueAsVector("MoveToLocation", MyPawn->GetActorLocation());
    12. //
    13. // GetBlackboardComponent()->SetValueAsObject("TargetActor", MyPawn);
    14. // }
    15. }

     

    为SAICharacter设置控制器

    游戏AI逻辑-范围内攻击:USBTService_CheckAttackRange(UBTService)

    范围查询逻辑

    • 检查AI与玩家的距离
    • LineOfSightTo:true if controller's pawn can see Other actor
    • 在黑板中添加对应key值,通过C++逻辑设置bool值:BlackBoardComp->SetValueAsBool(AttackRangeKey.SelectedKeyName, (bWithinRange && bHasLOS));

    逻辑为:当且仅当:

    • AI距离玩家小于MaxAttackRange时
    • AI能够看见玩家时 

    为真。 

    1. void USBTService_CheckAttackRange::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
    2. {
    3. Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds);
    4. // Check distance between ai pawn and target actor
    5. UBlackboardComponent* BlackBoardComp = OwnerComp.GetBlackboardComponent();
    6. if (ensure(BlackBoardComp))
    7. {
    8. AActor* TargetActor = Cast(BlackBoardComp->GetValueAsObject("TargetActor"));
    9. if (TargetActor)
    10. {
    11. AAIController* MyController = OwnerComp.GetAIOwner();
    12. APawn* AIPawn = MyController->GetPawn();
    13. if (ensure(AIPawn))
    14. {
    15. float DistanceTo = FVector::Distance(TargetActor->GetActorLocation(), AIPawn->GetActorLocation());
    16. bool bWithinRange = DistanceTo < MaxAttackRange;
    17. bool bHasLOS = false;
    18. if (bWithinRange)
    19. {
    20. bHasLOS = MyController->LineOfSightTo(TargetActor);
    21. }
    22. BlackBoardComp->SetValueAsBool(AttackRangeKey.SelectedKeyName, (bWithinRange && bHasLOS));
    23. }
    24. }
    25. }
    26. }

    在行为树添加此Service

    CheckAttackRange后,会首先进入WithinAttackRange?,若条件为真,则AI执行攻击;若条件为假,则AI向玩家移动,直到到达攻击范围内;

    游戏AI范围内攻击任务结点:USBTTask_RangedAttack

    AI朝向玩家:Set default focus

    环境查询系统:

    创建EQS

    图中蓝色球体为未选中移动目标,绿色球体为已被选择的目标,AI会对EQS所查询的目标进行移动 

    随机选取一个位置让AI移动

    在玩家周围半径内设置EQS生成移动目的地

    AI死亡

    1. void ASAICharacter::OnHealthChanged(AActor* InstigatorActor, USAttributeComponent* OwningComp, float NewHealth, float Delta)
    2. {
    3. if (Delta < 0.0f)
    4. {
    5. if (InstigatorActor != this)
    6. {
    7. SetTargetActor(InstigatorActor);
    8. }
    9. if (ActiveHealthBar == nullptr)
    10. {
    11. ActiveHealthBar = CreateWidget(GetWorld(), HealthBarWidgetClass);
    12. if (ActiveHealthBar)
    13. {
    14. ActiveHealthBar->AttachedActor = this;
    15. ActiveHealthBar->AddToViewport();
    16. }
    17. }
    18. GetMesh()->SetScalarParameterValueOnMaterials(TimeToHitParamName, GetWorld()->TimeSeconds);
    19. if (NewHealth <= 0.0f)
    20. {
    21. // stop BT
    22. AAIController* AIC = Cast(GetController());
    23. if (AIC)
    24. {
    25. AIC->GetBrainComponent()->StopLogic("Killed");
    26. }
    27. // ragdoll
    28. GetMesh()->SetAllBodiesSimulatePhysics(true);
    29. GetMesh()->SetCollisionProfileName("Ragdoll");
    30. // set lifespan
    31. SetLifeSpan(10.0f);
    32. }
    33. }
    34. }

  • 相关阅读:
    Django(10)-项目实战-对发布会管理系统进行测试并获取测试覆盖率
    17基于matlab卡尔曼滤波的行人跟踪算法,并给出算法估计误差结果,判断算法的跟踪精确性,程序已调通,可直接运行,基于MATLAB平台,可直接拍下。
    高等数学(第七版)同济大学 总习题六 个人解答
    637. 二叉树的层平均值-深度优先遍历+层次遍历-力扣双百代码
    复杂度分析
    前端开发中需要搞懂的字符编码知识
    PPT 生成整数序列字典序的r-组合算法
    【藏经阁一起读】(70)__《看见新力量(第七期)》
    【无标题】
    变态跳台阶,剑指offer
  • 原文地址:https://blog.csdn.net/Jason6620/article/details/126376051