The Re-Inventing the Wheel Thread

Bad Piggies and KSP both use Unity which actually has fixed timestepping on physics btw.

Edit-> Well, actually it’s not truly fixed timestep on unity either but at least it’s trying to run the physics at fixed rate there by default vs UE4 which has variable update rate on physics unless you manually change it.

so, trying to learn from mine and other peoples experiences with suspension,
a problem lies in getting the velocity. since you cant access a components physics velocity you have to cheat and use deltatime. when deltatime becomes too big due to low framerate, the calculations become ‘corrupt’ so to speak and there are issues.
this is why i messed up in my original screenshots, i was trying all sorts of ways to get a reliable velocity and accidentally moved that part out of the screenshot as i thought it was just part of the mess that didnt work.
every time the car looked as if it had good suspension it would at some point look like a plane going through turbulence or a low rider with a monkey at the controls.

since you cant access sub step in blueprints ive been trying to do it in code, which leads to probably the most important question…
how do you access sub step in code?

i found only an old post with not quite enough information, also it says

but of course there is nothing in the documentation and no examples of how to use this feature, or even if it is present in the current engine version at all

also the idea of using a constraint seems more logical/reliable than applying an upwards force to the chassis.
i think that using a wheel with actual collision constrained to the chassis would be far more reliable or less prone to launching the vehicle into the air than a line trace and addforce.

Do you mean that sample code provided by toxygen is not enough? His pull request was closed long time ago, so I think correction to the code was added.
I’m planning to try this spring and damper approach:
http://www.gamedev.net/page/resources/_/technical/math-and-physics/towards-a-simpler-stiffer-and-more-stable-spring-r3227
was thinking about it today and I don’t see a reason why I need to calculate variable effective mass as simply providing equally distributed mass of the vehicle in passive state should be enough. The main benefit of such formulation is that amplitude of force become depended on the value of delta time and should scale with it. Another nice befit is that damping is not related to time more than to speed which means that spring can produce X amount of force at Y amount of time. This is really nice because if one wants to have collision on wheels, you would have a nice formulation of how wheel impact force should be distributed between suspension spring, then shock absorber and what is left will go into chassis.

Regarding wheels collision. I know that PhysX doesn’t provide cylinder as a collision primitive. Do you guys think it’s possible to imitate it by filtering results of collision query using cube and a capsule?
Something like this:

  • query multi-collision on capsule and box into two separate arrays
  • transform collision points into object space of the wheel
  • filter out capsule collision points on their values of Y axis (assuming that Y is a right vector and Z is up vector of wheel)
  • filter out box collision points on the distance from center of the wheel in YZ plane
  • somehow chose a single collision point or pass all of them as output (can be used to generate patch for tire simulation)

yes
probably because i really dont know what im doing with ue4’s c++. i tried to copy what was there and got compilation errors, and im not totally sure where to put what tbh.
might just stick with blueprints for now and try a few different formulas

What you mean by access? You’ve already accessed rb velocity on your screenshots. Only issue I’ve seen on BPs that you can’t read the (physx) physics changes faster than Tick which itself can vary a lot based on actual rendering frame rate (well, on top of obvious performance issues).

It’s not that tricky, most of the code needed has been already posted on the pull request itself, just things on header are missing from the example. I will make a simple step by step example and post the code on this thread later on (today or tomorrow).

If you make open wheel vehicle, you of course need to cover the line trace. If you don’t do that, you’ll get behaviour like that. You could probably cover the wheel with cylinder collider that have collision setup that doesn’t block traces and actual road etc surfaces. That way colliders wouldn’t affect the regular simulation and you could still handle cases where wheels hit something like fences, railings etc. You could even handle some more specific scenarios with hit events where another vehicle has wheel on top of your vehicles wheels etc.

It’s been added to the engine since 4.7.

There is collision filtering, callbacks etc built-in to physx but I haven’t done research how that all works. Pretty sure you’ll find more info on that if you study physx sdk docs. If you could do that, wouldn’t you want use capsule collision instead of sphere though?

in the screenshot its for the chassis, or the root component of the car. i tried to get velocity from the wheel meshes but of course they dont have physics, and a line trace is just that. so getting the velocity of a ‘spring’ has to be some kind of hack using deltatime.

thanks both of you for all your input so far in this thread its very interesting, there’s lots to think about, lots to learn and lots to try :slight_smile:

Yeap, I mean capsule, not sphere. [Corrected]

Oh right, I think that when cars body doesn’t lean into any direction, velocity measured from main chassis should still give correct results. Meaning that it should work fine on even and inclined surfaces, but would give wrong data on bumps or if body leans in to some direction. Of course that’s not ideal but it seemed to work well enough to kill the bouncy springs when I used it like that on my earlier prototypes, I never tested it on very rough terrain though.

I wouldn’t consider calculating velocity using deltatime to be a hack, you may get precision issues but that’s something you always have to deal with when you do real-time physics.

Btw, if you want to simulate wheels as rigidbodies (which are set to not collide directly with the ground), you could connect rigidbodies to main chassis using constraints and still use traces or overlap events (I should check how you could access this data directly on substeps) to figure out the forces needed. This is more sim approach and will require more complex setup.

I made a simple c++ component example that uses substepping. I tried to remake this hover component tutorial.

Main difference is that it uses StaticMeshComponent as base and static mesh with collider needs to be Actors (or Pawns) root component. There has to be some better way to handle this, but I didn’t bother looking further on that as it’s more of an example how to use custom physics with substepping. Damping also uses the -b*v formula instead of dampling everything like on that hover component tutorial.

