Refactor Part 2: Lots of renaming, new classes, hitbox changes

This commit is contained in:
naomi 2025-05-18 03:48:48 +00:00
parent 475af2c03e
commit 98883aa62a
21 changed files with 1549 additions and 790 deletions

18
nb-configuration.xml Normal file
View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project-shared-configuration>
<!--
This file contains additional configuration written by modules in the NetBeans IDE.
The configuration is intended to be shared among all the users of project and
therefore it is assumed to be part of version control checkout.
Without this configuration present, some functionality in the IDE may be limited or fail altogether.
-->
<properties xmlns="http://www.netbeans.org/ns/maven-properties-data/1">
<!--
Properties that influence various parts of the IDE, especially code formatting and the like.
You can copy and paste the single properties, into the pom.xml file and the IDE will pick them up.
That way multiple projects can share the same settings (useful for formatting rules for example).
Any value defined here will override the pom.xml file value but is only applicable to the current project.
-->
<netbeans.hint.license>apache20</netbeans.hint.license>
</properties>
</project-shared-configuration>

View file

@ -4,9 +4,9 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>dev.boyfailure.quajra</groupId>
<groupId>dev.boyfailure</groupId>
<artifactId>VectorBreakout</artifactId>
<version>20250411_test1</version>
<version>1.0</version>
<packaging>jar</packaging>
@ -18,7 +18,7 @@
<maven.compiler.target>21</maven.compiler.target>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<exec.mainClass>dev.boyfailure.quajra.vectorbreakout.VectorBreakout</exec.mainClass>
<exec.mainClass>dev.boyfailure.vectorbreakout.VectorBreakout</exec.mainClass>
</properties>
<build>
@ -30,7 +30,7 @@
<configuration>
<archive>
<manifest>
<mainClass>dev.boyfailure.quajra.vectorbreakout.VectorBreakout</mainClass>
<mainClass>dev.boyfailure.vectorbreakout.VectorBreakout</mainClass>
</manifest>
</archive>
</configuration>

View file

@ -1,235 +0,0 @@
package dev.boyfailure.quajra.vectorbreakout;
import java.util.Arrays;
public class Ball {
short ballX = 250;
short ballY = 300;
short ballWidth = 7;
short ballHeight = 7;
private boolean isActive = false;
private boolean isInvincible = false;
byte ballColor = 0;
Hitbox hitBox;
byte speedX = 5;
byte speedY = 5;
private GameState gameState;
public Ball(GameState gameState, int x, int y, int width, int height, int color, boolean active) {
this.gameState = gameState;
this.ballX = (short) x;
this.ballY = (short) y;
this.ballWidth = (short) width;
this.ballHeight = (short) height;
this.ballColor = (byte) color;
this.hitBox = new Hitbox(x, y, width, height, 1);
this.isActive = active;
}
public void spawnBall(boolean useChancey) {
if (useChancey) {
this.ballX += (short) (gameState.getChancey() * 3);
this.ballY += (short) gameState.getChancey();
}
gameState.ballsOnScreen++;
this.isActive = true;
gameState.lives--;
gameState.textElementList.get(4).setText(String.valueOf(gameState.lives));
}
public void spawnBall(int x, int y) {
this.ballX = (short) x;
this.ballY = (short) y;
gameState.ballsOnScreen++;
this.isActive = true;
gameState.lives--;
gameState.textElementList.get(4).setText(String.valueOf(gameState.lives));
}
public void moveBall() {
this.ballX += this.speedX;
this.ballY += this.speedY;
this.hitBox.moveTo(this.ballX, this.ballY, this.ballWidth, this.ballHeight);
this.checkCollision();
}
public void bounce(int bounceType) {
switch((byte) bounceType) {
case 0:
this.speedY = (byte) -(this.speedY);
switch (gameState.getChancey()) {
case 0: break;
case 1: this.speedX++; break;
case 2: break;
case 3: this.speedX--; break;
case 4: break;
case 5: this.speedY++;break;
case 6: break;
case 7: this.speedY--;break;
case 8: break;
case 9: this.speedY++;speedX++;break;
case 10: break;
case 11: this.speedY--;speedX--;break;
case 12: break;
case 13: this.speedY++;speedY++;break;
case 14: break;
case 15: this.speedY--;speedY--;break;
}
break;
case 1:
this.speedX = (byte) -(this.speedX);
break;
case 2:
if (this.speedX > 0) {
this.speedX = (byte) -(this.speedX);
}
else {
this.speedX -= 4;
}
this.speedY = (byte) -(this.speedY);
break;
case 3:
if (this.speedX < 0) {
this.speedX = (byte) -(this.speedX);
}
else {
this.speedX += 4;
}
this.speedY = (byte) -(this.speedY);
break;
}
if (this.speedX < 2 && this.speedX >= 0) {this.speedX = 2;}
else if (this.speedX > -2 && this.speedX < 0) {this.speedX = -2;}
else if (this.speedX > 11) {this.speedX -= 3;} // Slow down if too fast
if (this.speedY < 4 && this.speedY >= 0) {this.speedY = 4;}
else if (this.speedY > -4 && this.speedY < 0) {this.speedY = -4;}
else if (this.speedY > 11) {this.speedY -= 3;} // Slow down if too fast
}
public void destroyBall() {
this.isActive = false;
this.ballX = 32767;
this.ballY = 32767;
gameState.ballsOnScreen--;
if (gameState.ballsOnScreen <= 0) {
gameState.ballsOnScreen = 0;
if (gameState.lives <= 0) {
gameState.gameLose();
}
}
}
// Checks to see if the ball is in contact with bricks, paddles, or walls
public void checkCollision() {
this.setInvincibilityState(true); // Delay bouncing again for a frame
// Check floor
if (this.ballY >= gameState.getInternalResY()) {
this.destroyBall();
return;
}
// Check ceiling
else if (this.ballY <= 0) {
this.bounce(0);
this.ballY = 0;
this.wallBounceAnimation();
return;
}
// Check walls
if (this.ballX <= 20) {
this.bounce(1);
this.ballX = 22;
this.wallBounceAnimation();
return;
}
else if ((this.ballX + this.ballWidth) >= (gameState.getInternalResX() - 20 - this.ballWidth)) {
this.bounce((byte) 1);
this.ballX = (short) (gameState.getInternalResX() - 25 - this.ballWidth);
this.wallBounceAnimation();
return;
}
// Check bricks
if (gameState.currentLevel != null) {
if (gameState.currentLevel.brickList != null) {
for (Brick brick : gameState.currentLevel.brickList) {
if (this.hitBox.collides(brick.hitBox.getBounds())) {
brick.breakBrick();
this.bounce(0);
gameState.brickCollector.add(brick);
return;
}
}
}
}
// Check paddles
for (Paddle paddle : gameState.paddleList) {
if (this.hitBox.collides(paddle.hitBox.getBounds())) {
if (this.hitBox.collides(paddle.hitBoxLeft.getBounds())) {
this.bounce(2);
}
else if (this.hitBox.collides(paddle.hitBoxRight.getBounds())) {
this.bounce(3);
}
else {
this.bounce(0);
}
this.ballY -= 2;
// Draw particles
Particle particle1, particle2, particle3;
particle1 = new Particle(gameState, 0, this.ballX, this.ballY - 10, 0, -2, 1, 2, 7, 3);
particle2 = new Particle(gameState, 0, this.ballX - 3, this.ballY - 10, -2, -4, 1, 2, 6, 3);
particle3 = new Particle(gameState, 0, this.ballX + 10, this.ballY - 10, 2, -4, 1, 2, 8, 3);
gameState.particleList.add(particle1);
gameState.particleList.add(particle2);
gameState.particleList.add(particle3);
particle1.spawn(5);
particle2.spawn(5);
particle3.spawn(5);
return; // Prevents bouncing multiple times
}
}
}
private void wallBounceAnimation() {
Particle particle1, particle2, particle3, particle4;
particle1 = new Particle(gameState, 0, this.ballX - 3, this.ballY - 10, -2, -2, 1, 2, 6, 3);
particle2 = new Particle(gameState, 0, this.ballX + 10, this.ballY - 10, 2, -2, 1, 2, 8, 3);
particle3 = new Particle(gameState, 0, this.ballX - 3, this.ballY + 3, 2, 2, 1, 2, 2, 3);
particle4 = new Particle(gameState, 0, this.ballX + 10, this.ballY + 3, -2, 2, 1, 2, 4, 3);
gameState.particleList.add(particle1);
gameState.particleList.add(particle2);
gameState.particleList.add(particle3);
gameState.particleList.add(particle4);
particle1.spawn(5);
particle2.spawn(5);
particle3.spawn(5);
particle4.spawn(5);
}
public boolean getActiveState() {
return this.isActive;
}
public void setActiveState(boolean activeState) {
this.isActive = activeState;
}
public boolean getInvincibilityState() {
return this.isInvincible;
}
public void setInvincibilityState(boolean invincibilityState) {
this.isInvincible = invincibilityState;
}
}

View file

