So, I’m writing an fps framework, and for the walk cycle, I wanted to use sine waves to simulate the bobbing of the viewmodel. Now, it works perfectly fine, but the issue is when velocity of the character changes ( When the character turns / jumps ) the change causes the animation to go all glitchy and weird. Any feedback is appreciated on how to stop this from happening!
Code:
self.viewmodel.PrimaryPart.CFrame *= CFrame.new(math.sin(time() * (velocity.Magnitude/offsetX))/bobbleOffset, math.sin(time() * (velocity.Magnitude/offsetY))/bobbleOffset, 0)
-- Velocity.Magnitude is the velocity of the character's HumanoidRootPart, bobbleOffset is set to 10 by default, and offsetX / offset Y are constant.
Issue:
Edit: I disabled character jumping, so the only issue now is climbing onto different height levels and turning, based on tests.
Seems unnecessary. math.sin is already acting as a smoothing function. Like @dthecoolest said, you’re using the total velocity magnitude, which means vertical velocity (jumping) will impact this where you don’t want it to.
Instead of using Velocity.Magnitude, you could do:
local CharacterXZVelocity = (Velocity *Vector3.new(1, 0, 1)).Magnitude
which will remove the Y-component and only consider movement left/right and forward/backwards
Strange, Velocity.Magnitude shouldn’t vary with rotation, and all of your other variables are constants.
By ‘glitch out’ do you mean similar behaviour to what was visible in the video where the animation just played out at a very high speed? Or does it look different?
Very strange, normal character movement shouldn’t exceed walkspeed with multiple inputs:
Try printing Velocity.Magnitude and see what’s happening to it when you press both. If it’s exceeding the normal walkspeed then you could try using math.min() to ensure it never exceeds to maximum walkspeed value.
Weird, the velocity does seem to change with direction. Before that, though, is there a way I can make it so the animation doesn’t rigidly go back to its original offset when a character stops / stops moving?
You may want a tween or lerp for that, or when a character stops you continue running the function until math.sin(…) is approximately equal to 0 (i.e. your rest state)
Edit: Actually it might be -1 as you’re using *=. A lerp or tween will probably be easier and more reliable.
can’t you just detect Humanoid.Magnitude and if it’s not equal to zero, play animation? It’s not realistic that if you run faster your arms move faster too xd, it’s sometimes true but still, you can do:
if Humanoid.MoveDirection.Magnitude > 0 then -- something like this, idk if this property name is correct
ArmsSpeed = 10
else
ArmsSpeed = 0
end
Also sorry for another reply, but it’s not weird, velocity is vector, every vector is composed of value and direction, soo it’s logical that if we change direction, vector changes too
That could work, but it still doesn’t fix the issue of stopping and starting to walk. Rn I’m updating a value of the object every RenderStepped, and I’m comparing it, but it still doesn’t seem to work sometimes. I think it’s because the tween doesn’t finish by the time the next frame renders
local isTransitioning;
RunService.RenderStepped:Connect(function()
if (Velocity.Magnitude > 0) and (not isTransitioning) then
self.viewmodel.PrimaryPart.CFrame *= CFrame.new(...)
elseif (not isTransitioning) then
isTransitioning = true;
-- lerp loop, or tween and tween.Completed:Wait() the CFrame back to its default position
isTransitioning = false;
end
end)
Something along these lines might work using a debounce.
It will stop the animation until it’s fully reverted back to its default position. This may not be perfect, but it’s probably the easiest way if you insist on using *= on the CFrame for the animation. I’d personally probably just manually set the CFrame (self.viewmodel.PrimaryPart.CFrame = CFrame.new(...)) using a separate timing function based on (time()-startTime) as then you’ll have more control over its position at any one time.
Alternatively instead of using the Velocity.Magnitude value, just do a: isMoving = (Velocity.Magnitude > 1 and 1) or 0, then multiplying your value like that. Then when the player stops moving you can keep that isMoving value at 1 until: math.sin(time()...) is close to the point where the resultant viewmodel CFrame is in its default state.
Edit: Something along these lines might work better without changing a significant amount of your code:
local isTransitioning;
local startTime = time();
RunService.RenderStepped:Connect(function()
if (Velocity.Magnitude > 0) and (not isTransitioning) then
local animationTime = time()-startTime;
self.viewmodel.PrimaryPart.CFrame *= CFrame.new(...) -- Use 'animationTime' instead of 'time()' here
elseif (not isTransitioning) then
isTransitioning = true;
-- lerp loop, or tween and tween.Completed:Wait() the CFrame back to its default position, check somewhere to make sure it's not already in its default position (i.e. if the character is standing still for an extended period of time)
isTransitioning = false;
startTime = time();
end
end)