Final product

This commit is contained in:
Gnarwhal 2021-04-25 17:48:30 -04:00 committed by Gnarwhal
commit 313bc87210
Signed by: Gnarwhal
GPG key ID: 0989A73D8C421174
34 changed files with 3161 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
.idea/
out/
*.iml

1132
res/illustrator/player.ai Normal file

File diff suppressed because it is too large Load diff

BIN
res/img/player/body.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 905 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 475 B

BIN
res/img/player/particle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 645 B

BIN
res/map/layout.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 B

View file

@ -0,0 +1,11 @@
#version 330 core
uniform vec4 input_color;
in float value;
out vec4 color;
void main() {
color = input_color * vec4(value, value, value, 1);
}

View file

@ -0,0 +1,13 @@
#version 330 core
uniform mat4 mvp;
layout (location = 0) in vec3 vertices;
layout (location = 1) in float in_value;
out float value;
void main() {
value = in_value;
gl_Position = mvp * vec4(vertices, 1);
}

View file

@ -0,0 +1,15 @@
#version 330 core
uniform float rotation;
uniform sampler2D sampler;
in vec2 texCoords;
out vec4 color;
void main() {
color = texture(sampler, texCoords + vec2(0, rotation));
if (color.a == 0) {
discard;
}
}

View file

@ -0,0 +1,13 @@
#version 330 core
uniform mat4 mvp;
layout (location = 0) in vec3 vertices;
layout (location = 1) in vec2 itexCoords;
out vec2 texCoords;
void main() {
texCoords = itexCoords;
gl_Position = mvp * vec4(vertices, 1);
}

BIN
res/thumbnail.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

3
src/META-INF/MANIFEST.MF Normal file
View file

@ -0,0 +1,3 @@
Manifest-Version: 1.0
Main-Class: com.gnarwhal.ld48.game.Main

View file

@ -0,0 +1,37 @@
package com.gnarwhal.ld48.engine.audio;
import org.lwjgl.openal.AL;
import org.lwjgl.openal.ALC;
import org.lwjgl.openal.ALC10;
import org.lwjgl.openal.ALCCapabilities;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import static org.lwjgl.openal.ALC10.*;
public class ALManagement {
private long device, context;
private ALCCapabilities deviceCaps;
public ALManagement() {
device = alcOpenDevice((ByteBuffer) null);
if (device == 0)
throw new IllegalStateException("Failed to open the default device.");
deviceCaps = ALC.createCapabilities(device);
context = alcCreateContext(device, (IntBuffer) null);
if (context == 0)
throw new IllegalStateException("Failed to create an OpenAL context.");
alcMakeContextCurrent(context);
AL.createCapabilities(deviceCaps);
}
public void destroy() {
ALC10.alcDestroyContext(context);
ALC10.alcCloseDevice(device);
}
}

View file

@ -0,0 +1,38 @@
package com.gnarwhal.ld48.engine.audio;
import org.lwjgl.openal.AL10;
public class Sound {
private int buffer;
private int sourceId;
public Sound(String path) {
sourceId = AL10.alGenSources();
buffer = AL10.alGenBuffers();
WaveData waveData = WaveData.create(path);
AL10.alBufferData(buffer, waveData.format, waveData.data, waveData.samplerate);
AL10.alSourcei(sourceId, AL10.AL_BUFFER, buffer);
AL10.alSourcef(sourceId, AL10.AL_GAIN, 1);
AL10.alSourcef(sourceId, AL10.AL_PITCH, 1);
}
public void play(boolean loop) {
AL10.alSourcei(sourceId, AL10.AL_LOOPING, loop ? 1 : 0);
AL10.alSource3f(sourceId, AL10.AL_POSITION, 0, 0, 0);
AL10.alSourcePlay(sourceId);
}
public void stop() {
AL10.alSourceStop(sourceId);
}
public void setVolume(float volume) {
AL10.alSourcef(sourceId, AL10.AL_GAIN, volume);
}
public void destroy() {
AL10.alDeleteBuffers(buffer);
AL10.alDeleteSources(sourceId);
}
}

View file

@ -0,0 +1,83 @@
package com.gnarwhal.ld48.engine.audio;
import org.lwjgl.BufferUtils;
import org.lwjgl.openal.AL10;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;
import java.io.*;
import java.nio.ByteBuffer;
public class WaveData {
final int format;
final int samplerate;
final int totalBytes;
final int bytesPerFrame;
final ByteBuffer data;
private final AudioInputStream audioStream;
private final byte[] dataArray;
private WaveData(AudioInputStream stream) {
this.audioStream = stream;
AudioFormat audioFormat = stream.getFormat();
format = getOpenAlFormat(audioFormat.getChannels(), audioFormat.getSampleSizeInBits());
this.samplerate = (int) audioFormat.getSampleRate();
this.bytesPerFrame = audioFormat.getFrameSize();
this.totalBytes = (int) (stream.getFrameLength() * bytesPerFrame);
this.data = BufferUtils.createByteBuffer(totalBytes);
this.dataArray = new byte[totalBytes];
loadData();
}
protected void dispose() {
try {
audioStream.close();
data.clear();
} catch (IOException e) {
e.printStackTrace();
}
}
private ByteBuffer loadData() {
try {
int bytesRead = audioStream.read(dataArray, 0, totalBytes);
data.clear();
data.put(dataArray, 0, bytesRead);
data.flip();
} catch (IOException e) {
e.printStackTrace();
System.err.println("Couldn't read bytes from audio stream!");
}
return data;
}
public static WaveData create(String file) {
WaveData wavStream = null;
try {
InputStream stream = new FileInputStream(new File(file));
InputStream bufferedInput = new BufferedInputStream(stream);
AudioInputStream audioStream = null;
audioStream = AudioSystem.getAudioInputStream(bufferedInput);
wavStream = new WaveData(audioStream);
} catch (UnsupportedAudioFileException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return wavStream;
}
private static int getOpenAlFormat(int channels, int bitsPerSample) {
if (channels == 1) {
return bitsPerSample == 8 ? AL10.AL_FORMAT_MONO8 : AL10.AL_FORMAT_MONO16;
} else {
return bitsPerSample == 8 ? AL10.AL_FORMAT_STEREO8 : AL10.AL_FORMAT_STEREO16;
}
}
}

View file

@ -0,0 +1,100 @@
package com.gnarwhal.ld48.engine.display;
import org.joml.Matrix4f;
import org.joml.Vector3f;
public class Camera {
private Matrix4f projection, projView;
private float width, height;
private Vector3f position;
private float rotation;
public Camera(float width, float height) {
setDims(width, height);
position = new Vector3f();
rotation = 0;
projView = new Matrix4f();
}
public void setDims(float width, float height) {
this.width = width;
this.height = height;
projection = new Matrix4f().setOrtho(0, width, height, 0, 0, 1);
}
public void update() {
projection.translate(position.negate(new Vector3f()), projView).rotateZ(-rotation);
}
public Matrix4f getProjection() {
return new Matrix4f(projection);
}
public Matrix4f getMatrix() {
return new Matrix4f(projView);
}
public float getX() {
return position.x;
}
public float getY() {
return position.y;
}
public Vector3f getPosition() {
return new Vector3f(position);
}
public float getWidth() {
return width;
}
public float getHeight() {
return height;
}
public void setX(float x) {
position.x = x;
}
public void setY(float y) {
position.y = y;
}
public void setPosition(float x, float y) {
position.set(x, y, position.z);
}
public void setPosition(Vector3f position) {
this.position.x = position.x;
this.position.y = position.y;
}
public void setCenter(float x, float y) {
position.set(x - width / 2, y - height / 2, position.z);
}
public void setCenter(Vector3f position) {
this.position.x = position.x - width / 2;
this.position.y = position.y - height / 2;
}
public void translate(float x, float y, float z) {
position.add(x, y, z);
}
public void translate(Vector3f transform) {
position.add(transform);
}
public void setRotation(float angle) {
rotation = angle;
}
public void rotate(float angle) {
rotation += angle;
}
}

View file

@ -0,0 +1,93 @@
package com.gnarwhal.ld48.engine.display;
import com.gnarwhal.ld48.engine.texture.Texture;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL30.*;
import static org.lwjgl.opengl.GL32.glFramebufferTexture;
public class Framebuffer {
int fbo, rbo, width, height;
int colorBuf, depthTex;
float r, g, b, a;
Framebuffer(int width, int height, float r, float g, float b, float a) {
this.width = width;
this.height = height;
this.r = r;
this.g = g;
this.b = b;
this.a = a;
fbo = glGenFramebuffers();
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glDrawBuffer(GL_COLOR_ATTACHMENT0);
rbo = 0;
colorBuf = 0;
depthTex = 0;
}
Framebuffer addColorAttachment(Texture texture) {
if (colorBuf == 0) {
int id = glGenTextures();
glBindTexture(GL_TEXTURE_2D, id);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
texture = new Texture(id, width, height);
colorBuf = 1;
}
return this;
}
Framebuffer addDepthTextureAttachment(Texture texture) {
if (depthTex == 0) {
int id = glGenTextures();
glBindTexture(GL_TEXTURE_2D, id);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, id, 0);
texture = new Texture(id, width, height);
depthTex = 1;
}
return this;
}
Framebuffer addDepthBufferAttachment() {
if (rbo == 0) {
rbo = glGenRenderbuffers();
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo);
}
return this;
}
void bind() {
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glViewport(0, 0, width, height);
glClearColor(r, g, b, a);
}
void unbind(Window window) {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, window.getWidth(), window.getHeight());
window.activateClearColor();
}
void clear() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
public void cleanup() {
if (rbo != 0)
glDeleteRenderbuffers(rbo);
glDeleteFramebuffers(fbo);
}
}

