Last server records
Velocity is a vector. We can specify it either as three components (projections on the axis of the coordinate system) or as a pair – module of velocity (speed) and its direction (unit vector).
When fps is 100, CS engine processes mouse movements and button presses 100 times in a second. Then, using this data, engine calculates behavior of the player model. More precisely, engine doesn’t know which buttons are pressed, but gets commands like “go to the right”, “duck”, “jump” and so on, but we will talk about buttons for the sake of simplicity.
We will derive velocity of the player on each engine time in 5 steps:
- Get the state of movement buttons (WASD)
- On basis of retrieved states get the wishful velocity (the one player desires to have) in the coordinate system bound to the player.
- Knowing the direction which player is aiming at, find a relation (transition matrix) between coordinate system of the player model and coordinate system of the outer world (that is map).
- In case of friction presence (walking on the ground, for example) reduce the current speed.
- On basis of the matrix, retrieved on step 3, translate wishful velocity into the coordinate system bound to the outer world. Then add this velocity to the current velocity in a certain way. At that method of adding is different in cases of walking on the ground and flying in the air.
First of all, let’s look at this function from input.cpp:
As you can see, it returns
val, which describes a state of some button. For instance, if the button was pressed at the previous frame and still pressed at the current frame, then
valequals 1. And in case if the button wasn’t pressed neither at the previous frame nor at the current frame, then
Now in the same input.cpp examine a part of code, which from pressed buttons derives a wishful velocity (
upmove), i. e. a direction we want to move along at the current frame. At the end of the article it becomes clear what is the final direction of movement afterwards all calculations.
For obtaining wishful velocity components we use values of cl_upspeed (default is 320), cl_forwardspeed (400), cl_backspeed (400), cl_sidespeed (400). While holding Shift button we get the speed multiplied by 0.3 in Half-Life and by 0.52 in CS 1.6. Then if the speed turned to be more than
maxspeed(250 with usp / knife), it’s scaled to be equal 250.
Let’s take up the part of PM_PlayerMove function from pm_shared.c:
What is that AngleVectors there? It’s kinda simple. Denote as
- ICS – inertial coordinate system, which is bound to the outer world (map)
- PCS – player coordinate system, which is bound to the player model
Wishful velocity that was retrieved in input.cpp, is written is PCS, at that PCS is a left-handed system (X axis points forward, Y right and Z up – these are the same directions which give us positive values of
upmove). We want to use wishful velocity for retrieving final velocity, which is written in ICS. So, AngleVectors function will provide us with a matrix of transition from ICS to PCS.
The input is the angles which determine a direction we looking along, i. e. position of PCS relative to ICS. These angles are called yaw (right-left, Z axis), pitch (up-down, Y axis) and roll (right-left tilt, X axis). For short denote them as
To retrieve a matrix of transition from ICS to PCS one need to multiply turning matrixes, corresponding to row-pitch-yaw angles. Plus one more matrix will set reflection of Y axis, so we will get a right-handed coordinate system from the left-handed one:
After multiplying we have exactly the same formulas as you could see in the code of AngleVectors. Required transition matrix looks like:
upare unit vectors that specify PCS axis written in ICS.
Get back to PM_PlayerMove. Further we have PM_Friction function to be called (in case we are walking on the ground). The interesting part is:
For sv_friction 4 and 100 fps (meaning duration of each frame is 0.01 seconds) we have
drop = control * 0.04. If our speed is more than sv_stopspeed (75 by default), than control equals to our speed, and
dropcomes to be 4 percent from it. These 4 percent is how much our speed will be slowed down at the output of PM_Friction.
After PM_Friction engine calls either PM_WalkMove (walking on the ground) or PM_AirMove (flying in the air). Let’s examine these cases separately.
Case 1: Walking
Here is PM_WalkMove:
sidemoveform input.cpp and then use
rightfrom transition matrix for the translation of velocity into the coordinate system of the map, just as we planned.
In order to exclude the influence of movement buttons on vertical speed (in PCS) we don’t use
upmovecomponent and zero vertical components of
right(which forces us to normalize these vectors, so they won’t affect wishful speed). Then speed is clipped to maxspeed and PM_Accelerate is called, having direction (unit vector) of wishful velocity and sv_accelerate (5 on default) as parameters.
DotProductis a scalar product of
wishdir. It’s calculated by multiplying their components, but we are interested in a physical meaning. Since
wishdiris a unit vector which determines a wishful direction, we actually project current
velocityon the direction of
wishdir. The more angle between them, the less projection is, i. e. currentspeed variable.
Next we retrieve
addspeedas wishful speed minus
currentspeed, and also
accel * pmove->frametime * pmove->frictioncan be estimated as
5 * 0.01 * 1 = 0.05, so
accelspeedis 5 percent form wishful speed).
Against correlation between
accelspeedone of these values become a length of speed gain, which is so desired by us. We add to the current velocity a vector, directed along
accelspeedlength (these vectors are both written in ICS, so this operation is absolutely legal).
Now let’s try to feel what it all means.
Experiment 1. You have pressed W button and now is running straight forward with knife or usp. PM_Friction at the current frame slows us down by 4 percent, so our speed is 240 units/s intead of 250. In that W is the only button pressed, input.cpp tells us that its state is 1, states of other movement buttons equal 0. We have
cmd->forwardmove = 400,
cmd->sidemove = 0, and after scaling (since we exceeded maximum speed of 250)
cmd->forwardmove = 250,
cmd->sidemove = 0. Vector (
smove) in PM_WalkMove function reveals the direction we are looking along. Direction of
wishdiris the same as direction of the current velocity,
wishspeedis 250 and we are entering PM_Accelerate. Here
currentspeed = 240 * cos(0) = 240
addspeed = 250 - 240 = 10
accelspeed = 250 * 0.05 = 12.5 > 10
accelspeedturns to be equal 10. Then we add 10 units/s to our speed of 240 (in the same direction) and speed now equals 250 units/s. That’s how we just walking straight forward.
Experiment 2. We are running the same way as in experiment 1 and suddenly pressing A. Our speed after PM_Friction is 240 units/s. W was pressed and is still pressed, so its state is 1. A is pressed just now, its state is 0.5 Thus,
cmd-forwardmove = 400,
cmd->sidemove = 200and then after scaling
cmd->forwardmove = 223.6,
cmd->sidemove = 111.8. Inside PM_WalkMove after retrieving wishful velocity we pass into PM_Accelerate
wishspeed = 250and unit vector
wishdir, which forms an angle of
arctg(200 / 400) = 26.565degrees with current velocity. In PM_Accelerate:
currentspeed = 240 * cos(26.565) = 214.66
addspeed = 250 - 214.66 = 35.34
accelspeed = 250 * 0.05 = 12.5 < 35.34
accelspeedstays to be equal 12.5. We add 12.5 units/s to our 240 along the direction of
wishdir. According to the cosine law:
x^2 = 240^2 + 12.5^2 + 2* 240 * 12.5 * cos(26.565) = 63122.77
x = 251.24
Thus, our velocity has turned to the left a bit and increased by 1.24 units/s.
During the subsequent frames the state of A button will be 1, therefore
wishdirwill form 45 degress with the direction of our view all the time. Here is the scheme of velocity adding:
As velocity vector is turning to the left, speed is changing in an intriguing way (cause of friction). It is decreasing for a few frames prior to 249, then growing up to 262 for 14 frames, and fluently falling for about 50 frames (recall that when fps is 100 every frame is 0.01 seconds long). Seems like we have explained a technique called fastrun.
In the end, velocity direction will be the same as
wishdir. At that angle
ubetween velocity and
wishdirright after presing A is 26.565 degrees, than jumps up to 45 and gradually decreasing to zero. And here we have a curious question - what if we turn
wishdirto the left all the time, keeping some constant
uangle - how
uwould depend from velocity?
Experiment 3. Turning
wishdirto the left means turning to the left direction of view. In order to do it with constant angular speed let's use «arrows» on keyboard (+left and +right commands). Just run with pressed A, W and left arrow. A few seconds later you will discover that you are running in a circle with a constant speed. In this PM_Friction will scale down the velocity every frame, while PM_Accelerate will turn it and increase by the same value.
Arrow rotation speed is defined by cvar cl_yawspeed, which is 210 by default. Let's reduce cl_yawspeed to 180. Radius of the circle we running in increased. Consequently
uangle became less. In addition,
addspeedis still more than
accelspeed, so the length of added vector remains the same. And that's why established speed became bigger.
We continue diminishing cl_yawspeed. Everything's go well, about 118 we have assured 277 units/s, but at cl_yawspeed 117 speed began to drop. Thus we found out that at some angle
uthe growth of speed for one frame will be maximum. Let's draw a 3D plot with help of MATLAB that will show the dependence of the speed increase from the angle
uand current speed:
In the third experiment we accelerated firstly i. e. were somewhere in red zone of the plot, than shifted to a constant angle
uwith zero speed increase i. e. fell into yellow zone. In this it's clear that the less we made
uthe more speed we were able to retain. However at speeds more than 250 units/s we meet the pit which does not allow us to gain speed at small values of
u. There's no point to consider the plot beyond 277-278 units/s since none of possible angles
uwill give us speed gain.
Case 2: Flying
Well, now leave a ground alone. We will examine what happens in the air.
PM_AirMove is different from PM_WalkMove only in that PM_AirAccelerate is called instead of PM_Accelerate and the parameter passed is sv_airaccelerate (10 on default) instead of sv_accelerate. PM_AirAccelerate by itself is pretty the same as PM_Accelerate:
But here are two important differences in physics. Firstly, there is no friction in the air, secondly we cut
wishspeedto 30 (
wishspdvariable is equal to
wishspeed). Thus, we cannot use the same tactics as on the ground. Indeed, our
currentspeedshouldn’t be more than 30, otherwise we won’t get any gain. And now we do the trick - refusing to use W and pressing A or D only. Also we should control angle
ubetween current speed and wishful direction such that
DotProductwon’t be more than 30.
However, let’s concentrate on numbers at first.
Take a run by pressing W, then jump, release W and press A. There is no friction affecting us since this moment, so at the first frame current velocity vector has length of 250 units/s and is oriented along the direction of our look. We will enter PM_AirAccelerate having
wishdirto be directed to the left and
wishspd = 30
currentspeed = 0 (cos(90) = 0)
addspeed = 30 - 0 = 30 > 0
accelspeed = 10 * 250 * 0.01 * 1 = 25 < 30
accelspeedremains to be equal 25. Final speed will be deviated to the left a bit, and according to the Pythagorean Theorem:
x^2 = 25^2 + 250^2 = 63125
x = 251.25
Thus, our speed is a bit more and we will fly large distance. Although, in fact the distance that we fly forward is absolutely the same as if we wouldn’t press any button after jump at all – total distance is more due to a small deviation to the left.
Just now the angle
wishdirand the current speed was about 90 degress. Suppose that we are doing lj and our speed before jump is 275 units/s. Just as on the ground we're going to learn how speed gain depends on angle
Let’s try to find an angle
DotProductwould be equal 30. From
cos(a) = 30 / 275we retrieve
u = 83.74degrees. If
uis less, speed is not changing, and if
uis more we have a speed gain. Firstly gain is not that big, but with increasing of angle
uthe value of
DotProductwill reach value of 5 (it will happen at the angle of
acrcos(5 / 275) = 88.96degrees),
addspeedwill be equal to 25 and subsequent growth of the angle
uwon’t affect the length of added vector –
accelspeedwill be equal to 25 too all the time. Moreover, this condition gives a maximum gain because further angle increasing will reduce the total velocity on account of vector addition. When the angle is more than 90 degrees,
addspeedis guaranteed to be more than
accelspeedand we still have speed gain. In that vector addition schematically looks like:
At the angle of 92.61 degrees velocity will just turn, but its length will stay the same. At the angles
umore than 92.61 we will loose the speed, at that the more angle, the more lost.
In total, we have three cases:
u <= 83.74
Velocity vector is not changing (neither module nor direction). If during the flight you turn your mouse to the left and press D, whereupon continue to drag mouse to the left, then your velocity will stay absolutely the same. Also if you are strafing with pressed W button, your velocity will stay the same as well.
83.74 < u < 92.61
Your velocity vector is changing the direction towards strafe button and increase in length. Maximum of gain will take place at the angle of 88.96 degrees. You can see this gain in a Gain column of lj statistics.
u >= 92.61
Velocity vector is changing its direction and decreasing its length (or keeps same length for
u = 92.61). This speed loss can be seen in a Loss column of lj statistics.
Turns out that in order to gain speed we have to keep angle
uin a quite narrow gap. If your speed is 340 units/s (very good result for lj) than the gap diminishes to
84.94 < u < 92.11, at that maximum of gain corresponds to
u = 89.16. Besides the gain itself will become less - at the beginning of jump you can add more than 1.5 units/s and at the end no more than 1.3 units/s. Summing up, the more speed, the harder to make gain.
Now let set a goal to maintain angle
uwhich will give us maximum speed gain (we will call it optimal). We need to understand how fast we should move the mouse. Assume that
xis an angle by which velocity will trun during one frame, and
vis a current speed. From law of sines we get:
sin(x) / 25 = sin(180 - x - (180 - u)) / v
v * sin(x) = 25 * (sin(u) * cos(x) - cos(u) * sin(x))
tg(x) = sin(u) / (v / 25 + cos(u))
v = 275,
u = 88.96we retrieve
x = 5.186degrees, and at
v = 340,
u = 89.16we have
x = 5.035degrees.
It means that if we were able to keep some optimal
uwe would have to do our strafes smoother as speed increases. However as we can see this effect is so insignificant there's a point to recall it only at really big speeds. For a normal lj it's more important to understand how transition from ground prestrafe to air strafe works, and what happens during the switch between strafes.
We will talk about these things in the next article dedicated to LongJump physics. And for now we're going to examine 3D plot showing dependence of speed gain from angle
uand current speed during the flight:
There is no speed gain at yellow plateau, speed loss at blue pit and only at the knoll in the middle we can speed up. And the more speed the lower and narrower this knoll becomes.
That's all for now, we will continue to deal with lj in the next article.