Create empty game window
This commit is contained in:
@@ -3,20 +3,25 @@
|
|||||||
*/
|
*/
|
||||||
package com.bartlomiejpluta.samplegame;
|
package com.bartlomiejpluta.samplegame;
|
||||||
|
|
||||||
|
import com.bartlomiejpluta.samplegame.core.engine.GameEngine;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.ApplicationArguments;
|
import org.springframework.boot.ApplicationArguments;
|
||||||
import org.springframework.boot.ApplicationRunner;
|
import org.springframework.boot.ApplicationRunner;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
|
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
|
||||||
public class App implements ApplicationRunner {
|
public class App implements ApplicationRunner {
|
||||||
|
private final GameEngine gameEngine;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(ApplicationArguments args) {
|
||||||
|
gameEngine.start();
|
||||||
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(App.class, args);
|
SpringApplication.run(App.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run(ApplicationArguments args) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package com.bartlomiejpluta.samplegame.core.engine;
|
||||||
|
|
||||||
|
import com.bartlomiejpluta.samplegame.core.thread.ThreadManager;
|
||||||
|
import com.bartlomiejpluta.samplegame.core.time.ChronoMeter;
|
||||||
|
import com.bartlomiejpluta.samplegame.core.ui.Window;
|
||||||
|
import com.bartlomiejpluta.samplegame.core.ui.WindowManager;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class DefaultGameEngine implements GameEngine {
|
||||||
|
private final WindowManager windowManager;
|
||||||
|
private final ThreadManager threadManager;
|
||||||
|
|
||||||
|
private final Thread thread;
|
||||||
|
private final Window window;
|
||||||
|
private final ChronoMeter chrono;
|
||||||
|
private final int targetUps;
|
||||||
|
|
||||||
|
private boolean running = false;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public DefaultGameEngine(WindowManager windowManager,
|
||||||
|
ThreadManager threadManager,
|
||||||
|
@Value("${app.window.title}") String title,
|
||||||
|
@Value("${app.window.width}") int width,
|
||||||
|
@Value("${app.window.height}") int height,
|
||||||
|
@Value("${app.core.targetUps}") int targetUps) {
|
||||||
|
this.windowManager = windowManager;
|
||||||
|
this.threadManager = threadManager;
|
||||||
|
|
||||||
|
|
||||||
|
this.window = windowManager.createWindow(title, width, height);
|
||||||
|
this.thread = threadManager.createThread(this::run);
|
||||||
|
this.chrono = new ChronoMeter();
|
||||||
|
this.targetUps = targetUps;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void run() {
|
||||||
|
try {
|
||||||
|
init();
|
||||||
|
loop();
|
||||||
|
} finally {
|
||||||
|
cleanUp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
window.init();
|
||||||
|
chrono.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loop() {
|
||||||
|
running = true;
|
||||||
|
var dt = 0.0f;
|
||||||
|
var accumulator = 0.0f;
|
||||||
|
var step = 1.0f / targetUps;
|
||||||
|
|
||||||
|
while (running && !window.shouldClose()) {
|
||||||
|
dt = chrono.getElapsedTime();
|
||||||
|
accumulator += dt;
|
||||||
|
|
||||||
|
input();
|
||||||
|
|
||||||
|
while (accumulator >= step) {
|
||||||
|
update(dt);
|
||||||
|
accumulator -= step;
|
||||||
|
}
|
||||||
|
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void input() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update(float dt) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void render() {
|
||||||
|
window.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cleanUp() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package com.bartlomiejpluta.samplegame.core.engine;
|
||||||
|
|
||||||
|
public interface GameEngine {
|
||||||
|
void start();
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.bartlomiejpluta.samplegame.core.error;
|
||||||
|
|
||||||
|
public class AppException extends RuntimeException {
|
||||||
|
public AppException() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||||
|
super(message, cause, enableSuppression, writableStackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.bartlomiejpluta.samplegame.core.thread;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class ThreadManager {
|
||||||
|
public Thread createThread(Runnable runnable) {
|
||||||
|
return new Thread(runnable);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
app/src/main/java/com/bartlomiejpluta/samplegame/core/time/ChronoMeter.java
Executable file
20
app/src/main/java/com/bartlomiejpluta/samplegame/core/time/ChronoMeter.java
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
package com.bartlomiejpluta.samplegame.core.time;
|
||||||
|
|
||||||
|
public class ChronoMeter {
|
||||||
|
private double latchedTime;
|
||||||
|
|
||||||
|
public void init() {
|
||||||
|
latchedTime = getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
private double getTime() {
|
||||||
|
return System.nanoTime() / 1_000_000_000.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getElapsedTime() {
|
||||||
|
double time = getTime();
|
||||||
|
float elapsedTime = (float) (time - latchedTime);
|
||||||
|
latchedTime = time;
|
||||||
|
return elapsedTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
107
app/src/main/java/com/bartlomiejpluta/samplegame/core/ui/Window.java
Executable file
107
app/src/main/java/com/bartlomiejpluta/samplegame/core/ui/Window.java
Executable file
@@ -0,0 +1,107 @@
|
|||||||
|
package com.bartlomiejpluta.samplegame.core.ui;
|
||||||
|
|
||||||
|
import com.bartlomiejpluta.samplegame.core.error.AppException;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.lwjgl.glfw.GLFWErrorCallback;
|
||||||
|
import org.lwjgl.glfw.GLFWVidMode;
|
||||||
|
import org.lwjgl.opengl.GL;
|
||||||
|
|
||||||
|
import static org.lwjgl.glfw.GLFW.*;
|
||||||
|
import static org.lwjgl.opengl.GL11.*;
|
||||||
|
import static org.lwjgl.opengl.GL11.GL_DEPTH_TEST;
|
||||||
|
import static org.lwjgl.system.MemoryUtil.NULL;
|
||||||
|
|
||||||
|
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public class Window {
|
||||||
|
private final String title;
|
||||||
|
private int width;
|
||||||
|
private int height;
|
||||||
|
private long windowHandle;
|
||||||
|
private boolean resized;
|
||||||
|
|
||||||
|
public void init() {
|
||||||
|
// Setup an error callback. The default implementation
|
||||||
|
// will print the error message in System.err.
|
||||||
|
GLFWErrorCallback.createPrint(System.err).set();
|
||||||
|
|
||||||
|
// Initialize GLFW. Most GLFW functions will not work before doing this.
|
||||||
|
if (!glfwInit()) {
|
||||||
|
throw new AppException("Unable to initialize GLFW");
|
||||||
|
}
|
||||||
|
|
||||||
|
glfwDefaultWindowHints(); // optional, the current window hints are already the default
|
||||||
|
glfwWindowHint(GLFW_VISIBLE, GL_FALSE); // the window will stay hidden after creation
|
||||||
|
glfwWindowHint(GLFW_RESIZABLE, GL_TRUE); // the window will be resizable
|
||||||
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||||
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
|
||||||
|
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||||
|
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
|
||||||
|
|
||||||
|
// Create the window
|
||||||
|
windowHandle = glfwCreateWindow(width, height, title, NULL, NULL);
|
||||||
|
if (windowHandle == NULL) {
|
||||||
|
throw new AppException("Failed to create the GLFW window");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup resize callback
|
||||||
|
glfwSetFramebufferSizeCallback(windowHandle, (window, width, height) -> {
|
||||||
|
Window.this.width = width;
|
||||||
|
Window.this.height = height;
|
||||||
|
Window.this.resized = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Setup a key callback. It will be called every time a key is pressed, repeated or released.
|
||||||
|
glfwSetKeyCallback(windowHandle, (window, key, scancode, action, mods) -> {
|
||||||
|
if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) {
|
||||||
|
glfwSetWindowShouldClose(window, true); // We will detect this in the rendering loop
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get the resolution of the primary monitor
|
||||||
|
GLFWVidMode videoMode = glfwGetVideoMode(glfwGetPrimaryMonitor());
|
||||||
|
|
||||||
|
// Center our window
|
||||||
|
glfwSetWindowPos(windowHandle, (videoMode.width() - width) / 2, (videoMode.height() - height) / 2);
|
||||||
|
|
||||||
|
// Make the OpenGL context current
|
||||||
|
glfwMakeContextCurrent(windowHandle);
|
||||||
|
|
||||||
|
// Enable V-Sync
|
||||||
|
glfwSwapInterval(1);
|
||||||
|
|
||||||
|
// Make the window visible
|
||||||
|
glfwShowWindow(windowHandle);
|
||||||
|
|
||||||
|
GL.createCapabilities();
|
||||||
|
|
||||||
|
// Support for transparencies
|
||||||
|
glEnable(GL_BLEND);
|
||||||
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
|
// Set the clear color
|
||||||
|
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update() {
|
||||||
|
glfwSwapBuffers(windowHandle);
|
||||||
|
glfwPollEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isKeyPressed(int keyCode) {
|
||||||
|
return glfwGetKey(windowHandle, keyCode) == GLFW_PRESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear(float r, float g, float b, float alpha) {
|
||||||
|
glClearColor(r, g, b, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean shouldClose() {
|
||||||
|
return glfwWindowShouldClose(windowHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Window create(String title, int width, int height) {
|
||||||
|
return new Window(title, width, height, -1, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
10
app/src/main/java/com/bartlomiejpluta/samplegame/core/ui/WindowManager.java
Executable file
10
app/src/main/java/com/bartlomiejpluta/samplegame/core/ui/WindowManager.java
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
package com.bartlomiejpluta.samplegame.core.ui;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class WindowManager {
|
||||||
|
public Window createWindow(String title, int width, int height) {
|
||||||
|
return Window.create(title, width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
8
app/src/main/resources/application.yml
Executable file
8
app/src/main/resources/application.yml
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
app:
|
||||||
|
window:
|
||||||
|
title: "Simple Game"
|
||||||
|
width: 640
|
||||||
|
height: 480
|
||||||
|
|
||||||
|
core:
|
||||||
|
targetUps: 50 # Updates per second
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Java source file was generated by the Gradle 'init' task.
|
|
||||||
*/
|
|
||||||
package com.bartlomiejpluta.samplegame;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
public class AppTest {
|
|
||||||
@Test public void testAppHasAGreeting() {
|
|
||||||
App classUnderTest = new App();
|
|
||||||
assertNotNull("app should have a greeting", classUnderTest.getGreeting());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user