Steps for any existing or blank project (BP or c++):

  1. Make sure you have substepping enabled from your Project Settings, you probably want to crank the max substeps to 16 right away for this.

  2. Add new C++ class and select Scene Component (you could use the same code on c++ actor/pawn mostly as is btw, just few lines would change, but let’s do the scene component now). You can do this from File menu or by clicking Add New on content browser.

  3. Make sure your new Scene Component is named as SuspensionComponent (so you can just copy paste the code as is):

  1. Open SuspensionComponent.h and find line that begins with something like: “class SUBSTEPEXAMPLE_API USuspensionComponent : public USceneComponent”. Yours will have different projectname_API on it. Paste the following code into SuspensionComponent.h but replace SUBSTEPEXAMPLE_API with your original projectname_API:

NOTE: In case you run this on UE 4.22+, change PhysSceneStep(FPhysScene PhysScene, uint32 SceneType, float DeltaTime) to PhysSceneStep(FPhysScene PhysScene, float DeltaTime) from the following code snippets as there’s no SceneType anymore in 4.22.**


#pragma once

#include "CoreMinimal.h"
#include "Components/SceneComponent.h"
#include "SuspensionComponent.generated.h"


UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class SUBSTEPEXAMPLE_API USuspensionComponent : public USceneComponent
{
    GENERATED_BODY()

private:
    FHitResult Trace(FVector TraceStart, FVector TraceDirection);
    FBodyInstance *BodyInstance;
    bool CacheBodyInstance();
    FDelegateHandle OnPhysSceneStepHandle;
    void PhysSceneStep(FPhysScene* PhysScene, uint32 SceneType, float DeltaTime);

    UPROPERTY()
    float PreviousDistance;

public:    
    virtual void BeginPlay() override;
    virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;

    UPROPERTY(EditAnywhere, Category = "Suspension")
    float TraceLength = 100.0f;
    UPROPERTY(EditAnywhere, Category = "Suspension")
    float SpringCoeff = 1000000.0f;
    UPROPERTY(EditAnywhere, Category = "Suspension")
    float DamperCoeff = 500.0f;
};

  1. Open SuspensionComponent.cpp, find first include line which is something like #include “yourprojectname.h”. Leave that include in place but replace the rest with this:

#include "SuspensionComponent.h"
#include "PhysicsPublic.h"

void USuspensionComponent::BeginPlay()
{
    Super::BeginPlay();

    UWorld* World = GetWorld();
    if (World)
    {
        FPhysScene* PScene = World->GetPhysicsScene();
        if (PScene)
        {
            // Register UE 4.15+ substep delegate
            OnPhysSceneStepHandle = PScene->OnPhysSceneStep.AddUObject(this, &USuspensionComponent::PhysSceneStep);
        }
    }
}

void USuspensionComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
    UWorld* World = GetWorld();
    if (World)
    {
        FPhysScene* PScene = World->GetPhysicsScene();
        if (PScene)
        {
            // Unregister substep delegate when component is removed
            PScene->OnPhysSceneStep.Remove(OnPhysSceneStepHandle);
        }
    }

    Super::EndPlay(EndPlayReason);
}

FHitResult USuspensionComponent::Trace(FVector TraceStart, FVector TraceDirection)
{
    FHitResult Hit(ForceInit);
    FCollisionQueryParams TraceParams(true);
    TraceParams.bTraceAsyncScene = true;
    TraceParams.bReturnPhysicalMaterial = false;
    FVector TraceEnd = TraceStart + (TraceDirection * TraceLength);
    GetWorld()->LineTraceSingleByChannel(Hit, TraceStart, TraceEnd, ECC_WorldDynamic, TraceParams);
    return Hit;
}

bool USuspensionComponent::CacheBodyInstance()
{
    // Get the primitive component from actors root (mesh or collider)
    UPrimitiveComponent* PrimitiveComponent = Cast<UPrimitiveComponent>(GetOwner()->GetRootComponent());

    // Cache the BodyInstance
    if (PrimitiveComponent)
    {
        BodyInstance = PrimitiveComponent->GetBodyInstance();
    }
    if (!BodyInstance)
    {
        return false;
    }
    return true;
}

// Called every physics substep
void USuspensionComponent::PhysSceneStep(FPhysScene* PhysScene, uint32 SceneType, float DeltaTime)
{
    // Make sure we have a valid BodyInstance
    if (!BodyInstance)
    {
        if (!CacheBodyInstance())
        {
            return;
        }
    }
    // Get up-to-date location directly from BodyInstance's transform
    FVector WorldLocation = BodyInstance->GetUnrealWorldTransform().GetLocation();

    FHitResult Hit = Trace(WorldLocation, -FVector::UpVector);
    if (!Hit.bBlockingHit)
    {
        PreviousDistance = TraceLength;
        return;
    }
    float CurrentDistance = (WorldLocation - Hit.Location).Size();
    float DamperVelocity = (CurrentDistance - PreviousDistance) / DeltaTime;
    PreviousDistance = CurrentDistance;

    // Calculate spring force
    float SpringForce = (1 - (CurrentDistance / TraceLength)) * SpringCoeff;

    // Apply damper force
    SpringForce -= DamperVelocity * DamperCoeff;

    FVector TotalForce = SpringForce * Hit.ImpactNormal;

    // You need to set bAllowSubstepping param to false on AddForce, only use it when calling AddForce from Tick
    BodyInstance->AddForce(TotalForce, false);
}

  1. Save .cpp and .h changes and compile. Hot-reload will probably not work for the first time, so prepare to compile from the code environment.

  2. Create a new blank Blueprint Class and select Actor (Pawn should work just fine as well).

  3. Go to Viewport mode and using Add Component add a Sphere and make it the root component on the BP. Also make sure you enable physics simulation for the Sphere.

  4. Using Add Component, now add Suspension (our new component which we just made) and attach it to Sphere.

  5. Save and Compile the BP, it should now look like this:

  1. Drag your new BP to your scene and hit play or simulate. It should now hover a ball, like on that hover tutorial. That’s it.

