So I created this thread to see if anyone wants to work on creating an N Wheeled class vehicle with me, as opposed to a bunch of people banging their heads against the wall separately. I know there are plenty of issues with the PhysX integration of the vehicle, and it’s incredibly complicated. But I’d be willing to put in the time coding it if I could find a couple of other people willing to code also, or at least help me with some of the issues with PhysX, I know we have enough people on here that are smart and dedicated enough to fix the vehicles in the engine, and get it done in a timely manner. Also, this wasn’t added to the Horrible Physics thread because I want to focus on solutions, not endless complaining because it doesn’t work, that thread is way too negative to make for a solid work environment. I wanted to gauge interest before I started throwing my code in here, I want people working together to solve this, not just taking my code and complaining because they can’t make it work. So if I get 2 or 3 solid responses from anyone I’ll post my code and progress so far.
I’ll post this code here for now, I still need to change some things but it works.
If you haven’t added this line, you probably should’ve already if your here but anyways, add this to your PhysXVehicleManager.cpp inside the switch statement in the RemoveVehicle function or it’ll crash every time the game exits.
case PxVehicleTypes::eDRIVENW:
((PxVehicleDriveNW*)PVehicle)->free();
break;
Forgot about this one, you need to add this to your VehicleProject.h, or whatever your project header file is.
// PhysX. <-- You don't have to add this obviously.
#include "PhysicsPublic.h"
#include "PhysXPublic.h"
Your build.cs file should have the PhysX, and APEX added to it, though I don’t understand why you need APEX (APEX is compiled on top of the PhysX library, so PhysX being dependent on APEX is a somewhat circular dependency).
// Copyright Unreal Engine Community.
using UnrealBuildTool;
public class VehicleProject : ModuleRules
{
public VehicleProject(TargetInfo Target)
{
PublicDependencyModuleNames.AddRange(
new string]
{
"Core",
"CoreUObject",
"Engine",
"InputCore",
"HeadMountedDisplay",
"PhysX",
"APEX",
});
}
}
VehicleMovementComponentNW.h
// Copyright Unreal Engine Community.
#pragma once
#include "Vehicles/WheeledVehicleMovementComponent.h"
#include "Curves/CurveFloat.h"
#include "VehicleMovementComponentNW.generated.h"
#if WITH_VEHICLE
namespace physx
{
class PxVehicleDriveNW;
}
#endif // WITH_VEHICLE
USTRUCT()
struct FDrivenWheelData
{
GENERATED_USTRUCT_BODY()
UPROPERTY(EditAnywhere, Category = Setup)
int32 DrivenWheelIndex;
UPROPERTY(EditAnywhere, Category = Setup)
bool IsDrivenWheel;
};
USTRUCT()
struct FVehicleDifferentialNWData
{
GENERATED_USTRUCT_BODY()
UPROPERTY(EditAnywhere, Category = Setup, AdvancedDisplay)
TArray<FDrivenWheelData> DWheelData;
};
USTRUCT()
struct FVehicleEngineNWData
{
GENERATED_USTRUCT_BODY()
// Torque (Nm) at a given RPM.
UPROPERTY(EditAnywhere, Category = Setup)
FRuntimeFloatCurve TorqueCurve;
// Maximum revolutions per minute of the engine.
UPROPERTY(EditAnywhere, Category = Setup, meta = (ClampMin = "0.01", UIMin = "0.01"))
float MaxRPM;
// Moment of inertia of the engine around the axis of rotation (Kgm^2).
UPROPERTY(EditAnywhere, Category = Setup, meta = (ClampMin = "0.01", UIMin = "0.01"))
float MOI;
// Damping rate of engine when full throttle is applied (Kgm^2/s).
UPROPERTY(EditAnywhere, Category = Setup, AdvancedDisplay, meta = (ClampMin = "0.0", UIMin = "0.0"))
float DampingRateFullThrottle;
// Damping rate of engine in at zero throttle when the clutch is engaged (Kgm^2/s).
UPROPERTY(EditAnywhere, Category = Setup, AdvancedDisplay, meta = (ClampMin = "0.0", UIMin = "0.0"))
float DampingRateZeroThrottleClutchEngaged;
// Damping rate of engine in at zero throttle when the clutch is disengaged (in neutral gear) (Kgm^2/s).
UPROPERTY(EditAnywhere, Category = Setup, AdvancedDisplay, meta = (ClampMin = "0.0", UIMin = "0.0"))
float DampingRateZeroThrottleClutchDisengaged;
// Find the peak torque produced by the TorqueCurve.
float FindPeakTorque() const;
};
USTRUCT()
struct FVehicleGearNWData
{
GENERATED_USTRUCT_BODY()
// Determines the amount of torque multiplication.
UPROPERTY(EditAnywhere, Category = Setup)
float Ratio;
// Value of engineRevs/maxEngineRevs that is low enough to gear down.
UPROPERTY(EditAnywhere, meta = (ClampMin = "0.0", UIMin = "0.0", ClampMax = "1.0", UIMax = "1.0"), Category = Setup)
float DownRatio;
// Value of engineRevs/maxEngineRevs that is high enough to gear up.
UPROPERTY(EditAnywhere, meta = (ClampMin = "0.0", UIMin = "0.0", ClampMax = "1.0", UIMax = "1.0"), Category = Setup)
float UpRatio;
};
USTRUCT()
struct FVehicleTransmissionNWData
{
GENERATED_USTRUCT_BODY()
// Whether to use automatic transmission.
UPROPERTY(EditAnywhere, Category = VehicleSetup, meta = (DisplayName = "Automatic Transmission"))
bool bUseGearAutoBox;
// Time it takes to switch gears (seconds).
UPROPERTY(EditAnywhere, Category = Setup, meta = (ClampMin = "0.0", UIMin = "0.0"))
float GearSwitchTime;
// Minimum time it takes the automatic transmission to initiate a gear change (seconds).
UPROPERTY(EditAnywhere, Category = Setup, meta = (editcondition = "bUseGearAutoBox", ClampMin = "0.0", UIMin = "0.0"))
float GearAutoBoxLatency;
// The final gear ratio multiplies the transmission gear ratios.
UPROPERTY(EditAnywhere, AdvancedDisplay, Category = Setup)
float FinalRatio;
// Forward gear ratios (up to 30).
UPROPERTY(EditAnywhere, Category = Setup, AdvancedDisplay)
TArray<FVehicleGearNWData> ForwardGears;
// Reverse gear ratio.
UPROPERTY(EditAnywhere, AdvancedDisplay, Category = Setup)
float ReverseGearRatio;
// Value of engineRevs/maxEngineRevs that is high enough to increment gear.
UPROPERTY(EditAnywhere, AdvancedDisplay, Category = Setup, meta = (ClampMin = "0.0", UIMin = "0.0", ClampMax = "1.0", UIMax = "1.0"))
float NeutralGearUpRatio;
// Strength of clutch (Kgm^2/s).
UPROPERTY(EditAnywhere, Category = Setup, AdvancedDisplay, meta = (ClampMin = "0.0", UIMin = "0.0"))
float ClutchStrength;
};
UCLASS()
class VEHICLEPROJECT_API UVehicleMovementComponentNW : public UWheeledVehicleMovementComponent
{
GENERATED_UCLASS_BODY()
UPROPERTY(EditAnywhere, Category = Vehicle)
int32 NumOfWheels;
// Engine.
UPROPERTY(EditAnywhere, Category = MechanicalSetup)
FVehicleEngineNWData EngineSetup;
// Differential.
UPROPERTY(EditAnywhere, Category = MechanicalSetup)
FVehicleDifferentialNWData DifferentialSetup;
//UPROPERTY(EditAnywhere, Category = MechanicalSetup).
FDrivenWheelData DrivenWheelSetup;
// Transmission Data.
UPROPERTY(EditAnywhere, Category = MechanicalSetup)
FVehicleTransmissionNWData TransmissionSetup;
// Maximum steering versus forward speed (Km/h).
UPROPERTY(EditAnywhere, Category = SteeringSetup)
FRuntimeFloatCurve SteeringCurve;
virtual void Serialize(FArchive & Ar) override;
virtual void ComputeConstants() override;
#if WITH_EDITOR
virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override;
#endif
protected:
#if WITH_VEHICLE
// Allocate and setup the PhysX vehicle.
virtual void SetupVehicle() override;
virtual void UpdateSimulation(float DeltaTime) override;
#endif // WITH_VEHICLE
// Update simulation data: engine.
void UpdateEngineSetup(const FVehicleEngineNWData& NewEngineSetup);
// Update Simulation data: differential.
void UpdateDifferentialSetup(const FVehicleDifferentialNWData& NewDifferentialSetup);
// Update simulation data: transmission.
void UpdateTransmissionSetup(const FVehicleTransmissionNWData& NewGearSetup);
};
VehicleMovementComponentNW.cpp
// Copyright Unreal Engine Community.
#include "VehicleProject.h"
#include "PxVehicleDriveNW.h"
#include "VehicleMovementComponentNW.h"
UVehicleMovementComponentNW::UVehicleMovementComponentNW(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
#if WITH_VEHICLE
PxVehicleEngineData DefEngineData;
EngineSetup.MOI = DefEngineData.mMOI;
EngineSetup.MaxRPM = OmegaToRPM(DefEngineData.mMaxOmega);
EngineSetup.DampingRateFullThrottle = DefEngineData.mDampingRateFullThrottle;
EngineSetup.DampingRateZeroThrottleClutchEngaged = DefEngineData.mDampingRateZeroThrottleClutchEngaged;
EngineSetup.DampingRateZeroThrottleClutchDisengaged = DefEngineData.mDampingRateZeroThrottleClutchDisengaged;
// Convert from PhysX curve to ours.
FRichCurve* TorqueCurveData = EngineSetup.TorqueCurve.GetRichCurve();
for (PxU32 KeyIdx = 0; KeyIdx < DefEngineData.mTorqueCurve.getNbDataPairs(); KeyIdx++)
{
float Input = DefEngineData.mTorqueCurve.getX(KeyIdx) * EngineSetup.MaxRPM;
float Output = DefEngineData.mTorqueCurve.getY(KeyIdx) * DefEngineData.mPeakTorque;
TorqueCurveData->AddKey(Input, Output);
}
PxVehicleClutchData DefClutchData;
TransmissionSetup.ClutchStrength = DefClutchData.mStrength;
PxVehicleGearsData DefGearSetup;
TransmissionSetup.GearSwitchTime = DefGearSetup.mSwitchTime;
TransmissionSetup.ReverseGearRatio = DefGearSetup.mRatios[PxVehicleGearsData::eREVERSE];
TransmissionSetup.FinalRatio = DefGearSetup.mFinalRatio;
PxVehicleAutoBoxData DefAutoBoxSetup;
TransmissionSetup.NeutralGearUpRatio = DefAutoBoxSetup.mUpRatios[PxVehicleGearsData::eNEUTRAL];
TransmissionSetup.GearAutoBoxLatency = DefAutoBoxSetup.getLatency();
TransmissionSetup.bUseGearAutoBox = true;
for (uint32 i = PxVehicleGearsData::eFIRST; i < DefGearSetup.mNbRatios; i++)
{
FVehicleGearNWData GearData;
GearData.DownRatio = DefAutoBoxSetup.mDownRatios*;
GearData.UpRatio = DefAutoBoxSetup.mUpRatios*;
GearData.Ratio = DefGearSetup.mRatios*;
TransmissionSetup.ForwardGears.Add(GearData);
}
// Init steering speed curve.
FRichCurve* SteeringCurveData = SteeringCurve.GetRichCurve();
SteeringCurveData->AddKey(0.f, 1.f);
SteeringCurveData->AddKey(20.f, 0.9f);
SteeringCurveData->AddKey(60.f, 0.8f);
SteeringCurveData->AddKey(120.f, 0.7f);
NumOfWheels = 4;
// Initialize WheelSetups array with 6 wheels.
WheelSetups.SetNum(NumOfWheels);
// Grab default values from physx.
PxVehicleDifferentialNWData DefDifferentialSetup;
for (int32 WheelIdx = 0; WheelIdx < NumOfWheels; ++WheelIdx)
{
FDrivenWheelData DiffData;
DiffData.IsDrivenWheel = DefDifferentialSetup.getIsDrivenWheel(WheelIdx);
DiffData.DrivenWheelIndex = WheelIdx;
DifferentialSetup.DWheelData.Add(DiffData);
}
#endif // WITH_VEHICLE
}
//////////////////////////////////////////////////////////////////////////
///////////Modified Code from WheeledVehicleMovementComponent4W///////////
#if WITH_EDITOR
void UVehicleMovementComponentNW::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
const FName PropertyName = PropertyChangedEvent.Property ? PropertyChangedEvent.Property->GetFName() : NAME_None;
if (PropertyName == TEXT("DownRatio"))
{
for (int32 GearIdx = 0; GearIdx < TransmissionSetup.ForwardGears.Num(); ++GearIdx)
{
FVehicleGearNWData & GearData = TransmissionSetup.ForwardGears[GearIdx];
GearData.DownRatio = FMath::Min(GearData.DownRatio, GearData.UpRatio);
}
}
else if (PropertyName == TEXT("UpRatio"))
{
for (int32 GearIdx = 0; GearIdx < TransmissionSetup.ForwardGears.Num(); ++GearIdx)
{
FVehicleGearNWData & GearData = TransmissionSetup.ForwardGears[GearIdx];
GearData.UpRatio = FMath::Max(GearData.DownRatio, GearData.UpRatio);
}
}
else if (PropertyName == TEXT("SteeringCurve"))
{
// Make sure values are capped between 0 and 1.
TArray<FRichCurveKey> SteerKeys = SteeringCurve.GetRichCurve()->GetCopyOfKeys();
for (int32 KeyIdx = 0; KeyIdx < SteerKeys.Num(); ++KeyIdx)
{
float NewValue = FMath::Clamp(SteerKeys[KeyIdx].Value, 0.f, 1.f);
SteeringCurve.GetRichCurve()->UpdateOrAddKey(SteerKeys[KeyIdx].Time, NewValue);
}
}
}
#endif // WITH_EDITOR
#if WITH_VEHICLE
static void GetVehicleDifferentialNWSetup(const FVehicleDifferentialNWData& Setup, PxVehicleDifferentialNWData& PxSetup)
{
for (int32 i = 0; i < Setup.DWheelData.Num(); i++)
{
PxSetup.setDrivenWheel(Setup.DWheelData*.DrivenWheelIndex, Setup.DWheelData*.IsDrivenWheel);
}
}
float FVehicleEngineNWData::FindPeakTorque() const
{
// Find max torque.
float PeakTorque = 0.f;
TArray<FRichCurveKey> TorqueKeys = TorqueCurve.GetRichCurveConst()->GetCopyOfKeys();
for (int32 KeyIdx = 0; KeyIdx < TorqueKeys.Num(); KeyIdx++)
{
FRichCurveKey& Key = TorqueKeys[KeyIdx];
PeakTorque = FMath::Max(PeakTorque, Key.Value);
}
return PeakTorque;
}
static void GetVehicleEngineSetup(const FVehicleEngineNWData& Setup, PxVehicleEngineData& PxSetup)
{
PxSetup.mMOI = M2ToCm2(Setup.MOI);
PxSetup.mMaxOmega = RPMToOmega(Setup.MaxRPM);
PxSetup.mDampingRateFullThrottle = M2ToCm2(Setup.DampingRateFullThrottle);
PxSetup.mDampingRateZeroThrottleClutchEngaged = M2ToCm2(Setup.DampingRateZeroThrottleClutchEngaged);
PxSetup.mDampingRateZeroThrottleClutchDisengaged = M2ToCm2(Setup.DampingRateZeroThrottleClutchDisengaged);
float PeakTorque = Setup.FindPeakTorque(); // In Nm.
PxSetup.mPeakTorque = M2ToCm2(PeakTorque); // Convert Nm to (kg cm^2/s^2).
// Convert from our curve to PhysX.
PxSetup.mTorqueCurve.clear();
TArray<FRichCurveKey> TorqueKeys = Setup.TorqueCurve.GetRichCurveConst()->GetCopyOfKeys();
int32 NumTorqueCurveKeys = FMath::Min<int32>(TorqueKeys.Num(), PxVehicleEngineData::eMAX_NB_ENGINE_TORQUE_CURVE_ENTRIES);
for (int32 KeyIdx = 0; KeyIdx < NumTorqueCurveKeys; KeyIdx++)
{
FRichCurveKey& Key = TorqueKeys[KeyIdx];
PxSetup.mTorqueCurve.addPair(FMath::Clamp(Key.Time / Setup.MaxRPM, 0.f, 1.f), Key.Value / PeakTorque); // Normalize torque to 0-1 range
}
}
static void GetVehicleGearSetup(const FVehicleTransmissionNWData& Setup, PxVehicleGearsData& PxSetup)
{
PxSetup.mSwitchTime = Setup.GearSwitchTime;
PxSetup.mRatios[PxVehicleGearsData::eREVERSE] = Setup.ReverseGearRatio;
for (int32 i = 0; i < Setup.ForwardGears.Num(); i++)
{
PxSetup.mRatios[i + PxVehicleGearsData::eFIRST] = Setup.ForwardGears*.Ratio;
}
PxSetup.mFinalRatio = Setup.FinalRatio;
PxSetup.mNbRatios = Setup.ForwardGears.Num() + PxVehicleGearsData::eFIRST;
}
static void GetVehicleAutoBoxSetup(const FVehicleTransmissionNWData& Setup, PxVehicleAutoBoxData& PxSetup)
{
for (int32 i = 0; i < Setup.ForwardGears.Num(); i++)
{
const FVehicleGearNWData& GearData = Setup.ForwardGears*;
PxSetup.mUpRatios* = GearData.UpRatio;
PxSetup.mDownRatios* = GearData.DownRatio;
}
PxSetup.mUpRatios[PxVehicleGearsData::eNEUTRAL] = Setup.NeutralGearUpRatio;
PxSetup.setLatency(Setup.GearAutoBoxLatency);
}
void SetupDriveHelper(const UVehicleMovementComponentNW* VehicleData, const PxVehicleWheelsSimData* PWheelsSimData, PxVehicleDriveSimDataNW& DriveData)
{
PxVehicleDifferentialNWData DifferentialSetup;
GetVehicleDifferentialNWSetup(VehicleData->DifferentialSetup, DifferentialSetup);
DriveData.setDiffData(DifferentialSetup);
PxVehicleEngineData EngineSetup;
GetVehicleEngineSetup(VehicleData->EngineSetup, EngineSetup);
DriveData.setEngineData(EngineSetup);
PxVehicleClutchData ClutchSetup;
ClutchSetup.mStrength = M2ToCm2(VehicleData->TransmissionSetup.ClutchStrength);
DriveData.setClutchData(ClutchSetup);
PxVehicleGearsData GearSetup;
GetVehicleGearSetup(VehicleData->TransmissionSetup, GearSetup);
DriveData.setGearsData(GearSetup);
PxVehicleAutoBoxData AutoBoxSetup;
GetVehicleAutoBoxSetup(VehicleData->TransmissionSetup, AutoBoxSetup);
DriveData.setAutoBoxData(AutoBoxSetup);
}
void UVehicleMovementComponentNW::SetupVehicle()
{
if (!UpdatedPrimitive)
{
return;
}
if (WheelSetups.Num() > 20 || WheelSetups.Num() < 2)
{
PVehicle = NULL;
PVehicleDrive = NULL;
return;
}
for (int32 WheelIdx = 0; WheelIdx < WheelSetups.Num(); ++WheelIdx)
{
const FWheelSetup& WheelSetup = WheelSetups[WheelIdx];
if (WheelSetup.BoneName == NAME_None)
{
return;
}
}
// Setup the chassis and wheel shapes.
SetupVehicleShapes();
// Setup mass properties.
SetupVehicleMass();
// Setup the wheels.
PxVehicleWheelsSimData* PWheelsSimData = PxVehicleWheelsSimData::allocate(NumOfWheels);
SetupWheels(PWheelsSimData);
// Setup drive data.
PxVehicleDriveSimDataNW DriveData;
SetupDriveHelper(this, PWheelsSimData, DriveData);
// Create the vehicle.
PxVehicleDriveNW* PVehicleDriveNW = PxVehicleDriveNW::allocate(NumOfWheels);
check(PVehicleDriveNW);
FBodyInstance* BodyInstance = UpdatedPrimitive->GetBodyInstance();
BodyInstance->ExecuteOnPhysicsReadWrite(&]
{
PxRigidDynamic* PRigidDynamic = BodyInstance->GetPxRigidDynamic_AssumesLocked();
PVehicleDriveNW->setup(GPhysXSDK, PRigidDynamic, *PWheelsSimData, DriveData, 0);
PVehicleDriveNW->setToRestState();
// Cleanup.
PWheelsSimData->free();
});
// This bit of code gives a link error, not sure why, so It stays here until I figure it out.
//ExecuteOnPxRigidDynamicReadWrite(UpdatedPrimitive->GetBodyInstance(), &] (PxRigidDynamic* PRigidDynamic)
//{
// PVehicleDriveNW->setup(GPhysXSDK, PRigidDynamic, *PWheelsSimData, DriveData, 0);
// PVehicleDriveNW->setToRestState();
// Cleanup.
// PWheelsSimData->free();
//});
PWheelsSimData = NULL;
// Cache values.
PVehicle = PVehicleDriveNW;
PVehicleDrive = PVehicleDriveNW;
SetUseAutoGears(TransmissionSetup.bUseGearAutoBox);
}
void UVehicleMovementComponentNW::UpdateSimulation(float DeltaTime)
{
if (PVehicleDrive == NULL)
return;
UpdatedPrimitive->GetBodyInstance()->ExecuteOnPhysicsReadWrite(&]
{
PxVehicleDriveNWRawInputData RawInputData;
RawInputData.setAnalogAccel(ThrottleInput);
RawInputData.setAnalogSteer(SteeringInput);
RawInputData.setAnalogBrake(BrakeInput);
RawInputData.setAnalogHandbrake(HandbrakeInput);
if (!PVehicleDrive->mDriveDynData.getUseAutoGears())
{
RawInputData.setGearUp(bRawGearUpInput);
RawInputData.setGearDown(bRawGearDownInput);
}
// Convert from our curve to PxFixedSizeLookupTable
PxFixedSizeLookupTable<8> SpeedSteerLookup;
TArray<FRichCurveKey> SteerKeys = SteeringCurve.GetRichCurve()->GetCopyOfKeys();
const int32 MaxSteeringSamples = FMath::Min(8, SteerKeys.Num());
for (int32 KeyIdx = 0; KeyIdx < MaxSteeringSamples; KeyIdx++)
{
FRichCurveKey& Key = SteerKeys[KeyIdx];
SpeedSteerLookup.addPair(KmHToCmS(Key.Time), FMath::Clamp(Key.Value, 0.f, 1.f));
}
PxVehiclePadSmoothingData SmoothData =
{
{ ThrottleInputRate.RiseRate, BrakeInputRate.RiseRate, HandbrakeInputRate.RiseRate, SteeringInputRate.RiseRate, SteeringInputRate.RiseRate },
{ ThrottleInputRate.FallRate, BrakeInputRate.FallRate, HandbrakeInputRate.FallRate, SteeringInputRate.FallRate, SteeringInputRate.FallRate }
};
PxVehicleDriveNW* PVehicleDriveNW = (PxVehicleDriveNW*)PVehicleDrive;
PxVehicleDriveNWSmoothAnalogRawInputsAndSetAnalogInputs(SmoothData, SpeedSteerLookup, RawInputData, DeltaTime, false, *PVehicleDriveNW);
});
}
#endif // WITH_VEHICLE
void UVehicleMovementComponentNW::UpdateEngineSetup(const FVehicleEngineNWData& NewEngineSetup)
{
#if WITH_VEHICLE
if (PVehicleDrive)
{
PxVehicleEngineData EngineData;
GetVehicleEngineSetup(NewEngineSetup, EngineData);
PxVehicleDriveNW* PVehicleDriveNW = (PxVehicleDriveNW*)PVehicleDrive;
PVehicleDriveNW->mDriveSimData.setEngineData(EngineData);
}
#endif // WITH_VEHICLE
}
void UVehicleMovementComponentNW::UpdateDifferentialSetup(const FVehicleDifferentialNWData& NewDifferentialSetup)
{
#if WITH_VEHICLE
if (PVehicleDrive)
{
PxVehicleDifferentialNWData DifferentialData;
GetVehicleDifferentialNWSetup(NewDifferentialSetup, DifferentialData);
PxVehicleDriveNW* PVehicleDriveNW = (PxVehicleDriveNW*)PVehicleDrive;
PVehicleDriveNW->mDriveSimData.setDiffData(DifferentialData);
}
#endif
}
void UVehicleMovementComponentNW::UpdateTransmissionSetup(const FVehicleTransmissionNWData& NewTransmissionSetup)
{
#if WITH_VEHICLE
if (PVehicleDrive)
{
PxVehicleGearsData GearData;
GetVehicleGearSetup(NewTransmissionSetup, GearData);
PxVehicleAutoBoxData AutoBoxData;
GetVehicleAutoBoxSetup(NewTransmissionSetup, AutoBoxData);
PxVehicleDriveNW* PVehicleDriveNW = (PxVehicleDriveNW*)PVehicleDrive;
PVehicleDriveNW->mDriveSimData.setGearsData(GearData);
PVehicleDriveNW->mDriveSimData.setAutoBoxData(AutoBoxData);
}
#endif
}
void BackwardsConvertCm2ToM2(float& val, float defaultValue)
{
if (val != defaultValue)
{
val = Cm2ToM2(val);
}
}
void UVehicleMovementComponentNW::Serialize(FArchive & Ar)
{
Super::Serialize(Ar);
#if WITH_VEHICLE
if (Ar.IsLoading() && Ar.UE4Ver() < VER_UE4_VEHICLES_UNIT_CHANGE)
{
PxVehicleEngineData DefEngineData;
float DefaultRPM = OmegaToRPM(DefEngineData.mMaxOmega);
// We need to convert from old units to new. This backwards compatible code fails in the rare case that they were using very strange values that are the new defaults in the correct units.
EngineSetup.MaxRPM = EngineSetup.MaxRPM != DefaultRPM ? OmegaToRPM(EngineSetup.MaxRPM) : DefaultRPM; // Need to convert from rad/s to RPM.
}
if (Ar.IsLoading() && Ar.UE4Ver() < VER_UE4_VEHICLES_UNIT_CHANGE2)
{
PxVehicleEngineData DefEngineData;
PxVehicleClutchData DefClutchData;
// We need to convert from old units to new. This backwards compatible code fails in the rare case that they were using very strange values that are the new defaults in the correct units.
BackwardsConvertCm2ToM2(EngineSetup.DampingRateFullThrottle, DefEngineData.mDampingRateFullThrottle);
BackwardsConvertCm2ToM2(EngineSetup.DampingRateZeroThrottleClutchDisengaged, DefEngineData.mDampingRateZeroThrottleClutchDisengaged);
BackwardsConvertCm2ToM2(EngineSetup.DampingRateZeroThrottleClutchEngaged, DefEngineData.mDampingRateZeroThrottleClutchEngaged);
BackwardsConvertCm2ToM2(EngineSetup.MOI, DefEngineData.mMOI);
BackwardsConvertCm2ToM2(TransmissionSetup.ClutchStrength, DefClutchData.mStrength);
}
#endif
}
void UVehicleMovementComponentNW::ComputeConstants()
{
Super::ComputeConstants();
MaxEngineRPM = EngineSetup.MaxRPM;
}
WheeledVehicleNW.h
// Copyright Unreal Engine Community.
#pragma once
#include "GameFramework/WheeledVehicle.h"
#include "WheeledVehicleNW.generated.h"
class UPhysicalMaterial;
class UCameraComponent;
class USpringArmComponent;
class UTextRenderComponent;
class UInputComponent;
UCLASS(config = Game)
class VEHICLEPROJECT_API AWheeledVehicleNW : public AWheeledVehicle
{
GENERATED_UCLASS_BODY()
// Spring arm that will offset the camera.
UPROPERTY(Category = Camera, VisibleDefaultsOnly, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
USpringArmComponent* SpringArm;
// Camera component that will be our viewpoint.
UPROPERTY(Category = Camera, VisibleDefaultsOnly, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
UCameraComponent* Camera;
// Camera component for the In-Car view.
UPROPERTY(Category = Camera, VisibleDefaultsOnly, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
UCameraComponent* InternalCamera;
/** Text component for the In-Car speed */
UPROPERTY(Category = Display, VisibleDefaultsOnly, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
UTextRenderComponent* InCarSpeed;
/** Text component for the In-Car gear */
UPROPERTY(Category = Display, VisibleDefaultsOnly, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
UTextRenderComponent* InCarGear;
/** Audio component for the engine sound */
UPROPERTY(Category = Display, VisibleDefaultsOnly, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
UAudioComponent* EngineSoundComponent;
public:
/** The current speed as a string e.g. 10 km/h */
UPROPERTY(Category = Display, VisibleDefaultsOnly, BlueprintReadOnly)
FText SpeedDisplayString;
/** The current gear as a string (R,N, 1,2 etc) */
UPROPERTY(Category = Display, VisibleDefaultsOnly, BlueprintReadOnly)
FText GearDisplayString;
/** The color of the In-Car gear text in forward gears */
UPROPERTY(Category = Display, VisibleDefaultsOnly, BlueprintReadOnly)
FColor GearDisplayColor;
/** The color of the In-Car gear text when in reverse */
UPROPERTY(Category = Display, VisibleDefaultsOnly, BlueprintReadOnly)
FColor GearDisplayReverseColor;
/** Are we using In-Car camera */
UPROPERTY(Category = Camera, VisibleDefaultsOnly, BlueprintReadOnly)
bool bInCarCameraActive;
/** Are we in reverse gear */
UPROPERTY(Category = Camera, VisibleDefaultsOnly, BlueprintReadOnly)
bool bInReverseGear;
/** Initial offset of In-Car camera */
FVector InternalCameraOrigin;
// Begin Pawn interface
virtual void SetupPlayerInputComponent(UInputComponent* InputComponent) override;
// End Pawn interface
// Begin Actor interface
virtual void Tick(float Delta) override;
virtual void BeginPlay() override;
// End Actor interface
/** Handle pressing forwards */
void MoveForward(float Val);
/** Setup the strings used on the HUD. */
void SetupInCarHUD();
/** Update the physics material used by the vehicle mesh */
void UpdatePhysicsMaterial();
/** Handle pressing right */
void MoveRight(float Val);
/** Handle handbrake pressed */
void OnHandbrakePressed();
/** Handle handbrake released */
void OnHandbrakeReleased();
/** Switch between cameras */
void OnToggleCamera();
/** Handle reset VR device */
void OnResetVR();
static const FName LookUpBinding;
static const FName LookRightBinding;
static const FName EngineAudioRPM;
private:
/**
* Activate In-Car camera. Enable camera and sets visibility of in-car HUD display.
*
* @param bState true will enable in car view and set visibility of various
*/
void EnableIncarView(const bool bState);
/** Update the gear and speed strings */
void UpdateHUDStrings();
/* Are we on a 'slippery' surface */
bool bIsLowFriction;
/** Slippery Material instance */
UPhysicalMaterial* SlipperyMaterial;
/** Non Slippery Material instance */
UPhysicalMaterial* NonSlipperyMaterial;
public:
/** Returns SpringArm subobject **/
FORCEINLINE USpringArmComponent* GetSpringArm() const { return SpringArm; }
/** Returns Camera subobject **/
FORCEINLINE UCameraComponent* GetCamera() const { return Camera; }
/** Returns InternalCamera subobject **/
FORCEINLINE UCameraComponent* GetInternalCamera() const { return InternalCamera; }
/** Returns InCarSpeed subobject **/
FORCEINLINE UTextRenderComponent* GetInCarSpeed() const { return InCarSpeed; }
/** Returns InCarGear subobject **/
FORCEINLINE UTextRenderComponent* GetInCarGear() const { return InCarGear; }
/** Returns EngineSoundComponent subobject **/
FORCEINLINE UAudioComponent* GetEngineSoundComponent() const { return EngineSoundComponent; }
};
VehicleMovementComponentNW.cpp
// Copyright Unreal Engine Community.
#include "VehicleProject.h"
#include "VehicleProjectWheelFront.h"
#include "VehicleProjectWheelRear.h"
#include "VehicleProjectHud.h"
#include "Components/SkeletalMeshComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include "Components/InputComponent.h"
#include "Components/TextRenderComponent.h"
#include "Sound/SoundCue.h"
#include "VehicleMovementComponentNW.h"
#include "PhysicalMaterials/PhysicalMaterial.h"
#include "Engine/SkeletalMesh.h"
#include "WheeledVehicleNW.h"
#ifdef HMD_INTGERATION
// Needed for VR Headset.
#include "Engine.h"
#include "IHeadMountedDisplay.h"
#endif // HMD_INTGERATION.
const FName AWheeledVehicleNW::LookUpBinding("LookUp");
const FName AWheeledVehicleNW::LookRightBinding("LookRight");
const FName AWheeledVehicleNW::EngineAudioRPM("RPM");
#define LOCTEXT_NAMESPACE "VehicleNW"
AWheeledVehicleNW::AWheeledVehicleNW(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer.SetDefaultSubobjectClass<UVehicleMovementComponentNW>(VehicleMovementComponentName))
{
// Car mesh.
static ConstructorHelpers::FObjectFinder<USkeletalMesh> CarMesh(TEXT("/Game/VehicleAdv/Vehicle/Vehicle_SkelMesh.Vehicle_SkelMesh"));
GetMesh()->SetSkeletalMesh(CarMesh.Object);
static ConstructorHelpers::FClassFinder<UObject> AnimBPClass(TEXT("/Game/VehicleAdv/Vehicle/VehicleAnimationBlueprint"));
GetMesh()->SetAnimationMode(EAnimationMode::AnimationBlueprint);
GetMesh()->SetAnimInstanceClass(AnimBPClass.Class);
// Setup friction materials.
static ConstructorHelpers::FObjectFinder<UPhysicalMaterial> SlipperyMat(TEXT("/Game/VehicleAdv/PhysicsMaterials/Slippery.Slippery"));
SlipperyMaterial = SlipperyMat.Object;
static ConstructorHelpers::FObjectFinder<UPhysicalMaterial> NonSlipperyMat(TEXT("/Game/VehicleAdv/PhysicsMaterials/NonSlippery.NonSlippery"));
NonSlipperyMaterial = NonSlipperyMat.Object;
UVehicleMovementComponentNW* VehicleNW = CastChecked<UVehicleMovementComponentNW>(GetVehicleMovementComponent());
check(VehicleNW->WheelSetups.Num() == 4);
// Wheels/Tires.
// Setup the wheels.
VehicleNW->WheelSetups[0].WheelClass = UVehicleProjectWheelFront::StaticClass();
VehicleNW->WheelSetups[0].BoneName = FName("PhysWheel_FL");
VehicleNW->WheelSetups[0].AdditionalOffset = FVector(0.f, 0.f, 0.f);
VehicleNW->WheelSetups[1].WheelClass = UVehicleProjectWheelFront::StaticClass();
VehicleNW->WheelSetups[1].BoneName = FName("PhysWheel_FR");
VehicleNW->WheelSetups[1].AdditionalOffset = FVector(0.f, 0.f, 0.f);
VehicleNW->WheelSetups[2].WheelClass = UVehicleProjectWheelRear::StaticClass();
VehicleNW->WheelSetups[2].BoneName = FName("PhysWheel_BL");
VehicleNW->WheelSetups[2].AdditionalOffset = FVector(0.f, 0.f, 0.f);
VehicleNW->WheelSetups[3].WheelClass = UVehicleProjectWheelRear::StaticClass();
VehicleNW->WheelSetups[3].BoneName = FName("PhysWheel_BR");
VehicleNW->WheelSetups[3].AdditionalOffset = FVector(0.f, 0.f, 0.f);
// Adjust the tire loading.
VehicleNW->MinNormalizedTireLoad = 0.0f;
VehicleNW->MinNormalizedTireLoadFiltered = 0.2f;
VehicleNW->MaxNormalizedTireLoad = 2.0f;
VehicleNW->MaxNormalizedTireLoadFiltered = 2.0f;
// Engine.
// Torque setup.
VehicleNW->MaxEngineRPM = 5700.0f;
VehicleNW->EngineSetup.TorqueCurve.GetRichCurve()->Reset();
VehicleNW->EngineSetup.TorqueCurve.GetRichCurve()->AddKey(0.0f, 400.0f);
VehicleNW->EngineSetup.TorqueCurve.GetRichCurve()->AddKey(1890.0f, 500.0f);
VehicleNW->EngineSetup.TorqueCurve.GetRichCurve()->AddKey(5730.0f, 400.0f);
// Adjust the steering .
VehicleNW->SteeringCurve.GetRichCurve()->Reset();
VehicleNW->SteeringCurve.GetRichCurve()->AddKey(0.0f, 1.0f);
VehicleNW->SteeringCurve.GetRichCurve()->AddKey(40.0f, 0.7f);
VehicleNW->SteeringCurve.GetRichCurve()->AddKey(120.0f, 0.6f);
// Automatic gearbox.
VehicleNW->TransmissionSetup.bUseGearAutoBox = true;
VehicleNW->TransmissionSetup.GearSwitchTime = 0.15f;
VehicleNW->TransmissionSetup.GearAutoBoxLatency = 1.0f;
// Physics settings.
// Adjust the center of mass - the buggy is quite low.
UPrimitiveComponent* UpdatedPrimitive = Cast<UPrimitiveComponent>(VehicleNW->UpdatedComponent);
if (UpdatedPrimitive)
{
UpdatedPrimitive->BodyInstance.COMNudge = FVector(8.0f, 0.0f, 0.0f);
}
// Set the inertia scale. This controls how the mass of the vehicle is distributed.
VehicleNW->InertiaTensorScale = FVector(1.0f, 1.333f, 1.2f);
// Create a spring arm component for our chase camera.
SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
SpringArm->SetRelativeLocation(FVector(0.0f, 0.0f, 34.0f));
SpringArm->SetWorldRotation(FRotator(-20.0f, 0.0f, 0.0f));
SpringArm->AttachTo(RootComponent);
SpringArm->TargetArmLength = 125.0f;
SpringArm->bEnableCameraLag = false;
SpringArm->bEnableCameraRotationLag = false;
SpringArm->bInheritPitch = true;
SpringArm->bInheritYaw = true;
SpringArm->bInheritRoll = true;
// Create the chase camera component .
Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("ChaseCamera"));
Camera->AttachTo(SpringArm, USpringArmComponent::SocketName);
Camera->SetRelativeRotation(FRotator(10.0f, 0.0f, 0.0f));
Camera->bUsePawnControlRotation = false;
Camera->FieldOfView = 90.f;
// Create In-Car camera component .
InternalCameraOrigin = FVector(-34.0f, 0.0f, 50.0f);
InternalCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("InternalCamera"));
//InternalCamera->AttachTo(SpringArm, USpringArmComponent::SocketName);
InternalCamera->bUsePawnControlRotation = false;
InternalCamera->FieldOfView = 90.f;
InternalCamera->SetRelativeLocation(InternalCameraOrigin);
InternalCamera->AttachTo(GetMesh());
// In car HUD.
// Create text render component for in car speed display.
InCarSpeed = CreateDefaultSubobject<UTextRenderComponent>(TEXT("IncarSpeed"));
InCarSpeed->SetRelativeScale3D(FVector(0.1f, 0.1f, 0.1f));
InCarSpeed->SetRelativeLocation(FVector(35.0f, -6.0f, 20.0f));
InCarSpeed->SetRelativeRotation(FRotator(0.0f, 180.0f, 0.0f));
InCarSpeed->AttachTo(GetMesh());
// Create text render component for in car gear display.
InCarGear = CreateDefaultSubobject<UTextRenderComponent>(TEXT("IncarGear"));
InCarGear->SetRelativeScale3D(FVector(0.1f, 0.1f, 0.1f));
InCarGear->SetRelativeLocation(FVector(35.0f, 5.0f, 20.0f));
InCarGear->SetRelativeRotation(FRotator(0.0f, 180.0f, 0.0f));
InCarGear->AttachTo(GetMesh());
// Setup the audio component and allocate it a sound cue.
static ConstructorHelpers::FObjectFinder<USoundCue> SoundCue(TEXT("/Game/VehicleAdv/Sound/Engine_Loop_Cue.Engine_Loop_Cue"));
EngineSoundComponent = CreateDefaultSubobject<UAudioComponent>(TEXT("EngineSound"));
EngineSoundComponent->SetSound(SoundCue.Object);
EngineSoundComponent->AttachTo(GetMesh());
// Colors for the in-car gear display. One for normal one for reverse.
GearDisplayReverseColor = FColor(255, 0, 0, 255);
GearDisplayColor = FColor(255, 255, 255, 255);
bIsLowFriction = false;
bInReverseGear = false;
}
void AWheeledVehicleNW::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
// Set up gameplay key bindings.
check(InputComponent);
InputComponent->BindAxis("MoveForward", this, &AWheeledVehicleNW::MoveForward);
InputComponent->BindAxis("MoveRight", this, &AWheeledVehicleNW::MoveRight);
InputComponent->BindAxis(LookUpBinding);
InputComponent->BindAxis(LookRightBinding);
InputComponent->BindAction("Handbrake", IE_Pressed, this, &AWheeledVehicleNW::OnHandbrakePressed);
InputComponent->BindAction("Handbrake", IE_Released, this, &AWheeledVehicleNW::OnHandbrakeReleased);
InputComponent->BindAction("SwitchCamera", IE_Pressed, this, &AWheeledVehicleNW::OnToggleCamera);
InputComponent->BindAction("ResetVR", IE_Pressed, this, &AWheeledVehicleNW::OnResetVR);
}
void AWheeledVehicleNW::MoveForward(float Val)
{
GetVehicleMovementComponent()->SetThrottleInput(Val);
}
void AWheeledVehicleNW::MoveRight(float Val)
{
GetVehicleMovementComponent()->SetSteeringInput(Val);
}
void AWheeledVehicleNW::OnHandbrakePressed()
{
GetVehicleMovementComponent()->SetHandbrakeInput(true);
}
void AWheeledVehicleNW::OnHandbrakeReleased()
{
GetVehicleMovementComponent()->SetHandbrakeInput(false);
}
void AWheeledVehicleNW::OnToggleCamera()
{
EnableIncarView(!bInCarCameraActive);
}
void AWheeledVehicleNW::EnableIncarView(const bool bState)
{
if (bState != bInCarCameraActive)
{
bInCarCameraActive = bState;
if (bState == true)
{
OnResetVR();
Camera->Deactivate();
InternalCamera->Activate();
APlayerController* PlayerController = GetWorld()->GetFirstPlayerController();
if ((PlayerController != nullptr) && (PlayerController->PlayerCameraManager != nullptr))
{
PlayerController->PlayerCameraManager->bFollowHmdOrientation = true;
}
}
else
{
InternalCamera->Deactivate();
Camera->Activate();
}
InCarSpeed->SetVisibility(bInCarCameraActive);
InCarGear->SetVisibility(bInCarCameraActive);
}
}
void AWheeledVehicleNW::Tick(float Delta)
{
Super::Tick(Delta);
// Setup the flag to say we are in reverse gear.
bInReverseGear = GetVehicleMovementComponent()->GetCurrentGear() < 0;
// Update physics material.
UpdatePhysicsMaterial();
// Update the strings used in the HUD (In-car and On-screen).
UpdateHUDStrings();
// Set the string in the In-car HUD.
SetupInCarHUD();
bool bHMDActive = false;
#ifdef HMD_INTGERATION
if ((GEngine->HMDDevice.IsValid() == true) && ((GEngine->HMDDevice->IsHeadTrackingAllowed() == true) || (GEngine->IsStereoscopic3D() == true)))
{
bHMDActive = true;
}
#endif // HMD_INTGERATION.
if (bHMDActive == false)
{
if ((InputComponent) && (bInCarCameraActive == true))
{
FRotator HeadRotation = InternalCamera->RelativeRotation;
HeadRotation.Pitch += InputComponent->GetAxisValue(LookUpBinding);
HeadRotation.Yaw += InputComponent->GetAxisValue(LookRightBinding);
InternalCamera->RelativeRotation = HeadRotation;
}
}
// Pass the engine RPM to the sound component.
float RPMToAudioScale = 2500.0f / GetVehicleMovementComponent()->GetEngineMaxRotationSpeed();
EngineSoundComponent->SetFloatParameter(EngineAudioRPM, GetVehicleMovementComponent()->GetEngineRotationSpeed()*RPMToAudioScale);
}
void AWheeledVehicleNW::BeginPlay()
{
Super::BeginPlay();
bool bWantInCar = false;
// First disable both speed/gear displays.
bInCarCameraActive = false;
InCarSpeed->SetVisibility(bInCarCameraActive);
InCarGear->SetVisibility(bInCarCameraActive);
#ifdef HMD_INTGERATION
// Enable in car view if HMD is attached.
bWantInCar = GEngine->HMDDevice.IsValid()
#endif // HMD_INTGERATION
EnableIncarView(bWantInCar);
// Start an engine sound playing.
EngineSoundComponent->Play();
}
void AWheeledVehicleNW::OnResetVR()
{
#ifdef HMD_INTGERATION
if (GEngine->HMDDevice.IsValid())
{
GEngine->HMDDevice->ResetOrientationAndPosition();
InternalCamera->SetRelativeLocation(InternalCameraOrigin);
GetController()->SetControlRotation(FRotator());
}
#endif // HMD_INTGERATION.
}
void AWheeledVehicleNW::UpdateHUDStrings()
{
float KPH = FMath::Abs(GetVehicleMovementComponent()->GetForwardSpeed()) * 0.036f;
int32 KPH_int = FMath::FloorToInt(KPH);
// Using FText because this is display text that should be localizable.
SpeedDisplayString = FText::Format(LOCTEXT("SpeedFormat", "{0} km/h"), FText::AsNumber(KPH_int));
if (bInReverseGear == true)
{
GearDisplayString = FText(LOCTEXT("ReverseGear", "R"));
}
else
{
int32 Gear = GetVehicleMovementComponent()->GetCurrentGear();
GearDisplayString = (Gear == 0) ? LOCTEXT("N", "N") : FText::AsNumber(Gear);
}
}
void AWheeledVehicleNW::SetupInCarHUD()
{
APlayerController* PlayerController = Cast<APlayerController>(GetController());
if ((PlayerController != nullptr) && (InCarSpeed != nullptr) && (InCarGear != nullptr))
{
// Setup the text render component strings.
InCarSpeed->SetText(SpeedDisplayString);
InCarGear->SetText(GearDisplayString);
if (bInReverseGear == false)
{
InCarGear->SetTextRenderColor(GearDisplayColor);
}
else
{
InCarGear->SetTextRenderColor(GearDisplayReverseColor);
}
}
}
void AWheeledVehicleNW::UpdatePhysicsMaterial()
{
if (GetActorUpVector().Z < 0)
{
if (bIsLowFriction == true)
{
GetMesh()->SetPhysMaterialOverride(NonSlipperyMaterial);
bIsLowFriction = false;
}
else
{
GetMesh()->SetPhysMaterialOverride(SlipperyMaterial);
bIsLowFriction = true;
}
}
}