Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 94 additions & 29 deletions src/com/redomar/game/Game.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.redomar.game.event.MouseHandler;
import com.redomar.game.gfx.Screen;
import com.redomar.game.gfx.SpriteSheet;
import com.redomar.game.gfx.lighting.Night;
import com.redomar.game.level.LevelHandler;
import com.redomar.game.lib.Either;
import com.redomar.game.lib.Font;
Expand All @@ -23,6 +24,7 @@
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.io.Serial;

/*
* This module forms the core architecture of the JavaGame. It coordinates the various
Expand All @@ -33,47 +35,48 @@
public class Game extends Canvas implements Runnable {

// Setting the size and name of the frame/canvas
@Serial
private static final long serialVersionUID = 1L;
private static final String game_Version = "v1.8.6 Alpha";
private static final int WIDTH = 160;
private static final int HEIGHT = (WIDTH / 3 * 2);
private static final int SCALE = 3;
private static final int SCALE = 100;
private static final int WIDTH = 3 * SCALE;
private static final int SCREEN_WIDTH = WIDTH * 2;
private static final int HEIGHT = (2 * SCALE);
private static final int SCREEN_HEIGHT = (HEIGHT * 2) + 30;
private static final Screen screen = new Screen(WIDTH, HEIGHT, new SpriteSheet("/sprite_sheet.png"));
private static final String NAME = "Game"; // The name of the JFrame panel
private static final Time time = new Time(); // Represents the calendar's time value, in hh:mm:ss
private static final boolean[] alternateCols = new boolean[2]; // Boolean array describing shirt and face colour

private static Game game;

// The properties of the player, npc, and fps/tps
private static boolean changeLevel = false; // Determines whether the player teleports to another level
private static boolean npc = false; // Non-player character (NPC) initialized to non-existing
private static int map = 0; // Map of the level, initialized to map default map
private static int shirtCol; // The colour of the character's shirt
private static int faceCol; // The colour (ethnicity) of the character (their face)
private static int faceCol; // The colour (ethnicizty) of the character (their face)
private static int fps; // The frame rate (frames per second), frequency at which images are displayed on the canvas
private static int tps; // The ticks (ticks per second), unit measure of time for one iteration of the game logic loop.
private static int steps;
private static boolean devMode; // Determines whether the game is in developer mode
private static boolean closingMode; // Determines whether the game will exit

private static int tileX = 0;
private static int tileY = 0;
// Audio, input, and mouse handler objects
private static JFrame frame;
private static AudioHandler backgroundMusic;
private static boolean running = false; // Determines whether the game is currently in process
private static InputHandler input; // Accepts keyboard input and follows the appropriate actions
private static MouseHandler mouse; // Tracks mouse movement and clicks, and follows the appropriate actions
private static InputContext context; // Provides methods to control text input facilities

// Graphics
private final BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
private final BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
private final int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); // Array of red, green and blue values for each pixel
private final int[] colours = new int[6 * 6 * 6]; // Array of 216 unique colours (6 shades of red, 6 of green, and 6 of blue)
private final BufferedImage image2 = new BufferedImage(WIDTH, HEIGHT - 30, BufferedImage.TYPE_INT_RGB);
private final Font font = new Font(); // Font object capable of displaying 2 fonts: Arial and Segoe UI
private final Printer printer = new Printer();
boolean musicPlaying = false;
private int tickCount = 0;
private Screen screen;
private LevelHandler level; // Loads and renders levels along with tiles, entities, projectiles and more.
//The entities of the game
private Player player;
Expand All @@ -87,9 +90,9 @@ public Game() {
context = InputContext.getInstance();

// The game can only be played in one distinct window size
setMinimumSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
setMaximumSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
setPreferredSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
setMinimumSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT));
setMaximumSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT));
setPreferredSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT));

setFrame(new JFrame(NAME)); // Creates the frame with a defined name
getFrame().setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Exits the program when user closes the frame
Expand Down Expand Up @@ -288,6 +291,28 @@ public static void setClosing(boolean closing) {
Game.closingMode = closing;
}

private static void mousePositionTracker() {
MouseHandler mouseHandler = Game.getMouse();
int mouseX = mouseHandler.getX();
int mouseY = mouseHandler.getY();

// Adjust mouse coordinates based on the current offset and scale of the game world
tileX = ((mouseX + 4 + screen.getxOffset()) / (8 * 2)) + screen.getxOffset() / 16;
tileY = ((mouseY + 4 + screen.getyOffset()) / (8 * 2)) + screen.getyOffset() / 16;
}

public static int getTileX() {
return tileX;
}

public static int getTileY() {
return tileY;
}

public static Screen getScreen() {
return screen;
}

/*
* This method initializes the game once it starts. It populates the colour array with actual colours (6 shades each of RGB).
* This method also builds the initial game level (custom_level), spawns a new vendor NPC, and begins accepting keyboard and mouse input/tracking.
Expand All @@ -301,12 +326,16 @@ public void init() {
int rr = (r * 255 / 5); // Split all 256 colours into 6 shades (0, 51, 102 ... 255)
int gg = (g * 255 / 5);
int bb = (b * 255 / 5);
colours[index++] = rr << 16 | gg << 8 | bb; // All colour values (RGB) are placed into one 32-bit integer, populating the colour array
// All colour values (RGB) are placed into one 32-bit integer, populating the colour array
// The first 8 bits are for alpha, the next 8 for red, the next 8 for green, and the last 8 for blue
// 0xFF000000 is ignored in BufferedImage.TYPE_INT_RGB, but is used in BufferedImage.TYPE_INT_ARGB
colours[index++] = 0xFF << 24 | rr << 16 | gg << 8 | bb;
}
}
}

screen = new Screen(WIDTH, HEIGHT, new SpriteSheet("/sprite_sheet.png"));
screen.setViewPortHeight(SCREEN_HEIGHT);
screen.setViewPortWidth(SCREEN_WIDTH);
input = new InputHandler(this); // Input begins to record key presses
setMouse(new MouseHandler(this)); // Mouse tracking and clicking is now recorded
// setWindow(new WindowHandler(this));
Expand Down Expand Up @@ -343,12 +372,13 @@ public synchronized void stop() {
*/
public void run() {
long lastTime = System.nanoTime();
double nsPerTick = 1000000000D / 60D; // The number of nanoseconds in one tick (number of ticks limited to 60 per update)
int nsPerS = 1_000_000_000;
double nsPerTick = nsPerS / 60D; // The number of nanoseconds in one tick (number of ticks limited to 60 per update)
// 1 billion nanoseconds in one second
int ticks = 0;
int frames = 0;

long lastTimer = System.currentTimeMillis(); // Used for updating ticks and frames once every second
long lastTimer = System.nanoTime(); // Used for updating ticks and frames once every second
double delta = 0;

init(); // Initialize the game environment
Expand All @@ -371,8 +401,8 @@ public void run() {
render();
}

if (System.currentTimeMillis() - lastTimer >= 1000) { // If elapsed time is greater than or equal to 1 second, update
lastTimer += 1000; // Updates in another second
if (System.nanoTime() - lastTimer >= nsPerS) { // If elapsed time is greater than or equal to 1 second, update
lastTimer += nsPerS; // Updates in another second
getFrame().setTitle("JavaGame - Version " + WordUtils.capitalize(game_Version).substring(1, game_Version.length()));
fps = frames;
tps = ticks;
Expand All @@ -393,12 +423,15 @@ public void tick() {
printer.cast().print("Failed to play music", PrintTypes.MUSIC);
printer.exception(exception.toString());
musicPlaying = false;
}, isPlaying -> musicPlaying = isPlaying);

}, isPlaying -> {
musicPlaying = isPlaying;
if (musicPlaying && !Game.getBackgroundMusic().getActive()) {
input.overWriteKey(input.getM_KEY(), false);
}
});
level.tick();
}