Do note that I tried to make this a very minimalistic example how to run physics directly from substeps, there isn’t many checks in place and there may be some small errors. If you find anything odd, drop me a note and I’ll correct it.

Tried it and got this:



Info Compiling game modules for hot reload
Info Parsing headers for SubSteppingExampleEditor
Info   Running UnrealHeaderTool "D:/Unreal/Unreal Projects/SubSteppingExample/SubSteppingExample.uproject" "D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\SubSteppingExampleEditor\Development\UnrealHeaderTool.manifest" -LogCmds="loginit warning, logexit warning, logdatabase error" -rocket -installed
Info Reflection code generated for SubSteppingExampleEditor in 8.8745666 seconds
Info Performing 3 actions (4 in parallel)
Info SuspensionComponent.cpp
Info SubSteppingExample.generated.cpp
Error d:\unreal\unreal projects\substeppingexample\source\substeppingexample\SuspensionComponent.h(13)  : error C2079: 'USuspensionComponent' uses undefined class 'SUBSTEPEXAMPLE_API'
Error d:\unreal\unreal projects\substeppingexample\source\substeppingexample\SuspensionComponent.h(13)  : error C2143: syntax error : missing ';' before ':'
Error d:\unreal\unreal projects\substeppingexample\source\substeppingexample\SuspensionComponent.h(13)  : error C2059: syntax error : ':'
Error d:\unreal\unreal projects\substeppingexample\source\substeppingexample\SuspensionComponent.h(13)  : error C2059: syntax error : 'public'
Error d:\unreal\unreal projects\substeppingexample\source\substeppingexample\SuspensionComponent.h(14)  : error C2143: syntax error : missing ';' before '{'
Error d:\unreal\unreal projects\substeppingexample\source\substeppingexample\SuspensionComponent.h(14)  : error C2447: '{' : missing function header (old-style formal list?)
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(4)  : error C2825: 'USuspensionComponent': must be a class or namespace when followed by '::'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.h(13)  : error C2079: 'USuspensionComponent' uses undefined class 'SUBSTEPEXAMPLE_API'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.h(13)  : error C2143: syntax error : missing ';' before ':'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.h(13)  : error C2059: syntax error : ':'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.h(13)  : error C2059: syntax error : 'public'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.h(14)  : error C2143: syntax error : missing ';' before '{'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.h(14)  : error C2447: '{' : missing function header (old-style formal list?)
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(16)  : error C2825: 'USuspensionComponent': must be a class or namespace when followed by '::'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(16)  : error C2039: 'StaticRegisterNativesUSuspensionComponent' : is not a member of '`global namespace''
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(16)  : error C2146: syntax error : missing ';' before identifier 'StaticRegisterNativesUSuspensionComponent'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(16)  : error C2182: 'USuspensionComponent' : illegal use of type 'void'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(16)  : error C2086: 'int USuspensionComponent' : redefinition
Info          D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.h(13)  : see declaration of 'USuspensionComponent'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(4)  : error C2143: syntax error : missing ';' before 'USuspensionComponent'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(17)  : error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(4)  : error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(4)  : error C2086: 'int USuspensionComponent' : redefinition
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(18)  : error C4508: 'StaticRegisterNativesUSuspensionComponent' : function should return a value; 'void' return type assumed        d:\unreal\unreal projects\substeppingexample\source\substeppingexample\SuspensionComponent.h(13) : see declaration of 'USuspensionComponent'
Info 
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(5)  : error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(5)  : error C2063: 'USuspensionComponent' : not a function
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(19)  : error C2923: 'TClassCompiledInDefer' : 'USuspensionComponent' is not a valid template type argument for parameter 'TClass'
Info          D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.h(13)  : see declaration of 'USuspensionComponent'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(19)  : error C2514: 'TClassCompiledInDefer' : class has no constructors
Info          d:\unreal\unreal engine\4.9\engine\source\runtime\coreuobject\public\uobject\UObjectBase.h(313)  : see declaration of 'TClassCompiledInDefer'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(19)  : error C2825: 'USuspensionComponent': must be a class or namespace when followed by '::'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(19)  : error C2039: 'GetPrivateStaticClass' : is not a member of '`global namespace''
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(19)  : error C2146: syntax error : missing ';' before identifier 'GetPrivateStaticClass'D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(5) : error C3755: 'USuspensionComponent': a delegate may not be defined
Info 
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(15)  : error C2825: 'USuspensionComponent': must be a class or namespace when followed by '::'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(15)  : error C2039: 'BeginPlay' : is not a member of '`global namespace''
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(15)  : error C2146: syntax error : missing ';' before identifier 'BeginPlay'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(15)  : error C2182: 'USuspensionComponent' : illegal use of type 'void'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(15)  : error C2086: 'int USuspensionComponent' : redefinition
Info          d:\unreal\unreal projects\substeppingexample\source\substeppingexample\SuspensionComponent.h(13)  : see declaration of 'USuspensionComponent'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(19)  : error C2373: 'USuspensionComponent' : redefinition; different type modifiers
Info          D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.h(13)  : see declaration of 'USuspensionComponent'D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(16) : error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
Info 
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(19)  : error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(18)  : error C2065: 'MainBodyMesh' : undeclared identifier
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(18)  : error C2227: left of '-&gt;GetRootComponent' must point to class/struct/union/generic type
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(19)  : error C2065: 'StaticClassFlags' : undeclared identifier        type is 'unknown-type'
Info 
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(19)  : error C2974: 'GetPrivateStaticClassBody' : invalid template argument for 'TClass', type expected
Info          D:\Unreal\Unreal Engine\4.9\Engine\Source\Runtime\CoreUObject\Public\UObject\Class.h(2387)  : see declaration of 'GetPrivateStaticClassBody'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(19)  : error C2440: 'return' : cannot convert from 'UClass *' to 'int'
Info         There is no context in which this conversion is possible
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(19)  : error C2617: 'GetPrivateStaticClass' : inconsistent return statement
Info          D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(19)  : see declaration of 'GetPrivateStaticClass'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(18)  : error C3861: 'GetOwner': identifier not found
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(19)  : error C2065: 'MainBodyMesh' : undeclared identifier
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(20)  : error C2065: 'MainBodyInstance' : undeclared identifier
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(20)  : error C2065: 'MainBodyMesh' : undeclared identifierD:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(67) : error C2825: 'USuspensionComponent': must be a class or namespace when followed by '::'
Info 
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(20)  : error C2227: left of '-&gt;GetBodyInstance' must point to class/struct/union/generic type
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(67)  : error C2039: 'StaticClass' : is not a member of '`global namespace''        type is 'unknown-type'
Info 
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(67)  : error C2146: syntax error : missing ';' before identifier 'StaticClass'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(67)  : error C3861: 'StaticClass': identifier not foundD:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(22) : error C2653: 'Super' : is not a class or namespace name
Info 
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(23)  : error C4508: 'BeginPlay' : function should return a value; 'void' return type assumed
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(27)  : error C2825: 'USuspensionComponent': must be a class or namespace when followed by '::'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(76)  : error C2825: 'USuspensionComponent': must be a class or namespace when followed by '::'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(27)  : error C2039: 'TickComponent' : is not a member of '`global namespace''D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(76) : error C2039: 'StaticClass' : is not a member of '`global namespace''
Info 
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(27)  : error C2146: syntax error : missing ';' before identifier 'TickComponent'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(76)  : error C2440: '=' : cannot convert from 'int' to 'UClass *'
Info         Conversion from integral type to pointer type requires reinterpret_cast, C-style cast or function-style cast
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(27)  : error C2182: 'USuspensionComponent' : illegal use of type 'void'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(76)  : error C2146: syntax error : missing ';' before identifier 'StaticClass'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(27)  : error C2086: 'int USuspensionComponent' : redefinitionD:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(76) : error C3861: 'StaticClass': identifier not found
Info 
Info          d:\unreal\unreal projects\substeppingexample\source\substeppingexample\SuspensionComponent.h(13)  : see declaration of 'USuspensionComponent'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(28)  : error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(29)  : error C2653: 'Super' : is not a class or namespace name
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(32)  : error C2065: 'MainBodyInstance' : undeclared identifierD:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(84) : error C2059: syntax error : ')'
Info 
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(84)  : error C2143: syntax error : missing '(' before ')'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(33)  : error C2065: 'MainBodyInstance' : undeclared identifier
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(33)  : error C2227: left of '->AddCustomPhysics' must point to class/struct/union/generic type
Info         type is 'unknown-type'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(33)  : error C2065: 'OnCalculateCustomPhysics' : undeclared identifier
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(35)  : error C4508: 'TickComponent' : function should return a value; 'void' return type assumed
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(38)  : error C2825: 'USuspensionComponent': must be a class or namespace when followed by '::'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(38)  : error C2039: 'CustomPhysics' : is not a member of '`global namespace''
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(38)  : error C2146: syntax error : missing ';' before identifier 'CustomPhysics'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(38)  : error C2182: 'USuspensionComponent' : illegal use of type 'void'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(38)  : error C2086: 'int USuspensionComponent' : redefinition
Info          d:\unreal\unreal projects\substeppingexample\source\substeppingexample\SuspensionComponent.h(13)  : see declaration of 'USuspensionComponent'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(39)  : error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(43)  : error C3861: 'Trace': identifier not found
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(48)  : error C2065: 'bOnGround' : undeclared identifier
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(49)  : error C2065: 'PreviousPosition' : undeclared identifier
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(51)  : error C2065: 'PreviousPosition' : undeclared identifier
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(52)  : error C2065: 'PreviousPosition' : undeclared identifier
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(53)  : error C2065: 'bOnGround' : undeclared identifier
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(56)  : error C2065: 'TraceLength' : undeclared identifier
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(56)  : error C2065: 'SpringValue' : undeclared identifier
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(59)  : error C2065: 'DamperValue' : undeclared identifier
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(87)  : error C2059: syntax error : ')'D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(69) : error C2065: 'bOnGround' : undeclared identifier
Info 
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(70)  : error C2065: 'SpringPosition' : undeclared identifierD:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(87) : error C2143: syntax error : missing '(' before ')'
Info 
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(70)  : error C2065: 'TraceLength' : undeclared identifier
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(72)  : error C4508: 'CustomPhysics' : function should return a value; 'void' return type assumed
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(74)  : error C2825: 'USuspensionComponent': must be a class or namespace when followed by '::'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(74)  : error C2039: 'Trace' : is not a member of '`global namespace''
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(74)  : error C2146: syntax error : missing ';' before identifier 'Trace'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(74)  : error C2371: 'USuspensionComponent' : redefinition; different basic types
Info          d:\unreal\unreal projects\substeppingexample\source\substeppingexample\SuspensionComponent.h(13)  : see declaration of 'USuspensionComponent'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(88)  : error C2059: syntax error : ')'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(88)  : error C2143: syntax error : missing '(' before ')'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(74)  : error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(89)  : error C2059: syntax error : ')'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(89)  : error C2143: syntax error : missing '(' before ')'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(79)  : error C2065: 'TraceLength' : undeclared identifier
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(90)  : error C2059: syntax error : ')'D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(80) : error C2227: left of '->LineTraceSingleByChannel' must point to class/struct/union/generic type
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(90)  : error C2143: syntax error : missing '(' before ')'
Info         type is 'unknown-type'
Info 
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(80)  : error C3861: 'GetWorld': identifier not found
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(81)  : error C2440: 'return' : cannot convert from 'FHitResult' to 'int'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(91)  : error C2059: syntax error : ')'
Info          No user-defined-conversion operator available that can perform this conversion, or the operator cannot be calledD:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(91)  : error C2143: syntax error : missing '(' before ')'
Info 
Error D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(82)  : error C2617: 'Trace' : inconsistent return statement
Info          D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.cpp(74)  : see declaration of 'Trace'D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(85) : error C2059: syntax error : ')'
Info 
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(121)  : error C2825: 'USuspensionComponent': must be a class or namespace when followed by '::'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(121)  : error C2143: syntax error : missing ';' before 'USuspensionComponent'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(121)  : error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(121)  : error C2086: 'int USuspensionComponent' : redefinition
Info          D:\Unreal\Unreal Projects\SubSteppingExample\Source\SubSteppingExample\SuspensionComponent.h(13)  : see declaration of 'USuspensionComponent'
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(121)  : error C2063: 'USuspensionComponent' : not a function
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(121)  : error C3755: 'USuspensionComponent': a delegate may not be defined
Error D:\Unreal\Unreal Projects\SubSteppingExample\Intermediate\Build\Win64\UE4Editor\Inc\SubSteppingExample\SubSteppingExample.generated.cpp(121)  : error C2065: 'Helper' : undeclared identifier
Info -------- End Detailed Actions Stats -----------------------------------------------------------
Info ERROR: UBT ERROR: Failed to produce item: D:\Unreal\Unreal Projects\SubSteppingExample\Binaries\Win64\UE4Editor-SubSteppingExample-2546.dll
Info Total build time: 11.96 seconds


Are you using launcher distributed 9.2?

That should work on most recent engine versions, but I forgot that header includes that one line which is your project name specific :slight_smile: Sorry about that. Replace that SUBSTEPEXAMPLE_API line with the line that UE4 generates by default on that part and it should compile.

I also missed the project name specific include on that cpp-file. I added notes on steps 4 & 5 about this. Should have tested this myself on blank project first. :slight_smile:

Ohh my bad, I thought I’ve named it the same :smiley:
Second fix was necessary, project header was missing, be default it’s added to cpp. In my case it was #include “SubSteppingExample.h”.

Great example! Works really well even at 10 fps. Thank you for sharing!

thanks 0lento
you should put that code on the wiki, sometimes all it takes is a simple working example of something to open that door.
is does work really well too, its noticeably smoother than anything ive seen in blueprints.

Nice to see that you both got it working :slight_smile: If you plan to build a full vehicle pawn or actor using that approach, you’d probably want to do that custom physics part on one centralized class and not separately for each wheel. You could still have wheels/suspension on components but call the physics related code from the main class (just to reduce the overhead).

If you play around with this stuff, be aware that you shouldn’t change mesh locations, do debug draws or write to uobjects on sub-steps itself. If you want for example to change a wheel position, you’d need to change a variable on sub-step to get the wheel position figured out and update the mesh location based on that var only once per tick (it also wouldn’t really make sense to update them while they are not rendered).

While we are at is, I remind that on physics step you can only rely on location data what you can read from FBodyIntances, if you read transforms etc from mesh objects itself, you’ll get data that has been updated last time on previous tick. This is something I did myself at first while not paying attention, although it should be obvious. Also beware that collision callbacks will be handled only once per tick as well. I should really look into that collision issue at some point as I’d really want to access that right away. Reason for why it’s done like it is, is explained here: Physics Sub-Stepping in Unreal Engine | Unreal Engine 5.3 Documentation.

I guess, but then it should be cleaned up, especially on those owner cast and line tracing parts. I mostly copy/pasted those bits here and slimmed it down just to give something for you guys to get things running.

right here is the very first incarnation of a 4 wheeled car in code.
this is basically a sketch, the code is heavily un-optimised and should not be a used as an example, however, maybe it will be a first step to something good.
please take note, im very much still feeling my way round ue4 and have barely touched its weird c++, let alone all this vehicle simulation stuff.
(saying that, already it performs infinitely better than the standard wheeled vehicle in some aspects)
theres probably lots of things i did wrong, i can already see a few so feel free to help improve it if you wish.
no wheel animation yet, unused variables ect

  1. create a c++ project
  2. create a new c++ class based on pawn called ‘TegCar_Pawn’ to keep it simple
  3. add inputs for ‘MoveForward’ and ‘MoveRight’ in your project properties
  4. copy/paste the code below to your new c++ class* (.h and .ccp files)

*change ‘CARPLUGIN_API’ in TegCar_Pawn.h to your project name or (dont overwrite that part)
*change #include “CarPlugin.h” to your project name in TegCar_Pawn.cpp (or dont overwrite that part)

  1. compile code and open editor
  2. create a blueprint based on TegCar_Pawn
  3. open the blueprint and give a mesh to the skeletal mesh component, and a camera if you want
  4. change the default pawn in your game mode to the new blueprint, build ramps ect
  5. raz, tweak, raz :slight_smile:

TegCar_Pawn.h


#pragma once

#include "GameFramework/Pawn.h"
// Needed for custom physics
#include "PhysicsPublic.h"

#include "TegCar_Pawn.generated.h"



UCLASS()
class CARPLUGIN_API ATegCar_Pawn : public APawn
{
	GENERATED_BODY()

private_subobject:
	/**  The main skeletal mesh associated with this Vehicle */
	DEPRECATED_FORGAME(4.6, "Mesh should not be accessed directly, please use GetMesh() function instead. Mesh will soon be private and your code will not compile.")
		UPROPERTY(Category = Vehicle, VisibleDefaultsOnly, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
	class USkeletalMeshComponent* Mesh;

	UPROPERTY(Category = VehicleSetup, VisibleDefaultsOnly, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
	class UArrowComponent* Arrow0;
	UPROPERTY(Category = VehicleSetup, VisibleDefaultsOnly, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
	class UArrowComponent* Arrow1;
	UPROPERTY(Category = VehicleSetup, VisibleDefaultsOnly, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
	class UArrowComponent* Arrow2;
	UPROPERTY(Category = VehicleSetup, VisibleDefaultsOnly, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
	class UArrowComponent* Arrow3;
	//arrow array
	UPROPERTY(EditAnywhere, Category = VehicleSetup)
		TArray<UArrowComponent*> ArrowArray;

private:
	FCalculateCustomPhysics OnCalculateCustomPhysics;
	void CustomPhysics(float DeltaTime, FBodyInstance* BodyInstance);
	FHitResult Trace(FVector TraceStart, FVector TraceDirection);
	FBodyInstance *MainBodyInstance;

	void ApplyWheel(float DeltaTime, FBodyInstance* BodyInstance, int32 Index);

	UPROPERTY()
		TArray<float> PreviousPosition;

	UPROPERTY()
		FVector ArrowLocation;

public:
	// Sets default values for this pawn's properties
	ATegCar_Pawn();

	

	/** Name of the MeshComponent. Use this name if you want to prevent creation of the component (with ObjectInitializer.DoNotCreateDefaultSubobject). */
	static FName VehicleMeshComponentName;

	// Called when the game starts or when spawned
	virtual void BeginPlay() override;
	
	// Called every frame
	virtual void Tick( float DeltaSeconds ) override;

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override;

	/** Handle pressing forwards */
	void MoveForward(float Val);

	/** Handle pressing right */
	void MoveRight(float Val);

	void AddDrive(float DeltaTime, FBodyInstance* BodyInstance, FVector Loc, FVector Dir, int32 Index);
	void AddLatGrip(float DeltaTime, FBodyInstance* BodyInstance, FVector Loc, FVector Dir, int32 Index);

	UPROPERTY(EditAnywhere, Category = "Suspension")
		float TraceLength = 50.0f;
	UPROPERTY(EditAnywhere, Category = "Suspension")
		float SpringValue = 800000.0f;
	UPROPERTY(EditAnywhere, Category = "Suspension")
		float DamperValue = 750.0f;
	UPROPERTY(BlueprintReadOnly, Category = "Suspension")
		TArray<bool> bOnGround;
	UPROPERTY(BlueprintReadOnly, Category = "Suspension")
		TArray<float> SpringPosition;
	UPROPERTY(BlueprintReadOnly, Category = "Suspension")
		FVector SpringLocation;
	UPROPERTY(EditAnywhere, Category = "Suspension")
		TArray<FVector> SpringTopLocation;

	//engine
	UPROPERTY(EditAnywhere, Category = "Engine")
		float EnginePower = 500000.0f;
	UPROPERTY(BlueprintReadOnly, Category = "Engine")
		float CurrentPower = 0.0f;
	UPROPERTY(EditAnywhere, Category = "Engine")
		float EngineBrake = 50.0f;

	//steering
	UPROPERTY(EditAnywhere, Category = "Steering")
		TArray<FVector> WheelDirection;
	UPROPERTY(EditAnywhere, Category = "Steering")
		float SteerAngle = 45.0f;
	UPROPERTY(BlueprintReadOnly, Category = "Steering")
		float CurrentAngle = 0.0f;
	UPROPERTY(EditAnywhere, Category = "Steering")
		float SteerSpeed = 3.0f;

	//grip
	UPROPERTY(EditAnywhere, Category = "Wheels")
		float Grip = 400.0f;

	/** Returns Mesh subobject **/
	class USkeletalMeshComponent* GetMesh() const;
	
};


TegCar_Pawn.cpp


#include "CarPlugin.h"
#include "TegCar_Pawn.h"
#include "Kismet/KismetMathLibrary.h"

FName ATegCar_Pawn::VehicleMeshComponentName(TEXT("VehicleMesh"));

// Sets default values
ATegCar_Pawn::ATegCar_Pawn()
{

	// Set this pawn to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	Mesh = CreateDefaultSubobject<USkeletalMeshComponent>(VehicleMeshComponentName);
	GetMesh()->SetCollisionProfileName(UCollisionProfile::Vehicle_ProfileName);
	GetMesh()->BodyInstance.bSimulatePhysics = true;
	GetMesh()->BodyInstance.bNotifyRigidBodyCollision = true;
	GetMesh()->BodyInstance.bUseCCD = true;
	GetMesh()->bBlendPhysics = true;
	GetMesh()->bGenerateOverlapEvents = true;
	GetMesh()->bCanEverAffectNavigation = false;
	RootComponent = GetMesh();

//add to array for easier handling, array of wheel structs would be better
	
	//arrows used as trace start locations
	SpringTopLocation.Add(FVector(120.0f, 90.0f, 0.0f));
	SpringTopLocation.Add(FVector(120.0f, -90.0f, 0.0f));
	SpringTopLocation.Add(FVector(-120.0f, 90.0f, 0.0f));
	SpringTopLocation.Add(FVector(-120.0f, -90.0f, 0.0f));

	Arrow0 = CreateDefaultSubobject<UArrowComponent>(TEXT("arrow0"));
	Arrow0->AttachParent = RootComponent;
	Arrow0->SetRelativeLocation(SpringTopLocation[0]);

	Arrow1 = CreateDefaultSubobject<UArrowComponent>(TEXT("arrow1"));
	Arrow1->AttachParent = RootComponent;
	Arrow1->SetRelativeLocation(SpringTopLocation[1]);

	Arrow2 = CreateDefaultSubobject<UArrowComponent>(TEXT("arrow2"));
	Arrow2->AttachParent = RootComponent;
	Arrow2->SetRelativeLocation(SpringTopLocation[2]);

	Arrow3 = CreateDefaultSubobject<UArrowComponent>(TEXT("arrow3"));
	Arrow3->AttachParent = RootComponent;
	Arrow3->SetRelativeLocation(SpringTopLocation[3]);
	
	ArrowArray.Emplace(Arrow0);
	ArrowArray.Emplace(Arrow1);
	ArrowArray.Emplace(Arrow2);
	ArrowArray.Emplace(Arrow3);

	WheelDirection.Init(FVector(0.0f, 0.0f, 0.0f), 4);//unused, intended for wheel animation later

	PreviousPosition.Init(0.0f,4);

	bOnGround.Init(false,4);

	SpringPosition.Init(0.0f, 4);


	// Bind function delegate
	OnCalculateCustomPhysics.BindUObject(this, &ATegCar_Pawn::CustomPhysics);

}

// Called when the game starts or when spawned
void ATegCar_Pawn::BeginPlay()
{
	Super::BeginPlay();

	// Get the static mesh from attached actors root

	if (GetMesh() != NULL){
		MainBodyInstance = GetMesh()->GetBodyInstance();

	}
	
}

// Called every frame
void ATegCar_Pawn::Tick( float DeltaTime )
{
	Super::Tick( DeltaTime );

	// Add custom physics on MainBodyMesh
	if (MainBodyInstance != NULL){
		MainBodyInstance->AddCustomPhysics(OnCalculateCustomPhysics);
		
	}
}

// Called to bind functionality to input
void ATegCar_Pawn::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
	Super::SetupPlayerInputComponent(InputComponent);

	// set up gameplay key bindings
	check(InputComponent);

	InputComponent->BindAxis("MoveForward", this, &ATegCar_Pawn::MoveForward);
	InputComponent->BindAxis("MoveRight", this, &ATegCar_Pawn::MoveRight);

}

void ATegCar_Pawn::MoveForward(float Val)
{
	CurrentPower = EnginePower * Val;
}

void ATegCar_Pawn::MoveRight(float Val)
{
	CurrentAngle = FMath::Lerp(CurrentAngle, SteerAngle * Val, SteerSpeed * GetWorld()->DeltaTimeSeconds);
//rotate first 2 arrow components, front wheels
	ArrowArray[0]->SetRelativeRotation(FRotator(0.0f, CurrentAngle, 0.0f));
	ArrowArray[1]->SetRelativeRotation(FRotator(0.0f, CurrentAngle, 0.0f));
}

// Called every substep for selected body instance
void ATegCar_Pawn::CustomPhysics(float DeltaTime, FBodyInstance* BodyInstance)
{
	if (ArrowArray.Num()>0){

	
	//trace and apply force for each arrow component	
	for (int32 b = 0; b < ArrowArray.Num(); b++)
		{
			//~~~~~~~~~~~~~~~~~~~~~~
			ApplyWheel(DeltaTime, BodyInstance, b);
			
		}
	}
}

void ATegCar_Pawn::ApplyWheel(float DeltaTime, FBodyInstance* BodyInstance, int32 Index){

	// BodyLocation is arrow location, the top of the spring
	FVector BodyLocation = ArrowArray[Index]->GetComponentLocation();

	FHitResult Hit = Trace(BodyLocation, -GetActorUpVector());
	if (Hit.bBlockingHit) {

		SpringLocation = Hit.ImpactPoint;

		float SpringPosition = (Hit.Location - BodyLocation).Size();

		// If previously on air, set previous position to current position
		if (!bOnGround[Index]){
			PreviousPosition[Index] = SpringPosition;
		}
		float DamperVelocity = (SpringPosition - PreviousPosition[Index]) / DeltaTime;
		PreviousPosition[Index] = SpringPosition;
		bOnGround[Index] = true;

		// Calculate spring force
		float SpringForce = (1 - (SpringPosition / TraceLength)) * SpringValue;

		// Apply damper force
		SpringForce -= DamperValue * DamperVelocity;

		FVector TotalForce = SpringForce * Hit.ImpactNormal;

		// Just as example, enabling following line would just cancel the gravity:
		//		TotalForce = BodyInstance->GetBodyMass() * 980.0f * FVector::UpVector;
		//spring
		BodyInstance->AddImpulseAtPosition(TotalForce * DeltaTime, BodyLocation);

		//drive, lateral grip and steering forces, should do it all in 1 go
		AddDrive(DeltaTime, BodyInstance, BodyLocation, GetActorForwardVector(), Index);
		AddLatGrip(DeltaTime, BodyInstance, BodyLocation, GetActorForwardVector(), Index);
	}
	else {
		bOnGround[Index] = false;
		SpringPosition[Index] = TraceLength;
	}
}

FHitResult ATegCar_Pawn::Trace(FVector TraceStart, FVector TraceDirection){
	FHitResult Hit(ForceInit);
	FCollisionQueryParams TraceParams(true);
	TraceParams.bTraceAsyncScene = true;
	TraceParams.bReturnPhysicalMaterial = false;
	FVector TraceEnd = TraceStart + (TraceDirection * TraceLength);
	GetWorld()->LineTraceSingleByChannel(Hit, TraceStart, TraceEnd, ECC_WorldDynamic, TraceParams);
	return Hit;
}

void ATegCar_Pawn::AddDrive(float DeltaTime, FBodyInstance* BodyInstance, FVector Loc, FVector Dir, int32 Index){

	FVector TempVel = BodyInstance->GetUnrealWorldVelocityAtPoint(Loc);
	FVector TempVec = ArrowArray[Index]->GetForwardVector().GetSafeNormal();

         //engine 'drag'
	float ForwardSpeed = FVector::DotProduct(TempVec, TempVel) * EngineBrake;
	Dir = -TempVec * ForwardSpeed;
        //engine power
	Dir += ArrowArray[Index]->GetForwardVector().GetSafeNormal() * CurrentPower;

	BodyInstance->AddImpulseAtPosition(Dir * DeltaTime, Loc);
}

void ATegCar_Pawn::AddLatGrip(float DeltaTime, FBodyInstance* BodyInstance, FVector Loc, FVector Dir, int32 Index){

	FVector TempVel = BodyInstance->GetUnrealWorldVelocityAtPoint(Loc);
	FVector TempVec = ArrowArray[Index]->GetRightVector().GetSafeNormal();
	
	float SideSpeed = FVector::DotProduct(TempVec, TempVel) * Grip;
        //- sideways speed of the component
	Dir = -TempVec * SideSpeed;

	BodyInstance->AddImpulseAtPosition(Dir * DeltaTime, Loc);
}

/** Returns Mesh subobject **/
USkeletalMeshComponent* ATegCar_Pawn::GetMesh() const { return Mesh; }



Nice start :slight_smile: There are few things I’d want to point out right away.

You are using GetComponentLocation to read the arrow location on each substep. That doesn’t work correctly as you need to read locations from FBodyInstances there. If your framerate drops low, you’ll see what I mean. In fact you can just alt tab out of the editor while game is running and unreal will drop the frames automatically (unless you’ve disabled that). If you do that, you’ll see that your suspension starts to rock and it’ll overshoot the forces eventually. This happens because GetComponentLocation doesn’t get the location from physics scene (like BodyInstance does) and the location you get is only updated once per tick.

As your arrows do not simulate physics, you have to read the base location from the chassis BodyInstance and offset the location values by suspensions relative location.



FTransform BodyTransform = BodyInstance->GetUnrealWorldTransform();
FVector SuspensionLocation = BodyTransform.TransformPosition(SpringTopLocation[Index]);

I also saw that you are adding impulses to the very same location on your code. If you do that later on as well, you could just put some FVector’s as return values for your AddDrive and AddLatGrip. Then you’d have something like TotalForce += AddDrive(… and TotalForce += AddLatGrip(… before that AddImpulse and it would work the same as giving 3 separate Impulses. Although this isn’t really important at this stage, it was just something I noticed.

thanks for the tips
i did wonder if it was getting the component location properly as it behaved strange a couple of times. adding an offset to the body location is probably the way to go.
also, now im over the first hurdle of making ‘anything’ work im rethinking the design, the proper equations ect can wait.
you are right about adding all the forces together and applying them in 1 go, i think it even says that in the code comments somewhere. that code is just an attempt at wrapping my head around tackling something like this.

for the next version, to make it more modular/reusable, im thinking of making some kind of wheel manager component and a wheel scene component. the wheel scene component would be basically a way of adding n amount of wheels in x location(s) and contain all the variables, location (offset) and possibly functions needed for a wheel with a spring.
then the wheel manager would iterate over all the wheel components in sub-step and actually do all the calculations and add forces.

just an update of progress so far.
extremely simple stuff atm and its very fun to drive :slight_smile:
ive moved the code to a plugin and am now starting the process of adding in some decent equations for grip, transmission, engine ect.
if anyone really needs this now i can up it somewhere, or you can wait a bit.