View file

@ -0,0 +1,227 @@
package com.gnarwhal.ld48.engine.display;
import org.joml.Vector2f;
import org.joml.Vector3f;
import org.lwjgl.glfw.GLFWErrorCallback;
import org.lwjgl.glfw.GLFWGamepadState;
import org.lwjgl.glfw.GLFWVidMode;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL.createCapabilities;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL13.GL_MULTISAMPLE;
public class Window {
public static int
SCREEN_WIDTH,
SCREEN_HEIGHT,
REFRESH_RATE;
public static final int
BUTTON_RELEASED = 0,
BUTTON_UNPRESSED = 1,
BUTTON_PRESSED = 2,
BUTTON_HELD = 3,
BUTTON_REPEAT = 4;
public static float SCALE;
private long window;
private int width, height;
private boolean resized;
private int[] mouseButtons = new int[GLFW_MOUSE_BUTTON_LAST + 1];
private int[] keys = new int[GLFW_KEY_LAST + 1];
private int[] gamepadButtons = new int[GLFW_GAMEPAD_BUTTON_LAST];
private GLFWGamepadState gamepadState;
public Window(String title, boolean vSync) {
init(0, 0, title, vSync, false, false, false);
}
public Window(String title, boolean vSync, boolean resizable, boolean decorated) {
init(800, 500, title, vSync, resizable, decorated, true);
}
public Window(int width, int height, String title, boolean vSync, boolean resizable, boolean decorated) {
init(width, height, title, vSync, resizable, decorated, false);
}
public void init(int lwidth, int lheight, String title, boolean vSync, boolean resizable, boolean decorated, boolean maximized) {
glfwSetErrorCallback(GLFWErrorCallback.createPrint(System.err));
for (int i = 0; i < mouseButtons.length; i++)
mouseButtons[i] = 0;
if(!glfwInit()) {
System.err.println("GLFW failed to initialize!");
System.exit(-1);
}
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwWindowHint(GLFW_SAMPLES, 8);
glfwWindowHint(GLFW_RESIZABLE, resizable ? GLFW_TRUE : GLFW_FALSE);
glfwWindowHint(GLFW_DECORATED, decorated ? GLFW_TRUE : GLFW_FALSE);
glfwWindowHint(GLFW_MAXIMIZED, maximized ? GLFW_TRUE : GLFW_FALSE);
GLFWVidMode vidMode = glfwGetVideoMode(glfwGetPrimaryMonitor());
SCREEN_WIDTH = vidMode.width();
SCREEN_HEIGHT = vidMode.height();
SCALE = SCREEN_HEIGHT / 1080f;
REFRESH_RATE = vidMode.refreshRate();
if(lwidth == 0 || lheight == 0) {
width = vidMode.width();
height = vidMode.height();
window = glfwCreateWindow(width, height, title, glfwGetPrimaryMonitor(), 0);
}
else {
this.width = lwidth;
this.height = lheight;
window = glfwCreateWindow(width, height, title, 0, 0);
}
glfwMakeContextCurrent(window);
createCapabilities();
glfwSwapInterval(vSync ? 1 : 0);
glfwSetWindowSizeCallback(window, (long window, int w, int h) -> {
width = w;
height = h;
resized = true;
glViewport(0, 0, width, height);
});
glfwSetMouseButtonCallback(window, (long window, int button, int action, int mods) -> {
if (action == GLFW_RELEASE)
mouseButtons[button] = BUTTON_RELEASED;
if (action == GLFW_PRESS)
mouseButtons[button] = BUTTON_PRESSED;
if (action == GLFW_REPEAT)
mouseButtons[button] = BUTTON_REPEAT;
});
glfwSetKeyCallback(window, (long window, int key, int scancode, int action, int mods) -> {
if (key != -1) {
if (action == GLFW_RELEASE)
keys[key] = BUTTON_RELEASED;
if (action == GLFW_PRESS)
keys[key] = BUTTON_PRESSED;
if (action == GLFW_REPEAT)
keys[key] = BUTTON_REPEAT;
}
});
activateClearColor();
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_MULTISAMPLE);
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
int[] awidth = new int[1], aheight = new int[1];
glfwGetWindowSize(window, awidth, aheight);
width = awidth[0];
height = aheight[0];
gamepadState = GLFWGamepadState.create();
}
public void update() {
for (int i = 0; i < mouseButtons.length; i++)
if (mouseButtons[i] == BUTTON_RELEASED || mouseButtons[i] == BUTTON_PRESSED)
++mouseButtons[i];
for (int i = 0; i < keys.length; i++)
if (keys[i] == BUTTON_RELEASED || keys[i] == BUTTON_PRESSED)
++keys[i];
if (glfwGetGamepadState(GLFW_JOYSTICK_1, gamepadState)) {
for (int i = 0; i < gamepadButtons.length; ++i) {
if (gamepadState.buttons(i) == GLFW_RELEASE) {
if (gamepadButtons[i] == BUTTON_RELEASED) {
gamepadButtons[i] = BUTTON_UNPRESSED;
} else if (gamepadButtons[i] != BUTTON_UNPRESSED) {
gamepadButtons[i] = BUTTON_RELEASED;
}
} else {
if (gamepadButtons[i] == BUTTON_PRESSED) {
gamepadButtons[i] = BUTTON_HELD;
} else if (gamepadButtons[i] != BUTTON_HELD) {
gamepadButtons[i] = BUTTON_PRESSED;
}
}
}
}
resized = false;
glfwPollEvents();
}
public void activateClearColor() {
glClearColor(0, 0, 0, 1);
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public void clear() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
public void swap() {
glfwSwapBuffers(window);
}
public void close() {
glfwSetWindowShouldClose(window, true);
}
public static void terminate() {
glfwTerminate();
}
public boolean shouldClose() {
return glfwWindowShouldClose(window);
}
public int keyPressed(int keyCode) {
return keys[keyCode];
}
public Vector3f getMouseCoords(Camera camera) {
double[] x = new double[1], y = new double[1];
glfwGetCursorPos(window, x, y);
Vector3f ret = new Vector3f((float) x[0], (float) y[0], 0);
return ret.mul(camera.getWidth() / this.width, camera.getHeight() / this.height, 1);
}
public int mousePressed(int button) {
return mouseButtons[button];
}
public boolean joystick(int joystick) {
return glfwJoystickPresent(joystick) && glfwJoystickIsGamepad(joystick);
}
public float getJoystickAxis(int axis) {
return gamepadState.axes(axis);
}
public int controllerButtonPressed(int button) {
return gamepadButtons[button];
}
public boolean wasResized() {
return resized;
}
}