/**
* This method displays the current state of the game.
*/
Expand All @@ -417,6 +450,7 @@ public void render() {
level.renderEntities(screen);
level.renderProjectileEntities(screen);


for (int y = 0; y < screen.getHeight(); y++) {
for (int x = 0; x < screen.getWidth(); x++) {
int colourCode = screen.getPixels()[x + y * screen.getWidth()];
Expand Down Expand Up @@ -452,10 +486,10 @@ public void render() {
changeLevel = false;
}

Graphics g = bs.getDrawGraphics();
g.drawRect(0, 0, getWidth(), getHeight());
Graphics2D g = (Graphics2D) bs.getDrawGraphics();
g.drawImage(image, 0, 0, getWidth(), getHeight() - 30, null);
status(g, isDevMode(), isClosing());
overlayRender(g);
g.drawImage(image2, 0, getHeight() - 30, getWidth(), getHeight(), null);
g.setColor(Color.WHITE);
g.setFont(font.getSegoe());
Expand Down Expand Up @@ -484,12 +518,24 @@ public void render() {
bs.show();
}

/**
* This method renders the overlay of the game, which is a transparent layer that is drawn over the game.
*/
private void overlayRender(Graphics2D g) {
g.setColor(new Color(0f, 0f, 0f, .192f)); // Transparent color
g.fillRect(0, 0, getWidth(), getHeight()-30);
}

/*
* This method displays information regarding various aspects/stats of the game, dependent upon
* whether it is running in developer mode, or if the application is closing.
*/
private void status(Graphics g, boolean TerminalMode, boolean TerminalQuit) {
private void status(Graphics2D g, boolean TerminalMode, boolean TerminalQuit) {
if (TerminalMode) {
new Night(g, screen).render(player.getPlayerAbsX(), player.getPlayerAbsY());
// make the background transparent
g.setColor(new Color(0, 0, 0, 100));
g.fillRect(0, 0, 195, 165);
g.setColor(Color.CYAN);
g.drawString("JavaGame Stats", 0, 10);
g.drawString("FPS/TPS: " + fps + "/" + tps, 0, 25);
Expand All @@ -499,9 +545,29 @@ private void status(Graphics g, boolean TerminalMode, boolean TerminalQuit) {
g.drawString("Foot Steps: " + steps, 0, 40);
g.drawString("NPC: " + WordUtils.capitalize(String.valueOf(isNpc())), 0, 55);
g.drawString("Mouse: " + getMouse().getX() + "x |" + getMouse().getY() + "y", 0, 70);
if (getMouse().getButton() != -1) g.drawString("Button: " + getMouse().getButton(), 0, 85);
g.setColor(Color.CYAN);
g.fillRect(getMouse().getX() - 12, getMouse().getY() - 12, 24, 24);
g.drawString("Mouse: " + (getMouse().getX() - 639 / 2d) + "x |" + (getMouse().getY() - 423 / 2d) + "y", 0, 85);
if (getMouse().getButton() != -1) g.drawString("Button: " + getMouse().getButton(), 0, 100);
mousePositionTracker();
g.drawString("Player: " + (int) player.getX() + "x |" + (int) player.getY() + "y", 0, 115);
double angle = Math.atan2(getMouse().getY() - player.getPlayerAbsY(), getMouse().getX() - player.getPlayerAbsX()) * (180.0 / Math.PI);
g.drawString("Angle: " + angle, 0, 130);

g.setColor(Color.cyan);
g.drawString("Player: \t\t\t\t\t\t\t\t\t\t\t\t" + player.getPlayerAbsX() + "x |" + player.getPlayerAbsY() + "y", 0, 145);
g.drawString("Player Offset: \t" + screen.getxOffset() + "x |" + screen.getyOffset() + "y", 0, 160);

// Set a different color for the player-origin line
g.setStroke(new BasicStroke(1));
g.setColor(Color.GREEN); // Green for the new line from the player's origin
g.drawLine(player.getPlayerAbsX(), player.getPlayerAbsY(), getMouse().getX(), getMouse().getY()); // Draw the line from the player's origin to the cursor
g.setColor(Color.DARK_GRAY);
g.drawLine(getWidth() / 2 + 8, getHeight() / 2 - 8, getMouse().getX(), getMouse().getY()); // Draw the line from the player's origin to the cursor
g.drawLine(getWidth() / 2 + 8, 0, getWidth() / 2 + 8, getHeight() - 30);
g.drawLine(0, getHeight() / 2 - 8, getWidth(), getHeight() / 2 - 8);
g.setColor(Color.yellow);
g.fillRect(player.getPlayerAbsX(), player.getPlayerAbsY(), 1, 1);


}
// If the game is shutting off
if (!TerminalQuit) {
Expand Down Expand Up @@ -529,5 +595,4 @@ public Vendor getVendor() {
public void setVendor(Vendor vendor) {
this.vendor = vendor;
}

}
17 changes: 12 additions & 5 deletions src/com/redomar/game/audio/AudioHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@ private void check(String path) {
}

/**
* Initialises an audio clip from the specified file path.
* Initialises an audio clip by loading an audio file from the specified path. This method sets up the audio stream and prepares the clip for playback.
*
* @param path the file path of the audio clip
* @param path the relative file path to the audio clip resource. The path must be accessible from the classpath and should not be null.
*/
private void initiate(String path) {
private void initiate(@NotNull String path) {
try {
InputStream inputStream = new BufferedInputStream(Objects.requireNonNull(AudioHandler.class.getResourceAsStream(path)));
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(inputStream);
Expand All @@ -59,6 +59,12 @@ private void initiate(String path) {
AudioInputStream decodedAudioInputStream = AudioSystem.getAudioInputStream(decodeFormat, audioInputStream);
clip = AudioSystem.getClip();
clip.open(decodedAudioInputStream);
clip.addLineListener(event -> {
if (event.getType() == LineEvent.Type.STOP) {
stop();
}
});

} catch (IOException e) {
musicPrinter.cast().exception("Audio file not found " + path);
musicPrinter.cast().exception(e.getMessage());
Expand Down Expand Up @@ -93,18 +99,19 @@ public void stop() {
if (clip == null) throw new RuntimeException("Empty clip");
if (clip.isRunning()) {
clip.stop();
clip.close();
if (!music) clip.close();
}
if (music & active) musicPrinter.print("Stopping Music");
} catch (Exception e) {
musicPrinter.print("Audio Handler Clip not found");
} finally {
if (music) musicPrinter.print("Stopping Music");
active = false;
}
}

public void close() {
stop();
clip.close();
}

public boolean getActive() {
Expand Down
40 changes: 37 additions & 3 deletions src/com/redomar/game/entities/Player.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public class Player extends Mob {
private static double speed = 1;
private final InputHandler inputHandler;
private int fireRate;
private int playerAbsX;
private int playerAbsY;

public Player(LevelHandler level, int x, int y, InputHandler inputHandler, String name, int shirtColour, int faceColour) {
super(level, "Player", x, y, PLAYER_TILE, speed, COLLISION_BORDERS, shirtColour, faceColour);
Expand All @@ -32,9 +34,14 @@ public void tick() {
double xa = 0;
double ya = 0;

// Calculate and set player's absolute X and Y positions
setPlayerAbsX((((int) getX() - Game.getScreen().getxOffset()) * 2) + 8);
setPlayerAbsY((((int) getY() - Game.getScreen().getyOffset()) * 2) + 7);


if (inputHandler != null) {

speed = inputHandler.getSHIFTED().isPressed() ? 2 : 1;
speed = inputHandler.getSHIFTED().isPressed() ? 2.5D : 1D;

if (inputHandler.getUP_KEY().isPressed()) {
ya -= speed;
Expand All @@ -60,10 +67,22 @@ public void tick() {
fireRate = Medium.FIRE_RATE;
}
if (!swim.isActive(swimType)) {
double dx = Game.getMouse().getX() - 480 / 2d;
double dy = Game.getMouse().getY() - 320 / 2d;

// Cursor position
int cursorX = Game.getMouse().getX();
int cursorY = Game.getMouse().getY();

// Calculate differences (dx, dy) between cursor and origin
double dx = cursorX - playerAbsX;
double dy = cursorY - playerAbsY;

// Calculate direction using atan2
double dir = Math.atan2(dy, dx);

// Continue with shooting logic
shoot(x, y, dir, Game.getMouse().getButton());

entityPrinter.highlight().print("Direction: " + dir + "º\t" + dx + "x\t" + dy + "y");
}
}
}
Expand Down Expand Up @@ -105,4 +124,19 @@ public String getSanitisedUsername() {
return this.name;
}

public int getPlayerAbsX() {
return playerAbsX;
}

public void setPlayerAbsX(int playerAbsX) {
this.playerAbsX = playerAbsX;
}

public int getPlayerAbsY() {
return playerAbsY;
}

public void setPlayerAbsY(int playerAbsY) {
this.playerAbsY = playerAbsY;
}
}
Loading