Skip to content

Commit a18f841

Browse files
authored
Merge pull request #29 from redomar/feature/player_monitoring
Refactor game code and add new features
2 parents 41d217e + 6ce9848 commit a18f841

File tree

13 files changed

+363
-157
lines changed

13 files changed

+363
-157
lines changed

src/com/redomar/game/Game.java

Lines changed: 94 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.redomar.game.event.MouseHandler;
1010
import com.redomar.game.gfx.Screen;
1111
import com.redomar.game.gfx.SpriteSheet;
12+
import com.redomar.game.gfx.lighting.Night;
1213
import com.redomar.game.level.LevelHandler;
1314
import com.redomar.game.lib.Either;
1415
import com.redomar.game.lib.Font;
@@ -23,6 +24,7 @@
2324
import java.awt.image.BufferStrategy;
2425
import java.awt.image.BufferedImage;
2526
import java.awt.image.DataBufferInt;
27+
import java.io.Serial;
2628

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

3537
// Setting the size and name of the frame/canvas
38+
@Serial
3639
private static final long serialVersionUID = 1L;
3740
private static final String game_Version = "v1.8.6 Alpha";
38-
private static final int WIDTH = 160;
39-
private static final int HEIGHT = (WIDTH / 3 * 2);
40-
private static final int SCALE = 3;
41+
private static final int SCALE = 100;
42+
private static final int WIDTH = 3 * SCALE;
43+
private static final int SCREEN_WIDTH = WIDTH * 2;
44+
private static final int HEIGHT = (2 * SCALE);
45+
private static final int SCREEN_HEIGHT = (HEIGHT * 2) + 30;
46+
private static final Screen screen = new Screen(WIDTH, HEIGHT, new SpriteSheet("/sprite_sheet.png"));
4147
private static final String NAME = "Game"; // The name of the JFrame panel
4248
private static final Time time = new Time(); // Represents the calendar's time value, in hh:mm:ss
4349
private static final boolean[] alternateCols = new boolean[2]; // Boolean array describing shirt and face colour
44-
4550
private static Game game;
46-
4751
// The properties of the player, npc, and fps/tps
4852
private static boolean changeLevel = false; // Determines whether the player teleports to another level
4953
private static boolean npc = false; // Non-player character (NPC) initialized to non-existing
5054
private static int map = 0; // Map of the level, initialized to map default map
5155
private static int shirtCol; // The colour of the character's shirt
52-
private static int faceCol; // The colour (ethnicity) of the character (their face)
56+
private static int faceCol; // The colour (ethnicizty) of the character (their face)
5357
private static int fps; // The frame rate (frames per second), frequency at which images are displayed on the canvas
5458
private static int tps; // The ticks (ticks per second), unit measure of time for one iteration of the game logic loop.
5559
private static int steps;
5660
private static boolean devMode; // Determines whether the game is in developer mode
5761
private static boolean closingMode; // Determines whether the game will exit
58-
62+
private static int tileX = 0;
63+
private static int tileY = 0;
5964
// Audio, input, and mouse handler objects
6065
private static JFrame frame;
6166
private static AudioHandler backgroundMusic;
6267
private static boolean running = false; // Determines whether the game is currently in process
6368
private static InputHandler input; // Accepts keyboard input and follows the appropriate actions
6469
private static MouseHandler mouse; // Tracks mouse movement and clicks, and follows the appropriate actions
6570
private static InputContext context; // Provides methods to control text input facilities
66-
6771
// Graphics
68-
private final BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
72+
private final BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
6973
private final int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); // Array of red, green and blue values for each pixel
7074
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)
7175
private final BufferedImage image2 = new BufferedImage(WIDTH, HEIGHT - 30, BufferedImage.TYPE_INT_RGB);
7276
private final Font font = new Font(); // Font object capable of displaying 2 fonts: Arial and Segoe UI
7377
private final Printer printer = new Printer();
7478
boolean musicPlaying = false;
7579
private int tickCount = 0;
76-
private Screen screen;
7780
private LevelHandler level; // Loads and renders levels along with tiles, entities, projectiles and more.
7881
//The entities of the game
7982
private Player player;
@@ -87,9 +90,9 @@ public Game() {
8790
context = InputContext.getInstance();
8891

8992
// The game can only be played in one distinct window size
90-
setMinimumSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
91-
setMaximumSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
92-
setPreferredSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
93+
setMinimumSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT));
94+
setMaximumSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT));
95+
setPreferredSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT));
9396

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

