Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into offline
Browse files Browse the repository at this point in the history
* origin/main: (22 commits)
  chore: document that DRGTK 4.0 works
  chore: mention DragonRiders recipes in outro
  chore: add testimonials to intro
  feat: expand Game Dev Resources
  fix: introduction of #method shorthand
  fix(ch03): awkward array phrasing
  fix: alpha max val
  chore: add newsletter links
  Update 06-time-attack.md
  grammar tweaks 04-target-practice.md
  Update 05-fireball-clean-up.md
  typo/grammar stuff 03-spit-fire.md
  `consistancy` 01-hello-dragon.md
  typos 02-player-movement.md
  chore: thank vlevo
  fix(ch01): Velvo edits edits
  various grammatical tweaks to 01-hello-dragon.md
  hello dragon typo fixes
  typo
  Small grammar-esc change
  ...
  • Loading branch information
brettchalupa committed Jan 8, 2023
2 parents 7194603 + 0fc27fd commit ccd258f
Show file tree
Hide file tree
Showing 12 changed files with 89 additions and 74 deletions.
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# Building Games with DragonRuby

🚧 A work in progress! Expect typos, issues, and incompleteness. 🚧

An open source book 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)
Expand All @@ -18,8 +16,8 @@ When the code is pushed to the `main` branch on GitHub, an action runs that depl

## Key Versions

- DragonRuby Game Toolkit: v3.24
- mdbook: v0.4.21
- DragonRuby Game Toolkit: v3.24, v4.0
- mdbook: v0.4.22

## Running Samples

Expand Down
66 changes: 33 additions & 33 deletions src/01-hello-dragon.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/02-player-movement.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ end

That displays our player dragon and nothing else. Excellent. Let's get this dragon moving!

In order to handle moving the player, we need to keep track of the position across the game loops. This lets us know where the player was last `tick`, check if they should move, and then update their position accordingly.
In order to handle moving the player, we need to keep track of the position across the game loops. This lets us know where the player was last tick, check if they should move, and then update their position accordingly.

Update your `mygame/app/main.rb` to be this:

Expand Down Expand Up @@ -59,7 +59,7 @@ Then, finally, we change the `x` and `y` value for the dragon sprite to be the v

## Checking for Input

In `#tick` we'll check to see if a given input is pressed. If it is, we'll change the sprite's x and y position accordingly.
In `#tick` (`#name` is Ruby shorthand for "the `name` method") we'll check to see if a given input is pressed. If it is, we'll change the sprite's x and y position accordingly.

Our ole buddy `args` has a little something known as `args.inputs`. This lets us check... inputs! Isn't programming nice? Most of the time the words used in programming make sense. But some of the time, they really don't, and it's a gosh dang nightmare. Let's commit right here, right now to trying to name things in a way that's useful. Okay, you're committed. When you name a method `def thingy`, you'll remember this. And your ears will ring a little bit and your eyes will get just a little dry and you'll remember that you broke your commitment.

Expand Down
22 changes: 11 additions & 11 deletions src/03-spit-fire.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Our next mission is to make our dragon spit fire because that's just what they d

In the last chapter, we used `args.inputs` to check for player input from the four main directions. If those inputs are pressed, then we move the dragon. Now let's check to see if the <kbd>Z</kbd> key is pressed to know when to have our dragon spit a fireball.

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 our `#tick` method, right above where we render the dragon sprite, let's check for that input:
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}}
Expand Down Expand Up @@ -38,13 +38,13 @@ The code above says: if the <kbd>Z</kbd> key is down OR the <kbd>J</kbd> key is

You can combine `||` on one line (`if monday || tuesday || wednesday`), but it can be helpful to break long lines to make our code easier to read.

Outputting info to the console isn't that helpful for the player, but can you begin to imagine how we'll use that check to instead spit a fireball.
Outputting info to the console isn't that helpful for the player, but can you begin to imagine how we'll use that check to instead spit a fireball?

## Displaying Fireballs

We want to display a fireball on the screen when the action key is pressed. We've got most of the code, all we need to do is change `puts` to instead display the fireball.

At the top of `#tick` (`#name` is Ruby shorthand for "the `name` method"), assign an empty array to `args.state.fireballs` if nothing has been assigned yet. This will let us keep track of fireballs across the game loop to do all sorts of things with them (display them, move them, collide them with other objects):
At the top of `#tick`, assign an empty array to `args.state.fireballs` if nothing has been assigned yet. This will let us keep track of fireballs across the game loop to do all sorts of things with them (display them, move them, collide them with other objects):

