Tutorials

Tutorials for getting started with programming on Linux

Shoot Bullets

Simple Astroids clone

Published
Liked the article? Share it!

Next we will implement shooting bullets. We define a specific number of bullets of which can be filled. If all bullets are active, the user will have to wait until they “fizzle out” before they can fire a new one. Bullets are set to the position and rotation of the player of when they are initialized and then travel at a fixed rate from that point.

main.c
#include <epoxy/gl.h> #include <epoxy/glx.h> #include <gtk/gtk.h> #include <math.h> #include "DashGL/dashgl.h" static void on_realize(GtkGLArea *area); static void on_render(GtkGLArea *area, GdkGLContext *context); static gboolean on_idle(gpointer data); static gboolean on_keydown(GtkWidget *widget, GdkEventKey *event); static gboolean on_keyup(GtkWidget *widget, GdkEventKey *event); #define NUM_BULLETS 10 #define WIDTH 640.0f #define HEIGHT 480.0f GLuint program; GLuint vao; GLint attribute_texcoord, attribute_coord2d; GLint uniform_mytexture, uniform_mvp; struct bullet { vec3 pos; GLfloat rot; gboolean active; int ticks; }; struct { GLuint vbo[2]; GLuint tex[2]; vec3 pos; mat4 mvp; gboolean left; gboolean right; gboolean up; gboolean space; GLfloat rot; GLfloat dx, dy, max; float bullet_speed; int num_ticks; struct bullet bullets[NUM_BULLETS]; } player; int main(int argc, char *argv[]) { GtkWidget *window; GtkWidget *glArea; gtk_init(&argc, &argv); // Initialize Window window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(window), "Astroids Tutorial"); gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); gtk_window_set_default_size(GTK_WINDOW(window), 640, 480); gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_UTILITY); g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL); g_signal_connect(window, "key-press-event", G_CALLBACK(on_keydown), NULL); g_signal_connect(window, "key-release-event", G_CALLBACK(on_keyup), NULL); // Initialize GTK GL Area glArea = gtk_gl_area_new(); gtk_widget_set_vexpand(glArea, TRUE); gtk_widget_set_hexpand(glArea, TRUE); g_signal_connect(glArea, "realize", G_CALLBACK(on_realize), NULL); g_signal_connect(glArea, "render", G_CALLBACK(on_render), NULL); gtk_container_add(GTK_CONTAINER(window), glArea); // Show widgets gtk_widget_show_all(window); gtk_main(); return 0; } static void on_realize(GtkGLArea *area) { // Debug Message int i; g_print("on realize\n"); gtk_gl_area_make_current(area); if(gtk_gl_area_get_error(area) != NULL) { fprintf(stderr, "Unknown error\n"); return; } const GLubyte *renderer = glGetString(GL_RENDER); const GLubyte *version = glGetString(GL_VERSION); printf("Renderer: %s\n", renderer); printf("OpenGL version supported %s\n", version); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glGenVertexArrays(1, &vao); glBindVertexArray(vao); GLfloat player_vertices[] = { -24.0, -24.0, 0.0, 0.0, -24.0, 24.0, 0.0, 1.0, 24.0, 24.0, 1.0, 1.0, -24.0, -24.0, 0.0, 0.0, 24.0, 24.0, 1.0, 1.0, 24.0, -24.0, 1.0, 0.0 }; glGenBuffers(1, &player.vbo[0]); glBindBuffer(GL_ARRAY_BUFFER, player.vbo[0]); glBufferData( GL_ARRAY_BUFFER, sizeof(player_vertices), player_vertices, GL_STATIC_DRAW ); GLfloat bullet_vertices[] = { -10.0, -10.0, 0.0, 0.0, -10.0, 10.0, 0.0, 1.0, 10.0, 10.0, 1.0, 1.0, -10.0, -10.0, 0.0, 0.0, 10.0, 10.0, 1.0, 1.0, 10.0, -10.0, 1.0, 0.0 }; glGenBuffers(1, &player.vbo[1]); glBindBuffer(GL_ARRAY_BUFFER, player.vbo[1]); glBufferData( GL_ARRAY_BUFFER, sizeof(bullet_vertices), bullet_vertices, GL_STATIC_DRAW ); player.pos[0] = WIDTH / 2.0f; player.pos[1] = HEIGHT / 2.0f; player.pos[2] = 0.0; player.left = FALSE; player.right = FALSE; player.up = FALSE; player.space = FALSE; mat4_translate(player.pos, player.mvp); player.tex[0] = shader_load_texture("sprites/player.png"); player.tex[1] = shader_load_texture("sprites/bullet.png"); player.dx = 0.0f; player.dy = 0.0f; player.max = 8.0f; player.bullet_speed = 6.0f; player.num_ticks = 70; for(i = 0; i const char *vs = "shader/vertex.glsl"; const char *fs = "shader/fragment.glsl"; program = shader_load_program(vs, fs); const char *attribute_name = "coord2d"; attribute_coord2d = glGetAttribLocation(program, attribute_name); if(attribute_coord2d == -1) { fprintf(stderr, "Could not bind attribute %s\n", attribute_name); return; } const char *uniform_name = "orthograph"; GLint uniform_ortho = glGetUniformLocation(program, uniform_name); if(uniform_ortho == -1) { fprintf(stderr, "Could not bind uniform %s\n", uniform_name); return; } uniform_name = "mvp"; uniform_mvp = glGetUniformLocation(program, uniform_name); if(uniform_mvp == -1) { fprintf(stderr, "Could not bind uniform %s\n", uniform_name); return; } glUseProgram(program); mat4 orthograph; mat4_orthagonal(WIDTH, HEIGHT, orthograph); glUniformMatrix4fv(uniform_ortho, 1, GL_FALSE, orthograph); g_timeout_add(20, on_idle, (void*)area); } static void on_render(GtkGLArea *area, GdkGLContext *context) { int i; mat4 pos, rot; gboolean draw_mirror = FALSE; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glUseProgram(program); glBindVertexArray(vao); glEnableVertexAttribArray(attribute_coord2d); glEnableVertexAttribArray(attribute_texcoord); // Draw bullet sprites glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, player.tex[1]); glUniform1i(uniform_mytexture, 0); for(i = 0; i WIDTH - 24.0f) { player.bullets[i].pos[0] -= WIDTH; draw_mirror = TRUE; } else if(player.bullets[i].pos[0] <24.0f) { player.bullets[i].pos[0] += WIDTH; draw_mirror = TRUE; } if(player.bullets[i].pos[1] > HEIGHT - 24.0f) { player.bullets[i].pos[1] -= HEIGHT; draw_mirror = TRUE; } else if(player.bullets[i].pos[1] <24.0f) { player.bullets[i].pos[1] += HEIGHT; draw_mirror = TRUE; } if(!draw_mirror) { continue; } mat4_translate(player.bullets[i].pos, pos); mat4_rotate_z(player.bullets[i].rot, rot); mat4_multiply(pos, rot, player.mvp); glUniformMatrix4fv(uniform_mvp, 1, GL_FALSE, player.mvp); glBindBuffer(GL_ARRAY_BUFFER, player.vbo[1]); glVertexAttribPointer( attribute_coord2d, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 4, 0 ); glVertexAttribPointer( attribute_texcoord, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 4, (void*)(sizeof(float) * 2) ); glDrawArrays(GL_TRIANGLES, 0, 6); } // Draw Player Sprite glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, player.tex[0]); glUniform1i(uniform_mytexture, 0); mat4_translate(player.pos, pos); mat4_rotate_z(player.rot, rot); mat4_multiply(pos, rot, player.mvp); glUniformMatrix4fv(uniform_mvp, 1, GL_FALSE, player.mvp); glBindBuffer(GL_ARRAY_BUFFER, player.vbo[0]); glVertexAttribPointer( attribute_coord2d, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 4, 0 ); glVertexAttribPointer( attribute_texcoord, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 4, (void*)(sizeof(float) * 2) ); glDrawArrays(GL_TRIANGLES, 0, 6); if(player.pos[0] > WIDTH - 24.0f) { player.pos[0] -= WIDTH; draw_mirror = TRUE; } else if(player.pos[0] <24.0f) { player.pos[0] += WIDTH; draw_mirror = TRUE; } if(player.pos[1] > HEIGHT - 24.0f) { player.pos[1] -= HEIGHT; draw_mirror = TRUE; } else if(player.pos[1] <24.0f) { player.pos[1] += HEIGHT; draw_mirror = TRUE; } if(draw_mirror) { mat4_translate(player.pos, pos); mat4_rotate_z(player.rot, rot); mat4_multiply(pos, rot, player.mvp); glUniformMatrix4fv(uniform_mvp, 1, GL_FALSE, player.mvp); glBindBuffer(GL_ARRAY_BUFFER, player.vbo[0]); glVertexAttribPointer( attribute_coord2d, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 4, 0 ); glVertexAttribPointer( attribute_texcoord, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 4, (void*)(sizeof(float) * 2) ); glDrawArrays(GL_TRIANGLES, 0, 6); } glDisableVertexAttribArray(attribute_coord2d); glDisableVertexAttribArray(attribute_texcoord); } static gboolean on_idle(gpointer data) { if(player.right) { player.rot -= 0.05f; } if(player.left) { player.rot += 0.05f; } int i; float dx, dy; if(player.up) { player.dx -= sin(player.rot) * 0.05; player.dy += cos(player.rot) * 0.05; } if(player.dx <-player.max) { player.dx = -player.max; } else if(player.dx > player.max) { player.dx = player.max; } if(player.dy <-player.max) { player.dy = -player.max; } else if(player.dy > player.max) { player.dy = player.max; } player.pos[0] += player.dx; player.pos[1] += player.dy; for(i = 0; i gtk_widget_queue_draw(GTK_WIDGET(data)); return TRUE; } static gboolean on_keydown(GtkWidget *widget, GdkEventKey *event) { switch(event->keyval) { case GDK_KEY_Left: player.left = TRUE; break; case GDK_KEY_Right: player.right = TRUE; break; case GDK_KEY_Up: player.up = TRUE; break; case GDK_KEY_space: if(!player.space) { int i; for(i = 0; i } } static gboolean on_keyup(GtkWidget *widget, GdkEventKey *event) { switch(event->keyval) { case GDK_KEY_Left: player.left = FALSE; break; case GDK_KEY_Right: player.right = FALSE; break; case GDK_KEY_Up: player.up = FALSE; break; case GDK_KEY_space: player.space = FALSE; break; } }

Compile

$ gcc -c -o DashGL/dashgl.o DashGL/dashgl.c -lepoxy -lpng -lm
$ gcc `pkg-config --cflags gtk+-3.0` main.c DashGL/dashgl.o `pkg-config --libs gtk+-3.0` \
-lepoxy -lm -lpng

Run

$ ./a.out