View file

@ -0,0 +1,50 @@
package com.gnarwhal.ld48.engine.model;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL20.*;
import static org.lwjgl.opengl.GL30.*;
public class Vao {
private int numAttribs = 0;
private int vao, ibo, count;
private int[] vbos = new int[15];
public Vao(float[] vertices, int[] indices) {
vao = glGenVertexArrays();
glBindVertexArray(vao);
addAttrib(vertices, 3);
ibo = glGenBuffers();
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW);
count = indices.length;
}
public void addAttrib(float[] data, int size) {
int vbo = glGenBuffers();
vbos[numAttribs] = vbo;
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, data, GL_STATIC_DRAW);
glVertexAttribPointer(numAttribs, size, GL_FLOAT, false, 0, 0);
++numAttribs;
}
public void render() {
glBindVertexArray(vao);
for(int i = 0; i < numAttribs; ++i)
glEnableVertexAttribArray(i);
glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_INT, 0);
for(int i = 0; i < numAttribs; ++i)
glDisableVertexAttribArray(i);
}
public void destroy() {
for(int vbo : vbos)
glDeleteBuffers(vbo);
glDeleteBuffers(ibo);
glDeleteVertexArrays(vao);
}
}

View file

@ -0,0 +1,8 @@
package com.gnarwhal.ld48.engine.properties;
public class ImproperFormattingException extends RuntimeException {
public ImproperFormattingException(String message) {
super(message);
}
}

View file

@ -0,0 +1,104 @@
package com.gnarwhal.ld48.engine.properties;
public class Properties {
private String name;
private PropNode head, cur;
public Properties(String name) {
this.name = new String(name);
}
public void add(PropNode node) {
if(head == null) {
head = node;
cur = node;
}
else {
cur.next = node;
cur = cur.next;
}
}
private PropNode get(String key) throws UndeclaredPropertyException {
String[] keys = key.split("\\.");
PropNode mobile = head;
while (mobile != null) {
if(mobile.key.equals(keys[0])) {
if(keys.length > 1 && mobile instanceof BlockNode)
return ((BlockNode) mobile).data.get(key.substring(keys[0].length() + 1));
else
return mobile;
}
mobile = mobile.next;
}
throw new UndeclaredPropertyException("Property '" + key + "' in properties '" + name + "' was not found!");
}
public String getAsString(String key) throws UndeclaredPropertyException {
return ((StringNode) get(key)).data;
}
public int getAsInt(String key) throws UndeclaredPropertyException {
return ((IntNode) get(key)).data;
}
public int[] getAsIntArray(String key) throws UndeclaredPropertyException {
PropNode node = get(key);
if(node instanceof IntNode)
return new int[] { ((IntNode) node).data };
return ((IntArrayNode) get(key)).data;
}
public double getAsDouble(String key) throws UndeclaredPropertyException {
PropNode node = get(key);
if(node instanceof IntNode)
return (double) ((IntNode) node).data;
return ((DoubleNode) get(key)).data;
}
public double[] getAsDoubleArray(String key) throws UndeclaredPropertyException {
PropNode node = get(key);
if(node instanceof DoubleNode)
return new double[] { ((DoubleNode) node).data };
if(node instanceof IntNode)
return new double[] { ((IntNode) node).data };
if(node instanceof IntArrayNode) {
int[] ints = getAsIntArray(key);
double[] ret = new double[ints.length];
for (int i = 0; i < ints.length; ++i)
ret[i] = ints[i];
return ret;
}
return ((DoubleArrayNode) get(key)).data;
}
public static class PropNode {
public String key;
public PropNode next;
}
public static class BlockNode extends PropNode {
public Properties data;
}
public static class StringNode extends PropNode {
public String data;
}
public static class IntNode extends PropNode {
public int data;
}
public static class IntArrayNode extends PropNode {
public int[] data;
}
public static class DoubleNode extends PropNode {
public double data;
}
public static class DoubleArrayNode extends PropNode {
public double[] data;
}
}

View file

@ -0,0 +1,91 @@
package com.gnarwhal.ld48.engine.properties;
import com.gnarwhal.ld48.engine.properties.Properties.*;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class PropertyReader {
private static int lineNum;
private static String path;
public static Properties readProperties(String path) {
Properties props = null;
try {
File file = new File(path);
Scanner scanner = new Scanner(file);
PropertyReader.path = path;
lineNum = 0;
props = readBlock(file.getName(), scanner).data;
scanner.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
System.exit(-1);
}
return props;
}
private static BlockNode readBlock(String name, Scanner scanner) {
BlockNode props = new BlockNode();
props.key = name;
props.data = new Properties(name);
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
line = line.replaceAll("\\s", "");
if(line.equals("}"))
break;
else if(line.length() < 2 || !line.substring(0, 2).equals("//")){
String[] pair = line.split(":");
if (pair.length != 2)
throw new ImproperFormattingException("Formatting exception on line " + line + " in file '" + path + "!");
pair[1] = pair[1].replaceAll("\\s", "");
if (pair[1].equals("{"))
props.data.add(readBlock(pair[0], scanner));
else if (pair[1].matches("(\\d+|0x[\\da-f]+)")) {
IntNode node = new IntNode();
node.key = pair[0];
node.data = Integer.decode(pair[1]);
props.data.add(node);
}
else if (pair[1].matches("(\\d+|0x[\\d0-9]+)(,(\\d+|0x[\\d0-9]+))+")) {
String[] data = pair[1].split(",");
int[] ints = new int[data.length];
for (int i = 0; i < ints.length; ++i)
ints[i] = Integer.decode(data[i]);
IntArrayNode node = new IntArrayNode();
node.key = pair[0];
node.data = ints;
props.data.add(node);
}
else if (pair[1].matches("\\d+\\.\\d+")) {
DoubleNode node = new DoubleNode();
node.key = pair[0];
node.data = Double.parseDouble(pair[1]);
props.data.add(node);
}
else if (pair[1].matches("\\d+\\.\\d+(,\\d+\\.\\d+)+")) {
String[] data = pair[1].split(",");
double[] doubles = new double[data.length];
for (int i = 0; i < doubles.length; ++i)
doubles[i] = Double.parseDouble(data[i]);
DoubleArrayNode node = new DoubleArrayNode();
node.key = pair[0];
node.data = doubles;
props.data.add(node);
}
else {
StringNode node = new StringNode();
node.key = pair[0];
node.data = pair[1];
props.data.add(node);
}
}
++lineNum;
}
return props;
}
}

