Movement
November 5, 2021About 4 min
Movement
Basic Movement
移动算法的结构图
2D
角色在2维空间移动,大多数3维游戏也可以看做二维的移动。

Statics
在处理角色的位置、转向这些数据使用的公式与算法叫做静态,因为这些数据不含有任何角色移动的数据。数据结构可以定义为:
class Static:
position: Vector
orientation: float
Kinematic
如果一个角色正在向一个方向移动,突然改变他的速度与方向,这看起来有点突兀。为了让这个运动更加的丝滑,不让角色加速太快。我们就需要一些算法去考虑角色当前的速度,使用合理的加速度去改变速度。
我们就要记录这个角色的速度与转向的速度(角速度),我们可以定义数据结构为:
class Kinematic:
position: Vector
orientation: float
velocity: Vector
rotation: float
Kinematic Movement
Seek
给定一个角色的静态数据以及目标的静态数据。来计算角色到目标的方向以及一个直线速度。大概实现如下:
class KinematicSeek:
character: Static
target: Static
maxSpeed: float
function getSteering() -> KinematicSteeringOutput:
result = new KinematicSteeringOutput()
# Get the direction to the target.
result.velocity = target.position - character.position
# The velocity is along this direction, at full speed.
result.velocity.normalize()
result.velocity *= maxSpeed
# Face in the direction we want to move.
character.orientation = newOrientation(
character.orientation,
result.velocity)
result.rotation = 0
return result
这个算法可以用于追击的一些情况。但是我们如果要让角色到一个点,然后停下来。我们可以添加一个以目标点为圆心的半径。当我们的角色到这个半径内时,就停止移动。当我们的设置半径不合理时,太小了,会使角色一直到不了目标点,还会看到角色的抖动。这里有几种解决方式:
- 固定时间到达目标点,需要设置一个最大速度阀值
- 扩大半径
class KinematicSeek:
character: Static
target: Static
maxSpeed: float
function getSteering() -> KinematicSteeringOutput:
result = new KinematicSteeringOutput()
# Get the direction to the target.
result.velocity = target.position - character.position
# Check if we’re within radius.
if result.velocity.length() < radius:
# Request no steering.
return null
# We need to move to our target, we’d like to
# get there in timeToTarget seconds.
result.velocity /= timeToTarget
# If this is too fast, clip it to the max speed.
if result.velocity.length() > maxSpeed:
result.velocity.normalize()
result.velocity *= maxSpeed
# Face in the direction we want to move.
character.orientation = newOrientation(
character.orientation,
result.velocity)
result.rotation = 0
return result
Wandering
Arrive
可以设置以目标点设置两个圈,一个大半径圈,当进入这个圈后,角色开始减速。小圈是判断角色是否已经到达。
class Arrive:
haracter: Kinematic
arget: Kinematic
axAcceleration: float
axSpeed: float
# The radius for arriving at the target.
argetRadius: float
# The radius for beginning to slow down.
slowRadius: float
# The time over which to achieve target speed.
timeToTarget: float = 0.1
function getSteering() -> SteeringOutput:
result = new SteeringOutput()
# Get the direction to the target.
direction = target.position - character.position
distance = direction.length()
# Check if we are there, return no steering.
if distance < targetRadius:
return null
# If we are outside the slowRadius, then move at max speed.
if distance > slowRadius:
targetSpeed = maxSpeed
# Otherwise calculate a scaled speed.
else:
targetSpeed = maxSpeed * distance / slowRadius
# The target velocity combines speed and direction
targetVelocity = direction
targetVelocity.normalize()
targetVelocity *= targetSpeed
# Acceleration tries to get to the target velocity.
result.linear = targetVelocity - character.velocity
result.linear /= timeToTarget
# Check if the acceleration is too fast.
if result.linear.length() > maxAcceleration:
result.linear.normalize()
result.linear *= maxAcceleration
result.angular = 0
return result
在很多实现中并没有使用到大圈这种方式,因为在减速是会有震荡的可能性
ALIGN
对齐使角色的转向与目标的转向相匹配。
class Align:
character: Kinematic
target: Kinematic
maxAngularAcceleration: float
maxRotation: float
# The radius for arriving at the target.
targetRadius: float
# The radius for beginning to slow down.
slowRadius: float
# The time over which to achieve target speed.
timeToTarget: float = 0.1
function getSteering() -> SteeringOutput:
result = new SteeringOutput()
# Get the naive direction to the target.
rotation = target.orientation - character.orientation
# Map the result to the (-pi, pi) interval.
rotation = mapToRange(rotation)
rotationSize = abs(rotation)
# Check if we are there, return no steering.
if rotationSize < targetRadius:
return null
# If we are outside the slowRadius, then use maximum tation.
if rotationSize > slowRadius:
targetRotation = maxRotation
# Otherwise calculate a scaled rotation.
else:
targetRotation = maxRotation * rotationSize / slowRadius
# The final target rotation combines speed (already inthe
# variable) and direction.
targetRotation *= rotation / rotationSize
# Acceleration tries to get to the target rotation.
result.angular = targetRotation - character.rotation
result.angular /= timeToTarget
# Check if the acceleration is too great.
angularAcceleration = abs(result.angular)
if angularAcceleration > maxAngularAcceleration:
result.angular /= angularAcceleration
result.angular *= maxAngularAcceleration
result.linear = 0
return result
VELOCITY MATCHING
把角色的速度与目标的数据设置为一样
class VelocityMatch:
character: Kinematic
target: Kinematic
maxAcceleration: float
# The time over which to achieve target speed.
timeToTarget = 0.1
function getSteering() -> SteeringOutput:
result = new SteeringOutput()
# Acceleration tries to get to the target velocity.
result.linear = target.velocity - character.velocity
result.linear /= timeToTarget
# Check if the acceleration is too fast.
if result.linear.length() > maxAcceleration:
result.linear.normalize()
result.linear *= maxAcceleration
result.angular = 0
return result
FACE
这个行为使角色看向目标,他委托对齐行为但是先计算目标的方向。
class Face extends Align:
# Overrides the Align.target member.
target: Kinematic
# ... Other data is derived from the superclass ...
# Implemented as it was in Pursue.
function getSteering() -> SteeringOutput:
# 1. Calculate the target to delegate to align
Work out the direction to target.
direction = target.position - character.position
# Check for a zero direction, and make no change if so.
if direction.length() == 0:
return target
# 2. Delegate to align.
Align.target = explicitTarget
Align.target.orientation = atan2(-direction.x, direction.z)
return Align.getSteering()
LOOKING WHERE YOU`RE GOING
class LookWhereYoureGoing extends Align:
# No need for an overridden target member, we have
# no explicit target to set.
# ... Other data is derived from the superclass ...
function getSteering() -> SteeringOutput:
# 1. Calculate the target to delegate to align
# Check for a zero direction, and make no change if so.
velocity: Vector = character.velocity
if velocity.length() == 0:
return null
# Otherwise set the target based on the velocity.
target.orientation = atan2(-velocity.x, velocity.z)
# 2. Delegate to align.
return Align.getSteering()