Let's learn Godot together

Make games! Discuss those games here.

Moderators: Bob the Hamster, marionline, SDHawk

User avatar
Mogri
Super Slime
Posts: 4672
Joined: Mon Oct 15, 2007 6:38 pm
Location: Austin, TX
Contact:

Let's learn Godot together

Post by Mogri »

I know, I'm late to the party here. Several of you have already used Godot, and most of the rest of you probably don't care. But today is the day I start learning Godot, and you're welcome to join me.

I'm probably not going to go through the whole process of making a game here. I have a few goals I want to achieve here. If you've got some, too, share yours, too. This is what I want to learn how to do:
  • UI stuff. Menus, dialogue, and so on. If you haven't done much outside of engines like the OHRRPGCE, you probably can't appreciate how much of a pain it is to do UI stuff. A good UI is necessary for every game ever, and this is especially true for menu-driven games like RPGs. Modern platforms have only made UI worse, since you can no longer make assumptions about the user's aspect ratio. I've been directed to this page as a resource.
  • Support for various control schemes. Keyboard, controller, mouse, whatever. I'd like it to be fairly painless to support a variety of input methods.
  • Mod support. Including localization. The asset library has a "Mod.io for Godot" extension, which sounds nice. Has anyone here used mod.io? I'm totally unfamiliar with it.
  • Data-driven design. Mod support is pretty useless if you've hardcoded your entire game.
  • Save/load. How hard is it to convert the current game state to/from a file? The community has several options available.

Let's get started

Use the "Download Latest" link on https://godotengine.org/. There's no installer, so put these files in your workspace directory. Create a subdirectory named TestProject. Launch Godot and create a new project in your TestProject directory in Compatibility mode.

Whoops! You're probably looking at a very confusing screen now. I sure am. Let's start going through the Godot tutorials, beginning with Step by Step. If you find that you're already getting lost, it may help to step back to Introduction, which defines some terms.

If you're reading along, you should quickly get to Creating your first scene, which shows the confusing screen we were looking at. It tells you to create a Label node, give it text, and then run. Hey presto! A few steps later, we've already launched our very first game. It's not interactive, but baby steps, you know? This is roughly the equivalent of filling in the first walkabout sprite and hitting "Run test game" on a fresh OHR project.

Next: the Instancing tutorial. I found that Godot froze for a few seconds when importing the project for this. I also found that while the "Main" scene did open automatically, it opened in 3D, and I had to click to 2D to get back to what the tutorial showed. This might be because I closed Godot between projects.

The tutorial instructs you to open ball.tscn and change the "Bounce" property. You have to open ball.tscn to do this: while the toolbar shows from the Main scene, you can't edit the PhysicsMaterial properties of the ball from there. (If you skipped the introduction, as I did, you may be surprised to learn that the ball is a scene. The introduction says, "A scene can be a character, a weapon, a menu in the user interface, a single house, an entire level, or anything you can think of.") The tutorial will then explain that PhysicsMaterial is a resource, and you can set direct properties of an instanced scene, but resources are shared between instances.

The next tutorial page, Scripting languages, does not contain any follow-along tutorial steps. Instead, it explains what your options are for scripting. Yes, there's no way around it: you will have to do some scripting to create a game in Godot. For the purposes of this thread, we'll stick to GDScript. It's not until the following page that we have any more tutorialing to do, so let's go there.

The instructions tell you to create a new project, but TestProject is fresh enough. Let's just jump back there and delete our Label node. The "Hello, world!" debug text from our _init function appears in the Godot engine window. After that, we're instructed to create a _process function. This is a fairly common paradigm in game engines: because the framerate is variable, and you want to maintain stable gameplay at any framerate, you have to be able to do your thing no matter how much time has elapsed since the last frame. In our _process function, that time lapse is the delta. In theory, you could remove the "* delta" from this line and see the rotation vary with the framerate, but in practice, the framerate is stable enough that you won't notice. (Also, the tutorial rotation is 180 degrees per second, and if we don't multiply by the delta, we're just rotating 180 degrees on each frame, which isn't going to do much for you visually.)

If you didn't reposition the sprite, it's going to be mostly offscreen. You can get back to the visual scene by clicking on "2D" at the top of the engine window. You can jump back to the script with the "Script" button. When adding the velocity stuff to your function, make sure it's indented like the rotation line.

We're almost done for today. Two more sections. First, Listening to player input. This is simple enough as long as you remember to put everything in the _process function by giving it the proper indentation. If you're just getting the hang of things, you can try making the sprite move backwards when you press down.

Finally, Using signals. This has you wire up a button to trigger set_process(not is_processing()) on your sprite. It took me a second to figure out what this meant. It's saying "set this object's 'should I run my _process function' property to the opposite of whatever it is now."

Godot is smart enough to let you access your new button via mouse or keyboard, which is encouraging. I can tell already that signals are going to be really important to getting a game of any complexity to work.

But that's good enough for today. Next time, I'm going to run through Your first 2D game.

If this thread is of any interest to you, please let me know! I'm not going to keep this going if nobody's reading. If you followed along -- especially if you ran into any trouble following along -- please let me know that as well.
User avatar
Bob the Hamster
Lord of the Slimes
Posts: 7662
Joined: Tue Oct 16, 2007 2:34 pm
Location: Hamster Republic (Ontario Enclave)
Contact:

Re: Let's learn Godot together

Post by Bob the Hamster »

This thread is very interesting to me and I am reading it!
User avatar
MorpheusKitami
Slime Knight
Posts: 218
Joined: Wed Nov 30, 2016 8:23 pm
Location: Somewhere in America

Re: Let's learn Godot together

Post by MorpheusKitami »

I've actually been meaning to try out Godot, less for the 2D stuff and more for the 3D stuff. Still interested though.
User avatar
Mogri
Super Slime
Posts: 4672
Joined: Mon Oct 15, 2007 6:38 pm
Location: Austin, TX
Contact:

Re: Let's learn Godot together

Post by Mogri »

I may dabble in the 3D a bit: it'd make doing a first-person dungeon crawler a lot easier. But the "Your first 3D game" tutorial assumes you've done the 2D tutorial anyway, so that will come first either way.
User avatar
Mogri
Super Slime
Posts: 4672
Joined: Mon Oct 15, 2007 6:38 pm
Location: Austin, TX
Contact:

Re: Let's learn Godot together

Post by Mogri »

Your first 2D game

Here's what I notice in the setup: you can specify the initial dimensions of the game window and force the game to stay in that aspect ratio. That's pretty nice. There are a lot of options here to get things to render just how you like.

For the player, we're instructed to create an "Area2D" with an "AnimatedSprite2D" child. "Area2D" says it provides collision, while "AnimatedSprite2D" will do the display. It's interesting that these are separate.

I did not find the next step especially intuitive. "Sprite Frames" is found in the right pane's Inspector tab under "Animation." Selecting "New SpriteFrames" immediately creates one, but you have to click again to bring up the editor. I'm not sure I'd have figured that out on my own.

Oh! Now, we have to add a CollisionShape2D to our Player. If Area2D is for collision, then why is CollisionShape2D a separate node? Hopefully, this becomes clear by the time we're done.


Next page: Coding the player. We're sticking to GDScript here, but I notice that there's a warning to C# users that you have to rebuild every time you add a new export. Yuck.

We're introduced to a more robust method of input handling than we got in the introductory tutorial. You may wonder, "Why do I have to define my own move_right? Why isn't that just built in?" Well, good news: it is. If you click "Show Built-in Actions" in the Input Map, you can scroll down a bit to find ui_right/left/up/down, which natively support keyboard and controller inputs. You can totally just use these instead of creating your own by changing the script to e.g. if Input.is_action_pressed("ui_right") instead of move_right.


Creating the enemy: I recently learned that "Mob" is short for "Mobile Object," originating from an Everquest precursor called DikuMUD.

Anyway, this one is a RigidBody2D instead of an Area2D. What's the difference? I'll dig into it if the tutorial doesn't end up covering it.

The tutorial says, "Set up the AnimatedSprite2D like you did for the player." It only later mentions that you should set the scale to 0.75.

