From 7f02d83ab8909e3697fdc88386183deac5b5ea44 Mon Sep 17 00:00:00 2001 From: Brett Chalupa Date: Sat, 16 May 2026 20:38:02 -0400 Subject: [PATCH] feat: update book for DragonRuby v7 Modernizes all 12 chapters to match what readers see when they unzip DragonRuby v7.x: - Replaces chapter_01/01_default with the actual v7.1 hello world (uses module Main, size_px, anchor_x/anchor_y, Kernel.tick_count). - Rewrites the Chapter 1 walkthrough to match. - Converts every positional array primitive ([x, y, w, h, path], [x, y, text, size, align]) to hash form across chapters 1-12. - Replaces size_enum with size_px using the docs' formula (size_px = 22 + 2 * size_enum). - Replaces alignment_enum / vertical_alignment_enum with anchor_x / anchor_y. - Switches args.state.tick_count to Kernel.tick_count. - Rework Chapter 3's "switching to hashes" arc into "a closer look at hashes" + "refactoring the player into a hash" since fireballs are now hashes from the start. - Introduces module Main as a refactor step in Chapter 11; wrap the refactor, title, and release samples (chapters 11-12) in it. - Updates mdbook to latest version. Lots of find and replace. But I did manually test all changed code examples with DR v7.1, so hoping this is all good! --- .github/FUNDING.yml | 7 +- .github/workflows/gh-pages.yml | 4 +- .gitignore | 1 + LICENSE.md | 52 +-- README.md | 34 +- book.toml | 3 +- src/01-hello-dragon.md | 106 ++--- src/02-player-movement.md | 28 +- src/03-spit-fire.md | 43 +- src/04-target-practice.md | 4 +- src/06-time-attack.md | 4 +- src/07-high-score.md | 4 +- src/08-sound.md | 2 +- src/11-scenes.md | 36 +- src/code/chapter_01/01_default/app/main.rb | 103 ++++- .../chapter_01/02_our_own_hello/app/main.rb | 6 +- src/code/chapter_02/app/main.rb | 8 +- src/code/chapter_03/01_input/app/main.rb | 8 +- .../chapter_03/02_input_expanded/app/main.rb | 8 +- .../03_displaying_fireballs/app/main.rb | 14 +- .../04_moving_fireballs/app/main.rb | 16 +- .../chapter_04/01_display_targets/app/main.rb | 2 +- .../chapter_04/02_target_method/app/main.rb | 2 +- .../03_collision_detection/app/main.rb | 2 +- .../chapter_04/04_remove_targets/app/main.rb | 2 +- .../05_spawn_new_targets/app/main.rb | 2 +- .../chapter_04/06_score_tracking/app/main.rb | 4 +- .../01_tracking_offscreen/app/main.rb | 4 +- src/code/chapter_05/02_optimizing/app/main.rb | 4 +- .../chapter_06/01_time_attack/app/main.rb | 16 +- .../02_restart_grace_period/app/main.rb | 16 +- src/code/chapter_06/03_refactor/app/main.rb | 16 +- .../01_load_and_save_data/app/main.rb | 24 +- src/code/chapter_08/01_sound/app/main.rb | 26 +- src/code/chapter_09/01_blue_sky/app/main.rb | 26 +- src/code/chapter_10/01_animating/app/main.rb | 26 +- src/code/chapter_11/01_refactor/app/main.rb | 338 +++++++-------- src/code/chapter_11/02_title/app/main.rb | 408 +++++++++--------- src/code/chapter_12/01_release/app/main.rb | 408 +++++++++--------- src/img/hello-world.jpg | Bin 35111 -> 22759 bytes src/img/hello-world.webp | Bin 23356 -> 22268 bytes src/outro.md | 4 +- 42 files changed, 991 insertions(+), 830 deletions(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 0206b94..d4328b8 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,5 @@ -patreon: brettchalupa -custom: ["https://www.buymeacoffee.com/brettchalupa", "https://paypal.me/brettchalupa"] +custom: + [ + "https://www.buymeacoffee.com/brettchalupa", + "https://paypal.me/brettchalupa", + ] diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index c8b9835..c9ff5a6 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -17,7 +17,7 @@ jobs: - name: Setup mdBook uses: peaceiris/actions-mdbook@v1 with: - mdbook-version: '0.4.22' + mdbook-version: "0.5.2" # mdbook-version: 'latest' - run: mdbook build @@ -26,7 +26,7 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@359bebbc29cbe6c87da6bc9ea3bc930432750108 with: - ruby-version: '3.1' + ruby-version: "3.1" - name: Install dependencies run: bundle install diff --git a/.gitignore b/.gitignore index 3e96e87..bbd8d0d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ book high-score.txt .DS_Store +errors/ diff --git a/LICENSE.md b/LICENSE.md index a98c4c6..b5ab755 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,43 +1,45 @@ # License -_Building Games with DragonRuby_ falls under two licenses for different content within it. +_Building Games with DragonRuby_ falls under two licenses for different content +within it. ## Code Snippets -First off, the code snippets. The code examples and sample projects contained in this book are licensed under The Unlicense. You can do whatever you want with the game code from the book. +First off, the code snippets. The code examples and sample projects contained in +this book are licensed under The Unlicense. You can do whatever you want with +the game code from the book. This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. + +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to ## The Written Words -The actual text of the book where the code is described falls under a different license: +The actual text of the book where the code is described falls under a different +license: -

Building Games with DragonRuby by Brett Chalupa is licensed under Attribution-NonCommercial-ShareAlike 4.0 International

+

Building Games with DragonRuby by Brett Chalupa is licensed under Attribution-NonCommercial-ShareAlike 4.0 International

-Examples of what you can do with the book under this license as long as it maintains the same license and gives proper credit: +Examples of what you can do with the book under this license as long as it +maintains the same license and gives proper credit: - Translate it - Reformat it diff --git a/README.md b/README.md index 3400b5d..16420d0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # Building Games with DragonRuby -An open-source book by Brett Chalupa and the Dragon Rider Community that walks you through how to build games with [DragonRuby Game Toolkit](https://dragonruby.org/toolkit/game). +An open-source book by Brett Chalupa and the Dragon Rider Community that walks +you through how to build games with +[DragonRuby Game Toolkit](https://dragonruby.org/toolkit/game). [Play the game you'll be building, right in the browser.](https://dragonridersunite.itch.io/dragonruby-book) @@ -8,26 +10,40 @@ An open-source book by Brett Chalupa and the Dragon Rider Community that walks y ## Developing -The book is authored and published with [mdBook](https://rust-lang.github.io/mdBook/guide/installation.html). Follow the installation instructions to get the `mdbook` binary installed. +The book is authored and published with +[mdBook](https://rust-lang.github.io/mdBook/guide/installation.html). Follow the +installation instructions to get the `mdbook` binary installed. Run `mdbook serve` to boot up the web server that handles compiling everything. -When the code is pushed to the `main` branch on GitHub, an action runs that deploys it to the `gh-pages` branch. +When the code is pushed to the `main` branch on GitHub, an action runs that +deploys it to the `gh-pages` branch. + +Download DragonRuby GTK for your platform, unzip it, and then clone the +repository inside of the new folder: + +``` +cd dragonruby +git clone git@github.com:DragonRidersUnite/book.git +``` ## Key Versions -- DragonRuby Game Toolkit: v3.x, v4.x, v5.x -- mdBook: v0.4.22 +- DragonRuby Game Toolkit: v7.x; might work for previous versions +- mdBook: v0.5.x ## Running Samples -The repository contains complete games for each separate code sample to ensure the code works and runs. +The repository contains complete games for each separate code sample to ensure +the code works and runs. -Code samples live in `src/code` and then are organized by chapter and then sections within a chapter. +Code samples live in `src/code` and then are organized by chapter and then +sections within a chapter. -Clone this book repository into a DragonRuby GTK engine directory and run the samples with: +Clone this book repository into a DragonRuby GTK engine directory and run the +samples with: -``` console +```console ./dragonruby book/src/code/chapter_03/01_input ``` diff --git a/book.toml b/book.toml index e268947..fd0110e 100644 --- a/book.toml +++ b/book.toml @@ -1,10 +1,9 @@ [book] authors = ["Brett Chalupa", "Dragon Rider Community"] language = "en" -multilingual = false src = "src" title = "Building Games with DragonRuby" -description = "A free book about making 2D games with DragonRuby Game Toolkit." +description = "A beginner-friendly guide to programming 2D games with DragonRuby Game Toolkit." [output.html] git-repository-url = "https://github.com/DragonRidersUnite/book" diff --git a/src/01-hello-dragon.md b/src/01-hello-dragon.md index 66ad8d5..99e8ca1 100644 --- a/src/01-hello-dragon.md +++ b/src/01-hello-dragon.md @@ -23,13 +23,13 @@ Let's go over the key files in the archive you just extracted: - `samples/` β€” a folder of example code that you can explore and run if you'd like, or just ignore for now - `docs/` β€” the DragonRuby Game Toolkit docs for the version you've downloaded, extremely handy if you want to dive deeper than this book - `mygame/` 🌟 β€” as the name implies, this is where the code, graphics, sound, and other assets live for your game; this is the primary folder you'll be working in - - `app/` β€” this is where your Ruby code goes - - `main.rb` β€” this is the main file for your game code, don't worry about the other two yet - - `data/` β€” where you can put any data for your game, like level editor files - - `fonts/` β€” where fonts go, duh! - - `metadata/` β€” configuration files for your game, we'll do more here later - - `sounds/` β€” where music and sound effects go - - `sprites/` β€” where images go that will be rendered in the game; there are already some basic ones present + - `app/` β€” this is where your Ruby code goes + - `main.rb` β€” this is the main file for your game code, don't worry about the other one yet + - `data/` β€” where you can put any data for your game, like level editor files + - `fonts/` β€” where fonts go, duh! + - `metadata/` β€” configuration files for your game, we'll do more here later + - `sounds/` β€” where music and sound effects go + - `sprites/` β€” where images go that will be rendered in the game; there are already some basic ones present Don't worry about the other files yet. Focus on the `mygame/` directory, and, specifically, `mygame/app/main.rb`. @@ -55,29 +55,35 @@ DRGTK handles the boring stuff when it comes to making gamesβ€”dealing with low- ## An Overview of the Main Game File -Let's take a look at what's in `mygame/app/main.rb`: +Let's take a look at what's in `mygame/app/main.rb`. There's a fair amount of code in there, so don't panic. We'll go through it in chunks. -``` ruby +```ruby {{#include code/chapter_01/01_default/app/main.rb}} ``` Open this file yourself in Visual Studio Code. Go ahead and do that: New Window > Open > [select the folder on your computer with the engine in it] -Seven lines of code to render text and an image? Not bad! (Trust me, if you did this all from scratch without an engine, it'd take hundreds of lines of code.) +Here's the very first line: -Here's what that code does, line by line: - -``` ruby +```ruby {{#include code/chapter_01/01_default/app/main.rb:1}} ``` +That's a Ruby `module` named `Main`. Don't worry about modules yet. DragonRuby looks for a module called `Main` to find the entry point of your game. For now, treat that line (and the matching `end` at the bottom of the file) as boilerplate. We'll cover modules properly later in the book. + +Inside the module is this: + +```ruby +{{#include code/chapter_01/01_default/app/main.rb:2}} +``` + This `def`ines a method called `tick` and passes in a parameter called `args`. `args` is short for arguments and is provided to us by DragonRuby GTK. Methods are reusable pieces of code that you can call to do something over and over again. Think of methods like verbs; they're actions. Methods are a foundational building block that allows for code reuse. Let's say you wanted to give a friend a cookie πŸͺ, you'd define a method called `give_cookie` and pass in the `friend` as a parameter. The friend then has a method called `eat` that we call, passing in a parameter of cookie. -``` ruby +```ruby def give_cookie(friend) friend.eat("cookie") end @@ -85,14 +91,14 @@ end You'd then call that method like: -``` ruby +```ruby francis = Friend.new give_cookie(francis) ``` Note: methods in Ruby have optional parentheses. You can use them or leave them out when defining and calling your methods. These are the same: -``` ruby +```ruby def give_cookie(friend) friend.eat("cookie") end @@ -106,10 +112,10 @@ I'm an agent of chaos and use _both_ styles throughout the book. Gotta keep ya' Wait, how'd we get talking about cookies and friends? Okay, back to the code above. -The next three lines handle outputting text to the screen: +The next chunk handles outputting text to the screen: -``` ruby -{{#include code/chapter_01/01_default/app/main.rb:2:4}} +```ruby +{{#include code/chapter_01/01_default/app/main.rb:8:13}} ``` Your eyes might be melting a little bit. But don't worry, wipe that melted bit of eye away and focus! It's pretty intuitive once you get the hang of it. @@ -120,49 +126,27 @@ Remember `args` from above? The parameter that's passed into `tick`? Well, you c The `<<` is called the shovel operator. It lets you push data into a collection. `labels` is the collection we're shoveling data into because we want to render some text. -And then finally the code within the brackets `[]` is an array of data that represents what we want to display on the screen. It may not be clear exactly what it's doing yet, but it'll become more clear over time. - -I think of arrays like a stack of CDs. The CDs are in a specific order, but you don't know what a given CD is unless you pull one out of the stack and look at it. You have to do the same thing with an array. In programming, you put data into an array to collect that data and keep track of it. If you want to know what's at a specific place in an array, you have to check. - -You can see some code in quotes, those are strings. And they're what's being displayed by the game. In order to show the text, though, we need to tell the engine where to place it on the screen. That's what the first two elements of the array do: they specify the `x` and `y` position of the text in the game. Then there's the text we want to render. That's followed by the text size. Then finally the alignment (centered in this case). - -Here's it broken down: +And then the code within the curly braces `{}` is a hash, a collection of named values. Each key (the bit before the `:`) describes what its value means. So `x: 640` says "put this label at an x position of 640." `y: 600` is the vertical position. `text:` is the actual text to display. `size_px:` is the font size in pixels. And `anchor_x:` / `anchor_y:` control which point on the label gets placed at `(x, y)`. `0.5` means "center it," `0` means "left edge / bottom edge," `1` means "right edge / top edge." -``` ruby -# x, y, text, size, alignment -[640, 500, 'Hello World!', 5, 1] -``` - -Don't worry about memorizing what the positional elements of the array mean. This is just a quick way to render text. We'll start using a more friendly approach soon enough. The sample game does this three times, thus rendering three different pieces of text. The `y` coordinate is different for each, stacking them on top of each other vertically instead of overlapping one another. +Don't worry about memorizing every key. We'll use them over and over and they'll become second nature. Gosh enough of this rambling, let's adjust some code. Change the text from `"Hello World!"` to `"Hello Dragon!"` and save the file. -![screenshot of the Hello Dragon! for DragonRuby GTK](./img/hello-dragon.jpg) - Did you see that? The game reloaded your code and changed the text to "Hello Dragon!" That was quick, wasn't it? That's one of the best parts of DRGTKβ€”live reloading of your game code in the engine. This allows you to make changes to your game quickly, reducing the time between the cycles of writing your code and testing it. +Further down in the file is the chunk that draws the spinning logo: -``` ruby -{{#include code/chapter_01/01_default/app/main.rb:6:11}} +```ruby +{{#include code/chapter_01/01_default/app/main.rb:43:48}} ``` -`args.outputs.sprites` is like `args.outputs.labels`, but instead of holding labels, it's a collection of sprites. It renders the `"dragonruby.png"` image at `x: 576`, `y: 280`, just like label text, and it also sets the size of the image to display. That's what the `w: 128` and `h: 101` represent. Finally, it rotates the image passing `args.state.tick_count` to `angle`. Don't worry about `tick_count` for now. Just know that we need to keep updating `angle` so that we can see the image rotating, and `tick_count` is convenient as it keeps incrementing for us. - -You will notice that instead of an array we are passing a hash as a set of keys and values. This is another, friendlier way to define a sprite. It allows us to specify each key and its value to render a sprite. It means we no longer have to rely on the ordinal positioning of an array. Regardless of whether we are using an array or a hash, this is an easy enough bit of code for putting an image on the screen and rotating it. +`args.outputs.sprites` is like `args.outputs.labels`, but instead of holding labels, it's a collection of sprites. It renders the `"dragonruby.png"` image at the `x` and `y` stored in `args.state.logo_rect`, sized `w` by `h`, and rotates it by passing `Kernel.tick_count` to `angle`. Don't worry about `tick_count` for now. Just know that DragonRuby provides it for us, it keeps incrementing as the game runs, and feeding it to `angle` is a quick way to make the image spin. -The next line is just another label that displays the path of the main file of our game, relative to the DRGTK folder: +The `args.state` you see used here is a place DragonRuby gives us to stash our own data. The `||=` near the top of the file says "set `logo_rect` to this hash only if it isn't already set", so the rect gets initialized on the first frame and then is just read on every frame after. -``` ruby -{{#include code/chapter_01/01_default/app/main.rb:13:17}} -``` - -Finally, the last line: - -``` ruby -{{#include code/chapter_01/01_default/app/main.rb:18}} -``` +The chunk at the bottom uses `args.inputs.keyboard` to move that rect around when the arrow keys are pressed. We'll get to player input in the next chapter, so don't sweat the details yet. -signifies that the definition of the `tick` method is over. Any code after that isn't part of the `tick` method. That's it for what we get out of the box. +That's it for what we get out of the box. ## Screen Coordinates Explained @@ -182,33 +166,33 @@ Back to making the game. Okay, yeah, you changed a string and it changed what wa I want you to delete the code in `mygame/app/main.rb`. Select it all and delete it. Yup, I know, the blank canvas is a challenge. But you need to start writing the code yourself in order to learn. So go ahead and type out each line below into your `main.rb`. -``` ruby +```ruby def tick args - args.outputs.labels << [120, 120, "Hello Dragon!"] + args.outputs.labels << { x: 120, y: 120, text: "Hello Dragon!" } end ``` -You know what that does, right? Well, you saw it automatically reload in your game, so of course, you do! +You know what that does, right? Well, you saw it automatically reload in your game, so of course, you do! Note that we dropped the `module Main` wrapper for now. DragonRuby is happy to find your `tick` method at the top level, and it keeps the example tiny while we're learning the basics. Let's greet our friend Oboe too. -``` ruby +```ruby def tick args - args.outputs.labels << [120, 120, "Hello Dragon!"] - args.outputs.labels << [120, 100, "Hello Oboe!"] + args.outputs.labels << { x: 120, y: 120, text: "Hello Dragon!" } + args.outputs.labels << { x: 120, y: 100, text: "Hello Oboe!" } end ``` Wow, so many friends we're saying hello to! The thing is... that code is a bit... duplicative. Let's write our own method to clean that up: -``` ruby +```ruby def greet friend "Hello #{friend}!" end def tick args - args.outputs.labels << [120, 120, greet("Dragon")] - args.outputs.labels << [120, 100, greet("Oboe")] + args.outputs.labels << { x: 120, y: 120, text: greet("Dragon") } + args.outputs.labels << { x: 120, y: 100, text: greet("Oboe") } end ``` @@ -220,7 +204,7 @@ Methods in Ruby return a value. Return values can then be used by the caller for Go ahead and change the `greet` method to: -``` ruby +```ruby def greet friend "Hello #{friend}!" 1 @@ -231,7 +215,7 @@ Whoa! What happened to our greetings? Well, we changed our code and it now alway Let's wrap up this chapter by displaying a sprite. In `mygame/sprites/misc/` there's a file named `dragon-0.png`. It's a pixel art dragon made by Nick Culbertson that's free to use. Pretty neat. Thanks, Nick! -``` ruby +```ruby {{#include code/chapter_01/02_our_own_hello/app/main.rb}} ``` diff --git a/src/02-player-movement.md b/src/02-player-movement.md index 57d50c9..a59c945 100644 --- a/src/02-player-movement.md +++ b/src/02-player-movement.md @@ -6,7 +6,7 @@ Simplify `mygame/app/main.rb` to be just this for now: ``` ruby def tick args - args.outputs.sprites << [120, 280, 100, 80, 'sprites/misc/dragon-0.png'] + args.outputs.sprites << { x: 120, y: 280, w: 100, h: 80, path: 'sprites/misc/dragon-0.png' } end ``` @@ -20,7 +20,13 @@ Update your `mygame/app/main.rb` to be this: def tick args args.state.player_x ||= 120 args.state.player_y ||= 280 - args.outputs.sprites << [args.state.player_x, args.state.player_y, 100, 80, 'sprites/misc/dragon-0.png'] + args.outputs.sprites << { + x: args.state.player_x, + y: args.state.player_y, + w: 100, + h: 80, + path: 'sprites/misc/dragon-0.png', + } end ``` @@ -82,11 +88,17 @@ def tick args args.state.player_y -= 10 end - args.outputs.sprites << [args.state.player_x, args.state.player_y, 100, 80, 'sprites/misc/dragon-0.png'] + args.outputs.sprites << { + x: args.state.player_x, + y: args.state.player_y, + w: 100, + h: 80, + path: 'sprites/misc/dragon-0.png', + } end ``` -You can now control the dragon with WASD, the arrow keys, or your controller. Pretty neat! It's almost fun. All it took was 10 lines of code. Let's break them down. +You can now control the dragon with WASD, the arrow keys, or your controller. Pretty neat! It's almost fun. Let's break it down. ``` ruby if args.inputs.left @@ -129,7 +141,13 @@ def tick args args.state.player_y -= speed end - args.outputs.sprites << [args.state.player_x, args.state.player_y, 100, 80, 'sprites/misc/dragon-0.png'] + args.outputs.sprites << { + x: args.state.player_x, + y: args.state.player_y, + w: 100, + h: 80, + path: 'sprites/misc/dragon-0.png', + } end ``` diff --git a/src/03-spit-fire.md b/src/03-spit-fire.md index c59919c..5f589f7 100644 --- a/src/03-spit-fire.md +++ b/src/03-spit-fire.md @@ -9,7 +9,7 @@ In the last chapter, we used `args.inputs` to check for player input from the fo To check if a key was pressed, we can use `args.inputs.keyboard.key_down` and then whatever key we want to check. So in our case, we'll check `args.inputs.keyboard.key_down.z`. In `#tick`, right above where we render the dragon sprite, let's check for that input: ``` ruby -{{#include code/chapter_03/01_input/app/main.rb:36:40}} +{{#include code/chapter_03/01_input/app/main.rb:36:38}} ``` Using `puts` is a really helpful way to check that our game works as we expect it to. In this case, every tick where the Z key is pressed down, it prints the string "Z key pressed" to the console (open it with ~, remember?). Run your game and press the Z key a bunch and then open your console. @@ -50,15 +50,15 @@ At the top of `#tick`, assign an empty array to `args.state.fireballs` if nothin {{#include code/chapter_03/03_displaying_fireballs/app/main.rb:1:4}} ``` -Then where we check for the action input, push a fireball into the `arg.state.fireballs` array based on the current player's x and y position: +Then where we check for the action input, push a fireball hash into the `args.state.fireballs` array based on the current player's x and y position: ``` ruby -{{#include code/chapter_03/03_displaying_fireballs/app/main.rb:38:45}} +{{#include code/chapter_03/03_displaying_fireballs/app/main.rb:38:46}} ``` -All we have to do is render our fireballs by pushing them into the `args.outputs.labels` collection. DragonRuby is smart enough to know that if we push an array into any `args.outputs` collection it'll flatten it and display them correctly. Thanks, DragonRuby! +Each fireball is a hash with `x`, `y`, and `text` keys, exactly the shape DragonRuby needs to render a label. All we have to do is render our fireballs by pushing them into the `args.outputs.labels` collection. DragonRuby is smart enough to know that if we push an array into any `args.outputs` collection it'll flatten it and display them correctly. Thanks, DragonRuby! -We've been using arrays to represent the fields for different data in our game like labels and sprites, but arrays have other uses too. Arrays are a great way to keep track of information that we need in a list. The array we've created in the code above tracks our fireballs. +We've been using hashes to represent things like the player sprite and now our fireball labels; each one is a single piece of data with named properties. Arrays, on the other hand, are great for keeping a list of things. We use both: an array of fireball hashes lets us track many fireballs and act on each one. Play your game and see what happens! Fireballs everywhere. Wait! You're not impressed by those fireballs? I'd be pretty frightened if the word "fireball" was flying at me. @@ -73,13 +73,13 @@ Guess what? We're sticking with ole "fireball" for now! It's silly and fun and I When we moved our player dragon, we took the x and y position and added or subtracted values in each `#tick` based on if any directional input was pressed. Our fireballs will move regardless of any button pressed once they're extruded from our dragon's mouth. Because our game is simple and the dragon only faces to the right, all of the fireballs will move to the right. How do we go about that on our X-Y axis? We just increase the `x` position of the fireball each tick. Let's do that and see what happens: ``` ruby -{{#include code/chapter_03/04_moving_fireballs/app/main.rb:38:48}} +{{#include code/chapter_03/04_moving_fireballs/app/main.rb:38:50}} ``` Right between where we add a new fireball to `args.state.fireballs` and we display them using `args.outputs.labels`, our new code does this: 1. Loops through the array of `args.state.fireballs` -2. For each fireball, updates the array value at the `0` index, which is the x position of the fireball, to be the dragon's speed plus 2 (because we want the fireball to move faster than the dragon). +2. For each fireball, updates its `x` to be its current `x` plus the dragon's speed plus 2 (because we want the fireball to move faster than the dragon). Move your dragon around, spit some fire, and bask in the glory of a word moving so smoothly across the screen that it almost looks like a... fireball! @@ -89,19 +89,9 @@ There are a lot of important concepts in those three newly added lines of code. Looping through an array of data in each `#tick` and then doing _something_ is the stuff games are made of! Here are some ways this can be applied in all sorts of games: enemy behavior, checking for collision, and animating. As our game (and any game you make) gets more complex, looping through collections of data becomes more and more common. -## Switching to Hashes +## A Closer Look at Hashes -So far throughout the book, we've been using arrays to represent the entities in our game, whether it be the player's dragon sprite or our fireball text that gets displayed. Remember `[args.state.player_x, args.state.player_y, 'fireball']`? Arrays are wonderful and important, but they aren't so great for representing structured data because it's difficult to remember what each piece of data in the array's positions represents. Remembering that `fireball[2]` is the text value and not the y value is tricky. Luckily, DragonRuby has a more verbose and clear data structure we can use for managing our data. It's called a hash! Much like arrays, hashes are extremely useful. - -Let's look at what the text example above would be like as a hash: - -``` ruby -{ - x: args.state.player_x, - y: args.state.player_y, - text: "fireball", -} -``` +We've been using hashes a lot already: every sprite and label we render is a hash. Let's take a moment to look at them more carefully because they're going to be everywhere in this book. Hashes are expressed through curly braces `{}` and contain `key: value` pairs separated by commas. The values of a hash can be anything, from numbers to strings to whatever your heart desires. Let's say we wanted to build our own hash to represent a dragon and put it in the `dragon` variable: @@ -113,22 +103,27 @@ dragon = { } ``` -Values of a hash are then accessed by their keys, so `dragon.name` returns the string `"Francis"`, `dragon.size` returns the string `"medium"`, and `dragon.age` returns the number `541`. This is much clearer than having to remember the position of these values within an array. +Values of a hash are then accessed by their keys, so `dragon.name` returns the string `"Francis"`, `dragon.size` returns the string `"medium"`, and `dragon.age` returns the number `541`. Nice and clear: each property has a name that says what it is. Quick note: if you see a hash with key-value pairs that look like this: `{ :name => "Francis" }`, don't fret! That's just another style in Ruby known as the hash rocket. In general, differentiate between arrays and hashes like this: **hashes are used to represent one piece of data with multiple properties** and **arrays are used to collect data (oftentimes hashes) to keep track of and manipulate them**. -Below is our entire game translated to use hashes instead of arrays for our rendering: +## Refactoring the Player into a Hash + +Right now our player's position and size are spread across a bunch of separate values: `args.state.player_x`, `args.state.player_y`, the `speed` variable, the `player_w` and `player_h` variables, and the path to the sprite hard-coded down in the render line. That's a lot of pieces representing one thing. + +Let's bring all of that together by putting the player into a single hash on `args.state`: ``` ruby {{#include code/chapter_03/05_switching_to_hashes/app/main.rb}} ``` -It may not seem like much has changed, but two key changes make this worthwhile: +A few key wins from this refactor: -1. `fireball.x += args.state.player.speed + 2` β€” it is much clearer when we move the fireball that we're adding to its `x` position AND using the player's speed -2. `args.outputs.sprites << args.state.player` β€” because we're keeping track of our player in `args.state.player` and it has the data DragonRuby needs to render it, we can just push it into `args.outputs.sprites` and not construct the array that we used to use +1. `args.state.player` is now the one place where the player lives: position, size, speed, sprite path, all together. +2. `args.outputs.sprites << args.state.player`: because the hash already has the keys DragonRuby needs (`x`, `y`, `w`, `h`, `path`), we can just push the player hash straight into `args.outputs.sprites`. No need to spell out the keys again at render time. +3. The bounds-checking and input code reads `args.state.player.x`, `args.state.player.speed`, etc., which is much clearer than the loose scalar variables. ## Displaying a Sprite diff --git a/src/04-target-practice.md b/src/04-target-practice.md index 554647f..d879477 100644 --- a/src/04-target-practice.md +++ b/src/04-target-practice.md @@ -30,7 +30,7 @@ If you quit your game and relaunch `dragonruby`, they will though. Why is that? Well, when DragonRuby loads our game code, if any value is set in `args.state` via `||=`, it won't reassign the new value because that value is already set. `args.state.targets` already has three targets in it, so when our code is reloaded, it doesn't know to discard our old targets. -Let's instruct DragonRuby to reset our state when the game runs by adding the special `$gtk.reset` after the `end` of `#tick`: +Let's instruct DragonRuby to reset our state when the game runs by adding the special `DR.reset` after the `end` of `#tick`. (`DR` is short for DragonRuby; it gives us access to engine-level functions like `reset`.) ``` ruby {{#include code/chapter_04/01_display_targets/app/main.rb:79:82}} @@ -184,7 +184,7 @@ Finally, display our score as a label in the upper-left area of the screen: {{#include code/chapter_04/06_score_tracking/app/main.rb:83:89}} ``` -This approach to displaying a label is different than previous chapters. Instead of using an array to represent the properties of the label, we're now using a hash like we do for sprites (for similar reasons: it's easier to remember and more clear). Setting `x`, `y`, and `text` should be familiar by this point. But `size_enum` is new. It's a way to specify how large the text is. It takes whatever value we set and adds that much to the default text size. Increase it from `4` to `40` or `-3` and see what you like best. +Setting `x`, `y`, and `text` on a label hash should be familiar by this point. But `size_px` is new. It's a way to specify how large the text is, in pixels. The default DragonRuby label is `22` pixels tall, so passing `30` makes it noticeably bigger. Try bumping it up to `60` or down to `14` and see what you like best. ![dragon spitting fireballs at three targets on the right side of the screen with the text 'Score: 14' displayed](./img/c04-score.jpg) diff --git a/src/06-time-attack.md b/src/06-time-attack.md index 9ecb383..e39be6b 100644 --- a/src/06-time-attack.md +++ b/src/06-time-attack.md @@ -28,7 +28,7 @@ Right below decreasing our `args.state.timer` by one, we check to see if the tim ``` If it is game over, then we let the player know, display their final score, and tell them how to play again (by pressing the fire button). We make an array of labels which we then push into `args.outputs.labels` to efficiently render them all. -If any of our fire keys are pressed, the game is reset with `$gtk.reset`, and the player can play again. +If any of our fire keys are pressed, the game is reset with `DR.reset`, and the player can play again. The `return` line is _really_ important. It says, return out of the `#tick` method so that none of the code below runs. We don't want to have the dragon be movable or for targets to spawn when it's game over. So we eject early and only display the game over screen details. @@ -42,7 +42,7 @@ Way at the bottom of `#tick`, let's display a label with the time remaining: We use the same pattern of creating a `labels` array, pushing in the player's score and the time, in ticks, remaining. In order to display the time remaining as seconds, we divide it by 60 and round. We do the opposite of what we did when we set the total time in ticks. -The `alignment_enum` lets us specify that we want the text to be right-aligned instead of the default left alignment. This lets us nicely position our timer in the upper right corner of the game. +The `anchor_x: 1` lets us specify that we want the text to be right-aligned (anchored at the right edge of the label) instead of the default left alignment. This lets us nicely position our timer in the upper right corner of the game. ![gameplay with Time Left reading 10 seconds](./img/c06-timer.jpg) diff --git a/src/07-high-score.md b/src/07-high-score.md index 18c06ed..0ac5aeb 100644 --- a/src/07-high-score.md +++ b/src/07-high-score.md @@ -13,8 +13,8 @@ This will require two parts: DragonRuby GTK gives us two handy methods to do so: -- `args.gtk.write_file("high-score.txt")` -- `args.gtk.read_file("high-score.txt")` +- `DR.write_file("high-score.txt")` +- `DR.read_file("high-score.txt")` Save data, especially for our game, can be as simple as a text file on the computer. While this may allow for save tampering, that's not something we're too worried about for our game. The foundations of writing our data to a file and loading it apply no matter how complex our game data persistent needs get. diff --git a/src/08-sound.md b/src/08-sound.md index bb5964c..f96813a 100644 --- a/src/08-sound.md +++ b/src/08-sound.md @@ -32,7 +32,7 @@ Easy enough! Playing music is a little more complex, but still pretty simple. You set a value on the `args.audio` hash that lets you manage the audio track across the game loop. You can check the current progress, whether or not it's paused, and more. That looks like this: ``` ruby -if args.state.tick_count == 1 +if Kernel.tick_count == 1 args.audio[:music] = { input: "sounds/flight.ogg", looping: true } end ``` diff --git a/src/11-scenes.md b/src/11-scenes.md index 21b721c..887b905 100644 --- a/src/11-scenes.md +++ b/src/11-scenes.md @@ -6,6 +6,36 @@ The concept of a "scene" isn't specific to DragonRuby Game Toolkit or any given When we introduced the game over functionality, we introduced a scene separate from gameplay. Kinda neat! But it wasn't the time to think about them in terms of scenes and reckon with how we add more. +Before we get into scenes, though, there's one structural change we should apply. + +## Wrapping It in `module Main` + +You may have noticed when you opened the default `main.rb` back in Chapter 1 that everything was wrapped in `module Main ... end`. We dropped that wrapper in our earlier chapters to keep the code small while we were learning. Now that we have a real game, let's bring our code in line with what DragonRuby's docs and sample apps use. + +The change is mechanical: indent everything in `mygame/app/main.rb` two spaces, then wrap it in a `module Main` block. The trailing `DR.reset` stays at the top level (we want it to run when the file is loaded, not when `tick` is called): + +``` ruby +module Main + FPS = 60 + + def spawn_target(args) + # ... + end + + # ...all the other methods, indented... + + def tick args + # ... + end +end + +DR.reset +``` + +That's it. `module Main` is a Ruby module, a namespace that groups related code together. DragonRuby looks for a module called `Main` and calls its `tick` method 60 times a second, just like before. Wrapping our methods inside it keeps them from spilling into Ruby's global scope, which is a healthier habit as your projects grow. + +From this point on, the code samples in the rest of the book are shown with the `module Main` wrapper applied. The chapter 11 and 12 sample files (`code/chapter_11/01_refactor`, `code/chapter_11/02_title`, `code/chapter_12/01_release`) all use this form. + ## Refactor So we've got two scenes right now: gameplay (where we shoot at targets) and game over (where we display the score and allow the player to restart). Let's refactor the code to put our scenes in different methods and allow the game to switch between them given certain conditions being met. @@ -19,7 +49,7 @@ Let's introduce a `#gameplay_tick` method that will contain the logic for our ga Not much of the code changes, but we do shuffle things around a bit. None of the methods above `#game_over_tick` change, so they're excluded: ``` ruby -{{#include code/chapter_11/01_refactor/app/main.rb:50:204}} +{{#include code/chapter_11/01_refactor/app/main.rb:51:205}} ``` `#game_over_tick` is the same except for the addition of: @@ -53,7 +83,7 @@ When players launch our game, they get dropped right into the gameplay. This can In our game code, let's introduce `#title_tick` that takes `args` as its only parameter, just like our other `*_tick` methods for our scenes. In `#title_tick`, we'll render some labels and look for input to start our game. If the fireball input is pressed, we'll play a sound effect, change the scene, and return early so we can move on to the next scene. ``` ruby -{{#include code/chapter_11/02_title/app/main.rb:196:232}} +{{#include code/chapter_11/02_title/app/main.rb:197:233}} ``` Replace `YOU` with your name since you made it. It's important to take credit for your work. @@ -61,7 +91,7 @@ Replace `YOU` with your name since you made it. It's important to take credit fo Then in `#tick` lazily initialize `args.state.scene` to now be `"title"`: ``` ruby -{{#include code/chapter_11/02_title/app/main.rb:234:242}} +{{#include code/chapter_11/02_title/app/main.rb:235:243}} ``` Now when you start the game, the title scene will be displayed: diff --git a/src/code/chapter_01/01_default/app/main.rb b/src/code/chapter_01/01_default/app/main.rb index 3c5c836..d418619 100644 --- a/src/code/chapter_01/01_default/app/main.rb +++ b/src/code/chapter_01/01_default/app/main.rb @@ -1,18 +1,87 @@ -def tick args - args.outputs.labels << [640, 540, 'Hello World!', 5, 1] - args.outputs.labels << [640, 500, 'Docs located at ./docs/docs.html and 100+ samples located under ./samples', 5, 1] - args.outputs.labels << [640, 460, 'Join the Discord server! https://discord.dragonruby.org', 5, 1] - - args.outputs.sprites << { x: 576, - y: 280, - w: 128, - h: 101, - path: 'dragonruby.png', - angle: args.state.tick_count } - - args.outputs.labels << { x: 640, - y: 60, - text: './mygame/app/main.rb', - size_enum: 5, - alignment_enum: 1 } +module Main + def tick args + args.state.logo_rect ||= { x: 576, + y: 200, + w: 128, + h: 101 } + + args.outputs.labels << { x: 640, + y: 600, + text: 'Hello World!', + size_px: 30, + anchor_x: 0.5, + anchor_y: 0.5 } + + args.outputs.labels << { x: 640, + y: 510, + text: "Documentation is located under the ./docs directory. 150+ samples are located under the ./samples directory.", + size_px: 20, + anchor_x: 0.5, + anchor_y: 0.5 } + + args.outputs.labels << { x: 640, + y: 480, + text: "You can also access these docs online at docs.dragonruby.org.", + size_px: 20, + anchor_x: 0.5, + anchor_y: 0.5 } + + args.outputs.labels << { x: 640, + y: 400, + text: "The code that powers what you're seeing right now is located at ./mygame/app/main.rb.", + size_px: 20, + anchor_x: 0.5, + anchor_y: 0.5 } + + args.outputs.labels << { x: 640, + y: 380, + text: "(you can change the code while the app is running and see the updates live)", + size_px: 20, + anchor_x: 0.5, + anchor_y: 0.5 } + + args.outputs.sprites << { x: args.state.logo_rect.x, + y: args.state.logo_rect.y, + w: args.state.logo_rect.w, + h: args.state.logo_rect.h, + path: 'dragonruby.png', + angle: Kernel.tick_count } + + args.outputs.labels << { x: 640, + y: 180, + text: "(use arrow keys to move the logo around)", + size_px: 20, + anchor_x: 0.5, + anchor_y: 0.5 } + + args.outputs.labels << { x: 640, + y: 80, + text: 'Join the Discord Server! https://discord.dragonruby.org', + size_px: 30, + anchor_x: 0.5 } + + if args.inputs.keyboard.left + args.state.logo_rect.x -= 10 + elsif args.inputs.keyboard.right + args.state.logo_rect.x += 10 + end + + if args.inputs.keyboard.down + args.state.logo_rect.y -= 10 + elsif args.inputs.keyboard.up + args.state.logo_rect.y += 10 + end + + if args.state.logo_rect.x > 1280 + args.state.logo_rect.x = 0 + elsif args.state.logo_rect.x < 0 + args.state.logo_rect.x = 1280 + end + + if args.state.logo_rect.y > 720 + args.state.logo_rect.y = 0 + elsif args.state.logo_rect.y < 0 + args.state.logo_rect.y = 720 + end + end end diff --git a/src/code/chapter_01/02_our_own_hello/app/main.rb b/src/code/chapter_01/02_our_own_hello/app/main.rb index 427fafd..34b67a1 100644 --- a/src/code/chapter_01/02_our_own_hello/app/main.rb +++ b/src/code/chapter_01/02_our_own_hello/app/main.rb @@ -3,7 +3,7 @@ def greet friend end def tick args - args.outputs.labels << [120, 120, greet("Dragon")] - args.outputs.labels << [120, 100, greet("Oboe")] - args.outputs.sprites << [120, 280, 100, 80, 'sprites/misc/dragon-0.png'] + args.outputs.labels << { x: 120, y: 120, text: greet("Dragon") } + args.outputs.labels << { x: 120, y: 100, text: greet("Oboe") } + args.outputs.sprites << { x: 120, y: 280, w: 100, h: 80, path: 'sprites/misc/dragon-0.png' } end diff --git a/src/code/chapter_02/app/main.rb b/src/code/chapter_02/app/main.rb index 47b5bdd..ef9f0cb 100644 --- a/src/code/chapter_02/app/main.rb +++ b/src/code/chapter_02/app/main.rb @@ -33,5 +33,11 @@ def tick args args.state.player_y = 0 end - args.outputs.sprites << [args.state.player_x, args.state.player_y, player_w, player_h, 'sprites/misc/dragon-0.png'] + args.outputs.sprites << { + x: args.state.player_x, + y: args.state.player_y, + w: player_w, + h: player_h, + path: 'sprites/misc/dragon-0.png', + } end diff --git a/src/code/chapter_03/01_input/app/main.rb b/src/code/chapter_03/01_input/app/main.rb index 56f6246..54844a6 100644 --- a/src/code/chapter_03/01_input/app/main.rb +++ b/src/code/chapter_03/01_input/app/main.rb @@ -37,5 +37,11 @@ def tick args puts "Z key pressed" end - args.outputs.sprites << [args.state.player_x, args.state.player_y, player_w, player_h, 'sprites/misc/dragon-0.png'] + args.outputs.sprites << { + x: args.state.player_x, + y: args.state.player_y, + w: player_w, + h: player_h, + path: 'sprites/misc/dragon-0.png', + } end diff --git a/src/code/chapter_03/02_input_expanded/app/main.rb b/src/code/chapter_03/02_input_expanded/app/main.rb index 1686fa2..20a0d93 100644 --- a/src/code/chapter_03/02_input_expanded/app/main.rb +++ b/src/code/chapter_03/02_input_expanded/app/main.rb @@ -39,5 +39,11 @@ def tick args puts "Spit fireball!" end - args.outputs.sprites << [args.state.player_x, args.state.player_y, player_w, player_h, 'sprites/misc/dragon-0.png'] + args.outputs.sprites << { + x: args.state.player_x, + y: args.state.player_y, + w: player_w, + h: player_h, + path: 'sprites/misc/dragon-0.png', + } end diff --git a/src/code/chapter_03/03_displaying_fireballs/app/main.rb b/src/code/chapter_03/03_displaying_fireballs/app/main.rb index 9f5fc3b..bff5103 100644 --- a/src/code/chapter_03/03_displaying_fireballs/app/main.rb +++ b/src/code/chapter_03/03_displaying_fireballs/app/main.rb @@ -38,10 +38,20 @@ def tick args if args.inputs.keyboard.key_down.z || args.inputs.keyboard.key_down.j || args.inputs.controller_one.key_down.a - args.state.fireballs << [args.state.player_x, args.state.player_y, 'fireball'] + args.state.fireballs << { + x: args.state.player_x, + y: args.state.player_y, + text: 'fireball', + } end args.outputs.labels << args.state.fireballs - args.outputs.sprites << [args.state.player_x, args.state.player_y, player_w, player_h, 'sprites/misc/dragon-0.png'] + args.outputs.sprites << { + x: args.state.player_x, + y: args.state.player_y, + w: player_w, + h: player_h, + path: 'sprites/misc/dragon-0.png', + } end diff --git a/src/code/chapter_03/04_moving_fireballs/app/main.rb b/src/code/chapter_03/04_moving_fireballs/app/main.rb index 07196e7..956245e 100644 --- a/src/code/chapter_03/04_moving_fireballs/app/main.rb +++ b/src/code/chapter_03/04_moving_fireballs/app/main.rb @@ -38,14 +38,24 @@ def tick args if args.inputs.keyboard.key_down.z || args.inputs.keyboard.key_down.j || args.inputs.controller_one.key_down.a - args.state.fireballs << [args.state.player_x, args.state.player_y, 'fireball'] + args.state.fireballs << { + x: args.state.player_x, + y: args.state.player_y, + text: 'fireball', + } end args.state.fireballs.each do |fireball| - fireball[0] += speed + 2 + fireball.x += speed + 2 end args.outputs.labels << args.state.fireballs - args.outputs.sprites << [args.state.player_x, args.state.player_y, player_w, player_h, 'sprites/misc/dragon-0.png'] + args.outputs.sprites << { + x: args.state.player_x, + y: args.state.player_y, + w: player_w, + h: player_h, + path: 'sprites/misc/dragon-0.png', + } end diff --git a/src/code/chapter_04/01_display_targets/app/main.rb b/src/code/chapter_04/01_display_targets/app/main.rb index 16278d3..adec8d9 100644 --- a/src/code/chapter_04/01_display_targets/app/main.rb +++ b/src/code/chapter_04/01_display_targets/app/main.rb @@ -79,4 +79,4 @@ def tick args args.outputs.sprites << [args.state.player, args.state.fireballs, args.state.targets] end -$gtk.reset +DR.reset diff --git a/src/code/chapter_04/02_target_method/app/main.rb b/src/code/chapter_04/02_target_method/app/main.rb index 8971e72..0fc2235 100644 --- a/src/code/chapter_04/02_target_method/app/main.rb +++ b/src/code/chapter_04/02_target_method/app/main.rb @@ -71,4 +71,4 @@ def tick args args.outputs.sprites << [args.state.player, args.state.fireballs, args.state.targets] end -$gtk.reset +DR.reset diff --git a/src/code/chapter_04/03_collision_detection/app/main.rb b/src/code/chapter_04/03_collision_detection/app/main.rb index 9276a42..5d7b5c3 100644 --- a/src/code/chapter_04/03_collision_detection/app/main.rb +++ b/src/code/chapter_04/03_collision_detection/app/main.rb @@ -77,4 +77,4 @@ def tick args args.outputs.sprites << [args.state.player, args.state.fireballs, args.state.targets] end -$gtk.reset +DR.reset diff --git a/src/code/chapter_04/04_remove_targets/app/main.rb b/src/code/chapter_04/04_remove_targets/app/main.rb index eade5a3..b348a38 100644 --- a/src/code/chapter_04/04_remove_targets/app/main.rb +++ b/src/code/chapter_04/04_remove_targets/app/main.rb @@ -81,4 +81,4 @@ def tick args args.outputs.sprites << [args.state.player, args.state.fireballs, args.state.targets] end -$gtk.reset +DR.reset diff --git a/src/code/chapter_04/05_spawn_new_targets/app/main.rb b/src/code/chapter_04/05_spawn_new_targets/app/main.rb index 56a1d78..9c5aee3 100644 --- a/src/code/chapter_04/05_spawn_new_targets/app/main.rb +++ b/src/code/chapter_04/05_spawn_new_targets/app/main.rb @@ -81,4 +81,4 @@ def tick args args.outputs.sprites << [args.state.player, args.state.fireballs, args.state.targets] end -$gtk.reset +DR.reset diff --git a/src/code/chapter_04/06_score_tracking/app/main.rb b/src/code/chapter_04/06_score_tracking/app/main.rb index 039a1b5..a2b8928 100644 --- a/src/code/chapter_04/06_score_tracking/app/main.rb +++ b/src/code/chapter_04/06_score_tracking/app/main.rb @@ -85,8 +85,8 @@ def tick args x: 40, y: args.grid.h - 40, text: "Score: #{args.state.score}", - size_enum: 4 + size_px: 30 } end -$gtk.reset +DR.reset diff --git a/src/code/chapter_05/01_tracking_offscreen/app/main.rb b/src/code/chapter_05/01_tracking_offscreen/app/main.rb index 71c394e..f6af92d 100644 --- a/src/code/chapter_05/01_tracking_offscreen/app/main.rb +++ b/src/code/chapter_05/01_tracking_offscreen/app/main.rb @@ -85,7 +85,7 @@ def tick args x: 40, y: args.grid.h - 40, text: "Score: #{args.state.score}", - size_enum: 4 + size_px: 30 } args.outputs.debug << { @@ -100,4 +100,4 @@ def tick args }.label! end -$gtk.reset +DR.reset diff --git a/src/code/chapter_05/02_optimizing/app/main.rb b/src/code/chapter_05/02_optimizing/app/main.rb index b1e405a..89b49a5 100644 --- a/src/code/chapter_05/02_optimizing/app/main.rb +++ b/src/code/chapter_05/02_optimizing/app/main.rb @@ -90,7 +90,7 @@ def tick args x: 40, y: args.grid.h - 40, text: "Score: #{args.state.score}", - size_enum: 4 + size_px: 30 } args.outputs.debug << { @@ -105,4 +105,4 @@ def tick args }.label! end -$gtk.reset +DR.reset diff --git a/src/code/chapter_06/01_time_attack/app/main.rb b/src/code/chapter_06/01_time_attack/app/main.rb index 890a423..c982a32 100644 --- a/src/code/chapter_06/01_time_attack/app/main.rb +++ b/src/code/chapter_06/01_time_attack/app/main.rb @@ -33,26 +33,26 @@ def tick args x: 40, y: args.grid.h - 40, text: "Game Over!", - size_enum: 10, + size_px: 42, } labels << { x: 40, y: args.grid.h - 90, text: "Score: #{args.state.score}", - size_enum: 4, + size_px: 30, } labels << { x: 40, y: args.grid.h - 132, text: "Fire to restart", - size_enum: 2, + size_px: 26, } args.outputs.labels << labels if args.inputs.keyboard.key_down.z || args.inputs.keyboard.key_down.j || args.inputs.controller_one.key_down.a - $gtk.reset + DR.reset end return @@ -126,16 +126,16 @@ def tick args x: 40, y: args.grid.h - 40, text: "Score: #{args.state.score}", - size_enum: 4, + size_px: 30, } labels << { x: args.grid.w - 40, y: args.grid.h - 40, text: "Time Left: #{(args.state.timer / 60).round}", - size_enum: 2, - alignment_enum: 2, + size_px: 26, + anchor_x: 1, } args.outputs.labels << labels end -$gtk.reset +DR.reset diff --git a/src/code/chapter_06/02_restart_grace_period/app/main.rb b/src/code/chapter_06/02_restart_grace_period/app/main.rb index 47ede2c..ddeeff7 100644 --- a/src/code/chapter_06/02_restart_grace_period/app/main.rb +++ b/src/code/chapter_06/02_restart_grace_period/app/main.rb @@ -33,19 +33,19 @@ def tick args x: 40, y: args.grid.h - 40, text: "Game Over!", - size_enum: 10, + size_px: 42, } labels << { x: 40, y: args.grid.h - 90, text: "Score: #{args.state.score}", - size_enum: 4, + size_px: 30, } labels << { x: 40, y: args.grid.h - 132, text: "Fire to restart", - size_enum: 2, + size_px: 26, } args.outputs.labels << labels @@ -53,7 +53,7 @@ def tick args (args.inputs.keyboard.key_down.z || args.inputs.keyboard.key_down.j || args.inputs.controller_one.key_down.a) - $gtk.reset + DR.reset end return @@ -127,16 +127,16 @@ def tick args x: 40, y: args.grid.h - 40, text: "Score: #{args.state.score}", - size_enum: 4, + size_px: 30, } labels << { x: args.grid.w - 40, y: args.grid.h - 40, text: "Time Left: #{(args.state.timer / 60).round}", - size_enum: 2, - alignment_enum: 2, + size_px: 26, + anchor_x: 1, } args.outputs.labels << labels end -$gtk.reset +DR.reset diff --git a/src/code/chapter_06/03_refactor/app/main.rb b/src/code/chapter_06/03_refactor/app/main.rb index d743052..5be2704 100644 --- a/src/code/chapter_06/03_refactor/app/main.rb +++ b/src/code/chapter_06/03_refactor/app/main.rb @@ -53,24 +53,24 @@ def game_over_tick(args) x: 40, y: args.grid.h - 40, text: "Game Over!", - size_enum: 10, + size_px: 42, } labels << { x: 40, y: args.grid.h - 90, text: "Score: #{args.state.score}", - size_enum: 4, + size_px: 30, } labels << { x: 40, y: args.grid.h - 132, text: "Fire to restart", - size_enum: 2, + size_px: 26, } args.outputs.labels << labels if args.state.timer < -30 && fire_input?(args) - $gtk.reset + DR.reset end end @@ -137,16 +137,16 @@ def tick args x: 40, y: args.grid.h - 40, text: "Score: #{args.state.score}", - size_enum: 4, + size_px: 30, } labels << { x: args.grid.w - 40, y: args.grid.h - 40, text: "Time Left: #{(args.state.timer / FPS).round}", - size_enum: 2, - alignment_enum: 2, + size_px: 26, + anchor_x: 1, } args.outputs.labels << labels end -$gtk.reset +DR.reset diff --git a/src/code/chapter_07/01_load_and_save_data/app/main.rb b/src/code/chapter_07/01_load_and_save_data/app/main.rb index 6ef7c19..0a7fd6c 100644 --- a/src/code/chapter_07/01_load_and_save_data/app/main.rb +++ b/src/code/chapter_07/01_load_and_save_data/app/main.rb @@ -49,10 +49,10 @@ def handle_player_movement(args) HIGH_SCORE_FILE = "high-score.txt" def game_over_tick(args) - args.state.high_score ||= args.gtk.read_file(HIGH_SCORE_FILE).to_i + args.state.high_score ||= DR.read_file(HIGH_SCORE_FILE).to_i if !args.state.saved_high_score && args.state.score > args.state.high_score - args.gtk.write_file(HIGH_SCORE_FILE, args.state.score.to_s) + DR.write_file(HIGH_SCORE_FILE, args.state.score.to_s) args.state.saved_high_score = true end @@ -61,13 +61,13 @@ def game_over_tick(args) x: 40, y: args.grid.h - 40, text: "Game Over!", - size_enum: 10, + size_px: 42, } labels << { x: 40, y: args.grid.h - 90, text: "Score: #{args.state.score}", - size_enum: 4, + size_px: 30, } if args.state.score > args.state.high_score @@ -75,14 +75,14 @@ def game_over_tick(args) x: 260, y: args.grid.h - 90, text: "New high-score!", - size_enum: 3, + size_px: 28, } else labels << { x: 260, y: args.grid.h - 90, text: "Score to beat: #{args.state.high_score}", - size_enum: 3, + size_px: 28, } end @@ -90,12 +90,12 @@ def game_over_tick(args) x: 40, y: args.grid.h - 132, text: "Fire to restart", - size_enum: 2, + size_px: 26, } args.outputs.labels << labels if args.state.timer < -30 && fire_input?(args) - $gtk.reset + DR.reset end end @@ -162,16 +162,16 @@ def tick args x: 40, y: args.grid.h - 40, text: "Score: #{args.state.score}", - size_enum: 4, + size_px: 30, } labels << { x: args.grid.w - 40, y: args.grid.h - 40, text: "Time Left: #{(args.state.timer / FPS).round}", - size_enum: 2, - alignment_enum: 2, + size_px: 26, + anchor_x: 1, } args.outputs.labels << labels end -$gtk.reset +DR.reset diff --git a/src/code/chapter_08/01_sound/app/main.rb b/src/code/chapter_08/01_sound/app/main.rb index c25ad11..829a97f 100644 --- a/src/code/chapter_08/01_sound/app/main.rb +++ b/src/code/chapter_08/01_sound/app/main.rb @@ -49,10 +49,10 @@ def handle_player_movement(args) HIGH_SCORE_FILE = "high-score.txt" def game_over_tick(args) - args.state.high_score ||= args.gtk.read_file(HIGH_SCORE_FILE).to_i + args.state.high_score ||= DR.read_file(HIGH_SCORE_FILE).to_i if !args.state.saved_high_score && args.state.score > args.state.high_score - args.gtk.write_file(HIGH_SCORE_FILE, args.state.score.to_s) + DR.write_file(HIGH_SCORE_FILE, args.state.score.to_s) args.state.saved_high_score = true end @@ -61,13 +61,13 @@ def game_over_tick(args) x: 40, y: args.grid.h - 40, text: "Game Over!", - size_enum: 10, + size_px: 42, } labels << { x: 40, y: args.grid.h - 90, text: "Score: #{args.state.score}", - size_enum: 4, + size_px: 30, } if args.state.score > args.state.high_score @@ -75,14 +75,14 @@ def game_over_tick(args) x: 260, y: args.grid.h - 90, text: "New high-score!", - size_enum: 3, + size_px: 28, } else labels << { x: 260, y: args.grid.h - 90, text: "Score to beat: #{args.state.high_score}", - size_enum: 3, + size_px: 28, } end @@ -90,17 +90,17 @@ def game_over_tick(args) x: 40, y: args.grid.h - 132, text: "Fire to restart", - size_enum: 2, + size_px: 26, } args.outputs.labels << labels if args.state.timer < -30 && fire_input?(args) - $gtk.reset + DR.reset end end def tick args - if args.state.tick_count == 1 + if Kernel.tick_count == 1 args.audio[:music] = { input: "sounds/flight.ogg", looping: true } end @@ -173,16 +173,16 @@ def tick args x: 40, y: args.grid.h - 40, text: "Score: #{args.state.score}", - size_enum: 4, + size_px: 30, } labels << { x: args.grid.w - 40, y: args.grid.h - 40, text: "Time Left: #{(args.state.timer / FPS).round}", - size_enum: 2, - alignment_enum: 2, + size_px: 26, + anchor_x: 1, } args.outputs.labels << labels end -$gtk.reset +DR.reset diff --git a/src/code/chapter_09/01_blue_sky/app/main.rb b/src/code/chapter_09/01_blue_sky/app/main.rb index 54c32ef..ee34d15 100644 --- a/src/code/chapter_09/01_blue_sky/app/main.rb +++ b/src/code/chapter_09/01_blue_sky/app/main.rb @@ -49,10 +49,10 @@ def handle_player_movement(args) HIGH_SCORE_FILE = "high-score.txt" def game_over_tick(args) - args.state.high_score ||= args.gtk.read_file(HIGH_SCORE_FILE).to_i + args.state.high_score ||= DR.read_file(HIGH_SCORE_FILE).to_i if !args.state.saved_high_score && args.state.score > args.state.high_score - args.gtk.write_file(HIGH_SCORE_FILE, args.state.score.to_s) + DR.write_file(HIGH_SCORE_FILE, args.state.score.to_s) args.state.saved_high_score = true end @@ -61,13 +61,13 @@ def game_over_tick(args) x: 40, y: args.grid.h - 40, text: "Game Over!", - size_enum: 10, + size_px: 42, } labels << { x: 40, y: args.grid.h - 90, text: "Score: #{args.state.score}", - size_enum: 4, + size_px: 30, } if args.state.score > args.state.high_score @@ -75,14 +75,14 @@ def game_over_tick(args) x: 260, y: args.grid.h - 90, text: "New high-score!", - size_enum: 3, + size_px: 28, } else labels << { x: 260, y: args.grid.h - 90, text: "Score to beat: #{args.state.high_score}", - size_enum: 3, + size_px: 28, } end @@ -90,17 +90,17 @@ def game_over_tick(args) x: 40, y: args.grid.h - 132, text: "Fire to restart", - size_enum: 2, + size_px: 26, } args.outputs.labels << labels if args.state.timer < -30 && fire_input?(args) - $gtk.reset + DR.reset end end def tick args - if args.state.tick_count == 1 + if Kernel.tick_count == 1 args.audio[:music] = { input: "sounds/flight.ogg", looping: true } end @@ -183,16 +183,16 @@ def tick args x: 40, y: args.grid.h - 40, text: "Score: #{args.state.score}", - size_enum: 4, + size_px: 30, } labels << { x: args.grid.w - 40, y: args.grid.h - 40, text: "Time Left: #{(args.state.timer / FPS).round}", - size_enum: 2, - alignment_enum: 2, + size_px: 26, + anchor_x: 1, } args.outputs.labels << labels end -$gtk.reset +DR.reset diff --git a/src/code/chapter_10/01_animating/app/main.rb b/src/code/chapter_10/01_animating/app/main.rb index 63ec78f..8d800c9 100644 --- a/src/code/chapter_10/01_animating/app/main.rb +++ b/src/code/chapter_10/01_animating/app/main.rb @@ -49,10 +49,10 @@ def handle_player_movement(args) HIGH_SCORE_FILE = "high-score.txt" def game_over_tick(args) - args.state.high_score ||= args.gtk.read_file(HIGH_SCORE_FILE).to_i + args.state.high_score ||= DR.read_file(HIGH_SCORE_FILE).to_i if !args.state.saved_high_score && args.state.score > args.state.high_score - args.gtk.write_file(HIGH_SCORE_FILE, args.state.score.to_s) + DR.write_file(HIGH_SCORE_FILE, args.state.score.to_s) args.state.saved_high_score = true end @@ -61,13 +61,13 @@ def game_over_tick(args) x: 40, y: args.grid.h - 40, text: "Game Over!", - size_enum: 10, + size_px: 42, } labels << { x: 40, y: args.grid.h - 90, text: "Score: #{args.state.score}", - size_enum: 4, + size_px: 30, } if args.state.score > args.state.high_score @@ -75,14 +75,14 @@ def game_over_tick(args) x: 260, y: args.grid.h - 90, text: "New high-score!", - size_enum: 3, + size_px: 28, } else labels << { x: 260, y: args.grid.h - 90, text: "Score to beat: #{args.state.high_score}", - size_enum: 3, + size_px: 28, } end @@ -90,17 +90,17 @@ def game_over_tick(args) x: 40, y: args.grid.h - 132, text: "Fire to restart", - size_enum: 2, + size_px: 26, } args.outputs.labels << labels if args.state.timer < -30 && fire_input?(args) - $gtk.reset + DR.reset end end def tick args - if args.state.tick_count == 1 + if Kernel.tick_count == 1 args.audio[:music] = { input: "sounds/flight.ogg", looping: true } end @@ -186,16 +186,16 @@ def tick args x: 40, y: args.grid.h - 40, text: "Score: #{args.state.score}", - size_enum: 4, + size_px: 30, } labels << { x: args.grid.w - 40, y: args.grid.h - 40, text: "Time Left: #{(args.state.timer / FPS).round}", - size_enum: 2, - alignment_enum: 2, + size_px: 26, + anchor_x: 1, } args.outputs.labels << labels end -$gtk.reset +DR.reset diff --git a/src/code/chapter_11/01_refactor/app/main.rb b/src/code/chapter_11/01_refactor/app/main.rb index c9b6a0f..8509095 100644 --- a/src/code/chapter_11/01_refactor/app/main.rb +++ b/src/code/chapter_11/01_refactor/app/main.rb @@ -1,206 +1,208 @@ -FPS = 60 - -def spawn_target(args) - size = 64 - { - x: rand(args.grid.w * 0.4) + args.grid.w * 0.6, - y: rand(args.grid.h - size * 2) + size, - w: size, - h: size, - path: 'sprites/target.png', - } -end - -def fire_input?(args) - args.inputs.keyboard.key_down.z || - args.inputs.keyboard.key_down.j || - args.inputs.controller_one.key_down.a -end - -def handle_player_movement(args) - if args.inputs.left - args.state.player.x -= args.state.player.speed - elsif args.inputs.right - args.state.player.x += args.state.player.speed +module Main + FPS = 60 + + def spawn_target(args) + size = 64 + { + x: rand(args.grid.w * 0.4) + args.grid.w * 0.6, + y: rand(args.grid.h - size * 2) + size, + w: size, + h: size, + path: 'sprites/target.png', + } end - if args.inputs.up - args.state.player.y += args.state.player.speed - elsif args.inputs.down - args.state.player.y -= args.state.player.speed + def fire_input?(args) + args.inputs.keyboard.key_down.z || + args.inputs.keyboard.key_down.j || + args.inputs.controller_one.key_down.a end - if args.state.player.x + args.state.player.w > args.grid.w - args.state.player.x = args.grid.w - args.state.player.w - end + def handle_player_movement(args) + if args.inputs.left + args.state.player.x -= args.state.player.speed + elsif args.inputs.right + args.state.player.x += args.state.player.speed + end - if args.state.player.x < 0 - args.state.player.x = 0 - end + if args.inputs.up + args.state.player.y += args.state.player.speed + elsif args.inputs.down + args.state.player.y -= args.state.player.speed + end - if args.state.player.y + args.state.player.h > args.grid.h - args.state.player.y = args.grid.h - args.state.player.h - end + if args.state.player.x + args.state.player.w > args.grid.w + args.state.player.x = args.grid.w - args.state.player.w + end - if args.state.player.y < 0 - args.state.player.y = 0 + if args.state.player.x < 0 + args.state.player.x = 0 + end + + if args.state.player.y + args.state.player.h > args.grid.h + args.state.player.y = args.grid.h - args.state.player.h + end + + if args.state.player.y < 0 + args.state.player.y = 0 + end end -end -HIGH_SCORE_FILE = "high-score.txt" -def game_over_tick(args) - args.state.high_score ||= args.gtk.read_file(HIGH_SCORE_FILE).to_i + HIGH_SCORE_FILE = "high-score.txt" + def game_over_tick(args) + args.state.high_score ||= DR.read_file(HIGH_SCORE_FILE).to_i - args.state.timer -= 1 + args.state.timer -= 1 - if !args.state.saved_high_score && args.state.score > args.state.high_score - args.gtk.write_file(HIGH_SCORE_FILE, args.state.score.to_s) - args.state.saved_high_score = true - end + if !args.state.saved_high_score && args.state.score > args.state.high_score + DR.write_file(HIGH_SCORE_FILE, args.state.score.to_s) + args.state.saved_high_score = true + end - labels = [] - labels << { - x: 40, - y: args.grid.h - 40, - text: "Game Over!", - size_enum: 10, - } - labels << { - x: 40, - y: args.grid.h - 90, - text: "Score: #{args.state.score}", - size_enum: 4, - } - - if args.state.score > args.state.high_score + labels = [] labels << { - x: 260, - y: args.grid.h - 90, - text: "New high-score!", - size_enum: 3, + x: 40, + y: args.grid.h - 40, + text: "Game Over!", + size_px: 42, } - else labels << { - x: 260, + x: 40, y: args.grid.h - 90, - text: "Score to beat: #{args.state.high_score}", - size_enum: 3, + text: "Score: #{args.state.score}", + size_px: 30, } - end - labels << { - x: 40, - y: args.grid.h - 132, - text: "Fire to restart", - size_enum: 2, - } - args.outputs.labels << labels + if args.state.score > args.state.high_score + labels << { + x: 260, + y: args.grid.h - 90, + text: "New high-score!", + size_px: 28, + } + else + labels << { + x: 260, + y: args.grid.h - 90, + text: "Score to beat: #{args.state.high_score}", + size_px: 28, + } + end - if args.state.timer < -30 && fire_input?(args) - $gtk.reset - end -end + labels << { + x: 40, + y: args.grid.h - 132, + text: "Fire to restart", + size_px: 26, + } + args.outputs.labels << labels -def gameplay_tick(args) - args.outputs.solids << { - x: 0, - y: 0, - w: args.grid.w, - h: args.grid.h, - r: 92, - g: 120, - b: 230, - } - - args.state.player ||= { - x: 120, - y: 280, - w: 100, - h: 80, - speed: 12, - } - - player_sprite_index = 0.frame_index(count: 6, hold_for: 8, repeat: true) - args.state.player.path = "sprites/misc/dragon-#{player_sprite_index}.png" - - args.state.fireballs ||= [] - args.state.targets ||= [ - spawn_target(args), spawn_target(args), spawn_target(args) - ] - args.state.score ||= 0 - args.state.timer ||= 30 * FPS - - args.state.timer -= 1 - - if args.state.timer == 0 - args.audio[:music].paused = true - args.outputs.sounds << "sounds/game-over.wav" - args.state.scene = "game_over" - return + if args.state.timer < -30 && fire_input?(args) + DR.reset + end end - handle_player_movement(args) + def gameplay_tick(args) + args.outputs.solids << { + x: 0, + y: 0, + w: args.grid.w, + h: args.grid.h, + r: 92, + g: 120, + b: 230, + } - if fire_input?(args) - args.outputs.sounds << "sounds/fireball.wav" - args.state.fireballs << { - x: args.state.player.x + args.state.player.w - 12, - y: args.state.player.y + 10, - w: 32, - h: 32, - path: 'sprites/fireball.png', + args.state.player ||= { + x: 120, + y: 280, + w: 100, + h: 80, + speed: 12, } - end - args.state.fireballs.each do |fireball| - fireball.x += args.state.player.speed + 2 + player_sprite_index = 0.frame_index(count: 6, hold_for: 8, repeat: true) + args.state.player.path = "sprites/misc/dragon-#{player_sprite_index}.png" - if fireball.x > args.grid.w - fireball.dead = true - next + args.state.fireballs ||= [] + args.state.targets ||= [ + spawn_target(args), spawn_target(args), spawn_target(args) + ] + args.state.score ||= 0 + args.state.timer ||= 30 * FPS + + args.state.timer -= 1 + + if args.state.timer == 0 + args.audio[:music].paused = true + args.outputs.sounds << "sounds/game-over.wav" + args.state.scene = "game_over" + return + end + + handle_player_movement(args) + + if fire_input?(args) + args.outputs.sounds << "sounds/fireball.wav" + args.state.fireballs << { + x: args.state.player.x + args.state.player.w - 12, + y: args.state.player.y + 10, + w: 32, + h: 32, + path: 'sprites/fireball.png', + } end - args.state.targets.each do |target| - if args.geometry.intersect_rect?(target, fireball) - args.outputs.sounds << "sounds/target.wav" - target.dead = true + args.state.fireballs.each do |fireball| + fireball.x += args.state.player.speed + 2 + + if fireball.x > args.grid.w fireball.dead = true - args.state.score += 1 - args.state.targets << spawn_target(args) + next + end + + args.state.targets.each do |target| + if args.geometry.intersect_rect?(target, fireball) + args.outputs.sounds << "sounds/target.wav" + target.dead = true + fireball.dead = true + args.state.score += 1 + args.state.targets << spawn_target(args) + end end end - end - args.state.targets.reject! { |t| t.dead } - args.state.fireballs.reject! { |f| f.dead } - - args.outputs.sprites << [args.state.player, args.state.fireballs, args.state.targets] - - labels = [] - labels << { - x: 40, - y: args.grid.h - 40, - text: "Score: #{args.state.score}", - size_enum: 4, - } - labels << { - x: args.grid.w - 40, - y: args.grid.h - 40, - text: "Time Left: #{(args.state.timer / FPS).round}", - size_enum: 2, - alignment_enum: 2, - } - args.outputs.labels << labels -end + args.state.targets.reject! { |t| t.dead } + args.state.fireballs.reject! { |f| f.dead } -def tick args - if args.state.tick_count == 1 - args.audio[:music] = { input: "sounds/flight.ogg", looping: true } + args.outputs.sprites << [args.state.player, args.state.fireballs, args.state.targets] + + labels = [] + labels << { + x: 40, + y: args.grid.h - 40, + text: "Score: #{args.state.score}", + size_px: 30, + } + labels << { + x: args.grid.w - 40, + y: args.grid.h - 40, + text: "Time Left: #{(args.state.timer / FPS).round}", + size_px: 26, + anchor_x: 1, + } + args.outputs.labels << labels end - args.state.scene ||= "gameplay" + def tick args + if Kernel.tick_count == 1 + args.audio[:music] = { input: "sounds/flight.ogg", looping: true } + end - send("#{args.state.scene}_tick", args) + args.state.scene ||= "gameplay" + + send("#{args.state.scene}_tick", args) + end end -$gtk.reset +DR.reset diff --git a/src/code/chapter_11/02_title/app/main.rb b/src/code/chapter_11/02_title/app/main.rb index db674d3..5c1ca74 100644 --- a/src/code/chapter_11/02_title/app/main.rb +++ b/src/code/chapter_11/02_title/app/main.rb @@ -1,244 +1,246 @@ -FPS = 60 - -def spawn_target(args) - size = 64 - { - x: rand(args.grid.w * 0.4) + args.grid.w * 0.6, - y: rand(args.grid.h - size * 2) + size, - w: size, - h: size, - path: 'sprites/target.png', - } -end - -def fire_input?(args) - args.inputs.keyboard.key_down.z || - args.inputs.keyboard.key_down.j || - args.inputs.controller_one.key_down.a -end - -def handle_player_movement(args) - if args.inputs.left - args.state.player.x -= args.state.player.speed - elsif args.inputs.right - args.state.player.x += args.state.player.speed +module Main + FPS = 60 + + def spawn_target(args) + size = 64 + { + x: rand(args.grid.w * 0.4) + args.grid.w * 0.6, + y: rand(args.grid.h - size * 2) + size, + w: size, + h: size, + path: 'sprites/target.png', + } end - if args.inputs.up - args.state.player.y += args.state.player.speed - elsif args.inputs.down - args.state.player.y -= args.state.player.speed + def fire_input?(args) + args.inputs.keyboard.key_down.z || + args.inputs.keyboard.key_down.j || + args.inputs.controller_one.key_down.a end - if args.state.player.x + args.state.player.w > args.grid.w - args.state.player.x = args.grid.w - args.state.player.w - end + def handle_player_movement(args) + if args.inputs.left + args.state.player.x -= args.state.player.speed + elsif args.inputs.right + args.state.player.x += args.state.player.speed + end - if args.state.player.x < 0 - args.state.player.x = 0 - end + if args.inputs.up + args.state.player.y += args.state.player.speed + elsif args.inputs.down + args.state.player.y -= args.state.player.speed + end - if args.state.player.y + args.state.player.h > args.grid.h - args.state.player.y = args.grid.h - args.state.player.h - end + if args.state.player.x + args.state.player.w > args.grid.w + args.state.player.x = args.grid.w - args.state.player.w + end + + if args.state.player.x < 0 + args.state.player.x = 0 + end + + if args.state.player.y + args.state.player.h > args.grid.h + args.state.player.y = args.grid.h - args.state.player.h + end - if args.state.player.y < 0 - args.state.player.y = 0 + if args.state.player.y < 0 + args.state.player.y = 0 + end end -end -HIGH_SCORE_FILE = "high-score.txt" -def game_over_tick(args) - args.state.high_score ||= args.gtk.read_file(HIGH_SCORE_FILE).to_i + HIGH_SCORE_FILE = "high-score.txt" + def game_over_tick(args) + args.state.high_score ||= DR.read_file(HIGH_SCORE_FILE).to_i - args.state.timer -= 1 + args.state.timer -= 1 - if !args.state.saved_high_score && args.state.score > args.state.high_score - args.gtk.write_file(HIGH_SCORE_FILE, args.state.score.to_s) - args.state.saved_high_score = true - end + if !args.state.saved_high_score && args.state.score > args.state.high_score + DR.write_file(HIGH_SCORE_FILE, args.state.score.to_s) + args.state.saved_high_score = true + end - labels = [] - labels << { - x: 40, - y: args.grid.h - 40, - text: "Game Over!", - size_enum: 10, - } - labels << { - x: 40, - y: args.grid.h - 90, - text: "Score: #{args.state.score}", - size_enum: 4, - } - - if args.state.score > args.state.high_score + labels = [] labels << { - x: 260, - y: args.grid.h - 90, - text: "New high-score!", - size_enum: 3, + x: 40, + y: args.grid.h - 40, + text: "Game Over!", + size_px: 42, } - else labels << { - x: 260, + x: 40, y: args.grid.h - 90, - text: "Score to beat: #{args.state.high_score}", - size_enum: 3, + text: "Score: #{args.state.score}", + size_px: 30, } - end - labels << { - x: 40, - y: args.grid.h - 132, - text: "Fire to restart", - size_enum: 2, - } - args.outputs.labels << labels + if args.state.score > args.state.high_score + labels << { + x: 260, + y: args.grid.h - 90, + text: "New high-score!", + size_px: 28, + } + else + labels << { + x: 260, + y: args.grid.h - 90, + text: "Score to beat: #{args.state.high_score}", + size_px: 28, + } + end - if args.state.timer < -30 && fire_input?(args) - $gtk.reset - end -end + labels << { + x: 40, + y: args.grid.h - 132, + text: "Fire to restart", + size_px: 26, + } + args.outputs.labels << labels -def gameplay_tick(args) - args.outputs.solids << { - x: 0, - y: 0, - w: args.grid.w, - h: args.grid.h, - r: 92, - g: 120, - b: 230, - } - - args.state.player ||= { - x: 120, - y: 280, - w: 100, - h: 80, - speed: 12, - } - - player_sprite_index = 0.frame_index(count: 6, hold_for: 8, repeat: true) - args.state.player.path = "sprites/misc/dragon-#{player_sprite_index}.png" - - args.state.fireballs ||= [] - args.state.targets ||= [ - spawn_target(args), spawn_target(args), spawn_target(args) - ] - args.state.score ||= 0 - args.state.timer ||= 30 * FPS - - args.state.timer -= 1 - - if args.state.timer == 0 - args.audio[:music].paused = true - args.outputs.sounds << "sounds/game-over.wav" - args.state.scene = "game_over" - return + if args.state.timer < -30 && fire_input?(args) + DR.reset + end end - handle_player_movement(args) + def gameplay_tick(args) + args.outputs.solids << { + x: 0, + y: 0, + w: args.grid.w, + h: args.grid.h, + r: 92, + g: 120, + b: 230, + } - if fire_input?(args) - args.outputs.sounds << "sounds/fireball.wav" - args.state.fireballs << { - x: args.state.player.x + args.state.player.w - 12, - y: args.state.player.y + 10, - w: 32, - h: 32, - path: 'sprites/fireball.png', + args.state.player ||= { + x: 120, + y: 280, + w: 100, + h: 80, + speed: 12, } - end - args.state.fireballs.each do |fireball| - fireball.x += args.state.player.speed + 2 + player_sprite_index = 0.frame_index(count: 6, hold_for: 8, repeat: true) + args.state.player.path = "sprites/misc/dragon-#{player_sprite_index}.png" - if fireball.x > args.grid.w - fireball.dead = true - next + args.state.fireballs ||= [] + args.state.targets ||= [ + spawn_target(args), spawn_target(args), spawn_target(args) + ] + args.state.score ||= 0 + args.state.timer ||= 30 * FPS + + args.state.timer -= 1 + + if args.state.timer == 0 + args.audio[:music].paused = true + args.outputs.sounds << "sounds/game-over.wav" + args.state.scene = "game_over" + return + end + + handle_player_movement(args) + + if fire_input?(args) + args.outputs.sounds << "sounds/fireball.wav" + args.state.fireballs << { + x: args.state.player.x + args.state.player.w - 12, + y: args.state.player.y + 10, + w: 32, + h: 32, + path: 'sprites/fireball.png', + } end - args.state.targets.each do |target| - if args.geometry.intersect_rect?(target, fireball) - args.outputs.sounds << "sounds/target.wav" - target.dead = true + args.state.fireballs.each do |fireball| + fireball.x += args.state.player.speed + 2 + + if fireball.x > args.grid.w fireball.dead = true - args.state.score += 1 - args.state.targets << spawn_target(args) + next + end + + args.state.targets.each do |target| + if args.geometry.intersect_rect?(target, fireball) + args.outputs.sounds << "sounds/target.wav" + target.dead = true + fireball.dead = true + args.state.score += 1 + args.state.targets << spawn_target(args) + end end end - end - args.state.targets.reject! { |t| t.dead } - args.state.fireballs.reject! { |f| f.dead } - - args.outputs.sprites << [args.state.player, args.state.fireballs, args.state.targets] - - labels = [] - labels << { - x: 40, - y: args.grid.h - 40, - text: "Score: #{args.state.score}", - size_enum: 4, - } - labels << { - x: args.grid.w - 40, - y: args.grid.h - 40, - text: "Time Left: #{(args.state.timer / FPS).round}", - size_enum: 2, - alignment_enum: 2, - } - args.outputs.labels << labels -end + args.state.targets.reject! { |t| t.dead } + args.state.fireballs.reject! { |f| f.dead } + + args.outputs.sprites << [args.state.player, args.state.fireballs, args.state.targets] -def title_tick args - if fire_input?(args) - args.outputs.sounds << "sounds/game-over.wav" - args.state.scene = "gameplay" - return + labels = [] + labels << { + x: 40, + y: args.grid.h - 40, + text: "Score: #{args.state.score}", + size_px: 30, + } + labels << { + x: args.grid.w - 40, + y: args.grid.h - 40, + text: "Time Left: #{(args.state.timer / FPS).round}", + size_px: 26, + anchor_x: 1, + } + args.outputs.labels << labels end - labels = [] - labels << { - x: 40, - y: args.grid.h - 40, - text: "Target Practice", - size_enum: 6, - } - labels << { - x: 40, - y: args.grid.h - 88, - text: "Hit the targets!", - } - labels << { - x: 40, - y: args.grid.h - 120, - text: "by YOU", - } - labels << { - x: 40, - y: 120, - text: "Arrows or WASD to move | Z or J to fire | gamepad works too", - } - labels << { - x: 40, - y: 80, - text: "Fire to start", - size_enum: 2, - } - args.outputs.labels << labels -end + def title_tick args + if fire_input?(args) + args.outputs.sounds << "sounds/game-over.wav" + args.state.scene = "gameplay" + return + end -def tick args - if args.state.tick_count == 1 - args.audio[:music] = { input: "sounds/flight.ogg", looping: true } + labels = [] + labels << { + x: 40, + y: args.grid.h - 40, + text: "Target Practice", + size_px: 34, + } + labels << { + x: 40, + y: args.grid.h - 88, + text: "Hit the targets!", + } + labels << { + x: 40, + y: args.grid.h - 120, + text: "by YOU", + } + labels << { + x: 40, + y: 120, + text: "Arrows or WASD to move | Z or J to fire | gamepad works too", + } + labels << { + x: 40, + y: 80, + text: "Fire to start", + size_px: 26, + } + args.outputs.labels << labels end - args.state.scene ||= "title" + def tick args + if Kernel.tick_count == 1 + args.audio[:music] = { input: "sounds/flight.ogg", looping: true } + end + + args.state.scene ||= "title" - send("#{args.state.scene}_tick", args) + send("#{args.state.scene}_tick", args) + end end -$gtk.reset +DR.reset diff --git a/src/code/chapter_12/01_release/app/main.rb b/src/code/chapter_12/01_release/app/main.rb index db674d3..5c1ca74 100644 --- a/src/code/chapter_12/01_release/app/main.rb +++ b/src/code/chapter_12/01_release/app/main.rb @@ -1,244 +1,246 @@ -FPS = 60 - -def spawn_target(args) - size = 64 - { - x: rand(args.grid.w * 0.4) + args.grid.w * 0.6, - y: rand(args.grid.h - size * 2) + size, - w: size, - h: size, - path: 'sprites/target.png', - } -end - -def fire_input?(args) - args.inputs.keyboard.key_down.z || - args.inputs.keyboard.key_down.j || - args.inputs.controller_one.key_down.a -end - -def handle_player_movement(args) - if args.inputs.left - args.state.player.x -= args.state.player.speed - elsif args.inputs.right - args.state.player.x += args.state.player.speed +module Main + FPS = 60 + + def spawn_target(args) + size = 64 + { + x: rand(args.grid.w * 0.4) + args.grid.w * 0.6, + y: rand(args.grid.h - size * 2) + size, + w: size, + h: size, + path: 'sprites/target.png', + } end - if args.inputs.up - args.state.player.y += args.state.player.speed - elsif args.inputs.down - args.state.player.y -= args.state.player.speed + def fire_input?(args) + args.inputs.keyboard.key_down.z || + args.inputs.keyboard.key_down.j || + args.inputs.controller_one.key_down.a end - if args.state.player.x + args.state.player.w > args.grid.w - args.state.player.x = args.grid.w - args.state.player.w - end + def handle_player_movement(args) + if args.inputs.left + args.state.player.x -= args.state.player.speed + elsif args.inputs.right + args.state.player.x += args.state.player.speed + end - if args.state.player.x < 0 - args.state.player.x = 0 - end + if args.inputs.up + args.state.player.y += args.state.player.speed + elsif args.inputs.down + args.state.player.y -= args.state.player.speed + end - if args.state.player.y + args.state.player.h > args.grid.h - args.state.player.y = args.grid.h - args.state.player.h - end + if args.state.player.x + args.state.player.w > args.grid.w + args.state.player.x = args.grid.w - args.state.player.w + end + + if args.state.player.x < 0 + args.state.player.x = 0 + end + + if args.state.player.y + args.state.player.h > args.grid.h + args.state.player.y = args.grid.h - args.state.player.h + end - if args.state.player.y < 0 - args.state.player.y = 0 + if args.state.player.y < 0 + args.state.player.y = 0 + end end -end -HIGH_SCORE_FILE = "high-score.txt" -def game_over_tick(args) - args.state.high_score ||= args.gtk.read_file(HIGH_SCORE_FILE).to_i + HIGH_SCORE_FILE = "high-score.txt" + def game_over_tick(args) + args.state.high_score ||= DR.read_file(HIGH_SCORE_FILE).to_i - args.state.timer -= 1 + args.state.timer -= 1 - if !args.state.saved_high_score && args.state.score > args.state.high_score - args.gtk.write_file(HIGH_SCORE_FILE, args.state.score.to_s) - args.state.saved_high_score = true - end + if !args.state.saved_high_score && args.state.score > args.state.high_score + DR.write_file(HIGH_SCORE_FILE, args.state.score.to_s) + args.state.saved_high_score = true + end - labels = [] - labels << { - x: 40, - y: args.grid.h - 40, - text: "Game Over!", - size_enum: 10, - } - labels << { - x: 40, - y: args.grid.h - 90, - text: "Score: #{args.state.score}", - size_enum: 4, - } - - if args.state.score > args.state.high_score + labels = [] labels << { - x: 260, - y: args.grid.h - 90, - text: "New high-score!", - size_enum: 3, + x: 40, + y: args.grid.h - 40, + text: "Game Over!", + size_px: 42, } - else labels << { - x: 260, + x: 40, y: args.grid.h - 90, - text: "Score to beat: #{args.state.high_score}", - size_enum: 3, + text: "Score: #{args.state.score}", + size_px: 30, } - end - labels << { - x: 40, - y: args.grid.h - 132, - text: "Fire to restart", - size_enum: 2, - } - args.outputs.labels << labels + if args.state.score > args.state.high_score + labels << { + x: 260, + y: args.grid.h - 90, + text: "New high-score!", + size_px: 28, + } + else + labels << { + x: 260, + y: args.grid.h - 90, + text: "Score to beat: #{args.state.high_score}", + size_px: 28, + } + end - if args.state.timer < -30 && fire_input?(args) - $gtk.reset - end -end + labels << { + x: 40, + y: args.grid.h - 132, + text: "Fire to restart", + size_px: 26, + } + args.outputs.labels << labels -def gameplay_tick(args) - args.outputs.solids << { - x: 0, - y: 0, - w: args.grid.w, - h: args.grid.h, - r: 92, - g: 120, - b: 230, - } - - args.state.player ||= { - x: 120, - y: 280, - w: 100, - h: 80, - speed: 12, - } - - player_sprite_index = 0.frame_index(count: 6, hold_for: 8, repeat: true) - args.state.player.path = "sprites/misc/dragon-#{player_sprite_index}.png" - - args.state.fireballs ||= [] - args.state.targets ||= [ - spawn_target(args), spawn_target(args), spawn_target(args) - ] - args.state.score ||= 0 - args.state.timer ||= 30 * FPS - - args.state.timer -= 1 - - if args.state.timer == 0 - args.audio[:music].paused = true - args.outputs.sounds << "sounds/game-over.wav" - args.state.scene = "game_over" - return + if args.state.timer < -30 && fire_input?(args) + DR.reset + end end - handle_player_movement(args) + def gameplay_tick(args) + args.outputs.solids << { + x: 0, + y: 0, + w: args.grid.w, + h: args.grid.h, + r: 92, + g: 120, + b: 230, + } - if fire_input?(args) - args.outputs.sounds << "sounds/fireball.wav" - args.state.fireballs << { - x: args.state.player.x + args.state.player.w - 12, - y: args.state.player.y + 10, - w: 32, - h: 32, - path: 'sprites/fireball.png', + args.state.player ||= { + x: 120, + y: 280, + w: 100, + h: 80, + speed: 12, } - end - args.state.fireballs.each do |fireball| - fireball.x += args.state.player.speed + 2 + player_sprite_index = 0.frame_index(count: 6, hold_for: 8, repeat: true) + args.state.player.path = "sprites/misc/dragon-#{player_sprite_index}.png" - if fireball.x > args.grid.w - fireball.dead = true - next + args.state.fireballs ||= [] + args.state.targets ||= [ + spawn_target(args), spawn_target(args), spawn_target(args) + ] + args.state.score ||= 0 + args.state.timer ||= 30 * FPS + + args.state.timer -= 1 + + if args.state.timer == 0 + args.audio[:music].paused = true + args.outputs.sounds << "sounds/game-over.wav" + args.state.scene = "game_over" + return + end + + handle_player_movement(args) + + if fire_input?(args) + args.outputs.sounds << "sounds/fireball.wav" + args.state.fireballs << { + x: args.state.player.x + args.state.player.w - 12, + y: args.state.player.y + 10, + w: 32, + h: 32, + path: 'sprites/fireball.png', + } end - args.state.targets.each do |target| - if args.geometry.intersect_rect?(target, fireball) - args.outputs.sounds << "sounds/target.wav" - target.dead = true + args.state.fireballs.each do |fireball| + fireball.x += args.state.player.speed + 2 + + if fireball.x > args.grid.w fireball.dead = true - args.state.score += 1 - args.state.targets << spawn_target(args) + next + end + + args.state.targets.each do |target| + if args.geometry.intersect_rect?(target, fireball) + args.outputs.sounds << "sounds/target.wav" + target.dead = true + fireball.dead = true + args.state.score += 1 + args.state.targets << spawn_target(args) + end end end - end - args.state.targets.reject! { |t| t.dead } - args.state.fireballs.reject! { |f| f.dead } - - args.outputs.sprites << [args.state.player, args.state.fireballs, args.state.targets] - - labels = [] - labels << { - x: 40, - y: args.grid.h - 40, - text: "Score: #{args.state.score}", - size_enum: 4, - } - labels << { - x: args.grid.w - 40, - y: args.grid.h - 40, - text: "Time Left: #{(args.state.timer / FPS).round}", - size_enum: 2, - alignment_enum: 2, - } - args.outputs.labels << labels -end + args.state.targets.reject! { |t| t.dead } + args.state.fireballs.reject! { |f| f.dead } + + args.outputs.sprites << [args.state.player, args.state.fireballs, args.state.targets] -def title_tick args - if fire_input?(args) - args.outputs.sounds << "sounds/game-over.wav" - args.state.scene = "gameplay" - return + labels = [] + labels << { + x: 40, + y: args.grid.h - 40, + text: "Score: #{args.state.score}", + size_px: 30, + } + labels << { + x: args.grid.w - 40, + y: args.grid.h - 40, + text: "Time Left: #{(args.state.timer / FPS).round}", + size_px: 26, + anchor_x: 1, + } + args.outputs.labels << labels end - labels = [] - labels << { - x: 40, - y: args.grid.h - 40, - text: "Target Practice", - size_enum: 6, - } - labels << { - x: 40, - y: args.grid.h - 88, - text: "Hit the targets!", - } - labels << { - x: 40, - y: args.grid.h - 120, - text: "by YOU", - } - labels << { - x: 40, - y: 120, - text: "Arrows or WASD to move | Z or J to fire | gamepad works too", - } - labels << { - x: 40, - y: 80, - text: "Fire to start", - size_enum: 2, - } - args.outputs.labels << labels -end + def title_tick args + if fire_input?(args) + args.outputs.sounds << "sounds/game-over.wav" + args.state.scene = "gameplay" + return + end -def tick args - if args.state.tick_count == 1 - args.audio[:music] = { input: "sounds/flight.ogg", looping: true } + labels = [] + labels << { + x: 40, + y: args.grid.h - 40, + text: "Target Practice", + size_px: 34, + } + labels << { + x: 40, + y: args.grid.h - 88, + text: "Hit the targets!", + } + labels << { + x: 40, + y: args.grid.h - 120, + text: "by YOU", + } + labels << { + x: 40, + y: 120, + text: "Arrows or WASD to move | Z or J to fire | gamepad works too", + } + labels << { + x: 40, + y: 80, + text: "Fire to start", + size_px: 26, + } + args.outputs.labels << labels end - args.state.scene ||= "title" + def tick args + if Kernel.tick_count == 1 + args.audio[:music] = { input: "sounds/flight.ogg", looping: true } + end + + args.state.scene ||= "title" - send("#{args.state.scene}_tick", args) + send("#{args.state.scene}_tick", args) + end end -$gtk.reset +DR.reset diff --git a/src/img/hello-world.jpg b/src/img/hello-world.jpg index aa2972ca6a18dd4d8907765e744d42230b6ae174..ccf252f12e74f9b6ade536396833848f4361e52e 100644 GIT binary patch literal 22759 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 M64)LQukMfk*(d-bIRF3v literal 35111 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 diff --git a/src/img/hello-world.webp b/src/img/hello-world.webp index 5fc57e8115438cb8ad3dababe8272cb61c87bdca..28d80b3b801a52de1527be86e7b9ae55ba267e14 100644 GIT binary patch literal 22268 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 literal 23356 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 diff --git a/src/outro.md b/src/outro.md index 02ad5d4..3560f20 100644 --- a/src/outro.md +++ b/src/outro.md @@ -18,7 +18,7 @@ Here are some ideas for what you could do to put your personal stamp on it: ## Say Thanks -Say thanks in [the DragonRuby Discord](https://discord.dragonruby.org) or on [Mastodon](https://mastodon.gamedev.place/@brettmakesgames). It would mean so much to me to know that you've read the book. The website version of this book has no analytics tracking. My success metric isn't the number of views this book goods. I'll only be able to measure the success of this book through community and connecting. Share what you've made! +Say thanks in [the DragonRuby Discord](https://discord.dragonruby.org) or on [Mastodon](https://mastodon.gamedev.place/@brettmakesgames). It would mean so much to me to know that you've read the book. The website version of this book has no analytics tracking. My success metric isn't the number of views this book gets. I'll only be able to measure the success of this book through community and connecting. Share what you've made! If you really want to show your appreciation, [buy me a coffee](https://www.buymeacoffee.com/brettchalupa) by sending me a small tip. Financial support means the world and helps me continue to make free resources like _Building Games with DragonRuby_. @@ -40,7 +40,7 @@ You'll find bonus chapters after this Outro. They're one-off deep dives into spe ## Gratitude -A big thank you to the DragonRuby community. This book would not have been possible without their support and guidance. +A big thank you to the DragonRuby community. This book would not have been possible without their support and guidance. In particular, in alphabetical order, thank you to: