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: (30 commits)
  fix: use absolute URLs for assets
  chore: update my newsletter link
  fix: typo & clarity in source control
  feat: add kenney assets to Game Dev Resources
  chore(source-control): improve clarity
  chore: further thank vlevo
  chore(ch12): improve clarity & proper itch.io formatting
  chore: add DR Zine to Outro
  Update README.md
  06-time-attack.md
  Update README.md
  03-spit-fire.md
  02-player-movement.md
  01-hello-dragon.md
  I keep shooting 'cause I keep missing introduction.md
  grammar README.md
  tweaks
  tweaks
  tweaks
  tweaks
  ...
  • Loading branch information
brettchalupa committed Mar 2, 2023
2 parents ccd258f + 4e2c7a0 commit cdb3dc3
Show file tree
Hide file tree
Showing 17 changed files with 117 additions and 104 deletions.
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Building Games with DragonRuby

An open source book that walks you through how to build games with [DragonRuby Game Toolkit](https://dragonruby.org/toolkit/game).
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 @@ -16,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, v4.0
- mdbook: v0.4.22
- DragonRuby Game Toolkit: v3.x, v4.x
- mdBook: v0.4.22

## Running Samples

Expand All @@ -33,4 +33,12 @@ Clone this book repository into a DragonRuby GTK engine directory and run the sa

## Publishing

How to make a release:

1. Merge the `main` branch into `offline`
2. Generate a new PDF via printing in Firefox, turning off the header and footer
3. Generate an HTML version of the book and zip it up
4. Create zips and upload them on itch.io
5. Create tags and releases for the offline branch and the main branch

The 3D cover file is generated with https://diybookcovers.com/
8 changes: 4 additions & 4 deletions src/01-hello-dragon.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ We'll start by rendering an image and some simple text on the screen. But first,

You're ready to work on your game. Let's get to it!

**ProTip:** don't delete the zip file! You can unzip it again when the times comes to start working on your next game. It's helpful to keep it around.
**ProTip:** don't delete the zip file! You can unzip it again when the time comes to start working on your next game. It's helpful to keep it around.

## What's in the Engine Zip

Expand Down Expand Up @@ -51,7 +51,7 @@ This isn't a game... yet! But it is doing three key things:

And you haven't even written any code yet. Not a bad starting place.

DRGTK handles the boring stuff when it comes to making games—dealing with low-level APIs like graphics, sound, and the game window. We can instead focus on creating our game instead of, for example, figuring out how to save data in a way that's compartible with Linux, Mac, Windows, Android, iOS, and web.
DRGTK handles the boring stuff when it comes to making games—dealing with low-level APIs like graphics, sound, and the game window. We can instead focus on creating our game instead of, for example, figuring out how to save data in a way that's compatible with Linux, Mac, Windows, Android, iOS, and the web.

## An Overview of the Main Game File

Expand Down Expand Up @@ -171,7 +171,7 @@ Let's take a detour down Screen Coordinates Road. The `x` and `y` values are coo

DRGTK games are made up of a window that's 1280x720 pixels in size. That's 1280 pixels wide and 720 pixels tall. The rectangle of the game screen contains 921600 pixels, that's those two numbers multiplied. Each of those pixels has a coordinate on the plane. It makes it easy to refer to a specific pixel by using its `x` and `y` position.

DRGTK starts 0, 0 in the lower left. So 1280, 720 would be the upper right. **Note:** this varies from most game engines, libraries, and tools, but it's intentional to make it easier to think about gravity and follows the geometric 2D plane that is taught in mathematics.
DRGTK starts 0, 0 in the lower left. So 1280, 720 would be the upper right. **Note:** This varies from most game engines, libraries, and tools, but it's intentional to make it easier to think about gravity and follows the geometric 2D plane that is taught in mathematics.

It's important to keep coordinates in mind, as we'll be using them a lot when making our game. A major aspect of games is moving things around on the screen, which we do by changing their coordinates.

Expand Down Expand Up @@ -215,7 +215,7 @@ The new code refactors (changes the implementation of the code without changing

The `"Hello #{friend}!"` code does what's called string interpolation. It takes whatever `friend` is, hopefully a name as a string, and inserts it. It's pretty similar to this code: `"Hello " + friend + "!"`, but quite a bit friendlier to use. The `#{}` tells Ruby to run any Ruby code within those curly braces.

Methods in Ruby return a value. Return values can then be used by the caller for whatever purposes are needed. In the example above, the return value is the string we built. Ruby returns the vaue of the last line of the method definition automatically. But you can explicitly return early with `return`, which can be useful if you want to end the execution of a method early.
Methods in Ruby return a value. Return values can then be used by the caller for whatever purposes are needed. In the example above, the return value is the string we built. Ruby returns the value of the last line of the method definition automatically. But you can explicitly return early with `return`, which can be useful if you want to end the execution of a method early.

Go ahead and change the `greet` method to:

Expand Down
20 changes: 10 additions & 10 deletions src/02-player-movement.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ There are a couple of new things here:
- `args.state`
- `||=`

Let's start with `args.state`. It's basically a blob that can be anything you want it to be, a bit like Kirby. Feed it `player_x` and it keeps track of it. Whatever you feed the `args.state`, it'll be accessible in future ticks. Keeping track of game state across ticks is important! It's part of the game loop. If we don't know where the player last was, how can we calculate where they should move to? We need to keep track of it in someplace. `args.state` is a fine place to start.
Let's start with `args.state`. It's basically a blob that can be anything you want it to be, a bit like Kirby. Feed it `player_x` and it keeps track of it. Whatever you feed the `args.state`, it'll be accessible in future ticks. Keeping track of the game state across ticks is important! It's part of the game loop. If we don't know where the player last was, how can we calculate where they should move to? We need to keep track of it in someplace. `args.state` is a fine place to start.

You can define anything on `args.state`, so it's up to you to use useful names. You could make `args.state.bleh` and set it to your favorite color, `args.state.bleh = "blue"` or your age, `args.state.age = 30`. Much like Kirby, `args.state` doesn't care what you feed it. It's just hungry for your data.

Expand All @@ -53,7 +53,7 @@ That calls the `puts` method and passes our variable `name` to it as a parameter

Remember how tick is running once every 60 seconds? We don't want to always set `args.state.player_x` to `120`. We just want to set it to that initially and then we'll update that value when we press keys on our keyboard or buttons on our gamepad. We haven't done that yet, but that's what's next.

Wow! That was a lot of explanation for two measly lines of code. But I'm telling ya', they're two really important lines of code when it comes to game programming.
Wow! That was a lot of explanation for two measly lines of code. But I'm telling ya, they're two really important lines of code when it comes to game programming.

Then, finally, we change the `x` and `y` value for the dragon sprite to be the value stored in `args.state` so that we can actually make use of that value instead of our hard-coded position before.

Expand Down Expand Up @@ -96,7 +96,7 @@ elsif args.inputs.right
end
```

This section checks for horizontal movement. If the left input is pressed, reduce the player's x position by 10 pixels. `-=` means, subtract the value on the right from the value on the left. It's the same as `args.state.player_x = args.state.player_x - 10`, but it's much more concise. We increase `player_x` to move right, decrease it to move left.
This section checks for horizontal movement. If the left input is pressed, reduce the player's x position by 10 pixels. `-=` means, subtract the value on the right from the value on the left. It's the same as `args.state.player_x = args.state.player_x - 10`, but it's much more concise. We increase `player_x` to move right and decrease it to move left.

`if` and `elsif` are conditional checks. The code only runs if the value is true (more specifically, truthy, but let's not worry about that yet).

Expand All @@ -107,7 +107,7 @@ elsif args.inputs.down
args.state.player_y -= 10
end
```
Then we check for vertical movement. We add to `player_y` to move up, decrease it to move down.
Then we check for vertical movement. We add to `player_y` to move up and decrease it to move down.

What if we wanted our dragon to move faster though? We could change those four instances of `10` to be `12` and see how that feels, sure. But that's annoying to update it all over. Let's make use of a variable! We'll call it `speed`:

Expand Down Expand Up @@ -147,27 +147,27 @@ Our dragon won't leave the screen. Woot woot! We've got some serious code here!

We moved the width and height of the player into variables so that they're easier to reference and reuse. Boom. We need those to do some math on the boundaries too. There's a general programming idea out there known as Don't Repeat Yourself (DRY). As soon as you have a piece of code, especially a number, that represents a value and is used multiple times, put it in a variable. This makes its intent clear as to what it represents and makes it easier to change. Win-win.

Here's the good stuff. We check the boundary for the x axis:
Here's the good stuff. We check the boundary for the x-axis:

``` ruby
{{#include code/chapter_02/app/main.rb:20:26}}
```

We check the right side of the screen: if the current player's x position plus their width is greater than `args.grid.w`, then we set the x position to the width of the screen (`args.grid.w`) minus the width of the sprite. For example, if we move the sprite so it has the x position of 1284, 4 pixels past the right edge of the screen, we override that change and set it to 1280 minus the player's width.

It's so important that this happens after checking for input. You don't want to change `args.state.player_x` after this check, otherwise the boundary won't be enforced. Order matters with the code we write within `tick`.
It's so important that this happens after checking for input. You don't want to change `args.state.player_x` after this check, otherwise, the boundary won't be enforced. Order matters with the code we write within `tick`.

`args.grid.w` is the width of the screen. It's always 1280, but we don't want to have that magic number in our code. So we use `args.grid.w`.

Next we check the left side of the screen: if the player's x is less than 0, then we set it to zero. That's a bit similar to the right side, just simpler.
Next, we check the left side of the screen: if the player's x is less than 0, then we set it to zero. That's a bit similar to the right side, just simpler.

Then we do the same thing for the top and bottom of the screen by checking the y position.

## Extra Credit

- When you move the dragon horizontally and vertically at the same time, the dragon moves twice as fast. How could you make it so the dragon moves at a uniform speed still when that happens?
- Ruby has a method which ensures that a numeric value is between some bounds, find it and replace our bounds checking code with it.
- When you move the dragon horizontally and vertically at the same time, the dragon moves twice as fast. How could you make it so the dragon still moves at a uniform speed when that happens?
- Ruby has a method which ensures that a numeric value is between some bounds, find it and replace our bounds-checking code with it.

## What's Next

In the next chapter we'll make our dragon spit fireballs when we press a key or button. Watch out!
In the next chapter, we'll make our dragon spit fireballs when we press a key or button. Watch out!
8 changes: 5 additions & 3 deletions src/03-spit-fire.md
Original file line number Diff line number Diff line change
Expand Up @@ -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'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.
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.

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 @@ -70,7 +70,7 @@ Wait, where are you going? Why are you muttering "I didn't sign up to read no st

Guess what? We're sticking with ole "fireball" for now! It's silly and fun and I haven't found a good fireball sprite to use. We'll get there, we'll get there. But let's first make the fireballs move across the screen.

When we moved our player dragon, we took the x and y position and added or subtracted values in each `#tick` based upon 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:
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}}
Expand Down Expand Up @@ -136,7 +136,9 @@ With that refactor done, let's display a sprite for our fireball and call it a c

