【UE4C++ ActionRougelike-03】可交互对象


【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);
}

3、效果演示

优化攻击动画

文章作者: Woilin
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Woilin !
评论
  目录