Skip to content

Commit

Permalink
Merge pull request #29 from redomar/feature/player_monitoring
Browse files Browse the repository at this point in the history
Refactor game code and add new features
  • Loading branch information
redomar committed Feb 12, 2024
2 parents 41d217e + 6ce9848 commit a18f841
Show file tree
Hide file tree
Showing 13 changed files with 363 additions and 157 deletions.
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

0 comments on commit a18f841

Please sign in to comment.