SPACE SHOOTER
QUICK FIXES
MOVING IN FOUR DIRECTIONS
PLAYER
Moving Left
In the starting code, the player can only move to the left. This is defined in the act() method, shown below:
if(getKey('a'))
{
xSpeed -= PLAYER_SPEED;
}
Moving Right
To allow the player to move in different directions, we simply need to add other key commands to this list. For example, use the getKey() method with the letter 'd' to try and move to the right by adding the player's speed.
if(getKey('d'))
{
xSpeed += PLAYER_SPEED;
}
Other Directions
Use "W" and "S" to move up and down respectively.
CASE INSENSITIVITY
PLAYER
Taking Both Cases
The starting code has a small bug; it only works on lowercase letters! This means that if the user accidentally hits the caps lock key, they won't move at all. Let's make each direction respond to both capital and lower case letters.
if(getKey('a') || getKey('A'))
{
xSpeed -= PLAYER_SPEED;
}
STAY ON SCREEN
PLAYER
Moving Left
We need to add a requirement to our movement to make sure that the player only moves to the left when that move would not put them off the screen. We'll need to add another case to our if statement. Be careful with parentheses to make sure our logic is correct.
if((getKey('a') || getKey('A')) && x > 0)
{
xSpeed -= PLAYER_SPEED;
}
Other Directions
Each movement will need to do a similar check. Make sure you consider
Is it moving horizontally or vertically? (x vs. y, width vs. height)
Do you need to accomodate for the width/height of the ship? (uses "w" or "h")
Should we be comparing it to top/left or right/bottom of the screen? (uses 0 or width/height)
Test all sides in your game before moving on!
SHOT COOLDOWN
PLAYER
Making a Timer
There are many ways to solve this problem, but we'll start by using a system that you might commonly want to implement: a shot timer with a specified cooldown.
First, we'll start by adding a new variable to the Player class. Make sure it is declared inside the player class but not inside any methods.
class Player extends GameObject
{
int shotTimer = 0;
...
Shooting and Cooldown
Next, we'll modify the shooting code to (1) only shoot when shotTimer is zero and (2) set the shotTimer to PLAYER_COOLDOWN.
if (getKey(' ') && shotTimer == 0)
{
objects.add(new PlayerShotBasic(x, y));
objects.add(new PlayerShotBasic(x+24, y));
shotTimer = PLAYER_COOLDOWN;
}
Note that we didn't need to declare PLAYER_COOLDOWN because it was already setup for you in values. Take a look over there and try changing the number it represents. The larger the value, the longer time between shots.
Updating the Timer
Finally, we need to add code to decrease our shot timer every frame until it reaches zero. Anywhere in the act method, you can add the following code:
if(shotTimer > 0)
{
shotTimer--;
}
TAKING DAMAGE
GAME OBJECT
The takeDamage() method
This method is called automatically when there is a collision. Every GameObject has an amount of health represented by the variable curHealth. We want an object to lose health , but the current code just kills the object!
public void takeDamage(float amount)
{
die();
}
Reducing curHealth
We can modify takeDamage to reduce health by the amount of damage passed to this method.
public void takeDamage(float amount)
{
curHealth = curHealth - amount;
if(curHealth <= 0)
{
curHealth = 0;
die();
}
}
Setting starting health for Player and EvilSquare
Notice that when you run this code, it won't do anything! This is because all objects have exactly 1 health to start.
To fix this, modify the constructors of both the player and evil square to set starting current and maximum health values.
Reminders:
You do not declare curHealth and maxHealth because they are inherited from GameObject.
You'll need to create new constants in values for the code below to work. Try different numers and see what works.
Repeat this example for EvilSquare.
Player(float x, float y)
{
super(x, y, blueTriangle.width, blueTriangle.height);
image = blueTriangle;
curHealth = PLAYER_HEALTH;
maxHealth = PLAYER_HEALTH;
}
DEBUG MESSAGES
GAME OBJECT
Getting Information
So often while you're working on this program, it might not be clear WHY something isn't working. For instance, when we fixed health we might think our code was incorrect. But actually, we just hadn't set the proper health values.
To get information, we have two main options.
Use println() to output information to the console
Use text() to draw information on the screen
Print Example: Shot Timer
Let's imagine that our shot timer wasn't working, and we couldn't tell why.
We might add a print statement to output the value of the shotTimer each time we fire. If we notice it never changes from 0, then we know something is wrong with that variable!
println("Shot Timer: " + shotTimer);
Text Example: Displaying Health
We can also display information on the screen as text. This is especially useful when:
The information applies to a lot of different objects at the same time
The information is tied to position or a visual element
I recommend that while you're working on your game, you add in code to GameObject to display the health of each unit above its head. It's simple and we can comment out this line later for release.
void render()
{
image(image, x, y, w, h);
if(maxHealth != 1)
{
textAlign(CENTER, CENTER);
text((int) curHealth, x + w / 2, y - 15);
}
}
A few notes on the code above:
We don't display objects with a max health of 1 (avoids health on every bullet)
Uses (int) to avoid decimal values
Adds w / 2 to x and uses textAlign to center it
Subtracts 15 from y to put it above the object
PROJECTILE DURATION
PROJECTILE
Why Set A Duration?
Making projectiles have a limited duration has two important features:
It means they get removed automatically over time, so they don't take up memory and processing power as they fly off screen forever
It allows you to make shots with different ranges
Range = Speed * Duration
Projectile Class
Create a constant in Values called PROJECTILE_DURATION and set it equal to 100
Add an integer variable called duration
In the constructor, set duration equal to PROJECTILE_DURATION.
Implement an act method that overrides game object's act method
void act()
{
super.act()
if(timer > duration)
{
die();
}
}
PlayerShotBasic
Create a constant called PLAYER_SHOT_DURATION in Values and give it a value
In the constructor, add a line that sets duration equal to PLAYER_SHOT_DURATION
RedShot
Repeat the process above for RedShot and all future projectiles that have a different duration than the default duration
BETTER ENEMY
EVIL SQUARE
Enemy Shooting
Copy the player's shooting code and add into EvilSquare. This includes all of the shotTimer elements.
If you run this code, you'll find that pressing space bar causes the enemy to shoot player bullets and they are immediately removed from the game. Ooops!
You'll need to make a few changes to enemies:
Have them fire an enemy shot called RedShot() instead of PlayerShotBasic()
Remove the keyPress condition
Make the shotTimer change based on EVIL_SQUARE_COOLDOWN
if (shotTimer == 0)
{
objects.add(new RedShot(x, y));
shotTimer = EVIL_SQUARE_COOLDOWN;
}
Enemy Movement
You'll need to make your first enemy do more than simply move straight down. Start by changing the constants that define their speed in values. For instance, consider making them move diagonally down and to the right.
You'll need to handle the edges of the screen. Some common solutions:
Enemies bounce off the right and left sides of the screen
Enemies wrap from the right to the left and vice versa
This is a game design decision. Both are okay. Which one looks better? Feels more fun? Choose the method that fits your vision.
You can certainly make this enemy more complex, consider things like:
Making the enemy stop moving to fire
Add in acceleration and deceleration
Have enemies move in different directions (some go left, some go right)