Merge pull request #11 from Vlevo/Vlevo-5

tweaks for chaps 4-6
This commit is contained in:
Brett Chalupa 2023-01-08 11:27:19 -05:00 committed by GitHub
commit 9ed2d9f214
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 16 additions and 16 deletions

View file

@ -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)
@ -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_.
@ -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
@ -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!
@ -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}}

View file

@ -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

View file

@ -19,7 +19,7 @@ We'll start by introducing `args.state.timer` that will be used to keep track of
{{#include code/chapter_06/01_time_attack/app/main.rb:25:28}}
```
We lazily set it to `30 * 60`. We want the game to last thirty seconds and our `#tick` method runs sixty times every second, so we multiple them together to get the total number of ticks our timer will run for. We'll then subtract one from `args.state.timer` every `#tick` so that it decreases as we play our game.
We lazily set it to `30 * 60`. We want the game to last thirty seconds and our `#tick` method runs sixty times every second, so we multiply them together to get the total number of ticks our timer will run for. We'll then subtract one from `args.state.timer` every `#tick` so that it decreases as we play our game.
Right below decreasing our `args.state.timer` by one, we check to see if the timer is less than zero. If it is, that means game over.
@ -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 `$gtk.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.
@ -40,9 +40,9 @@ Way at the bottom of `#tick`, let's display a label with the time remaining:
{{#include code/chapter_06/01_time_attack/app/main.rb:124:138}}
```
We use the same pattern of creating a `labels` array, pushing in the player's score and the time remaining. In order to get the time remaining, we divide it by 60 and round. We do the opposite of what we did when we set the total time in ticks.
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` let's 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 let's us nicely position our timer in the upper right corner of the game.
![gameplay with Time Left reading 10 seconds](./img/c06-timer.jpg)
@ -58,7 +58,7 @@ If you happen to press the fire button right when the timer runs out, you may re
Because we keep subtracting from `args.state.timer`, we can check to see if the current value is less than -30. If it is, then we'll accept input to restart the game.
`&&` (double ampersand, often read as "and-and") means that both sides of the expression must be true for the code within the conditional to happen. In our new restart check, we combine AND and OR by saying: if the game timer is less than -30 AND any of our fire keys are down, then we reset the game. When you group together expressions in parentheses, `(monday? || tuesday?)`, it evaluates them as one express against the other checks. We care about the timer being below a certain amount AND any of the inputs being pressed.
`&&` (double ampersand, often read as "and-and") means that both sides of the expression must be true for the code within the conditional to happen. In our new restart check, we combine AND and OR by saying: if the game timer is less than -30 AND any of our fire keys are down, then we reset the game. When you group together expressions in parentheses, `(monday? || tuesday?)`, it evaluates them as one expression against the other checks. We care about the timer being below a certain amount AND any of the inputs being pressed.
Combining logic in this way for flow control is very common when making games. `&&` and `||` are pretty common operators in most programming languages.
@ -66,7 +66,7 @@ Combining logic in this way for flow control is very common when making games. `
Our main `#tick` method is getting a bit long in the tooth, being over 100 lines long. We've also duplicated two things: frames per second with the `60` value and checking for fire input. This is a good opportunity to refactor our code once again to make it easier to work with. Let's break up `#tick` into a series of smaller methods that we call from within it. Encapsulating our logic into smaller pieces makes it easier to work on those smaller pieces without concerning ourselves with the rest of the code.
How small should you make your methods? That's up to you. Use your best judgment and do what feels right. Code can change and grow quite organically. Once something feels too big or complex or is duplicated, improve it. Don't over-engineer your game right from the start, otherwise you'll be off in the weeds and not actually making your game fun. On the other hand, if you just neglect your code, you'll make it more difficult to change, thus slowing down the development process. There's a fine line between over-engineering and creating a mess.
How small should you make your methods? That's up to you. Use your best judgment and do what feels right. Code can change and grow quite organically. Once something feels too big or complex or is duplicated, improve it. Don't over-engineer your game right from the start, otherwise, you'll be off in the weeds and not actually making your game fun. On the other hand, if you just neglect your code, you'll make it more difficult to change, thus slowing down the development process. There's a fine line between over-engineering and creating a mess.
Here's the entire game broken down into some smaller methods to make it easier to work with moving forward: