【UE4C++ ActionRougelike-03】可交互对象
一、爆炸油桶
1、创建油桶类
新建油桶类SExplosionBarrel,声明所需组件
//SExplosionBarrel.h
UCLASS()
class ACTIONROGUELIKE_API ASExplosionBarrel : public AActor
{
protected:
//静态网格体
UPROPERTY(VisibleAnywhere)
UStaticMeshComponent* MeshComp;
//爆炸作用力
UPROPERTY(VisibleAnywhere)
URadialForceComponent* ForceComp;
//发生碰撞处理函数
UFUNCTION(BlueprintCallable)
void OnActorHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);
}
构造函数中创建对应组件,并注册组件的碰撞事件
//SExplosionBarrel.cpp
ASExplosionBarrel::ASExplosionBarrel()
{
//创建静态网格体
MeshComp = CreateDefaultSubobject<UStaticMeshComponent>("MeshComp");
//开启"模拟物理"
MeshComp->SetSimulatePhysics(true);
//设置碰撞预设为PhysicsActor
MeshComp->SetCollisionProfileName(UCollisionProfile::PhysicsActor_ProfileName);
RootComponent = MeshComp;
//创建爆炸作用力
ForceComp = CreateDefaultSubobject<URadialForceComponent>("ForceComp");
ForceComp->SetupAttachment(MeshComp);
ForceComp->Radius = 500.0f; //作用力范围
ForceComp->ImpulseStrength = 500.0f; //作用力大小
ForceComp->bImpulseVelChange = true; //作用力效果忽略质量大小
//注册组件的碰撞事件,绑定到OnActorHit函数上
MeshComp->OnComponentHit.AddDynamic(this, &ASExplosionBarrel::OnActorHit);
}
void ASExplosionBarrel::OnActorHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
UE_LOG(LogTemp, Log, TEXT("OnActorHit in Explosive Barrel"));
//ForceComp释放作用力
ForceComp->FireImpulse();
}
2、效果演示
二、宝箱
创建C++接口来实现与Actor进行交互,将交互功能封装成一个交互组件
1、创建玩家交互接口
创建交互接口类SInteractionInterface,声明交互函数Interact()
//SInteractionInterface.h
//提供交互的API
class ACTIONROGUELIKE_API ISInteractionInterface
{
public:
//交互功能函数,参数表示发起交互的对象
UFUNCTION(BlueprintCallable,BlueprintNativeEvent)
void Interact(APawn* InstigatorPawn);
};
2、创建宝箱类
创建宝箱类STreasureChestItem,声明所需组件
//STreasureChestItem.h
//继承交互接口类
class ACTIONROGUELIKE_API ASTreasureChestItem : public AActor, public ISInteractionInterface
{
public:
UPROPERTY(EditAnywhere)
float OpenPitch;
//重写互动接口的互动方法
void Interact_Implementation(APawn* InstigatorPawn) override;
protected:
//宝箱体Mesh
UPROPERTY(VisibleAnywhere)
UStaticMeshComponent* BaseMesh;
//宝箱盖子Mesh
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
UStaticMeshComponent* LidMesh;
}
创建蓝图类TreasureChest_BP,并设置对应Mesh,在构造函数中创建宝箱类所需组件,并实现宝箱类继承的交互方法
//STreasureChestItem.cpp
ASTreasureChestItem::ASTreasureChestItem()
{
//创建宝箱底部Mesh
BaseMesh = CreateDefaultSubobject<UStaticMeshComponent>("BaseMesh");
RootComponent = BaseMesh;
//创建宝箱盖子Mesh
LidMesh = CreateDefaultSubobject<UStaticMeshComponent>("LidMesh");
LidMesh->SetupAttachment(BaseMesh);
//打开宝箱时盖子倾斜角度
OpenPitch = 110.0f;
}
//实现宝箱的交互方法
void ASTreasureChestItem::Interact_Implementation(APawn* InstigatorPawn)
{
//修改盖子的Rotation,达到宝箱打开的效果
LidMesh->SetRelativeRotation(FRotator(OpenPitch, 0, 0));
}
3、创建玩家交互组件
此时还差实现玩家能够碰撞查询到玩家面前可交互的Actor是什么,然后与Actor进行交互。将这一功能从Character类中抽离出来实现交互组件,实现交互功能的解耦,也避免Character类越来越庞大。
新建一个ActorComponent类SInteractionComponent表示交互组件,交互组件中声明一个公共方法来表示进行交互。
//SInteractionComponent.h
class ACTIONROGUELIKE_API USInteractionComponent : public UActorComponent
{
public:
//主动进行交互方法
void PrimaryInteract();
}
在.cpp中实现交互方法,通过射线检测,检测角色从眼部开始的射线相交的Actor,如果该Actor实现了交互接口类,则可以进行交互。
//SInteractionComponent.cpp
//主动进行交互
void USInteractionComponent::PrimaryInteract()
{
//碰撞查询参数
FCollisionObjectQueryParams ObjectQueryParams;
ObjectQueryParams.AddObjectTypesToQuery(ECC_WorldDynamic); //WorldDynamic类型时检测
AActor* Owner = GetOwner(); //获取角色拥有者
//EyeLocation作为射线检测的起点,EyeLocation+EyeRotation作为射线检测的终点
FVector EyeLocation;
FRotator EyeRotation;
//实参传递,修改Location和Rotation,将EyeLocation作为Start
Owner->GetActorEyesViewPoint(EyeLocation, EyeRotation);
//设置End
FVector End = EyeLocation + (EyeRotation.Vector()*1000);
//FHitResult Hit; //碰撞结果
//射线检测;参数: 命中结果(FHitReslut)、开始位置、终止位置、碰撞查询参数(FCollisionObjectQueryParams)
//GetWorld()->LineTraceSingleByObjectType(Hit, EyeLocation, End, ObjectQueryParams);
TArray<FHitResult> Hits;
FCollisionShape Shape;
Shape.SetSphere(30.0f);
//最后会得到Hits的TArray,其中每个Hit包含这次Hit的结果信息
bool bBlockHit = GetWorld()->SweepMultiByObjectType(Hits, EyeLocation, End, FQuat::Identity, ObjectQueryParams, Shape);
FColor LinearColors = bBlockHit ? FColor::Green : FColor::Red; //发生碰撞时射线检测为绿,否则为红
for (FHitResult Hit : Hits)
{
AActor* HitActor = Hit.GetActor();
if (HitActor)
{
//如果HitActor实现了交互接口类USInteractionInterface
if (HitActor->Implements<USInteractionInterface>())
{
//Owner强转为APawn
APawn* MyPawn = Cast<APawn>(Owner);
//调用实现的接口
ISInteractionInterface::Execute_Interact(HitActor, MyPawn);
}
//遇到首个交互体后跳出循环
break;
}
}
//射线检测可视化
DrawDebugLine(GetWorld(), EyeLocation, End, LinearColors, false, 2.0f, 0, 2.0f);
}
同之前一样在项目设置->Input中绑定按键与输入事件,在MyCharacter.cpp中绑定事件与处理函数
//MyCharacter.cpp
void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
PlayerInputComponent->BindAction(TEXT("PickUp"), IE_Pressed, this, &AMyCharacter::PickUp);
}
void AMyCharacter::PickUp()
{
if (InteractionComp) {
InteractionComp->PrimaryInteract();
}
}
4、效果演示
三、攻击动画优化
1、添加攻击动画
MyCharacter.h中声明攻击动画AttackAnim(UAnimMontage*)
//AMyCharacter.h
class ACTIONROGUELIKE_API AMyCharacter : public ACharacter
{
//交互组件
UPROPERTY(VisibleAnywhere, Category = "Attack")
USInteractionComponent* InteractionComp;
//攻击动画
UPROPERTY(EditAnywhere, Category = "Attack")
UAnimMontage* AttackAnim;
};
MyCharacter.cpp在PrimaryAttack函数中添加播放动画
//AMyCharacter.cpp
void AMyCharacter::PrimaryAttack()
{
//播放攻击动画
PlayAnimMontage(AttackAnim);
}
效果如下:
问题:魔法攻击类生成位置在初始手部的插槽,而不是完成攻击动画后手部插槽的位置
如何解决?
- 通过一个Timer设置延迟生成攻击效果
- 动画通知/动画事件生成攻击效果
2、延迟攻击特性生成
MyCharacter.h声明定时器TimerHandle_PrimaryAttack和定时器结束处理函数PrimaryAttack_TimeElapsed()
//MyCharacter.h
class ACTIONROGUELIKE_API AMyCharacter : public ACharacter
{
//攻击时的TimerHandle,处理动画导致的攻击生成位置错误
FTimerHandle TimerHandle_PrimaryAttack;
void PrimaryAttack_TimeElapsed();
};
MyCharacter.cpp在PrimaryAttack()函数中播放攻击动画,并设置定时器结束后生成攻击特效
//MyCharacter.cpp
void AMyCharacter::PrimaryAttack()
{
//播放攻击动画
PlayAnimMontage(AttackAnim);
//设置定时器,延迟生成攻击特效,0.2s后调用PrimaryAttack_TimeElapsed()
GetWorldTimerManager().SetTimer(TimerHandle_PrimaryAttack, this, &AMyCharacter::PrimaryAttack_TimeElapsed, 0.2f);
//销毁定时器,当角色抬手攻击时死亡,定时器销毁,不会执行PrimaryAttack_TimeElapsed()
//GetWorldTimerManager.ClearTimer(TimerHandle_PrimaryAttack);
}
//生成攻击特效
void AMyCharacter::PrimaryAttack_TimeElapsed()
{
//获取骨骼体手部的插槽作为魔法攻击类的生成点
FVector HandLocation = GetMesh()->GetSocketLocation("Muzzle_01");
//生成朝向:控制器当前的旋转量 生成位置:手部插槽
FTransform SpawnTM = FTransform(GetControlRotation(),HandLocation);
//参数设置
FActorSpawnParameters SpawnParams;
//设置为:总是生成;粒子在角色手部插槽生成,可能会与角色发生碰撞
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
//从World中的SpawnTM按SpawnParams设置生成Projectileclass类
GetWorld()->SpawnActor<AActor>(Projectileclass ,SpawnTM, SpawnParams);
}