Also a bit unclear: queue_free() deletes an instance of the Mob. For any built-in function, you can Ctrl+Click to get an explanation of what it does. This doesn't help much with discovery, but it's nice while going through the tutorial.

Oddly, we create an animation for our Mob but not any movement behavior. They're saving that for the next step for some reason.


So let's go there. The main game scene tells us to do a lot of things without saying how or why. The "Position" property of Marker2D is found under Node2D > Transform in the inspector.

Near the end, we discover why we never gave Mob any behavior: we're doing it in the Main script. I think this is a bad idea! Godot is very vocal about how it follows the principles of Object-Oriented Programming, and cramming Mob behavior into the Main script breaks one of its core tenets, Encapsulation: loosely, it's the idea that the behavior of a thing should be contained within that thing. So let's go through a quick exercise to fix that.

Player already has a start(pos) function. Let's create something similar for Mob:

Code: Select all

func spawn(pos, direction):
	position = pos
	direction += randf_range(-PI / 4, PI / 4)
	rotation = direction

	# Choose the velocity for the mob.
	var velocity = Vector2(randf_range(150.0, 250.0), 0.0)
	linear_velocity = velocity.rotated(direction)
We've copied in some of the behavior that's currently in _on_mob_timer_timeout, so let's modify that to incorporate the new spawn function.

Code: Select all

func _on_mob_timer_timeout():
	# Create a new instance of the Mob scene.
	var mob = mob_scene.instantiate()

	# Choose a random location on Path2D.
	var mob_spawn_location = get_node("MobPath/MobSpawnLocation")
	mob_spawn_location.progress_ratio = randf()

	# Set the mob's direction perpendicular to the path direction.
	var direction = mob_spawn_location.rotation + PI / 2

	mob.spawn(mob_spawn_location.position, direction)

	# Spawn the mob by adding it to the Main scene.
	add_child(mob)
If you test your game before and after these changes, it should behave the same. ("Why bother if nothing's changed?" you ask. It doesn't make much difference for a project of this scale, but once a project gets too big to hold it all in your head at once, it's really important to organize things in a logical manner.)


Next page: Heads up display. Our first taste of UI!

This page tells us to do a bunch of theme overrides on various components. We could do that, or we could just use an actual theme. To do that, open up ScoreLabel's Theme instead of Theme Overrides, set the theme to "New Theme," and set the Default Font and Default Font Size as indicated in the tutorial. Click "Save As..." in the bottom Theme pane and save it as theme.tres. Then, go into Theme in Message and StartButton and load theme.tres.

We can make one more improvement on the tutorial. If you make it to the end of this page and try out your game, you'll notice that the Player starts mostly offscreen, peeking out of the top left corner. We can add $Player.hide() to _ready in the Main script and $Player.show() to new_game.

There are a lot of little steps in this very simple HUD. If you're wondering why I made a big deal about UI, consider how the tutorial would look if you had an actual menu here!


Finishing up now. I don't have too much to add here except that it tells you to wire up StartButton to the Enter key right after giving you instructions on adding controller support. You should probably make a way for the controller to hit the button too, yeah?


Debrief

Now that I've gotten my feet wet with the example game, here are my (relatively minor) gripes so far:
  • Node names are semantic. If I rename "HUD" to "ScoreHUD" or whatever, I need to replace all instances of "$HUD" as well. Do I have a great solution for this? No, not necessarily. I think the IDE should handle the renaming, but that still leaves you up a creek if you decide HUD should be a child of some other scene. Even that isn't too bad: you just need to declare var my_hud = get_node("SomeNode/HUD") and use that.
  • The tutorial teaches you bad practices. It'd be one thing if it went back and said, "Here's how we should have done it." The tutorial is all open-source -- I mean, all of Godot is open-source -- and I don't feel strongly enough about this to change it myself. But then, I also don't feel knowledgeable enough to change it myself.
  • The tutorial leaves out a lot. We've only got "Your first 3D game" left before we're left to sink or swim. Are you swim-ready? Personally, I don't feel like I've been given the tools to succeed. I'm going to have to do a lot of experimentation, that's for sure. This is heavily mitigated by the amount of community content available to browse.
Now, in fairness, the very first article in the very next section is Best practices, but the information there is way more in the weeds than you're ready for. (I am assuming that "Your first 3D game" is not incredibly more thorough than this one. I'm prepared to eat my words if I'm wrong about that.)
User avatar
MorpheusKitami
Slime Knight
Posts: 218
Joined: Wed Nov 30, 2016 8:23 pm
Location: Somewhere in America

Re: Let's learn Godot together

Post by MorpheusKitami »

I got as far as the signals section before I apparently got into a bizarre glitch, not a good sign. When it tries to use the Get Node to link to the timer it just won't register a link. I don't see how I could do something wrong. It has the name, Timer, it's supposed to, as does the script. I even copy/pasted the code directly from the manual and no dice. I try fiddling around with various file paths*, which I shouldn't need to since it's a child node, but that either breaks the script or causes no changes. This wasn't that hard for you, was it?
*both with the seeming option to do a path:node/placement/in/tree thanks to the autocomplete and because the debug menu mentions Root/Sprite2D I figured that in quotes might be another option.
I'm getting a bad feeling about this. It's kind of a bad sign if the tutorial screws with me in a way that a rank amateur at the engine shouldn't have to cope with just yet.
User avatar
Mogri
Super Slime
Posts: 4672
Joined: Mon Oct 15, 2007 6:38 pm
Location: Austin, TX
Contact:

Re: Let's learn Godot together

Post by Mogri »

No, I didn't have any such issue. Which step is that? Do you mean the get_node function in GDScript or something else?
User avatar
Mogri
Super Slime
Posts: 4672
Joined: Mon Oct 15, 2007 6:38 pm
Location: Austin, TX
Contact:

Re: Let's learn Godot together

Post by Mogri »

Bonus: Baby's first menu

I started up a new project! After far longer than I care to admit, I created a basic menu:

Image

In the process, I learned a lot of things! Here are several of them:
  • You're not supposed to use ui_down and so on for your game controls; they're reserved for the UI.
  • In order to be navigable by keyboard and mouse, buttons (and other controls) need to be descendants of a Control node. Even then, you'll need to manually set focus on one of them. In this case, that looks like $Control/Menu/LoadGameButton.grab_focus().
  • You can override the neighbor order for a given control, but the built-in behavior works really well, so you shouldn't need to. I thought you needed to assign them manually until I figured out the previous bullet point.
  • The quit button -- the only working button in my menu -- is wired up to get_tree().quit().
  • Styling the buttons wasn't too hard. See this page. I don't know how you're meant to create a duplicate of a box style, but if you click to edit an existing style and "Save As" a different one, all references to the original will point to the new file, which is... certainly a design choice that they made.
  • Getting the buttons lined up was oddly difficult. You can line them up by hand if you don't care about them being spaced perfectly, but that's tedious and unsatisfying. Here's the actual solution:
1. Create a VBoxContainer.
If you want them spaced horizontally, use HBoxContainer instead. If you're putting buttons in it, you want this node to be a child of a Control node, as mentioned above (although I don't know why Godot wants this, since BoxContainer already extends Control). If you want a grid, then you're going to need a VBoxContainer full of HBoxContainers or an HBoxContainer full of VBoxContainers. Or maybe there's a better component for grids, I dunno.

2. Add the buttons to the BoxContainer.
Children of a BoxContainer are automatically positioned within the container. But wait! They're all crammed to one side! What do we do?

3. ?????
As it happens, this is really unintuitive. You can either go into the BoxContainer's properties under Control > Theme > Theme Overrides > Constants and set a Separation value, or you can put Control nodes between your Button nodes and enable their Layout > Container Sizing > Expand property. I went with the latter approach, which has the advantage of automatically resizing. If you want a specific distance between your buttons instead of justifying the contents of the container, then Separation is the way to go.

4. Profit!
This is the sort of thing that I'd write a library around in the future. You can dynamically generate all of these nodes instead of laying everything out by hand, and while that takes more time, it's reusable. Someone else has almost certainly done this already, so you could instead trawl the asset library for something that mostly meets your needs.


As it turns out, I went through the entire asset library (or at least the subset that's compatible with 4.0) just recently! I found like five different libraries for handling dialogue, so you can look forward to my analysis of their pros and cons in a future update.
User avatar
MorpheusKitami
Slime Knight
Posts: 218
Joined: Wed Nov 30, 2016 8:23 pm
Location: Somewhere in America

Re: Let's learn Godot together

Post by MorpheusKitami »

Mogri wrote: Fri Oct 20, 2023 2:33 pm No, I didn't have any such issue. Which step is that? Do you mean the get_node function in GDScript or something else?
Yeah, for some reason that get_node("Timer") isn't working no matter what I try to do. It doesn't seem to be linking up properly, it just spits out an error saying it can't find it relative to the source node.
From what I understand, the get_node should automatically work for child nodes, if not, it should work if I put in the path to it, yet, nothing seems to fix it. If I try to give it the absolute path, it still spits out an error; If I try to write in parentheses path: to/node without quotes, the code fails, with quotes it just spits out the same error as before. It's weird.
User avatar
Mogri
Super Slime
Posts: 4672
Joined: Mon Oct 15, 2007 6:38 pm
Location: Austin, TX
Contact:

Re: Let's learn Godot together

Post by Mogri »

I don't see get_node("Timer") anywhere within the tutorial. (By the way, $ is shorthand for get_node, so $Timer would do the same thing.)

Where are you calling this from? Does the node for this script have a child named Timer?
User avatar
MorpheusKitami
Slime Knight
Posts: 218
Joined: Wed Nov 30, 2016 8:23 pm
Location: Somewhere in America

Re: Let's learn Godot together

Post by MorpheusKitami »

Mogri wrote: Sat Oct 21, 2023 8:54 pm I don't see get_node("Timer") anywhere within the tutorial. (By the way, $ is shorthand for get_node, so $Timer would do the same thing.)

Where are you calling this from? Does the node for this script have a child named Timer?
This bit here in the using signals tutorial, the Node.get_node() bit. It is set up exactly the same way as the complete script further down, and Timer is a child of the sprite so it should work like that. I did try $Timer, but that produced the same error. The only thing I can think of is that I failed to do something on the timer's end to make it connect to the script, but the tutorial doesn't say I have to do anything on the timer's end beyond making it a child.
User avatar
Mogri
Super Slime
Posts: 4672
Joined: Mon Oct 15, 2007 6:38 pm
Location: Austin, TX
Contact:

Re: Let's learn Godot together

Post by Mogri »

And your script is attached to the Sprite2D, right? Not the root Node2D?
User avatar
MorpheusKitami
Slime Knight
Posts: 218
Joined: Wed Nov 30, 2016 8:23 pm
Location: Somewhere in America

Re: Let's learn Godot together

Post by MorpheusKitami »

Mogri wrote: Sun Oct 22, 2023 3:38 pm And your script is attached to the Sprite2D, right? Not the root Node2D?
Yes, it's the child of Sprite 2D. There should be an image in this post of how it's set up.
Attachments
godot.png
godot.png (182.96 KiB) Viewed 651 times
User avatar
Mogri
Super Slime
Posts: 4672
Joined: Mon Oct 15, 2007 6:38 pm
Location: Austin, TX
Contact:

Re: Let's learn Godot together

Post by Mogri »

OK, that all looks correct. What's the exact error message that you're seeing? Does it happen when you try to run the scene or at some other time?

I'm happy to take a look at it if you want to upload the project, too.
User avatar
MorpheusKitami
Slime Knight
Posts: 218
Joined: Wed Nov 30, 2016 8:23 pm
Location: Somewhere in America

Re: Let's learn Godot together

Post by MorpheusKitami »

I'll include the zip anyway, but the error is:

Code: Select all

E 0:00:00:0580   sprite2d.gd:8 @ _ready(): Node not found: "Timer" (relative to "/root/Sprite2D").
  <C++ Error>    Method/function failed. Returning: nullptr
  <C++ Source>   scene/main/node.cpp:1626 @ get_node()
  <Stack Trace>  sprite2d.gd:8 @ _ready()
A link:
https://www.mediafire.com/file/3co24950 ... D.zip/file
(sorry for the Mediafire link, but you can't upload zip files in this part of the forum)
Post Reply