``` ruby
{{#include code/chapter_03/03_displaying_fireballs/app/main.rb:1:4}}
Expand All @@ -58,7 +58,7 @@ Then where we check for the action input, push a fireball into the `arg.state.fi

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 use arrays to represent various data in our game like labels and sprites, but we can create our own arrays to keep track of the data in our game. Arrays are a great way to collect data that we then use throughout our game. In this case, we're maintaining a collection of fireballs.
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 we need in a list. The array we've created in the code above tracks our fireballs.

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.

Expand All @@ -85,13 +85,13 @@ Move your dragon around, spit some fire, and bask in the glory of a word moving

Try changing around `speed + 2` to make the fireballs move faster or slower and see how that feels. Adjusting speed values can really change the vibe of your game and is important in tuning it to feel just right.

There are a lot of important concepts in those three newly added lines of code. In Ruby, when there's an array of data, we can loop through **each** item and modify their properties. Games are composed of huge collections of things: enemies, fireballs, menu options, player inventory. Arrays (and just collections in general) aren't anything to be afraid of, and soon enough, you'll be naturally thinking of your game data in terms of arrays.
There are a lot of important concepts in those three newly added lines of code. In Ruby, when there's an array of data, we can loop through **each** item and modify their properties. Games are composed of huge collections of things: enemies, fireballs, menu options, player inventory, etc. Arrays (and just collections in general) aren't anything to be afraid of, and soon enough, you'll be naturally thinking of your game data in terms of arrays.

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, animating. As our game (and any game you make) gets more complex, looping through collections of data becomes more and more common.
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

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.
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:

