Hi there,
I'm currently struggling to get my camera follow my player. Currently I can move my player around, but he can move outside of
the screen which is not what I want (thought I also don't want the world to be limited by screen size etc.).
I've already searched through the internet, but it seems like everyone is either using deprecated OpenGL stuff or 3D perspective cameras. :/
Note: I'm using an 2D orthogonal camera!
I'm also wondering how to move the camera code out into a seperate camera class.
Below you can find a minimal compileable example:
moveable player:
public class TestPlayer {
private int x = 0, y = 0;
private float size = 1.f;
private int matLocation = 0;
private int shaderProgram = 0;
private IntBuffer vertexArray = BufferUtils.createIntBuffer(1);
private IntBuffer vertexBuffer = BufferUtils.createIntBuffer(1);
private IntBuffer indexBuffer = BufferUtils.createIntBuffer(1);
private String[] vertexShaderSource = {
"#version 450 core\n",
"\n",
"layout (location = 0) in vec3 position;\n",
"\n",
"uniform mat4 u_MVP;\n",
"\n",
"void main() {\n",
" gl_Position = u_MVP * vec4(position, 1);\n",
"}\n"
};
private String[] fragmentShaderSource = {
"#version 450 core\n",
"\n",
"layout (location = 0) out vec4 colour;\n",
"layout (location = 1) uniform vec4 u_Colour;\n",
"\n",
"void main() {\n",
" colour = u_Colour;\n",
"}"
};
public TestPlayer(Vector3f position, Vector3f lookat) {
this.updatePositions();
this.shaderProgram = this.createShader();
glUseProgram(this.shaderProgram);
matLocation = glGetUniformLocation(this.shaderProgram, "u_MVP");
glUseProgram(0);
glBindVertexArray(0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
private void updatePositions() {
if(vertexArray.hasRemaining()) {
glDeleteVertexArrays(this.vertexArray);
vertexArray.clear();
}
if(indexBuffer.hasRemaining()) {
glDeleteBuffers(this.indexBuffer);
indexBuffer.clear();
}
if(vertexBuffer.hasRemaining()) {
glDeleteBuffers(this.vertexBuffer);
vertexBuffer.clear();
}
float[] positions = {
x-size, y-size,
x , y+size,
x+size, y-size
};
int[] indices = {
0, 1, 2
};
glGenVertexArrays(this.vertexArray);
FloatBuffer vertexData = BufferUtils.createFloatBuffer(3 * 2);
vertexData.put(positions);
vertexData.flip();
glGenBuffers(this.vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, this.vertexBuffer.get(0));
glBufferData(GL_ARRAY_BUFFER, vertexData, GL_STATIC_DRAW);
glBindVertexArray(this.vertexArray.get(0));
glBindBuffer(GL_ARRAY_BUFFER, this.vertexBuffer.get(0));
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, false, Float.BYTES * 2, 0);
IntBuffer indexData = BufferUtils.createIntBuffer(3);
indexData.put(indices);
indexData.flip();
glGenBuffers(this.indexBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this.indexBuffer.get(0));
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexData, GL_STATIC_DRAW);
}
private Vector3f position = new Vector3f(x, y, 0);
private Vector3f direction = new Vector3f(0, 0, -1);
private Vector3f up = new Vector3f(0, 1, 0);
private Vector3f tmp = new Vector3f();
private Matrix4f projection = new Matrix4f();
public static Matrix4f view = new Matrix4f();
private Matrix4f model = new Matrix4f().translate(position);
private Matrix4f modelView = new Matrix4f();
private FloatBuffer buffer = BufferUtils.createFloatBuffer(4 * 4);
public void update() {
Matrix4f tmpM = new Matrix4f().identity();
projection.setOrtho2D(-16.f, 16.f, -9.f, 9.f);
view.setLookAt(position, tmp.set(position).add(direction), up);
view.mul(model, tmpM);
projection.mul(tmpM, modelView);
}
public void keyUpdate(int key) {
if(key == GLFW.GLFW_KEY_W)
position.sub(0, 1, 0);
if(key == GLFW.GLFW_KEY_S)
position.add(0, 1, 0);
if(key == GLFW.GLFW_KEY_A)
position.add(1, 0, 0);
if(key == GLFW.GLFW_KEY_D)
position.sub(1, 0, 0);
}
public void render(double currentTime) {
this.updatePositions();
glUseProgram(this.shaderProgram);
glBindVertexArray(this.vertexArray.get(0));
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this.indexBuffer.get(0));
// colour
glUniform4f(1, 0.f, .8f, 1.f, 1.f);
glUniformMatrix4fv(matLocation, false, modelView.get(buffer));
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, 0);
}
public void dispose() {
glBindVertexArray(0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDeleteVertexArrays(this.vertexArray);
glDeleteBuffers(this.indexBuffer);
glDeleteBuffers(this.vertexBuffer);
glUseProgram(0);
glDeleteProgram(this.shaderProgram);
}
private int createShader() {
int program = glCreateProgram();
int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, vertexShaderSource);
glCompileShader(vertexShader);
if(glGetShaderi(vertexShader, GL_COMPILE_STATUS) == GL_FALSE) {
System.err.println("ERROR: Compiling vertex shader");
System.err.println(glGetShaderInfoLog(vertexShader));
glDeleteShader(vertexShader);
return -1;
}
int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, fragmentShaderSource);
glCompileShader(fragmentShader);
if(glGetShaderi(fragmentShader, GL_COMPILE_STATUS) == GL_FALSE) {
System.err.println("ERROR: Compiling fragment shader");
System.err.println(glGetShaderInfoLog(fragmentShader));
glDeleteShader(fragmentShader);
return -1;
}
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
glLinkProgram(program);
glValidateProgram(program);
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
return program;
}
}
Static object:
public class TestPlayer2 {
private int x = 3, y = 3;
private float size = 1.f;
private int shaderProgram = 0;
private IntBuffer vertexArray = BufferUtils.createIntBuffer(1);
private IntBuffer vertexBuffer = BufferUtils.createIntBuffer(1);
private IntBuffer indexBuffer = BufferUtils.createIntBuffer(1);
private int matLocation = 0;
private Vector3f position = new Vector3f(x, y, 0);
private Vector3f direction = new Vector3f(0, 0, -1);
private Vector3f up = new Vector3f(0, 1, 0);
private Vector3f tmp = new Vector3f();
private Matrix4f projection = new Matrix4f();
public static Matrix4f view = new Matrix4f();
private Matrix4f model = new Matrix4f().translate(position);
private Matrix4f modelView = new Matrix4f();
private FloatBuffer buffer = BufferUtils.createFloatBuffer(4 * 4);
private String[] vertexShaderSource = {
"#version 450 core\n",
"\n",
"layout (location = 0) in vec4 position;\n",
"\n",
"uniform mat4 u_MVP;\n",
"\n",
"void main() {\n",
" gl_Position = u_MVP * position;\n",
"}\n"
};
private String[] fragmentShaderSource = {
"#version 450 core\n",
"\n",
"layout (location = 0) out vec4 colour;\n",
"layout (location = 1) uniform vec4 u_Colour;\n",
"\n",
"void main() {\n",
" colour = u_Colour;\n",
"}"
};
public TestPlayer2() {
float[] positions = {
x-size, y-size,
x , y+size,
x+size, y-size
};
int[] indices = {
0, 1, 2
};
glGenVertexArrays(this.vertexArray);
FloatBuffer vertexData = BufferUtils.createFloatBuffer(3 * 2);
vertexData.put(positions);
vertexData.flip();
glGenBuffers(this.vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, this.vertexBuffer.get(0));
glBufferData(GL_ARRAY_BUFFER, vertexData, GL_STATIC_DRAW);
glBindVertexArray(this.vertexArray.get(0));
glBindBuffer(GL_ARRAY_BUFFER, this.vertexBuffer.get(0));
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, false, Float.BYTES * 2, 0);
IntBuffer indexData = BufferUtils.createIntBuffer(3);
indexData.put(indices);
indexData.flip();
glGenBuffers(this.indexBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this.indexBuffer.get(0));
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexData, GL_STATIC_DRAW);
this.shaderProgram = this.createShader();
glUseProgram(this.shaderProgram);
matLocation = glGetUniformLocation(this.shaderProgram, "u_MVP");
glUseProgram(0);
glBindVertexArray(0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
private int createShader() {
int program = glCreateProgram();
int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, vertexShaderSource);
glCompileShader(vertexShader);
if(glGetShaderi(vertexShader, GL_COMPILE_STATUS) == GL_FALSE) {
System.err.println("ERROR: Compiling vertex shader");
System.err.println(glGetShaderInfoLog(vertexShader));
glDeleteShader(vertexShader);
return -1;
}
int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, fragmentShaderSource);
glCompileShader(fragmentShader);
if(glGetShaderi(fragmentShader, GL_COMPILE_STATUS) == GL_FALSE) {
System.err.println("ERROR: Compiling fragment shader");
System.err.println(glGetShaderInfoLog(fragmentShader));
glDeleteShader(fragmentShader);
return -1;
}
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
glLinkProgram(program);
glValidateProgram(program);
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
return program;
}
public void update() {
// Nothing in here, static entity
Matrix4f tmpM = new Matrix4f().identity();
projection.setOrtho2D(-16.f, 16.f, -9.f, 9.f);
view.setLookAt(position, tmp.set(position).add(direction), up);
view.mul(model, tmpM);
projection.mul(tmpM, modelView);
}
public void render(double currentTime) {
glUseProgram(this.shaderProgram);
glBindVertexArray(this.vertexArray.get(0));
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this.indexBuffer.get(0));
glUniform4f(1, 8.f, .8f, 1.f, 1.f);
glUniformMatrix4fv(matLocation, false, modelView.get(buffer));
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, 0);
}
public void dispose() {
glBindVertexArray(0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDeleteVertexArrays(this.vertexArray);
glDeleteBuffers(this.indexBuffer);
glDeleteBuffers(this.vertexBuffer);
glUseProgram(0);
glDeleteProgram(this.shaderProgram);
}
}
Main class:
public class HelloWorld {
// The window handle
private long window;
private TestPlayer player;
private TestPlayer2 player2;
private Callback glErrorCallback;
private static final IntBuffer SCREEN_WIDTH = BufferUtils.createIntBuffer(1);
private static final IntBuffer SCREEN_HEIGHT = BufferUtils.createIntBuffer(1);
public void run() {
// Output in my case: Hello LWJGL 3.2.0 build 12!
System.out.println("Hello LWJGL " + Version.getVersion() + "!");
init();
loop();
if(glErrorCallback != null)
this.glErrorCallback.free();
// Free the window callbacks and destroy the window
glfwFreeCallbacks(window);
glfwDestroyWindow(window);
GL.setCapabilities(null);
// Terminate GLFW and free the error callback
glfwTerminate();
glfwSetErrorCallback(null).free();
}
private void init() {
GLFWErrorCallback.createPrint(System.err).set();
if ( !glfwInit() )
throw new IllegalStateException("Unable to initialize GLFW");
// Configure GLFW
glfwDefaultWindowHints();
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE);
// Create the window
window = glfwCreateWindow(1280, 720, "Hello World!", NULL, NULL);
if ( window == NULL )
throw new RuntimeException("Failed to create the GLFW window");
glfwSetKeyCallback(window, (window, key, scancode, action, mods) -> {
if ( key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE )
glfwSetWindowShouldClose(window, true);
if ( action == GLFW.GLFW_PRESS)
if(player != null)
player.keyUpdate(key);
});
// 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
glfwMakeContextCurrent(window);
// Enable v-sync
glfwSwapInterval(1);
// Make the window visible
glfwShowWindow(window);
}
private void loop() {
GL.createCapabilities();
glfwGetFramebufferSize(this.window, SCREEN_WIDTH, SCREEN_HEIGHT);
glViewport(0, 0, SCREEN_WIDTH.get(), SCREEN_HEIGHT.get());
this.glErrorCallback = GLUtil.setupDebugMessageCallback();
// Set the clear color
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
player = new TestPlayer(new Vector3f(0, 0, 0), new Vector3f(0, 0, 0));
player2 = new TestPlayer2();
// Run the rendering loop until the user has attempted to close
// the window or has pressed the ESCAPE key.
while ( !glfwWindowShouldClose(window) ) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear the framebuffer
// normally, update would not be called as often as render
player.update();
player2.update();
player2.render(glfwGetTime());
// the player with the camera
player.render(glfwGetTime());
glfwSwapBuffers(window); // swap the color buffers
// Poll for window events. The key callback above will only be
// invoked during this call.
glfwPollEvents();
}
player.dispose();
player2.dispose();
}
public static void main(String[] args) {
new HelloWorld().run();
}
}
Thx in advance.
ShadowDragon