View file

@ -0,0 +1,8 @@
package com.gnarwhal.ld48.engine.properties;
public class UndeclaredPropertyException extends Exception {
public UndeclaredPropertyException(String exception) {
super(exception);
}
}

View file

@ -0,0 +1,22 @@
package com.gnarwhal.ld48.engine.shaders;
import static org.lwjgl.opengl.GL20.*;
public class GradientShader extends Shader {
private int color_loc;
public GradientShader() {
super("res/shaders/gradient/vert.gls", "res/shaders/gradient/frag.gls");
getUniforms();
}
@Override
protected void getUniforms() {
color_loc = glGetUniformLocation(program, "input_color");
}
public void setColor(float r, float g, float b, float a) {
glUniform4f(color_loc, r, g, b, a);
}
}

View file

@ -0,0 +1,27 @@
package com.gnarwhal.ld48.engine.shaders;
import static org.lwjgl.opengl.GL20.glGetUniformLocation;
import static org.lwjgl.opengl.GL20.glUniform1f;
public class PlayerShader extends Shader {
private int rotation_loc;
public PlayerShader() {
super("res/shaders/player/vert.gls", "res/shaders/player/frag.gls");
getUniforms();
}
@Override
protected void getUniforms() {
rotation_loc = glGetUniformLocation(program, "rotation");
}
public void setRotation(float rotation) {
rotation = (rotation % 1) * 2;
if (rotation > 1) {
rotation -= 2;
}
glUniform1f(rotation_loc, rotation);
}
}

View file

@ -0,0 +1,74 @@
package com.gnarwhal.ld48.engine.shaders;
import org.joml.Matrix4f;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import static org.lwjgl.opengl.GL20.*;
public abstract class Shader {
protected int program;
protected int mvpLoc;
protected Shader(String vertPath, String fragPath) {
program = glCreateProgram();
int vert = loadShader(vertPath, GL_VERTEX_SHADER);
int frag = loadShader(fragPath, GL_FRAGMENT_SHADER);
glAttachShader(program, vert);
glAttachShader(program, frag);
glLinkProgram(program);
glDetachShader(program, vert);
glDetachShader(program, frag);
glDeleteShader(vert);
glDeleteShader(frag);
mvpLoc = glGetUniformLocation(program, "mvp");
}
private int loadShader(String path, int type) {
StringBuilder file = new StringBuilder();
try {
BufferedReader reader = new BufferedReader(new FileReader(new File(path)));
String line;
while((line = reader.readLine()) != null)
file.append(line + '\n');
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
String source = file.toString();
int shader = glCreateShader(type);
glShaderSource(shader, source);
glCompileShader(shader);
if(glGetShaderi(shader, GL_COMPILE_STATUS) != 1)
throw new RuntimeException("Failed to compile shader: " + path + "! " + glGetShaderInfoLog(shader));
return shader;
}
protected abstract void getUniforms();
public void setMVP(Matrix4f matrix) {
glUniformMatrix4fv(mvpLoc, false, matrix.get(new float[16]));
}
public void enable() {
glUseProgram(program);
}
public void disable() {
glUseProgram(0);
}
public void destroy() {
glDeleteProgram(program);
}
}

View file

@ -0,0 +1,88 @@
package com.gnarwhal.ld48.engine.texture;
import org.lwjgl.BufferUtils;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL13.GL_TEXTURE0;
import static org.lwjgl.opengl.GL13.glActiveTexture;
public class Texture {
protected int id, width, height;
public Texture(String name) {
this(name, GL_CLAMP);
}
public Texture(String name, int wrap) {
BufferedImage bi = null;
try {
bi = ImageIO.read(new File(name));
} catch (IOException e) {
e.printStackTrace();
}
if (bi != null) {
width = bi.getWidth();
height = bi.getHeight();
int[] pixels = bi.getRGB(0, 0, width, height, null, 0, width);
ByteBuffer buffer = BufferUtils.createByteBuffer(width * height * 4);
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int pixel = pixels[i * width + j];
buffer.put((byte)((pixel >> 16) & 0xFF)); // Red
buffer.put((byte)((pixel >> 8) & 0xFF)); // Green
buffer.put((byte)((pixel ) & 0xFF)); // Blue
buffer.put((byte)((pixel >> 24) & 0xFF)); // Alpha
}
}
buffer.flip();
id = glGenTextures();
bind();
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
unbind();
}
}
public Texture(int id, int width, int height) {
this.id = id;
this.width = width;
this.height = height;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public void bind() {
bind(0);
}
public void bind(int activeTexture) {
glActiveTexture(GL_TEXTURE0 + activeTexture);
glBindTexture(GL_TEXTURE_2D, id);
}
public void unbind() {
glBindTexture(GL_TEXTURE_2D, 0);
}
public void destroy() {
glDeleteTextures(id);
}
}

View file

@ -0,0 +1,27 @@
package com.gnarwhal.ld48.game;
import com.gnarwhal.ld48.engine.display.Camera;
import com.gnarwhal.ld48.engine.display.Window;
public class GamePanel {
private Map map;
private Player player;
public GamePanel() {
map = new Map();
player = new Player();
}
public void update(Window window, Camera camera) {
player.move(window);
map.check_collisions(player);
player.update(camera);
}
public void render(Camera camera) {
map.render_floor(camera);
player.render(camera);
map.render_walls(camera);
}
}

View file

