-
Notifications
You must be signed in to change notification settings - Fork 16
At the end of this chapter, your will be able to move pc
sprite by arrow keys. Below is the screenshot for Chapter 4.
Check out commit: 11fbf11.
Godot provides several ways to handle inputs on different levels. The topic is covered in Tutorials/Inputs. Our approach in this demo involves two steps. First register inputs with a specific name in Project Settings/Input Map
. Then respond to input events in scripts by implementing _unhandled_input()
.
Open Input Map
(official tutorial). Bind arrow keys and Vi keys (hjkl) to one of four actions: move_left
, move_right
, move_up
and move_down
.
Add PCMove
(Node2D
node) to MainScene
node. Attach PCMove.gd
to the newly created node. Add InputName.gd
to library/
folder to store action names as string constants. Inside PCMove.gd
, write code to respond to keyboard inputs by printing messages in the console window.
# InputName.gd
const MOVE_LEFT: String = "move_left"
# PCMove.gd
var _new_InputName := preload("res://library/InputName.gd").new()
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed(_new_InputName.MOVE_LEFT):
print("move left")
Check out commit: fb307f1.
Moving PC involves three steps:
- Get a reference to
pc
node inPCMove.gd
. - Convert inputs to a pair of integers.
- Set PC's new position based on the two integers.
Let's solve step two first.
# PCMove.gd
func _unhandled_input(event: InputEvent) -> void:
var x: int = 0
var y: int = 0
if event.is_action_pressed(_new_InputName.MOVE_LEFT):
x -= 1
elif event.is_action_pressed(_new_InputName.MOVE_RIGHT):
x += 1
elif event.is_action_pressed(_new_InputName.MOVE_UP):
y -= 1
elif event.is_action_pressed(_new_InputName.MOVE_DOWN):
y += 1
In order to get a reference to pc
node, we can call get_tree().get_nodes_in_group("pc")[0]
. Because according to InitWorld.gd
, pc
group has only one member, that is, pc
node. However, I think this is a good chance to introduce signal
. Let InitWorld.gd
emit a signal, sprite_created
, whenever a new sprite is instanced. PCMove.gd
receives and decodes the signal and gets a reference to pc
.
# InitWorld.gd
signal sprite_created(new_sprite)
func _create_sprite(prefab: PackedScene, group: String, x: int, y: int,
x_offset: int = 0, y_offset: int = 0) -> void:
# Add this line to the end.
emit_signal("sprite_created", new_sprite)
# PCMove.gd
var _pc: Sprite
func _ready() -> void:
var __ = get_node("../InitWorld").connect("sprite_created", self,
"_on_InitWorld_sprite_created")
print("connect: {0}".format([__]))
func _unhandled_input(event: InputEvent) -> void:
print("pc: {0}".format([_pc]))
var source: Array = _new_ConvertCoord.vector_to_array(_pc.position)
var x: int = source[0]
var y: int = source[1]
if event.is_action_pressed(_new_InputName.MOVE_LEFT):
x -= 1
elif event.is_action_pressed(_new_InputName.MOVE_RIGHT):
x += 1
elif event.is_action_pressed(_new_InputName.MOVE_UP):
y -= 1
elif event.is_action_pressed(_new_InputName.MOVE_DOWN):
y += 1
_pc.position = _new_ConvertCoord.index_to_vector(x, y)
func _on_InitWorld_sprite_created(new_sprite: Sprite) -> void:
if new_sprite.is_in_group(_new_GroupName.PC):
_pc = new_sprite
When testing the game, it turns out that even though connect
returns 0, which means that sprite_created
is successfully connected with _on_InitWorld_sprite_created()
, the reference to pc
node is still null. This is due to the fact that PCMove._ready()
is called after InitWorld._ready()
. The signal is connected to the function, but it is not received even once. One possible solution is to move initialization code from InitWorld._ready()
to InitWorld._process()
.
# InitWorld.gd
var _initialized: bool = false
func _process(_delta) -> void:
if not _initialized:
_initialized = true
_init_floor()
_init_wall()
_init_dwarf()
_init_PC()
_init_indicator()
The full code at current stage is available at the start of this part. We will take another approach to this problem in the next part.
Check out commit: aa35961.
PC already responds to arrow keys in the last part. We shall now refactor code to make it more elegant and robust. Instead of initializing game world in InitWorld._process()
, we create the dungeon after pressing Space. This includes two tasks.
-
InitWorld._unhandled_input()
is active when game starts. It responds to Space key and calls initialization functions.InitWorld
no longer reponds to inputs once Space is pressed. -
PCMove._unhandled_input()
is inactive at first. It becomes active oncepc
sprite is created.
We need to ensure that only one _unhandled_input()
is active at a time to avoid potential conflicts.
To fix PCMove.gd
is simple.
# PCMove.gd
func _ready() -> void:
var __ = get_node("../InitWorld").connect("sprite_created", self,
"_on_InitWorld_sprite_created")
set_process_unhandled_input(false)
func _on_InitWorld_sprite_created(new_sprite: Sprite) -> void:
if new_sprite.is_in_group(_new_GroupName.PC):
_pc = new_sprite
set_process_unhandled_input(true)
Open Project Settings/Input Map
. Bind Space key to action init_world
. Also add the action name to res://library/InputName.gd
. As for InitWorld
, first remove InitWorld._process()
. Then move _init
functions to another place.
# InitWorld.gd
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed(_new_InputName.INIT_WORLD):
_init_floor()
_init_wall()
_init_PC()
_init_dwarf()
_init_indicator()
set_process_unhandled_input(false)
A Godot expert (you, for example) would frown at this line of code in PCMove.gd
:
# PCMove.gd
var __ = get_node("../InitWorld").connect("sprite_created", self,
"_on_InitWorld_sprite_created")
It is not a good idea to refer to sibling nodes directly. The official guide suggests:
If a scene must interact with an external context, experienced developers recommend the use of Dependency Injection.
In order to fix this issue, first remove the code above. Attach MainScene.gd
to MainScene
node. We shall connect signals in the parent node of InitWorld
and PCMove
.
# MainScene.gd
var __
func _ready():
__ = get_node("InitWorld").connect("sprite_created", get_node("PCMove"),
"_on_InitWorld_sprite_created")
The function connect()
returns an integer which we are not interested in. We declare a variable __
to receive the return value but we shall never use it. A variable whose name starts with an underscore can be declared but never used. We use these tricks to avoid warning messages.