Reputation: 641
I would like to learn about game development in Godot Engine. I'm trying to make a mobile game similar to the game Missiles:
Right now I have a functioning joystick. I get the value as a normalized Vector2:
var joystick_value = joystick.get_value()
But I can't figure out how to change the velocity of the plane based on the joystick value. Plus to set some limit on how much the plane can turn (or the max angle).
(The plane is a KinematicBody2D)
Any ideas?
Upvotes: 2
Views: 1390
Reputation: 40355
If we are talking about KinematicBody2D and velocity
we are talking of a script something like this, give or take:
extends KinematicBody2D
var velocity:Vector2 = Vector2.ZERO # pixels/second
func _physics_process(_delta:float) -> void:
move_and_slide(velocity)
Perhaps you are better of working with speed
and direction
instead of velocity
. We can do that too:
extends KinematicBody2D
var speed:float = 0 # pixels/second
var direction:Vector2 = Vector2.UP # pixels
func _physics_process(_delta:float) -> void:
var velocity = direction.normalized() * speed
move_and_slide(velocity)
What if we wanted an angle
instead of a direction
vector? Sure:
extends KinematicBody2D
var speed:float = 0 # pixels/second
var angle:float = 0 # radians
func _physics_process(_delta:float) -> void:
var velocity = Vector2.RIGHT.rotated(angle) * speed
move_and_slide(velocity)
Since we will do steering, we want to rotate the KinematicBody2D
according to its velocity.
Sure, we can get a rotation angle
from a velocity
:
extends KinematicBody2D
var velocity:Vector2 = Vector2.ZERO # pixels/second
func _physics_process(_delta:float) -> void:
rotation = velocity.angle()
move_and_slide(velocity)
Similarly with a direction
vector, or if you have an angle
you can use that directly.
For steering we will be keeping the speed and changing angle. So we want the speed
and angle
version I showed above. With rotation, of course:
extends KinematicBody2D
var speed:float = 0 # pixels/second
var angle:float = 0 # radians
func _physics_process(_delta:float) -> void:
rotation = angle
var velocity = Vector2.RIGHT.rotated(angle) * speed
move_and_slide(velocity)
And now we will have a target_angle
which will come from user input. In your case that means:
var target_angle = joystick.get_value().angle()
Now, notice we don't know in what direction is the rotation. Doing target_angle - angle
does not work, because it could be shorter to rotate the other way around. Thus, we will do this:
var angle_difference = wrapf(target_angle - angle, -PI, PI)
What does wrapf
do? It "wraps" the value to a range. For example, wrapf(11, 0, 10)
is 1
, because it went over 10
by 1
, and 1 + 0
is 1
. And wrapf(4, 5, 10)
is 9
because it went below 5
by 1
and 10 - 1
is 9
. Hopefully that makes sense.
We are wrapping in the range from -PI
to PI
so it gives the angle difference in the direction that is shorter to make the rotation.
We will also need angular_speed
. That is, how much the angle changes per unit of time (the unit is in angle/time). Notice that is not the same as how much the angle changes (the unit is in angles). To convert, we multiply by the elapsed time since last time:
var delta_angle = angular_speed * delta
Ah, actually, we need that in the direction of angle_difference
. Thus, its sign
:
var delta_angle = angular_speed * delta * sign(angle_difference)
And we do not want to overshoot. Thus, if delta_angle
has greater absolute value than angle_difference
, we need to set delta_angle
to angle_difference
:
var angle_difference = wrapf(target_angle - angle, -PI, PI)
var delta_angle= angular_speed * delta * sign(angle_difference)
if abs(delta_angle) > abs(angle_difference):
delta_angle = angle_difference
We can save one call to abs
there:
var angle_difference = wrapf(target_angle - angle, -PI, PI)
var delta_angle_abs = angular_speed * delta
var delta_angle = delta_angle_abs * sign(angle_difference)
if delta_angle_abs > abs(angle_difference):
delta_angle = angle_difference
Put it all together:
extends KinematicBody2D
var speed:float = 0 # pixels/second
var angle:float = 0 # radians
var angular_speed:float = 0 # radians/second
func _physics_process(delta:float) -> void:
var target_angle = joystick.get_value().angle()
var angle_difference = wrapf(target_angle - angle, -PI, PI)
var delta_angle_abs = angular_speed * delta
var delta_angle = delta_angle_abs * sign(angle_difference)
if delta_angle_abs > abs(angle_difference):
delta_angle = angle_difference
angle += delta_angle
rotation = angle
var velocity = Vector2.RIGHT.rotated(angle) * speed
move_and_slide(velocity)
And finally, some refactoring, including but not limited to extracting that chunk of code to another function:
extends KinematicBody2D
var speed:float = 0 # pixels/second
var angle:float = 0 # radians
var angular_speed:float = 0 # radians/second
func _physics_process(delta:float) -> void:
var target_angle = joystick.get_value().angle()
angle = apply_rotation_speed(angle, target_angle, angular_speed, delta)
rotation = angle
var velocity = Vector2.RIGHT.rotated(angle) * speed
move_and_slide(velocity)
static func apply_rotation_speed(from:float, to:float, angle_speed:float, delta:float) -> float:
var diff = wrapf(to - from, -PI, PI)
var angle_delta = angle_speed * delta
if angle_delta > abs(diff):
return to
return from + angle_delta * sign(diff)
Here is a version with angular acceleration:
extends KinematicBody2D
var speed:float = 0 # pixels/second
var angle:float = 0 # radians
var angular_speed:float = 0 # radians/second
var angular_acceleration:float = 0 # radians/second^2
func _physics_process(delta:float) -> void:
var target_angle = joystick.get_value().angle()
if angle == target_angle:
angular_speed = 0
else:
angular_speed += angular_acceleration * delta
angle = apply_rotation_speed(angle, target_angle, angular_speed, delta)
rotation = angle
var velocity = Vector2.RIGHT.rotated(angle) * speed
move_and_slide(velocity)
static func apply_rotation_speed(from:float, to:float, angle_speed:float, delta:float) -> float:
var diff = wrapf(to - from, -PI, PI)
var angle_delta = angle_speed * delta
if angle_delta > abs(diff):
return to
return from + angle_delta * sign(diff)
And the shiny version with angular easing:
extends KinematicBody2D
var speed = 10
var angle:float = 0
var angular_speed:float = 0
export(float, EASE) var angular_easing:float = 1
func _physics_process(delta:float) -> void:
var target_angle = (get_viewport().get_mouse_position() - position).angle()
angle = apply_rotation_easing(angle, target_angle, angular_easing, delta)
rotation = angle
var velocity = Vector2.RIGHT.rotated(angle) * speed
move_and_slide(velocity)
static func apply_rotation_easing(from:float, to:float, easing:float, delta:float) -> float:
var diff = wrapf(to - from, -PI, PI)
var diff_norm = abs(diff)
var angle_speed = ease(diff_norm / PI, easing)
var angle_delta = angle_speed * delta
if angle_delta > diff_norm:
return to
return from + angle_delta * sign(diff)
Set angular_easing
to some value between 0 and 1 to have it accelerate as it begins to rotate and decelerate as it approaches the target angle. With a value of 0 it does not rotate. With a value of 1 it rotates with constant velocity. See ease.
I tested the code in this answer (with some non-zero values), and this for mouse control:
var target_angle = (get_viewport().get_mouse_position() - position).angle()
It works.
Upvotes: 4