I did these between 2019-07-28 and 2019-088-17. I never made that game.
I'm learning how to use the Godot game engine and so I'm going to start keeping my notes on my blog so I can remember them, and hopefully gather resources that may help you.
My goal is to rebuild an ancient RPG I made called Final Existence on the Amiga in AMOS Basic, which was a YA alternate universe apocalyptic game that looked and felt a lot like Chrono Trigger. I've become a bigger fan of ARPGs lately, and I'll rebuild it using that paradigm.
There are a crap ton of tutorials out there, but I've been having trouble gathering all the pieces I want together to build an ARPG, and there's a lot of moving parts in Godot, so this is my attempt to wrangle all that together in one place for myself.
So, on with the notes:
TileMap
/Area2D
collisions for making the character walk around and get
stopped by things, and know what I ran into.export var
to select target sceneexport var
for warp target in other sceneget_tree().root.get_child()
/.add_child()
Scene#queue_free
to safely remove from memory!Camera2D#make_current()
Camera2D#limit_?
?Node2D
Main
scene with KinematicBody2D
Player
and Node2D
MapContainer
MapContainer
contains the first Map
Player
handles own position, using move_and_collide
to detect
collisions, and move_and_slide
to complete moves after initial
collisions.Player
gets collision Node
and determines if it is a Door
Door
is a subclass of KinematicBody2D
with the exported properties
DestinationScenePath : String
and ExitID : int
.Player
emits a signal with DestinationScenePath
and ExitID
Main
listens for collision signal from Player
and, once received,
manually swaps out the old map in MapContainer
with the loaded
DestinationScenePath
…exit
group and
finds a node in that group with an ExitID
property that matches
the one received in the signal. If found, move Player
to
that Node2D#position
.Map
and Map2
have nodes in this order:TileMap
Walkable
Door
s with appropriately set DestinationScenePath
and ExitID
TileMap
Walls
with tiles with Collision
enabledExitNode
s, each in the exit
group and with an ExitID
that matches
the Door
from the other scene.KinematicBody2D#_physics_process
to sync code with
physics engine!Vector2D
motion
object:StaticBody2D
doesn't move but it interacts with physicsCanvasItem#Modulate
can hack in a color on a thing.is_on_floor()
and move_and_slide(velocity, floor_vector)
Vector2(0, -1)
is an up vector, which makes floors work.velocity = move_and_slide(velocity)
to set it to “remaining motion”I'm now thinking the game will be a walking simulator with QTE. No items, no stats, no tight action stuff, just dialog choices, exploration, and QTE. Should be pretty straightforward.
This doesn't take into account transforms on the Map
or the MapContainer
.
python
func get_map_max_size():
var maxSize = Rect2(0,0,0,0)
for node in $MapContainer/Map.get_children():
# Object#is_class is like Ruby Object#is_a?.
# if I used instances in the tree whose classes subclassed TileMap,
# is_class will return true for those instances.
if node.is_class("TileMap"):
var mapSize = Transform2D(
Vector2(node.cell_size.x, 0),
Vector2(0, node.cell_size.y),
Vector2()
) * node.get_used_rect().size
maxSize = maxSize.expand(Vector2(mapSize.x, mapSize.y))
return maxSize
KinematicBody2D
for Player
)Player
canMove
and canInteract
canMove = false
in dialogue boxescanInteract != false
if colliding with Area2D
Item
contains Area2D
for interaction, and StaticBody2D
for collision_ready
, find all in group Interactible
and connect to Player
body_enter
and body_exit
_input
event for capturing inputArray
Player#inventory
File
to read JSON files: https://www.reddit.com/r/godot/comments/7wj5p3/need_help_reading_data_from_a_json_file/Object
/Array
traversal is JavaScript-likeunittest
-stylePlayer
picks up signal and resets its Camera2D
limit_?
properties
from the provided Rect2
.Transition
with a ColorRect
that is blackColorRect#self_modulate#a
to change alphaMain
and Transition
AnimationPlayer
AnimationPlayer
with ColorRect
as a childColorRect#self_modulate
Main
halfway through to continue the loadingres://
directory with Directory
instance and cursor get_next()
CanvasLayer
overtop of everything where GUIs live, otherwise
they'll be affected by the viewport/Camera
.Containers
and their children reflow automatically, but I
found I had to reset settings a lot in child containers after adjusting
parent containers to get them “right”.ColorRect
to take up the whole container's background is a challenge.MarginContainer
Custom Constants is where you're setting the interior margins
of the box! This is the setting you want to make a MarginContainer
do what
you expect it to do!(H|V)BoxContainers
with the Size Flag set to Expand
to create spacers.DialogueBox
inside a VBoxContainer
, I call it Aligner
.global_position.y < $Camera.get_camera_screen_center().y
.Player
during _process
, I use atTopOfScreen
.VBoxContainer
's alignment
and margin_bottom
to position the DialogBox
:
python
if $Player.atTopOfScreen:
$GUI/Aligner.alignment = VBoxContainer.ALIGN_END
$GUI/Aligner.margin_bottom = -100 # height of container
else:
$GUI/Aligner.alignment = VBoxContainer.ALIGN_BEGIN
$GUI/Aligner.margin_bottom = 0
Input.is_action_just_released
and
!Input.is_action_pressed
with ui_accept
to find out if
interaction should continue. Rough pseudocode logic:
python
var stopInteracting = false
# only continue if everything is ok
if isInteracting and isAllowedToContinueInteracting and Input.is_action_just_released("ui_accept"):
emit_signal("continue_interaction", "next")
isAllowedToContinueInteracting = false
# have to release the key to continue
if !Input.is_action_pressed("ui_accept"):
isAllowedToContinueInteracting = true
if canMove && !isInteracting:
if interactTarget && Input.is_action_just_released("ui_accept"):
isInteracting = true
emit_signal("start_interaction", self)
isAllowedToContinueInteracting = false
if stopInteracting:
isInteracting = false
stopInteracting = false
This gets a little weird with a menu system, though, as I want the DialogBox
to control input. I may move Player
and DialogBox
Input
handling up to
Main
and send it down to the appropriate objects based on game state.
setget
doesn't fire setters and getters
if you are setting or getting the value in the same script, so this does not fire the setter:
python
# CoolCat.gd
var cat setget set_cat
func set_cat(newCat):
cat = newCat
do_cat_things()
func _process():
if somethingHappens:
cat = "meow" # do_cat_things is not called.
new
in a static
method:
python
class_name Kitten
static func forCharacter(char):
var kitten = new()
kitten.setupCharacter(char)
return kitten
Instead, load
the file from res://
and run new
from that. This is to
get around multithreading issues it seems:
python
class_name Kitten
static func forCharacter(char):
var kitten = load('res://classes/Kittens.gd').new()
kitten.setupCharacter(char)
return kitten
if
blocks for movement:
python
const MOVES = [
["ui_down", "y", WALK_SPEED],
["ui_up", "y", -WALK_SPEED],
["ui_left", "x", -WALK_SPEED],
["ui_right", "x", +WALK_SPEED],
]
func _process(delta):
var vector = Vector2(0,0)
for move in MOVES:
if Input.is_action_pressed(move[0]):
vector[move[1]] += move[2]
Krita is my art tool of choice anyway, so let's see what the Internets have to say about using it for stills and animations:
w
) for tiling backgroundsI made a pixel art in Krita:
Then I put him in my game:
Then I walked behind him:
Oops.
My structure of the game looks like this, because I thought
having Player
outside of Map
and not repeated in all the
maps would make things easier:
But it should look like this, and I'll have to do more
management of Player
within maps when maps change:
My idea of centralizing all player input to Main
and
pushing down movement events to Player
may be the direction
I go for restructuring this…stay tuned.
Success! I got my player to walk behind the rabbit by
placing both in a YSort
:
But I have to make sure everything map-related goes
into the YSort
. My Exit Nodes were outside the YSort
,
so when I warped back into the larger first Map,
I ended up in a weird spot. Also, moving all of the
input logic to Main
and pushing down events to
Map
and such is the way to go.
Next is making my TileMap
s be the kind where the
player can walk “behind” them.
I got the game to export to Android, as I already had the Android environment installed for React Native and NativeScript-Vue compilation:
I hooked up a USB controller via an OTG cable and got the game to react to inputs, but, as expected, the YAML data didn't work, so I'll need to hook up a preflight script to covert YAML to JSON and use the JSON loader on Android, or just use JSON everywhere and run a watcher to convert YAML on-the-fly while I work.