HIDE NAV

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);
    }

}