虚幻引擎5 C++ 工程修改
基类继承修改
之前继承的基类是 AWheeledVehicle,查看UE5源码
class UE_DEPRECATED(4.26, "PhysX is deprecated. Use the AWheeledVehiclePawn from the ChaosVehiclePhysics Plugin.") AWheeledVehicle;
将被废弃了... 与时俱进,根据提示更改基类为 AWheeledVehiclePawn。这样我们需要引入新的模块ChaosVehiclePhysics。
使用VS2019打开UEVehicleProject.sln。打开UEVehicleProject.Build.cs文件。添加"ChaosVehiclePhysics"模块。
...
public UEVehicleProject(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "PhysXVehicles", "ChaosVehicles" });
...
}
...
打开UEVehicleProject.uproject文件。添加"ChaosVehiclePhysics"模块,并禁用掉"PhysXVehicles"模块。
{
"FileVersion": 3,
"EngineAssociation": "5.0",
"Category": "",
"Description": "",
"Modules": [
{
"Name": "UEVehicleProject",
"Type": "Runtime",
"LoadingPhase": "Default",
"AdditionalDependencies": [
"Engine"
]
}
],
"Plugins": [
{
"Name": "PhysXVehicles",
"Enabled": false
},
{
"Name": "ChaosVehiclesPlugin",
"Enabled": true
},
{
"Name": "Bridge",
"Enabled": true,
"SupportedTargetPlatforms": [
"Win64",
"Mac",
"Linux"
]
}
]
}
当然,也可以在UE5工程中添加"ChaosVehiclePhysics"模块,搜索添加即可,这里就不截图了。
这样,我们修改基类的继承即可,当然轮子也需要修改,因为上篇文章没有涉及,在下文中讲解即可。
...
UCLASS()
class UEVEHICLEPROJECT_API ABaseVehicle : public AWheeledVehiclePawn
{
...
};
还需要修改的还有动画蓝图,要将Wheel Handler更改为Wheel Controller,来控制车轮动画。由于提前禁掉"PhysXVehicles"模块,这里只能重新做一遍。

代码优化修改
由于ABaseVehicle类的代码量过多,后续还需要添加人车交互的代码,这里将cpp的代码进行了按功能分离,对功能查找和阅读都是十分有益的。

•BaseVehicle.cpp 一些虚函数 BeginPlay、Tick 等•BaseVehicle_Init.cpp 初始化函数•BaseVehicle_Basic.cpp 基础交互•BaseVehicle_Drive.cpp 驾驶交互
虚幻引擎5 C++ 汽车摄像机交互
摄像机与汽车交互,分为汽车内部和汽车外部两个摄像机。
汽车摄像机交互-内外摄像机切换控制 展示:

汽车摄像机交互-内部摄像机转向控制 展示:

汽车摄像机交互-外部摄像机转向控制 展示:

汽车摄像机交互-外部摄像机缩放控制 展示:

首先再回顾一下ABaseVehicle 的构造函数。
...
ABaseVehicle::ABaseVehicle()
{
InitBaseValue(); // 初始化 基础 数值
InitBaseMesh(); // 初始化 基础 模型
InitCameraComponent(); // 初始化 摄像机 组件
InitLightComponent(); // 初始化 灯光 组件
InitBrakeSystemComponent(); // 初始化 刹车系统 组件
InitSteeringWheelComponent(); // 初始化 方向盘 组件
InitExhaustComponent(); // 初始化 尾气 组件
InitDashboardComponent(); // 初始化 仪表盘 组件
InitSoundComponent(); // 初始化 声音 组件
InitVehicleMovementComponent(); // 初始化 轮胎系统 组件
InitDoorComponent(); // 初始化 车门 组件
InitColorComponent(); // 初始化 颜色 组件
}
...
先补充一下InitBaseMesh()的初始化,设置汽车开启物理和碰撞检测。
...
void ABaseVehicle::InitBaseMesh()
{
...
VehicleMesh->bBlendPhysics = true;
VehicleMesh->SetCollisionProfileName(UCollisionProfile::Vehicle_ProfileName);
}
这里摄像机交互,我们关注InitCameraComponent()函数,摄像机组件的初始化。
BaseVehicle.h 中相关摄像机的定义,注意基类已经更改成了AWheeledVehiclePawn,后续将不再提示。
...
UCLASS()
class UEVEHICLEPROJECT_API ABaseVehicle : public AWheeledVehiclePawn
{
GENERATED_BODY()
public:
ABaseVehicle();
...
// 摄像机控制
// 切换摄像机
UFUNCTION(BlueprintCallable, Category = "VehicleCamera")
void VehicleChangeCamera(ECameraType camera_type);
// 弹簧臂缩放
UFUNCTION(BlueprintCallable, Category = "VehicleCamera")
void CameraSpringArmZoomIn();
UFUNCTION(BlueprintCallable, Category = "VehicleCamera")
void CameraSpringArmZoomOut();
// 摄像机转向
UFUNCTION(BlueprintCallable, Category = "VehicleCamera")
void CameraTurn(float axis_value, bool is_rate = false);
// 摄像机 LookUp
UFUNCTION(BlueprintCallable, Category = "VehicleCamera")
void CameraLookUp(float axis_value, bool is_rate = false);
...
protected:
virtual void BeginPlay() override;
...
void InitCameraComponent(); // 初始化 摄像机 组件
...
protected:
...
// 机器摇臂
UPROPERTY(VisibleAnywhere, Category = "Camera", meta = (AllowPrivateAccess = "true"))
USpringArmComponent* SpringArm;
// 外部摄像机
UPROPERTY(VisibleAnywhere, Category = "Camera", meta = (AllowPrivateAccess = "true"))
UCameraComponent* ExternalCamera;
//UPROPERTY(VisibleAnywhere, Category = "Camera", meta = (AllowPrivateAccess = "true"))
//USceneComponent* Scene;
// 内部摄像机
UPROPERTY(VisibleAnywhere, Category = "Camera", meta = (AllowPrivateAccess = "true"))
UCameraComponent* InternalCamera;
...
ECameraType CameraType;
// 基础旋转角度
float BaseTurnRate;
// 基础看望角度
float BaseLookUpRate;
float LookUpInput;
...
};
BaseVehicle_Init.cpp 中 初始化的相关实现。
void ABaseVehicle::InitCameraComponent()
{
// 本在InitBaseValue() 函数中初始化,简化讲解写在这里。
BaseTurnRate = 1.0f;
BaseLookUpRate = 1.0f;
LookUpInput = 0.0f;
///////////////////////////////////////////////////////////////////////////////////
SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
SpringArm->SetupAttachment(VehicleMesh);
// 设置相对位置
SpringArm->SetRelativeLocation(FVector(0.f, 0.f, 120.f));
SpringArm->SetRelativeRotation(FRotator(-15.f, 0.f, 0.f));
SpringArm->TargetArmLength = 700.f;
SpringArm->bInheritPitch = false;
SpringArm->bInheritYaw = true;
SpringArm->bInheritRoll = false;
SpringArm->bEnableCameraLag = true;
SpringArm->bEnableCameraRotationLag = true;
SpringArm->CameraLagSpeed = 8.0f;
SpringArm->CameraRotationLagSpeed = 2.5f;
ExternalCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("ExternalCamera"));
ExternalCamera->FieldOfView = 90.0f;
ExternalCamera->SetupAttachment(SpringArm);
//设置车内相机的位置
InternalCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("InternalCamera"));
// 大体位置,不同类型车 蓝图调整位置
InternalCamera->SetRelativeLocation(FVector(9.25f, -35.7f, 125.8f));
InternalCamera->FieldOfView = 110.0f;
InternalCamera->SetupAttachment(VehicleMesh);
}
BaseVehicle_Drive.cpp 中 摄像机交互的实现。
切换摄像机的实现:
void ABaseVehicle::VehicleChangeCamera(ECameraType camera_type)
{
switch (camera_type)
{
case ECameraType::ECT_External:
ExternalCamera->Activate(true);
InternalCamera->Deactivate();
break;
case ECameraType::ECT_Internal:
InternalCamera->Activate(true);
ExternalCamera->Deactivate();
break;
case ECameraType::ECT_Count:
break;
default:
break;
}
//ECameraType tmpCamera = (ECameraType)((uint8)CameraType + 1);
//if (tmpCamera == ECameraType::ECT_Count) tmpCamera = ECameraType::ECT_External;
CameraType = camera_type;
}
缩放摄像机的实现:
void ABaseVehicle::CameraSpringArmZoomIn()
{
if (SpringArm->TargetArmLength > 300.0f)
{
SpringArm->TargetArmLength = SpringArm->TargetArmLength - 20.0f;
}
}
void ABaseVehicle::CameraSpringArmZoomOut()
{
if (SpringArm->TargetArmLength < 1000.f)
{
SpringArm->TargetArmLength = SpringArm->TargetArmLength + 20.0f;
}
}
摄像机的转向和朝向的实现:
void ABaseVehicle::CameraTurn(float axis_value, bool is_rate /*= false*/)
{
float CameraYaw = axis_value;
if (is_rate)
CameraYaw = axis_value * BaseTurnRate * GetWorld()->DeltaTimeSeconds;
FRotator CameraRotator = FRotator(0.0f, CameraYaw, 0.0f);
InternalCamera->AddRelativeRotation(CameraRotator);
SpringArm->AddRelativeRotation(CameraRotator);
}
void ABaseVehicle::CameraLookUp(float axis_value, bool is_rate /*= false*/)
{
if (is_rate)
LookUpInput = axis_value * BaseLookUpRate * GetWorld()->DeltaTimeSeconds;
else
LookUpInput = axis_value;
// Pitch 旋转限制
FRotator spring_arm_rotator = SpringArm->GetRelativeRotation();
float spring_arm_pitch = spring_arm_rotator.Pitch;
if ((spring_arm_pitch < -66.6f && LookUpInput < 0.0f) == false)
{
if ((spring_arm_pitch > 0.0f && LookUpInput > 0.0f) == false)
{
FRotator CameraRotator = FRotator( LookUpInput, 0.0f, 0.0f);
InternalCamera->AddRelativeRotation(CameraRotator);
SpringArm->AddRelativeRotation(CameraRotator);
}
}
}
BaseVehicle.cpp 的 BeginPlay() 函数中,将摄像机设置成为了外部摄像机。
void ABaseVehicle::BeginPlay()
{
Super::BeginPlay();
ExternalCamera->SetAutoActivate(false);
ExternalCamera->Deactivate();
InternalCamera->SetAutoActivate(false);
InternalCamera->Deactivate();
//CameraType = ECameraType::ECT_External;
VehicleChangeCamera(ECameraType::ECT_External);
...
}
虚幻引擎5 C++ 汽车其他交互
再看驾驶交互之前,有必要来看一看与汽车相关的其他交互,都是为汽车驾驶做完善。
汽车其他交互-方向盘交互 展示:

汽车其他交互-仪表盘交互 展示:

其他交互,我们关注InitBrakeSystemComponent()、InitSteeringWheelComponent()、InitExhaustComponent()、InitDashboardComponent()、InitSoundComponent()函数。
分别对刹车系统、方向盘、尾气、仪表盘、声音等组件进行初始化。
BaseVehicle.h 中相关的定义。
UCLASS()
class UEVEHICLEPROJECT_API ABaseVehicle : public AWheeledVehicle
{
GENERATED_BODY()
public:
ABaseVehicle();
...
protected:
...
// 卡钳控制
void VehicleCaliperRotation(bool left_side, FName in_socket_name, USceneComponent* caliper, bool front);
// 仪表盘控制
void VehicleMarkerRotation(USceneComponent* marker, float rotation, float rot_multiplier);
...
void InitBrakeSystemComponent(); // 初始化 刹车系统 组件
void InitSteeringWheelComponent(); // 初始化 方向盘 组件
void InitExhaustComponent(); // 初始化 尾气 组件
void InitDashboardComponent(); // 初始化 仪表盘 组件
void InitSoundComponent(); // 初始化 声音 组件
...
protected:
...
// 刹车系统 卡钳
UPROPERTY(VisibleAnywhere, Category = "BrakeSystem", meta = (AllowPrivateAccess = "true"))
UStaticMeshComponent* FrontCaliperL;
UPROPERTY(VisibleAnywhere, Category = "BrakeSystem", meta = (AllowPrivateAccess = "true"))
UStaticMeshComponent* FrontCaliperR;
UPROPERTY(VisibleAnywhere, Category = "BrakeSystem", meta = (AllowPrivateAccess = "true"))
UStaticMeshComponent* RearCaliperL;
UPROPERTY(VisibleAnywhere, Category = "BrakeSystem", meta = (AllowPrivateAccess = "true"))
UStaticMeshComponent* RearCaliperR;
// 方向盘
UPROPERTY(VisibleAnywhere, Category = "SteeringWheel", meta = (AllowPrivateAccess = "true"))
UStaticMeshComponent* SteeringWheel;
// 方向盘 双手握点 IK动画使用
UPROPERTY(VisibleAnywhere, Category = "SteeringWheel", meta = (AllowPrivateAccess = "true"))
UStaticMeshComponent* WheelHandlerL;
UPROPERTY(VisibleAnywhere, Category = "SteeringWheel", meta = (AllowPrivateAccess = "true"))
UStaticMeshComponent* WheelHandlerR;
// 尾气特效
UPROPERTY(VisibleAnywhere, Category = "Exhaust", meta = (AllowPrivateAccess = "true"))
UParticleSystemComponent* SmokeL;
UPROPERTY(VisibleAnywhere, Category = "Exhaust", meta = (AllowPrivateAccess = "true"))
UParticleSystemComponent* SmokeR;
// RPM仪表盘
UPROPERTY(VisibleAnywhere, Category = "Dashboard", meta = (AllowPrivateAccess = "true"))
UStaticMeshComponent* RPMMarker;
// 车速仪表盘
UPROPERTY(VisibleAnywhere, Category = "Dashboard", meta = (AllowPrivateAccess = "true"))
UStaticMeshComponent* SpeedMarker;
// 声音
UPROPERTY(VisibleAnywhere, Category = "Sound", meta = (AllowPrivateAccess = "true"))
UAudioComponent* EngineSound;
...
// 尾气特效
UParticleSystem* StillSmokeParticle; // 静止时烟雾
UParticleSystem* MoveSmokeParticle; // 运动时烟雾
...
FVector WheelHandlerPosL;
FRotator WheelHandlerRotL;
FVector WheelHandlerPosR;
FRotator WheelHandlerRotR;
// 声音
USoundBase* StartSound;
USoundBase* ImpactSound;
...
}
BaseVehicle_Init.cpp 中 初始化的相关实现。
InitBrakeSystemComponent()函数,刹车系统卡钳组件的实现:
void ABaseVehicle::InitBrakeSystemComponent()
{
static ConstructorHelpers::FObjectFinder<UStaticMesh> sm_caliper(TEXT("StaticMesh'/Game/Meshs/SM_caliper.SM_caliper'"));
if (sm_caliper.Succeeded() == false) return;
/*
static ConstructorHelpers::FObjectFinder<UMaterialInterface> mi_dark(TEXT("/Game/Materials/Material_Instances/MI_Dark_Metal.MI_Dark_Metal"));
if (mi_dark.Succeeded() == false) return;
UMaterialInstanceDynamic* mid_dark = UMaterialInstanceDynamic::Create((UMaterialInterface*)sm_caliper.Object, nullptr);
*/
FrontCaliperL = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("FrontCaliperL"));
if (FrontCaliperL != nullptr)
{
FrontCaliperL->SetRelativeLocation(FVector(129.1f, -81.5f, 35.1f));
FrontCaliperL->SetRelativeRotation(FRotator(0.0f, 0.0f, -180.0f));
FrontCaliperL->SetRelativeScale3D(FVector(1.2f, 1.2f, 1.2f));
FrontCaliperL->Mobility = EComponentMobility::Movable; // 可移动
FrontCaliperL->SetStaticMesh(sm_caliper.Object); // 模型
//FrontCaliperL->SetMaterial(0, mid_dark); // 材质
FrontCaliperL->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName); // 无碰撞
FrontCaliperL->SetupAttachment(VehicleMesh);
}
FrontCaliperR = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("FrontCaliperR"));
if (FrontCaliperR != nullptr)
{
FrontCaliperR->SetRelativeLocation(FVector(129.1f, 81.5f, 35.1f));
FrontCaliperR->SetRelativeRotation(FRotator(0.0f, 0.0f, 0.0f));
FrontCaliperR->SetRelativeScale3D(FVector(1.2f, 1.2f, 1.2f));
FrontCaliperR->Mobility = EComponentMobility::Movable; // 可移动
FrontCaliperR->SetStaticMesh(sm_caliper.Object); // 模型
//FrontCaliperR->SetMaterial(0, mid_dark); // 材质
FrontCaliperR->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName); // 无碰撞
FrontCaliperR->SetupAttachment(VehicleMesh);
}
RearCaliperL = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("RearCaliperL"));
if (RearCaliperL != nullptr)
{
RearCaliperL->SetRelativeLocation(FVector(-141.3f, -81.0f, 35.7f));
RearCaliperL->SetRelativeRotation(FRotator(0.0f, 0.0f, 0.0f));
RearCaliperL->SetRelativeScale3D(FVector(1.2f, 1.2f, 1.2f));
RearCaliperL->Mobility = EComponentMobility::Movable; // 可移动
RearCaliperL->SetStaticMesh(sm_caliper.Object); // 模型
//RearCaliperL->SetMaterial(0, mid_dark); // 材质
RearCaliperL->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName); // 无碰撞
RearCaliperL->SetupAttachment(VehicleMesh);
}
RearCaliperR = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("RearCaliperR"));
if (RearCaliperR != nullptr)
{
RearCaliperR->SetRelativeLocation(FVector(-141.3f, 81.0f, 35.7f));
RearCaliperR->SetRelativeRotation(FRotator(0.0f, 0.0f, -180.0f));
RearCaliperR->SetRelativeScale3D(FVector(1.2f, 1.2f, 1.2f));
RearCaliperR->Mobility = EComponentMobility::Movable; // 可移动
RearCaliperR->SetStaticMesh(sm_caliper.Object); // 模型
//RearCaliperR->SetMaterial(0, mid_dark); // 材质
RearCaliperR->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName); // 无碰撞
RearCaliperR->SetupAttachment(VehicleMesh);
}
}
InitSteeringWheelComponent()函数,方向盘组件的实现:
void ABaseVehicle::InitSteeringWheelComponent()
{
static ConstructorHelpers::FObjectFinder<UStaticMesh> sm_steering_wheel(TEXT("StaticMesh'/Game/Meshs/Hatchback/SM_Hatchback_SW.SM_Hatchback_SW'"));
if (sm_steering_wheel.Succeeded() == false) return;
static ConstructorHelpers::FObjectFinder<UStaticMesh> sm_sphere(TEXT("StaticMesh'/Engine/BasicShapes/Sphere.Sphere'"));
if (sm_sphere.Succeeded() == false) return;
SteeringWheel = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("SteeringWheel"));
if (SteeringWheel != nullptr)
{
SteeringWheel->SetRelativeLocation(FVector(70.5f, -39.6f, 84.0f));
SteeringWheel->SetRelativeRotation(FRotator(-20.0f, 0.0f, 0.0f));
SteeringWheel->Mobility = EComponentMobility::Movable; // 可移动
SteeringWheel->SetStaticMesh(sm_steering_wheel.Object);
SteeringWheel->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName); // 无碰撞
SteeringWheel->SetupAttachment(VehicleMesh);
WheelHandlerL = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("WheelHandlerL"));
WheelHandlerR = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("WheelHandlerR"));
if (WheelHandlerL && WheelHandlerR)
{
WheelHandlerL->SetRelativeLocation(FVector(-37.6f, -17.0f, 7.2f));
WheelHandlerL->SetRelativeRotation(FRotator(20.0f, 0.0f, -150.0f));
WheelHandlerL->SetRelativeScale3D(FVector(0.05f, 0.05f, 0.05f));
WheelHandlerL->Mobility = EComponentMobility::Movable; // 可移动
WheelHandlerL->SetStaticMesh(sm_sphere.Object);
WheelHandlerL->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName); // 无碰撞
//WheelHandlerL->SetupAttachment(SteeringWheel);
WheelHandlerL->AttachToComponent(SteeringWheel, FAttachmentTransformRules::KeepRelativeTransform);
WheelHandlerL->SetVisibility(false); // 不可见
WheelHandlerR->SetRelativeLocation(FVector(-38.6f, 16.5f, 8.4f));
WheelHandlerR->SetRelativeRotation(FRotator(2.5f, -170.0f, 28.0f));
WheelHandlerR->SetRelativeScale3D(FVector(0.05f, 0.05f, 0.05f));
WheelHandlerR->Mobility = EComponentMobility::Movable; // 可移动
WheelHandlerR->SetStaticMesh(sm_sphere.Object);
WheelHandlerR->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName); // 无碰撞
//WheelHandlerR->SetupAttachment(SteeringWheel);
WheelHandlerR->AttachToComponent(SteeringWheel, FAttachmentTransformRules::KeepRelativeTransform);
WheelHandlerR->SetVisibility(false); // 不可见
}
}
}
InitExhaustComponent()函数,尾气组件的实现:
void ABaseVehicle::InitExhaustComponent()
{
static ConstructorHelpers::FObjectFinder<UParticleSystem> ps_still_smoke(TEXT("/Game/Particles/P_smoke_still.P_smoke_still"));
if (ps_still_smoke.Succeeded() == false) return;
StillSmokeParticle = ps_still_smoke.Object;
static ConstructorHelpers::FObjectFinder<UParticleSystem> ps_move_smoke(TEXT("/Game/Particles/P_smoke_still.P_smoke_still"));
if (ps_move_smoke.Succeeded() == false) return;
MoveSmokeParticle = ps_move_smoke.Object;
static ConstructorHelpers::FObjectFinder<UMaterial> m_smoke_uv(TEXT("/Game/Particles/Materials/M_smoke_subUV.M_smoke_subUV"));
if (m_smoke_uv.Succeeded() == false) return;
UMaterialInstanceDynamic* mid_smoke_uv = UMaterialInstanceDynamic::Create(m_smoke_uv.Object, nullptr);
SmokeL = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("SmokeL"));
if (SmokeL != nullptr)
{
SmokeL->SetRelativeLocation(FVector(-210.0f, 50.0f, 32.0f));
SmokeL->SetRelativeRotation(FRotator( 0.0f, -180.0f, 0.0f));
SmokeL->SetTemplate(StillSmokeParticle);
//SmokeL->SetMaterial(0, mid_smoke_uv); // 材质
SmokeL->SetupAttachment(VehicleMesh);
}
SmokeR = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("SmokeR"));
if (SmokeR != nullptr)
{
SmokeR->SetRelativeLocation(FVector(-210.0f, -50.0f, 32.0f));
SmokeR->SetRelativeRotation(FRotator(0.0f, -180.0f, 0.0f));
SmokeR->SetTemplate(StillSmokeParticle);
//SmokeR->SetMaterial(0, mid_smoke_uv); // 材质
SmokeR->SetupAttachment(VehicleMesh);
}
}
InitDashboardComponent()函数,仪表盘组件的实现:
void ABaseVehicle::InitDashboardComponent()
{
static ConstructorHelpers::FObjectFinder<UStaticMesh> sm_marker(TEXT("StaticMesh'/Game/Meshs/SM_marker.SM_marker'"));
if (sm_marker.Succeeded() == false) return;
RPMMarker = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("RPMMarker"));
if (RPMMarker != nullptr)
{
RPMMarker->SetRelativeLocation(FVector(64.3f, -34.0f, 96.2f));
RPMMarker->SetRelativeRotation(FRotator(3.0f, 0.0f, -128.0f));
RPMMarker->SetRelativeScale3D(FVector(0.75f, 0.75f, 0.75f));
RPMMarker->Mobility = EComponentMobility::Movable; // 可移动
RPMMarker->SetStaticMesh(sm_marker.Object); // 模型
RPMMarker->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName); // 无碰撞
RPMMarker->SetupAttachment(VehicleMesh);
}
SpeedMarker = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("SpeedMarker"));
if (SpeedMarker != nullptr)
{
SpeedMarker->SetRelativeLocation(FVector(64.3f, -42.6f, 96.2f));
SpeedMarker->SetRelativeRotation(FRotator(3.0f, 0.0f, -128.0f));
SpeedMarker->SetRelativeScale3D(FVector(0.75f, 0.75f, 0.75f));
SpeedMarker->Mobility = EComponentMobility::Movable; // 可移动
SpeedMarker->SetStaticMesh(sm_marker.Object); // 模型
SpeedMarker->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName); // 无碰撞
SpeedMarker->SetupAttachment(VehicleMesh);
}
}
InitSoundComponent()函数,声音组件的实现:
void ABaseVehicle::InitSoundComponent()
{
EngineSound = CreateDefaultSubobject<UAudioComponent>(TEXT("EngineSound"));
EngineSound->SetRelativeLocation(FVector(127.f, 0.0f, 113.f));
EngineSound->SetupAttachment(VehicleMesh);
static ConstructorHelpers::FObjectFinder<USoundBase> sb_start(TEXT("/Game/Sounds/cues/standard/start_standard_Cue.start_standard_Cue"));
StartSound = sb_start.Object;
static ConstructorHelpers::FObjectFinder<USoundBase> sb_impact(TEXT("/Game/Sounds/waves/impacts/low1.low1"));
ImpactSound = sb_impact.Object;
}
BaseVehicle_Drive.cpp 中,两个辅助函数的编写。都是在驾驶交互时调用。
VehicleCaliperRotation(bool left_side, FName in_socket_name, USceneComponent* caliper, bool front),卡钳控制。
•left_side 是否是左边的卡钳•in_socket_name 骨骼名称•caliper 卡钳组件•front 是否是前轮卡钳
void ABaseVehicle::VehicleCaliperRotation(bool left_side, FName in_socket_name, USceneComponent* caliper, bool front)
{
float WheelYaw = 0.0f;
float WheelRoll = 0.0f;
FVector WheelLocation = FVector::ZeroVector;
if (left_side == true)
WheelRoll = 180.f;
FTransform t_socket = VehicleMesh->GetSocketTransform(in_socket_name, ERelativeTransformSpace::RTS_Component);
WheelLocation = t_socket.GetLocation();
WheelYaw = t_socket.GetRotation().Rotator().Yaw;
if ((WheelYaw < 60.0f && WheelYaw > -60.0f) == false)
{
WheelYaw = WheelYaw + 180.0f;
}
if (front == false)
{
WheelYaw = 0.0f;
}
if (caliper != nullptr)
{
FRotator r_wheel_rotator = FRotator( 0.0f, WheelYaw, WheelRoll);
caliper->SetRelativeLocationAndRotation(WheelLocation, r_wheel_rotator);
}
}
VehicleMarkerRotation(USceneComponent* marker, float rotation, float rot_multiplier),仪表盘控制。
•marker 仪表指针组件•rotation 角度•rot_multiplier 角度乘子因子
void ABaseVehicle::VehicleMarkerRotation(USceneComponent* marker, float rotation, float rot_multiplier)
{
float roll = rotation * rot_multiplier - 127.0f;
FRotator r_marker_rotation = FRotator::ZeroRotator;
r_marker_rotation.Roll = roll;
r_marker_rotation.Pitch = 3.0f;
marker->SetRelativeRotation(r_marker_rotation);
}
虚幻引擎5 C++ 汽车驾驶交互
驾驶交互,分为油门、转向和刹车。
汽车驾驶交互-驾驶控制 展示:

汽车是基于物理的,凹凸地面、撞曲面墙,都有相应的交互感。
终于讲到,最为关键的驾驶交互了,之前所做的一切都是为驾驶而生。让我们看一下最为灵魂的部分。
前轮和后轮的代码:
FrontWheel.h
...
UCLASS()
class UEVEHICLEPROJECT_API UFrontWheel : public UChaosVehicleWheel
{
GENERATED_BODY()
UFrontWheel();
};
RearWheel.h
...
UCLASS()
class UEVEHICLEPROJECT_API URearWheel : public UChaosVehicleWheel
{
GENERATED_BODY()
URearWheel();
};
FrontWheel.cpp
UFrontWheel::UFrontWheel()
{
WheelRadius = 34.5f;
WheelWidth = 21.0f;
// Mass = 20.0f;
// DampingRate = 0.25f;
FrictionForceMultiplier = 2.0f;
CorneringStiffness = 500.0f;
bAffectedByEngine = false;
bAffectedByHandbrake = false; // 不受手刹影响
bAffectedBySteering = true;
AxleType = EAxleType::Front;
SpringRate = 200.0f;
SpringPreload = 100.f;
SuspensionDampingRatio = 0.5f;
WheelLoadRatio = 0.0f;
RollbarScaling = 0.5f;
SuspensionMaxRaise = 8;
SuspensionMaxDrop = 12;
WheelLoadRatio = 0.5f;
MaxSteerAngle = 55.0f; // 前轮可以旋转度数
}
RearWheel.cpp
URearWheel::URearWheel()
{
WheelRadius = 34.5f;
WheelWidth = 21.0f;
//Mass = 20.0f;
//DampingRate = 0.25f;
FrictionForceMultiplier = 2.0f;
CorneringStiffness = 500.0f;
bAffectedByEngine = true;
bAffectedByHandbrake = true; // 受手刹影响
bAffectedBySteering = false;
AxleType = EAxleType::Rear;
SpringRate = 200.0f;
SpringPreload = 100.f;
SuspensionDampingRatio = 0.5f;
WheelLoadRatio = 0.0f;
RollbarScaling = 0.5f;
SuspensionMaxRaise = 8;
SuspensionMaxDrop = 12.0f;
WheelLoadRatio = 0.5f;
MaxSteerAngle = 0.0f; // 后轮不能转动
}
BaseVehicle.h 中相关的定义。
UCLASS()
class UEVEHICLEPROJECT_API ABaseVehicle : public AWheeledVehiclePawn
{
GENERATED_BODY()
public:
ABaseVehicle();
...
// 驾驶控制
// 油门
UFUNCTION(BlueprintCallable, Category = "VehicleDrive")
void VehicleThrottle(float axis_value);
// 转向
UFUNCTION(BlueprintCallable, Category = "VehicleDrive")
void VehicleSteer(float axis_value);
// 手刹
UFUNCTION(BlueprintCallable, Category = "VehicleDrive")
void VehicleHandBrake(bool is_hand_brake);
...
protected:
...
void InitVehicleMovementComponent(); // 初始化 轮胎系统 组件
...
protected:
...
// 轮胎组件
UPROPERTY(VisibleAnywhere, Category = "Vehicle4W", meta = (AllowPrivateAccess = "true"))
UChaosWheeledVehicleMovementComponent* Vehicle4W;
...
// 油门
float Throttle;
...
// 当前档位
int CurrentGear;
// 最大速度
float MaxSpeed;
};
BaseVehicle_Init.cpp 中,InitVehicleMovementComponent()函数,轮胎系统初始化的相关实现。
void ABaseVehicle::InitVehicleMovementComponent()
{
Vehicle4W = CastChecked<UChaosWheeledVehicleMovementComponent>(GetVehicleMovement());
// Vehicle4W->bDeprecatedSpringOffsetMode = true;
// 设置轮胎数
Vehicle4W->WheelSetups.SetNum(4);
//check(Vehicle4W->WheelSetups.Num() == 4);
Vehicle4W->WheelSetups[0].WheelClass = UFrontWheel::StaticClass();
Vehicle4W->WheelSetups[0].BoneName = FName("Front_Right_Wheel");
Vehicle4W->WheelSetups[1].WheelClass = UFrontWheel::StaticClass();
Vehicle4W->WheelSetups[1].BoneName = FName("Front_Left_Wheel");
Vehicle4W->WheelSetups[2].WheelClass = URearWheel::StaticClass();
Vehicle4W->WheelSetups[2].BoneName = FName("Rear_Right_Wheel");
Vehicle4W->WheelSetups[3].WheelClass = URearWheel::StaticClass();
Vehicle4W->WheelSetups[3].BoneName = FName("Rear_Left_Wheel");
// 引擎设置
// 转矩设置
Vehicle4W->EngineSetup.MaxRPM = 5700.0f;
Vehicle4W->EngineSetup.MaxTorque = 500.0f;
Vehicle4W->EngineSetup.TorqueCurve.GetRichCurve()->Reset();
Vehicle4W->EngineSetup.TorqueCurve.GetRichCurve()->AddKey(0.0f, 400.0f);
Vehicle4W->EngineSetup.TorqueCurve.GetRichCurve()->AddKey(1890.0f, 500.0f);
Vehicle4W->EngineSetup.TorqueCurve.GetRichCurve()->AddKey(5730.0f, 400.0f);
// 设置前轮驱动
Vehicle4W->DifferentialSetup.DifferentialType = EVehicleDifferential::FrontWheelDrive;
// 调整方向盘
Vehicle4W->SteeringSetup.SteeringCurve.GetRichCurve()->Reset();
Vehicle4W->SteeringSetup.SteeringCurve.GetRichCurve()->AddKey(0.0f, 1.0f);
Vehicle4W->SteeringSetup.SteeringCurve.GetRichCurve()->AddKey(20.0f, 0.9f);
Vehicle4W->SteeringSetup.SteeringCurve.GetRichCurve()->AddKey(60.0f, 0.8f);
Vehicle4W->SteeringSetup.SteeringCurve.GetRichCurve()->AddKey(120.0f, 0.6f);
// 前轮的驱动力略高于后轮
Vehicle4W->DifferentialSetup.FrontRearSplit = 0.65;
// 自动挡设置
Vehicle4W->TransmissionSetup.bUseAutomaticGears = true;
Vehicle4W->TransmissionSetup.bUseAutoReverse = true;
Vehicle4W->TransmissionSetup.GearChangeTime = 0.15f;
// 物理设置
// 调整重心
UPrimitiveComponent* UpdatedPrimitive = Cast<UPrimitiveComponent>(Vehicle4W->UpdatedComponent);
if (UpdatedPrimitive)
{
UpdatedPrimitive->BodyInstance.COMNudge = FVector(8.0f, 0.0f, -15.0f);
}
// 惯性设置,将控制车辆质量的分配方式
Vehicle4W->InertiaTensorScale = FVector(1.0f, 1.333f, 1.2f);
}
VehicleThrottle(float axis_value)函数,油门控制。
void ABaseVehicle::VehicleThrottle(float axis_value)
{
UChaosWheeledVehicleMovementComponent* WheeledVehicle = static_cast<UChaosWheeledVehicleMovementComponent*>(GetVehicleMovement());
if (WheeledVehicle == nullptr) return;
Throttle = axis_value;
// 启动轮胎系统
if (Throttle > 0)
{
GetVehicleMovementComponent()->SetThrottleInput(Throttle);
GetVehicleMovementComponent()->SetBrakeInput(0.f);
}
else
{
GetVehicleMovementComponent()->SetThrottleInput(0.f);
GetVehicleMovementComponent()->SetBrakeInput(-Throttle);
}
//Print() + "Throttle::" + ": " + Throttle; // 默认输出
// 速度驱动 引擎声音
float f_rotaion_speed = FMath::Abs<float>(WheeledVehicle->GetEngineRotationSpeed());
EngineSound->SetFloatParameter(FName{ TEXT("RPM") }, f_rotaion_speed);
// RPM 和 速度 仪表盘控制
// RPM
float f_max_rotation_speed = WheeledVehicle->GetEngineMaxRotationSpeed();
float f_rpm_rotation = f_rotaion_speed / f_max_rotation_speed;
VehicleMarkerRotation(RPMMarker, f_rpm_rotation, 250.f);
// Speed
float f_forward_speed = WheeledVehicle->GetForwardSpeed();
float f_speed_rotation = FMath::Abs<float>(f_forward_speed) / MaxSpeed;
VehicleMarkerRotation(SpeedMarker, f_speed_rotation, 250.f);
// 尾气控制
if (f_speed_rotation > 66.6f) // 运动时烟雾
{
if (SmokeL->Template == StillSmokeParticle)
SmokeL->SetTemplate(MoveSmokeParticle);
if (SmokeR->Template == StillSmokeParticle)
SmokeR->SetTemplate(MoveSmokeParticle);
}
else // 静止时烟雾
{
if (SmokeL->Template == StillSmokeParticle)
SmokeL->SetTemplate(MoveSmokeParticle);
if (SmokeR->Template == StillSmokeParticle)
SmokeR->SetTemplate(MoveSmokeParticle);
}
// 灯光控制
static bool is_brakelights_or_reverselights_on = false;
if (Throttle < 0) // 倒车
{
if (f_forward_speed < -1.0f)
{
VehicleLights(PositionLights, false, true);
is_brakelights_or_reverselights_on = true;
}
else
{
VehicleLights(PositionLights, true, false);
is_brakelights_or_reverselights_on = true;
}
}
else
{
if (is_brakelights_or_reverselights_on == true)
{
VehicleLights(PositionLights, false, false);
is_brakelights_or_reverselights_on = false;
}
}
}
VehicleSteer(float axis_value)函数,转向控制。
void ABaseVehicle::VehicleSteer(float axis_value)
{
float Steering = axis_value;
GetVehicleMovementComponent()->SetSteeringInput(Steering);
// 卡钳控制
VehicleCaliperRotation(false, TEXT("Front_Right_caliper"), FrontCaliperR, true);
VehicleCaliperRotation(true, TEXT("Front_Left_caliper"), FrontCaliperL, true);
VehicleCaliperRotation(false, TEXT("Rear_Right_caliper"), RearCaliperR, false);
VehicleCaliperRotation(true, TEXT("Rear_Left_caliper"), RearCaliperL, false);
// 方向盘控制
FTransform t_front_right_wheel = VehicleMesh->GetSocketTransform(TEXT("Front_Right_Wheel"), ERelativeTransformSpace::RTS_Component);
float WheelYaw = t_front_right_wheel.GetRotation().Rotator().Yaw;
FRotator WheelRotator = FRotator( 0.0f, 0.0f, WheelYaw) * 2.0f;
SteeringWheel->SetRelativeRotation(WheelRotator);
// 手部 IK
WheelHandlerPosL = WheelHandlerL->GetComponentToWorld().GetLocation();
WheelHandlerRotL = WheelHandlerL->GetComponentToWorld().GetRotation().Rotator();
WheelHandlerPosR = WheelHandlerR->GetComponentToWorld().GetLocation();
WheelHandlerRotR = WheelHandlerR->GetComponentToWorld().GetRotation().Rotator();
}
VehicleHandBrake(bool is_hand_brake)函数,手刹控制。
void ABaseVehicle::VehicleHandBrake(bool is_hand_brake)
{
// 刹车
GetVehicleMovementComponent()->SetHandbrakeInput(is_hand_brake);
// 刹车灯控制
VehicleLights(PositionLights, is_hand_brake, false);
}
最后,看一下 SUV 的蓝图函数调用,BP_SUV。
摄像机相关蓝图:

输入控制相关蓝图:





