Skip to content

Instantly share code, notes, and snippets.

@progschj
Created March 26, 2013 10:05
Show Gist options
  • Save progschj/5244296 to your computer and use it in GitHub Desktop.
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.
/* 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