Download the fireball sprite below and put it in `mygame/sprites/fireball.png`:

![fireball sprite](./code/chapter_03/06_sprite/sprites/fireball.png)
![fireball sprite](https://book.dragonriders.community/code/chapter_03/06_sprite/sprites/fireball.png)

[Download sprite](https://book.dragonriders.community/code/chapter_03/06_sprite/sprites/fireball.png)

I just made that! It's not half bad, right?

Expand Down
4 changes: 2 additions & 2 deletions src/06-time-attack.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

We've _almost_ got a game. But we need some way for the game to end. A lot of game loops end with the player's character dying, where they respawn or start over again. Other game loops end when the player reaches the end of a level.

For our simple game, let's add a 30 second timer that counts down. The objective of our game will be to see how many targets the player can hit in that time window. Let's call our game **Target Practice**. Every dragon needs some practice before they head out into battle, right?
For our simple game, let's add a 30-second timer that counts down. The objective of our game will be to see how many targets the player can hit in that time window. Let's call our game **Target Practice**. Every dragon needs some practice before they head out into battle, right?

Adding a timer to our game introduces a few new concepts we'll build out in this chapter:

Expand Down Expand Up @@ -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 let's us nicely position our timer in the upper right corner of the game.
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.

![gameplay with Time Left reading 10 seconds](./img/c06-timer.jpg)

Expand Down
20 changes: 10 additions & 10 deletions src/07-high-score.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# High-Score

Saving and loading data is a key piece of functionality when it comes to making games. We may want to keep track of all sorts of important data across play sessions. For _Target Practice_, let's keep it simple and track the high-score each time a new one is set.
Saving and loading data is a key piece of functionality when it comes to making games. We may want to keep track of all sorts of important data across play sessions. For _Target Practice_, let's keep it simple and track the high score each time a new one is set.

## Load & Save Data

When the game is over, let's display whether or not a new high-score was achieved. If it is higher than the previous, we'll save that new high-score. Otherwise, we'll display the high-score and encourage the player to try to beat it.
When the game is over, let's display whether or not a new high score was achieved. If it is higher than the previous one, we'll save that new high score. Otherwise, we'll display the high score and encourage the player to try to beat it.

This will require two parts:

1. Saving the score when a new high-score is achieved
2. Loading the previous high-score to compare the player's score to
1. Saving the score when a new high score is achieved
2. Loading the previous high score to compare the player's score to

DragonRuby GTK gives us two handy methods to do so:

Expand All @@ -26,26 +26,26 @@ We'll be working exclusively in `#game_over_tick`:

We read the value from the `HIGH_SCORE_FILE`, which is `high-score.txt`. If the file doesn't exist, it'll be `0` because we call `#to_i` on the file reading process.

Then, if we haven't saved the high-score yet and the player's score is greater than the high-score, we save it in the file and set a value in `args.state.saved_high_score` so that we don't save it every single time `#game_over_tick` gets called each frame of the game.
Then, if we haven't saved the high score yet and the player's score is greater than the high score, we save it in the file and set a value in `args.state.saved_high_score` so that we don't save it every single time `#game_over_tick` gets called each frame of the game.

``` ruby
{{#include code/chapter_07/01_load_and_save_data/app/main.rb:73:87}}
```

When we're constructing our `labels` to render, we add a condition that checks if we've got a new high-score. If we do, then we let the player know. Otherwise we display the current high-score for them to chase after.
When we're constructing our `labels` to render, we add a condition that checks if we've got a new high score. If we do, then we let the player know. Otherwise, we display the current high score for them to chase after.

![game over screen showing a score of 1 and a high-score of 4](./img/c07-low-score.jpg)
![game over screen showing a score of 1 and a high score of 4](./img/c07-low-score.jpg)

![game over screen showing a new high-score of 30](./img/c07-high-score.jpg)
![game over screen showing a new high score of 30](./img/c07-high-score.jpg)

## Summary

We load and save data relating to how our player has done. While saving one value is likely to be a bit too trivial for most games, the core concepts are pretty similar. You'll write your data to a file, read that file, and then do whatever you need to with it.

## Extra Credit

- How would you save the date and time the high-score was achieved at?
- Displaying one high-score is neat. But what if it showed the last 5 scores in addition to the highest score?
- How would you save the date and time the high score was achieved at?
- Displaying one high score is neat. But what if it showed the last 5 scores in addition to the highest score?

## What's Next

Expand Down
Loading

0 comments on commit cdb3dc3

Please sign in to comment.