HW03 - james-bern/CS136 GitHub Wiki
Enter the Gungeon
- i wrote a class called
Vector2
, which can be used to represent a 2D mathematical point or vector-
e.g., in high-school geometry,
$(0.0, 0.0)^T$ was the origin of the plane, and$(1.0, 0.0)^T$ was the$x$ -axis
-
e.g., in high-school geometry,
- here is a simplified version of the class:
class Vector2 {
// // INSTANCE VARIABLES
double x;
double y;
// // toString() is a special Java method that is called automatically by System.out.print*
public String toString() {
return "(" + x + ", " + y + ")";
}
// // CONSTRUCTORS
// Option A: default constructor (instance variables cleared to zero automatically)
Vector2() {}
// Option B: specify x and y separately
Vector2(double x, double y) {
this.x = x;
this.y = y;
}
// Option C: copy constructor (copy x and y from some other Vector2 instance/object)
Vector2(Vector2 other) {
this.x = other.x;
this.y = other.x;
}
// // INSTANCE METHODS (called on an instance of the Vector2 class)
// this + other
Vector2 add(Vector2 other) {
return new Vector2(this.x + other.x, this.y + other.y);
}
// this - other
Vector2 minus(Vector2 other) {
return new Vector2(this.x - other.x, this.y - other.y);
}
// // STATIC METHODS (NOT called on an instance of the Vector2 class)
// distance between points a and b
static Vector2 distanceBetween(Vector2 a, Vector2 b) {
Vector2 v = b.minus(a);
return Math.sqrt(v.x * v.x + v.y * v.y);
}
}
Vector2 a = new Vector2(1.0, 2.0);
// This is the same thing but with more typing.
Vector2 a = new Vector2();
a.x = 1.0;
a.y = 2.0;
Vector2 a = new Vector2(1.0, 2.0);
Vector2 b = new Vector2(3.0, 4.0);
Vector2 c = a.plus(b);
// This is the same thing but with more typing.
Vector2 a = new Vector2();
a.x = 1.0;
a.y = 2.0;
Vector2 b = new Vector2();
b.x = 3.0;
b.y = 4.0;
Vector2 c = new Vector2();
c.x = a.x + b.x;
c.y = a.y + b.y;
Vector2 a = new Vector2(1.0, 2.0);
Vector2 b = new Vector2(3.0, 4.0);
double d = Vector2.distanceBetween(a, b);
-
primitive types include
bool
int
,double
, andchar
-
int a;
is "an int"
-
int a = 1; // a = 1; a is an int
int b = a; // a = 1, b = 1; a and b are DIFFERENT ints
// (which happen to have the same value)
++a; // a = 2, b = 1;
- in Java, everything else (i.e., instances/objects of classes) are non-primitive types
- you create a new instance/object of a class using the
new
keyword, which returns a reference to that instance/object -
Vector2 a;
is "a reference to an instance of the Vector2 class" - in diagrams, we often draw references as arrows
- you create a new instance/object of a class using the
Vector2 a = new Vector2(0.0, 0.0); // a -> (0.0, 0.0)
Vector2 b = a; // a, b -> (0.0, 0.0);
// a and b are references to the SAME instance of the Vector2 class
// there is only 1 instance because we only called new once!
a.x = 1.0; // a, b -> (1.0, 0.0)
- Consider two circles A and B.
- Circle A has center position
$\mathbf{p}_A$ and radius$r_A$ . - Circle B has center position
$\mathbf{p}_B$ and radius$r_B$ . - The two circles intersect if the distance between
$\mathbf{p}_A$ and$\mathbf{p}_B$ is less than$r_A + r_B.$
- Circle A has center position
Skim the Documentation for Vector2
, Vector3
, and App
-
First, read through the Starter Code very carefully, and play the game. Figure out how the game works by poking around, making small changes to the code (for example,
red -> green
) and seeing what happens.- This could easily take 1-2 hours.
-
To get a B:
- ✅ Make the game have two turrets firing at the player instead of one.
- ✨ BEFORE (NOTE: Non-turret code left out for clarity; It is still actually there!)
class HW03 { Turret turret; void setup() { turret = new Turret(); turret.color = new Vector3(Vector3.red); ... turret.position = new Vector2(0.0, 0.0); } void loop() { if (turret.framesSinceFired++ == 32) { turret.framesSinceFired = 0; // fire bullet ... // SO MUCH CODE } drawCircle(turret.position, turret.radius, turret.color); } }
- ✨ AFTER (NOTE: Non-turret code left out for clarity; It is still actually there!)
class HW03 { Turret[] turrets; void setup() { // called once at the beginning of the game turrets = new Turret[2]; for (int turretIndex = 0; turretIndex < turrets.length; ++turretIndex) { turrets[turretIndex] = new Turret(); turrets[turretIndex].color = new Vector3(Vector3.red); ... } turrets[0].position = new Vector2(-40.0, 0.0); turrets[1].position = new Vector2( 40.0, 0.0); } void loop() { // called over and over, once per frame for (int turretIndex = 0; turretIndex < turrets.length; ++turretIndex) { if (turrets[turretIndex].framesSinceFired++ == 32) { turrets[turretIndex].framesSinceFired = 0; // fire bullet ... // SO MUCH CODE } drawCircle(turret.position, turret.radius, turret.color); } } }
- 🚨 The turrets must be stored in an array like
Turret[] turrets;
.- 🚨 The turret array must be initialized in
setup()
using a for loop to avoid repeating code.- Some of the initialization (like position) won't make sense to put in the for loop. That's okay, you can put those things after the for loop. Just try to avoid massive repetition.
- 🚨 The turret array must be updated in
loop()
using a for loop to avoid repeating code.
- 🚨 The turret array must be initialized in
- ✨ BEFORE (NOTE: Non-turret code left out for clarity; It is still actually there!)
- ✅ Press
I
,J
,K
, orL
to make the player fire a bullets up, left, down, or right.- ✨ AFTER (NOTE: Non-bullet-firing code left out for clarity; It is still actually there!)
class HW03 { // First, decide what arguments this function should take. // Then, move the code marked SO MUCH CODE (everything under `// fire bullet`) into the body of this function. // Then, "hook it up" to the arguments (`bullet.position = position;`, etc.). // NOTE: I show how `position` would be done below. // You will have to figure out the other arguments yourself. void fireBullet(Vector2 position, ...) { for (int bulletIndex = 0; bulletIndex < bullets.length; ++bulletIndex) { if (!bullets[bulletIndex].alive) { // write to first empty ("dead") slot in bullets array bullets[bulletIndex].position = new Vector2(position); ... break; } } } void setup() { // called once at the beginning of the game ... } void loop() { // called over and over, once per frame // player ... if (keyPressed('I')) { fireBullet(player.position, ...); } if (keyPressed('J')) { fireBullet(player.position, ...); } if (keyPressed('K')) { fireBullet(player.position, ...); } if (keyPressed('L')) { fireBullet(player.position, ...); } // turrets for (int turretIndex = 0; turretIndex < turrets.length; ++turretIndex) { if (turrets[turretIndex].framesSinceFired++ == 32) { turrets[turretIndex].framesSinceFired = 0; fireBullet(turrets[turretIndex].position, ...); } drawCircle(turrets[turretIndex].position, turrets[turretIndex].radius, turrets[turretIndex].color); } // bullets ... } }
- 🚨 Both the turrets and the player should fire bullets using a single function something like
void fireBullet(...) { ... }
, which will need to take the bullet's type (Bullet.TYPE_PLAYER
orBULLET.TYPE_TURRET
) as one of its arguments. It will also need to take the bullet's position and velocity.- 🚨 All fired bullets must be written to the same
bullets
array that was provided in the starter code.
- 🚨 All fired bullets must be written to the same
- ✅ These "player bullets" must be the same color as the player.
- ✨ AFTER (NOTE: Non-bullet-firing code left out for clarity; It is still actually there!)
- ✅ Make the game have two turrets firing at the player instead of one.
-
To get an A:
-
✅ Implement circle-circle collision detection.
- ✅ If the player gets hit by a turret bullet, the game must reset.
- ✨ Just call
reset();
🙂👍
- ✨ Just call
- ✅ If the turret gets hit by a player bullet, that bullet must disappear.
- You can just set
bullet.alive = false;
🙂👍
- You can just set
- 🚨 The circle-circle intersection math must show up in exactly one place in the code (NOT two places).
- ✨ Perhaps inside a function like
boolean circleCircleIntersection(...) { ...}
;- If you do write a function, keep it simple and general-purpose! Make it take positions and radii and arguments. 🙂👍
- ✨ Perhaps inside a function like
- ✅ Turrets have health and die after getting hit a few times.
- 🚨 Dead turrets should not be drawn.
- 🚨 Dead turrets should not collide with bullets.
- 🚨 Dead turrets should not fire bullets (though bullets a turret fired before dying can be kept alive).
- ✅ If the player gets hit by a turret bullet, the game must reset.
-
✅ Clean up your code.
- 🚨 Delete commented out code.
- 🚨 There should NOT be the line
Turret turret;
still in your code when you submit it. 😬
- 🚨 There should NOT be the line
- 🚨 Make sure variable names are reasonable and consistent.
- 🚨 Make sure indentation is reasonable and consistent.
- 🚨 Delete commented out code.
-
✅ (Optional but Recommended) After you've finished the for loop version of initializing the turrets, you can write a (non-default) constructor
Turret(Vector2 position)
which sets its position to the argumentposition
and also sets its radius, color, etc. to default values (the stuff that used to be in the body of the loop). Once you do this, your code for initializationturrets
will look like this:turrets = new Turret[2]; turrets[0] = new Turret(new Vector2(-40.0, 0.0)); turrets[1] = new Turret(new Vector2( 40.0, 0.0));
-
-
To get an S:
- ✅ Implement everything above.
- ✅ Make the game good. 🙂
- ✨ Some ideas...
- When turret gets hit it should flash different colors.
- More weapons.
- Particle effects.
- Make the turret chase you around.
- Turrets that shoot turrets.
- You have a pet.
- Turrets have health bars.
- You can dive roll like in enter the gungeon.
- Obstacles (walls, etc.)
- Enemies are smart (A* search, etc.)
- ???
- ✨ Some ideas...
- Upload your code file to GradeScope by the due date.
-
Live demo your code to Jim or a Grading TA in Lab.
- 🚨 You must do this in order to receive points.
- If you are trying for an S, you must demo to Jim.
- Create a folder called
HW03
- Create a new file in that folder called
Cow.java
and copy and paste the contents of Cow.java inside- Click link above
- Click icon with the two squares at the top right of the file to copy it
- Create a new file in that folder called
HW03.java
and copy and paste the starter code below inside - Open both
Cow.java
andHW03.java
in DrJava - Click on
HW03.java
-
Compile
andRun
// NOTE: Read and compile and run and understand this file before you start working!
import java.util.*;
class HW03 extends App {
// NOTE: please ignore this particular use of the keyword static
// (static nested class); it is NOT what we talked about in lecture
// feel free to delete this comment after you finish reading it
static class Player {
Vector3 color;
double radius;
Vector2 position;
}
static class Turret {
Vector3 color;
double radius;
Vector2 position;
int framesSinceFired;
}
static class Bullet {
Vector3 color;
double radius;
Vector2 position;
Vector2 velocity;
boolean alive;
int age;
int type;
static final int TYPE_PLAYER = 0;
static final int TYPE_TURRET = 1;
}
////////////////////////////////////////////////////////////////////////////
Player player;
Turret turret; // TODO: Replace this line with "Turret[] turrets;"
// bullets is a big, fixed-size array of Bullet objects (slots)
// initially all Bullet objects are "not live" ("dead"),
// which means they are not being updated or drawn
Bullet[] bullets;
////////////////////////////////////////////////////////////////////////////
// TODO: Write a new function "void fireBullet(...) { ... }"
////////////////////////////////////////////////////////////////////////////
void setup() { // called once at the beginning of the game
player = new Player();
player.color = new Vector3(Vector3.cyan);
player.radius = 4.0;
player.position = new Vector2(0.0, -40.0);
turret = new Turret();
turret.color = new Vector3(Vector3.red);
turret.radius = 8.0;
turret.position = new Vector2(0.0, 0.0);
bullets = new Bullet[256];
for (int bulletIndex = 0; bulletIndex < bullets.length; ++bulletIndex) {
bullets[bulletIndex] = new Bullet();
}
}
void loop() { // called over and over, once per frame
// player
if (keyHeld('W')) { player.position.y += 1.0; }
if (keyHeld('A')) { player.position.x -= 1.0; }
if (keyHeld('S')) { player.position.y -= 1.0; }
if (keyHeld('D')) { player.position.x += 1.0; }
drawCircle(player.position, player.radius, player.color);
// turret
if (turret.framesSinceFired++ == 32) {
turret.framesSinceFired = 0;
// fire bullet
for (int bulletIndex = 0; bulletIndex < bullets.length; ++bulletIndex) {
if (!bullets[bulletIndex].alive) { // write to first unused ("dead") slot in bullets array
bullets[bulletIndex].position = new Vector2(turret.position); // copy constructor (instead of just referring to the same Vector2 object)
bullets[bulletIndex].velocity = Vector2.directionVectorFrom(turret.position, player.position);
bullets[bulletIndex].radius = 2.0;
bullets[bulletIndex].color = new Vector3(Vector3.yellow);
bullets[bulletIndex].alive = true;
bullets[bulletIndex].age = 0;
bullets[bulletIndex].type = Bullet.TYPE_TURRET;
break;
}
}
}
drawCircle(turret.position, turret.radius, turret.color);
// bullets
for (int bulletIndex = 0; bulletIndex < bullets.length; ++bulletIndex) {
if (!bullets[bulletIndex].alive) { continue; } // skip dead bullets
// kill bullets that are too old (they're probably off-screen anyway)
if (bullets[bulletIndex].age++ > 128) {
bullets[bulletIndex].alive = false;
}
// "physics"
bullets[bulletIndex].position = bullets[bulletIndex].position.plus(bullets[bulletIndex].velocity);
// draw
drawCircle(bullets[bulletIndex].position, bullets[bulletIndex].radius, bullets[bulletIndex].color);
}
}
public static void main(String[] arguments) {
App app = new HW03();
app.setWindowBackgroundColor(0.0, 0.0, 0.0);
app.setWindowSizeInWorldUnits(128.0, 128.0);
app.setWindowCenterInWorldUnits(0.0, 0.0);
app.setWindowHeightInPixels(512);
app.setWindowTopLeftCornerInPixels(64, 64);
app.run();
}
}