Create empty game window

This commit is contained in:
2021-01-30 10:27:04 +01:00
parent 915067f3ca
commit f480fffc8e
10 changed files with 287 additions and 19 deletions

View File

@@ -3,20 +3,25 @@
*/
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.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class App implements ApplicationRunner {
private final GameEngine gameEngine;
@Override
public void run(ApplicationArguments args) {
gameEngine.start();
}
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
@Override
public void run(ApplicationArguments args) {
}
}

View File

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

View File

@@ -0,0 +1,5 @@
package com.bartlomiejpluta.samplegame.core.engine;
public interface GameEngine {
void start();
}

View File

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

View File

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

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

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

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

View File

@@ -0,0 +1,8 @@
app:
window:
title: "Simple Game"
width: 640
height: 480
core:
targetUps: 50 # Updates per second

View File

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