Game Engine - Pt.4
Better time control is added in the form of framerate limiting and the first delay counter for shot cooldown was also added.
RunNGunApp.java
package ceccs;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import java.util.ArrayList;
public class RunNGunApp extends Application {
Scene gameScene;
InputManger inputManger;
ArrayList<Projectile> projectiles;
Player player;
GameTimer gTimer;
StackPane outerStackPane;
Pane motionPane;
double physW,physH;
Rectangle backgroundRect;
int shotCooldown = 0;
int shotResetTime = 40;
@Override
public void start(Stage primaryStage) throws Exception {
physH = 600;
physW = 800;
motionPane = new Pane();
backgroundRect = new Rectangle(physW,physH, Color.BLACK);
outerStackPane = new StackPane(backgroundRect, motionPane);
gameScene = new Scene(outerStackPane, physW, physH);
primaryStage.setScene(gameScene);
primaryStage.setResizable(false);
primaryStage.show();
player = new Player(this);
gTimer = new GameTimer(this);
//Spawwing 100 random projectiles
projectiles = new ArrayList();
projectileSpawnTest(100);
inputManger = new InputManger(this);
gTimer.start();
}
void projectileSpawnTest(int numProjectiles){
for (int i = 0; i < numProjectiles; i++) {
double randX = physW * Math.random();
double randY = physH * Math.random();
double randVx = 10* Math.random() - 5;
double randVy = 10* Math.random() - 5;
new Projectile(this, randX, randY, randVx, randVy);
}
}
void playerShoot(char dir){
if(shotCooldown <= 0) {
switch (dir) {
case 'u' -> new Projectile(this, player.x, player.y, 0, -10);
case 'd' -> new Projectile(this, player.x, player.y, 0, 10);
case 'l' -> new Projectile(this, player.x, player.y, -10, 0);
case 'r' -> new Projectile(this, player.x, player.y, 10, 0);
}
shotCooldown = shotResetTime;
}
//todo: next session add play contols - tie shooting and moving to controls
}
public static void main(String[] args) {
launch(args);
}
}
GameTimer.java
package ceccs;
import javafx.animation.AnimationTimer;
public class GameTimer extends AnimationTimer {
RunNGunApp app;
long prevFrameTime;
final int targetFramesPerSecond = 120;
final long targetNanosPerFrame = 1000000000 / targetFramesPerSecond;
public GameTimer(RunNGunApp app){
this.app = app;
}
@Override
public void start() {
super.start();
prevFrameTime = System.nanoTime();
}
@Override
public void handle(long now) {
long nanosElapsed = now - prevFrameTime;
if(nanosElapsed > targetNanosPerFrame) {
updateGameState();
updateInput();
updatePhysics();
updateGraphics();
prevFrameTime = now;
}
}
void updateGameState(){
//timers and counters
if(app.shotCooldown > 0) app.shotCooldown--;
}
void updatePhysics(){
app.player.updatePhysics();
for (int i = 0; i < app.projectiles.size(); i++) {
app.projectiles.get(i).updatePhysics();
}
}
void updateGraphics(){
app.player.updateGraphics();
for (int i = 0; i < app.projectiles.size(); i++) {
app.projectiles.get(i).updateGraphics();
}
}
void updateInput(){
//player motion
if(app.inputManger.p_up_pressed && !app.inputManger.p_dn_pressed){
app.player.vy = -2;
} else if(!app.inputManger.p_up_pressed && app.inputManger.p_dn_pressed) {
app.player.vy = 2;
} else {
app.player.vy = 0;
}
if(app.inputManger.p_lf_pressed && !app.inputManger.p_rt_pressed){
app.player.vx = -2;
} else if(!app.inputManger.p_lf_pressed && app.inputManger.p_rt_pressed) {
app.player.vx = 2;
} else {
app.player.vx = 0;
}
//player shooting
if(app.inputManger.shot_lf_pressed && !app.inputManger.shot_rt_pressed
&& !app.inputManger.shot_up_pressed && !app.inputManger.shot_dn_pressed) {
app.playerShoot('l');
} else if(!app.inputManger.shot_lf_pressed && app.inputManger.shot_rt_pressed
&& !app.inputManger.shot_up_pressed && !app.inputManger.shot_dn_pressed) {
app.playerShoot('r');
} else if(!app.inputManger.shot_lf_pressed && !app.inputManger.shot_rt_pressed
&& app.inputManger.shot_up_pressed && !app.inputManger.shot_dn_pressed) {
app.playerShoot('u');
} else if(!app.inputManger.shot_lf_pressed && !app.inputManger.shot_rt_pressed
&& !app.inputManger.shot_up_pressed && app.inputManger.shot_dn_pressed) {
app.playerShoot('d');
}
}
}
InputManager.java
package ceccs;
import javafx.event.EventHandler;
import javafx.scene.input.KeyEvent;
public class InputManger {
RunNGunApp app;
boolean p_up_pressed, p_dn_pressed, p_lf_pressed, p_rt_pressed;
boolean shot_up_pressed, shot_dn_pressed, shot_lf_pressed, shot_rt_pressed;
public InputManger(RunNGunApp app){
this.app = app;
app.gameScene.setOnKeyPressed(event -> {
switch (event.getCode()){
case W -> p_up_pressed = true;
case S -> p_dn_pressed = true;
case A -> p_lf_pressed = true;
case D -> p_rt_pressed = true;
case UP -> shot_up_pressed = true;
case DOWN -> shot_dn_pressed = true;
case LEFT -> shot_lf_pressed = true;
case RIGHT -> shot_rt_pressed = true;
}
});
app.gameScene.setOnKeyReleased(event -> {
switch (event.getCode()){
case W -> p_up_pressed = false;
case S -> p_dn_pressed = false;
case A -> p_lf_pressed = false;
case D -> p_rt_pressed = false;
case UP -> shot_up_pressed = false;
case DOWN -> shot_dn_pressed = false;
case LEFT -> shot_lf_pressed = false;
case RIGHT -> shot_rt_pressed = false;
}
});
}
}
Player.java
package ceccs;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
public class Player extends Circle {
RunNGunApp app;
double x, y, vx, vy, ax, ay, size;
public Player(RunNGunApp app){
this.app = app;
size = 5;
setRadius(size);
setFill(Color.LIME);
app.motionPane.getChildren().add(this);
}
void updatePhysics(){
vx += ax;
x += vx;
vy += ay;
y += vy;
}
void updateGraphics(){
setLayoutX(x);
setLayoutY(y);
setRadius(size);
}
}
Projectile.java
package ceccs;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
public class Projectile extends Circle {
RunNGunApp app;
double x,y,vx,vy, size;
public Projectile(RunNGunApp app, double x0, double y0, double vx0, double vy0){
this.app = app;
x = x0;
y = y0;
vx = vx0;
vy = vy0;
size = 3;
setRadius(size);
setFill(Color.RED);
//add new projectile to list of projectiles
app.projectiles.add(this);
//add to the visual data
app.motionPane.getChildren().add(this);
}
void updatePhysics(){
x += vx;
y += vy;
}
void updateGraphics(){
setLayoutX(x);
setLayoutY(y);
setRadius(size);
}
}