@ -0,0 +1,93 @@
package com.gnarwhal.ld48.game;
import com.gnarwhal.ld48.engine.audio.ALManagement;
import com.gnarwhal.ld48.engine.display.Camera;
import com.gnarwhal.ld48.engine.display.Window;
import com.gnarwhal.ld48.engine.shaders.Shader;
public class Main {
public static int fps;
public static double dtime;
public static double adtime;
private static double freezeDuration;
private static double freezeTime;
private ALManagement al;
private Window window;
private Camera camera;
private GamePanel panel;
public static void freeze(float duration) {
freezeDuration = duration;
freezeTime = 0;
}
public void start() {
init();
int frames = 0;
long curTime, pastTime, pastSec, nspf = 1000000000 / Window.REFRESH_RATE;
pastTime = System.nanoTime();
pastSec = pastTime;
while(!window.shouldClose()) {
curTime = System.nanoTime();
if (curTime - pastTime > nspf) {
adtime = nspf / 1000000000d;
if (freezeDuration > freezeTime + adtime) {
dtime = 0;
} else if (freezeDuration > freezeTime) {
dtime = adtime - (freezeDuration - freezeTime);
} else {
dtime = adtime;
}
freezeTime += adtime;
update();
render();
pastTime += nspf;
++frames;
}
if (curTime - pastSec > 1000000000) {
fps = frames;
frames = 0;
pastSec += 1000000000;
}
if (nspf - curTime + pastTime > 10000000) try {
Thread.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
}
al.destroy();
Window.terminate();
}
private void init() {
al = new ALManagement();
final int WIN_WIDTH = 1920, WIN_HEIGHT = 1080;
window = new Window("Ludum Dare 48", true);
//window = new Window(WIN_WIDTH * 3/4, WIN_HEIGHT * 3/4, "Ludum Dare 48", true, true, true);
camera = new Camera(WIN_WIDTH, WIN_HEIGHT);
panel = new GamePanel();
}
private void update() {
window.update();
panel.update(window, camera);
camera.update();
}
private void render() {
window.clear();
panel.render(camera);
window.swap();
}
public static void main(String[] args) {
new Main().start();
}
}

View file

@ -0,0 +1,381 @@
package com.gnarwhal.ld48.game;
import com.gnarwhal.ld48.engine.display.Camera;
import com.gnarwhal.ld48.engine.model.Vao;
import com.gnarwhal.ld48.engine.shaders.GradientShader;
import org.joml.Vector2f;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
public class Map {
private static class Tile {
public static abstract class RenderPass {
protected float offset;
protected RenderPass(float offset) {
this.offset = offset;
}
public abstract void render(Camera camera, int x, int y);
}
public static class GradientPass extends RenderPass {
private static GradientShader shader = null;
private Vao vao;
private float r, g, b, a;
public GradientPass(Vao vao, float offset, float r, float g, float b, float a) {
super(offset);
if (shader == null) {
shader = new GradientShader();
}
this.vao = vao;
this.r = r;
this.g = g;
this.b = b;
this.a = a;
}
public void render(Camera camera, int x, int y) {
shader.enable();
shader.setColor(r, g, b, a);
shader.setMVP(camera.getMatrix().translate(x * TILE_DIMS, y * TILE_DIMS - offset, 0).scale(TILE_DIMS, TILE_DIMS, 1));
vao.render();
}
}
public static final int
LEVEL_GROUND = 0,
LEVEL_WALL = 1;
public int level;
public boolean solid;
public ArrayList<RenderPass> render_passes;
public Tile(int level, boolean solid, ArrayList<RenderPass> render_passes) {
this.level = level;
this.solid = solid;
this.render_passes = render_passes;
}
public Tile clone() {
return new Tile(
this.level,
this.solid,
this.render_passes == null ? null : new ArrayList<>(this.render_passes)
);
}
}
public static float TILE_DIMS = 128.0f;
private static final ArrayList<Tile.RenderPass> TOP_GROUND_RENDER = new ArrayList<>();
private static final ArrayList<Tile.RenderPass> MID_GROUND_RENDER = new ArrayList<>();
private static final ArrayList<Tile.RenderPass>[] WALL_BORDER_RENDER = new ArrayList[256];
private static final Tile
GROUND_TILE = new Tile(Tile.LEVEL_GROUND, true, null),
WALL_TILE = new Tile(Tile.LEVEL_WALL, true, null);
private Tile[][] map;
public Map() {
if (TOP_GROUND_RENDER.size() == 0) {
Vao vao = new Vao(
new float[] {
1, 0, 0,
1, 1, 0,
0, 1, 0,
0, 0, 0
},
new int[] {
0, 1, 3,
1, 2, 3
}
);
vao.addAttrib(
new float[] { 0.5f, 0.5f, 0.5f, 0.5f },
1
);
Tile.GradientPass floor_pass = new Tile.GradientPass(vao, 0, 1, 1, 1, 1);
MID_GROUND_RENDER.add(floor_pass);
vao = new Vao(
new float[] {
1, 0, 0,
1, 0.75f, 0,
0, 0.75f, 0,
0, 0, 0
},
new int[] {
0, 1, 3,
1, 2, 3
}
);
vao.addAttrib(
new float[] { 0.35f, 1.0f, 1.0f, 0.35f },
1
);
Tile.GradientPass wall_pass = new Tile.GradientPass(vao, TILE_DIMS * 0.75f, 1, 1, 1, 1);
TOP_GROUND_RENDER.add(wall_pass);
TOP_GROUND_RENDER.add(floor_pass);
float[] vertices = new float[] {
0, 0, 0,
0.25f, 0, 0,
0.75f, 0, 0,
1, 0, 0,
1, 0.15f, 0,
1, 0.85f, 0,
1, 1, 0,
0.75f, 1, 0,
0.25f, 1, 0,
0, 1, 0,
0, 0.85f, 0,
0, 0.15f, 0,
0.25f, 0.15f, 0,
0.75f, 0.15f, 0,
0.75f, 0.85f, 0,
0.25f, 0.85f, 0
};
int[] indices = new int[] {
0, 11, 12,
0, 12, 1,
1, 12, 2,
12, 13, 2,
2, 13, 3,
3, 13, 4,
4, 13, 5,
13, 14, 5,
5, 14, 6,
6, 14, 7,
7, 14, 8,
14, 15, 8,
8, 15, 9,
9, 15, 10,
10, 15, 11,
15, 12, 11,
12, 15, 13,
13, 15, 14
};
float[] values = new float[16];
for (int i = 0; i <= 0b11111111; ++i) {
for (int j = 0; j < 16; ++j) {
values[j] = 0;
}
int mask = i;
for (int j = 0; j < 4; ++j) {
if ((mask & 1) != 0) {
for (int k = 0; k < 4; ++k) {
values[(j * 3 + k) % 12] = 0.25f;
}
}
mask >>= 1;
}
for (int j = 0; j < 4; ++j) {
if ((mask & 1) != 0) {
values[j * 3] = 0.25f;
}
mask >>= 1;
}
vao = new Vao(vertices, indices);
vao.addAttrib(values, 1);
ArrayList<Tile.RenderPass> render_pass = new ArrayList<>();
render_pass.add(new Tile.GradientPass(vao, TILE_DIMS * 0.75f, 1, 1, 1, 1));
WALL_BORDER_RENDER[i] = render_pass;
}
}
try {
final int WALL_COLOR = 0xFF000000;
final int GROUND_COLOR = 0xFFFFFFFF;
BufferedImage map_layout = ImageIO.read(new File("res/map/layout.png"));
map = new Tile[map_layout.getWidth()][map_layout.getHeight()];
for (int x = 0; x < map_layout.getWidth(); ++x) {
for (int y = 0; y < map_layout.getHeight(); ++y) {
if (map_layout.getRGB(x, y) == WALL_COLOR) {
map[x][y] = WALL_TILE.clone();
} else if (map_layout.getRGB(x, y) == GROUND_COLOR) {
map[x][y] = GROUND_TILE.clone();
if (y > 0 && map[x][y - 1].level == Tile.LEVEL_WALL) {
map[x][y].render_passes = TOP_GROUND_RENDER;
} else {
map[x][y].render_passes = MID_GROUND_RENDER;
}
}
}
}
for (int x = 0; x < map_layout.getWidth(); ++x) {
for (int y = 0; y < map_layout.getHeight(); ++y) {
if (map[x][y].level == Tile.LEVEL_WALL) {
int result = 0;
if ( y > 0 && map[x ][y - 1].level == Tile.LEVEL_GROUND) { result |= 1 << 0; }
if (x < map.length - 1 && map[x + 1][y ].level == Tile.LEVEL_GROUND) { result |= 1 << 1; }
if ( y < map[0].length - 1 && map[x ][y + 1].level == Tile.LEVEL_GROUND) { result |= 1 << 2; }
if (x > 0 && map[x - 1][y ].level == Tile.LEVEL_GROUND) { result |= 1 << 3; }
if (x > 0 && y > 0 && map[x - 1][y - 1].level == Tile.LEVEL_GROUND) { result |= 1 << 4; }
if (x < map.length - 1 && y > 0 && map[x + 1][y - 1].level == Tile.LEVEL_GROUND) { result |= 1 << 5; }
if (x < map.length - 1 && y < map[0].length - 1 && map[x + 1][y + 1].level == Tile.LEVEL_GROUND) { result |= 1 << 6; }
if (x > 0 && y < map[0].length - 1 && map[x - 1][y + 1].level == Tile.LEVEL_GROUND) { result |= 1 << 7; }
map[x][y].render_passes = new ArrayList<>(WALL_BORDER_RENDER[result]);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void check_collisions(Player player) {
Vector2f player_motion = new Vector2f(player.velocity);
Vector2f player_position = player.base_position.add(0, 0.5f * TILE_DIMS, new Vector2f());
Vector2f player_pos_min = new Vector2f(
Math.min(0, player_motion.x),
Math.min(0, player_motion.y)
).add(player_position).add(new Vector2f(-Player.PLAYER_DIMS * 0.5f));
Vector2f player_pos_max = new Vector2f(
Math.max(0, player_motion.x),
Math.max(0, player_motion.y)
).add(player_position).add(new Vector2f(Player.PLAYER_DIMS * 0.5f));
int min_x = (int) (player_pos_min.x / TILE_DIMS);
int min_y = (int) (player_pos_min.y / TILE_DIMS);
int max_x = (int) (player_pos_max.x / TILE_DIMS);
int max_y = (int) (player_pos_max.y / TILE_DIMS);
float min_t = 0.0f;
while (min_t < 1.0f) {
player_position = player.base_position.add(0, 0.5f * TILE_DIMS, new Vector2f());
min_t = 1.0f;
float neutralization_direction = 0.0f;
for (int x = min_x; x <= max_x; ++x) {
for (int y = min_y; y <= max_y; ++y) {
if (map[x][y].level == Tile.LEVEL_WALL) {
Vector2f[] recall_distances = new Vector2f[] {
check_intersection(x, y, player_position.add(-Player.PLAYER_DIMS * 0.5f, -Player.PLAYER_DIMS * 0.5f, new Vector2f()), player_motion),
check_intersection(x, y, player_position.add( Player.PLAYER_DIMS * 0.5f, -Player.PLAYER_DIMS * 0.5f, new Vector2f()), player_motion),
check_intersection(x, y, player_position.add(-Player.PLAYER_DIMS * 0.5f, Player.PLAYER_DIMS * 0.5f, new Vector2f()), player_motion),
check_intersection(x, y, player_position.add( Player.PLAYER_DIMS * 0.5f, Player.PLAYER_DIMS * 0.5f, new Vector2f()), player_motion)
};
for (int i = 0; i < 4; ++i) {
if (recall_distances[i] != null && recall_distances[i].x < min_t) {
min_t = recall_distances[i].x;
neutralization_direction = recall_distances[i].y;
}
}
}
}
}
if (min_t < 1.0f) {
Vector2f valid_motion = player_motion.mul(min_t, new Vector2f());
player.base_position.add(valid_motion);
player_motion.sub(valid_motion).mul(neutralization_direction, 1 - neutralization_direction);
player.proc_collision();
if (player_motion.lengthSquared() < 0.001f) {
min_t = 1.0f;
}
} else {
player.base_position.add(player_motion);
}
}
}
public Vector2f check_intersection(int x, int y, Vector2f position, Vector2f motion) {
Vector2f wall_start = new Vector2f(x * TILE_DIMS, y * TILE_DIMS);
Vector2f wall_end = new Vector2f(0, TILE_DIMS);
Vector2f min = new Vector2f(1, 0);
for (int i = 0; i < 4; ++i) {
if (motion.dot(new Vector2f(-wall_end.y, wall_end.x)) < 0) {
float t0, t1;
if (motion.y == 0) {
t0 = (motion.y * (wall_start.x - position.x) / motion.x + position.y - wall_start.y) / (wall_end.y - motion.y * wall_end.x / motion.x);
t1 = (wall_end.x * t0 + wall_start.x - position.x) / motion.x;
} else {
t0 = (motion.x * (wall_start.y - position.y) / motion.y + position.x - wall_start.x) / (wall_end.x - motion.x * wall_end.y / motion.y);
t1 = (wall_end.y * t0 + wall_start.y - position.y) / motion.y;
}
if (0 < t0 && t0 < 1
&& -0.0001f <= t1 && t1 < 1
&& t1 < min.x) {
min.set(t1, i % 2);
}
}
/*
a0 * t + b0 = c0 * s + d0
a1 * t + b1 = c1 * s + d1
s = (a0 * t + b0 - d0) / c0
a1 * t + b1 = c1 * (a0 * t + b0 - d0) / c0 + d1
a1 * t + b1 - c1 * a0 * t / c0 = c1 * (b0 - d0) / c0 + d1
(a1 - c1 * a0 / c0) * t + b1 = c1 * (b0 - d0) / c0 + d1
t = (c1 * (b0 - d0) / c0 + d1 - b1) / (a1 - c1 * a0 / c0)
*/
wall_start.add(wall_end);
wall_end.set(wall_end.y, -wall_end.x);
}
if (min.x < 1) {
return min;
} else {
return null;
}
}
public void render_floor(Camera camera) {
int min_x = (int) Math.max((camera.getX() / TILE_DIMS), 0);
int min_y = (int) Math.max((camera.getY() / TILE_DIMS), 0);
int max_x = (int) Math.min(((camera.getX() + camera.getWidth()) / TILE_DIMS), map.length - 1);
int max_y = (int) Math.min(((camera.getY() + camera.getHeight()) / TILE_DIMS) + 1, map[0].length - 1);
for (int x = min_x; x <= max_x; ++x) {
for (int y = min_y; y <= max_y; ++y) {
if (map[x][y].level == Tile.LEVEL_GROUND) {
ArrayList<Tile.RenderPass> render_passes = map[x][y].render_passes;
if (render_passes != null) {
for (int i = 0; i < render_passes.size(); ++i) {
render_passes.get(i).render(camera, x, y);
}
}
}
}
}
}
public void render_walls(Camera camera) {
int min_x = (int) Math.max((camera.getX() / TILE_DIMS), 0);
int min_y = (int) Math.max(((camera.getY() + 0.75f * TILE_DIMS) / TILE_DIMS), 0);
int max_x = (int) Math.min(((camera.getX() + camera.getWidth()) / TILE_DIMS), map.length - 1);
int max_y = (int) Math.min(((camera.getY() + camera.getHeight() + 0.75f * TILE_DIMS) / TILE_DIMS), map[0].length - 1);
for (int x = min_x; x <= max_x; ++x) {
for (int y = min_y; y <= max_y; ++y) {
if (map[x][y].level == Tile.LEVEL_WALL) {
ArrayList<Tile.RenderPass> render_passes = map[x][y].render_passes;
if (render_passes != null) {
for (int i = 0; i < render_passes.size(); ++i) {
render_passes.get(i).render(camera, x, y);
}
}
}
}
}
}
}

View file

@ -0,0 +1,419 @@
package com.gnarwhal.ld48.game;
import com.gnarwhal.ld48.engine.display.Camera;
import com.gnarwhal.ld48.engine.display.Window;
import com.gnarwhal.ld48.engine.model.Vao;
import com.gnarwhal.ld48.engine.shaders.PlayerShader;
import com.gnarwhal.ld48.engine.texture.Texture;
import org.joml.Vector2f;
import org.lwjgl.glfw.GLFW;
import java.util.ArrayList;
import static org.lwjgl.glfw.GLFW.*;
public class Player {
private static class Particle {
public Vector2f position;
public Vector2f velocity;
public float dimensions;
public float kill_hour;
public float clock;
}
public static final float PLAYER_DIMS = 64.0f;
public static final int
EXPR_NORMAL = 0,
EXPR_THREE_SMOAKS = 1,
EXPR_CONFUSED = 2,
EXPR_SQUINT = 3;
private static final float
HOVER_FLUCTUATION = 42.0f,
HOVER_CYCLE_RATE = 2.5f;
private static final int
NO_ACTION = 0,
QUICK_ATTACK = 1,
DASH = 2;
public float hover_offset;
public float hover_clock;
public Vector2f base_position;
public Vector2f position;
public Vector2f velocity;
private static PlayerShader shader;
private static Vao vao;
private Texture body;
private Texture[] eyes;
private Texture particle;
private int expression;
private float eye_rotation;
private float direction;
private float rate_bias;
private float spawn_trigger;
private float position_bias;
private float target_interp_clock;
private float particle_spawn_offset;
private Vector2f particle_base_target;
private Vector2f particle_target;
private ArrayList<Particle> particles;
private int performing_action;
private float action_clock;
private float action_progress;
private boolean cancel_action;
private float quick_attack_rotation;
private float vertical_offset;
private Vector2f quick_attack_direction;
private Vector2f dash_direction;
public Player() {
if (vao == null) {
shader = new PlayerShader();
vao = new Vao(
new float[] {
0.5f, -0.5f, 0, // Top left
0.5f, 0.5f, 0, // Bottom left
-0.5f, 0.5f, 0, // Bottom right
-0.5f, -0.5f, 0 // Top right
},
new int[] {
0, 1, 3,
1, 2, 3
}
);
vao.addAttrib(
new float[] {
1, 0,
1, 1,
0, 1,
0, 0
},
2
);
}
body = new Texture("res/img/player/body.png");
eyes = new Texture[] {
new Texture("res/img/player/normal_eyes.png"),
new Texture("res/img/player/three_smoaks_eyes.png"),
new Texture("res/img/player/confused_eyes.png"),
new Texture("res/img/player/squint_eyes.png")
};
particle = new Texture("res/img/player/particle.png");
hover_offset = 0.0f;
base_position = new Vector2f(5 * Map.TILE_DIMS + PLAYER_DIMS * 0.5f, 8 * Map.TILE_DIMS);
position = new Vector2f(base_position);
velocity = new Vector2f();
expression = EXPR_NORMAL;
direction = 1.0f;
particles = new ArrayList<>();
rate_bias = 1.0f;
spawn_trigger = 0.0f;
target_interp_clock = 1.0f;
particle_spawn_offset = 0.0f;
particle_base_target = new Vector2f(-PLAYER_DIMS, -PLAYER_DIMS * 1.5f);
particle_target = new Vector2f(particle_base_target);
}
public float rescale(float progress, float start, float end) {
return (progress - start) / (end - start);
}
public void move(Window window) {
final float RUN_VELOCITY = Map.TILE_DIMS * 3.5f;
final float WALK_VELOCITY = Map.TILE_DIMS * 1.5f;
final float QUICK_ATTACK_VELOCITY = Map.TILE_DIMS * 0.5f;
final float VERTICAL_VELOCITY_SCALAR = 0.75f;
float target_velocity = RUN_VELOCITY;
if (window.keyPressed(GLFW.GLFW_KEY_LEFT_CONTROL) >= Window.BUTTON_PRESSED || window.controllerButtonPressed(GLFW_GAMEPAD_BUTTON_B) >= Window.BUTTON_PRESSED) {
target_velocity = WALK_VELOCITY;
}
if (performing_action == QUICK_ATTACK) {
target_velocity = QUICK_ATTACK_VELOCITY;
} else if (performing_action == DASH) {
target_velocity = 0.0f;
}
Vector2f input_velocity = new Vector2f(0);
if (window.joystick(GLFW_JOYSTICK_1)) {
input_velocity.x = window.getJoystickAxis(GLFW_GAMEPAD_AXIS_LEFT_X);
input_velocity.y = window.getJoystickAxis(GLFW_GAMEPAD_AXIS_LEFT_Y);
if (Math.abs(input_velocity.x) < 0.25f) { input_velocity.x = 0; }
if (Math.abs(input_velocity.y) < 0.25f) { input_velocity.y = 0; }
}
if (input_velocity.lengthSquared() == 0) {
if (window.keyPressed(GLFW.GLFW_KEY_A) >= Window.BUTTON_PRESSED) {
input_velocity.x -= 1;
}
if (window.keyPressed(GLFW.GLFW_KEY_D) >= Window.BUTTON_PRESSED) {
input_velocity.x += 1;
}
if (window.keyPressed(GLFW.GLFW_KEY_W) >= Window.BUTTON_PRESSED) {
input_velocity.y -= 1;
}
if (window.keyPressed(GLFW.GLFW_KEY_S) >= Window.BUTTON_PRESSED) {
input_velocity.y += 1;
}
}
if (input_velocity.lengthSquared() != 0) {
input_velocity.normalize(target_velocity);
input_velocity.y *= VERTICAL_VELOCITY_SCALAR;
}
if (action_clock < 0.0f) {
action_clock += Main.dtime;
} else if (performing_action == NO_ACTION) {
if (window.keyPressed(GLFW.GLFW_KEY_SPACE) == Window.BUTTON_PRESSED || window.controllerButtonPressed(GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER) == Window.BUTTON_PRESSED) {
performing_action = QUICK_ATTACK;
action_clock = 0.0f;
if (input_velocity.lengthSquared() != 0) {
quick_attack_direction = input_velocity.normalize(new Vector2f());
} else {
quick_attack_direction = new Vector2f(direction, 0);
}
} else if (window.keyPressed(GLFW.GLFW_KEY_LEFT_SHIFT) == Window.BUTTON_PRESSED || window.controllerButtonPressed(GLFW_GAMEPAD_BUTTON_LEFT_BUMPER) == Window.BUTTON_PRESSED) {
performing_action = DASH;
action_clock = 0.0f;
if (input_velocity.lengthSquared() != 0) {
dash_direction = input_velocity.normalize(new Vector2f());
} else {
dash_direction = new Vector2f(direction, 0);
}
}
}
final float MAX_BIAS = 0.25f;
rate_bias = Math.max(MAX_BIAS, lerp(MAX_BIAS, 1.0f, 1 - (input_velocity.length() / RUN_VELOCITY)));
if (performing_action == DASH) {
action_clock += Main.dtime;
final float DASH_TIME = 0.15f;
final float DASH_COOLDOWN = 0.25f;
if (cancel_action) {
action_clock = DASH_TIME;
cancel_action = false;
}
if (action_clock >= DASH_TIME) {
performing_action = NO_ACTION;
action_clock = -DASH_COOLDOWN;
expression = EXPR_NORMAL;
rate_bias = 1.0f;
particle_spawn_offset = 0;
} else {
rate_bias = 0.01f;
particle_spawn_offset = dash_direction.angle(new Vector2f(direction, -1)) * 2.0f / (float) Math.PI;
final float DASH_SPEED = Map.TILE_DIMS * 15;
input_velocity.set(dash_direction).mul(DASH_SPEED);
}
}
if (performing_action == NO_ACTION && direction * input_velocity.x < 0) {
direction = -direction;
target_interp_clock = 0.0f;
}
velocity = input_velocity.mul((float) Main.dtime);
if (window.keyPressed(GLFW_KEY_Q) == Window.BUTTON_PRESSED) {
velocity.x = -400;
velocity.y = 0;
}
}
public void proc_collision() {}
private float lerp(float start, float end, float lerp) {
return start + (end - start) * lerp;
}
public void update(Camera camera) {
camera.setCenter(base_position.x, base_position.y);
if (performing_action == NO_ACTION) {
hover_clock = (hover_clock + (float) Main.dtime) % HOVER_CYCLE_RATE;
hover_offset = (float) Math.sin(2 * Math.PI * hover_clock * (1 / HOVER_CYCLE_RATE)) * HOVER_FLUCTUATION * 0.5f;
}
position.set(base_position);
if (performing_action == QUICK_ATTACK) {
action_clock += Main.dtime;
final float QUICK_ATTACK_TIME = 0.5f;
final float QUICK_ATTACK_COOLDOWN = 0.1f;
if (cancel_action) {
action_clock = QUICK_ATTACK_TIME;
cancel_action = false;
}
if (action_clock >= QUICK_ATTACK_TIME) {
performing_action = NO_ACTION;
action_clock = -QUICK_ATTACK_COOLDOWN;
expression = EXPR_NORMAL;
}
action_progress = action_clock / QUICK_ATTACK_TIME;
vertical_offset = 0;
quick_attack_rotation = 0;
eye_rotation = 0;
particle_spawn_offset = 0;
particle_target.set(particle_base_target);
if (action_progress < 0.5f) {
float smooth = (1 - (float) Math.cos(Math.PI * 2 * rescale(action_progress, 0, 0.6f))) * 0.5f;
final float RECOIL_AMOUNT = PLAYER_DIMS * 0.4f;
vertical_offset = -smooth * RECOIL_AMOUNT;
position.y += vertical_offset;
}
if (action_progress > 0.25f) {
float progress = rescale(action_progress, 0.15f, 1);
quick_attack_rotation = (float) Math.PI * 2 * progress * direction;
eye_rotation = progress;
particle_spawn_offset = progress * 4 * -direction;
final float PARTICLE_BOOST = 2;
rate_bias = 0.1f;
float sin = (float) Math.sin(quick_attack_rotation);
float cos = (float) Math.cos(quick_attack_rotation);
particle_target.set(
cos * particle_base_target.x - sin * particle_base_target.y,
sin * particle_base_target.x + cos * particle_base_target.y
).mul(PARTICLE_BOOST);
final float ORTHOGONAL = PLAYER_DIMS * 0.5f;
final float PARALLEL = PLAYER_DIMS * 1.15f;
position
.add(quick_attack_direction.mul((1 - (float) Math.cos(quick_attack_rotation)) * PARALLEL, new Vector2f()))
.add(new Vector2f(-quick_attack_direction.y, quick_attack_direction.x).mul((float) Math.sin(quick_attack_rotation) * ORTHOGONAL));
}
}
//////// PARTICLE SYSTEM ////////
for (int i = 0; i < particles.size(); ++i) {
Particle p = particles.get(i);
p.clock += Main.dtime;
if (p.clock >= p.kill_hour) {
particles.remove(i);
--i;
} else {
final float PARTICLE_MIN_DIMS = 16.0f;
final float PARTICLE_MAX_DIMS = 28.0f;
float interp = p.clock / p.kill_hour;
p.dimensions = lerp(PARTICLE_MAX_DIMS, PARTICLE_MIN_DIMS, interp);
p.position.add(p.velocity.mul((float) Main.dtime, new Vector2f()));
}
}
final float TURN_RATE = 0.7f;
target_interp_clock = Math.min(target_interp_clock + (float) Main.dtime / TURN_RATE, 1);
spawn_trigger -= Main.dtime;
if (spawn_trigger < 0) {
Particle p = new Particle();
final float PARTICLE_MIN_LIFETIME = 0.5f;
final float PARTICLE_MAX_LIFETIME = 1.0f;
p.kill_hour = lerp(PARTICLE_MIN_LIFETIME, PARTICLE_MAX_LIFETIME, (float) Math.random());
Vector2f spawn_position = new Vector2f(position).add(0, hover_offset);
position_bias = (4.5f + direction * 0.5f + (float) Math.random() * 2 - 1 + particle_spawn_offset) % 4;
if (0 <= position_bias && position_bias < 1) {
spawn_position.add(
-PLAYER_DIMS * 0.4f * (position_bias * 2.0f - 1.0f),
-PLAYER_DIMS * 0.4f
);
} else if (1 <= position_bias && position_bias < 2) {
position_bias -= 1;
spawn_position.add(
-PLAYER_DIMS * 0.4f,
PLAYER_DIMS * 0.4f * (position_bias * 2.0f - 1.0f)
);
} else if (2 <= position_bias && position_bias < 3) {
position_bias -= 2;
spawn_position.add(
PLAYER_DIMS * 0.4f * (position_bias * 2.0f - 1.0f),
PLAYER_DIMS * 0.4f
);
} else {
position_bias -= 3;
spawn_position.add(
PLAYER_DIMS * 0.4f,
-PLAYER_DIMS * 0.4f * (position_bias * 2.0f - 1.0f)
);
}
p.position = spawn_position;
p.velocity = new Vector2f(particle_target).mul((2 * target_interp_clock - 1) * direction, 1).add(position).sub(spawn_position).add(velocity.mul(0.1f, new Vector2f()));
particles.add(p);
final float BASE_SPAWN_RATE = 0.15f;
spawn_trigger = BASE_SPAWN_RATE * rate_bias;
}
}
public void render(Camera camera) {
shader.enable();
particle.bind();
for (int i = 0; i < particles.size(); ++i) {
Particle p = particles.get(i);
shader.setMVP(
camera
.getMatrix()
.translate(
p.position.x,
p.position.y,
0
)
.scaleXY(
p.dimensions,
p.dimensions
)
);
vao.render();
}
shader.setMVP(
camera
.getMatrix()
.translate(
position.x,
position.y + hover_offset,
0
)
.scaleXY(direction * PLAYER_DIMS, PLAYER_DIMS));
shader.setRotation(0);
body.bind();
vao.render();
shader.setRotation(eye_rotation);
eyes[expression].bind();
vao.render();
}
}