@ -1,81 +0,0 @@
package dev.boyfailure.quajra.vectorbreakout;
public class Brick {
short brickX;
short brickY;
short brickWidth = 40;
short brickHeight = 17;
private boolean isBroken = false;
byte brickColor = 0;
byte scoreOnBreak = 10;
byte specialAbility = 0;
Hitbox hitBox;
private GameState gameState;
/**
* Bricks.
* @param gameState The GameState in which the Brick will reside.
* @param x The x coordinate of the Brick.
* @param y The y coordinate of the Brick.
* @param isBroken Determines whether the Brick is broken.
* @param brickColor The color code of the Brick.
* @param score The score the Player earns upon breaking the Brick.
* @param specialAbility A special event that occurs upon breaking the Brick.
*/
public Brick(GameState gameState, int x, int y, boolean isBroken, int brickColor, int score, int specialAbility) {
this.gameState = gameState;
this.brickX = (short) x;
this.brickY = (short) y;
this.isBroken = isBroken;
this.brickColor = (byte) brickColor;
this.scoreOnBreak = (byte) score;
this.hitBox = new Hitbox(x, y, this.brickWidth, this.brickHeight, 0);
this.specialAbility = (byte) specialAbility;
}
public void breakBrick() {
if (!isBroken) {
isBroken = true;
gameState.score += this.scoreOnBreak + (gameState.level - 1);
gameState.textElementList.get(3).setText(String.valueOf(gameState.score));
gameState.bricksOnScreen--;
switch(this.specialAbility) {
case 1:
gameState.lives++;
break;
case 2:
Ball ball = new Ball(gameState, 300, 300, 7, 7, 0, true);
ball.spawnBall((this.brickX + (this.brickWidth / 2)), (this.brickY + (this.brickHeight / 2)));
break;
default:
break;
}
}
// Draw particles
Particle particle1, particle2, particle3;
particle1 = new Particle(gameState, 0, this.brickX + 12, this.brickY + 3, 1, 1, this.brickColor, 2, 4, 1);
particle2 = new Particle(gameState, 0, this.brickX + 20, this.brickY + 13, 1, 1, this.brickColor, 2, 3, 1);
particle3 = new Particle(gameState, 0, this.brickX + 32, this.brickY + 7, 1, 1, this.brickColor, 2, 2, 1);
gameState.particleList.add(particle1);
gameState.particleList.add(particle2);
gameState.particleList.add(particle3);
particle1.spawn(5);
particle2.spawn(5);
particle3.spawn(5);
this.brickX = 32767;
this.brickY = 32767;
this.hitBox.moveTo(32760, 32760, 2, 2);
}
public boolean getBrokenState() {
return this.isBroken;
}
public void setBrokenState(boolean brokenState) {
this.isBroken = brokenState;
}
}

View file

@ -1,56 +0,0 @@
package dev.boyfailure.quajra.vectorbreakout;
public class Paddle {
short paddleX;
short paddleY;
short paddleWidth = 100;
short paddleHeight = 7;
byte paddleColor;
byte paddleSpeed = 0;
boolean isActive = true;
float speedMultiplier = 1;
Hitbox hitBox;
Hitbox hitBoxLeft;
Hitbox hitBoxRight;
private GameState gameState;
public Paddle(GameState gameState, int x, int y, int width, int height, int paddleColor) {
this.gameState = gameState;
this.paddleX = (short) x;
this.paddleY = (short) y;
this.paddleWidth = (short) width;
this.paddleHeight = (short) height;
this.paddleColor = (byte) paddleColor;
this.hitBox = new Hitbox(x, y, width, height, 4);
this.hitBoxLeft = new Hitbox(x, y, width / 5, height, 4);
this.hitBoxRight = new Hitbox(x + ((width / 5) * 4), y, width / 5, height, 4);
}
public void movePaddle(boolean direction) {
if (this.paddleSpeed <= 15) {this.paddleSpeed += 3;}
if (direction) {this.paddleX += (this.paddleSpeed * this.speedMultiplier);}
else {this.paddleX -= (this.paddleSpeed * this.speedMultiplier);}
if (this.paddleX <= 20) {this.paddleX = 20;}
else if (this.paddleX >= (gameState.getInternalResX() - this.paddleWidth - 20)) {this.paddleX = (short) (gameState.getInternalResX() - this.paddleWidth - 20);}
this.hitBox.moveTo(this.paddleX, this.paddleY, this.paddleWidth, this.paddleHeight);
this.hitBoxLeft.moveTo(this.paddleX, this.paddleY, this.paddleWidth / 5, this.paddleHeight);
this.hitBoxRight.moveTo(this.paddleX + ((this.paddleWidth / 5) * 4), this.paddleY, this.paddleWidth / 5, this.paddleHeight);
}
public void cullPaddle() {
isActive = false;
this.paddleX = 32767;
this.paddleY = 32767;
}
public boolean getActiveState() {
return this.isActive;
}
public void setActiveState(boolean activeState) {
this.isActive = activeState;
}
}

View file

@ -1,126 +0,0 @@
package dev.boyfailure.quajra.vectorbreakout;
import java.util.Timer;
import java.util.TimerTask;
public class TextElement {
String text;
boolean active = true;
private boolean isVisible = false;
float textScale = 1;
float vectorScale = 1;
byte textColor;
short x;
short y;
short duration = 0;
long creationTime;
private GameState gameState;
/**
* Text elements.
* @param gameState The GameState in which the TextElement will reside.
* @param text The text that will be displayed.
* @param textScale The size of the text on screen.
* @param x The x coordinate of the text's origin point.
* @param y The y coordinate of the text's origin point.
* @param textColor The color of the text.
* @param vectorScale The scale of the text.
*/
public TextElement(GameState gameState, String text, float textScale, int x, int y, int textColor, float vectorScale) {
this.gameState = gameState;
this.text = text;
this.textScale = textScale;
this.x = (short) x;
this.y = (short) y;
this.textColor = (byte) textColor;
this.vectorScale = vectorScale;
}
/**
* Text elements with a fadeout timer.
* @param gameState The GameState in which the TextElement will reside.
* @param text The text that will be displayed.
* @param textScale The size of the text on screen.
* @param x The x coordinate of the text's origin point.
* @param y The y coordinate of the text's origin point.
* @param textColor The color of the text.
* @param vectorScale The scale of the text.
* @param duration How long the text will stay on screen, in milliseconds.
*/
public TextElement(GameState gameState, String text, float textScale, int x, int y, int textColor, float vectorScale, int duration) {
this.gameState = gameState;
this.text = text;
this.textScale = textScale;
this.x = (short) x;
this.y = (short) y;
this.textColor = (byte) textColor;
this.vectorScale = vectorScale;
this.duration = (short) duration;
this.creationTime = System.currentTimeMillis();
}
public boolean getActiveState() {
return this.active;
}
/**
* Prepares the TextElement for rendering.
*/
public void activateTimer() {
this.active = true;
long testLong = this.creationTime;
if (this.duration > 0) {
Timer levelTitleGTFO = new Timer();
levelTitleGTFO.schedule(new TimerTask() {
public void run() {
TextElement.this.setVisibility(false);
}
}, this.duration);
}
this.setVisibility(true);
}
public boolean getVisibility() {
return this.isVisible;
}
public void setVisibility(boolean visibility) {
this.isVisible = visibility;
}
/**
* Prepares the TextElement for culling.
*/
public void cull() {
this.active = false;
this.x = 32767;
this.y = 32767;
}
/**
* Changes the text that the TextElement will display.
* @param text The text that the TextElement will display.
*/
public void setText(String text) {
this.text = text;
}
/**
* Changes the text that the TextElement will display.
* @param x The x coordinate of the text's origin point.
* @param y The y coordinate of the text's origin point.
*/
public void moveTo(int x, int y) {
this.x = (short) x;
this.y = (short) y;
}
/**
* Changes the color of the text.
* @param textColor The color code of the text.
*/
public void setColor(int textColor) {
this.textColor = (byte) textColor;
}
}

View file

@ -1,64 +0,0 @@
package dev.boyfailure.quajra.vectorbreakout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import javax.swing.JFrame;
import javax.swing.Timer;
public class VectorBreakout {
// The embeddable game
public static GameState gameState = new GameState();
// The window for the game
public static JFrame gameFrame = new JFrame();
public static GameDisplay gameCanvas = new GameDisplay(gameState);
public VectorBreakout() {
}
public static void main(String[] args) {
// Enable OpenGL for hardware acceleration
System.setProperty("sun.java2d.opengl", "true");
// Set the game window's properties
gameFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
gameFrame.setTitle(gameState.gameName);
gameFrame.setSize(gameState.getInternalResX(), gameState.getInternalResY());
gameFrame.getContentPane().setBackground(Color.black);
gameFrame.getContentPane().add(gameCanvas);
gameFrame.setVisible(true);
gameFrame.addKeyListener(gameState.getKeyListener());
// Adapts vector drawing scale when the window is resized
gameFrame.addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent componentEvent) {
gameState.setWindowResX((short) gameFrame.getBounds().width);
gameState.setWindowResY((short) gameFrame.getBounds().height);
gameCanvas.setBeamScaleX(gameState.getWindowResX() / 800f);
gameCanvas.setBeamScaleY(gameState.getWindowResY() / 600f);
}
});
Timer gameTick = new Timer((1000 / gameState.getGameTickRate()), gameState.getGameTickActionListener());
gameTick.start();
Timer frameDisplay = new Timer((1000 / gameState.getTargetFrameRate()), new ActionListener() {
public void actionPerformed(ActionEvent e) {
gameState.incrementFrameCounter();
gameFrame.repaint();
}
});
frameDisplay.start();
Timer gameCuller = new Timer(1000, gameState.getGameStateUpdateActionListener());
gameCuller.start();
}
}

View file