Expand All @@ -113,19 +113,19 @@ 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 more clear 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`. This is much clearer than having to remember the position of these values within an array.

Quick note: if you see a hash with key-value pairs that look like this: `{ :name => "Francis" }`, don't fret! That's just an another style in Ruby known as the hash rocket.
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 represent one piece of data with multiple properties** and **arrays are used to collect data (often times hashes) to keep track of and manipulate them**.
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:

``` ruby
{{#include code/chapter_03/05_switching_to_hashes/app/main.rb}}
```

It may not seem like much has changed, but there are two key changes that make this worthwhile:
It may not seem like much has changed, but two key changes make this worthwhile:

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
Expand Down
18 changes: 9 additions & 9 deletions src/04-target-practice.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ We need to render our target sprites too, so include those in the array we push
{{#include code/chapter_04/01_display_targets/app/main.rb:79}}
```

Similar to how we represent the player and fireball sprites, the targets have an x and y coordinate for position, a width and height, and an image file to represent the sprite. We create three items in our `args.state.targets` array, which then displays three different targets.
Similar to how we represent the player and fireball sprites, the targets have x and y coordinates for position, width and height for size, and an image file to represent the sprite. We create three items in our `args.state.targets` array, which then displays three different targets.

![dragon sprite with three targets rendered to the right](./img/c04-display-targets.jpg)

Expand Down Expand Up @@ -54,9 +54,9 @@ Then when we lazily assign `args.state.targets ||=`, we call the method three ti

## Collision Detection

The fireballs our dragon spits just fly behind our targets and off the screen into infinity. Let's make it so that if a fireball hits a target, _something_ happens. Eventually we'll want to play a sound, remove the target, and even play an animation. But humble beginnings, humble beginnings.
The fireballs our dragon spits just fly behind our targets and off the screen into infinity. Let's make it so that if a fireball hits a target, _something_ happens. Eventually, we'll want to play a sound, remove the target, and even play an animation. But humble beginnings, humble beginnings.

Collision detection is when one object overlaps with another object in our game. Because we're manufacturing the space the game exists in, there's no physics like we have in real life. We need to simulate that by checking to see if two objects are attempting to exist in the same point and react accordingly.
Collision detection is when one object overlaps with another object in our game. Because we're manufacturing the space the game exists in, there's no physics like we have in real life. We need to simulate that by checking to see if two objects are attempting to exist at the same point and react accordingly.

Our fireball sprite is a 32x32 square, so we want to check in every single game loop whether or not the points of the fireball's square overlapping with any of the points of the target sprites. If they are overlapping, then we do that _something_.

Expand All @@ -76,9 +76,9 @@ Here's the written out logic behind the collision detection we'll implement:
{{#include code/chapter_04/03_collision_detection/app/main.rb:67:75}}
```

Play the game and hit the some targets. Nothing visually happens (yet), but if you check the console (<kbd>~</kbd>), you'll see that `"fireball hit target"` was output multiple times.
Play the game and hit some targets. Nothing visually happens (yet), but if you check the console (<kbd>~</kbd>), you'll see that `"fireball hit target"` was output multiple times.

With a loop and a method we've implemented collision detection. That wasn't too bad, was it?
With a loop and a method, we've implemented collision detection. That wasn't too bad, was it?

## Remove Targets On Collision

Expand All @@ -105,13 +105,13 @@ So in our collision detection code where we call `puts`, we'll instead mark the
{{#include code/chapter_04/04_remove_targets/app/main.rb:67:79}}
```

Since the target and fireball that collided are no longer being tracked in `args.state`, they don't get rendered on the screen and are, for all intents and purposes, gone! We then `#reject!` each fireball and target that are `dead`.
Since the target and fireball that collided are no longer being tracked in `args.state`, they don't get rendered on the screen and are, for all intents and purposes, gone! We then `#reject!` each fireball and target that is `dead`.

This almost feels like a game. That's a great feeling. We're getting close to _fun_.

## Spawn New Targets

Shooting three targets and having them disappear doesn't make for much fun though. After the three targets are hit, it's just your dragon floating in the sky with not much else to do. We're back to chapter 3! Ugh, chapter 3 was so boring! I can't believe we ever even made anything that boring before. (But remember how cool it was when we got the fireballs working? That was cool! It's funny how games evolve and what it used to be seems so basic compared to where we're at now.)
Shooting three targets and having them disappear doesn't make for much fun though. After the three targets are hit, it's just your dragon floating in the sky with not much else to do. We're back to chapter 3! Ugh, chapter 3 was so boring! I can't believe we ever even made anything that boring before. (But remember how cool it was when we got the fireballs working? That was cool! It's funny how a game evolves and what it used to be, seems so basic compared to where we're at now.)

Remember back in the day, way back when, like a few sections ago, when we introduced `#spawn_target`? It was helpful then, but now it's going to be even more helpful. We'll call it every time we destroy a target so that a new one spawns. We'll be able to play _Target Practice_ forever!

Expand All @@ -134,13 +134,13 @@ We create a `size` variable to store the width and height of the sprite to use i

Then we apply some math. Don't let math scare you away from programming! We'll keep it simple and the toolbox you need is relatively small. Plus, the math will help make our game even better. Games make math fun.

`rand` is a method that we get from DragonRuby that's available everywhere. `rand` without any parameter generates a random real number between 0 and 1. That's not really useful for us right now, so we can instead pass in a parameter that sets the upper boundary of the random number. `rand(100)` generates a random integer between 0 up to 100 (not including 100).
`rand` is a method that we get from DragonRuby that's available everywhere. `rand` without any parameter generates a random real number between 0 and 1. That's not really useful for us right now, so we can instead pass in a parameter that sets the upper boundary of the random number. `rand(100)` generates a random integer from 0 up to 100 (not including 100).

So for the x position of the target, we generate a random number that's up to two-fifths the width of the game screen and then we add three-fifths of the width to that number so that the targets spawn on the far right side of the screen. We don't want to spawn targets too close to the player, otherwise our game will be too easy.

For the y position, we generate a random y position based on the height of the game, but we subtract twice the size of the target sprite and then add one of its sizes back to the random number to give the spawn area a gutter. This prevents the target from spawning partially off the screen, which would make it impossible to hit.

### Change initial three targets
### Change the initial three targets

``` ruby
{{#include code/chapter_04/05_spawn_new_targets/app/main.rb:22:24}}
Expand Down
2 changes: 1 addition & 1 deletion src/05-fireball-clean-up.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Feel free to remove the `args.outputs.debug` lines if you don't want to see them

There will be many opportunities when working on your games to optimize your code so that it performs better. This was just a taste of what that process can be like. As you get better at making games, you'll improve at making them more performant.

Don't obsesses over performance too much yet though. Focus on making your game fun to play.
Don't obsess over performance too much yet though. Focus on making your game fun to play.

## What's Next

Expand Down
Loading

0 comments on commit ccd258f

Please sign in to comment.