Created
March 26, 2013 10:05
-
-
Save progschj/5244296 to your computer and use it in GitHub Desktop.
A simple multiplayer "twin stick shooter" done during 3h hours at a gamespace.ch meet and make. I started with this: https://github.com/progschj/OpenGL-Examples/blob/master/02indexed_vbo.cpp and just added stuff from there. It has only been tested with two wireless xbox360 controllers.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 3h Twin stick shooter | |
* Autor: Jakob Progsch | |
*/ | |
#include <GL3/gl3w.h> | |
#include <GL/glfw.h> | |
#include <iostream> | |
#include <string> | |
#include <vector> | |
#include <stack> | |
#include <algorithm> | |
#include <cmath> | |
bool running; | |
// window close callback function | |
int closedWindow() | |
{ | |
running = false; | |
return GL_TRUE; | |
} | |
// helper to check and display for shader compiler errors | |
bool check_shader_compile_status(GLuint obj) | |
{ | |
GLint status; | |
glGetShaderiv(obj, GL_COMPILE_STATUS, &status); | |
if(status == GL_FALSE) | |
{ | |
GLint length; | |
glGetShaderiv(obj, GL_INFO_LOG_LENGTH, &length); | |
std::vector<char> log(length); | |
glGetShaderInfoLog(obj, length, &length, &log[0]); | |
std::cerr << &log[0]; | |
return false; | |
} | |
return true; | |
} | |
// helper to check and display for shader linker error | |
bool check_program_link_status(GLuint obj) | |
{ | |
GLint status; | |
glGetProgramiv(obj, GL_LINK_STATUS, &status); | |
if(status == GL_FALSE) | |
{ | |
GLint length; | |
glGetProgramiv(obj, GL_INFO_LOG_LENGTH, &length); | |
std::vector<char> log(length); | |
glGetProgramInfoLog(obj, length, &length, &log[0]); | |
std::cerr << &log[0]; | |
return false; | |
} | |
return true; | |
} | |
template<class T> | |
T clamp(T v, T min, T max) | |
{ | |
return std::max(std::min(v, max), min); | |
} | |
int main() | |
{ | |
int width = 1920; | |
int height = 1080; | |
if(glfwInit() == GL_FALSE) | |
{ | |
std::cerr << "failed to init GLFW" << std::endl; | |
return 1; | |
} | |
// sadly glew doesn't play nice with core profiles... | |
glfwOpenWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); | |
glfwOpenWindowHint(GLFW_OPENGL_VERSION_MAJOR, 3); | |
glfwOpenWindowHint(GLFW_OPENGL_VERSION_MINOR, 3); | |
glfwSwapInterval(1); | |
// create a window | |
if(glfwOpenWindow(width, height, 0, 0, 0, 8, 24, 8, GLFW_FULLSCREEN) == GL_FALSE) | |
{ | |
std::cerr << "failed to open window" << std::endl; | |
glfwTerminate(); | |
return 1; | |
} | |
// setup windows close callback | |
glfwSetWindowCloseCallback(closedWindow); | |
if (gl3wInit()) | |
{ | |
std::cerr << "failed to init GL3W" << std::endl; | |
glfwCloseWindow(); | |
glfwTerminate(); | |
return 1; | |
} | |
// shader source code | |
std::string vertex_source = | |
"#version 330\n" | |
"uniform vec4 size = vec4(16,9,1./16,1./9);\n" | |
"layout(location = 0) in vec4 vposition;\n" | |
"layout(location = 1) in vec2 pos;\n" | |
"layout(location = 2) in float radius;\n" | |
"layout(location = 3) in vec4 color;\n" | |
"out vec4 fcolor;\n" | |
"out vec2 relpos;\n" | |
"void main() {\n" | |
" fcolor = color;\n" | |
" relpos = vposition.xy;\n" | |
" gl_Position = vec4((radius*vposition.xy + pos)*size.zw,0,1);\n" | |
"}\n"; | |
std::string fragment_source = | |
"#version 330\n" | |
"in vec4 fcolor;\n" | |
"in vec2 relpos;\n" | |
"layout(location = 0) out vec4 FragColor;\n" | |
"void main() {\n" | |
" FragColor = fcolor * smoothstep(0, 1, 1-dot(relpos, relpos)*4);\n" | |
"}\n"; | |
// program and shader handles | |
GLuint shader_program, vertex_shader, fragment_shader; | |
// we need these to properly pass the strings | |
const char *source; | |
int length; | |
// create and compiler vertex shader | |
vertex_shader = glCreateShader(GL_VERTEX_SHADER); | |
source = vertex_source.c_str(); | |
length = vertex_source.size(); | |
glShaderSource(vertex_shader, 1, &source, &length); | |
glCompileShader(vertex_shader); | |
if(!check_shader_compile_status(vertex_shader)) | |
{ | |
return 1; | |
} | |
// create and compiler fragment shader | |
fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); | |
source = fragment_source.c_str(); | |
length = fragment_source.size(); | |
glShaderSource(fragment_shader, 1, &source, &length); | |
glCompileShader(fragment_shader); | |
if(!check_shader_compile_status(fragment_shader)) | |
{ | |
return 1; | |
} | |
// create program | |
shader_program = glCreateProgram(); | |
// attach shaders | |
glAttachShader(shader_program, vertex_shader); | |
glAttachShader(shader_program, fragment_shader); | |
// link the program and check for errors | |
glLinkProgram(shader_program); | |
check_program_link_status(shader_program); | |
GLuint size_uniform = glGetUniformLocation(shader_program, "size"); | |
// vao and vbo handle | |
GLuint playervao, playervbo, bulletvao, bulletvbo, vbo, ibo; | |
// generate and bind the vao | |
glGenVertexArrays(1, &playervao); | |
glBindVertexArray(playervao); | |
// generate and bind the vertex buffer object | |
glGenBuffers(1, &vbo); | |
glBindBuffer(GL_ARRAY_BUFFER, vbo); | |
// data for a fullscreen quad | |
GLfloat vertexData[] = { | |
// X Y Z | |
0.5f, 0.5f, 0.0f, | |
-0.5f, 0.5f, 0.0f, | |
0.5f,-0.5f, 0.0f, | |
-0.5f,-0.5f, 0.0f, | |
}; | |
// fill with data | |
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat)*4*3, vertexData, GL_STATIC_DRAW); | |
// set up generic attrib pointers | |
glEnableVertexAttribArray(0); | |
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(GLfloat), (char*)0 + 0*sizeof(GLfloat)); | |
// generate and bind the index buffer object | |
glGenBuffers(1, &ibo); | |
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo); | |
GLuint indexData[] = { | |
0,1,2, // first triangle | |
2,1,3, // second triangle | |
}; | |
// fill with data | |
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint)*2*3, indexData, GL_STATIC_DRAW); | |
glGenBuffers(1, &playervbo); | |
glBindBuffer(GL_ARRAY_BUFFER, playervbo); | |
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat)*4*6, 0, GL_DYNAMIC_DRAW); | |
glEnableVertexAttribArray(1); | |
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 6*sizeof(GLfloat), (char*)0 + 0*sizeof(GLfloat)); | |
glVertexAttribDivisor(1, 1); | |
glEnableVertexAttribArray(2); | |
glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, 6*sizeof(GLfloat), (char*)0 + 2*sizeof(GLfloat)); | |
glVertexAttribDivisor(2, 1); | |
glEnableVertexAttribArray(3); | |
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 6*sizeof(GLfloat), (char*)0 + 3*sizeof(GLfloat)); | |
glVertexAttribDivisor(3, 1); | |
glGenVertexArrays(1, &bulletvao); | |
glBindVertexArray(bulletvao); | |
glBindBuffer(GL_ARRAY_BUFFER, vbo); | |
// set up generic attrib pointers | |
glEnableVertexAttribArray(0); | |
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(GLfloat), (char*)0 + 0*sizeof(GLfloat)); | |
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo); | |
glGenBuffers(1, &bulletvbo); | |
glBindBuffer(GL_ARRAY_BUFFER, bulletvbo); | |
const int max_bullets = 16*1024; | |
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat)*max_bullets*6, 0, GL_DYNAMIC_DRAW); | |
glEnableVertexAttribArray(1); | |
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 6*sizeof(GLfloat), (char*)0 + 0*sizeof(GLfloat)); | |
glVertexAttribDivisor(1, 1); | |
glEnableVertexAttribArray(2); | |
glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, 6*sizeof(GLfloat), (char*)0 + 2*sizeof(GLfloat)); | |
glVertexAttribDivisor(2, 1); | |
glEnableVertexAttribArray(3); | |
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 6*sizeof(GLfloat), (char*)0 + 3*sizeof(GLfloat)); | |
glVertexAttribDivisor(3, 1); | |
// "unbind" vao | |
glBindVertexArray(0); | |
float playerpositions[8] = {0,0,0,0,0,0,0,0}; | |
float playerradius[4] = {3, 3, 3, 3}; | |
float playercolor[12] = {1,0,0, 0,1,0, 0,0,1, 1,1,0}; | |
float playercooldown[4] = {0,0,0,0}; | |
float playerflash[4] = {0,0,0,0}; | |
int bullets = 0; | |
std::stack<size_t> freebullets; | |
for(size_t i = 0;i<max_bullets;++i) | |
freebullets.push(max_bullets-i-1); | |
std::vector<float> bulletpositions(2*max_bullets); | |
std::vector<float> bulletradius(max_bullets); | |
std::vector<float> bulletcolor(3*max_bullets); | |
std::vector<float> bulletvelocity(2*max_bullets); | |
std::vector<float> bulletage(max_bullets, 1000); | |
std::vector<int> bulletowner(max_bullets); | |
float threshold = 0.15f; | |
glBlendFunc(GL_ONE, GL_ONE); | |
glEnable(GL_BLEND); | |
double t = glfwGetTime(); | |
running = true; | |
while(running) | |
{ | |
// terminate on excape | |
if(glfwGetKey(GLFW_KEY_ESC)) | |
{ | |
running = false; | |
} | |
double newt = glfwGetTime(); | |
float dt = newt - t; | |
t = newt; | |
// clear first | |
glClear(GL_COLOR_BUFFER_BIT); | |
// use the shader program | |
glUseProgram(shader_program); | |
float axes[8]; | |
unsigned char buttons[15]; | |
for(int i = 0;i<2;++i) | |
{ | |
playercooldown[i] = std::max(0.0f, playercooldown[i]-dt); | |
playerflash[i] *= std::pow(0.3, dt); | |
glfwGetJoystickPos(i, axes, 8); | |
glfwGetJoystickButtons(i, buttons, 15); | |
if(axes[5] < 0.5 && playercooldown[i] == 0 && playerradius[i] > 1) | |
{ | |
size_t bulletindex = freebullets.top(); | |
freebullets.pop(); | |
playercooldown[i] = 0.05f; | |
bulletpositions[2*bulletindex + 0] = playerpositions[2*i + 0]; | |
bulletpositions[2*bulletindex + 1] = playerpositions[2*i + 1]; | |
bulletradius[bulletindex + 0] = 0.5; | |
bulletcolor[3*bulletindex + 0] = playercolor[3*i + 0] + 0.5; | |
bulletcolor[3*bulletindex + 1] = playercolor[3*i + 1] + 0.5; | |
bulletcolor[3*bulletindex + 2] = playercolor[3*i + 2] + 0.5; | |
bulletvelocity[2*bulletindex + 0] = -30.0f*(axes[3]-clamp(axes[3], -threshold, threshold)); | |
bulletvelocity[2*bulletindex + 1] = -30.0f*(axes[4]-clamp(axes[4], -threshold, threshold)); | |
bulletage[bulletindex + 0] = 0; | |
bulletowner[bulletindex + 0] = i; | |
bullets++; | |
} | |
playerpositions[2*i+0] += 0.07f*(axes[0]-clamp(axes[0], -threshold, threshold)); | |
playerpositions[2*i+1] += 0.07f*(axes[1]-clamp(axes[1], -threshold, threshold)); | |
playerpositions[2*i+0] = clamp(playerpositions[2*i+0], -16.0f+0.5f, 16.0f-0.5f); | |
playerpositions[2*i+1] = clamp(playerpositions[2*i+1], -9.0f+0.5f, 9.0f-0.5f); | |
} | |
glBindVertexArray(playervao); | |
glBindBuffer(GL_ARRAY_BUFFER, playervbo); | |
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat)*4*6, 0, GL_DYNAMIC_DRAW); | |
// map the buffer | |
float *mapped = | |
reinterpret_cast<float*>( | |
glMapBufferRange(GL_ARRAY_BUFFER, 0, | |
sizeof(GLfloat)*4*6, | |
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | |
) | |
); | |
for(int i = 0;i<2;++i) | |
{ | |
mapped[6*i + 0] = playerpositions[2*i + 0]; | |
mapped[6*i + 1] = playerpositions[2*i + 1]; | |
mapped[6*i + 2] = playerradius[i]; | |
mapped[6*i + 3] = playercolor[3*i + 0] + playerflash[i]; | |
mapped[6*i + 4] = playercolor[3*i + 1] + playerflash[i]; | |
mapped[6*i + 5] = playercolor[3*i + 2] + playerflash[i]; | |
} | |
// unmap the buffer | |
glUnmapBuffer(GL_ARRAY_BUFFER); | |
glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0, 2); | |
glBindVertexArray(bulletvao); | |
glBindBuffer(GL_ARRAY_BUFFER, bulletvbo); | |
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat)*max_bullets*6, 0, GL_DYNAMIC_DRAW); | |
// map the buffer | |
mapped = | |
reinterpret_cast<float*>( | |
glMapBufferRange(GL_ARRAY_BUFFER, 0, | |
sizeof(GLfloat)*max_bullets*6, | |
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | |
) | |
); | |
int j = 0; | |
float bullettimeout = 5; | |
for(int i = 0;i<max_bullets;++i) | |
{ | |
if(bulletage[i] > bullettimeout) | |
continue; | |
bulletpositions[2*i + 0] += dt*bulletvelocity[2*i + 0]; | |
bulletpositions[2*i + 1] += dt*bulletvelocity[2*i + 1]; | |
bulletage[i] += dt; | |
for(int k = 0;k<2;++k) | |
{ | |
if(bulletowner[i]==k) | |
continue; | |
float diffx = bulletpositions[2*i + 0]-playerpositions[2*k + 0]; | |
float diffy = bulletpositions[2*i + 1]-playerpositions[2*k + 1]; | |
float dist = std::sqrt(diffx*diffx+diffy*diffy); | |
if(2*dist < playerradius[k]) | |
{ | |
playerradius[k] -= 0.1; | |
playerradius[bulletowner[i]] += 0.1; | |
playerflash[k] = 1; | |
bulletage[i] = 100; | |
} | |
} | |
if(bulletage[i] > bullettimeout) | |
{ | |
freebullets.push(i); | |
bullets--; | |
continue; | |
} | |
mapped[6*j + 0] = bulletpositions[2*i + 0]; | |
mapped[6*j + 1] = bulletpositions[2*i + 1]; | |
mapped[6*j + 2] = bulletradius[i]; | |
mapped[6*j + 3] = bulletcolor[3*i + 0]; | |
mapped[6*j + 4] = bulletcolor[3*i + 1]; | |
mapped[6*j + 5] = bulletcolor[3*i + 2]; | |
j++; | |
} | |
glUnmapBuffer(GL_ARRAY_BUFFER); | |
glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0, bullets); | |
// check for errors | |
GLenum error = glGetError(); | |
if(error != GL_NO_ERROR) | |
{ | |
std::cerr << gluErrorString(error); | |
running = false; | |
} | |
// finally swap buffers | |
glfwSwapBuffers(); | |
} | |
// delete the created objects | |
glDeleteVertexArrays(1, &playervao); | |
glDeleteVertexArrays(1, &bulletvao); | |
glDeleteBuffers(1, &playervbo); | |
glDeleteBuffers(1, &bulletvbo); | |
glDeleteBuffers(1, &vbo); | |
glDeleteBuffers(1, &ibo); | |
glDetachShader(shader_program, vertex_shader); | |
glDetachShader(shader_program, fragment_shader); | |
glDeleteShader(vertex_shader); | |
glDeleteShader(fragment_shader); | |
glDeleteProgram(shader_program); | |
glfwCloseWindow(); | |
glfwTerminate(); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment