Are you perhaps using Live Coding / Hot Reload for this? You may need to fully restart UE for new classes to show.
i created a new test character class to see if i could retrace my steps, when i made the new class the editor immediately found the other ones. Is there any way to prompt this refresh without adding a new file? I havent tested whether the files work but i guess they should? Ah yes you just posted, perhaps the live coding is getting in the way ? i remember seeing people saying it causes problems
Itās usually recommended to restart UE on any header reflection-related (new/changed U specifiers, etc.) changes like these, Iāve both experienced and heard about many issues with Live Coding in these cases.
If your project isnāt humongous, it shouldnāt take that long anyway, better safe than sorry.
Let me know if the reparenting goes well. Maybe duplicate the drone asset just in case and check on that first, reparenting can definitely be fiddly.
I think, magically, all my problems have gone, at this rate i will have finished my game by morning! haha. But yes for some reason the reparent seemed to work without a hitch too, perhaps because i added the blueprintable keyword or whatever it is.
I should be able to revert to source control if the reparent is catastrophic.
I was restarting the editor every time, it didnt seem to be doing the trick, it only changed when i added a new C++ class, i think i just had to prompt a proper recompile or something
Thanks again you have been great, ill try to remember to revisit these threads when i finally have a game worth showing off.
Good luck, if you run into any problems in the future feel free to stop by, someone is usually around to help!
Youāve been very precise and open in communication so I have high hopes for your project!
Hello again, i dont need any help i just wanted to add the correction to this code that i found, as i think there is a slight syntax error, the code that worked for me was
AMyCharacter::AMyCharacter(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer.SetDefaultSubobjectClass<UMyMovementComponent>(ACharacter::CharacterMovementComponentName))
{
...constructor body, if any...
}
I discovered this as there is this line in the character.h file
/** Name of the CharacterMovement component. Use this name if you want to use a different class (with ObjectInitializer.SetDefaultSubobjectClass). */
static ENGINE_API FName CharacterMovementComponentName;
Maybe this is useful if someone is debugging a similar issue, although it looks like you cant edit old posts? Hopefully this reply is visible enough
Okay, so this time I could use a hand processing the override of calc velocity, i think i know ish what needs to be done but there are some issues as i try to do it.
I am referencing the source here - UnrealEngine/Engine/Source/Runtime/Engine/Private/Components/CharacterMovementComponent.cpp at release Ā· EpicGames/UnrealEngine
this is the monster method -
void UCharacterMovementComponent::CalcVelocity(float DeltaTime, float Friction, bool bFluid, float BrakingDeceleration)
{
// Do not update velocity when using root motion or when SimulatedProxy and not simulating root motion - SimulatedProxy are repped their Velocity
if (!HasValidData() || HasAnimRootMotion() || DeltaTime < MIN_TICK_TIME || (CharacterOwner && CharacterOwner->GetLocalRole() == ROLE_SimulatedProxy && !bWasSimulatingRootMotion))
{
return;
}
Friction = FMath::Max(0.f, Friction);
const float MaxAccel = GetMaxAcceleration();
float MaxSpeed = GetMaxSpeed();
// Check if path following requested movement
bool bZeroRequestedAcceleration = true;
FVector RequestedAcceleration = FVector::ZeroVector;
float RequestedSpeed = 0.0f;
if (ApplyRequestedMove(DeltaTime, MaxAccel, MaxSpeed, Friction, BrakingDeceleration, RequestedAcceleration, RequestedSpeed))
{
bZeroRequestedAcceleration = false;
}
if (bForceMaxAccel)
{
// Force acceleration at full speed.
// In consideration order for direction: Acceleration, then Velocity, then Pawn's rotation.
if (Acceleration.SizeSquared() > UE_SMALL_NUMBER)
{
Acceleration = Acceleration.GetSafeNormal() * MaxAccel;
}
else
{
Acceleration = MaxAccel * (Velocity.SizeSquared() < UE_SMALL_NUMBER ? UpdatedComponent->GetForwardVector() : Velocity.GetSafeNormal());
}
AnalogInputModifier = 1.f;
}
// Path following above didn't care about the analog modifier, but we do for everything else below, so get the fully modified value.
// Use max of requested speed and max speed if we modified the speed in ApplyRequestedMove above.
const float MaxInputSpeed = FMath::Max(MaxSpeed * AnalogInputModifier, GetMinAnalogSpeed());
MaxSpeed = FMath::Max(RequestedSpeed, MaxInputSpeed);
// Apply braking or deceleration
const bool bZeroAcceleration = Acceleration.IsZero();
const bool bVelocityOverMax = IsExceedingMaxSpeed(MaxSpeed);
// Only apply braking if there is no acceleration, or we are over our max speed and need to slow down to it.
if ((bZeroAcceleration && bZeroRequestedAcceleration) || bVelocityOverMax)
{
const FVector OldVelocity = Velocity;
const float ActualBrakingFriction = (bUseSeparateBrakingFriction ? BrakingFriction : Friction);
ApplyVelocityBraking(DeltaTime, ActualBrakingFriction, BrakingDeceleration);
// Don't allow braking to lower us below max speed if we started above it.
if (bVelocityOverMax && Velocity.SizeSquared() < FMath::Square(MaxSpeed) && FVector::DotProduct(Acceleration, OldVelocity) > 0.0f)
{
Velocity = OldVelocity.GetSafeNormal() * MaxSpeed;
}
}
else if (!bZeroAcceleration)
{
// Friction affects our ability to change direction. This is only done for input acceleration, not path following.
const FVector AccelDir = Acceleration.GetSafeNormal();
const float VelSize = Velocity.Size();
Velocity = Velocity - (Velocity - AccelDir * VelSize) * FMath::Min(DeltaTime * Friction, 1.f);
}
// Apply fluid friction
if (bFluid)
{
Velocity = Velocity * (1.f - FMath::Min(Friction * DeltaTime, 1.f));
}
// Apply input acceleration
if (!bZeroAcceleration)
{
const float NewMaxInputSpeed = IsExceedingMaxSpeed(MaxInputSpeed) ? Velocity.Size() : MaxInputSpeed;
Velocity += Acceleration * DeltaTime;
Velocity = Velocity.GetClampedToMaxSize(NewMaxInputSpeed);
}
// Apply additional requested acceleration
if (!bZeroRequestedAcceleration)
{
const float NewMaxRequestedSpeed = IsExceedingMaxSpeed(RequestedSpeed) ? Velocity.Size() : RequestedSpeed;
Velocity += RequestedAcceleration * DeltaTime;
Velocity = Velocity.GetClampedToMaxSize(NewMaxRequestedSpeed);
}
if (bUseRVOAvoidance)
{
CalcAvoidanceVelocity(DeltaTime);
}
}
I think what i need to do is change the usage of
const bool bVelocityOverMax = IsExceedingMaxSpeed(MaxSpeed);
to
const bool bVelocityOverMax = IsExceedingMaxVelocity(velocity,maxVelocity);
Which is taking two FVectors, and then it should work⦠maybeā¦
A big glaring question is that I stumbled across the Async version of the movement component and CalcVelocity Method, which apparently is handling some networking, so if I change this method to do this, will that break the networking of this character? and other machines will no longer sync the movement correctly?
That line seems like a good place to start, but Iād be surprised if thatās all of it. Either way, if it turns out itās not all of it, you should be able to step through these functions with the debugger and see what happens to the velocity and at which point it stops matching what youād expect. Would probably be useful to look into data breakpoints, set a data breakpoint on the velocity components, and that way jump to the next line that updates it.
As for the Async version of the component, I canāt help much, Iāve never explicitly used it or looked at that code. It doesnāt seem networking-related per se, seems like an option for multithreaded character movement (for games with many characters I presume). I wouldnāt worry about it unless/until it proves to be relevant.
Haha, yes it was definitely not all of it, I need to at least add in new braking methods for horizontal and vertical braking only, so still deciphering those, but my brain has switched off totally now so its a task for tomorrow, but i donāt feel like i am stuck here, just a matter of pushing through. The task is just small enough that i think i can make a decent attempt at finishing this before changing task, and i think i am learning a lot.
That data breakpoints is a good tip, and its shown me that Breakpoints in VS can be conditional too, which sounds pretty useful.
Good to know with the Async, you have certainly more experience than I do and itās reassuring that it is not setting off any alarm bells.
So progress has been made, not quite there yet, I am going to drop my code here in case its useful to future googlers and it might help me figure out the issues with the forum as my rubber duck. The issue at the moment is that there is a stutter, (a slower one than when I started writing this) as it reaches a given max speed it will reach it and sharply slow down as it reaches it ( I checked the braking method, doesnt seem to be that) (it reaches slightly higher, but this might be because of the 1% leeway given in the code)
So in the CalcVelocity method, i found two major places where the MaxSpeed was being used - If you are too fast,slow down, and then if you are trying to accelerate over the max speed, dont.
Too fast, slow down happens in here -
if ((bZeroAcceleration && bZeroRequestedAcceleration) || bVelocityOverMax)
{
const FVector OldVelocity = Velocity;
const float ActualBrakingFriction = (bUseSeparateBrakingFriction ? BrakingFriction : Friction);
ApplyVelocityBraking(DeltaTime, ActualBrakingFriction, BrakingDeceleration);
// Dont allow braking to lower us below max speed if we started above it.
if (bVelocityOverMax && Velocity.SizeSquared() < FMath::Square(MaxSpeed) && FVector::DotProduct(Acceleration, OldVelocity) > 0.0f)
{
Velocity = OldVelocity.GetSafeNormal() * MaxSpeed;
}
}
The last part corrects for if you are inputting in the same direction as the brake (when you are too fast), only lower to the max speed rather than let the brake lower you past it. My alternate implementation of this might not be working and this could be causing the stutter.
The implementation UE implementation of āDont deliberately go too fastā
// Apply input acceleration
if (!bZeroAcceleration)
{
const float NewMaxInputSpeed = IsExceedingMaxSpeed(MaxInputSpeed) ? Velocity.Size() : MaxInputSpeed;
Velocity += Acceleration * DeltaTime;
Velocity = Velocity.GetClampedToMaxSize(NewMaxInputSpeed);
}
// Apply additional requested acceleration
if (!bZeroRequestedAcceleration)
{
const float NewMaxRequestedSpeed = IsExceedingMaxSpeed(RequestedSpeed) ? Velocity.Size() : RequestedSpeed;
Velocity += RequestedAcceleration * DeltaTime;
Velocity = Velocity.GetClampedToMaxSize(NewMaxRequestedSpeed);
}
The naming of variables is weird here, I am not sure i understand this method and was an error in my own code and could still be. It sets NewMaxRequestedSpeed to either the max speed, or the current speed if you happen to already be overfast, so that you dont slow to max speed if something else has made you go too fast.
Now for my method, itās still a bit scrappy, needs to be tidied but it works almost
void UDroneCharacterMovementComponent::CalcVelocity(float DeltaTime, float Friction, bool bFluid, float BrakingDeceleration)
{
//Super::CalcVelocity(DeltaTime, Friction, bFluid, BrakingDeceleration);
//if (true) { return; }; // block off functionality that is a work in progress
// Do not update velocity when using root motion or when SimulatedProxy and not simulating root motion - SimulatedProxy are repped their Velocity
if (!HasValidData() || HasAnimRootMotion() || DeltaTime < MIN_TICK_TIME || (CharacterOwner && CharacterOwner->GetLocalRole() == ROLE_SimulatedProxy && !bWasSimulatingRootMotion))
{
return;
}
Friction = FMath::Max(0.f, Friction);
const float MaxAccel = GetMaxAcceleration();
float MaxSpeed = 1200.f;
float MaxHorizontalSpeed = 800.f;
float MaxDownSpeed = 800.f;
float MaxUpSpeed = 800;
// Check if path following requested movement
bool bZeroRequestedAcceleration = true;
FVector RequestedAcceleration = FVector::ZeroVector;
float RequestedSpeed = 0.0f;
if (ApplyRequestedMove(DeltaTime, MaxAccel, MaxSpeed, Friction, BrakingDeceleration, RequestedAcceleration, RequestedSpeed))
{
bZeroRequestedAcceleration = false;
}
if (bForceMaxAccel)
{
// Force acceleration at full speed.
// In consideration order for direction: Acceleration, then Velocity, then Pawn's rotation.
if (Acceleration.SizeSquared() > UE_SMALL_NUMBER)
{
Acceleration = Acceleration.GetSafeNormal() * MaxAccel;
}
else
{
Acceleration = MaxAccel * (Velocity.SizeSquared() < UE_SMALL_NUMBER ? UpdatedComponent->GetForwardVector() : Velocity.GetSafeNormal());
}
AnalogInputModifier = 1.f;
}
// Path following above didn't care about the analog modifier, but we do for everything else below, so get the fully modified value.
// Use max of requested speed and max speed if we modified the speed in ApplyRequestedMove above.
const float MaxInputSpeed = FMath::Max(MaxSpeed * AnalogInputModifier, GetMinAnalogSpeed());
const float MaxInputHorizontalSpeed = FMath::Max(MaxHorizontalSpeed * AnalogInputModifier, GetMinAnalogSpeed());
const float MaxInputUpSpeed = FMath::Max(MaxUpSpeed * AnalogInputModifier, GetMinAnalogSpeed());
const float MaxInputDownSpeed = FMath::Max(MaxUpSpeed * AnalogInputModifier, GetMinAnalogSpeed());
MaxSpeed = FMath::Max(RequestedSpeed, MaxInputSpeed);
// Apply braking or deceleration
const bool bZeroAcceleration = Acceleration.IsZero();
//const bool bVelocityOverMax = IsExceedingMaxSpeed(MaxSpeed);
// Define vector of bools, so we can use it to determine if we should apply braking or not.
const bool bHVelocityOverMax = IsExceedingMaxHorizontalSpeed(MaxHorizontalSpeed);
const bool bUpVelocityOverMax = IsExceedingMaxUpSpeed(MaxUpSpeed);
const bool bDownVelocityOverMax = IsExceedingMaxDownSpeed(MaxDownSpeed);
// Only apply braking if there is no acceleration, or we are over our max speed and need to slow down to it.
if ((bZeroAcceleration && bZeroRequestedAcceleration))
{
const float ActualBrakingFriction = (bUseSeparateBrakingFriction ? BrakingFriction : Friction);
ApplyVelocityBraking(DeltaTime, ActualBrakingFriction, BrakingDeceleration);
// Don't allow braking to lower us below max speed if we started above it.
}
else if (!bZeroAcceleration)
{
// Friction affects our ability to change direction. This is only done for input acceleration, not path following.
const FVector AccelDir = Acceleration.GetSafeNormal();
const float VelSize = Velocity.Size();
Velocity = Velocity - (Velocity - AccelDir * VelSize) * FMath::Min(DeltaTime * Friction, 1.f);
}
if (bHVelocityOverMax || bUpVelocityOverMax || bDownVelocityOverMax) // If we are over either max or horizontal speed, apply appropriate braking.
{
// If we are over max speed, we need to apply braking to get back under it.
const FVector OldVelocity = Velocity;
const FVector OldHorizontalVelocity = FVector(Velocity.X, Velocity.Y, 0.f);
const float ActualBrakingFriction = (bUseSeparateBrakingFriction ? BrakingFriction : Friction);
if (bHVelocityOverMax)
{
ApplyHorizontalVelocityBraking(DeltaTime, ActualBrakingFriction, BrakingDeceleration);
}
if (bUpVelocityOverMax || bDownVelocityOverMax)
{
ApplyVerticalVelocityBraking(DeltaTime, ActualBrakingFriction, BrakingDeceleration);
}
FVector NewHorizontalVelocity = FVector(Velocity.X, Velocity.Y, 0.f);
FVector NewVerticalVelocity = FVector(0.f, 0.f, Velocity.Z);
// Don't allow braking to lower us below max speed if we started above it.
if ((bHVelocityOverMax) && NewHorizontalVelocity.SizeSquared() < FMath::Square(MaxHorizontalSpeed) && FVector::DotProduct(Acceleration, OldHorizontalVelocity) > 0.0f)
{
//Velocity = OldVelocity.GetSafeNormal() * MaxSpeed;
Velocity = OldHorizontalVelocity.GetSafeNormal() * MaxHorizontalSpeed + NewVerticalVelocity;
}
if ((bUpVelocityOverMax || bDownVelocityOverMax) && (Velocity.Z < FMath::Square(MaxUpSpeed) && Velocity.Z > -MaxDownSpeed) && FVector::DotProduct(Acceleration, NewVerticalVelocity) > 0.0f)
{
Velocity.Z = (Velocity.Z > 0) ? MaxUpSpeed : -MaxDownSpeed;
}
}
// Apply fluid friction
if (bFluid)
{
Velocity = Velocity * (1.f - FMath::Min(Friction * DeltaTime, 1.f));
}
// Apply input acceleration
if (!bZeroAcceleration)
{
FVector HorizontalV = FVector(Velocity.X, Velocity.Y, 0);
const float newMaxHorizontalSpeed = IsExceedingMaxHorizontalSpeed(MaxInputHorizontalSpeed) ? HorizontalV.Size() : MaxInputHorizontalSpeed;
const float newMaxDownSpeed = IsExceedingMaxDownSpeed(MaxInputDownSpeed) ? Velocity.Z : MaxInputDownSpeed;
const float newMaxUpSpeed = IsExceedingMaxUpSpeed(MaxInputUpSpeed) ? Velocity.Z : MaxInputDownSpeed;
Velocity += Acceleration * DeltaTime;
HorizontalV = FVector(Velocity.X, Velocity.Y, 0);
// clamp vector to with horizontal and vertical NEW max speeds - new because if you are already over the max speed, we dont want to slow too fast
HorizontalV = HorizontalV.GetClampedToMaxSize(newMaxHorizontalSpeed);
Velocity.Z = FMath::Clamp(Velocity.Z, -newMaxDownSpeed, newMaxUpSpeed);
Velocity = FVector(HorizontalV.X, HorizontalV.Y, Velocity.Z);
//const float NewMaxInputSpeed = IsExceedingMaxSpeed(MaxInputSpeed) ? Velocity.Size() : MaxInputSpeed;
//Velocity = Velocity.GetClampedToMaxSize(NewMaxInputSpeed);
}
// Apply additional requested acceleration
if (!bZeroRequestedAcceleration)
{
const float NewMaxRequestedSpeed = IsExceedingMaxSpeed(RequestedSpeed) ? Velocity.Size() : RequestedSpeed;
Velocity += RequestedAcceleration * DeltaTime;
Velocity = Velocity.GetClampedToMaxSize(NewMaxRequestedSpeed);
}
if (bUseRVOAvoidance)
{
CalcAvoidanceVelocity(DeltaTime);
}
}
Its working ish, I managed to stop the stutter a bit by fixing the prevent input from overspeeding part, but theres still a stutter that just has a longer period. I am still investigating, writing this helped me fix at least one bug though so that was useful. ( i have left out my implementation of the braking, i think it works okay, but i will investigate now if they are causing the stutter)
(obviously hard to guess without a debugger, thereās a lot of code here, but Iāll comment just in case)
This block in your last snippet still uses UEās ApplyVelocityBraking
, which seems very likely to be causing the stutter since itās using the single built-in speed limit instead of your horizontal/vertical speed limits.
// Only apply braking if there is no acceleration, or we are over our max speed and need to slow down to it.
if ((bZeroAcceleration && bZeroRequestedAcceleration))
{
const float ActualBrakingFriction = (bUseSeparateBrakingFriction ? BrakingFriction : Friction);
ApplyVelocityBraking(DeltaTime, ActualBrakingFriction, BrakingDeceleration);
}
Ah good spot, i chose to keep that as that should only be applying if there is no acceleration, which means that it should slow down in both directions I will keep an eye out for it, i think the stutter is in fact because my braking methods are being called at the wrong time, I think i forgot to put in the 1% error margin in the isExceedingMaxSpeeds methods, that has helped, i now have no issues with going horizontal and going up, however my recent change has slowed going down to a crawlā¦
Success!
You can have a little preview there. Speed is being printed out, hopefully you can see it, if you can its pretty obvious that there are different max speeds for moving up down and horizontally. I didnt bother to decouple horizontal directions because for now the drone is symmetric horizontally (and maybe always).
The issue is you might be able to notice that while max speed has been decoupled, max accelleration has not, so only half way there maybe, but good to have actually solved something reasonably complex in C++. Definitely learned some things