1
\$\begingroup\$

I have some platformer code to make my character jump based on height. I'd like to be able to tune it in the editor using both world units or number of tiles. I can do this with setters:

extends CharacterBody2D

const TILE_SIZE := 16.0

@export_group("Jump Movement")
## Height in world units.
@export_range(1, 160) var jump_height: float = TILE_SIZE * 4
## Height in tile count.
@export_range(1, 10, 0.25) var jump_tiles: float = 4:
    get:
        return jump_height / TILE_SIZE
    set(value):
        jump_height = value * TILE_SIZE
@export var jump_speed: float:
    get:
        return calc_jump_speed()
@export_range(0, 9999) var gravity: float = 3840


func calc_jump_speed():
    return -sqrt(2 * gravity * jump_height)


func _physics_process(dt: float) -> void:
    velocity.y += gravity * dt
    if Input.is_action_just_pressed("jump0"):
        velocity.y = calc_jump_speed()
    move_and_slide()

godot inspector showing the above node

However, the editor doesn't display updated values when I change them: setting jump_tiles to 1 should change jump_height to 16 but it stays at 64 and jump_speed is always 0. If I change jump_height to 128, my character jumps higher but no values change. The underlying values do change -- my character only jumps 1 tile. And when viewing the player in Remote scene tab, the values all update. But when I save the file, it stores the values seen in the Local inspector and only if they're modified:

[node name="Node2D" type="CharacterBody2D" parent="."]
position = Vector2(-71, 376.054)
collision_mask = 15
script = ExtResource("10_ob28b")
jump_height = 128.0
jump_tiles = 1.0

If I restart the editor, it uses my jump_tiles value and not jump_height. It seems that the values are applied in the order they appear in script and loading jump_tiles from the tscn executes the setter which overrides the jump_height value from the tscn.

I can add @tool to the top of the script and then the values change, but then my character is difficult to work with: they fall due to gravity and walk away from their spawn point in the editor scene while I'm testing.

The Running code in the editor docs say you can use not Engine.is_editor_hint() to skip any code that shouldn't run in editor (like applying gravity), but also has a big red warning about how you might crash the editor.

So how can I add derived inspector values without making my whole script affected by @tool?

\$\endgroup\$

3 Answers 3

1
\$\begingroup\$

My current solution is to remove @export from these derived values:

## Height in world units.
@export_range(1, 160) var jump_height: float = TILE_SIZE * 4
## Height in tile count.
var jump_tiles: float = 4:
    get:
        return jump_height / TILE_SIZE
    set(value):
        jump_height = value * TILE_SIZE
var jump_speed: float:
    get:
        return calc_jump_speed()

I can view and edit them in the Remote scene tab, tune the number of tiles to jump, and copy jump_height to paste into the Local scene tab.

The Remote Scene tab Showing my above node in the Remote Scene tab

I lose the bounds and step options from @export_range, but at least I don't have redundant values serialized and the values are always in sync. Alternatively, I only export the derived values so jump_height is computed when loading jump_tiles.

I hope someone has a better answer!

\$\endgroup\$
1
\$\begingroup\$

So how can I add derived inspector values without making my whole script affected by @tool?

You can't. Inspector fields update when the underlying variables change. So, to update a different field, you need to update the corresponding variable from a different place. This requires the execution of additional code to affect that variable and can't happen in-editor without using a @tool script.

I can add @tool (...) but then my character is difficult to work with (...) while I'm testing.

Of course, you want game logic to run only when playing. One solution is, you can use a guard clause in your functions to prevent in-editor execution:

func _physics_process(dt: float) -> void:
    if Engine.is_editor_hint(): return
    // Do stuff...

The Running code in the editor docs (...) also has a big red warning about how you might crash the editor.

Yeah, because that's true. However, this shouldn't prevent you from using legitimate editor features, and your use case doesn't seem one of those edge cases the docs are implying :)

\$\endgroup\$
0
\$\begingroup\$

This is happening because the editor is using the value from jump_tiles to update jump_height, however you are not updating jump_tiles itself, causing jump_height to reset every time you open the scene and play the game.

To solve this, update your setter to also update jump_tiles, like this:

@export_range(1, 10, 0.25) var jump_tiles: float = 4:
    get:
        return jump_height / TILE_SIZE
    set(value):
        jump_tiles = value
        jump_height = value * TILE_SIZE
\$\endgroup\$
1

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .