# Godot learning I did these between 2019-07-28 and 2019-088-17. I never made that game. ## Part 1 I'm learning how to use the [Godot game engine](https://godotengine.org) 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: ### What do I (think I) need next? * `TileMap`/`Area2D` collisions for making the character walk around and get stopped by things, and know what I ran into. * A map that's bigger than the screen that follows the character around. ### What have I found? * Started with [Your First Game](https://godot.readthedocs.io/en/stable/getting_started/step_by_step/your_first_game.html) to get a handle on editor in general * GDScript is very Python-y, which is not my strongest language, but I'm getting the hang of it * I wish Godot had vim keyboard shortcut support * Learned [TileMap and TileSet](https://godot.readthedocs.io/en/stable/tutorials/2d/using_tilemaps.html) because I plan on using tiles to make maps * Still need to figure out how to get things to run into tiles * Walked through Kinematic Character 2D * The [official docs](https://godot.readthedocs.io/en/stable/tutorials/physics/kinematic_character_2d.html) * [Using KinematicBody2D](http://kidscancode.org/blog/2018/02/godot3_kinematic2d/) on KidsCanCode * Still think I'm missing something with this: * Can't quite get the character to walk/move as expected * Also this is more platformer-y than I need right now * Watching [Top-down RPG/Adventure in Godot](https://www.youtube.com/watch?v=ZmSyLc5KDxo) * [i18n in Godot](https://docs.godotengine.org/en/3.1/tutorials/i18n/internationalizing_games.html) * Looks like gettext * Switch scenes via "NPC" with a Screen Change custom event * `export var` to select target scene * `export var` for warp target in other scene * Remove old scene from DOM * `get_tree().root.get_child()`/`.add_child()` * `Scene#queue_free` to safely remove from memory! * Add new scene to DOM * Find player in new scene * Move to position of warp target * Put nodes into a group called "exit" to focus the search * Dialog box * Move it out of the way if the character is there * The scene will need a camera * Find the [Camera2D](https://docs.godotengine.org/en/3.1/classes/class_camera2d.html) * Only one camera in a scene, pick one using `Camera2D#make_current()` * Put the camera in the thing that needs to be tracked * Drag margins require tracked object to hit edges before moving * Make the camera current, or nothing will move * Clamp the camera scroll to the size of screen with `Camera2D#limit_?`? * Grab the dimensions of something that's as big as the scene * Can you get scene/child node offset size * Player move from scene to scene * Primarily to transfer player state * Video shows physically moving player node, since Player object contains state, but I'm thinking to have a Vuex/Redux like store external to visual scenes where such data can be stored and reconstituted * Moving nodes from Scene to Scene seems error-prone, more reason for external state * [Crystal](https://crystal-lang.org/) * Like Ruby meets C? * Using separate TileMaps * One for walkable areas, one for collisions, one for stuff outside the play area * [Signals](https://docs.godotengine.org/en/3.1/getting_started/step_by_step/signals.html) * Emit a signal on transport to recalculate camera clamping ### Solution to "RPG Map with tiles and doors" * `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`. * On collision, `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`... * ...then searches the new scene for nodes in the `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` * Any `Door`s with appropriately set `DestinationScenePath` and `ExitID` * `TileMap` `Walls` with tiles with `Collision` enabled * `ExitNode`s, each in the `exit` group and with an `ExitID` that matches the `Door` from the other scene. ## Part 2 ### Lunchtime video: Platform Game Tutorial * https://www.youtube.com/watch?v=wETY5_9kFtA * Grid setup & snapping in top/dot menu * Remember to use `KinematicBody2D#_physics_process` to sync code with physics engine! * The `Vector2D` `motion` object: * Having a drag on horizontal motion & capping horiz motion to have inertia? * `StaticBody2D` doesn't move but it interacts with physics * `CanvasItem#Modulate` can hack in a color on a thing. * Ensure you move extents of Collision shapes and leave the Transform alone. * `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" * If you've hit the ground, there's no more remaining velocity, so you don't have to stop adding gravity yourself. ### Game planning 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. ### Solution to "Pixel size of Scene for Camera2D clamping" 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 ``` ## Part 3 ### Lunchtime Video: Branching Dialogue and Dynamic Events * https://www.youtube.com/watch?v=aVutIoCGAfE&t=3s * Use Nodes to group objects rather than using top level Node type (`KinematicBody2D` for `Player`) * `Player` `canMove` and `canInteract` * `canMove = false` in dialogue boxes * `canInteract != false` if colliding with `Area2D` * `Item` contains `Area2D` for interaction, and `StaticBody2D` for collision * On load map `_ready`, find all in group `Interactible` and connect to `Player` `body_enter` and `body_exit` * Would need more refinement if 2+ interactible objects entered * Facing? Distance to center of all objects? * `_input` event for capturing input * `Array` `Player#inventory` * `File` to read JSON files: https://www.reddit.com/r/godot/comments/7wj5p3/need_help_reading_data_from_a_json_file/ * Inkle Writer is discontinued, superceded by ink: https://www.inklestudios.com/ink/web-tutorial/ * Inky is an editor that runs on Linux...kinda * Picking a data structure for: * Event tracking * Dialogue * Other game state * `Object`/`Array` traversal is JavaScript-like * Dialogue/routing/game state seems finicky enough that a testing framework would be worth using * Gut: https://github.com/bitwes/Gut * Python `unittest`-style * Issues with 3.1? * Build the dialog box via instancing new Classes and adding to a container ### Solution to "Clamp camera to max area of Map Scene" * Emit a signal on start/map change of calculated max scene area * `Player` picks up signal and resets its `Camera2D` `limit_?` properties from the provided `Rect2`. ### Solution to "Fade to black when changing rooms" * Made a `Transition` with a `ColorRect` that is black * Can use `ColorRect#self_modulate#a` to change alpha * First started with signals going back and forth between `Main` and `Transition` * Throw around timers? Coroutines/yields? * Reddit comment (https://www.reddit.com/r/godot/comments/8ourxn/godot_and_gdscript_the_wonders_of_yielding/) pointed me at `AnimationPlayer` * https://www.gamefromscratch.com/post/2015/06/07/Godot-Engine-Tutorial-Part-10-Animation.aspx * Created an `AnimationPlayer` with `ColorRect` as a child * Property transition for `ColorRect#self_modulate` * Call function transition to fire off an emit to `Main` halfway through to continue the loading * Store the map/exit ID from the door globally and grab them halfway through * Will have to disable Player movement on transition to prevent double triggering ## Part 4 ### Dialogue Boxes * https://www.youtube.com/watch?v=VeCrE-ge8xM * Separate node for running the logic of the dialogue box * Like Vue data/computed/methods separated from template, but more manual lifting * Player is a state machine based on input dialogue data * DB object to get assets * Can iterate over a `res://` directory with `Directory` instance and cursor `get_next()` * Can create [text Resources](https://docs.godotengine.org/en/3.1/getting_started/step_by_step/resources.html) as an alternative data storage vs. JSON/YAML * Using YAML: https://github.com/Beliaar/godot-yaml-asset * If we need JSON for native, switching out loaders should be easy * GUI containers: * Create a `CanvasLayer` overtop of everything where GUIs live, otherwise they'll be affected by the viewport/`Camera`. * Docs _say_ that `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". * Getting a `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! * Use `(H|V)BoxContainers` with the Size Flag set to `Expand` to create spacers. ### Solving "Move RPG dialogue box to other end of screen to not hide player" * Put the `DialogueBox` inside a `VBoxContainer`, I call it `Aligner`. * Player is above the centerline of the viewport if `global_position.y < $Camera.get_camera_screen_center().y`. * Set that on a property of `Player` during `_process`, I use `atTopOfScreen`. * Use the `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 ``` ## Part 5 ### Dialogue Boxes Part 2 * Use a combination of `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](https://godotengine.org/qa/37723/how-to-use-setget-keyword?show=37728#a37728) 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. ``` ## Part 6 ### Odds and Ends * If you want to make a factory class method, you can't use `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 ``` * I hate making up/down/left/right `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] ``` ### Making Pixel Art [Krita](https://krita.org) is my art tool of choice anyway, so let's see what the Internets have to say about using it for stills and animations: * https://www.youtube.com/watch?v=aaRzNTCanIQ * The newest video I could find * Modern Krita comes with a Pixel Art brush to align the pointer with the grid * Fill Bucket * Threshold 1, Grow Selection 0, Feathering 0 * Disable Antialiasing on selecton tools * Subwindows for new views * Transform * Nearest Neighbor * Use wraparound (`w`) for tiling backgrounds * https://www.youtube.com/watch?v=ebF1pIxLpnU * Use Alpha Lock to make shading easier * Emulate antialiasing to make transitions between colors smoother * I need to get better about mirroring the canvas to check symmetry... ## Part 7 ## YSort and 2d RPG games I made a pixel art in Krita: {{:old_blog_posts:rabbit_pixel_art.png?nolink&400|}} Then I put him in my game: {{:old_blog_posts:rabbit_in_game.png?nolink&400|}} Then I walked behind him: {{:old_blog_posts:ysort_needed.png?nolink&400|}} 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: * Main * Map * Rabbit * Player But it should look like this, and I'll have to do more management of `Player` within maps when maps change: * Main * Map * [YSort](https://docs.godotengine.org/en/3.1/classes/class_ysort.html) * Player * Rabbit 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. ## Part 8 ### YSort! Success! I got my player to walk behind the rabbit by placing both in a `YSort`: {{:old_blog_posts:ysort_in_place.png?nolink&400|}} 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. ### Android Export I got the game to export to Android, as I already had the Android environment installed for React Native and NativeScript-Vue compilation: {{:old_blog_posts:android_export.jpg?nolink&400|}} 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.