@ -0,0 +1,283 @@
/*
* Copyright 2025 Naomi (boyfailure.dev).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.boyfailure.vectorbreakout;
/**
* The balls that move around the playfield and hit bricks.
* @author Naomi (boyfailure.dev)
* @since 20250323
*/
public class Ball extends Entity {
private boolean isActive = false;
private boolean isInvincible = false;
byte ballColor = 0;
byte speedX = 5;
byte speedY = 5;
private GameState gameState;
public Ball(GameState gameState, int x, int y, int width, int height, int color, boolean active) {
this.gameState = gameState;
this.posX = (short) x;
this.posY = (short) y;
this.width = (short) width;
this.height = (short) height;
this.ballColor = (byte) color;
this.hitBox = new Hitbox(x, y, width, height, 1);
this.isActive = active;
}
public void spawnBall(boolean useLFSR) {
if (useLFSR) {this.spawnBall(((gameState.getPokey().getDecimalValue() / 2) + gameState.getPokey().getShiftedDecimalValue() + 30), (this.posY + (gameState.getPokey().getDecimalValue() / 4)) - 40);}
else {this.spawnBall(this.posX, this.posY);}
}
public void spawnBall(int x, int y) {
this.posX = (short) x;
this.posY = (short) y;
this.isActive = true;
if (gameState.getBalls().size() <= 1) {
gameState.removeLife();
gameState.getTextElements().get(4).setText(String.valueOf(gameState.getLives()));
}
}
public void moveBall() {
this.posX += this.speedX;
this.posY += this.speedY;
this.hitBox.moveTo(this.posX, this.posY, this.width, this.height);
this.checkCollision();
}
/**
* Richochets the ball.
* @param bounceType The code for which direction the ball bounces
*/
public void bounce(int bounceType) {
switch((byte) bounceType) {
case 0:
this.speedY = (byte) -(this.speedY);
byte randByte = (byte) (gameState.getPokey().getDecimalValue() / 16);
if (randByte < 0) {randByte = 0;}
else if (randByte > 15) {randByte = 15;}
switch (randByte) {
case 0: break;
case 1: this.speedX++; break;
case 2: break;
case 3: this.speedX--; break;
case 4: break;
case 5: this.speedY++;break;
case 6: break;
case 7: this.speedY--;break;
case 8: break;
case 9: this.speedY++;speedX++;break;
case 10: break;
case 11: this.speedY--;speedX--;break;
case 12: break;
case 13: this.speedY++;speedY++;break;
case 14: break;
case 15: this.speedY--;speedY--;break;
}
break;
case 1:
this.speedX = (byte) -(this.speedX);
break;
case 2:
if (this.speedX > 0) {
this.speedX = (byte) -(this.speedX);
}
else {
this.speedX -= 4;
}
this.speedY = (byte) -(this.speedY);
break;
case 3:
if (this.speedX < 0) {
this.speedX = (byte) -(this.speedX);
}
else {
this.speedX += 4;
}
this.speedY = (byte) -(this.speedY);
break;
}
if (this.speedX < 2 && this.speedX >= 0) {this.speedX = 2;}
else if (this.speedX > -2 && this.speedX < 0) {this.speedX = -2;}
else if (this.speedX > 11) {this.speedX -= 3;} // Slow down if too fast
if (this.speedY < 4 && this.speedY >= 0) {this.speedY = 4;}
else if (this.speedY > -4 && this.speedY < 0) {this.speedY = -4;}
else if (this.speedY > 11) {this.speedY -= 3;} // Slow down if too fast
}
public void destroyBall() {
this.isActive = false;
this.posX = 32767;
this.posY = 32767;
this.hitBox.setBounds(32766, 32766, 32767, 32767);
if (gameState.getBalls().size() <= 1) {
if (gameState.getLives() <= 0) {
gameState.gameLose();
}
}
}
public void cull() {
this.isActive = false;
this.posX = 32767;
this.posY = 32767;
this.hitBox.setBounds(32766, 32766, 32767, 32767);
}
/**
* Checks to see if the ball is in contact with other Entities and walls.
*/
public synchronized void checkCollision() {
this.setInvincibilityState(true); // Delay bouncing again for a frame
// Check floor
if (this.posY >= gameState.getInternalResY()) {
this.destroyBall();
return;
}
// Check ceiling
else if (this.posY <= 0) {
this.bounce(0);
this.posY = 0;
this.wallBounceAnimation();
return;
}
// Check walls
if (this.posX <= 20) {
this.bounce(1);
this.posX = 22;
this.wallBounceAnimation();
return;
}
else if ((this.posX + this.width) >= (gameState.getInternalResX() - 20 - this.width)) {
this.bounce(1);
this.posX = (short) (gameState.getInternalResX() - 25 - this.width);
this.wallBounceAnimation();
return;
}
// Check bricks
if (gameState.getCurrentLevel() != null) {
if (gameState.getCurrentLevel().brickList != null) {
for (Brick brick : gameState.getCurrentLevel().brickList) {
// Check for left bounce
if (this.hitBox.collides(brick.hitBoxLeft.getBounds())) {
if (this.hitBox.collides(brick.hitBox.getBounds())) {
this.bounce(0);
this.bounce(1);
}
else {
this.bounce(1);
}
brick.breakBrick();
return;
}
// Check for right bounce
else if (this.hitBox.collides(brick.hitBoxRight.getBounds())) {
if (this.hitBox.collides(brick.hitBox.getBounds())) {
this.bounce(0);
this.bounce(1);
}
else {
this.bounce(1);
}
brick.breakBrick();
return;
}
// Check for vertical bounce
else if (this.hitBox.collides(brick.hitBox.getBounds())) {
brick.breakBrick();
this.bounce(0);
return;
}
}
}
}
// Check paddles
for (Paddle paddle : gameState.getPaddles()) {
if (this.hitBox.collides(paddle.hitBox.getBounds())) {
if (this.hitBox.collides(paddle.hitBoxLeft.getBounds())) {
this.bounce(2);
}
else if (this.hitBox.collides(paddle.hitBoxRight.getBounds())) {
this.bounce(3);
}
else {
this.bounce(0);
}
this.posY -= 2;
// Draw particles
Particle particle1, particle2, particle3;
particle1 = new Particle(gameState, 0, this.posX, this.posY - 10, 0, -2, 1, 2, 7, 3);
particle2 = new Particle(gameState, 0, this.posX - 3, this.posY - 10, -2, -4, 1, 2, 6, 3);
particle3 = new Particle(gameState, 0, this.posX + 10, this.posY - 10, 2, -4, 1, 2, 8, 3);
gameState.getParticles().add(particle1);
gameState.getParticles().add(particle2);
gameState.getParticles().add(particle3);
particle1.spawn(5);
particle2.spawn(5);
particle3.spawn(5);
return; // Prevents bouncing multiple times
}
}
}
private void wallBounceAnimation() {
Particle particle1, particle2, particle3, particle4;
particle1 = new Particle(gameState, 0, this.posX - 3, this.posY - 10, -2, -2, 1, 2, 6, 3);
particle2 = new Particle(gameState, 0, this.posX + 10, this.posY - 10, 2, -2, 1, 2, 8, 3);
particle3 = new Particle(gameState, 0, this.posX - 3, this.posY + 3, 2, 2, 1, 2, 2, 3);
particle4 = new Particle(gameState, 0, this.posX + 10, this.posY + 3, -2, 2, 1, 2, 4, 3);
gameState.getParticles().add(particle1);
gameState.getParticles().add(particle2);
gameState.getParticles().add(particle3);
gameState.getParticles().add(particle4);
particle1.spawn(5);
particle2.spawn(5);
particle3.spawn(5);
particle4.spawn(5);
}
public boolean getActiveState() {
return this.isActive;
}
public void setActiveState(boolean activeState) {
this.isActive = activeState;
}
public boolean getInvincibilityState() {
return this.isInvincible;
}
public void setInvincibilityState(boolean invincibilityState) {
this.isInvincible = invincibilityState;
}
}

View file

@ -0,0 +1,120 @@
/*
* Copyright 2025 Naomi (boyfailure.dev).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.boyfailure.vectorbreakout;
/**
* The bricks that appear in each level.
* @author Naomi (boyfailure.dev)
* @since 20250323
*/
public class Brick extends Entity {
private boolean isBroken = false;
byte brickColor = 0;
byte scoreOnBreak = 10;
byte specialAbility = 0;
Hitbox hitBoxLeft;
Hitbox hitBoxRight;
private GameState gameState;
/**
* Constructs a brick.
* @param gameState The GameState in which the Brick will reside.
* @param x The x coordinate of the Brick.
* @param y The y coordinate of the Brick.
* @param isBroken Determines whether the Brick is broken.
* @param brickColor The color code of the Brick.
* @param score The score the Player earns upon breaking the Brick.
* @param specialAbility A special event that occurs upon breaking the Brick.
*/
public Brick(GameState gameState, int x, int y, boolean isBroken, int brickColor, int score, int specialAbility) {
this.width = 40;
this.height = 17;
this.gameState = gameState;
this.posX = (short) x;
this.posY = (short) y;
this.isBroken = isBroken;
this.brickColor = (byte) brickColor;
this.scoreOnBreak = (byte) score;
this.hitBox = new Hitbox(x + 2, y, this.width - 4, this.height, 0);
this.hitBoxLeft = new Hitbox(x, y + 2, 6, this.height - 4, 0);
this.hitBoxRight = new Hitbox(x + this.width - 6, y + 2, 6, this.height - 4, 0);
this.specialAbility = (byte) specialAbility;
}
public void breakBrick() {
if (!isBroken) {
isBroken = true;
gameState.addScore(this.scoreOnBreak + (gameState.getCurrentLevelID() - 1));
gameState.getTextElements().get(3).setText(String.valueOf(gameState.getScore()));
switch(this.specialAbility) {
case 1:
gameState.addLife();
break;
case 2:
while (gameState.getCollectorActiveState()) {}
Ball newBall = new Ball(gameState, 300, 300, 7, 7, 0, true);
this.gameState.getBalls().add(newBall);
newBall.spawnBall((this.posX + (this.width / 2)), (this.posY + (this.height / 2)));
break;
default:
break;
}
// Show score on break
TextElement bruh = new TextElement(this.gameState,
String.valueOf(this.scoreOnBreak),
0.25f,
this.posX + (this.width / 3),
this.posY - 3,
this.brickColor,
1,
300);
bruh.activateTimer();
bruh.setActiveState(true);
//bruh.moveUpOnFrame();
this.gameState.getTextElements().add(bruh);
}
// Draw particles
Particle particle1, particle2, particle3;
particle1 = new Particle(gameState, 0, this.posX + 12, this.posY + 3, 1, 1, this.brickColor, 2, 4, 1);
particle2 = new Particle(gameState, 0, this.posX + 20, this.posY + 13, 1, 1, this.brickColor, 2, 3, 1);
particle3 = new Particle(gameState, 0, this.posX + 32, this.posY + 7, 1, 1, this.brickColor, 2, 2, 1);
gameState.getParticles().add(particle1);
gameState.getParticles().add(particle2);
gameState.getParticles().add(particle3);
particle1.spawn(5);
particle2.spawn(5);
particle3.spawn(5);
this.posX = 32767;
this.posY = 32767;
this.hitBox.moveTo(32760, 32760, 2, 2);
this.hitBoxLeft.moveTo(32760, 32760, 2, 2);
this.hitBoxRight.moveTo(32760, 32760, 2, 2);
}
public boolean getBrokenState() {
return this.isBroken;
}
public void setBrokenState(boolean brokenState) {
this.isBroken = brokenState;
}
}

View file

