做完这两个功能总共花费了一个下午加一个晚上的时间,瞬移魔法弹难度较低,黑洞魔法可能我的理解有误导致消耗时间较长,我会在下面把踩的坑写出来。
加上这个作业,我们一共做了三个魔法子弹了。同时那个老师也说我们可以写一个父类出来,我这里就写了个父类,然后更改了魔法子弹(最初的那个子弹)的父类,让它继承我们写的一个父类。
父类的代码如下:
父类代码与最初的那个魔法子弹并没有什么区别,最多就是我把一些之前在蓝图上实现的功能通过C++实现了出来,我会在代码中用中文注释标出我哪里使用了C++的代码来实现教程内的蓝图。
SFatherMagicProjectile.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "SFatherMagicProjectile.generated.h"
UCLASS()
class ACTIONROGUELIKE_API ASFatherMagicProjectile : public AActor
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere)
class USphereComponent * SphereComp;
UPROPERTY(EditAnywhere)
class UProjectileMovementComponent * MovementComp;
UPROPERTY(EditAnywhere)
class UParticleSystemComponent * ParticleComp;
public:
// Sets default values for this actor's properties
ASFatherMagicProjectile();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
UFUNCTION()
virtual void OnComponentHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, FHitResult Hit);
};
SFatherMagicProjectile.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "SFatherMagicProjectile.h"
#include "Components/SphereComponent.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include "Particles/ParticleSystemComponent.h"
// Sets default values
ASFatherMagicProjectile::ASFatherMagicProjectile()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
FScriptDelegate OnHit;
OnHit.BindUFunction(this, STATIC_FUNCTION_FNAME(TEXT("ASFatherMagicProjectile::OnComponentHit")));
SphereComp = CreateDefaultSubobject<USphereComponent>(TEXT("Sphere Comp"));
SphereComp->SetCollisionProfileName("Projectile");
SphereComp->SetSphereRadius(10.0f, true);
SphereComp->OnComponentHit.Add(OnHit);
RootComponent = SphereComp;
MovementComp = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("Movement Comp"));
MovementComp->InitialSpeed = 1000.0f;
MovementComp->bRotationFollowsVelocity = true;
MovementComp->bInitialVelocityInLocalSpace = true;
MovementComp->ProjectileGravityScale = 0.0f;
ParticleComp = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("Particle Comp"));
ParticleComp->SetupAttachment(SphereComp);
}
// Called when the game starts or when spawned
//此处是beginplay事件,我们在蓝图内最开始判断是否忽略instigator,此处做相同处理,先获取instigator对象,然后进行忽略
void ASFatherMagicProjectile::BeginPlay()
{
Super::BeginPlay();
APawn* Instigator_01 = AActor::GetInstigator();
SphereComp->IgnoreActorWhenMoving(Instigator_01, true);
}
// Called every frame
void ASFatherMagicProjectile::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
//此处处理是判断是否有除了instigator以外的碰撞产生,如果有,那么就消除自身
void ASFatherMagicProjectile::OnComponentHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, FHitResult Hit)
{
APawn* Instigator_01 = AActor::GetInstigator();
if (Instigator_01!=OtherActor)
{
GetWorld()->DestroyActor(this);
DrawDebugSphere(GetWorld(), Hit.ImpactPoint, 10.0f, 16, FColor::Red, false, 2.0f, 0U, 1.0f);
}
}
可以发现,这里的代码基本上和我们在MagicProjectile上写的一致,所以当我们的MagicProjectile将父类修改成这个的时候,就不需要写任何的代码了。
更改后的SMagicProjectile代码如下:
SMagicProjectile.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "SFatherMagicProjectile.h"
#include "SMagicProjectTile.generated.h"
UCLASS()
class ACTIONROGUELIKE_API ASMagicProjectTile : public ASFatherMagicProjectile//此处更改父类
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ASMagicProjectTile();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
};
SMagicProjectile.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "SMagicProjectTile.h"
#include "Components/SphereComponent.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include "Particles/ParticleSystemComponent.h"
// Sets default values
ASMagicProjectTile::ASMagicProjectTile()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
}
// Called when the game starts or when spawned
void ASMagicProjectTile::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void ASMagicProjectTile::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
瞬移魔法弹:难度较低,主要是time Handel的使用以及如何去移动我们的人物的问题。还记得我们前面在生成魔法子弹对象的时候么?我们是不是设置了子弹的instigator这个属性?那么我们就可以通过这个属性获取到我们的主角,最后通过setActorLocation()这个函数实现人物位置的变化。
代码如下:
SDashProjectile.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "SFatherMagicProjectile.h"
#include "SDashProjectile.generated.h"
/**
*
*/
UCLASS()
class ACTIONROGUELIKE_API ASDashProjectile : public ASFatherMagicProjectile
{
GENERATED_BODY()
//定义一个timerhandle
protected:
FTimerHandle TimerHandle_Dash;
public:
ASDashProjectile();
protected:
virtual void BeginPlay()override;
//重写我们的受击函数
void OnComponentHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, FHitResult Hit) ;
//新写一个时间到了要触发的函数
UFUNCTION()
void TimeElasped();
};
SDashProjectile.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "SDashProjectile.h"
ASDashProjectile::ASDashProjectile()
{
PrimaryActorTick.bCanEverTick = true;
}
void ASDashProjectile::BeginPlay()
{
Super::BeginPlay();
//生成这个类的对象的一瞬间,我们就开始计时,等到了两秒之后就调用TimeElasped()函数
GetWorldTimerManager().SetTimer(TimerHandle_Dash, this, &ASDashProjectile::TimeElasped,2.0f);
}
void ASDashProjectile::OnComponentHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, FHitResult Hit)
{
//我们还是需要父类的代码,毕竟当子弹打到物体的时候是需要消失的
Super::OnComponentHit(HitComponent, OtherActor, OtherComp, NormalImpulse, Hit);
//一旦在倒计时结束之间打到了物体,那么这个倒计时就没用了,我们就需要把这个倒计时给删了,也就是停止倒计时
GetWorldTimerManager().ClearTimer(TimerHandle_Dash);
//停止倒计时之后,我们需要设置玩家的位置,也就是平常所说的瞬移
this->GetInstigator()->SetActorLocation(Hit.ImpactPoint);
}
//这个函数是当两秒到了之后调用的函数
void ASDashProjectile::TimeElasped()
{
//进行瞬移操作
this->GetInstigator()->SetActorLocation(this->GetActorLocation());
//删除自身
GetWorld()->DestroyActor(this);
}
写到这里我并没有遇到什么太大的问题,毕竟所用到的知识教程里面都提到过了。
后面的黑洞魔法弹的实现功能,我遇到了点问题,我最开始的思路是在子弹上面增加一个径向力组件,然后每帧都fire Impulse一次,但是实际上的表现非常糟糕,于是我去ue官方的文档去查是否有什么函数能持续给模拟物理的组件施加一个持续的力的函数(提一嘴,UE的中文区的文档写的非常糟糕,一直都是这样,不知道中文区的人们每天在忙什么。。。b站的官号有些视频是比较好的,查东西的优先级我是:b站>CSDN>UE文档,不过这个教程的纯代码的分享我没在CSDN上面看到,有一个哥们写了这个功能,但是他没放代码。。。)最后找到了一个函数AddRadioForce()这个函数并不是给我们的子弹用的,而是给我们子弹影响范围内的物体用的。具体使用方法看代码,我会写注释
SBlackWholeProjectile.h:
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "SFatherMagicProjectile.h"
#include "SBlackWholeProjectile.generated.h"
/**
*
*/
UCLASS()
class ACTIONROGUELIKE_API ASBlackWholeProjectile : public ASFatherMagicProjectile
{
GENERATED_BODY()
//设置一个timerhandle,两秒后消除我们的这个黑洞子弹
FTimerHandle TimeHandle;
//设置一个队列,队列的类型是组件引用,存储的是所有重叠的组件
TArray<UPrimitiveComponent*> OverlapComp;
public:
ASBlackWholeProjectile();
protected:
virtual void BeginPlay()override;
protected:
virtual void Tick(float DeltaTime)override;
//重写受击函数
void OnComponentHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, FHitResult Hit);
//增加一个两秒后调用的函数
UFUNCTION()
void TimeElapsed();
};
SBlackMagicProjectile.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "SBlackWholeProjectile.h"
#include "PhysicsEngine/RadialForceComponent.h"
#include "SFatherMagicProjectile.h"
#include "Components/SphereComponent.h"
#include "Components/PrimitiveComponent.h"
ASBlackWholeProjectile::ASBlackWholeProjectile()
{
PrimaryActorTick.bCanEverTick = true;
//此处更改父类的SohereComp组件,使用另一个碰撞检测配置文件BlackWhole,这个碰撞检测的配置文件是除了摄像机和不可视的组件是忽略外,其它的全是重叠,也就是overlap
SphereComp->SetCollisionProfileName("BlackWhole");
//更改组件半径
SphereComp->SetSphereRadius(500.0f, true);
}
void ASBlackWholeProjectile::BeginPlay()
{
Super::BeginPlay();
//事件开始的时候开始计时
GetWorldTimerManager().SetTimer(TimeHandle, this, &ASBlackWholeProjectile::TimeElapsed, 2.0f);
}
void ASBlackWholeProjectile::Tick(float DeltaTime)
{
//每帧都获取和SphereComp重叠的组件并全部丢到列表里面
SphereComp->GetOverlappingComponents(OverlapComp);
//队列表里面的组件进行遍历
for (int32 i = 0 ; i<OverlapComp.Num();i++)
{
//获取组件
UPrimitiveComponent* PrimComp = OverlapComp[i];
//如果组件存在而且组件的模拟物理属性为真的话
if (PrimComp&&PrimComp->IsSimulatingPhysics())
{
//初始化径向力
const float SphereRadius = SphereComp->GetScaledSphereRadius();
const float ForseStrength = -2000.0f;
//增加径向力,力的方向是子弹的位置,半径和力的大小是上面提到的两行代码,力的衰减是RIF常量,冲量速度变更为true
PrimComp->AddRadialForce(GetActorLocation(), SphereRadius, ForseStrength, ERadialImpulseFalloff::RIF_Constant, true);
}
}
}
//重写的受击函数,debug用
void ASBlackWholeProjectile::OnComponentHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, FHitResult Hit)
{
FString Text = FString::Printf(TEXT("Fired"));
DrawDebugString(GetWorld(), Hit.ImpactPoint, Text, nullptr, FColor::Blue, 2.0f, true, 1.0f);
}
//两秒后调用的函数
void ASBlackWholeProjectile::TimeElapsed()
{
GetWorld()->DestroyActor(this);
}
大伙对AddRadialForce()函数中的ERadialImpulseFalloff::RIF_Constant可能不太熟悉,那么看这个截图可能能理解一点
我们可以看到,径向力组件的属性里面有一个衰减,各位去看一下这个衰减对应的英文,是Falloff,就是ERadialImpulseFalloff,RIF_Constant也就是RIF常量。说白了这个参数就是设置这个属性的。
各位也能发现,我们其实不是给子弹加一个负向的力,而是给子弹覆盖范围的所有物体施加一个负向的力。这个是我踩的一个思维的坑,施加力的对象与我的认知是相反的。