294+
private static void mousePositionTracker() {
295+
MouseHandler mouseHandler = Game.getMouse();
296+
int mouseX = mouseHandler.getX();
297+
int mouseY = mouseHandler.getY();
298+
299+
// Adjust mouse coordinates based on the current offset and scale of the game world
300+
tileX = ((mouseX + 4 + screen.getxOffset()) / (8 * 2)) + screen.getxOffset() / 16;
301+
tileY = ((mouseY + 4 + screen.getyOffset()) / (8 * 2)) + screen.getyOffset() / 16;
302+
}
303+
304+
public static int getTileX() {
305+
return tileX;
306+
}
307+
308+
public static int getTileY() {
309+
return tileY;
310+
}
311+
312+
public static Screen getScreen() {
313+
return screen;
314+
}
315+
291316
/*
292317
* This method initializes the game once it starts. It populates the colour array with actual colours (6 shades each of RGB).
293318
* This method also builds the initial game level (custom_level), spawns a new vendor NPC, and begins accepting keyboard and mouse input/tracking.
@@ -301,12 +326,16 @@ public void init() {
301326
int rr = (r * 255 / 5); // Split all 256 colours into 6 shades (0, 51, 102 ... 255)
302327
int gg = (g * 255 / 5);
303328
int bb = (b * 255 / 5);
304-
colours[index++] = rr << 16 | gg << 8 | bb; // All colour values (RGB) are placed into one 32-bit integer, populating the colour array
329+
// All colour values (RGB) are placed into one 32-bit integer, populating the colour array
330+
// The first 8 bits are for alpha, the next 8 for red, the next 8 for green, and the last 8 for blue
331+
// 0xFF000000 is ignored in BufferedImage.TYPE_INT_RGB, but is used in BufferedImage.TYPE_INT_ARGB
332+
colours[index++] = 0xFF << 24 | rr << 16 | gg << 8 | bb;
305333
}
306334
}
307335
}
308336

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

351-
long lastTimer = System.currentTimeMillis(); // Used for updating ticks and frames once every second
381+
long lastTimer = System.nanoTime(); // Used for updating ticks and frames once every second
352382
double delta = 0;
353383

354384
init(); // Initialize the game environment
@@ -371,8 +401,8 @@ public void run() {
371401
render();
372402
}
373403

374-
if (System.currentTimeMillis() - lastTimer >= 1000) { // If elapsed time is greater than or equal to 1 second, update
375-
lastTimer += 1000; // Updates in another second
404+
if (System.nanoTime() - lastTimer >= nsPerS) { // If elapsed time is greater than or equal to 1 second, update
405+
lastTimer += nsPerS; // Updates in another second
376406
getFrame().setTitle("JavaGame - Version " + WordUtils.capitalize(game_Version).substring(1, game_Version.length()));
377407
fps = frames;
378408
tps = ticks;
@@ -393,12 +423,15 @@ public void tick() {
393423
printer.cast().print("Failed to play music", PrintTypes.MUSIC);
394424
printer.exception(exception.toString());
395425
musicPlaying = false;
396-
}, isPlaying -> musicPlaying = isPlaying);
397-
426+
}, isPlaying -> {
427+
musicPlaying = isPlaying;
428+
if (musicPlaying && !Game.getBackgroundMusic().getActive()) {
429+
input.overWriteKey(input.getM_KEY(), false);
430+
}
431+
});
398432
level.tick();
399433
}
400434

401-
402435
/**
403436
* This method displays the current state of the game.
404437
*/
@@ -417,6 +450,7 @@ public void render() {
417450
level.renderEntities(screen);
418451
level.renderProjectileEntities(screen);
419452

453+
420454
for (int y = 0; y < screen.getHeight(); y++) {
421455
for (int x = 0; x < screen.getWidth(); x++) {
422456
int colourCode = screen.getPixels()[x + y * screen.getWidth()];
@@ -452,10 +486,10 @@ public void render() {
452486
changeLevel = false;
453487
}
454488

455-
Graphics g = bs.getDrawGraphics();
456-
g.drawRect(0, 0, getWidth(), getHeight());
489+
Graphics2D g = (Graphics2D) bs.getDrawGraphics();
457490
g.drawImage(image, 0, 0, getWidth(), getHeight() - 30, null);
458491
status(g, isDevMode(), isClosing());
492+
overlayRender(g);
459493
g.drawImage(image2, 0, getHeight() - 30, getWidth(), getHeight(), null);
460494
g.setColor(Color.WHITE);
461495
g.setFont(font.getSegoe());
@@ -484,12 +518,24 @@ public void render() {
484518
bs.show();
485519
}
486520

521+
/**
522+
* This method renders the overlay of the game, which is a transparent layer that is drawn over the game.
523+
*/
524+
private void overlayRender(Graphics2D g) {
525+
g.setColor(new Color(0f, 0f, 0f, .192f)); // Transparent color
526+
g.fillRect(0, 0, getWidth(), getHeight()-30);
527+
}
528+
487529
/*
488530
* This method displays information regarding various aspects/stats of the game, dependent upon
489531
* whether it is running in developer mode, or if the application is closing.
490532
*/
491-
private void status(Graphics g, boolean TerminalMode, boolean TerminalQuit) {
533+
private void status(Graphics2D g, boolean TerminalMode, boolean TerminalQuit) {
492534
if (TerminalMode) {
535+
new Night(g, screen).render(player.getPlayerAbsX(), player.getPlayerAbsY());
536+
// make the background transparent
537+
g.setColor(new Color(0, 0, 0, 100));
538+
g.fillRect(0, 0, 195, 165);
493539
g.setColor(Color.CYAN);
494540
g.drawString("JavaGame Stats", 0, 10);
495541
g.drawString("FPS/TPS: " + fps + "/" + tps, 0, 25);
@@ -499,9 +545,29 @@ private void status(Graphics g, boolean TerminalMode, boolean TerminalQuit) {
499545
g.drawString("Foot Steps: " + steps, 0, 40);
500546
g.drawString("NPC: " + WordUtils.capitalize(String.valueOf(isNpc())), 0, 55);
501547
g.drawString("Mouse: " + getMouse().getX() + "x |" + getMouse().getY() + "y", 0, 70);
502-
if (getMouse().getButton() != -1) g.drawString("Button: " + getMouse().getButton(), 0, 85);
503-
g.setColor(Color.CYAN);
504-
g.fillRect(getMouse().getX() - 12, getMouse().getY() - 12, 24, 24);
548+
g.drawString("Mouse: " + (getMouse().getX() - 639 / 2d) + "x |" + (getMouse().getY() - 423 / 2d) + "y", 0, 85);
549+
if (getMouse().getButton() != -1) g.drawString("Button: " + getMouse().getButton(), 0, 100);
550+
mousePositionTracker();
551+
g.drawString("Player: " + (int) player.getX() + "x |" + (int) player.getY() + "y", 0, 115);
552+
double angle = Math.atan2(getMouse().getY() - player.getPlayerAbsY(), getMouse().getX() - player.getPlayerAbsX()) * (180.0 / Math.PI);
553+
g.drawString("Angle: " + angle, 0, 130);
554+
555+
g.setColor(Color.cyan);
556+
g.drawString("Player: \t\t\t\t\t\t\t\t\t\t\t\t" + player.getPlayerAbsX() + "x |" + player.getPlayerAbsY() + "y", 0, 145);
557+
g.drawString("Player Offset: \t" + screen.getxOffset() + "x |" + screen.getyOffset() + "y", 0, 160);
558+
559+
// Set a different color for the player-origin line
560+
g.setStroke(new BasicStroke(1));
561+
g.setColor(Color.GREEN); // Green for the new line from the player's origin
562+
g.drawLine(player.getPlayerAbsX(), player.getPlayerAbsY(), getMouse().getX(), getMouse().getY()); // Draw the line from the player's origin to the cursor
563+
g.setColor(Color.DARK_GRAY);
564+
g.drawLine(getWidth() / 2 + 8, getHeight() / 2 - 8, getMouse().getX(), getMouse().getY()); // Draw the line from the player's origin to the cursor
565+
g.drawLine(getWidth() / 2 + 8, 0, getWidth() / 2 + 8, getHeight() - 30);
566+
g.drawLine(0, getHeight() / 2 - 8, getWidth(), getHeight() / 2 - 8);
567+
g.setColor(Color.yellow);
568+
g.fillRect(player.getPlayerAbsX(), player.getPlayerAbsY(), 1, 1);
569+
570+
505571
}
506572
// If the game is shutting off
507573
if (!TerminalQuit) {
@@ -529,5 +595,4 @@ public Vendor getVendor() {
529595
public void setVendor(Vendor vendor) {
530596
this.vendor = vendor;
531597
}
532-
533598
}

src/com/redomar/game/audio/AudioHandler.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,11 @@ private void check(String path) {
4545
}
4646

4747
/**
48-
* Initialises an audio clip from the specified file path.
48+
* 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.
4949
*
50-
* @param path the file path of the audio clip
50+
* @param path the relative file path to the audio clip resource. The path must be accessible from the classpath and should not be null.
5151
*/
52-
private void initiate(String path) {
52+
private void initiate(@NotNull String path) {
5353
try {
5454
InputStream inputStream = new BufferedInputStream(Objects.requireNonNull(AudioHandler.class.getResourceAsStream(path)));
5555
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(inputStream);
@@ -59,6 +59,12 @@ private void initiate(String path) {
5959
AudioInputStream decodedAudioInputStream = AudioSystem.getAudioInputStream(decodeFormat, audioInputStream);
6060
clip = AudioSystem.getClip();
6161
clip.open(decodedAudioInputStream);
62+
clip.addLineListener(event -> {
63+
if (event.getType() == LineEvent.Type.STOP) {
64+
stop();
65+
}
66+
});
67+
6268
} catch (IOException e) {
6369
musicPrinter.cast().exception("Audio file not found " + path);
6470
musicPrinter.cast().exception(e.getMessage());
@@ -93,18 +99,19 @@ public void stop() {
9399
if (clip == null) throw new RuntimeException("Empty clip");
94100
if (clip.isRunning()) {
95101
clip.stop();
96-
clip.close();
102+
if (!music) clip.close();
97103
}
104+
if (music & active) musicPrinter.print("Stopping Music");
98105
} catch (Exception e) {
99106
musicPrinter.print("Audio Handler Clip not found");
100107
} finally {
101-
if (music) musicPrinter.print("Stopping Music");
102108
active = false;
103109
}
104110
}
105111

106112
public void close() {
107113
stop();
114+
clip.close();
108115
}
109116

110117
public boolean getActive() {

src/com/redomar/game/entities/Player.java

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ public class Player extends Mob {
1919
private static double speed = 1;
2020
private final InputHandler inputHandler;
2121
private int fireRate;
22+
private int playerAbsX;
23+
private int playerAbsY;
2224

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

37+
// Calculate and set player's absolute X and Y positions
38+
setPlayerAbsX((((int) getX() - Game.getScreen().getxOffset()) * 2) + 8);
39+
setPlayerAbsY((((int) getY() - Game.getScreen().getyOffset()) * 2) + 7);
40+
41+
3542
if (inputHandler != null) {
3643

37-
speed = inputHandler.getSHIFTED().isPressed() ? 2 : 1;
44+
speed = inputHandler.getSHIFTED().isPressed() ? 2.5D : 1D;
3845

3946
if (inputHandler.getUP_KEY().isPressed()) {
4047
ya -= speed;
@@ -60,10 +67,22 @@ public void tick() {
6067
fireRate = Medium.FIRE_RATE;
6168
}
6269
if (!swim.isActive(swimType)) {
63-
double dx = Game.getMouse().getX() - 480 / 2d;
64-
double dy = Game.getMouse().getY() - 320 / 2d;
70+
71+
// Cursor position
72+
int cursorX = Game.getMouse().getX();
73+
int cursorY = Game.getMouse().getY();
74+
75+
// Calculate differences (dx, dy) between cursor and origin
76+
double dx = cursorX - playerAbsX;
77+
double dy = cursorY - playerAbsY;
78+
79+
// Calculate direction using atan2
6580
double dir = Math.atan2(dy, dx);
81+
82+
// Continue with shooting logic
6683
shoot(x, y, dir, Game.getMouse().getButton());
84+
85+
entityPrinter.highlight().print("Direction: " + dir + \t" + dx + "x\t" + dy + "y");
6786
}
6887
}
6988
}
@@ -105,4 +124,19 @@ public String getSanitisedUsername() {
105124
return this.name;
106125
}
107126

127+
public int getPlayerAbsX() {
128+
return playerAbsX;
129+
}
130+
131+
public void setPlayerAbsX(int playerAbsX) {
132+
this.playerAbsX = playerAbsX;
133+
}
134+
135+
public int getPlayerAbsY() {
136+
return playerAbsY;
137+
}
138+
139+
public void setPlayerAbsY(int playerAbsY) {
140+
this.playerAbsY = playerAbsY;
141+
}
108142
}

0 commit comments

Comments
 (0)