- Refer to this page for an explanation of bhopping as a whole (air-strafing is a part of bhopping).
- It seems that the original developers tried to do it the original way, but they've ran into an issue of some sort. They've then tried a different attempt, and this is where the exploit started.
As mentioned, the following is the reason why this is all possible: instead of limiting velocity directly, only the projection of the current velocity onto acceleration is limited. Usually, you would have a value for max velocity and a vector for velocity. When the player intends to move, the difference from max velocity from the length of the velocity vector will be added (to the velocity). In this case, the vector projection of the current velocity and the normalized acceleration direction is used as the velocity length (, not the actual velocity length). The acceleration is a server-defined property that determines how much to add to the current velocity. Since the acceleration direction vector is normalized, this all works out: the acceleration is multiplied to the acceleration direction, and this value will be added to the current velocity vector. This is if the acceleration value is not truncated; the acceleration value is truncated if the current velocity projection plus the acceleration value exceeds the max velocity. It will be truncated to the max velocity minus the velocity projection.
The vector projection of a on b is a vector whose magnitude is the scalar projection of a on b and whose angle against b is either 0 or 180 degrees.
Alternatively, we can simply use a dot product.
Implementation in Code
from bge import logic
from mathutils import Vector
cont = logic.getCurrentController()
player = cont.owner
keyboard = bge.logic.keyboard
input_active = bge.logic.KX_INPUT_ACTIVE
forward_input_key = "WKEY" # Forward
backward_input_key = "SKEY" # Backward
leftward_input_key = "AKEY" # Leftward
rightward_input_key = "DKEY" # Rightward
forward_input = keyboard.events[getattr(bge.events, forward_input_key)]
backward_input = keyboard.events[getattr(bge.events, backward_input_key)]
leftward_input = keyboard.events[getattr(bge.events, leftward_input_key)]
rightward_input = keyboard.events[getattr(bge.events, rightward_input_key)]
svr_accelerate_air = 1.2 # These properties are server-defined. Increasing svr_accelerate_air will make
svr_velocity_max_air = 10 # air-strafing easier, and svr_velocity_max_air is the max in-air velocity
velocity = player.getLinearVelocity(True)
player["velocity"] = velocity.length
accelerate_direction = Vector()
if forward_input == input_active: # Every tick, this will determine where the player is intending to go.
accelerate_direction += Vector((0.0, 1.0, 0.0)) # Pressing any movement key will change this value.
if backward_input == input_active:
accelerate_direction += Vector((0.0, -1.0, 0.0))
if leftward_input == input_active:
accelerate_direction += Vector((-1.0, 0.0, 0.0))
if rightward_input == input_active:
accelerate_direction += Vector((1.0, 0.0, 0.0))
accelerate_direction.normalize() # This is crucial to the result!
def playerAccelerate(velocity, velocity_max, accelerate_direction, accelerate):
velocity_projection = numpy.dot(velocity, accelerate_direction)
velocity_accelerate = accelerate
if velocity_projection + velocity_accelerate > velocity_max: # Here, we truncate the acceleration value
velocity_accelerate = velocity_max - velocity_projection # if it will make for an unbalanced total velocity.
player.localLinearVelocity += accelerate_direction * velocity_accelerate # Acceleration multiplied by accelerate_direction will be added
# to the current velocity. This is why normalizing the vector
# is crucial!
playerAccelerate(velocity, svr_velocity_max_air, accelerate_direction, svr_accelerate_air) # Execute playerAccelerate() every tick. This is
# done in this format to make adding ground acceleration
# easier to implement.
- The variables use "air" in the name as, in order to create a full movement system, ground acceleration will also be needed. Ground acceleration will have its own set of values.
I have compiled all the work in a .blend file very neatly. Hopefully, there is nothing misleading or confusing. Everything works perfectly (that I know of). The final result:
- The file contains explanation and instructions for air-strafing.
- Here is an example of the exploit: you can see that my velocity is over the server-defined max, but I am using air-strafing to exploit it!