Adjusting rotation behavior with player controlled speed up/slow down

Godot Version

4.4

Question

I am trying to create game that will require the player to control the speed of a rotating circle. I want the player to be able to begin the clockwise rotation via a mouse click, and then speed up and slow down the rotation using their keyboard (“a” for speed up, “s” for slow down). I also want the rotation to gradually slow down on it’s own if the player stops clicking anything. I have been able to successfully implement the above, but I want to tweak the spinning behavior so that the spinning stops when rotation_speed reaches 0, instead of overshooting and going into negative numbers and spinning clockwise increasingly fast (which is what I believe is happening when trying to use the “s” key to slow down to a stop, based on the print() function).

An additional challenge is that I want the player to be able to continue making as many full 360 degree clockwise rotations as they want, but of course once the circle does a full rotation the rotation resets to 0, therefore forcing a stop (although I am a bit confused on this as the built in rotate() function and the rotate property are not the same thing, and the rotation_speed variable I created is another thing besides. I think this is a math comprehension issue for me, lol).

I hope that is clear! I feel like I am a bit in the weeds here, and the solution may require some math, which is not my strong suit. Please let me know if you have any questions or if I need to clarify something.

Below is my current code:

extends Node2D

@export var rotation_speed = 0 #TAU * 2
var move = false
var stop = false

func _process(_delta):
	if move == true:
		$Carousel/Carousel.rotate(1 + rotation_speed) #is the 1 necessary?
		await get_tree().create_timer(3).timeout
		stop = true
		print("spinning:", rotation_speed)
	if stop == true and rotation_speed >= 0:
		move = false
		rotation_speed -= 0.01
		print("stopping:", rotation_speed)

func _input(_event: InputEvent) -> void:
	if Input.is_action_pressed("start"):
		move = true
	if move == true:
		if Input.is_action_pressed("spin"):
			rotation_speed += 0.1
			print("speed up:", rotation_speed)
		if Input.is_action_pressed("slow") and rotation_speed >= 0:
			rotation_speed -= 0.1
			print("slowing:", rotation_speed)

There are a few things unrelated to your questions I want to talk about first:

  1. For a continuous change while a button is pressed, use the Input singleton in _process() or _physics_process() (and multiply the amount of change by delta).
  2. Don’t await in _process(). Use a Timer node for this.
  3. When calling rotate(), you should not add anything to your rotation_speed - unless you want an additional, permanent torque, and in that case, the added value should be multiplied by delta.

About your questions: To prevent a value becoming less than 0, you can set it to max(0, new_value) instead of just subtracting from it:

			rotation_speed = max(0.0, rotation_speed - 0.1)

Either do that wherever you change the value, or use a setter function.

I’m not sure how this would cause a stop? Rotation values can exceed TAU or 360°, this shouldn’t cause any issues.
If you want, you can use posmod() to keep the values in a dedicated range:

	#adding to rotation while keeping it in range 0 to TAU:
	rotation = posmodf(rotation + rotation_speed, TAU)

But this shouldn’t be necessary.


Example for the full script:

extends Node2D

const SPEED_UP = 0.1
const SLOWING = -0.1
const STOPPING = -0.01

var rotation_speed: float = 0.0 :
	set(value):
		rotation_speed = max(0.0, value)

@onready var carousel: Node2D = $Carousel/Carousel #not sure which exact type it has
@onready var timer: Timer = $Timer


func _process(delta: float) -> void:
	if timer.is_stopped():
		rotation_speed += STOPPING * delta
		print("stopping:", rotation_speed)
	else:
		if Input.is_action_pressed("spin"):
			rotation_speed += SPEED_UP * delta
			print("speed up:", rotation_speed)
		if Input.is_action_pressed("slow"):
			rotation_speed += SLOWING * delta
			print("slowing:", rotation_speed)
	carousel.rotate(rotation_speed)


func _input(event: InputEvent) -> void:
	if event.is_action_pressed("start"):
		timer.start()
		set_process_input(false)

This requires a Timer node as a child. (one_shot enabled, wait_time set to 3.0)