-
Notifications
You must be signed in to change notification settings - Fork 17
At the end of this chapter, your will be able to move pc sprite by arrow keys.
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
pcnode 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.InitWorldno longer reponds to inputs once Space is pressed. -
PCMove._unhandled_input()is inactive at first. It becomes active oncepcsprite 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)