{"id":403,"date":"2024-09-29T15:09:24","date_gmt":"2024-09-29T15:09:24","guid":{"rendered":"https:\/\/johnnycarlos.com\/?p=403"},"modified":"2025-10-19T12:41:40","modified_gmt":"2025-10-19T12:41:40","slug":"think-like-a-speedrunner","status":"publish","type":"post","link":"https:\/\/johnnycarlos.com\/index.php\/2024\/09\/29\/think-like-a-speedrunner\/","title":{"rendered":"Think Like a Speedrunner"},"content":{"rendered":"\n<p>In pondering how to learn video game programming, I just came up with this concept:  Think Like a Speedrunner<\/p>\n\n\n\n<p>In short, learn a development task, and the repeat it.  Keep repeating it so often you can do it very quickly. The first attempt may take a very long time.  You&#8217;ll need to research it, there will be issues, you may need to reach out to the programming community for help, etc.  And after you get it all sorted out though, it still may not all fit inside your head.  So, do it again.   Repeat it so many times you can do it from scratch and without needing to look at any guides.<\/p>\n\n\n\n<p>So that&#8217;s what I&#8217;m going to do next with learning Godot.  I&#8217;m going to keep doing the tutorial from the documentation until I can do it like a speedrunner.<\/p>\n\n\n\n<p>I don&#8217;t recall exactly how long it took me to do the first time, but it was at at least four evenings&#8230;<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>Here are my notes from the second pass, a highly distilled version of the documented steps:<\/p>\n\n\n\n<p class=\"has-large-font-size\">PART 1: SETUP<\/p>\n\n\n\n<p><strong>Step1: Create a project<\/strong><\/p>\n\n\n\n<p><strong>Step 2:<\/strong> Project Settings -&gt; Window -&gt; Viewport Width\/Height -&gt; 480 x 720<\/p>\n\n\n\n<p><strong>Step 3: <\/strong>Project Settings -&gt; Windows -&gt; Mode = canvas items &amp;&amp; Aspect = keep<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"has-large-font-size\">PART 2: PLAYER SCENE<\/p>\n\n\n\n<p><strong>Step 1<\/strong>: Create a Player root node as Area2D and enable Group Select<\/p>\n\n\n\n<p><strong>Step 2:<\/strong> Create child node AnimatedSprite2D<\/p>\n\n\n\n<p><strong>Step 3:<\/strong> Sprite Frames -&gt; New SpriteFrames<\/p>\n\n\n\n<p><strong>Step 4:<\/strong> rename default animation to &#8220;walk&#8221; and a new one called &#8220;up&#8221;<\/p>\n\n\n\n<p><strong>Step 5:<\/strong> Drag in two images for each animation<\/p>\n\n\n\n<p><strong>Step 6:<\/strong> Player -&gt; AnimatedSprite2D -&gt; Node2D -&gt; Transform -&gt; Scale -&gt; 0.5, 0.5<\/p>\n\n\n\n<p><strong>Step 7:<\/strong> Create child node of Player -&gt; CollisionShape2D -&gt; CapsuleShape2D<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"has-large-font-size\">Part 3: CODING PLAYER<\/p>\n\n\n\n<p><strong>Step 1:<\/strong> Player -&gt; Attach Script<\/p>\n\n\n\n<p><strong>Step 2:<\/strong> Declare member variables:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>extends Area2D\n\n@export var speed = 400 # How fast the player will move (pixels\/sec).\nvar screen_size # Size of the game window.<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li>export allows us to set its value in the Inspector (if you change the value here, it will override the value written in the script)<\/li>\n<\/ul>\n\n\n\n<p><strong>Step 3:<\/strong> _ready<\/p>\n\n\n\n<p>_ready is called when a node enters the scene tree, which is a good time to find the size of the game window<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>func _ready():\n\tscreen_size = get_viewport_rect().size<\/code><\/pre>\n\n\n\n<p><strong>Step4:<\/strong> _process<\/p>\n\n\n\n<p>Use the&nbsp;<code>_process()<\/code>&nbsp;function to define what the player will do.&nbsp;<code>_process()<\/code>&nbsp;is called every frame, so we&#8217;ll use it to update elements of our game, which we expect will change often. For the player, we need to do the following:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Check for input.<\/li>\n\n\n\n<li>Move in the given direction.<\/li>\n\n\n\n<li>Play the appropriate animation.<\/li>\n<\/ul>\n\n\n\n<p><strong>Step 4a<\/strong>: Map Input Keys<\/p>\n\n\n\n<p>Project -&gt; Project Settings -&gt; Input Map -&gt; type &#8220;move_right&#8221; -&gt; click Add -&gt; + Add Event -&gt; &lt;press right arrow&gt;<\/p>\n\n\n\n<p>Repeat for left, up, down<\/p>\n\n\n\n<p><strong>Step 4b:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>func _process(delta):\n\tvar velocity = Vector2.ZERO # The player's movement vector.\n\tif Input.is_action_pressed(\"move_right\"):\n\t\tvelocity.x += 1\n\tif Input.is_action_pressed(\"move_left\"):\n\t\tvelocity.x -= 1\n\tif Input.is_action_pressed(\"move_down\"):\n\t\tvelocity.y += 1\n\tif Input.is_action_pressed(\"move_up\"):\n\t\tvelocity.y -= 1\n\n\tif velocity.length() &gt; 0:\n\t\tvelocity = velocity.normalized() * speed\n\t\t$AnimatedSprite2D.play()\n\telse:\n\t\t$AnimatedSprite2D.stop()<\/code><\/pre>\n\n\n\n<p>If you hold&nbsp;<code>right<\/code>&nbsp;and&nbsp;<code>down<\/code>&nbsp;at the same time, the resulting&nbsp;<code>velocity<\/code>&nbsp;vector will be&nbsp;<code>(1,&nbsp;1)<\/code>. In this case, since we&#8217;re adding a horizontal and a vertical movement, the player would move&nbsp;<em>faster<\/em>&nbsp;diagonally than if it just moved horizontally.  We can prevent that if we&nbsp;<em>normalize<\/em>&nbsp;the velocity, which means we set its&nbsp;<em>length<\/em>&nbsp;to&nbsp;<code>1<\/code>, then multiply by the desired speed. This means no more fast diagonal movement.<\/p>\n\n\n\n<p><code>$<\/code>&nbsp;returns the node at the relative path from the current node<\/p>\n\n\n\n<p><strong>Step 5:<\/strong><\/p>\n\n\n\n<p>Now that we have a movement direction, we can update the player&#8217;s position. We can also use&nbsp;<code>clamp()<\/code>&nbsp;to prevent it from leaving the screen.&nbsp;<em>Clamping<\/em>&nbsp;a value means restricting it to a given range. Add the following to the bottom of the&nbsp;<code>_process<\/code>&nbsp;function<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>position += velocity * delta\nposition = position.clamp(Vector2.ZERO, screen_size)\n<\/code><\/pre>\n\n\n\n<p>The&nbsp;delta&nbsp;parameter in the&nbsp;_process()&nbsp;function refers to the&nbsp;<em>frame length<\/em>&nbsp;&#8211; the amount of time that the previous frame took to complete. Using this value ensures that your movement will remain consistent even if the frame rate changes.<\/p>\n\n\n\n<p><strong>Step 6:<\/strong> Choosing which animation is playing based on player direction<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>if velocity.x != 0:\n\t$AnimatedSprite2D.animation = \"walk\"\n\t$AnimatedSprite2D.flip_v = false\n\t# See the note below about the following boolean assignment.\n\t$AnimatedSprite2D.flip_h = velocity.x &lt; 0\nelif velocity.y != 0:\n\t$AnimatedSprite2D.animation = \"up\"\n\t$AnimatedSprite2D.flip_v = velocity.y &gt; 0<\/code><\/pre>\n\n\n\n<p><strong>Step 7.<\/strong> When you&#8217;re sure the movement is working correctly, add this line to&nbsp;<code>_ready()<\/code>, so the player will be hidden when the game starts:  <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>hide()<\/code><\/pre>\n\n\n\n<p><strong>Step 8<\/strong>. Preparing for collisions<\/p>\n\n\n\n<p>We want&nbsp;<code>Player<\/code>&nbsp;to detect when it&#8217;s hit by an enemy, but we haven&#8217;t made any enemies yet! That&#8217;s OK, because we&#8217;re going to use Godot&#8217;s&nbsp;<em>signal<\/em>&nbsp;functionality to make it work.  Add the following at the top of the script. If you&#8217;re using GDScript, add it after&nbsp;<code>extends&nbsp;Area2D<\/code>:  <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>signal hit<\/code><\/pre>\n\n\n\n<p>This defines a custom signal called &#8220;hit&#8221; that we will have our player emit (send out) when it collides with an enemy. We will use&nbsp;<code>Area2D<\/code>&nbsp;to detect the collision. Select the&nbsp;<code>Player<\/code>&nbsp;node and click the &#8220;Node&#8221; tab next to the Inspector tab to see the list of signals the player can emit.<\/p>\n\n\n\n<p>Notice our custom &#8220;hit&#8221; signal is there as well! Since our enemies are going to be&nbsp;<code>RigidBody2D<\/code>&nbsp;nodes, we want the&nbsp;<code>body_entered(body:&nbsp;Node2D)<\/code>&nbsp;signal. This signal will be emitted when a body contacts the player. Click &#8220;Connect..&#8221; and the &#8220;Connect a Signal&#8221; window appears.<\/p>\n\n\n\n<p>Next add this code to the func:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>func _on_body_entered(body):\n\thide() # Player disappears after being hit.\n\thit.emit()\n\t# Must be deferred as we can't change physics properties on a physics callback.\n\t$CollisionShape2D.set_deferred(\"disabled\", true)<\/code><\/pre>\n\n\n\n<p>Each time an enemy hits the player, the signal is going to be emitted. We need to disable the player&#8217;s collision so that we don&#8217;t trigger the&nbsp;<code>hit<\/code>&nbsp;signal more than once.<\/p>\n\n\n\n<p><strong>Step 9:<\/strong>  Reset player when starting a new game<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>func start(pos):\n\tposition = pos\n\tshow()\n\t$CollisionShape2D.disabled = false<\/code><\/pre>\n\n\n\n<p class=\"has-large-font-size\">PART 4: CREATING THE ENEMY<\/p>\n\n\n\n<p><strong>Step 1:<\/strong> Create a Mob scene as RigidBody2D and enabled Group Select<\/p>\n\n\n\n<p><strong>Step 2<\/strong>:  Add children: AnimatedSprite2D, CollisionShape2D, VisibleOnScreenNotifier2D<\/p>\n\n\n\n<p><strong>Step 3<\/strong>: Select the&nbsp;<code>Mob<\/code>&nbsp;node and set its&nbsp;<code>Gravity&nbsp;Scale<\/code>&nbsp;property in the&nbsp;<a href=\"https:\/\/docs.godotengine.org\/en\/stable\/classes\/class_rigidbody2d.html#class-rigidbody2d\">RigidBody2D<\/a>&nbsp;section of the inspector to&nbsp;<code>0<\/code>. This will prevent the mob from falling downwards.<\/p>\n\n\n\n<p><strong>Step 4<\/strong>:  Under CollisionObject2D section, expand the Collision group and uncheck the 1 inside the Mask property. This will ensure the mobs do not collide with each other.<\/p>\n\n\n\n<p><strong>Step 5<\/strong>: Set up the AnimatedSprite2D with 3 animations: fly, swim, and walk.<\/p>\n\n\n\n<p><strong>Step 6<\/strong>: Set the AnimatedSprite2D&#8217;s Scale property to (0.75, 0.75).<\/p>\n\n\n\n<p><strong>Step 7<\/strong>: Add a CapsuleShape2D for the collision.<\/p>\n\n\n\n<p><strong>Step 8<\/strong>: Add a script to Mob:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>extends RigidBody2D\n\nfunc _ready():\n\tvar mob_types = $AnimatedSprite2D.sprite_frames.get_animation_names()\n\t$AnimatedSprite2D.play(mob_types&#91;randi() % mob_types.size()])\n\nfunc _on_visible_on_screen_notifier_2d_screen_exited():\n\tqueue_free()<\/code><\/pre>\n\n\n\n<p class=\"has-large-font-size\">Part 5:  Main Game Scene<\/p>\n\n\n\n<p><strong>Step 1<\/strong>: Create a new scene and add a Node named Main. (The reason we are using Node instead of Node2D is because this node will be a container for handling game logic. It does not require 2D functionality itself.)<\/p>\n\n\n\n<p><strong>Step 2<\/strong>:  Click the&nbsp;<strong>Instance<\/strong>&nbsp;button (represented by a chain link icon) and select your saved&nbsp;<code>player.tscn<\/code>.<\/p>\n\n\n\n<p><strong>Step 3<\/strong>: Add the following nodes as a childred of Main:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Timer -&gt; MobTimer  (Wait Time -&gt; 0.5)<\/li>\n\n\n\n<li>Timer -&gt; ScoreTimer (Wait Time -&gt; 1)<\/li>\n\n\n\n<li>Timer -&gt; StartTimer (Wait Time -&gt; 2) and (One Shot -&gt; &#8220;On&#8221;)<\/li>\n\n\n\n<li>Marker2D -&gt; StartPosition  (Position -&gt; 240, 450)<\/li>\n<\/ul>\n\n\n\n<p><strong>Step 4<\/strong>:  The Main node will be spawning new mobs, and we want them to appear at a random location on the edge of the screen. Add a Path2D node named MobPath as a child of Main<\/p>\n\n\n\n<p><strong>Step 5<\/strong>: Select the middle one (&#8220;Add Point&#8221;) and draw the path by clicking to add the points. To have the points snap to the grid, make sure &#8220;Use Grid Snap&#8221; and &#8220;Use Smart Snap&#8221; are both selected. These options can be found to the left of the &#8220;Lock&#8221; button, appearing as a magnet next to some dots and intersecting lines, respectively.<\/p>\n\n\n\n<p><strong>Step 6<\/strong>: After placing point 4, click the &#8220;Close Curve&#8221; button.<\/p>\n\n\n\n<p>Add a PathFollow2D node as a child of MobPath and name it MobSpawnLocation. This node will automatically rotate and follow the path as it moves, so we can use it to select a random position and direction along the path.<\/p>\n\n\n\n<p><strong>Step 7<\/strong>: Add a script to Main:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>extends Node\n\n@export var mob_scene: PackedScene\nvar score<\/code><\/pre>\n\n\n\n<p>You should now have a mob_scene property in the Inspector and you want to add a value to it either:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Drag\u00a0<code>mob.tscn<\/code>\u00a0from the &#8220;FileSystem&#8221; dock and drop it in the\u00a0<strong>Mob Scene<\/strong>\u00a0property.<\/li>\n\n\n\n<li>Click the down arrow next to &#8220;[empty]&#8221; and choose &#8220;Load&#8221;. Select\u00a0<code>mob.tscn<\/code>.<\/li>\n<\/ul>\n\n\n\n<p>Double-click the Hit signal in Player and Connect.  Type &#8220;game_over&#8221; in the Receiver Method.<\/p>\n\n\n\n<p><strong>Step 8<\/strong>: You are aiming to have the\u00a0<code>hit<\/code>\u00a0signal emitted from\u00a0<code>Player<\/code>\u00a0and handled in the\u00a0<code>Main<\/code>\u00a0script. Add the following code to the new function, as well as a\u00a0<code>new_game<\/code>\u00a0function that will set everything up for a new game:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>func game_over():\n\t$ScoreTimer.stop()\n\t$MobTimer.stop()\n\nfunc new_game():\n\tscore = 0\n\t$Player.start($StartPosition.position)\n\t$StartTimer.start()<\/code><\/pre>\n\n\n\n<p><strong>Step 9<\/strong>:  Now connect the\u00a0<code>timeout()<\/code>\u00a0signal of each of the Timer nodes (<code>StartTimer<\/code>,\u00a0<code>ScoreTimer<\/code>, and\u00a0<code>MobTimer<\/code>) to the main script.\u00a0<\/p>\n\n\n\n<p><strong>Step 10<\/strong>: <code>StartTimer<\/code>\u00a0will start the other two timers.\u00a0<code>ScoreTimer<\/code>\u00a0will increment the score by 1:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>func _on_score_timer_timeout():\n\tscore += 1\n\nfunc _on_start_timer_timeout():\n\t$MobTimer.start()\n\t$ScoreTimer.start()<\/code><\/pre>\n\n\n\n<p>Step 11:  In&nbsp;<code>_on_mob_timer_timeout()<\/code>, we will create a mob instance, pick a random starting location along the&nbsp;<code>Path2D<\/code>, and set the mob in motion. The&nbsp;<code>PathFollow2D<\/code>&nbsp;node will automatically rotate as it follows the path, so we will use that to select the mob&#8217;s direction as well as its position. When we spawn a mob, we&#8217;ll pick a random value between&nbsp;<code>150.0<\/code>&nbsp;and&nbsp;<code>250.0<\/code>&nbsp;for how fast each mob will move (it would be boring if they were all moving at the same speed).<\/p>\n\n\n\n<p>Note that a new instance must be added to the scene using\u00a0<code>add_child()<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>func _on_mob_timer_timeout():\n\t# Create a new instance of the Mob scene.\n\tvar mob = mob_scene.instantiate()\n\n\t# Choose a random location on Path2D.\n\tvar mob_spawn_location = $MobPath\/MobSpawnLocation\n\tmob_spawn_location.progress_ratio = randf()\n\n\t# Set the mob's direction perpendicular to the path direction.\n\tvar direction = mob_spawn_location.rotation + PI \/ 2\n\n\t# Set the mob's position to a random location.\n\tmob.position = mob_spawn_location.position\n\n\t# Add some randomness to the direction.\n\tdirection += randf_range(-PI \/ 4, PI \/ 4)\n\tmob.rotation = direction\n\n\t# Choose the velocity for the mob.\n\tvar velocity = Vector2(randf_range(150.0, 250.0), 0.0)\n\tmob.linear_velocity = velocity.rotated(direction)\n\n\t# Spawn the mob by adding it to the Main scene.\n\tadd_child(mob)<\/code><\/pre>\n\n\n\n<p><strong>Step 11<\/strong>: Testing<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>func _ready():\n\tnew_game()<\/code><\/pre>\n\n\n\n<p>When you&#8217;re sure everything is working, remove the call to\u00a0<code>new_game()<\/code>\u00a0from\u00a0<code>_ready()<\/code>\u00a0and replace it with\u00a0<code>pass<\/code>.<\/p>\n\n\n\n<p class=\"has-large-font-size\"><strong>PART 6<\/strong>: HUD<\/p>\n\n\n\n<p><strong>Step 1<\/strong>: Create a new scene, click the &#8220;Other Node&#8221; button and add a CanvasLayer node named HUD.<\/p>\n\n\n\n<p><strong>Step 2<\/strong>: The basic node for UI elements is Control<\/p>\n\n\n\n<p>Create the following as children of the HUD node:<\/p>\n\n\n\n<p>Label named ScoreLabel.<\/p>\n\n\n\n<p>Label named Message.<\/p>\n\n\n\n<p>Button named StartButton.<\/p>\n\n\n\n<p>Timer named MessageTimer.<\/p>\n\n\n\n<p><strong>Step 3<\/strong>: Click on the\u00a0<code>ScoreLabel<\/code>\u00a0and type a number into the\u00a0<code>Text<\/code>\u00a0field in the Inspector.<\/p>\n\n\n\n<p>Under &#8220;Theme Overrides > Fonts&#8221;, choose &#8220;Load&#8221; and select the &#8220;Xolonium-Regular.ttf&#8221; file.<\/p>\n\n\n\n<p>Theme Overrides -> Font Sizes -> 64<\/p>\n\n\n\n<p>Repeat this step for Messages and StartButton<\/p>\n\n\n\n<p><strong>Step 4<\/strong>: <code>Control<\/code>\u00a0nodes have a position and size, but they also have anchors. Anchors define the origin &#8211; the reference point for the edges of the node.  You can drag the nodes to place them manually, or for more precise placement, use &#8220;Anchor Presets&#8221;.<\/p>\n\n\n\n<h5 class=\"wp-block-heading\">ScoreLabel<\/h5>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Add the text\u00a0<code>0<\/code>.<\/li>\n\n\n\n<li>Set the &#8220;Horizontal Alignment&#8221; and &#8220;Vertical Alignment&#8221; to\u00a0<code>Center<\/code>.<\/li>\n\n\n\n<li>Choose the &#8220;Anchor Preset&#8221;\u00a0<code>Center\u00a0Top<\/code>.<\/li>\n<\/ol>\n\n\n\n<h5 class=\"wp-block-heading\">Message<\/h5>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Add the text\u00a0<code>Dodge\u00a0the\u00a0Creeps!<\/code>.<\/li>\n\n\n\n<li>Set the &#8220;Horizontal Alignment&#8221; and &#8220;Vertical Alignment&#8221; to\u00a0<code>Center<\/code>.<\/li>\n\n\n\n<li>Set the &#8220;Autowrap Mode&#8221; to\u00a0<code>Word<\/code>, otherwise the label will stay on one line.<\/li>\n\n\n\n<li>Under &#8220;Control &#8211; Layout\/Transform&#8221; set &#8220;Size X&#8221; to\u00a0<code>480<\/code>\u00a0to use the entire width of the screen.<\/li>\n\n\n\n<li>Choose the &#8220;Anchor Preset&#8221;\u00a0<code>Center<\/code>.<\/li>\n<\/ol>\n\n\n\n<h5 class=\"wp-block-heading\">StartButton<\/h5>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Add the text\u00a0<code>Start<\/code>.<\/li>\n\n\n\n<li>Under &#8220;Control &#8211; Layout\/Transform&#8221;, set &#8220;Size X&#8221; to\u00a0<code>200<\/code>\u00a0and &#8220;Size Y&#8221; to\u00a0<code>100<\/code>\u00a0to add a little bit more padding between the border and text.<\/li>\n\n\n\n<li>Choose the &#8220;Anchor Preset&#8221;\u00a0<code>Center\u00a0Bottom<\/code>.<\/li>\n\n\n\n<li>Under &#8220;Control &#8211; Layout\/Transform&#8221;, set &#8220;Position Y&#8221; to\u00a0<code>580<\/code>.<\/li>\n<\/ol>\n\n\n\n<p>On the&nbsp;<code>MessageTimer<\/code>, set the&nbsp;<code>Wait&nbsp;Time<\/code>&nbsp;to&nbsp;<code>2<\/code>&nbsp;and set the&nbsp;<code>One&nbsp;Shot<\/code>&nbsp;property to &#8220;On&#8221;.<\/p>\n\n\n\n<p><strong>Step 5<\/strong>: Now add this script to HUD:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>extends CanvasLayer\n\n# Notifies `Main` node that the button has been pressed\nsignal start_game\n\nfunc show_message(text):\n\t$Message.text = text\n\t$Message.show()\n\t$MessageTimer.start()\n\nfunc show_game_over():\n\tshow_message(\"Game Over\")\n\t# Wait until the MessageTimer has counted down.\n\tawait $MessageTimer.timeout\n\n\t$Message.text = \"Dodge the Creeps!\"\n\t$Message.show()\n\t# Make a one-shot timer and wait for it to finish.\n\tawait get_tree().create_timer(1.0).timeout\n\t$StartButton.show()\n\nfunc update_score(score):\n\t$ScoreLabel.text = str(score)<\/code><\/pre>\n\n\n\n<p><strong>Step 6<\/strong>: Connect the\u00a0<code>pressed()<\/code>\u00a0signal of\u00a0<code>StartButton<\/code>\u00a0and the\u00a0<code>timeout()<\/code>\u00a0signal of\u00a0<code>MessageTimer<\/code>\u00a0to the\u00a0<code>HUD<\/code>\u00a0node, and add the following code to the new functions:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>func _on_start_button_pressed():\n\t$StartButton.hide()\n\tstart_game.emit()\n\nfunc _on_message_timer_timeout():\n\t$Message.hide()<\/code><\/pre>\n\n\n\n<p><strong>Step 7<\/strong>: Now that we&#8217;re done creating the\u00a0<code>HUD<\/code>\u00a0scene, go back to\u00a0<code>Main<\/code>. Instance the\u00a0<code>HUD<\/code>\u00a0scene in\u00a0<code>Main<\/code>\u00a0like you did the\u00a0<code>Player<\/code>\u00a0scene.<\/p>\n\n\n\n<p><strong>Step 8<\/strong>: Now we need to connect the\u00a0<code>HUD<\/code>\u00a0functionality to our\u00a0<code>Main<\/code>\u00a0script. This requires a few additions to the\u00a0<code>Main<\/code>\u00a0scene.  In the Node tab, connect the HUD&#8217;s\u00a0<code>start_game<\/code>\u00a0signal to the\u00a0<code>new_game()<\/code>\u00a0function of the Main node.  \u00a0Type &#8220;new_game&#8221; below &#8220;Receiver Method&#8221;.  In new_game(), update the score display and show the &#8220;Get Ready&#8221; message:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$HUD.update_score(score)\n$HUD.show_message(\"Get Ready\")<\/code><\/pre>\n\n\n\n<p>In game_over() we need to call the corresponding HUD function:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$HUD.show_game_over()<\/code><\/pre>\n\n\n\n<p>Finally, add this to\u00a0<code>_on_score_timer_timeout()<\/code>\u00a0to keep the display in sync with the changing score:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$HUD.update_score(score)<\/code><\/pre>\n\n\n\n<p><strong>Step 9<\/strong>: If you play until &#8220;Game Over&#8221; and then start a new game right away, the creeps from the previous game may still be on the screen. It would be better if they all disappeared at the start of a new game. We just need a way to tell\u00a0<em>all<\/em>\u00a0the mobs to remove themselves. We can do this with the &#8220;group&#8221; feature.  In the&nbsp;<code>Mob<\/code>&nbsp;scene, select the root node and click the &#8220;Node&#8221; tab next to the Inspector (the same place where you find the node&#8217;s signals). Next to &#8220;Signals&#8221;, click &#8220;Groups&#8221; and you can type a new group name and click &#8220;Add&#8221;.<\/p>\n\n\n\n<p>Now all mobs will be in the &#8220;mobs&#8221; group. We can then add the following line to the new_game() function in Main:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>get_tree().call_group(\"mobs\", \"queue_free\")<\/code><\/pre>\n\n\n\n<p class=\"has-large-font-size\">PART 7: FINISHING UP<\/p>\n\n\n\n<p><strong>Step 1<\/strong>: One way to change the background color is to use a ColorRect node. Make it the first node under Main so that it will be drawn behind the other nodes. ColorRect only has one property: Color. Choose a color you like and select &#8220;Layout&#8221; -> &#8220;Anchors Preset&#8221; -> &#8220;Full Rect&#8221; either in the toolbar at the top of the viewport or in the inspector so that it covers the screen.<\/p>\n\n\n\n<p><strong>Step 2<\/strong>:  Add two AudioStreamPlayer nodes as children of Main. Name one of them Music and the other DeathSound. On each one, click on the Stream property, select &#8220;Load&#8221;, and choose the corresponding audio file.  All audio is automatically imported with the\u00a0<code>Loop<\/code>\u00a0setting disabled. If you want the music to loop seamlessly, click on the Stream file arrow, select\u00a0<code>Make\u00a0Unique<\/code>, then click on the Stream file and check the\u00a0<code>Loop<\/code>\u00a0box.<\/p>\n\n\n\n<p>Step 3: To play the music, add $Music.play() in the new_game() function and $Music.stop() in the game_over() function.  Finally, add $DeathSound.play() in the game_over() function:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>func game_over():\n\t...\n\t$Music.stop()\n\t$DeathSound.play()\n\nfunc new_game():\n\t...\n\t$Music.play()<\/code><\/pre>\n\n\n\n<p><strong>Step 4<\/strong>: Enter key shortcut: Select &#8220;Project&#8221; -> &#8220;Project Settings&#8221; and then click on the &#8220;Input Map&#8221; tab. In the same way you created the movement input actions, create a new input action called start_game and add a key mapping for the Enter key.<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In pondering how to learn video game programming, I just came up with this concept: Think Like a Speedrunner In short, learn a development task, and the repeat it. Keep repeating it so often you can do it very quickly. The first attempt may take a very long time. You&#8217;ll need to research it, there [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"cybocfi_hide_featured_image":"","footnotes":""},"categories":[22],"tags":[],"class_list":["post-403","post","type-post","status-publish","format-standard","hentry","category-godot"],"_links":{"self":[{"href":"https:\/\/johnnycarlos.com\/index.php\/wp-json\/wp\/v2\/posts\/403","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/johnnycarlos.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/johnnycarlos.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/johnnycarlos.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/johnnycarlos.com\/index.php\/wp-json\/wp\/v2\/comments?post=403"}],"version-history":[{"count":8,"href":"https:\/\/johnnycarlos.com\/index.php\/wp-json\/wp\/v2\/posts\/403\/revisions"}],"predecessor-version":[{"id":428,"href":"https:\/\/johnnycarlos.com\/index.php\/wp-json\/wp\/v2\/posts\/403\/revisions\/428"}],"wp:attachment":[{"href":"https:\/\/johnnycarlos.com\/index.php\/wp-json\/wp\/v2\/media?parent=403"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/johnnycarlos.com\/index.php\/wp-json\/wp\/v2\/categories?post=403"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/johnnycarlos.com\/index.php\/wp-json\/wp\/v2\/tags?post=403"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}