目录
[1.1 上下浮动效果](#1.1 上下浮动效果)
[1.2 拥有碰撞检测](#1.2 拥有碰撞检测)
[1.3 拥有静态网格体组件](#1.3 拥有静态网格体组件)
[4.1 下载动画资源](#4.1 下载动画资源)
[4.2 动画重定向](#4.2 动画重定向)
[4.2.1 创建Mixamo的IK绑定](#4.2.1 创建Mixamo的IK绑定)
[4.2.2 创建Echo的IK绑定](#4.2.2 创建Echo的IK绑定)
[4.2.3 创建IK重定向器](#4.2.3 创建IK重定向器)
效果

步骤
一、创建Item类
Item类派生自Actor类,该类主要作用是可以上下浮动、拥有碰撞检测以及拥有静态网格体组件。
首先在该类中实现上下浮动的功能,这里根据公式的规律来让item在Z轴方向上下运动。因此这里在"Item.h"中定义如下3个变量和1个函数,Amplitude为公式中的A(振幅),RunningTime为公式中的t。InitialLocation表示item的初始位置。TransformedSin()用于得出公式中的z值。
1.1 上下浮动效果

InitialLocation通过在BeginPlay中获取1次item的初始位置

在"Item.cpp"中每帧更新当前运行时间"RunningTime",TransformedSin输出初始Z坐标加上正弦函数值

在蓝图中只需要每帧设置item的位置就可以实现上下浮动效果

1.2 拥有碰撞检测
为了实现碰撞检测,这里先在"Item.h"中定义球形碰撞组件

在"Item.cpp"构造函数中创建球形碰撞组件,注意需先引入头文件:
#include "Components/SphereComponent.h"

定义OnSphereOverlap和OnSphereEndOverlap函数,表示有物体接触和离开item碰撞检测区域后要做的事。使用virtual关键字是为了在子类Weapon中重写这两个函数。

在BeginPlay中设置物体接触和离开item时的委托函数分别为OnSphereOverlap和OnSphereEndOverlap

这里可以先不实现有物体接触和离开item碰撞检测区域后要做的事,等在继承Item类的Weapon类中再具体实现

1.3 拥有静态网格体组件
在"Item.h"中声明静态网格体组件ItemMesh

构造函数中创建组件

二、创建Weapon类
在Quixel Bridge中添加一把剑的资产

添加后项目浏览器包含如下资产

新建Weapon类继承自Item类


代码创建好后,再创建一个基于Weapon的蓝图类

这里命名为"BP_Weapon"

打开"BP_Weapon",设置静态网格体,并修改球形碰撞区域半径以适应网格体大小

为了让剑产生上下浮动效果,需要在蓝图中添加如下节点

在"Weapon.h"中申明重写父类的OnSphereOverlap和OnSphereEndOverlap函数

三、附加Actor到Sockets
现有有了剑,接下来要解决的问题是如何让剑握在角色手里,这里用到的是scokets,可以用来将actor添加到骨骼网格体的骨骼上。
找打角色的骨骼网格体并打开

鼠标点击hand_r,点击添加插槽

这里命名为"RightHandSocket"

添加预览资产为剑

可以看到此时的Socket位置和方向不对,需要调整

如下图是调整后的socket位置和旋转

在"预览场景设置"中可以预览角色播放动画时的姿势

四、准备角色动画
4.1 下载动画资源
访问Mixamo,由于Mixamo动画不能直接用在我们的角色上,因此先下载Mixamo角色"XBot"

将下载后的.fbx文件导入UEEditor,直接点击"导入所有"

导入后文件如下

在Mixamo中下载动画资源,这里先下载持剑站立和攻击的动画

导入后只保留如下动画序列资产

4.2 动画重定向
4.2.1 创建Mixamo的IK绑定
新建"IK绑定"

这里命名为"IKRig_XBot"

打开"IKRig_XBot",预览骨骼网格体选择"XBot"

右键点击"Hips",设置其为重定向根

右键点击"Hips",选择"新建重定向链"

链名称设置为"Root",然后点击"添加链"

选中脊柱的三个骨骼,选择"新建重定向链"


同样的,选择脖子和头建立链条

选中"RightShoulder"新建链条

这里命名链条为"RightClavicle"

选中"LeftShoulder",新建链条

这里命名为"LeftClavicle"

选择右臂三根骨骼,新建链条,命名为"RightArm"

选择左臂三根骨骼,新建链条,命名为"LeftArm"

选择左右手拇指新建链条


左右手食指


左右手中指


左右手无名指


左右手小指


左右腿

现在我们有了Mixamo骨骼上所有IK绑定的链条,接下来创建我们的角色"Echo"的IK绑定
4.2.2 创建Echo的IK绑定
新建一个IK绑定,这里命名为"IKRig_Echo"

打开"IKRig_Echo",预览骨骼网格体设置为"Echo"

接下来只需要创建与"IKRig_XBot"那些链条相对应的链条即可。首先设置骨骼"pelvis"为重定向根。

选择Root骨骼,新建重定向链,这里链名称同样设置为"Root"


建立脊椎链,注意链条名称要和"IKRig_XBot"中的链条名称保持一致,这样UE才能对应上。

头部链条

左右锁骨


左右手臂


左右手食指


左右手中指


左右手小指


左右手无名指


左右手拇指


左右腿


4.2.3 创建IK重定向器
创建一个IK重定向器

这里命名为"RTG_XBot"

打开"RTG_XBot",设置源IKRig资产为"IKRig_XBot",设置目标IKRig资产为"IKRig_Echo"

为了方便观察,设置目标网格体偏移为150

新建一个重定向姿势,这里命名为"ToEcho"


点击编辑"重定向姿势"

调整手臂、腿部姿势,尽量保证和Mixamo角色姿势一致


注意在俯视角中看到手臂也不是直的,需要调整


再调整一下手掌

调整好后可以预览动画观察动作是否正常

选中要重定向的动画,然后点击"导出选定动画"

导出动画序列如下

打开动画序列,可以再调整一下socket的位置

代码
(1) Item类
cpp
// Item.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Item.generated.h"
UCLASS()
class SLASH_API AItem : public AActor
{
GENERATED_BODY()
public:
AItem();
virtual void Tick(float DeltaTime) override;
protected:
virtual void BeginPlay() override;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Sine Parameters")
float Amplitude = 30.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Sine Parameters")
float RunningTime;
UPROPERTY(EditAnywhere, BlueprintReadOnly)
FVector InitialLocation;
UFUNCTION(BlueprintPure)
float TransformedSin();
UFUNCTION()
virtual void OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
UFUNCTION()
virtual void OnSphereEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
private:
UPROPERTY(VisibleAnywhere)
UStaticMeshComponent* ItemMesh;
UPROPERTY(VisibleAnywhere)
class USphereComponent* Sphere;
};
cpp
// Item.cpp
#include "Items/Item.h"
#include "Slash/DebugMacros.h"
#include "Components/SphereComponent.h"
AItem::AItem()
{
PrimaryActorTick.bCanEverTick = true;
ItemMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ItemMeshComponent"));
RootComponent = ItemMesh;
Sphere = CreateDefaultSubobject<USphereComponent>(TEXT("Sphere"));
Sphere->SetupAttachment(RootComponent);
}
void AItem::BeginPlay()
{
Super::BeginPlay();
InitialLocation = GetActorLocation();
Sphere->OnComponentBeginOverlap.AddDynamic(this, &AItem::OnSphereOverlap);
Sphere->OnComponentEndOverlap.AddDynamic(this, &AItem::OnSphereEndOverlap);
}
void AItem::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
RunningTime += DeltaTime;
}
float AItem::TransformedSin()
{
return InitialLocation.Z + Amplitude * FMath::Sin(RunningTime * 5.f);
}
void AItem::OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
const FString OtherActorName = OtherActor->GetName();
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(1, 5.f, FColor::Red, OtherActorName);
}
}
void AItem::OnSphereEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
const FString OtherActorName = FString("Ending Overlap with: ") + OtherActor->GetName();
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(1, 5.f, FColor::Blue, OtherActorName);
}
}
(2)Weapon类
cpp
// Weapon.h
#pragma once
#include "CoreMinimal.h"
#include "Items/Item.h"
#include "Weapon.generated.h"
/**
*
*/
UCLASS()
class SLASH_API AWeapon : public AItem
{
GENERATED_BODY()
protected:
virtual void OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) override;
virtual void OnSphereEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex) override;
};
cpp
// Weapon.cpp
#include "Items/Weapons/Weapon.h"
void AWeapon::OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
Super::OnSphereOverlap(OverlappedComponent, OtherActor, OtherComp, OtherBodyIndex, bFromSweep, SweepResult);
}
void AWeapon::OnSphereEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
Super::OnSphereEndOverlap(OverlappedComponent, OtherActor, OtherComp, OtherBodyIndex);
}