Got example 2 working

Player input; and a different way of setting up the renderer
This commit is contained in:
Brychan Dempsey 2021-06-12 15:53:30 +12:00
parent bca92faa88
commit 117589b17b
12 changed files with 322 additions and 61 deletions

View File

@ -38,7 +38,7 @@
<dependency>
<groupId>nz.ac.massey.javaecs</groupId>
<artifactId>javaecs</artifactId>
<version>0.9.2-PRERELEASE</version>
<version>0.9.9-RELEASE_CANDIDATE</version>
</dependency>
</dependencies>
<build>

BIN
demo2/sprite.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,22 +1,121 @@
package nz.ac.massey.javaecs.examples;
import java.io.FileInputStream;
import java.util.concurrent.Semaphore;
import javafx.scene.Group;
import javafx.scene.image.Image;
import javafx.stage.Stage;
import nz.ac.massey.javaecs.Engine;
import nz.ac.massey.javaecs.Entity;
import nz.ac.massey.javaecs.examples.Components.Control;
import nz.ac.massey.javaecs.examples.Components.Input;
import nz.ac.massey.javaecs.examples.Components.Render;
import nz.ac.massey.javaecs.examples.Components.Sprite;
import nz.ac.massey.javaecs.examples.Components.Vec2D;
import nz.ac.massey.javaecs.examples.Systems.ControlSystem;
import nz.ac.massey.javaecs.examples.Systems.PlayerSystem;
import nz.ac.massey.javaecs.examples.Systems.RenderSystem;
/**
* Hello world!
*/
public final class App {
private App() {
public class App {
static App thisApp;
public App() {
thisApp = this;
}
static Engine gameEngine;
public static Stage worldStage;
public static Group worldGroup;
static Integer syncManager = 0;
static Semaphore rendererFinished = new Semaphore(0);
/**
* Says hello to the world.
* @param args The arguments of the program.
*/
public static void main(String[] args) {
Engine gameEngine = new Engine();
public static void main(String[] args) throws Exception{
/************************************
** Initialise engine **
*************************************/
gameEngine = new Engine(16384);
/************************************
** Register game components **
*************************************/
gameEngine.registerComponent(Control.class);
gameEngine.registerComponent(Render.class);
gameEngine.registerComponent(Sprite.class);
gameEngine.registerComponent(Vec2D.class);
gameEngine.registerComponent(Input.class);
/************************************
** Register and initialise systems **
*************************************/
ControlSystem controlSystem = new ControlSystem(gameEngine);
gameEngine.registerSystem(ControlSystem.class, controlSystem);
RenderSystem renderSystem = new RenderSystem(gameEngine, 10.);
gameEngine.registerSystem(RenderSystem.class, renderSystem);
PlayerSystem playerSystem = new PlayerSystem(gameEngine);
gameEngine.registerSystem(PlayerSystem.class, playerSystem);
/************************************
** Create known entities **
*************************************/
// Input controller - gets player input
Entity inputEntity = gameEngine.createEntity();
gameEngine.addComponent(inputEntity, Input.class, new Input());
Entity playerEntity = gameEngine.createEntity();
gameEngine.addComponent(playerEntity, Vec2D.class, new Vec2D(25,25));
gameEngine.addComponent(playerEntity, Render.class, new Render(Sprite.class));
gameEngine.addComponent(playerEntity, Control.class, new Render(Control.class));
// Load the sprite
System.err.println(System.getProperty("user.dir"));
Image image = new Image(new FileInputStream("sprite.png"));
gameEngine.addComponent(playerEntity, Sprite.class, new Sprite(image, 50, 50));
// Push rendering into its own thread (to meet the design principle of a JavaFX program)
Thread renderThread = new Thread(new RenderStarter());
renderThread.start();
System.out.println("Hello World!");
// Use a semaphore to block until the renderer is set-up
rendererFinished.acquire();
/************************************
** Run init() on the systems **
*************************************/
controlSystem.init(worldStage);
playerSystem.init(inputEntity);
renderSystem.init(worldGroup);
double dt = 0.001;
double frameRate = 1.0 / 144; // 1 second divided by target number of frames
boolean exit = false;
long startTime = System.nanoTime();
int state = 0;
while (!exit){
// Run system updates
controlSystem.update(dt);
playerSystem.update(dt);
renderSystem.update(dt);
dt = (System.nanoTime() - startTime) / 1e9; // convert nanoseconds to seconds
startTime = System.nanoTime();
if (dt < frameRate){
Thread.sleep((int)((frameRate-dt)*1000));
dt = (System.nanoTime() - startTime) / 1e9;
startTime = System.nanoTime();
}
}
renderThread.join(); // Wait for any render task to exit
}
}

View File

@ -1,5 +1,5 @@
package nz.ac.massey.javaecs.examples.Components;
public class Control {
public int input = -1;
}

View File

@ -0,0 +1,9 @@
package nz.ac.massey.javaecs.examples.Components;
import java.util.concurrent.atomic.AtomicInteger;
public class Input {
// As this is a user-input- (i.e. timeframe in 10's or 100's of milliseconds) -derived value, the performance impact of
// AtomicInteger is irrelevant compared to its synchronisation ability.
public AtomicInteger input = new AtomicInteger(-1);
}

View File

@ -3,5 +3,13 @@ package nz.ac.massey.javaecs.examples.Components;
import javafx.scene.image.Image;
public class Sprite {
Image sprite;
public Image sprite;
public Integer height;
public Integer width;
public Sprite(Image sprite, int height, int width){
this.sprite = sprite;
this.width = width;
this.height = height;
}
}

View File

@ -0,0 +1,28 @@
package nz.ac.massey.javaecs.examples;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class JFXView extends Application {
public static Group root;
@Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("JavaECS Demo 2");
App.worldGroup = new Group();
App.worldStage = primaryStage;
Scene scene = new Scene(App.worldGroup, 1024, 1024, Color.BLACK);
primaryStage.setScene(scene);
primaryStage.show();
// Notifiy that the renderer is ready
App.rendererFinished.release();
}
public static void run(){
launch(new String[0]);
}
}

View File

@ -0,0 +1,16 @@
package nz.ac.massey.javaecs.examples;
import javafx.scene.Group;
// Add a renderer
public class RenderStarter implements Runnable{
@Override
public void run() {
JFXView.run();
}
void updateScene(Group newGroup){
JFXView.root.getChildren().clear();
JFXView.root.getChildren().add(newGroup);
}
}

View File

@ -1,17 +1,81 @@
package nz.ac.massey.javaecs.examples.Systems;
import nz.ac.massey.javaecs.ECSSystem;
import java.util.BitSet;
import javafx.application.Platform;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.stage.Stage;
import nz.ac.massey.javaecs.ECSSystem;
import nz.ac.massey.javaecs.Engine;
import nz.ac.massey.javaecs.Entity;
import nz.ac.massey.javaecs.examples.Components.Control;
import nz.ac.massey.javaecs.examples.Components.Input;
import nz.ac.massey.javaecs.examples.Components.Render;
/**
* The control system creates a handler for input in the world stage.
* It then passes input events as they occur to the Input component
* The ControlSystem reads this input every frame update and enacts
* on the player controller
*/
public class ControlSystem extends ECSSystem {
Engine gameEngine;
Entity inputEntity;
public ControlSystem(Engine gamEngine){
this.gameEngine = gamEngine;
registrationSet = new BitSet();
registrationSet.set(gameEngine.getComponentIndex(Input.class));
}
public void init(Stage world) {
// use a single entity to control inputs, it is present in the world stage
// and is responsible for collecting & passing data
inputEntity = entities.iterator().next();
// Add an event handler to the stage. This
Platform.runLater(new Runnable(){
@Override
public void run() {
world.addEventHandler(KeyEvent.KEY_PRESSED, (key) -> {
if(key.getCode()==KeyCode.W) {
updateKeyPress(0);
System.err.println("W pressed");
}
else if(key.getCode()==KeyCode.S) {
updateKeyPress(1);
System.err.println("S pressed");
}
else if(key.getCode()==KeyCode.A) {
updateKeyPress(2);
System.err.println("A pressed");
}
else if(key.getCode()==KeyCode.D) {
updateKeyPress(3);
System.err.println("D pressed");
}
});
}
});
}
@Override
public void init() {
public void update(double dt) {
//Input inputUpdate = (Input)gameEngine.getComponentData(inputEntity, Input.class);
// TODO Auto-generated method stub
}
// Updates the input's keypressed state
protected void updateKeyPress(int dir){
Input inputUpdate = (Input)gameEngine.getComponentData(inputEntity, Input.class);
inputUpdate.input.set(dir);
}
@Override
public void update() {
public void init() throws Exception {
// TODO Auto-generated method stub
}

View File

@ -0,0 +1,59 @@
package nz.ac.massey.javaecs.examples.Systems;
import java.util.BitSet;
import nz.ac.massey.javaecs.ECSSystem;
import nz.ac.massey.javaecs.Engine;
import nz.ac.massey.javaecs.Entity;
import nz.ac.massey.javaecs.examples.Components.Control;
import nz.ac.massey.javaecs.examples.Components.Input;
import nz.ac.massey.javaecs.examples.Components.Sprite;
import nz.ac.massey.javaecs.examples.Components.Vec2D;
public class PlayerSystem extends ECSSystem{
Engine gameEngine;
Entity inputEntity; // Entity which recieves event data
public PlayerSystem(Engine gameEngine){
this.gameEngine = gameEngine;
registrationSet = new BitSet();
registrationSet.set(gameEngine.getComponentIndex(Control.class));
}
public void init(Entity inputEntity){
this.inputEntity = inputEntity;
}
@Override
public void init() throws Exception {
// TODO Auto-generated method stub
}
@Override
public void update(double dt) {
int state = ((Input)gameEngine.getComponentData(inputEntity, Input.class)).input.get();
// The value of the input may change before control leaves here; sychronisation isn't
// hugely important through this section. So we can just ignore the change
for (Entity entity : entities) {
Vec2D pos = (Vec2D)gameEngine.getComponentData(entity, Vec2D.class);
Sprite sprite = (Sprite)gameEngine.getComponentData(entity, Sprite.class);
if (state == 0){ // W
pos.y -= sprite.height/2;
}
else if (state == 1){ // S
pos.y += sprite.height/2;
}
else if (state == 2){ // A
pos.x -= sprite.width/2;
}
else if (state == 3){ // D
pos.x += sprite.width/2;
}
}
// Finally, unset the state
((Input)gameEngine.getComponentData(inputEntity, Input.class)).input.set(-1); // Unset the state, it has been handled
}
}

View File

@ -6,19 +6,23 @@ import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import nz.ac.massey.javaecs.ECSSystem;
import nz.ac.massey.javaecs.Engine;
import nz.ac.massey.javaecs.Entity;
import nz.ac.massey.javaecs.examples.Components.Control;
import nz.ac.massey.javaecs.examples.Components.Input;
import nz.ac.massey.javaecs.examples.Components.Render;
import nz.ac.massey.javaecs.examples.Components.Sprite;
import nz.ac.massey.javaecs.examples.Components.Vec2D;
/*
* This render system is different from demo 1, as its main functionality is provided directly by the program, not as an entity.
*/
public class RenderSystem extends ECSSystem {
JFXView jFXView;
Engine gameEngine;
Group renderGroup;
Thread renderThread;
double renderScale;
int screenX = 1024;
@ -29,69 +33,43 @@ public class RenderSystem extends ECSSystem {
// Set registrations
registrationSet = new BitSet();
registrationSet.set(gameEngine.getComponentIndex(Render.class));
registrationSet.set(gameEngine.getComponentIndex(Vec2D.class));
this.renderScale = renderScale;
}
@Override
public void init() {
// Spawn a new asynchronous thread (won't rejoin the main program flow)
renderThread = new Thread(new RenderStarter());
renderThread.start();
public void init(Group renderGroup) throws Exception {
this.renderGroup = renderGroup;
}
@Override
public void update() {
Group renderScene = new Group();
public void init() throws Exception {
// TODO Auto-generated method stub
}
@Override
public void update(double dt) {
Group frame = new Group();
for (Entity entity : entities) {
if (((Render)gameEngine.getComponentData(entity, Render.class)).renderType == Control.class){
// Do control updates here
}
else if (((Render)gameEngine.getComponentData(entity, Render.class)).renderType == Sprite.class){
Vec2D vec2d = (Vec2D)gameEngine.getComponentData(entity, Vec2D.class);
Vec2D pos = (Vec2D)gameEngine.getComponentData(entity, Vec2D.class);
if (((Render)gameEngine.getComponentData(entity, Render.class)).renderType == Sprite.class){
// Render the sprite
Sprite sprite = (Sprite)gameEngine.getComponentData(entity, Sprite.class);
ImageView spriteRender = new ImageView(sprite.sprite);
spriteRender.setX(pos.x);
spriteRender.setY(pos.y);
spriteRender.setFitWidth(sprite.width);
spriteRender.setFitHeight(sprite.height);
frame.getChildren().add(spriteRender);
}
}
// Must be run by the UI thread, not the main thread.
// This dispatches the operation run() to the UI.
Platform.runLater(new Runnable(){
@Override
public void run() {
JFXView.root.getChildren().clear();
JFXView.root.getChildren().add(renderScene);
renderGroup.getChildren().clear();
renderGroup.getChildren().add(frame);
}
});
}
// Add a renderer
public class RenderStarter implements Runnable{
@Override
public void run() {
JFXView.run();
}
void updateScene(Group newGroup){
JFXView.root.getChildren().clear();
JFXView.root.getChildren().add(newGroup);
}
}
public static class JFXView extends Application {
private static Group root;
@Override
public void start(Stage primaryStage) throws Exception {
// based on the colorful circles sample at https://docs.oracle.com/javase/8/javafx/get-started-tutorial/animation.htm
root = new Group();
Scene scene = new Scene(root, 1024, 1024, Color.BLACK);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void run(){
launch(new String[0]);
}
}
}

BIN
demo2/src/sprite.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB