Making Knockback as Seamless as Possible

Hey everyone!

I’m messing with a combat system right now, and I’m just not satisfied with the way BodyVelocity is working with my vision. I have an animation that’s about 2 seconds long, and I want knockback afterwards. Here’s just the base animation:

I’m sure seeing the video it’s clear where the knockback is meant to be. The problem is that because animations don’t actually move the HumanoidRootPart, right as the animation ends and the knockback is applied, the character jarringly teleports back into the position they were in before the attack, as shown here:

Here’s the script that runs the animation and triggers the knockback.

hitbox.Touched:Connect(function(part)
			if part.Parent:FindFirstChild("Humanoid") then --if detected part is a humanoid
				hitbox:Destroy() --Prevent multiple touched events
				
				local victim = Players:GetPlayerFromCharacter(part.Parent) --easy access to hit player (If it was a player)
				
				--Initialize animations
				local atk = player.Character.Humanoid.Animator:LoadAnimation(RS.Animations.Combat.HumanUnpowered.ShoCharAtk)
				local vic = part.Parent.Humanoid.Animator:LoadAnimation(RS.Animations.Combat.HumanUnpowered.ShoCharVic)
				
				--Attaches another sound the the victim and adds it to garbage collection queue
				wind.Parent = part.Parent.Head
				game.Debris:AddItem(wind, 5)
				
				--Take damage, play first sound
				part.Parent.Humanoid:TakeDamage(10)
				impact:Play()
				
				--Prevents characters from moving during animation
				player.Character.Humanoid.WalkSpeed = 0
				player.Character.Humanoid.JumpHeight = 0
				player.Character.Humanoid.AutoRotate = false
				
				part.Parent.Humanoid.WalkSpeed = 0
				part.Parent.Humanoid.JumpHeight = 0
				part.Parent.Humanoid.AutoRotate = false
				

				for _,v in pairs(player.Character:GetChildren()) do
					if v:IsA("BasePart") then
						v.CanTouch = false
					end
				end
				
				--adjust both characters to be facing each other and close together
				part.Parent.HumanoidRootPart.CFrame = CFrame.new(part.Parent.HumanoidRootPart.Position, player.Character.HumanoidRootPart.Position)
				player.Character.HumanoidRootPart.CFrame = part.Parent.HumanoidRootPart.CFrame * CFrame.new(0,0,-3) * CFrame.Angles(0,math.pi,0)
				
				--Play animations
				atk:Play()
				vic:Play()
				
				
				vic.Stopped:Connect(function()
					if victim then 
						hitFunc(victim, true, 1.5, 0)
						isDown[victim] = false
					end

					part.Parent.Humanoid:TakeDamage(20)
					local velocity = Instance.new("BodyVelocity", part.Parent.HumanoidRootPart)
					velocity.MaxForce = Vector3.new(math.huge,math.huge,math.huge)
					velocity.P = 1000
					local angle = ((part.Parent.HumanoidRootPart.Position - player.Character.HumanoidRootPart.Position) * Vector3.new(10,0,10)).Unit * 70 + Vector3.new(0,5,0)
					velocity.Velocity = angle
					game:GetService("Debris"):AddItem(velocity,.3)
					part.Parent.Humanoid.WalkSpeed = 16
					part.Parent.Humanoid.JumpHeight = 7.2
					part.Parent.Humanoid.AutoRotate = true
				end)

Moving the HRP during animation would just change how the character is animated, as animations play relative to the HRP. So, my question is, are there any ways to get around this issue? Are there other methods of animation that don’t have this problem? Is there something wrong with the way I’m applying BodyVelocity? Is this animation specifically just not feasible because of the position it puts the character in? What should I do to get a result as close to my vision as possible?

7 Likes

I believe the problem with the jarring is caused by latency with server-client communication, if everything here is happening on the server, then if one player has a higher ping than the other, the animations will be off sync per client, thus resulting in a jitter – the player being knocked back is slightly behind in time than the attacking player creating a not-so-subtle rubber-band effect. One possible way I would think to handle this better is to handle the animations on the client side in local scripts using remote events, but have the actual attack and knocking back done entirely on the server, I think in theory this would eliminate the major rubber-banding happening (assuming the knockback isn’t an animation as well).

2 Likes

Yes the animations are both run by the server, and I did that because I was under the impression that latency would be worse if I fired an event to the two clients to run the separate animations. Would that not cause more latency then running them both at the same time from the same server script?

1 Like

It would not really cause as much latency since it would use ping of each client to smooth it in out. What I would suggest is just do a FireAllClients.

Thank you for your response, is it necessary to use FireAllClients? Could I not just fire to the clients who are running the animations and have them replicate from there? Or is there a reason other than replication that you are suggesting FireAllClients

1 Like

You can do that but I suggest doing it with fire all clients because there is no need to have it play on the server.

1 Like

@rottendogDkR

I’ve taken your responses into consideration because it is a good point, and it solves an issue that I didn’t even realize I had. However, it still does not fix the issue from my original post. As you can see, I’ve switched to running the animations themselves client-sided and the abrupt transition is still there.

Video of issue:

New Script:

		hitbox.Touched:Connect(function(part)
			if part.Parent:FindFirstChild("Humanoid") then --if detected part is a humanoid
				hitbox:Destroy() --Prevent multiple touched events
				
				local victim = Players:GetPlayerFromCharacter(part.Parent) --easy access to hit player (If it was a player)
				
				--Attaches another sound the the victim and adds it to garbage collection queue
				wind.Parent = part.Parent.Head
				game.Debris:AddItem(wind, 5)
				
				--Take damage, play first sound
				part.Parent.Humanoid:TakeDamage(10)
				impact:Play()
				
				--Prevents characters from moving during animation
				player.Character.Humanoid.WalkSpeed = 0
				player.Character.Humanoid.JumpHeight = 0
				player.Character.Humanoid.AutoRotate = false
				
				part.Parent.Humanoid.WalkSpeed = 0
				part.Parent.Humanoid.JumpHeight = 0
				part.Parent.Humanoid.AutoRotate = false
				
				for _,v in pairs(player.Character:GetChildren()) do
					if v:IsA("BasePart") then
						v.CanTouch = false
					end
				end
				
				--adjust both characters to be facing each other and close together
				part.Parent.HumanoidRootPart.CFrame = CFrame.new(part.Parent.HumanoidRootPart.Position, player.Character.HumanoidRootPart.Position)
				player.Character.HumanoidRootPart.CFrame = part.Parent.HumanoidRootPart.CFrame * CFrame.new(0,0,-3.5) * CFrame.Angles(0,math.pi,0)
				
				animEvent:FireAllClients(player, victim, RS:FindFirstChild("ShoCharAtk", true), RS:FindFirstChild("ShoCharVic", true))
				
				local vic = part.Parent.Humanoid.Animator:LoadAnimation(RS:FindFirstChild("ShoCharVic", true))
				
				if vic.Length == 0 then
					while vic.Length == 0 do
						task.wait()
					end
				end
				
				task.delay(vic.Length, function()
					if victim then 
						hitFunc(victim, true, 1.5, 0)
						isDown[victim] = false
					end

					part.Parent.Humanoid:TakeDamage(20)
					local velocity = Instance.new("BodyVelocity", part.Parent.HumanoidRootPart)
					velocity.MaxForce = Vector3.new(math.huge,math.huge,math.huge)
					velocity.P = 1000
					local angle = ((part.Parent.HumanoidRootPart.Position - player.Character.HumanoidRootPart.Position) * Vector3.new(10,0,10)).Unit * 70 + Vector3.new(0,5,0)
					velocity.Velocity = angle
					game:GetService("Debris"):AddItem(velocity,.3)
					part.Parent.Humanoid.WalkSpeed = 16
					part.Parent.Humanoid.JumpHeight = 7.2
					part.Parent.Humanoid.AutoRotate = true
					
					for _,v in pairs(player.Character:GetChildren()) do
						if v:IsA("BasePart") then
							v.CanTouch = true
						end
					end
					
					player.Character.Humanoid.WalkSpeed = 16
					player.Character.Humanoid.JumpHeight = 7.2
					player.Character.Humanoid.AutoRotate = true
					canPunch[player] = true
					canBlock[player] = true
					chargeEvent:FireClient(player)
					return
				end)
			end
		end)

Thank you for your help

2 Likes

Hmmm I see this is still happening in your video, for that I would try looking into Network Ownership next.

Like I said in the original post, I’m almost certain the problem is caused by the HumanoidRootPart being stationary for the duration of the animation. Changing or messing with network ownership is not going to solve the problem of the player’s body orientation switching when the animation ends.

1 Like

At least try it but if not you should make your own velocity.

What do you mean? I DID make my own velocity it’s right there in the script. I’m not sure what you’re trying to suggest?

1 Like

The only way I can think of solving this issue, assuming you are correct that it’s caused by the animation setting positional/orientational data is by not animating the movement of the target. That probably doesn’t make sense, so I’ll elaborate.

What I mean is, animate the target being launched into the air the way Roblox does player jumping animations - don’t actually make them get launched up in the animation, just make it look like they’re being launched. Then, after setting network ownership to the player throwing the other, have that player control the launching aspect so that they are actually being launched (physically, meaning the HRP is being moved with the rest of the rig), and then after they land again, launch them in whichever direction you desire. Theoretically, at least the way I imagine it playing out, this would solve the strange issue you face.

I realise you don’t want to use BodyVelocity, but it’s the only solution I can think of at the moment.

Edit: I didn’t take a look at your code, so if there’s something I’ve missed, please let me know.

I’m sorry if it was unclear, but I’m absolutely fine with using BodyVelocity as long as it turns out the way I want it to. If there is no way to get the result I want using BodyVelocity I’ll look for another way. I’ll try this method and see what I can get out of it, thanks!

2 Likes

Your own custom velocity what I mean is making your own without using instance.new and to simulate your own knock back with your own gravity.

Okay, this is something completely foreign to me, I have no idea how to do that. Could you maybe link some resources or something?

1 Like

This seems more like a BodyVelocity issue, not an animation issue. It can happen when a BodyVelocity is made on the server. Can you try changing the MaxForce from math.huge to 100000? Also I recommend setting its parent after you set it up.

local velocity = Instance.new("BodyVelocity")
velocity.MaxForce = Vector3.new(100000,100000,100000)
local angle = ((part.Parent.HumanoidRootPart.Position - player.Character.HumanoidRootPart.Position) * Vector3.new(10,0,10)).Unit * 70 + Vector3.new(0,5,0)
velocity.Velocity = angle
velocity.Parent = part.Parent.HumanoidRootPart

This was easy, I had actually already made the second change cause I remembered reading somewhere that setting the parent too early could cause jitters. Here’s the code and the results:

local velocity = Instance.new("BodyVelocity")
velocity.MaxForce = Vector3.new(100000,100000,100000)
velocity.P = 1000
local angle = ((part.Parent.HumanoidRootPart.Position - player.Character.HumanoidRootPart.Position) * Vector3.new(10,0,10)).Unit * 70 + Vector3.new(0,5,0)
velocity.Velocity = angle
velocity.Parent = part.Parent.HumanoidRootPart
game:GetService("Debris"):AddItem(velocity,.3)

Still flips over

To prevent flipping over you can disable the Humanoid’s states “Ragdoll” and “FallingDown”.

(HumanoidStateType)

I think you’re misunderstanding that part. The ragdoll is supposed to happen, it’s just the weird pause right before that is causing me trouble. Maybe it isn’t perfectly clear in this one clip, but the person being thrown flips upside down while they are in the air. At the end of the animation when they are hit, they are being hit in the back. The problem comes afterwards, when the animation ends and their body resets to the position it was in at the start of the animation, so they are facing each other again. It’s jarring and it’s like the player is T-Posing right before they get launched away

So the player that is being attacked is NOT supposed to be facing the player that is attacking him?