From 5dd6d8579b3a6d98ee5155c809e18fabe40211ea Mon Sep 17 00:00:00 2001 From: Valerie Date: Wed, 18 May 2022 07:46:03 -0400 Subject: [PATCH] init --- .gitignore | 1 + .vscode/launch.json | 20 ++ .vscode/settings.json | 3 + pom.xml | 139 ++++++++++++++ res/textures.pdn | Bin 0 -> 18895 bytes res/textures.png | Bin 0 -> 7645 bytes shaders/flat.frag | 15 ++ shaders/flat.vert | 17 ++ src/main/java/xyz/valnet/engine/App.java | 149 +++++++++++++++ src/main/java/xyz/valnet/engine/Game.java | 50 +++++ .../xyz/valnet/engine/graphics/Drawing.java | 36 ++++ .../java/xyz/valnet/engine/graphics/Font.java | 55 ++++++ .../xyz/valnet/engine/graphics/Sprite.java | 27 +++ .../xyz/valnet/engine/graphics/Texture.java | 82 +++++++++ .../xyz/valnet/engine/graphics/Tile9.java | 49 +++++ .../java/xyz/valnet/engine/math/Matrix4f.java | 86 +++++++++ .../java/xyz/valnet/engine/math/Vector3f.java | 19 ++ .../java/xyz/valnet/engine/math/Vector4f.java | 29 +++ .../java/xyz/valnet/engine/math/Vector4i.java | 21 +++ .../valnet/engine/scenegraph/GameObject.java | 15 ++ .../valnet/engine/scenegraph/IRenderable.java | 5 + .../scenegraph/IRenderableListener.java | 6 + .../xyz/valnet/engine/scenegraph/IScene.java | 9 + .../valnet/engine/scenegraph/ITickable.java | 5 + .../xyz/valnet/engine/shaders/Shader.java | 145 +++++++++++++++ .../valnet/engine/shaders/SimpleShader.java | 59 ++++++ .../xyz/valnet/engine/util/BufferUtils.java | 31 ++++ .../java/xyz/valnet/engine/util/Math.java | 9 + .../java/xyz/valnet/hadean/HadeanGame.java | 63 +++++++ .../valnet/hadean/gameobjects/Terrain.java | 47 +++++ .../java/xyz/valnet/hadean/input/Button.java | 121 ++++++++++++ .../valnet/hadean/input/IButtonListener.java | 5 + .../xyz/valnet/hadean/scenes/GameScene.java | 44 +++++ .../xyz/valnet/hadean/scenes/MenuScene.java | 78 ++++++++ .../java/xyz/valnet/hadean/util/Assets.java | 173 ++++++++++++++++++ 35 files changed, 1613 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 pom.xml create mode 100644 res/textures.pdn create mode 100644 res/textures.png create mode 100644 shaders/flat.frag create mode 100644 shaders/flat.vert create mode 100644 src/main/java/xyz/valnet/engine/App.java create mode 100644 src/main/java/xyz/valnet/engine/Game.java create mode 100644 src/main/java/xyz/valnet/engine/graphics/Drawing.java create mode 100644 src/main/java/xyz/valnet/engine/graphics/Font.java create mode 100644 src/main/java/xyz/valnet/engine/graphics/Sprite.java create mode 100644 src/main/java/xyz/valnet/engine/graphics/Texture.java create mode 100644 src/main/java/xyz/valnet/engine/graphics/Tile9.java create mode 100644 src/main/java/xyz/valnet/engine/math/Matrix4f.java create mode 100644 src/main/java/xyz/valnet/engine/math/Vector3f.java create mode 100644 src/main/java/xyz/valnet/engine/math/Vector4f.java create mode 100644 src/main/java/xyz/valnet/engine/math/Vector4i.java create mode 100644 src/main/java/xyz/valnet/engine/scenegraph/GameObject.java create mode 100644 src/main/java/xyz/valnet/engine/scenegraph/IRenderable.java create mode 100644 src/main/java/xyz/valnet/engine/scenegraph/IRenderableListener.java create mode 100644 src/main/java/xyz/valnet/engine/scenegraph/IScene.java create mode 100644 src/main/java/xyz/valnet/engine/scenegraph/ITickable.java create mode 100644 src/main/java/xyz/valnet/engine/shaders/Shader.java create mode 100644 src/main/java/xyz/valnet/engine/shaders/SimpleShader.java create mode 100644 src/main/java/xyz/valnet/engine/util/BufferUtils.java create mode 100644 src/main/java/xyz/valnet/engine/util/Math.java create mode 100644 src/main/java/xyz/valnet/hadean/HadeanGame.java create mode 100644 src/main/java/xyz/valnet/hadean/gameobjects/Terrain.java create mode 100644 src/main/java/xyz/valnet/hadean/input/Button.java create mode 100644 src/main/java/xyz/valnet/hadean/input/IButtonListener.java create mode 100644 src/main/java/xyz/valnet/hadean/scenes/GameScene.java create mode 100644 src/main/java/xyz/valnet/hadean/scenes/MenuScene.java create mode 100644 src/main/java/xyz/valnet/hadean/util/Assets.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..a82eed7 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "java", + "name": "Launch App", + "request": "launch", + "mainClass": "xyz.valnet.hadean.HadeanGame", + "projectName": "hadean", + "console": "internalConsole", + "internalConsoleOptions": "neverOpen", + "osx": { + "vmArgs": "-XstartOnFirstThread" + } + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e0f15db --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.configuration.updateBuildConfiguration": "automatic" +} \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..b88eb52 --- /dev/null +++ b/pom.xml @@ -0,0 +1,139 @@ + + + + 4.0.0 + + xyz.valnet.hadean + hadean + 1.0-SNAPSHOT + + hadean + + http://www.example.com + + + UTF-8 + 17 + 17 + 3.3.1 + natives-windows + + + + + + + org.lwjgl + lwjgl-bom + ${lwjgl.version} + import + pom + + + + + + + + org.lwjgl + lwjgl + + + org.lwjgl + lwjgl-assimp + + + org.lwjgl + lwjgl-glfw + + + org.lwjgl + lwjgl-openal + + + org.lwjgl + lwjgl-opengl + + + org.lwjgl + lwjgl-stb + + + org.lwjgl + lwjgl + ${lwjgl.natives} + + + org.lwjgl + lwjgl-assimp + ${lwjgl.natives} + + + org.lwjgl + lwjgl-glfw + ${lwjgl.natives} + + + org.lwjgl + lwjgl-openal + ${lwjgl.natives} + + + org.lwjgl + lwjgl-opengl + ${lwjgl.natives} + + + org.lwjgl + lwjgl-stb + ${lwjgl.natives} + + + + + + + + + maven-clean-plugin + 3.1.0 + + + + maven-resources-plugin + 3.0.2 + + + maven-compiler-plugin + 3.8.0 + + + maven-surefire-plugin + 2.22.1 + + + maven-jar-plugin + 3.0.2 + + + maven-install-plugin + 2.5.2 + + + maven-deploy-plugin + 2.8.2 + + + + maven-site-plugin + 3.7.1 + + + maven-project-info-reports-plugin + 3.0.0 + + + + + diff --git a/res/textures.pdn b/res/textures.pdn new file mode 100644 index 0000000000000000000000000000000000000000..5447fc7d3c57cea83497403987417b8e8f2a905c GIT binary patch literal 18895 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 literal 0 HcmV?d00001 diff --git a/shaders/flat.frag b/shaders/flat.frag new file mode 100644 index 0000000..3346114 --- /dev/null +++ b/shaders/flat.frag @@ -0,0 +1,15 @@ +//SpriteBatch will use texture unit 0 +uniform sampler2D u_texture; + +//"in" varyings from our vertex shader +varying vec4 vColor; +varying vec2 vTexCoord; + +void main() { + vec4 texColor = texture2D(u_texture, vTexCoord); + if(texColor == vec4(1, 0, 1, 1) || texColor == vec4(1, 0, 0, 1)) { + gl_FragColor = vec4(0, 0, 0, 0); + } else { + gl_FragColor = texColor * vColor; + } +} \ No newline at end of file diff --git a/shaders/flat.vert b/shaders/flat.vert new file mode 100644 index 0000000..e9a2591 --- /dev/null +++ b/shaders/flat.vert @@ -0,0 +1,17 @@ +//combined projection and view matrix +uniform mat4 uProjection; +uniform vec4 uColor; + +//"in" attributes from our SpriteBatch +attribute vec2 Position; +attribute vec2 TexCoord; + +//"out" varyings to our fragment shader +varying vec4 vColor; +varying vec2 vTexCoord; + +void main() { + vColor = uColor; + vTexCoord = TexCoord; + gl_Position = uProjection * vec4(Position, 0.0, 1.0); +} \ No newline at end of file diff --git a/src/main/java/xyz/valnet/engine/App.java b/src/main/java/xyz/valnet/engine/App.java new file mode 100644 index 0000000..5f5f6ce --- /dev/null +++ b/src/main/java/xyz/valnet/engine/App.java @@ -0,0 +1,149 @@ +package xyz.valnet.engine; + +import org.lwjgl.*; +import org.lwjgl.glfw.*; +import org.lwjgl.opengl.*; +import org.lwjgl.system.*; + +import xyz.valnet.engine.math.Matrix4f; + +import java.nio.*; + +import static org.lwjgl.glfw.Callbacks.*; +import static org.lwjgl.glfw.GLFW.*; +import static org.lwjgl.opengl.GL11.*; +import static org.lwjgl.system.MemoryStack.*; +import static org.lwjgl.system.MemoryUtil.*; + +public class App { + + // The window handle + private long window; + private int width = 800, height = 450; + private Matrix4f matrix = Matrix4f.orthographic(0, width, height, 0, 1, -1); + public static int mouseX, mouseY; + public static boolean mouseLeft, mouseMiddle, mouseRight; + + private Game game; + + public void run() { + System.out.println("Hello LWJGL " + Version.getVersion() + "!"); + + init(); + loop(); + + // Free the window callbacks and destroy the window + glfwFreeCallbacks(window); + glfwDestroyWindow(window); + + // Terminate GLFW and free the error callback + glfwTerminate(); + glfwSetErrorCallback(null).free(); + } + + private 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 IllegalStateException("Unable to initialize GLFW"); + + // Configure GLFW + glfwDefaultWindowHints(); // optional, the current window hints are already the default + glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // the window will stay hidden after creation + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); // the window will be resizable + + // Create the window + window = glfwCreateWindow(width, height, "Hello World!", NULL, NULL); + if ( window == NULL ) + throw new RuntimeException("Failed to create the GLFW window"); + + // Setup a key callback. It will be called every time a key is pressed, repeated or released. + glfwSetKeyCallback(window, (window, key, scancode, action, mods) -> { + if ( key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE ) + glfwSetWindowShouldClose(window, true); // We will detect this in the rendering loop + }); + + + glfwSetCursorPosCallback(window, new GLFWCursorPosCallback() { + @Override + public void invoke(long window, double xpos, double ypos) { + mouseX = (int) xpos; + mouseY = (int) ypos; + } + }); + + glfwSetMouseButtonCallback(window, new GLFWMouseButtonCallback() { + @Override + public void invoke(long window, int button, int action, int mods) { + + if(button >= 3) return; + // System.out.println("window: " + window + ", button: " + button + ", action: " + action + ", mods: " + mods); + if(button == GLFW_MOUSE_BUTTON_LEFT) { mouseLeft = action == 1; return; } + if(button == GLFW_MOUSE_BUTTON_RIGHT) { mouseRight = action == 1; return; } + if(button == GLFW_MOUSE_BUTTON_MIDDLE) { mouseMiddle = action == 1; return ; } + + } + }); + + // Get the thread stack and push a new frame + try ( MemoryStack stack = stackPush() ) { + IntBuffer pWidth = stack.mallocInt(1); // int* + IntBuffer pHeight = stack.mallocInt(1); // int* + + // Get the window size passed to glfwCreateWindow + glfwGetWindowSize(window, pWidth, pHeight); + + // Get the resolution of the primary monitor + GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor()); + + // Center the window + glfwSetWindowPos( + window, + (vidmode.width() - pWidth.get(0)) / 2, + (vidmode.height() - pHeight.get(0)) / 2 + ); + } // the stack frame is popped automatically + + // Make the OpenGL context current + glfwMakeContextCurrent(window); + // Enable v-sync + glfwSwapInterval(1); + + // Make the window visible + glfwShowWindow(window); + + // This line is critical for LWJGL's interoperation with GLFW's + // OpenGL context, or any context that is managed externally. + // LWJGL detects the context that is current in the current thread, + // creates the GLCapabilities instance and makes the OpenGL + // bindings available for use. + GL.createCapabilities(); + + + glClearColor(0.3f, 0.3f, 0.3f, 1.0f); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + game.start(); + } + + private void loop() { + while (!glfwWindowShouldClose(window)) { + game.updateViewMatrix(matrix); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear the framebuffer + + game.render(); + game.update(); + + glfwSwapBuffers(window); + glfwPollEvents(); + } + } + + public App(Game game) { + this.game = game; + } +} \ No newline at end of file diff --git a/src/main/java/xyz/valnet/engine/Game.java b/src/main/java/xyz/valnet/engine/Game.java new file mode 100644 index 0000000..21d5163 --- /dev/null +++ b/src/main/java/xyz/valnet/engine/Game.java @@ -0,0 +1,50 @@ +package xyz.valnet.engine; + +import static xyz.valnet.engine.util.Math.lerp; + +import xyz.valnet.engine.math.Matrix4f; +import xyz.valnet.engine.scenegraph.IScene; + +public abstract class Game { + private IScene scene; + + protected float averageFPS = 0; + protected int measuredFPS = 0; + + private int framesSinceKeyframe = 0; + private long lastFrame = System.nanoTime(); + private long lastKeyframe = System.nanoTime(); + + public abstract void start(); + + public void changeScene(IScene scene) { + if(this.scene != null) { + this.scene.disable(); + } + scene.enable(); + this.scene = scene; + } + + public void render() { + scene.render(); + } + + public void update() { + scene.update(0); + + long nanoTime = System.nanoTime(); + + // average framerate + averageFPS = lerp(averageFPS, 1_000_000_000f / (nanoTime - lastFrame), (nanoTime - lastFrame) / 1_000_000_000f); + lastFrame = nanoTime; + + framesSinceKeyframe ++; + if(nanoTime > lastKeyframe + 1_000_000_000) { + measuredFPS = framesSinceKeyframe; + framesSinceKeyframe = 0; + lastKeyframe += 1_000_000_000; + } + } + + public abstract void updateViewMatrix(Matrix4f matrix); +} diff --git a/src/main/java/xyz/valnet/engine/graphics/Drawing.java b/src/main/java/xyz/valnet/engine/graphics/Drawing.java new file mode 100644 index 0000000..77ff94b --- /dev/null +++ b/src/main/java/xyz/valnet/engine/graphics/Drawing.java @@ -0,0 +1,36 @@ +package xyz.valnet.engine.graphics; + +import static org.lwjgl.opengl.GL20.*; + +import xyz.valnet.engine.shaders.SimpleShader; + +public class Drawing { + + private static Texture bound = null; + + public static void drawSprite(Sprite sprite, int x, int y) { + drawSprite(sprite, x, y, sprite.width, sprite.height); + } + + public static void drawSprite(Sprite sprite, int x, int y, int width, int height) { + // lazy texture binding + if(bound != sprite.atlas) { + if(bound != null) bound.unbind(); + sprite.atlas.bind(); + } + + glBegin(GL_QUADS); + glVertexAttrib2f(SimpleShader.TEX_COORD, sprite.sourceBoxUV.x, sprite.sourceBoxUV.y); + glVertex2f(x, y); + + glVertexAttrib2f(SimpleShader.TEX_COORD, sprite.sourceBoxUV.x + sprite.sourceBoxUV.z, sprite.sourceBoxUV.y); + glVertex2f(x + width, y); + + glVertexAttrib2f(SimpleShader.TEX_COORD, sprite.sourceBoxUV.x + sprite.sourceBoxUV.z, sprite.sourceBoxUV.y + sprite.sourceBoxUV.w); + glVertex2f(x + width, y + height); + + glVertexAttrib2f(SimpleShader.TEX_COORD, sprite.sourceBoxUV.x, sprite.sourceBoxUV.y + sprite.sourceBoxUV.w); + glVertex2f(x, y + height); + glEnd(); + } +} \ No newline at end of file diff --git a/src/main/java/xyz/valnet/engine/graphics/Font.java b/src/main/java/xyz/valnet/engine/graphics/Font.java new file mode 100644 index 0000000..a794f71 --- /dev/null +++ b/src/main/java/xyz/valnet/engine/graphics/Font.java @@ -0,0 +1,55 @@ +package xyz.valnet.engine.graphics; + +import java.util.Map; + +import xyz.valnet.engine.math.Vector4i; + +public class Font { + + private Map charset; + private final int w, h; + + public Font(Map charset, int w, int h) { + this.charset = charset; + this.w = w; + this.h = h; + } + + public void drawString(String str, int x, int y) { + int cursorX = x; + int cursorY = y; + + Sprite s; + + for(char c : str.toCharArray()) { + if(c == '\n') { + cursorY += h; + cursorX = x; + continue; + } + if(c == '\r') continue; + if(c == '\t') { + cursorX += w * 4; + continue; + } + if(c == ' ' || !charset.containsKey(c)) { + cursorX += w; + continue; + } + s = charset.get(c); + Drawing.drawSprite(s, cursorX, cursorY); + cursorX += w; + } + } + + public Vector4i measure(String text) { + String[] lines = text.split("\n"); + int longest = 0; + int c = 0; + for(String line : lines) { + c = line.length(); + if(c > longest) longest = c; + } + return new Vector4i(longest * w, lines.length * h, 0, 0); + } +} diff --git a/src/main/java/xyz/valnet/engine/graphics/Sprite.java b/src/main/java/xyz/valnet/engine/graphics/Sprite.java new file mode 100644 index 0000000..5d8cf36 --- /dev/null +++ b/src/main/java/xyz/valnet/engine/graphics/Sprite.java @@ -0,0 +1,27 @@ +package xyz.valnet.engine.graphics; + +import xyz.valnet.engine.math.Vector4f; +import xyz.valnet.engine.math.Vector4i; + +public class Sprite { + public final Vector4f sourceBoxUV; + private final Vector4i sourceBoxPixels; + public final Texture atlas; + public final int width; + public final int height; + + public Sprite(Texture tex, Vector4i box) { + sourceBoxPixels = box; + sourceBoxUV = new Vector4f( + sourceBoxPixels.x / (float) tex.width, + sourceBoxPixels.y / (float) tex.height, + sourceBoxPixels.z / (float) tex.width, + sourceBoxPixels.w / (float) tex.height + ); + atlas = tex; + width = sourceBoxPixels.z; + height = sourceBoxPixels.w; + } + + +} diff --git a/src/main/java/xyz/valnet/engine/graphics/Texture.java b/src/main/java/xyz/valnet/engine/graphics/Texture.java new file mode 100644 index 0000000..580b0b6 --- /dev/null +++ b/src/main/java/xyz/valnet/engine/graphics/Texture.java @@ -0,0 +1,82 @@ +package xyz.valnet.engine.graphics; + +import java.awt.image.BufferedImage; +import java.io.FileInputStream; +import java.io.IOException; + +import javax.imageio.ImageIO; + +import xyz.valnet.engine.util.BufferUtils; + +import static org.lwjgl.opengl.GL20.*; + +public class Texture { + + public final int width, height; + public final int handle; + private final BufferedImage img; + + private boolean registered = false; + + public Texture(String path) { + img = loadImageFromDisk(path); + if(img == null) { + width = 0; + height = 0; + handle = -1; + return; + } + width = img.getWidth(); + height = img.getHeight(); + handle = registerImg(); + } + + private BufferedImage loadImageFromDisk (String path) { + try { + return ImageIO.read(new FileInputStream(path)); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + private int registerImg() { + if(registered) return handle; + registered = true; + + int[] pixels = new int[width * height]; + int[] data = new int[width * height]; + + // populate pixels + img.getRGB(0, 0, width, height, pixels, 0, width); + + // convert to data + for (int i = 0; i < width * height; i++) { + int a = (pixels[i] & 0xff000000) >> 24; + int r = (pixels[i] & 0xff0000) >> 16; + int g = (pixels[i] & 0xff00) >> 8; + int b = (pixels[i] & 0xff); + + data[i] = a << 24 | b << 16 | g << 8 | r; + } + + int result = glGenTextures(); + glBindTexture(GL_TEXTURE_2D, result); + glActiveTexture(GL_TEXTURE0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, BufferUtils.createIntBuffer(data)); + glBindTexture(GL_TEXTURE_2D, 0); + return result; + // return 0; + } + + public void bind() { + glBindTexture(GL_TEXTURE_2D, handle); + } + + public void unbind() { + glBindTexture(GL_TEXTURE_2D, 0); + } + +} diff --git a/src/main/java/xyz/valnet/engine/graphics/Tile9.java b/src/main/java/xyz/valnet/engine/graphics/Tile9.java new file mode 100644 index 0000000..56987d9 --- /dev/null +++ b/src/main/java/xyz/valnet/engine/graphics/Tile9.java @@ -0,0 +1,49 @@ +package xyz.valnet.engine.graphics; + +public class Tile9 { + + private final Sprite topLeft; + private final Sprite top; + private final Sprite topRight; + private final Sprite left; + private final Sprite center; + private final Sprite right; + private final Sprite bottomLeft; + private final Sprite bottom; + private final Sprite bottomRight; + + public Tile9( + Sprite topLeft, + Sprite top, + Sprite topRight, + Sprite left, + Sprite center, + Sprite right, + Sprite bottomLeft, + Sprite bottom, + Sprite bottomRight + ) { + this.topLeft = topLeft; + this.top = top; + this.topRight = topRight; + this.left = left; + this.center = center; + this.right = right; + this.bottomLeft = bottomLeft; + this.bottom = bottom; + this.bottomRight = bottomRight; + } + + public void draw(int x, int y, int w, int h) { + Drawing.drawSprite(topLeft, x, y, topLeft.width, topLeft.height); + Drawing.drawSprite(top, x + topLeft.width, y, w - topLeft.width - topRight.width, top.height); + Drawing.drawSprite(topRight, x + w - topRight.width, y, topLeft.width, topLeft.height); + Drawing.drawSprite(left, x, y + topLeft.height, left.width, h - top.height - bottom.height); + Drawing.drawSprite(center, x + left.width, y + top.height, w - left.width - right.width, h - top.height - bottom.height); + Drawing.drawSprite(right, x + w - right.width, y + topRight.height, right.width, h - top.height - bottom.height); + Drawing.drawSprite(bottomLeft, x, y + h - bottomLeft.height, bottomLeft.width, bottomLeft.height); + Drawing.drawSprite(bottom, x + bottomLeft.width, y + h - bottom.height, w - bottomLeft.width - bottomRight.width, bottom.height); + Drawing.drawSprite(bottomRight, x + w - bottomRight.width, y + h - bottomRight.height, bottomLeft.width, bottomLeft.height); + } + +} diff --git a/src/main/java/xyz/valnet/engine/math/Matrix4f.java b/src/main/java/xyz/valnet/engine/math/Matrix4f.java new file mode 100644 index 0000000..e37ea57 --- /dev/null +++ b/src/main/java/xyz/valnet/engine/math/Matrix4f.java @@ -0,0 +1,86 @@ +package xyz.valnet.engine.math; + +import java.nio.FloatBuffer; + +import xyz.valnet.engine.util.BufferUtils; + +public class Matrix4f { + + public static final int SIZE = 4 * 4; + public float[] elements = new float[SIZE]; + + public Matrix4f() { + + } + + public static Matrix4f identity() { + Matrix4f result = new Matrix4f(); + for (int i = 0; i < SIZE; i++) { + result.elements[i] = 0.0f; + } + result.elements[0 + 0 * 4] = 1.0f; + result.elements[1 + 1 * 4] = 1.0f; + result.elements[2 + 2 * 4] = 1.0f; + result.elements[3 + 3 * 4] = 1.0f; + + return result; + } + + public static Matrix4f orthographic(float left, float right, float bottom, float top, float near, float far) { + Matrix4f result = identity(); + + result.elements[0 + 0 * 4] = 2.0f / (right - left); + + result.elements[1 + 1 * 4] = 2.0f / (top - bottom); + + result.elements[2 + 2 * 4] = 2.0f / (near - far); + + result.elements[0 + 3 * 4] = (left + right) / (left - right); + result.elements[1 + 3 * 4] = (bottom + top) / (bottom - top); + result.elements[2 + 3 * 4] = (far + near) / (far - near); + + return result; + } + + public static Matrix4f translate(Vector3f vector) { + Matrix4f result = identity(); + result.elements[0 + 3 * 4] = vector.x; + result.elements[1 + 3 * 4] = vector.y; + result.elements[2 + 3 * 4] = vector.z; + return result; + } + + public static Matrix4f rotate(float angle) { + Matrix4f result = identity(); + float r = (float) Math.toRadians(angle); + float cos = (float) Math.cos(r); + float sin = (float) Math.sin(r); + + result.elements[0 + 0 * 4] = cos; + result.elements[1 + 0 * 4] = sin; + + result.elements[0 + 1 * 4] = -sin; + result.elements[1 + 1 * 4] = cos; + + return result; + } + + public Matrix4f multiply(Matrix4f matrix) { + Matrix4f result = new Matrix4f(); + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + float sum = 0.0f; + for (int e = 0; e < 4; e++) { + sum += this.elements[x + e * 4] * matrix.elements[e + y * 4]; + } + result.elements[x + y * 4] = sum; + } + } + return result; + } + + public FloatBuffer toFloatBuffer() { + return BufferUtils.createFloatBuffer(elements); + } + +} diff --git a/src/main/java/xyz/valnet/engine/math/Vector3f.java b/src/main/java/xyz/valnet/engine/math/Vector3f.java new file mode 100644 index 0000000..3f15490 --- /dev/null +++ b/src/main/java/xyz/valnet/engine/math/Vector3f.java @@ -0,0 +1,19 @@ +package xyz.valnet.engine.math; + +public class Vector3f { + + public float x, y, z; + + public Vector3f() { + x = 0.0f; + y = 0.0f; + z = 0.0f; + } + + public Vector3f(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + } + +} diff --git a/src/main/java/xyz/valnet/engine/math/Vector4f.java b/src/main/java/xyz/valnet/engine/math/Vector4f.java new file mode 100644 index 0000000..6e7d28f --- /dev/null +++ b/src/main/java/xyz/valnet/engine/math/Vector4f.java @@ -0,0 +1,29 @@ +package xyz.valnet.engine.math; + +public class Vector4f { + + public float x, y, z, w; + + public Vector4f() { + x = 0.0f; + y = 0.0f; + z = 0.0f; + w = 0.0f; + } + + public Vector4f(float x, float y, float z, float w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + + public static Vector4f one = new Vector4f(1f, 1f, 1f, 1f); + public static Vector4f black = new Vector4f(0f, 0f, 0f, 1f); + public static Vector4f zero = new Vector4f(0f, 0f, 0f, 0f); + + public String toString() { + return "(" + this.x + ", " + this.y + ", " + this.z + ", " + this.w + ")"; + } + +} diff --git a/src/main/java/xyz/valnet/engine/math/Vector4i.java b/src/main/java/xyz/valnet/engine/math/Vector4i.java new file mode 100644 index 0000000..3fc72fb --- /dev/null +++ b/src/main/java/xyz/valnet/engine/math/Vector4i.java @@ -0,0 +1,21 @@ +package xyz.valnet.engine.math; + +public class Vector4i { + + public int x, y, z, w; + + public Vector4i() { + x = 0; + y = 0; + z = 0; + w = 0; + } + + public Vector4i(int x, int y, int z, int w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + +} diff --git a/src/main/java/xyz/valnet/engine/scenegraph/GameObject.java b/src/main/java/xyz/valnet/engine/scenegraph/GameObject.java new file mode 100644 index 0000000..ea51b4c --- /dev/null +++ b/src/main/java/xyz/valnet/engine/scenegraph/GameObject.java @@ -0,0 +1,15 @@ +package xyz.valnet.engine.scenegraph; + +public class GameObject implements IRenderable, ITickable { + // private IScene scene; + + public GameObject(IScene scene) { + // this.scene = scene; + } + + @Override + public void render() {} + + @Override + public void tick(float dTime) {} +} diff --git a/src/main/java/xyz/valnet/engine/scenegraph/IRenderable.java b/src/main/java/xyz/valnet/engine/scenegraph/IRenderable.java new file mode 100644 index 0000000..0a39453 --- /dev/null +++ b/src/main/java/xyz/valnet/engine/scenegraph/IRenderable.java @@ -0,0 +1,5 @@ +package xyz.valnet.engine.scenegraph; + +public interface IRenderable { + public void render(); +} diff --git a/src/main/java/xyz/valnet/engine/scenegraph/IRenderableListener.java b/src/main/java/xyz/valnet/engine/scenegraph/IRenderableListener.java new file mode 100644 index 0000000..8f4a1ec --- /dev/null +++ b/src/main/java/xyz/valnet/engine/scenegraph/IRenderableListener.java @@ -0,0 +1,6 @@ +package xyz.valnet.engine.scenegraph; + +public interface IRenderableListener { + public void addRenderable(IRenderable renderable); + public void removeRenderable(IRenderable renderable); +} diff --git a/src/main/java/xyz/valnet/engine/scenegraph/IScene.java b/src/main/java/xyz/valnet/engine/scenegraph/IScene.java new file mode 100644 index 0000000..cd61300 --- /dev/null +++ b/src/main/java/xyz/valnet/engine/scenegraph/IScene.java @@ -0,0 +1,9 @@ +package xyz.valnet.engine.scenegraph; + +public interface IScene { + public void render(); + public void update(float dTime); + + public void enable(); + public void disable(); +} diff --git a/src/main/java/xyz/valnet/engine/scenegraph/ITickable.java b/src/main/java/xyz/valnet/engine/scenegraph/ITickable.java new file mode 100644 index 0000000..9af77f9 --- /dev/null +++ b/src/main/java/xyz/valnet/engine/scenegraph/ITickable.java @@ -0,0 +1,5 @@ +package xyz.valnet.engine.scenegraph; + +public interface ITickable { + public void tick(float dTime); +} diff --git a/src/main/java/xyz/valnet/engine/shaders/Shader.java b/src/main/java/xyz/valnet/engine/shaders/Shader.java new file mode 100644 index 0000000..377d2c9 --- /dev/null +++ b/src/main/java/xyz/valnet/engine/shaders/Shader.java @@ -0,0 +1,145 @@ +package xyz.valnet.engine.shaders; + +import static org.lwjgl.opengl.GL11.*; +import static org.lwjgl.opengl.GL20.*; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import xyz.valnet.engine.math.Matrix4f; +import xyz.valnet.engine.math.Vector3f; +import xyz.valnet.engine.math.Vector4f; + +public class Shader { + + private boolean enabled = false; + + public final int handle; + + public final static int POSITION = 0; + + private Map locationCache = new HashMap(); + + public Shader(String vertPath, String fragPath) { + handle = load(vertPath, fragPath); + } + + private static String loadAsString(String file) { + StringBuilder result = new StringBuilder(); + try { + BufferedReader reader = new BufferedReader(new FileReader(file)); + String buffer = ""; + while ((buffer = reader.readLine()) != null) { + result.append(buffer + '\n'); + } + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + return result.toString(); + } + + public int load(String vertPath, String fragPath) { + String vert = Shader.loadAsString(vertPath); + String frag = Shader.loadAsString(fragPath); + return create(vert, frag); + } + + public int create(String vert, String frag) { + int program = glCreateProgram(); + int vertID = glCreateShader(GL_VERTEX_SHADER); + int fragID = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(vertID, vert); + glShaderSource(fragID, frag); + + glCompileShader(vertID); + if (glGetShaderi(vertID, GL_COMPILE_STATUS) == GL_FALSE) { + System.err.println("Failed to compile vertex shader!"); + System.err.println(glGetShaderInfoLog(vertID)); + return -1; + } + + glCompileShader(fragID); + if (glGetShaderi(fragID, GL_COMPILE_STATUS) == GL_FALSE) { + System.err.println("Failed to compile fragment shader!"); + System.err.println(glGetShaderInfoLog(fragID)); + return -1; + } + + bindAttributes(program); + + glAttachShader(program, vertID); + glAttachShader(program, fragID); + glLinkProgram(program); + glValidateProgram(program); + + glDeleteShader(vertID); + glDeleteShader(fragID); + + return program; + } + + protected void bindAttributes(int program) { + glBindAttribLocation(program, POSITION, "Position"); + } + + public int getUniform(String name) { + if (locationCache.containsKey(name)) + return locationCache.get(name); + + int result = glGetUniformLocation(handle, name); + if (result == -1) + System.err.println("Could not find uniform variable '" + name + "'!"); + else + locationCache.put(name, result); + return result; + } + + public void setUniform1i(String name, int value) { + if (!enabled) enable(); + glUniform1i(getUniform(name), value); + } + + public void setUniform1f(String name, float value) { + if (!enabled) enable(); + glUniform1f(getUniform(name), value); + } + + public void setUniform2f(String name, float x, float y) { + if (!enabled) enable(); + glUniform2f(getUniform(name), x, y); + } + + public void setUniform3f(String name, Vector3f vector) { + if (!enabled) enable(); + glUniform3f(getUniform(name), vector.x, vector.y, vector.z); + } + + public void setUniform4f(String name, Vector4f vector) { + if (!enabled) enable(); + glUniform4f(getUniform(name), vector.x, vector.y, vector.z, vector.w); + } + + public void setUniformMat4f(String name, Matrix4f matrix) { + if (!enabled) enable(); + glUniformMatrix4fv(getUniform(name), false, matrix.toFloatBuffer()); + } + + public void setMatrices (Matrix4f projection) { + setUniformMat4f("uProjection", projection); + } + + public void enable() { + glUseProgram(handle); + enabled = true; + } + + public void disable() { + glUseProgram(0); + enabled = false; + } + +} diff --git a/src/main/java/xyz/valnet/engine/shaders/SimpleShader.java b/src/main/java/xyz/valnet/engine/shaders/SimpleShader.java new file mode 100644 index 0000000..dea30de --- /dev/null +++ b/src/main/java/xyz/valnet/engine/shaders/SimpleShader.java @@ -0,0 +1,59 @@ +package xyz.valnet.engine.shaders; + +import java.util.Stack; + +import static org.lwjgl.opengl.GL20.*; + +import xyz.valnet.engine.math.Vector4f; + +public class SimpleShader extends Shader { + + private Stack colorStack = new Stack(); + + public final static int COLOR = 1; + public final static int TEX_COORD = 2; + + public SimpleShader(String vertPath, String fragPath) { + super(vertPath, fragPath); + } + + public void pushColor(Vector4f color) { + // System.out.println("pushing " + color + " onto color stack"); + colorStack.push(color); + // printColorStack(); + + setUniform4f("uColor", color); + } + + public void swapColor(Vector4f color) { + popColor(); + pushColor(color); + } + + public void popColor() { + // System.out.println("popping the color stack"); + colorStack.pop(); + Vector4f newColor = colorStack.peek(); + // printColorStack(); + + if(newColor == null) { + setUniform4f("uColor", Vector4f.one); + return; + } + setUniform4f("uColor", newColor); + } + + // private void printColorStack() { + // for(Vector4f color : colorStack) { + // System.out.println(" " + color); + // } + // System.out.println(""); + // } + + @Override + protected void bindAttributes(int program) { + glBindAttribLocation(program, COLOR, "Color"); + glBindAttribLocation(program, TEX_COORD, "TexCoord"); + } + +} diff --git a/src/main/java/xyz/valnet/engine/util/BufferUtils.java b/src/main/java/xyz/valnet/engine/util/BufferUtils.java new file mode 100644 index 0000000..d1ad7e2 --- /dev/null +++ b/src/main/java/xyz/valnet/engine/util/BufferUtils.java @@ -0,0 +1,31 @@ +package xyz.valnet.engine.util; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; + +public class BufferUtils { + + private BufferUtils() { + } + + public static ByteBuffer createByteBuffer(byte[] array) { + ByteBuffer result = ByteBuffer.allocateDirect(array.length).order(ByteOrder.nativeOrder()); + result.put(array).flip(); + return result; + } + + public static FloatBuffer createFloatBuffer(float[] array) { + FloatBuffer result = ByteBuffer.allocateDirect(array.length << 2).order(ByteOrder.nativeOrder()).asFloatBuffer(); + result.put(array).flip(); + return result; + } + + public static IntBuffer createIntBuffer(int[] array) { + IntBuffer result = ByteBuffer.allocateDirect(array.length << 2).order(ByteOrder.nativeOrder()).asIntBuffer(); + result.put(array).flip(); + return result; + } + +} diff --git a/src/main/java/xyz/valnet/engine/util/Math.java b/src/main/java/xyz/valnet/engine/util/Math.java new file mode 100644 index 0000000..4cbd5da --- /dev/null +++ b/src/main/java/xyz/valnet/engine/util/Math.java @@ -0,0 +1,9 @@ +package xyz.valnet.engine.util; + +public class Math { + public static float lerp(float a, float b, float n) { + if(n >= 1) return b; + if(n <= 0) return a; + return a + (b - a) * n; + } +} diff --git a/src/main/java/xyz/valnet/hadean/HadeanGame.java b/src/main/java/xyz/valnet/hadean/HadeanGame.java new file mode 100644 index 0000000..8d8cce3 --- /dev/null +++ b/src/main/java/xyz/valnet/hadean/HadeanGame.java @@ -0,0 +1,63 @@ +package xyz.valnet.hadean; + +import xyz.valnet.engine.App; +import xyz.valnet.engine.Game; +import xyz.valnet.engine.math.Matrix4f; +import xyz.valnet.engine.math.Vector4f; + +import xyz.valnet.hadean.scenes.MenuScene; +import xyz.valnet.hadean.util.Assets; + + +public class HadeanGame extends Game { + public static final HadeanGame Hadean = new HadeanGame(); + + public static void main(String[] args) { + new App(Hadean).run(); + } + + @Override + public void start() { + Assets.flat.pushColor(Vector4f.one); + changeScene(new MenuScene()); + } + + @Override + public void render() { + super.render(); + renderDebugInfo(); + } + + private Runtime runtime = Runtime.getRuntime(); + private static Vector4f fontColor = new Vector4f(0, 1, 1, 1); + + private void renderDebugInfo() { + long allocated = runtime.totalMemory(); + long max = runtime.maxMemory(); + + Assets.flat.pushColor(Vector4f.black); + Assets.font.drawString("FPS: " + Math.round(averageFPS) + "/" + measuredFPS + " | AVG/MEASURED", 1, 1); + Assets.font.drawString("Mouse: <" + App.mouseX + ", " + App.mouseY + ">", 1, 17); + Assets.font.drawString("MEMORY: " + (int)((allocated / (double)max) * 100) + "% (" + (allocated / (1024 * 1024)) + "/" + (max / (1024 * 1024)) + "MB)", 1, 33); + Assets.font.drawString("", 1, 49); + Assets.font.drawString("", 1, 65); + Assets.font.drawString("", 1, 81); + + Assets.flat.swapColor(fontColor); + Assets.font.drawString("FPS: " + Math.round(averageFPS) + "/" + measuredFPS + " | AVG/MEASURED", 0, 0); + Assets.font.drawString("Mouse: <" + App.mouseX + ", " + App.mouseY + ">", 0, 16); + Assets.font.drawString("MEMORY: " + (int)((allocated / (double)max) * 100) + "% (" + (allocated / (1024 * 1024)) + "/" + (max / (1024 * 1024)) + "MB)", 0, 32); + Assets.font.drawString("", 0, 48); + Assets.font.drawString("", 0, 64); + Assets.font.drawString("", 0, 80); + + Assets.flat.popColor(); + } + + // receive the updated matrix every frame for the actual window. + @Override + public void updateViewMatrix(Matrix4f matrix) { + Assets.flat.setMatrices(matrix); + } + +} diff --git a/src/main/java/xyz/valnet/hadean/gameobjects/Terrain.java b/src/main/java/xyz/valnet/hadean/gameobjects/Terrain.java new file mode 100644 index 0000000..fc81051 --- /dev/null +++ b/src/main/java/xyz/valnet/hadean/gameobjects/Terrain.java @@ -0,0 +1,47 @@ +package xyz.valnet.hadean.gameobjects; + +import xyz.valnet.engine.graphics.Drawing; +import xyz.valnet.engine.graphics.Sprite; +import xyz.valnet.engine.math.Vector4f; +import xyz.valnet.engine.scenegraph.GameObject; +import xyz.valnet.engine.scenegraph.IScene; +import xyz.valnet.hadean.util.Assets; + +public class Terrain extends GameObject { + + private final int WORLD_SIZE = 30; + private final int TILE_SIZE = 8; + + private int[][] variation = new int[WORLD_SIZE][WORLD_SIZE]; + private Vector4f[][] colorVariations = new Vector4f[WORLD_SIZE][WORLD_SIZE]; + + public Terrain(IScene scene) { + super(scene); + for (int i = 0; i < WORLD_SIZE; i++) { + for (int j = 0; j < WORLD_SIZE; j++) { + variation[i][j] = (int) Math.floor(Math.random() * 4); + colorVariations[i][j] = new Vector4f((float) Math.random() * 0.2f, 0.4f + (float) Math.random() * 0.2f, (float) Math.random() * 0.05f, 1f); + } + } + } + + @Override + public void render() { + int left = 400 - (WORLD_SIZE * TILE_SIZE / 2); + int top = 225 - (WORLD_SIZE * TILE_SIZE / 2); + + + Sprite s; + Assets.flat.pushColor(Vector4f.one); + for (int i = 0; i < WORLD_SIZE; i++) { + for (int j = 0; j < WORLD_SIZE; j++) { + s = Assets.defaultTerrain[variation[i][j]]; + Assets.flat.swapColor(colorVariations[i][j]); + Drawing.drawSprite(s, left + i * s.width, top + j * s.height); + } + } + Assets.flat.popColor(); + + } + +} diff --git a/src/main/java/xyz/valnet/hadean/input/Button.java b/src/main/java/xyz/valnet/hadean/input/Button.java new file mode 100644 index 0000000..2e31b59 --- /dev/null +++ b/src/main/java/xyz/valnet/hadean/input/Button.java @@ -0,0 +1,121 @@ +package xyz.valnet.hadean.input; + +import static xyz.valnet.engine.util.Math.lerp; + +import xyz.valnet.engine.App; +import xyz.valnet.engine.graphics.Tile9; +import xyz.valnet.engine.math.Vector4f; +import xyz.valnet.engine.math.Vector4i; +import xyz.valnet.hadean.util.Assets; + +public class Button { + + private final int x, y, width, height; + private final String text; + private final Tile9 frame; + private final int textWidth, textHeight; + private float hPad, vPad; + private Vector4i box; + + protected float hoverVPad = 0.0f; + protected float hoverHPad = 0.1f; + protected float activeVPad = 0.1f; + protected float activeHPad = 0.0f; + + public Button(Tile9 frame, String text, int x, int y, int w, int h) { + this.x = x; + this.y = y; + width = w; + height = h; + this.text = text; + this.frame = frame; + Vector4i measuredText = Assets.font.measure(text); + textWidth = measuredText.x; + textHeight = measuredText.y; + box = new Vector4i(x, y, w, h); + } + + public void draw() { + frame.draw(box.x, box.y, box.z, box.w); + + Assets.flat.pushColor(Vector4f.black); + Assets.font.drawString(text, 1 + x + (width - textWidth) / 2, 1 + y + (height - textHeight) / 2); + + Assets.flat.swapColor(Vector4f.one); + Assets.font.drawString(text, x + (width - textWidth) / 2, y + (height - textHeight) / 2); + + Assets.flat.popColor(); + } + + private boolean hovered = false; + + private int state = 0; + private final static int IDLE = 0; + private final static int INACTIVE = 1; + private final static int HOVER = 2; + private final static int ACTIVE = 3; + private final static int ACTIVE_NO_HOVER = 4; + + private IButtonListener listener = null; + + public void update() { + box.x = x - (int)hPad; + box.y = y - (int)vPad; + box.z = width + ((int)hPad) * 2; + box.w = height + ((int)vPad) * 2; + + hovered = App.mouseX >= box.x && App.mouseX <= box.x + box.z && App.mouseY >= box.y && App.mouseY <= box.y + box.w; + boolean mouseDown = App.mouseLeft; + + float desiredVPad = 0, desiredHPad = 0; + + if(state == HOVER) { + desiredVPad += height * hoverVPad; + desiredHPad += width * hoverHPad; + } + + if(state == ACTIVE) { + desiredVPad += height * activeVPad; + desiredHPad += width * activeHPad; + } + + vPad = lerp(vPad, desiredVPad, 0.1f); + hPad = lerp(hPad, desiredHPad, 0.1f); + + if(state == IDLE) { + if(hovered) { + state = HOVER; + } else if (mouseDown) { + state = INACTIVE; + } + } else if (state == HOVER) { + if(!hovered) { + state = IDLE; + } else if(mouseDown) { + state = ACTIVE; + } + } else if (state == INACTIVE) { + if(!mouseDown) { + state = IDLE; + } + } else if (state == ACTIVE) { + if(!hovered) { + state = ACTIVE_NO_HOVER; + } else if(!mouseDown) { + state = IDLE; + listener.click(this); + } + } else if (state == ACTIVE_NO_HOVER) { + if(hovered) { + state = ACTIVE; + } else if (!mouseDown) { + state = IDLE; + } + } + + } + + public void registerClickListener(IButtonListener listener) { + this.listener = listener; + } +} diff --git a/src/main/java/xyz/valnet/hadean/input/IButtonListener.java b/src/main/java/xyz/valnet/hadean/input/IButtonListener.java new file mode 100644 index 0000000..b3469b9 --- /dev/null +++ b/src/main/java/xyz/valnet/hadean/input/IButtonListener.java @@ -0,0 +1,5 @@ +package xyz.valnet.hadean.input; + +public interface IButtonListener { + public void click(Button target); +} \ No newline at end of file diff --git a/src/main/java/xyz/valnet/hadean/scenes/GameScene.java b/src/main/java/xyz/valnet/hadean/scenes/GameScene.java new file mode 100644 index 0000000..74dcf8c --- /dev/null +++ b/src/main/java/xyz/valnet/hadean/scenes/GameScene.java @@ -0,0 +1,44 @@ +package xyz.valnet.hadean.scenes; + +import java.util.ArrayList; +import java.util.List; + +import xyz.valnet.engine.scenegraph.GameObject; +import xyz.valnet.engine.scenegraph.IScene; +import xyz.valnet.hadean.gameobjects.Terrain; + +public class GameScene implements IScene { + + // generic + private List objects = new ArrayList(); + // private List renderables = new ArrayList(); + + // specific + private GameObject terrain; + + @Override + public void render() { + for(GameObject obj : objects) { + ((Terrain)obj).render(); + } + } + + @Override + public void update(float dTime) { + for(GameObject obj : objects) { + obj.tick(dTime); + } + } + + @Override + public void enable() { + terrain = new Terrain(this); + objects.add(terrain); + } + + @Override + public void disable() { + objects.clear(); + } + +} diff --git a/src/main/java/xyz/valnet/hadean/scenes/MenuScene.java b/src/main/java/xyz/valnet/hadean/scenes/MenuScene.java new file mode 100644 index 0000000..fcaa5a5 --- /dev/null +++ b/src/main/java/xyz/valnet/hadean/scenes/MenuScene.java @@ -0,0 +1,78 @@ +package xyz.valnet.hadean.scenes; + +import xyz.valnet.engine.math.Vector4f; +import xyz.valnet.engine.scenegraph.IScene; +import xyz.valnet.hadean.input.Button; +import xyz.valnet.hadean.input.IButtonListener; +import xyz.valnet.hadean.util.Assets; + +import static xyz.valnet.hadean.HadeanGame.Hadean; + +public class MenuScene implements IScene, IButtonListener { + + private Button btnNewGame = new Button(Assets.frame, "New Game", 50, 200, 128, 32); + private Button btnLoadGame = new Button(Assets.frame, "Load Game", 50, 240, 128, 32); + private Button btnOptions = new Button(Assets.frame, "Options", 50, 280, 128, 32); + private Button btnQuit = new Button(Assets.frame, "Quit", 50, 320, 128, 32); + + public MenuScene() { + btnNewGame.registerClickListener(this); + btnLoadGame.registerClickListener(this); + btnOptions.registerClickListener(this); + btnQuit.registerClickListener(this); + } + + public Vector4f green = new Vector4f(0.0f, 1.0f, 0.2f, 1.0f); + public Vector4f cyan = new Vector4f(0.1f, 0.7f, 1.0f, 1.0f); + public Vector4f yellow = new Vector4f(1.0f, 1.0f, 0.0f, 1.0f); + public Vector4f red = new Vector4f(1.0f, 0.1f, 0.1f, 1.0f); + + @Override + public void render() { + Assets.flat.pushColor(green); + btnNewGame.draw(); + Assets.flat.swapColor(cyan); + btnLoadGame.draw(); + Assets.flat.swapColor(yellow); + btnOptions.draw(); + Assets.flat.swapColor(red); + btnQuit.draw(); + Assets.flat.popColor(); + } + + @Override + public void update(float dTime) { + btnNewGame.update(); + btnLoadGame.update(); + btnOptions.update(); + btnQuit.update(); + } + + @Override + public void click(Button target) { + if(target == btnNewGame) { + newGame(); + } else if(target == btnQuit) { + quit(); + } + } + + private void newGame() { + Hadean.changeScene(new GameScene()); + } + + private void quit() { + + } + + @Override + public void enable() { + + } + + @Override + public void disable() { + + } + +} diff --git a/src/main/java/xyz/valnet/hadean/util/Assets.java b/src/main/java/xyz/valnet/hadean/util/Assets.java new file mode 100644 index 0000000..cd567d3 --- /dev/null +++ b/src/main/java/xyz/valnet/hadean/util/Assets.java @@ -0,0 +1,173 @@ +package xyz.valnet.hadean.util; + +import java.util.HashMap; +import java.util.Map; + +import xyz.valnet.engine.graphics.Font; +import xyz.valnet.engine.graphics.Sprite; +import xyz.valnet.engine.graphics.Texture; +import xyz.valnet.engine.graphics.Tile9; +import xyz.valnet.engine.math.Vector4i; +import xyz.valnet.engine.shaders.SimpleShader; + +public class Assets { + + public static final Texture atlas; + public static final Font font; + public static final Tile9 redFrame; + public static final Tile9 frame; + public static final Tile9 fireFrame; + + public static final Sprite[] defaultTerrain; + + public static final SimpleShader flat; + + static { + flat = new SimpleShader("shaders/flat.vert", "shaders/flat.frag"); + + atlas = new Texture("res/textures.png"); + + defaultTerrain = new Sprite[] { + new Sprite(atlas, new Vector4i(24, 72, 8, 8)), + new Sprite(atlas, new Vector4i(24, 64, 8, 8)), + new Sprite(atlas, new Vector4i(32, 72, 8, 8)), + new Sprite(atlas, new Vector4i(32, 64, 8, 8)) + }; + + Map charset = new HashMap(); + + charset.put('A', new Sprite(atlas, new Vector4i( 0, 0, 8, 16))); + charset.put('B', new Sprite(atlas, new Vector4i( 8, 0, 8, 16))); + charset.put('C', new Sprite(atlas, new Vector4i( 16, 0, 8, 16))); + charset.put('D', new Sprite(atlas, new Vector4i( 24, 0, 8, 16))); + charset.put('E', new Sprite(atlas, new Vector4i( 32, 0, 8, 16))); + charset.put('F', new Sprite(atlas, new Vector4i( 40, 0, 8, 16))); + charset.put('G', new Sprite(atlas, new Vector4i( 48, 0, 8, 16))); + charset.put('H', new Sprite(atlas, new Vector4i( 56, 0, 8, 16))); + charset.put('I', new Sprite(atlas, new Vector4i( 64, 0, 8, 16))); + charset.put('J', new Sprite(atlas, new Vector4i( 72, 0, 8, 16))); + charset.put('K', new Sprite(atlas, new Vector4i( 80, 0, 8, 16))); + charset.put('L', new Sprite(atlas, new Vector4i( 88, 0, 8, 16))); + charset.put('M', new Sprite(atlas, new Vector4i( 96, 0, 8, 16))); + charset.put('N', new Sprite(atlas, new Vector4i(104, 0, 8, 16))); + charset.put('O', new Sprite(atlas, new Vector4i(112, 0, 8, 16))); + charset.put('P', new Sprite(atlas, new Vector4i(120, 0, 8, 16))); + charset.put('Q', new Sprite(atlas, new Vector4i(128, 0, 8, 16))); + charset.put('R', new Sprite(atlas, new Vector4i(136, 0, 8, 16))); + charset.put('S', new Sprite(atlas, new Vector4i(144, 0, 8, 16))); + charset.put('T', new Sprite(atlas, new Vector4i(152, 0, 8, 16))); + charset.put('U', new Sprite(atlas, new Vector4i(160, 0, 8, 16))); + charset.put('V', new Sprite(atlas, new Vector4i(168, 0, 8, 16))); + charset.put('W', new Sprite(atlas, new Vector4i(176, 0, 8, 16))); + charset.put('X', new Sprite(atlas, new Vector4i(184, 0, 8, 16))); + charset.put('Y', new Sprite(atlas, new Vector4i(192, 0, 8, 16))); + charset.put('Z', new Sprite(atlas, new Vector4i(200, 0, 8, 16))); + charset.put(':', new Sprite(atlas, new Vector4i(208, 0, 8, 16))); + charset.put(';', new Sprite(atlas, new Vector4i(216, 0, 8, 16))); + charset.put('.', new Sprite(atlas, new Vector4i(224, 0, 8, 16))); + charset.put(',', new Sprite(atlas, new Vector4i(232, 0, 8, 16))); + charset.put('!', new Sprite(atlas, new Vector4i(240, 0, 8, 16))); + charset.put('?', new Sprite(atlas, new Vector4i(248, 0, 8, 16))); + + charset.put('a', new Sprite(atlas, new Vector4i( 0, 16, 8, 16))); + charset.put('b', new Sprite(atlas, new Vector4i( 8, 16, 8, 16))); + charset.put('c', new Sprite(atlas, new Vector4i( 16, 16, 8, 16))); + charset.put('d', new Sprite(atlas, new Vector4i( 24, 16, 8, 16))); + charset.put('e', new Sprite(atlas, new Vector4i( 32, 16, 8, 16))); + charset.put('f', new Sprite(atlas, new Vector4i( 40, 16, 8, 16))); + charset.put('g', new Sprite(atlas, new Vector4i( 48, 16, 8, 16))); + charset.put('h', new Sprite(atlas, new Vector4i( 56, 16, 8, 16))); + charset.put('i', new Sprite(atlas, new Vector4i( 64, 16, 8, 16))); + charset.put('j', new Sprite(atlas, new Vector4i( 72, 16, 8, 16))); + charset.put('k', new Sprite(atlas, new Vector4i( 80, 16, 8, 16))); + charset.put('l', new Sprite(atlas, new Vector4i( 88, 16, 8, 16))); + charset.put('m', new Sprite(atlas, new Vector4i( 96, 16, 8, 16))); + charset.put('n', new Sprite(atlas, new Vector4i(104, 16, 8, 16))); + charset.put('o', new Sprite(atlas, new Vector4i(112, 16, 8, 16))); + charset.put('p', new Sprite(atlas, new Vector4i(120, 16, 8, 16))); + charset.put('q', new Sprite(atlas, new Vector4i(128, 16, 8, 16))); + charset.put('r', new Sprite(atlas, new Vector4i(136, 16, 8, 16))); + charset.put('s', new Sprite(atlas, new Vector4i(144, 16, 8, 16))); + charset.put('t', new Sprite(atlas, new Vector4i(152, 16, 8, 16))); + charset.put('u', new Sprite(atlas, new Vector4i(160, 16, 8, 16))); + charset.put('v', new Sprite(atlas, new Vector4i(168, 16, 8, 16))); + charset.put('w', new Sprite(atlas, new Vector4i(176, 16, 8, 16))); + charset.put('x', new Sprite(atlas, new Vector4i(184, 16, 8, 16))); + charset.put('y', new Sprite(atlas, new Vector4i(192, 16, 8, 16))); + charset.put('z', new Sprite(atlas, new Vector4i(200, 16, 8, 16))); + charset.put('\'', new Sprite(atlas, new Vector4i(208, 16, 8, 16))); + charset.put('"', new Sprite(atlas, new Vector4i(216, 16, 8, 16))); + charset.put('[', new Sprite(atlas, new Vector4i(224, 16, 8, 16))); + charset.put(']', new Sprite(atlas, new Vector4i(232, 16, 8, 16))); + charset.put('{', new Sprite(atlas, new Vector4i(240, 16, 8, 16))); + charset.put('}', new Sprite(atlas, new Vector4i(248, 16, 8, 16))); + + charset.put('0', new Sprite(atlas, new Vector4i( 0, 32, 8, 16))); + charset.put('1', new Sprite(atlas, new Vector4i( 8, 32, 8, 16))); + charset.put('2', new Sprite(atlas, new Vector4i( 16, 32, 8, 16))); + charset.put('3', new Sprite(atlas, new Vector4i( 24, 32, 8, 16))); + charset.put('4', new Sprite(atlas, new Vector4i( 32, 32, 8, 16))); + charset.put('5', new Sprite(atlas, new Vector4i( 40, 32, 8, 16))); + charset.put('6', new Sprite(atlas, new Vector4i( 48, 32, 8, 16))); + charset.put('7', new Sprite(atlas, new Vector4i( 56, 32, 8, 16))); + charset.put('8', new Sprite(atlas, new Vector4i( 64, 32, 8, 16))); + charset.put('9', new Sprite(atlas, new Vector4i( 72, 32, 8, 16))); + charset.put('@', new Sprite(atlas, new Vector4i( 80, 32, 8, 16))); + charset.put('#', new Sprite(atlas, new Vector4i( 88, 32, 8, 16))); + charset.put('$', new Sprite(atlas, new Vector4i( 96, 32, 8, 16))); + charset.put('%', new Sprite(atlas, new Vector4i(104, 32, 8, 16))); + charset.put('^', new Sprite(atlas, new Vector4i(112, 32, 8, 16))); + charset.put('&', new Sprite(atlas, new Vector4i(120, 32, 8, 16))); + charset.put('*', new Sprite(atlas, new Vector4i(128, 32, 8, 16))); + charset.put('(', new Sprite(atlas, new Vector4i(136, 32, 8, 16))); + charset.put(')', new Sprite(atlas, new Vector4i(144, 32, 8, 16))); + charset.put('<', new Sprite(atlas, new Vector4i(152, 32, 8, 16))); + charset.put('>', new Sprite(atlas, new Vector4i(160, 32, 8, 16))); + charset.put('_', new Sprite(atlas, new Vector4i(168, 32, 8, 16))); + charset.put('-', new Sprite(atlas, new Vector4i(176, 32, 8, 16))); + charset.put('+', new Sprite(atlas, new Vector4i(184, 32, 8, 16))); + charset.put('=', new Sprite(atlas, new Vector4i(192, 32, 8, 16))); + charset.put('/', new Sprite(atlas, new Vector4i(200, 32, 8, 16))); + charset.put('\\', new Sprite(atlas, new Vector4i(208, 32, 8, 16))); + charset.put('♥', new Sprite(atlas, new Vector4i(216, 32, 8, 16))); + charset.put('|', new Sprite(atlas, new Vector4i(224, 32, 8, 16))); + font = new Font(charset, 8, 16); + + frame = new Tile9( + new Sprite(atlas, new Vector4i(24, 88, 8, 8)), + new Sprite(atlas, new Vector4i(32, 88, 8, 8)), + new Sprite(atlas, new Vector4i(40, 88, 8, 8)), + new Sprite(atlas, new Vector4i(24, 96, 8, 8)), + new Sprite(atlas, new Vector4i(32, 96, 8, 8)), + new Sprite(atlas, new Vector4i(40, 96, 8, 8)), + new Sprite(atlas, new Vector4i(24, 104, 8, 8)), + new Sprite(atlas, new Vector4i(32, 104, 8, 8)), + new Sprite(atlas, new Vector4i(40, 104, 8, 8)) + ); + + redFrame = new Tile9( + new Sprite(atlas, new Vector4i( 0, 88, 8, 8)), + new Sprite(atlas, new Vector4i( 8, 88, 8, 8)), + new Sprite(atlas, new Vector4i(16, 88, 8, 8)), + new Sprite(atlas, new Vector4i( 0, 96, 8, 8)), + new Sprite(atlas, new Vector4i( 8, 96, 8, 8)), + new Sprite(atlas, new Vector4i(16, 96, 8, 8)), + new Sprite(atlas, new Vector4i( 0, 104, 8, 8)), + new Sprite(atlas, new Vector4i( 8, 104, 8, 8)), + new Sprite(atlas, new Vector4i(16, 104, 8, 8)) + ); + + fireFrame = new Tile9( + new Sprite(atlas, new Vector4i( 0, 88 - 24, 8, 8)), + new Sprite(atlas, new Vector4i( 8, 88 - 24, 8, 8)), + new Sprite(atlas, new Vector4i(16, 88 - 24, 8, 8)), + new Sprite(atlas, new Vector4i( 0, 96 - 24, 8, 8)), + new Sprite(atlas, new Vector4i( 8, 96 - 24, 8, 8)), + new Sprite(atlas, new Vector4i(16, 96 - 24, 8, 8)), + new Sprite(atlas, new Vector4i( 0, 104 - 24, 8, 8)), + new Sprite(atlas, new Vector4i( 8, 104 - 24, 8, 8)), + new Sprite(atlas, new Vector4i(16, 104 - 24, 8, 8)) + ); + + } +}