@ -0,0 +1,65 @@
/*
* Copyright 2025 naomi.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.boyfailure.vectorbreakout;
import java.awt.Point;
/**
* Base class for all entities in the game.
* @author Naomi (boyfailure.dev)
* @since 1.0
*/
public class Entity {
/** The horizontal position of the entity, in pixels. */
short posX;
/** The vertical position of the entity, in pixels. */
short posY;
/** The width of the entity, in pixels. */
short width;
/** The height of the entity, in pixels. */
short height;
/** The default hitbox of the entity. */
Hitbox hitBox;
//<editor-fold defaultstate="collapsed" desc=" Accessor methods (get/set) ">
/**
* Returns the position of the entity as a Point.
* @return the position of the entity
*/
public Point getPosition() {
return new Point(this.posX, this.posY);
}
/**
* Returns the horizontal position of the entity.
* @return the horizontal position of the entity
*/
public short getPositionX() {
return this.posX;
}
/**
* Returns the vertical position of the entity.
* @return the vertical position of the entity
*/
public short getPositionY() {
return this.posY;
}
//</editor-fold>
}

View file

@ -1,4 +1,20 @@
package dev.boyfailure.quajra.vectorbreakout;
/*
* Copyright 2025 Naomi (boyfailure.dev).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.boyfailure.vectorbreakout;
import java.awt.BasicStroke;
import java.awt.Color;
@ -7,6 +23,11 @@ import java.awt.Graphics2D;
import javax.swing.JComponent;
import java.awt.geom.Line2D;
/**
* The logic to draw the game on the screen.
* @author Naomi (boyfailure.dev)
* @since 20250323
*/
public class GameDisplay extends JComponent {
// Change this to 1 if you build for older versions of Java. Older versions
@ -45,7 +66,7 @@ public class GameDisplay extends JComponent {
this.beamThicknessScale = beamScale;
}
public void paint(Graphics gameGraphics) {
public synchronized void paint(Graphics gameGraphics) {
super.paint(gameGraphics);
@ -65,18 +86,18 @@ public class GameDisplay extends JComponent {
}
// Bricks
if (gameState.currentLevel != null) {
if (gameState.currentLevel.brickList != null) {
for (Brick brick : gameState.currentLevel.brickList) {
if (gameState.getCurrentLevel() != null) {
if (gameState.getCurrentLevel().brickList != null) {
for (Brick brick : gameState.getCurrentLevel().brickList) {
if (!brick.getBrokenState()) {
resetBeam();
g2.setStroke(bt(3));
moveBeam(brick.brickX + 1, brick.brickY + 1);
moveBeam(brick.posX + 1, brick.posY + 1);
g2.setColor(vc(brick.brickColor));
g2.draw(drawVec(brick.brickWidth - 2, 0));
g2.draw(drawVec(0, -(brick.brickHeight - 2)));
g2.draw(drawVec(-(brick.brickWidth - 2), 0));
g2.draw(drawVec(0, brick.brickHeight - 2));
g2.draw(drawVec(brick.width - 2, 0));
g2.draw(drawVec(0, -(brick.height - 2)));
g2.draw(drawVec(-(brick.width - 2), 0));
g2.draw(drawVec(0, brick.height - 2));
}
}
}
@ -84,34 +105,34 @@ public class GameDisplay extends JComponent {
// Paddles
g2.setColor(Color.white);
for (Paddle paddle : gameState.paddleList) {
for (Paddle paddle : gameState.getPaddles()) {
if (paddle.getActiveState()) {
resetBeam();
g2.setStroke(bt(3));
moveBeam(paddle.paddleX, paddle.paddleY);
g2.draw(drawVec(paddle.paddleWidth, 0));
g2.draw(drawVec(0, -(paddle.paddleHeight)));
g2.draw(drawVec(-(paddle.paddleWidth), 0));
g2.draw(drawVec(0, paddle.paddleHeight));
moveBeam(paddle.posX, paddle.posY);
g2.draw(drawVec(paddle.width, 0));
g2.draw(drawVec(0, -(paddle.height)));
g2.draw(drawVec(-(paddle.width), 0));
g2.draw(drawVec(0, paddle.height));
}
}
// Balls
g2.setStroke(bt(3));
g2.setColor(Color.yellow);
for (Ball ball : gameState.ballList) {
for (Ball ball : gameState.getBalls()) {
if (ball.getActiveState()) {
resetBeam();
moveBeam(ball.ballX, ball.ballY);
g2.draw(drawVec(ball.ballWidth, 0));
g2.draw(drawVec(0, -ball.ballHeight));
g2.draw(drawVec(-ball.ballWidth, 0));
g2.draw(drawVec(0, ball.ballHeight));
moveBeam(ball.posX, ball.posY);
g2.draw(drawVec(ball.width, 0));
g2.draw(drawVec(0, -ball.height));
g2.draw(drawVec(-ball.width, 0));
g2.draw(drawVec(0, ball.height));
}
}
// Particles
for (Particle particle : gameState.particleList) {
for (Particle particle : gameState.getParticles()) {
if (particle.getActiveState()) {
resetBeam();
moveBeam(particle.particleX, particle.particleY);
@ -121,10 +142,11 @@ public class GameDisplay extends JComponent {
}
}
/* TextElements
/*
* TextElements
*
* Note on rendering numbers:
*
*
* I am aware that I could be using a switch statement for these.
* However, this did not compile when testing in JDK 1.5, as it would
* refuse to accept anything other than numbers in a switch statement.
@ -132,24 +154,25 @@ public class GameDisplay extends JComponent {
* systems, I used these goofy if/else statements so I could compile
* on the older JDKs and play on my older systems...
*/
for (TextElement element : gameState.textElementList) {
if (element.getActiveState() && element.getVisibility()) {
for (TextElement element : gameState.getTextElements()) {
if (element.getActiveState() && element.getVisibleState()) {
if (element.movesUpOnFrame()) {element.moveTo(element.getPositionX(), element.getPositionY() - 1);}
resetBeam();
moveBeam(element.x, element.y);
moveBeam(element.getPositionX(), element.getPositionY());
// Save the current beam scale to revert once TextElement is done rendering
float oldBeamScaleX = beamScaleX;
float oldBeamScaleY = beamScaleY;
float oldBeamScaleX = this.beamScaleX;
float oldBeamScaleY = this.beamScaleY;
// Adjust the beam scale to accomodate the scale of the TextElement
beamScaleX = beamScaleX * element.textScale;
beamScaleY = beamScaleY * element.textScale;
this.beamScaleX *= element.getTextScale();
this.beamScaleY *= element.getTextScale();
// Render the TextElement
g2.setColor(vc(element.textColor));
g2.setStroke(bt(element.vectorScale));
String stringToSplit = String.valueOf(element.text);
String[] stringToRender = stringToSplit.split("");
g2.setColor(vc(element.getColor()));
g2.setStroke(bt(element.getVectorThickness()));
String[] stringToRender = element.getText().split("");
//<editor-fold defaultstate="collapsed" desc=" Beam routines for letters/numbers ">
for (short i = iStart; i < stringToRender.length; i++) {
if ("1".equals(stringToRender[i])) {
@ -233,7 +256,7 @@ public class GameDisplay extends JComponent {
}
// Simulates a newline in a TextElement
else if ("\\".equals(stringToRender[i])) {
moveBeam(-(beamX - element.x), 30);
moveBeam(-(beamX - element.getPositionX()), 30);
}
else if ("A".equals(stringToRender[i])) {
g2.draw(drawVec(0, -17));
@ -477,67 +500,81 @@ public class GameDisplay extends JComponent {
//</editor-fold>
// Revert back to the previous beam scale
beamScaleX = oldBeamScaleX;
beamScaleY = oldBeamScaleY;
this.beamScaleX = oldBeamScaleX;
this.beamScaleY = oldBeamScaleY;
}
}
gameState.actualFrames++; // Count up actual frames rendered
gameState.incrementActualFrames(); // Count up actual frames rendered
// Debug Menu
if (gameState.getDebugMenuState()) {
g2.setColor(Color.white);
g2.drawString(gameState.gameName + " v" + gameState.gameVersion, 1, 12);
g2.drawString("frame: " + gameState.getFrameCounter(), 1, 24);
g2.drawString("beamScaleX: " + beamScaleX, 1, 36);
g2.drawString("beamScaleY: " + beamScaleY, 1, 48);
g2.drawString("isPaused: " + gameState.getPausedState(), 1, 60);
g2.drawString("targetFrameRate: " + gameState.getTargetFrameRate(), 150, 24);
g2.drawString("actualFrameRate: " + gameState.getActualFrameRate(), 150, 36);
g2.drawString("actualFrames: " + gameState.actualFrames, 150, 48);
g2.drawString("chancey: " + gameState.getChancey(), 150, 60);
g2.drawString("ballsOnScreen: " + gameState.ballsOnScreen, 300, 24);
g2.drawString(gameState.getGameName() + " v" + gameState.getGameVersion(), 1, 12);
g2.drawString("Frame: " + gameState.getFrameCounter(), 1, 24);
g2.drawString("Horizontal beamscale: " + beamScaleX, 1, 36);
g2.drawString("Vertical beamscale: " + beamScaleY, 1, 48);
g2.drawString("Paused: " + gameState.getPausedState(), 1, 60);
g2.drawString("Target framerate: " + gameState.getTargetFrameRate(), 150, 24);
g2.drawString("Actual framerate: " + gameState.getActualFrameRate(), 150, 36);
g2.drawString("actualFrames: " + gameState.getActualFrames(), 150, 48);
g2.drawString("LFSR: " + gameState.getPokey().getDecimalValue(), 150, 60);
g2.drawString("Ball count: " + gameState.getBalls().size(), 300, 24);
g2.drawString("Particle count: " + gameState.getParticles().size(), 300, 36);
g2.drawString("Paddle count: " + gameState.getPaddles().size(), 300, 48);
g2.drawString("Text count: " + gameState.getTextElements().size(), 300, 60);
g2.drawString("Brick count: " + gameState.getCurrentLevel().brickList.size(), 300, 72);
short padBoxX, padBoxY, padBoxX2, padBoxY2;
g2.setStroke(new BasicStroke(1));
// Paddles
g2.setColor(vc(10, 0.5f));
for (Paddle paddle : gameState.paddleList) {
for (Paddle paddle : gameState.getPaddles()) {
padBoxX = (short) paddle.hitBox.getBounds().getBounds2D().getMinX();
padBoxY = (short) paddle.hitBox.getBounds().getBounds2D().getMinY();
padBoxX2 = (short) paddle.hitBox.getBounds().getBounds2D().getWidth();
padBoxY2 = (short) paddle.hitBox.getBounds().getBounds2D().getHeight();
g2.fillRect(padBoxX, padBoxY + (40 - paddle.paddleHeight), padBoxX2, padBoxY2);
g2.fillRect(padBoxX, padBoxY + (40 - paddle.height), padBoxX2, padBoxY2);
padBoxX = (short) paddle.hitBoxLeft.getBounds().getBounds2D().getMinX();
padBoxY = (short) paddle.hitBoxLeft.getBounds().getBounds2D().getMinY();
padBoxX2 = (short) paddle.hitBoxLeft.getBounds().getBounds2D().getWidth();
padBoxY2 = (short) paddle.hitBoxLeft.getBounds().getBounds2D().getHeight();
g2.fillRect(padBoxX, padBoxY + (40 - paddle.paddleHeight), padBoxX2, padBoxY2);
g2.fillRect(padBoxX, padBoxY + (40 - paddle.height), padBoxX2, padBoxY2);
padBoxX = (short) paddle.hitBoxRight.getBounds().getBounds2D().getMinX();
padBoxY = (short) paddle.hitBoxRight.getBounds().getBounds2D().getMinY();
padBoxX2 = (short) paddle.hitBoxRight.getBounds().getBounds2D().getWidth();
padBoxY2 = (short) paddle.hitBoxRight.getBounds().getBounds2D().getHeight();
g2.fillRect(padBoxX, padBoxY + (40 - paddle.paddleHeight), padBoxX2, padBoxY2);
g2.fillRect(padBoxX, padBoxY + (40 - paddle.height), padBoxX2, padBoxY2);
}
// Balls
g2.setColor(vc(11, 1.0f));
for (Ball ball : gameState.ballList) {
for (Ball ball : gameState.getBalls()) {
padBoxX = (short) ball.hitBox.getBounds().getBounds2D().getMinX();
padBoxY = (short) ball.hitBox.getBounds().getBounds2D().getMinY();
padBoxX2 = (short) ball.hitBox.getBounds().getBounds2D().getWidth();
padBoxY2 = (short) ball.hitBox.getBounds().getBounds2D().getHeight();
g2.fillRect(padBoxX, padBoxY + (40 - ball.ballHeight), padBoxX2, padBoxY2);
g2.fillRect(padBoxX, padBoxY + (40 - ball.height), padBoxX2, padBoxY2);
}
// Bricks
g2.setColor(vc(12, 0.5f));
for (Brick brick : gameState.currentLevel.brickList) {
for (Brick brick : gameState.getCurrentLevel().brickList) {
padBoxX = (short) brick.hitBox.getBounds().getBounds2D().getMinX();
padBoxY = (short) brick.hitBox.getBounds().getBounds2D().getMinY();
padBoxX2 = (short) brick.hitBox.getBounds().getBounds2D().getWidth();
padBoxY2 = (short) brick.hitBox.getBounds().getBounds2D().getHeight();
g2.fillRect(padBoxX, padBoxY + (40 - brick.brickHeight), padBoxX2, padBoxY2);
g2.fillRect(padBoxX, padBoxY + (40 - brick.height), padBoxX2, padBoxY2);
padBoxX = (short) brick.hitBoxLeft.getBounds().getBounds2D().getMinX();
padBoxY = (short) brick.hitBoxLeft.getBounds().getBounds2D().getMinY();
padBoxX2 = (short) brick.hitBoxLeft.getBounds().getBounds2D().getWidth();
padBoxY2 = (short) brick.hitBoxLeft.getBounds().getBounds2D().getHeight();
g2.fillRect(padBoxX, padBoxY + (40 - brick.height), padBoxX2, padBoxY2);
padBoxX = (short) brick.hitBoxRight.getBounds().getBounds2D().getMinX();
padBoxY = (short) brick.hitBoxRight.getBounds().getBounds2D().getMinY();
padBoxX2 = (short) brick.hitBoxRight.getBounds().getBounds2D().getWidth();
padBoxY2 = (short) brick.hitBoxRight.getBounds().getBounds2D().getHeight();
g2.fillRect(padBoxX, padBoxY + (40 - brick.height), padBoxX2, padBoxY2);
}
}

View file

@ -0,0 +1,70 @@
/*
* Copyright 2025 Naomi (boyfailure.dev).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.boyfailure.vectorbreakout;
import java.awt.Color;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import javax.swing.JFrame;
/**
* The window in which the game is displayed.
* @author Naomi (boyfailure.dev)
* @since 1.0
*/
public class GameFrame extends JFrame {
private GameState gameState;
private GameDisplay gameDisplay;
public GameFrame(GameState gameState, GameDisplay gameDisplay) {
this.gameState = gameState;
this.gameDisplay = gameDisplay;
// Set the game window's properties
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setTitle(this.gameState.getGameName());
this.setSize(this.gameState.getWindowResX(), this.gameState.getWindowResY());
this.getContentPane().setBackground(Color.black);
this.getContentPane().add(this.gameDisplay);
// Add the component adapter to change vector drawing scale when the window is resized
this.addComponentListener(this.getComponentAdapter());
// Add the key listener to get player input
this.addKeyListener(this.gameState.getKeyListener());
}
//<editor-fold defaultstate="collapsed" desc=" Event listeners ">
public ComponentAdapter getComponentAdapter() {
return new GameComponentAdapter();
}
class GameComponentAdapter extends ComponentAdapter {
public void componentResized(ComponentEvent componentEvent) {
GameFrame.this.gameState.setWindowResX((short) GameFrame.this.getBounds().width);
GameFrame.this.gameState.setWindowResY((short) GameFrame.this.getBounds().height);
GameFrame.this.gameDisplay.setBeamScaleX(GameFrame.this.gameState.getWindowResX() / 800f);
GameFrame.this.gameDisplay.setBeamScaleY(GameFrame.this.gameState.getWindowResY() / 600f);
if (GameFrame.this.gameDisplay.getBeamScaleX() <= GameFrame.this.gameDisplay.getBeamScaleY()) {GameFrame.this.gameDisplay.setBeamThicknessScale(GameFrame.this.gameDisplay.getBeamScaleX());}
else {GameFrame.this.gameDisplay.setBeamThicknessScale(GameFrame.this.gameDisplay.getBeamScaleY());}
}
}
//</editor-fold>
}

View file

@ -1,4 +1,20 @@
package dev.boyfailure.quajra.vectorbreakout;
/*
* Copyright 2025 Naomi (boyfailure.dev).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.boyfailure.vectorbreakout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
@ -6,58 +22,70 @@ import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
/**
* The embeddable game object which contains the main loop and logic.
* @author Naomi (boyfailure.dev)
* @since 20250511
*/
public class GameState {
public String gameName = "Vector Breakout";
public String gameVersion = "20250511";
/** The name of the game. */
private String gameName = "Vector Breakout";
/** The version of the game. */
private String gameVersion = "1.0";
/** Determines whether the game has been started. */
private boolean isGameStarted = false;
/** Determines whether the game has been paused. */
private boolean isPaused = false;
/** Determines whether the debug menu has been enabled. */
private boolean debugMenuEnabled = false;
public boolean isLost = false;
public boolean movingLeft = false;
public boolean movingRight = false;
public boolean confettiMode = false;
public boolean collectorActive = false;
public byte lives = 5;
private byte chancey = 0;
private final byte maxChanceyValue = 16;
public byte ballsOnScreen = 0;
/** Determines whether the game has been lost. */
private boolean isLost = false;
/** Determines whether the player is moving left. */
private boolean movingLeft = false;
/** Determines whether the player is moving right. */
private boolean movingRight = false;
/** Determines whether "Confetti Mode" (long-lasting particles) has been enabled. */
private boolean confettiMode = false;
/** Determines whether the collector function is running. */
private boolean collectorActive = false;
/** The number of lives the player has. */
private byte lives = 5;
/** The target frame rate for the game. */
private short targetFrameRate = 60;
public short actualFrames = 0;
private short actualFrames = 0;
private short actualFrameRate = 0;
public byte gameTickRate = 60;
private byte gameTickRate = 60;
private short internalResX = 800;
private short internalResY = 600;
private short windowResX = 1024;
private short windowResY = 768;
public short level = 1;
public short bricksOnScreen = 0;
public int score = 0;
private short windowResX = 1280;
private short windowResY = 960;
private short level = 1;
private short bricksOnScreen = 0;
private int score = 0;
private long frameCounter = 0;
public ArrayList<Ball> ballList;
public ArrayList<Particle> particleList;
public ArrayList<Paddle> paddleList;
public ArrayList<TextElement> textElementList;
private Level currentLevel;
public ArrayList<Ball> ballCollector;
public ArrayList<Particle> particleCollector;
public ArrayList<Paddle> paddleCollector;
public ArrayList<TextElement> textElementCollector;
public ArrayList<Brick> brickCollector;
private ArrayList<Ball> ballList;
private ArrayList<Particle> particleList;
private ArrayList<Paddle> paddleList;
private ArrayList<TextElement> textElementList;
private ArrayList<Ball> ballCollector;
private ArrayList<Particle> particleCollector;
private ArrayList<Paddle> paddleCollector;
private ArrayList<TextElement> textElementCollector;
private ArrayList<Brick> brickCollector;
private LFSR pokey;
public Level currentLevel;
// Debug stuffs
public boolean debugSpawnAllBalls = false;
public boolean debugUseGiantPaddle = false;
public short debugStartLevel = 1;
public GameState() {
this.initComponents();
this.showTitleScreen();
}
//<editor-fold defaultstate="collapsed" desc=" Event listeners ">
public KeyListener getKeyListener() {
return new GameKeyListener();
}
@ -79,37 +107,29 @@ public class GameState {
class GameKeyListener implements KeyListener {
public void keyPressed(KeyEvent e) {
switch(e.getKeyCode()) {
case 27: // Escape
case 27: // Escape - Toggle debug menu
toggleDebugMenu();
break;
case 10: // Enter
if (GameState.this.getGameStartedState()) {
GameState.this.togglePausedState();
if (!GameState.this.getPausedState()) {
GameState.this.textElementList.get(6).setVisibility(false);
}
else {
GameState.this.textElementList.get(6).setVisibility(true);
}
}
case 10: // Enter - Pause/unpause
if (GameState.this.getGameStartedState()) {GameState.this.togglePausedState();}
break;
case 35: // End
case 35: // End - Start debug level
if (isLost || !GameState.this.getGameStartedState()) {GameState.this.setGameStartedState(true);GameState.this.newGame(true);}
break;
case 32: // Space
case 32: // Space - Start normal game, spawn ball
if (isLost || !GameState.this.getGameStartedState()) {GameState.this.setGameStartedState(true);GameState.this.newGame();}
else if (!GameState.this.getPausedState()) {
if (ballsOnScreen < 1) {
if (GameState.this.getBalls().size() < 1) {
Ball newBall = new Ball(GameState.this, 200, 300, 7, 7, 0, true);
GameState.this.ballList.add(newBall);
newBall.spawnBall(true);
}
}
break;
case 37: // Left
case 37: // Left - move left
if (GameState.this.getGameStartedState()) {movingLeft = true;}
break;
case 39: // Right
case 39: // Right - move right
if (GameState.this.getGameStartedState()) {movingRight = true;}
break;
}
@ -126,9 +146,9 @@ public class GameState {
}
public void keyTyped(KeyEvent e) {}
}
class GameStateUpdateListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
public synchronized void actionPerformed(ActionEvent e) {
GameState.this.collectorActive = true;
GameState.this.ballCollector = new ArrayList<>();
@ -145,7 +165,6 @@ public class GameState {
for (Brick brick : GameState.this.currentLevel.brickList) {
if (brick.getBrokenState()) {
GameState.this.brickCollector.add(brick);
//GameState.this.currentLevel.brickList.remove(brick);
}
else {
bricksOnScreen++;
@ -156,14 +175,12 @@ public class GameState {
for (Ball ball : GameState.this.ballList) {
if (!ball.getActiveState()) {
//GameState.this.ballList.remove(ball);
GameState.this.ballCollector.add(ball);
}
}
for (Particle particle: GameState.this.particleList) {
if (!particle.getActiveState()) {
//GameState.this.particleList.remove(particle);
GameState.this.particleCollector.add(particle);
}
}
@ -186,56 +203,129 @@ public class GameState {
}
}
public void incrementFrameCounter() {
this.frameCounter++;
}
public long getFrameCounter() {
return this.frameCounter;
}
//</editor-fold>
public void incrementChancey() {
this.chancey++;
if (this.chancey >= this.maxChanceyValue) {
this.chancey = 0;
}
}
public byte getChancey() {
return this.chancey;
}
//<editor-fold defaultstate="collapsed" desc=" Accessor methods (get/set) ">
//<editor-fold defaultstate="collapsed" desc=" Version information accessors ">
/**
* Gets the name of the game.
* @return the name of the game
*/
public String getGameName() {
return this.gameName;
}
/**
* Gets the version number of the game.
* @return the version number of the game
*/
public String getGameVersion() {
return this.gameVersion;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Pokey accessors ">
/**
* Gets the current instance of "Pokey", the game's LFSR.
* @return pokey
*/
public LFSR getPokey() {
return this.pokey;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Tick rate accessors ">
/**
* Gets the current game tick rate, in Hz.
* @return the current game tick rate (in Hz)
*/
public byte getGameTickRate() {
return this.gameTickRate;
}
/**
* Sets the current game tick rate, in Hz.
* @param newTickRate the game tick rate, in Hz
*/
public void setGameTickRate(byte newTickRate) {
this.gameTickRate = (byte) newTickRate;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Frame rate accessors ">
/**
* Gets the target game frame rate, in Hz.
* @return the target game frame rate (in Hz)
*/
public short getTargetFrameRate() {
return this.targetFrameRate;
}
/**
* Gets the actual game frame rate, in Hz.
* @return the actual game frame rate (in Hz)
*/
public short getActualFrameRate() {
return this.actualFrameRate;
}
/**
* Increments the frame counter.
*/
public void incrementFrameCounter() {
this.frameCounter++;
}
/**
* Gets the value of the frame counter.
* @return the value of the frame counter
*/
public long getFrameCounter() {
return this.frameCounter;
}
/**
* Gets the actual frames drawn during the last second.
* @return the actual frames drawn during the last second.
*/
public short getActualFrames() {
return this.actualFrames;
}
public void incrementActualFrames() {
this.actualFrames++;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Pause state accessors ">
public boolean getPausedState() {
return this.isPaused;
}
public void togglePausedState() {
this.isPaused = !this.isPaused;
if (this.getPausedState()) {this.textElementList.get(6).getVisibleState(true);}
else {this.textElementList.get(6).getVisibleState(false);}
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Debug menu state accessors ">
public boolean getDebugMenuState() {
return this.debugMenuEnabled;
}
public void toggleDebugMenu() {
this.debugMenuEnabled = !this.debugMenuEnabled;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Game start state accessors ">
public boolean getGameStartedState() {
return this.isGameStarted;
}
public void setGameStartedState(boolean startState) {
this.isGameStarted = startState;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Resolution accessors ">
public short getInternalResX() {
return this.internalResX;
}
@ -254,25 +344,94 @@ public class GameState {
public void setWindowResY(short resolution) {
this.windowResY = resolution;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Lives accessors ">
public byte getLives() {
return this.lives;
}
public void addLives(int livesAdded) {
if (livesAdded >= 127) {livesAdded = 127;}
else if (livesAdded < 0) {livesAdded = 0;}
this.lives += (byte) livesAdded;
}
public void addLife() {
this.addLives(1);
}
public void removeLife() {
this.lives--;
if (this.lives < 0) {this.lives = 0;}
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Score accessors ">
public int getScore() {
return this.score;
}
public void addScore(int scoreAdded) {
this.score += scoreAdded;
}
public void resetScore() {
this.score = 0;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Level accessors ">
public short getCurrentLevelID() {
return this.level;
}
public void setCurrentLevelID(short levelID) {
this.level = levelID;
}
public Level getCurrentLevel() {
return this.currentLevel;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" List accessors ">
public ArrayList<Ball> getBalls() {
return this.ballList;
}
public ArrayList<Particle> getParticles() {
return this.particleList;
}
public ArrayList<Paddle> getPaddles() {
return this.paddleList;
}
public ArrayList<TextElement> getTextElements() {
return this.textElementList;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Collector accessors ">
public boolean getCollectorActiveState() {
return this.collectorActive;
}
//</editor-fold>
//</editor-fold>
public synchronized void onGameTick() {
public void onGameTick() {
this.incrementChancey();
if (this.getPausedState()) {return;}
// Move player if keys are held
for (Paddle paddle : this.paddleList) {
if (this.movingLeft) {
paddle.movePaddle(false);
}
else if (this.movingRight) {
paddle.movePaddle(true);
}
else {
paddle.paddleSpeed = 0;
try {
for (Paddle paddle : this.paddleList) {
if (this.movingLeft) {
paddle.movePaddle(false);
}
else if (this.movingRight) {
paddle.movePaddle(true);
}
else {
paddle.paddleSpeed = 0;
}
}
} catch(java.util.ConcurrentModificationException except) {
System.err.println("ConcurrentModificationException, onGameTick() Paddle iteration " + System.currentTimeMillis());
}
// Ball logic (movement, collision checks)
try {
for (Ball ball : this.ballList) {
@ -281,27 +440,28 @@ public class GameState {
}
}
} catch(java.util.ConcurrentModificationException except) {
System.err.println("ConcurrentModificationException, onGameTick() Ball iteration\n"
+ "Resetting ballList ArrayList.");
// except.printStackTrace();
this.ballList = new ArrayList<>();
System.err.println("ConcurrentModificationException, onGameTick() Ball iteration " + System.currentTimeMillis());
}
// Particles
for (Particle particle : this.particleList) {
if (particle.getActiveState()) {
particle.update();
try {
for (Particle particle : this.particleList) {
if (particle.getActiveState()) {
particle.update();
}
}
} catch(java.util.ConcurrentModificationException except) {
System.err.println("ConcurrentModificationException, onGameTick() Particle iteration " + System.currentTimeMillis());
}
}
public void showTitleScreen() {
textElementList.get(0).activateTimer();
textElementList.get(1).activateTimer();
textElementList.get(2).activateTimer();
}
public void gameLose() {
public synchronized void gameLose() {
if (this.currentLevel != null) {
for (Brick brick : this.currentLevel.brickList) {
brick.setBrokenState(true);
@ -314,22 +474,22 @@ public class GameState {
}
for (Paddle paddle : this.paddleList) {
paddle.setActiveState(false);
paddle.cullPaddle();
this.paddleCollector.add(paddle);
}
this.isLost = true;
this.ballsOnScreen = 0;
this.textElementList.get(3).setVisibility(false);
this.textElementList.get(4).setVisibility(false);
this.textElementList.get(5).setVisibility(false);
this.textElementList.get(8).setVisibility(true);
this.textElementList.get(9).setVisibility(true);
this.textElementList.get(3).getVisibleState(false);
this.textElementList.get(4).getVisibleState(false);
this.textElementList.get(5).getVisibleState(false);
this.textElementList.get(8).getVisibleState(true);
this.textElementList.get(9).getVisibleState(true);
this.getGameStateUpdateActionListener().actionPerformed(new ActionEvent(this, 0, ""));
}
public void newGame() {
newGame(false);
}
public void newGame(boolean debugLevel) {
lives = 5;
isLost = false;
@ -337,39 +497,33 @@ public class GameState {
if (debugLevel) {level = 32767;}
else {level = 1;}
frameCounter = 0;
ballsOnScreen = 0;
movingLeft = false;
movingRight = false;
if (debugUseGiantPaddle) {this.paddleList.add(new Paddle(this, 0, 500, 800, 60, 0));}
else {this.paddleList.add(new Paddle(this, 350, 500, 100, 15, 0));}
if (this.debugUseGiantPaddle) {
this.paddleList.add(new Paddle(this, 0, 500, 800, 60, 0));
}
else {
this.paddleList.add(new Paddle(this, 350, 500, 100, 15, 0));
}
this.textElementList.get(0).setVisibility(false);
this.textElementList.get(1).setVisibility(false);
this.textElementList.get(2).setVisibility(false);
this.textElementList.get(3).setVisibility(true);
this.textElementList.get(4).setVisibility(true);
this.textElementList.get(5).setVisibility(true);
this.paddleList.add(new Paddle(this, 350, 500, 100, 15, 0));
this.textElementList.get(0).getVisibleState(false);
this.textElementList.get(1).getVisibleState(false);
this.textElementList.get(2).getVisibleState(false);
this.textElementList.get(3).getVisibleState(true);
this.textElementList.get(4).getVisibleState(true);
this.textElementList.get(5).getVisibleState(true);
textElementList.get(3).setText(String.valueOf(this.score));
textElementList.get(4).setText(String.valueOf(this.lives));
textElementList.get(5).setText(String.valueOf(this.level));
this.textElementList.get(8).setVisibility(false);
this.textElementList.get(9).setVisibility(false);
if (debugStartLevel > 1) {level = debugStartLevel;}
this.textElementList.get(8).getVisibleState(false);
this.textElementList.get(9).getVisibleState(false);
this.currentLevel = new Level(this, level);
this.currentLevel.constructLevel();
this.updateStaticTextElements();
}
public void initComponents() {
public synchronized void initComponents() {
this.ballList = new ArrayList<>();
this.particleList = new ArrayList<>();
this.paddleList = new ArrayList<>();
this.textElementList = new ArrayList<>();
this.pokey = new LFSR();
// List of indices
textElementList.add(new TextElement(this, "VECTOR BREAKOUT", 2.5f, 67, 280, 15, 2)); // 0 - Title
textElementList.add(new TextElement(this, "MMXXV BOYFAILURE.DEV", 0.75f, 259, 315, 15, 1)); // 1 - Copyright

View file

@ -1,7 +1,28 @@
package dev.boyfailure.quajra.vectorbreakout;
/*
* Copyright 2025 Naomi (boyfailure.dev).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.boyfailure.vectorbreakout;
import java.awt.geom.Rectangle2D;
/**
* Invisible regions in each object that dictate when it collides with other objects.
* @author Naomi (boyfailure.dev)
* @since 20250327
*/
public class Hitbox {
private Rectangle2D bounds;

View file

@ -0,0 +1,98 @@
/*
* Copyright 2025 Naomi (boyfailure.dev).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.boyfailure.vectorbreakout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Timer;
/**
* A software implementation of a LFSR-based (linear frequency shift register) pseudo-random number generator.
* @author Naomi (boyfailure.dev)
* @since 1.0
*/
public class LFSR {
private static boolean[] originalArray = new boolean[] {true,false,true,true,true,true,true,true,false,true,false,false,false,true,true,false,false};
private boolean[] bitArray;
private int bitCount;
private int bitsReturned;
private int firstXorIndex;
private int secondXorIndex;
private Timer lfsrTimer;
public LFSR(int bitCount, int bitsReturned, int firstXorIndex, int secondXorIndex) {
this.bitCount = bitCount;
this.bitsReturned = bitsReturned;
this.firstXorIndex = firstXorIndex;
this.secondXorIndex = secondXorIndex;
if (bitsReturned > bitCount) {bitsReturned = bitCount;}
bitArray = new boolean[bitCount];
for (int i = 0; i < bitCount; i++) {
if (i < 17) {bitArray[i] = LFSR.originalArray[i];}
else {
bitArray[i] = bitArray[i - 3] ^ bitArray[i - 8];
}
}
this.lfsrTimer = new Timer(1, new ActionListener(){public void actionPerformed(ActionEvent e) {LFSR.this.shift();}});
this.lfsrTimer.start();
}
public LFSR() {
this(17, 8, 11, 16);
}
public int getDecimalValue() {
int regValue = 0;
int bitIndex = this.bitCount - 1;
for (int i = 0; i < this.bitsReturned; i++) {
if (this.bitArray[bitIndex]) {
regValue += Math.pow(2, i);
}
bitIndex--;
}
return regValue;
}
public int getShiftedDecimalValue() {
this.shift();
return this.getDecimalValue();
}
public void shift() {
for (int i = (this.bitCount - 1); i > 0; i--) {
this.bitArray[i] = this.bitArray[i - 1];
}
this.bitArray[0] = this.bitArray[this.firstXorIndex] ^ this.bitArray[this.secondXorIndex];
}
public String getBits() {
String result = "";
for (int i = 0; i < this.bitCount; i++) {
if (this.bitArray[i] == true) {
result += "1";
}
else {
result += "0";
}
}
return result;
}
}

View file

@ -1,7 +1,28 @@
package dev.boyfailure.quajra.vectorbreakout;
/*
* Copyright 2025 Naomi (boyfailure.dev).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.boyfailure.vectorbreakout;
import java.util.ArrayList;
/**
* The progressive levels that contain different brick layouts.
* @author Naomi (boyfailure.dev)
* @since 20250325
*/
public class Level {
short levelID;
@ -17,8 +38,8 @@ public class Level {
public Level(GameState gameState, int id) {
this.gameState = gameState;
this.levelID = (short) id;
switch(id) {
switch(this.levelID) {
case 1:
this.levelName = "HELLO WORLD";
this.levelLayout = new byte[]
@ -29,35 +50,35 @@ public class Level {
this.colorMap = new byte[]
{10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,
13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12};
break;
case 2:
this.levelName = "DOUBLE TROUBLE";
this.levelLayout = new byte[]
{4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
{4,4,4,4,4, 4,4,4,4,4,4,4,4, 4,4,4,4,4,4,
3,3,3,3,3,16,3,3,3,3,3,3,3,16,3,3,3,3,3,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1};
2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,
1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1};
this.colorMap = new byte[]
{10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,
13,13,13,13,13,18,13,13,13,13,13,13,13,18,13,13,13,13,13,
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12};
break;
case 3:
this.levelName = "THE GAP";
this.levelLayout = new byte[]
{5,5,5,5,5,5,5,5,0,0,0,5,5,5,5,5,5,5,5,
4,4,4,4,4,4,4,4,0,0,0,4,4,4,4,4,4,4,4,
{5,5,5,5,5, 5,5,5,0,0,0,5,5, 5,5,5,5,5,5,
4,4,4,4,4, 4,4,4,0,0,0,4,4, 4,4,4,4,4,4,
3,3,3,3,3,16,3,3,0,0,0,3,3,16,3,3,3,3,3,
2,2,2,2,2,2,2,2,0,0,0,2,2,2,2,2,2,2,2,
1,1,1,1,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1};
2,2,2,2,2, 2,2,2,0,0,0,2,2, 2,2,2,2,2,2,
1,1,1,1,1, 1,1,1,0,0,0,1,1, 1,1,1,1,1,1};
this.colorMap = new byte[]
{14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,
13,13,13,13,13,18,13,13,13,13,13,13,13,18,13,13,13,13,13,
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12};
break;
case 4:
@ -70,7 +91,7 @@ public class Level {
this.colorMap = new byte[]
{10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,
13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12};
break;
case 5:
@ -88,12 +109,12 @@ public class Level {
this.colorMap = new byte[]
{10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,
13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,
13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12};
break;
case 6:
@ -112,11 +133,11 @@ public class Level {
{14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,
13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,
10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,
13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12};
break;
case 1001:
@ -130,14 +151,13 @@ public class Level {
this.colorMap = new byte[]
{20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,
19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,
20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20};
break;
case 1002:
this.levelName = "I USE ARCH BTW";
this.levelLayout = new byte[]
//1 2 3 4 5 6 7 8 9 0
{0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,6,6,6,0,0,0,0,0,0,0,0,
@ -168,17 +188,17 @@ public class Level {
7,1,2,3,4,5,6,7,1,2,3,4,5,6,7,1,2,3,4,
1,2,3,4,5,6,7,1,2,3,4,5,6,7,1,2,3,4,5};
this.colorMap = new byte[]
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,0,
2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,0,1,
3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,0,1,2,
4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,0,1,2,3,
5,6,7,8,9,10,11,12,13,14,15,16,17,18,0,1,2,3,4,
6,7,8,9,10,11,12,13,14,15,16,17,18,0,1,2,3,4,5,
7,8,9,10,11,12,13,14,15,16,17,18,0,1,2,3,4,5,6};
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,
1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18, 0,
2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18, 0, 1,
3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18, 0, 1, 2,
4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18, 0, 1, 2, 3,
5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18, 0, 1, 2, 3, 4,
6, 7, 8, 9,10,11,12,13,14,15,16,17,18, 0, 1, 2, 3, 4, 5,
7, 8, 9,10,11,12,13,14,15,16,17,18, 0, 1, 2, 3, 4, 5, 6};
break;
default:
this.levelName = "LEVEL 404: NOT FOUND (" + levelID + ")";
this.levelName = "LEVEL 404: NOT FOUND (" + this.levelID + ")";
this.levelLayout = new byte[]
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
@ -191,7 +211,7 @@ public class Level {
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2};
break;
}
}
public void constructLevel() {
@ -234,9 +254,8 @@ public class Level {
}
public void startLevel() {
gameState.bricksOnScreen = (short) this.brickList.size();
gameState.textElementList.set(7, new TextElement(gameState, this.levelName, 1, 24, 480, 15, 2, 3000));
gameState.textElementList.get(7).activateTimer();
gameState.getTextElements().set(7, new TextElement(gameState, this.levelName, 1, 24, 480, 15, 2, 3000));
gameState.getTextElements().get(7).activateTimer();
}
}
}

View file

@ -1,7 +1,28 @@
package dev.boyfailure.quajra.vectorbreakout;
/*
* Copyright 2025 Naomi (boyfailure.dev).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.boyfailure.vectorbreakout;
import java.util.ArrayList;
/**
* Customizable game menus.
* @author Naomi (boyfailure.dev)
* @since 1.0
*/
public class Menu {
public String menuTitle = "Menu";

View file

@ -0,0 +1,75 @@
/*
* Copyright 2025 Naomi (boyfailure.dev).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.boyfailure.vectorbreakout;
/**
* The player-controlled paddle that guides balls into bricks.
* @author Naomi (boyfailure.dev)
* @since 20250323
*/
public class Paddle extends Entity {
byte paddleColor;
byte paddleSpeed = 0;
boolean isActive = true;
float speedMultiplier = 1;
Hitbox hitBoxLeft;
Hitbox hitBoxRight;
private GameState gameState;
public Paddle(GameState gameState, int x, int y, int width, int height, int paddleColor) {
this.gameState = gameState;
this.posX = (short) x;
this.posY = (short) y;
this.width = (short) width;
this.height = (short) height;
this.paddleColor = (byte) paddleColor;
this.hitBox = new Hitbox(x, y, width, height, 4);
this.hitBoxLeft = new Hitbox(x, y, width / 5, height, 4);
this.hitBoxRight = new Hitbox(x + ((width / 5) * 4), y, width / 5, height, 4);
}
public void movePaddle(boolean direction) {
if (this.paddleSpeed <= 15) {this.paddleSpeed += 3;}
if (direction) {this.posX += (this.paddleSpeed * this.speedMultiplier);}
else {this.posX -= (this.paddleSpeed * this.speedMultiplier);}
if (this.posX <= 20) {this.posX = 20;}
else if (this.posX >= (gameState.getInternalResX() - this.width - 20)) {this.posX = (short) (gameState.getInternalResX() - this.width - 20);}
this.hitBox.moveTo(this.posX, this.posY, this.width, this.height);
this.hitBoxLeft.moveTo(this.posX, this.posY, this.width / 5, this.height);
this.hitBoxRight.moveTo(this.posX + ((this.width / 5) * 4), this.posY, this.width / 5, this.height);
}
public void cullPaddle() {
isActive = false;
this.posX = 32767;
this.posY = 32767;
this.hitBox.setBounds(32766, 32766, 32767, 32767);
this.hitBoxLeft.setBounds(32766, 32766, 32767, 32767);
this.hitBoxRight.setBounds(32766, 32766, 32767, 32767);
}
public boolean getActiveState() {
return this.isActive;
}
public void setActiveState(boolean activeState) {
this.isActive = activeState;
}
}

View file

@ -1,5 +1,26 @@
package dev.boyfailure.quajra.vectorbreakout;
/*
* Copyright 2025 Naomi (boyfailure.dev).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.boyfailure.vectorbreakout;
/**
* Small graphical effects that appear on screen.
* @author Naomi (boyfailure.dev)
* @since 20250325
*/
public class Particle {
byte particleID;
@ -32,8 +53,9 @@ public class Particle {
public void spawn(int lifetime) {
this.isActive = true;
if (gameState.confettiMode) {this.particleLifetime = 400;}
else {this.particleLifetime = (short) lifetime;}
// if (gameState.getConfettiMode()) {this.particleLifetime = 400;}
// else {this.particleLifetime = (short) lifetime;}
this.particleLifetime = (short) lifetime;
}
public void update() {

View file

@ -0,0 +1,247 @@
/*
* Copyright 2025 Naomi (boyfailure.dev).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.boyfailure.vectorbreakout;
import java.awt.Point;
import java.util.Timer;
import java.util.TimerTask;
/**
* Fields of text that display on screen.
* @author Naomi (boyfailure.dev)
* @since 20250326
*/
public class TextElement {
private String text;
private boolean active = true;
private boolean isVisible = false;
private boolean moveUpOnFrame = false;
private float textScale = 1;
private float vectorThickness = 1;
private byte textColor;
private short x;
private short y;
private short duration = 0;
private long creationTime;
private GameState gameState;
/**
* Text elements.
* @param gameState the GameState in which the TextElement will reside
* @param text the text that will be displayed
* @param textScale the size of the text on screen
* @param x the x coordinate of the text's origin point
* @param y the y coordinate of the text's origin point
* @param textColor the color of the text
* @param vectorThickness the thickness of the vector lines
*/
public TextElement(GameState gameState, String text, float textScale, int x, int y, int textColor, float vectorThickness) {
this.gameState = gameState;
this.text = text;
this.textScale = textScale;
this.x = (short) x;
this.y = (short) y;
this.textColor = (byte) textColor;
this.vectorThickness = vectorThickness;
}
/**
* Text elements with a fadeout timer
* @param gameState the GameState in which the TextElement will reside
* @param text the text that will be displayed
* @param textScale the size of the text on screen
* @param x the x coordinate of the text's origin point
* @param y the y coordinate of the text's origin point
* @param textColor the color of the text
* @param vectorThickness the thickness of the vector lines
* @param duration how long the text will stay on screen, in milliseconds
*/
public TextElement(GameState gameState, String text, float textScale, int x, int y, int textColor, float vectorThickness, int duration) {
this.gameState = gameState;
this.text = text;
this.textScale = textScale;
this.x = (short) x;
this.y = (short) y;
this.textColor = (byte) textColor;
this.vectorThickness = vectorThickness;
this.duration = (short) duration;
this.creationTime = System.currentTimeMillis();
}
//<editor-fold defaultstate="collapsed" desc=" Accessor methods (get/set) ">
//<editor-fold defaultstate="collapsed" desc=" Active state accessors ">
/**
* Gets the active status of the TextElement.
* @return the active status of the TextElement
*/
public boolean getActiveState() {
return this.active;
}
/**
* Sets the active status of the TextElement.
* @param activeState the active status of the TextElement
*/
public void setActiveState(boolean activeState) {
this.active = activeState;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Visibility state accessors ">
/**
* Gets the visibility state of the TextElement.
* @return the visibility state of the TextElement
*/
public boolean getVisibleState() {
return this.isVisible;
}
/**
* Sets the visible state of the TextElement.
* @param visibleState the visible state of the TextElement
*/
public void getVisibleState(boolean visibleState) {
this.isVisible = visibleState;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Color code accessors ">
/**
* Gets the color code of the TextElement.
* @return the color code of the TextElement
*/
public byte getColor() {
return this.textColor;
}
/**
* Sets the color code of the TextElement.
* @param textColor the color code of the TextElement
*/
public void setColor(int textColor) {
this.textColor = (byte) textColor;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Scaling accessors ">
/**
* Returns the thickness of the TextElement's vector lines.
* @return the thickness of the TextElement's vector lines
*/
public float getVectorThickness() {
return this.vectorThickness;
}
/**
* Sets the thickness of the TextElement's vector lines.
* @param vectorThickness the new thickness of the TextElement's vector lines
*/
public void setVectorThickness(float vectorThickness) {
this.vectorThickness = vectorThickness;
}
/**
* Returns the scale of the text.
* @return the scale of the text
*/
public float getTextScale() {
return this.textScale;
}
/**
* Sets the scale of the text.
* @param textScale the new scale of the text
*/
public void setTextScale(float textScale) {
this.textScale = textScale;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Text value accessors ">
/**
* Gets the text in the TextElement.
* @return the text in the TextElement
*/
public String getText() {
return this.text;
}
/**
* Changes the text that the TextElement will display.
* @param text the text that the TextElement will display
*/
public void setText(String text) {
this.text = text;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Moves up on frame accessors ">
public boolean movesUpOnFrame() {
return this.moveUpOnFrame;
}
public void moveUpOnFrame() {
this.moveUpOnFrame = true;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc=" Position accessors ">
public Point getPosition() {
return new Point(this.x, this.y);
}
public short getPositionX() {
return this.x;
}
public short getPositionY() {
return this.y;
}
//</editor-fold>
//</editor-fold>
/**
* Prepares the TextElement for rendering.
*/
public void activateTimer() {
this.setActiveState(true);
long testLong = this.creationTime;
if (this.duration > 0) {
Timer levelTitleGTFO = new Timer();
levelTitleGTFO.schedule(new TimerTask() {
public void run() {
TextElement.this.getVisibleState(false);
}
}, this.duration);
}
this.getVisibleState(true);
}
/**
* Prepares the TextElement for culling.
*/
public void cull() {
this.active = false;
this.x = 32767;
this.y = 32767;
}
/**
* Changes the text that the TextElement will display.
* @param x The x coordinate of the text's origin point.
* @param y The y coordinate of the text's origin point.
*/
public void moveTo(int x, int y) {
this.x = (short) x;
this.y = (short) y;
}
}

View file

@ -0,0 +1,71 @@
/*
* Copyright 2025 Naomi (boyfailure.dev).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.boyfailure.vectorbreakout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Timer;
/**
* The initial code to launch the game.
* @author Naomi (boyfailure.dev)
* @since 20250323
*/
public class VectorBreakout {
public static GameState gameState;
public static GameDisplay gameDisplay;
public static GameFrame gameFrame;
/**
* This empty constructor is currently only in here because the main class
* constructor generated in new projects on NetBeans 4. Will investigate to
* see if this is necessary in JDK 1.5 next time I am using those tools.
*/
public VectorBreakout() {}
public static void main(String[] args) {
// Enable OpenGL for hardware acceleration
System.setProperty("sun.java2d.opengl", "true");
// Initialize the components
gameState = new GameState();
gameDisplay = new GameDisplay(gameState);
gameFrame = new GameFrame(gameState, gameDisplay);
Timer gameTick = new Timer((1000 / gameState.getGameTickRate()), gameState.getGameTickActionListener());
gameTick.start();
Timer frameDisplay = new Timer((1000 / gameState.getTargetFrameRate()), new ActionListener() {
public void actionPerformed(ActionEvent e) {
gameState.incrementFrameCounter();
gameFrame.repaint();
}
});
frameDisplay.start();
Timer gameCuller = new Timer(1000, gameState.getGameStateUpdateActionListener());
gameCuller.start();
// Show the game window
gameState.showTitleScreen();
gameFrame.setVisible(true);
}
}