Игра Cube Shooter

shuter.jpg

В данной задаче реализована двухмерная игра с трехмерными элементами.
Язык С. Графические библиотеки: GTK+ и Cairo

Описание игры: На вас падают трехмерные кубики! ваша цель уничтожать их как можно быстрее при помощи пушки
(целимся и стреляем мышкой)

Алгоритм:

0) а) Загружаем кубик(может быть заменен на любую фигуру) из файла:
файл имеет структуру:
<количество точек> <количество сторон>
{.... перечисление точек...
X Y Z}
{перечисление сторон.....
<количество точек в стороне>{перечисление порядковых номеров точек}
}

b) Рисуем фон, сохраняем его в PixBuf.
1) выполняем необходимые расчеты:
а) поворачиваем кубики. для поворота, каждая точка куба умножается на матрицу

{vx*vx*(1 - cos(angl)) + cos(angl),vx*vy*(1 - cos(angl)) + vz*sin(angl),vx*vz*(1 - cos(angl)) - vy * sin(angl),0},
		{vx*vy*(1-cos(angl))-vz*sin(angl),vy*vy*(1-cos(angl))+cos(angl),vy*vz*(1 - cos(angl)) + vx*sin(angl),0},
		{vx*vz*(1 - cos(angl))+vy*sin(angl),vy*vz*(1-cos(angl)) - vx * sin(angl),vz*vz*(1 - cos(angl))+cos(angl) ,0},
		{0,0,0,1}

где vx,vy,vz - единичный нормированый вектор поворота. angl - угол на который совершается поворот.
b) просчитываем видимость граней: берем вектор нормальли каждой стороны и получаем угол между ним и вектором камеры, если угол < 0 то сторона невидема.
c) передвигаем кубы(падают в низ) и пули(при выстреле пуля получает вектор поворота пушки, и двигается в соответствии нему). просчитываем столкновения.
2) рисуем фон из PixBuf'a, прорисовываем все элементы игры(пули, кубы, пушку);
3) возвращаемся в пункт 1)

содержимое файла "rotation.h"

#ifndef ROTATION_H_INCLUDED
#define ROTATION_H_INCLUDED
 
#include <stdlib.h>
#include <math.h>
#include <gtk/gtk.h>
 
typedef struct point point;
typedef struct side side;
typedef struct cube cube;
typedef enum{false,true} bool;
typedef struct cube_data cdata;
 
 
#define camera -280.0 // растояние по Z оси на котором находится камера
 
struct point // структура описывающая точку
{
    double x,y,z;
};
struct side // структура описывает стороны фигуры
{
    point **p;
    char pcount;
    bool visible;
};
struct cube // структура описывает фигуру
{
    point *points;
    side *sides;
    gint center_x, center_y;
    float rotate_speed, size;
    bool showinvisible,prespect;
    char pcount, scount, vector_x,vector_y,vector_z;
    float move_speed;
};
 
struct cube_data // структура содержет в себе всю информацию о загруженых фигурах
{
    cube **cubes;
    char cCount;
    GtkWidget *widget;
    cairo_t *cr;
    double **matrix;
};
 
 
void CheckVisible(cube *, int );
void RotateCube(cube *);
cube * create_cube(char *,gint,gint,float,char,char,char,bool,bool,float,float);
void init_cube_data(cdata *, GtkWidget *,char);
void Draw_Cube(cairo_t *,cube *);
#endif // ROTATION_H_INCLUDED

содержимое файла "rotation.с"

#include "rotation.h"
#include <stdlib.h>
#include <math.h>
#include <gtk/gtk.h>
 
void SetCubePos(cube *cb, gint x, gint y) // изменяем положение фигуры
{
    cb->center_x = x;
    cb->center_y = y;
}
 
void CheckVisible(cube *cb, int cam) // проверка видимости граней
{// при построении файла со сторонами трехмерной фигуры, точки раставляються так чтобы вектор нормали стороны был направлен наружу
    // в проверке вычисляется угол между нормалью камеры и нормалью сторон, и если он отрицательный - то сторона невидема
    int i, j;
    double x1, y1, z1, x2, y2, z2, x3, y3, z3;
    for (i=0;i<cb->scount;i++)
    {
        j = cb->sides[i].pcount-1;
        z1 = cb->sides[i].p[0]->z;
        z2 = cb->sides[i].p[1]->z;
        z3 = cb->sides[i].p[j]->z;
        x1 = cb->sides[i].p[0]->x;
        y1 = cb->sides[i].p[0]->y;
        x2 = cb->sides[i].p[1]->x;
        y2 = cb->sides[i].p[1]->y;
        x3 = cb->sides[i].p[j]->x;
        y3 = cb->sides[i].p[j]->y;
        if (cam != 0 ) // преобразовываем координаты в зависимости от типа роэкции
        {
            x1 /= 1 - z1/cam; y1 /= 1 - z1/cam;
            x2 /= 1 - z2/cam; y2 /= 1 - z2/cam;
            x3 /= 1 - z3/cam; y3 /= 1 - z3/cam;
        }
        x2 -= x1;
        y2 -= y1;
        x3 -= x1;
        y3 -= y1;
        if ((x2*y3 - y2*x3)*cam > 0) // проверяем угол
        {
            cb->sides[i].visible = true;
        }
		else
		{
		     cb->sides[i].visible = false;
		}
     }
}
 
void RotateCube(cube *cb) // функция вращения фигуры
{ // для вращения используется матрица поворота вокруг точки 0.0.0  с заданым единичным вектором вращения
    int i,j;
    double n = sqrt(cb->vector_x*cb->vector_x + cb->vector_y*cb->vector_y + cb->vector_z*cb->vector_z);
    double angl = cb->rotate_speed * 3.1415/180, vx = cb->vector_x/n, vy = cb->vector_y/n,vz = cb->vector_z/n;
    double matrix[4][4] = { // матрица поворота
		{vx*vx*(1 - cos(angl)) + cos(angl),vx*vy*(1 - cos(angl)) + vz*sin(angl),vx*vz*(1 - cos(angl)) - vy * sin(angl),0},
		{vx*vy*(1-cos(angl))-vz*sin(angl),vy*vy*(1-cos(angl))+cos(angl),vy*vz*(1 - cos(angl)) + vx*sin(angl),0},
		{vx*vz*(1 - cos(angl))+vy*sin(angl),vy*vz*(1-cos(angl)) - vx * sin(angl),vz*vz*(1 - cos(angl))+cos(angl) ,0},
		{0,0,0,1}
	};
    double Result[4];
    for(j = 0; j < cb->pcount; j++) // каждая точка фигуры умножается на матрицу поворота
    {
        for (i = 0; i < 4; i++)
        {
            Result[i] = (matrix[i][0] * cb->points[j].x) + (matrix[i][1] * cb->points[j].y) + (matrix[i][2] * cb->points[j].z) + matrix[i][3];
        }
        cb->points[j].x = Result[0];
        cb->points[j].y = Result[1];
        cb->points[j].z = Result[2];
    }
}
 
// функция инициализации фигуры
cube * create_cube(char *file, // путь к файлу с описанием точек и сторон
                    gint centerx, gint centery, // положение куба на экране
                    float rotatespeed, // скорость вращения
                    char vector_x, char vector_y, char vector_z, // единичный вектор вращения
                    bool show_invisible, //фигура прозрачная(true)/не прозрачная(false)
                    bool prespective, // проэкция преспективная(true)/параллельная(false)
                    float size, // маштаб фигуры
                    float mspeed) // скорость передвижения  по экрану (используется только в этой задаче)
{
    FILE *f;
    cube *cb=NULL;
    f=fopen(file,"r");
    if(f != NULL) // считываем параметры из файла
    {
        int pCount,sCount,i,j,sPoints;
        cb = (cube*) malloc(sizeof(cube));
        fscanf(f,"%d %d",&pCount,&sCount);
        //printf("Points: %d Sides: %d\n",pCount,sCount);
        cb->points = (point*) malloc(sizeof(point) * pCount);
        cb->sides = (side*) malloc(sizeof(side) * sCount);
        cb->scount = sCount;
        cb->pcount =  pCount;
        cb->center_x = centerx;
        cb->center_y = centery;
        cb->prespect = prespective;
        cb->showinvisible = show_invisible;
        cb->vector_x = vector_x;
        cb->vector_y = vector_y;
        cb->vector_z = vector_z;
        cb->rotate_speed = rotatespeed;
        cb->size = size;
        cb->move_speed = mspeed;
        for(i = 0; i < pCount;i++)
        {
            fscanf(f,"%lf %lf %lf",&cb->points[i].x,&cb->points[i].y,&cb->points[i].z);
            //printf("%.2lf %.2lf %.2lf\n",cb->points[i].x,cb->points[i].y,cb->points[i].z);
        }
        for(i = 0; i < sCount;i++)
        {
            fscanf(f,"%d",&sPoints);
            cb->sides[i].pcount = sPoints;
            cb->sides[i].p = (point **) malloc(sPoints * sizeof(point *));
            cb->sides[i].visible = true;
            //printf("%d:",sPoints);
            for(j=0; j < sPoints; j++)
            {
                fscanf(f,"%d",&pCount);
                //printf(" %d",pCount);
                cb->sides[i].p[j] = &cb->points[pCount];
            }
            //printf("\n");
        }
        fclose(f);
    }
    return cb; //
}
 
void Draw_Cube(cairo_t *cr,cube *cb) // рисуем фигуру
{
    int i,j;
    for (i=0;i<cb->scount;i++)
    {
        if(cb->sides[i].visible == true || cb->showinvisible == true) // грань рисуется в случае если она видима,
        {// или куб прозрачный
            if(cb->prespect == false) // если проэкция парралельная
            {
                cairo_move_to(cr,cb->size*cb->sides[i].p[0]->x + cb->center_x, cb->size*cb->sides[i].p[0]->y + cb->center_y);
                for (j=1;j<cb->sides[i].pcount; j++)
                {
                    cairo_line_to(cr, cb->size*cb->sides[i].p[j]->x+ cb->center_x,cb->size*cb->sides[i].p[j]->y + cb->center_y);
                }
                cairo_line_to(cr, cb->size*cb->sides[i].p[0]->x + cb->center_x, cb->size*cb->sides[i].p[0]->y+ cb->center_y);
            }
            else // если проэкция преспективная
            {
                cairo_move_to(cr,cb->size*cb->sides[i].p[0]->x/(1-cb->sides[i].p[0]->z/camera) + cb->center_x, cb->size*cb->sides[i].p[0]->y/(1-cb->sides[i].p[0]->z/camera) + cb->center_y);
                for (j=1;j<cb->sides[i].pcount; j++)
                {
                    cairo_line_to(cr, cb->size*cb->sides[i].p[j]->x/(1-cb->sides[i].p[j]->z/camera)+ cb->center_x,cb->size*cb->sides[i].p[j]->y/(1-cb->sides[i].p[j]->z/camera) + cb->center_y);
                }
                cairo_line_to(cr, cb->size*cb->sides[i].p[0]->x/(1-cb->sides[i].p[0]->z/camera) + cb->center_x, cb->size*cb->sides[i].p[0]->y/(1-cb->sides[i].p[0]->z/camera)+ cb->center_y);
            }
        }
        cairo_set_source_rgb(cr, 101.0/255.0-8*i/255, 47.0/255.0, 39.0/255.0);
        if(cb->showinvisible == true) // если куб прозрачный то рисуем только линии
            cairo_stroke(cr);
        else cairo_fill(cr); // если же нет, то закрашиваем сторону
    }
 
}
 
void init_cube_data(cdata *d, GtkWidget *wd,char cubes_count)
{
    d->widget = wd;
    d->cubes = (cube **) malloc(sizeof(cube)*cubes_count);
    d->cCount = cubes_count;
    d->cr = NULL;
}
 
 
//

содержимое файла "main.c"

#include <stdlib.h>
#include "rotation.h"
#include <math.h>
#include <gtk/gtk.h>
 
#define C_COUNT 5    // количество падающих кубов
 
GdkPixbuf * pb = NULL;
int timeractive=0;
int count=0;
int dir=1;
typedef struct data data;
typedef struct blist blist;
typedef struct bullet bullet;
struct data // структура содержит основные переменные программы
{
        GtkWidget *widget;
        cdata *cd;
        double cx, cy, v_x, v_y;
        int key_was_pressed;
};
 
struct bullet // структура описывающая пулю
{
    bullet *left;
    bullet *right;
    double b_x, b_y, vb_x, vb_y;
};
 
struct blist // структура содержит информацию о выпущеных пулях (структура: двунаправленный список)
{
    bullet * first;
    bullet * last;
    int bcount;
}bullets;
 
void init_game_data(data *d, GtkWidget *w, cdata *c) // инициализация переменных игры
{
    d->widget = w;
    d->cd = c;
    d->key_was_pressed = 0;
    bullets.bcount = 0;
    bullets.first = bullets.last = NULL;
}
 
void add_bullet(double vx, double vy, double x, double y) // добавление пули в список
{ // при добавление указываем точку вылета, и вектор направления.
    bullet *b = (bullet *) malloc(sizeof(bullet));
    b->b_x = x;
    b->b_y = y;
    b->vb_x = vx;
    b->vb_y = vy;
    b->left = bullets.last;
    b->right = NULL;
    if(bullets.last!= NULL) bullets.last->right = b;
    if(bullets.first == NULL) bullets.first = b;
    bullets.last = b;
    bullets.bcount++;
}
void destroy_bullet(bullet *b) // уничтожение пули, с удалением из списка
{
    if(bullets.bcount == 1)
    {
        bullets.first = bullets.last = NULL;
        free(b);
    }
    else
    {
        if(bullets.first == b)
        {
            bullets.first->right->left=NULL;
            bullets.first = bullets.first->right;
            free(b);
        }
        else if(bullets.last == b)
        {
            bullets.last->left->right=NULL;
            bullets.last = bullets.last->left;
            free(b);
        }
        else
        {
            b->left->right = b->right;
            b->right->left= b->left;
            free(b);
        }
    }
    bullets.bcount--;
}
gint engine(gpointer d) // функция вызваемая таймером, осуществляет все расчеты.
{
    data *v = (data*) d;
    if(timeractive == 1)
    {
        gtk_widget_queue_draw(v->widget);
        return 0;
    }
    double xx = v->cx - v->widget->allocation.width/2; // получаем вектор поворота пушки
    double yy = v->cy - v->widget->allocation.height;
    double norm = sqrt(xx*xx + yy*yy);
    v->v_x = xx/norm; // нормируем вектор поворота
    v->v_y = yy/norm;
    int i=0, out=0;
    bullet *b=bullets.first, *temp;
    while(b != NULL) // перебираем все пули, перемещая их и просчитываем столкновения
    {
        b->b_x += b->vb_x*10;
        b->b_y += b->vb_y*10;
        if(b->b_y < -20 || (b->b_x < -20 || b->b_x > v->widget->allocation.width+20)) // уничтожаем пулю если она вышла за экран
        {
            temp=b->right;
            destroy_bullet(b);
            b = temp;
            continue;
        }
        out = 0;
        for(i = 0; i<C_COUNT;i++) // перебираем все кубы, просчитываем столкновения
        {
            if(abs(b->b_y - v->cd->cubes[i]->center_y) <= v->cd->cubes[i]->size*10 && abs(b->b_x - v->cd->cubes[i]->center_x) <= v->cd->cubes[i]->size*10)
            // если пуля попадает в область куба, то он уничтожается (путем переноса вверх сцены)
            {// после переноса куб получает новые параметры размера, скорости и вращения
                temp=b->left;
                destroy_bullet(b);
                b = temp;
                out = 1;
                v->cd->cubes[i]->center_y = -100 - rand()%100;
                v->cd->cubes[i]->center_x = 50 + rand()%v->widget->allocation.width;
                v->cd->cubes[i]->move_speed=1+rand()%5;
                v->cd->cubes[i]->size=3+rand()%3;
                v->cd->cubes[i]->vector_x=-1+rand()%3;
                break;
            }
        }
        if(out == 1) continue;
        b=b->right;
    }
    for(i = 0; i<C_COUNT;i++) // поворачиваем кубы, проверяем видимость граней, проверяем выход за придел экрана
    {
        RotateCube(v->cd->cubes[i]); // поворот точек куба
        v->cd->cubes[i]->center_y+=v->cd->cubes[i]->move_speed;
        if(v->cd->cubes[i]->center_y > v->widget->allocation.height+100){ // в случае выхода за экран, возвращаем на верх сцены
            v->cd->cubes[i]->center_y = -100 - rand()%100; // и присваиваем новые параметры
            v->cd->cubes[i]->center_x = 50 + rand()%v->widget->allocation.width;
            v->cd->cubes[i]->move_speed = 1+rand()%5;
            v->cd->cubes[i]->size=3+rand()%3;
            v->cd->cubes[i]->vector_x=-1+rand()%3;
        }
        CheckVisible(v->cd->cubes[i], 1000); // вызываем проверку видимости граней
    }
    v->key_was_pressed = 0;
    gtk_widget_queue_draw(v->widget);
    return 1;
}
 
gint draw_met(data *d,bullet *b) // функция рисования градиентной пули
{
    cairo_t *cr = gdk_cairo_create(d->widget->window);
    cairo_pattern_t *pt;
    cairo_set_line_width(cr,0.1);
    pt = cairo_pattern_create_radial(b->b_x,b->b_y,0,b->b_x,b->b_y,15);
    cairo_pattern_add_color_stop_rgba(pt,0.2,0,0,1,0.5);
    cairo_pattern_add_color_stop_rgba(pt,0.9,0,0,0.0,0.05);
    cairo_arc(cr,b->b_x,b->b_y,15,0,360);
    cairo_set_source(cr, pt);
    cairo_fill(cr);
    cairo_pattern_destroy(pt);
    cairo_destroy(cr);
    return 1;
}
 
gint draw(GtkWidget * w, GdkEventExpose *event, gpointer d) // функция перерисовки сцены
{
    data * dt = (data*) d;
    double sx = (double)w->allocation.width/2.0;
    double sy = (double)w->allocation.height/2.0;
    int i;
    cairo_t *cr=gdk_cairo_create(w->window);
    if(timeractive == 0) // при первом вызове создаем фон
    {
        cairo_rectangle(cr,0,0,sx*2,sy*2);
        cairo_set_source_rgb(cr, 0,0, 20.0/255.0);
        cairo_fill(cr);
        for (i=0; i < 1000;i++) // рисуем звезды
        {
            float x = rand()%w->allocation.width;
            float y = rand()%w->allocation.height;
            cairo_rectangle(cr,x,y,1,1);
            cairo_set_source_rgb(cr,(float)(rand()%2)/2,(float)(rand()%2)/2,(float)(rand()%2)/2);
            cairo_fill(cr);
        }
        pb = gdk_pixbuf_get_from_drawable(NULL,w->window,NULL,0,0,0,0,w->allocation.width,w->allocation.height); // сохраняем фон в памяти
        timeractive = 2;
        g_timeout_add(30,engine,d); // включаем таймер
 
    }
    else
    {
        GdkGC * gc = w->style->fg_gc[GTK_WIDGET_STATE (w)];
        gdk_draw_pixbuf(w->window,gc,pb,0,0,0,0,sx*2,sy*2,0,0,0); // рисуем фон из памяти
        // рисуем пушку
        cairo_pattern_t *pt = cairo_pattern_create_radial(sx,sy*2,10,sx,sy*2,80);
        cairo_pattern_add_color_stop_rgba(pt,0.2,0,0,1,0.5);
        cairo_pattern_add_color_stop_rgba(pt,0.9,0,0,0.0,0.05);
        cairo_set_source(cr, pt);
        cairo_move_to(cr,sx,sy*2);
        cairo_line_to(cr,sx+dt->v_x*50, sy*2+dt->v_y*50 );
        cairo_set_line_width(cr,18);
        cairo_stroke(cr);
        cairo_set_line_width(cr,0.1);
        cairo_arc(cr,sx,sy*2,80,0,360);
        cairo_fill(cr);
        cairo_pattern_destroy(pt);
        // рисуем пули
        cairo_set_line_width(cr,2);
        if(bullets.bcount > 0)
        {
            bullet *b=bullets.first;
            while(b != NULL)
            {
                draw_met(dt,b);
                b=b->right;
            }
        }
        // рисуем кубы
        for(i = 0; i<C_COUNT;i++)
        {
            Draw_Cube(cr,dt->cd->cubes[i]);
        }
        // рисуем прицел
        cairo_arc(cr,dt->cx,dt->cy,10,0,360);
        cairo_set_source_rgb(cr, 0,1,0);
        cairo_stroke(cr);
    }
    cairo_destroy(cr);
    return 1;
}
 
gint mouse(GtkWidget *wd, GdkEventMotion *event, gpointer d)// функция вызывается при передвижении мышки по экрану
{ // обновляет координаты положения курсора
    if(event != NULL)
    {
        ((data *)d)->cx = event->x;
        ((data *)d)->cy = event->y;
    }
    return 1;
}
gint ButtonPressed(GtkWidget * window, GdkEventButton *event, gpointer d) // вызываеться при нажатии кнопки мыши
{// если на сцене не более пяти пуль, то производим выстрел)
    data * dt = (data*) d;
    if(bullets.bcount >= 5) return 1;
    add_bullet(dt->v_x, dt->v_y, dt->widget->allocation.width/2.0 + dt->v_x *40, dt->widget->allocation.height+ dt->v_y *40);
    return 0;
}
gint key_pressed(GtkWidget *w, GdkEventKey *event, gpointer dp) // функция вызывается при нажатии на клавишу клавиатуры
{
    data *d = (data *) dp;
    printf("key: %d\n",event->keyval);
	if(d->key_was_pressed == 1) return 1;
	d->key_was_pressed = 1;
	switch(event->keyval) // если нажата клавиша Esc - выходим из игры.
	{
        case 65307: gtk_main_quit(); break;
        default: d->key_was_pressed = 1;
    }
	return 1;
}
 
int main ( int argc, char ** argv)
{
    srand(time(0));
	gtk_init( &argc, &argv);
	data d;
	cdata cd;
	GtkWidget * window =  gtk_window_new (GTK_WINDOW_TOPLEVEL);
	gtk_window_fullscreen (window);
	gtk_window_set_resizable(window,0);
	gtk_window_set_title (window, "Planet");
	init_cube_data(&cd, window, C_COUNT);
	init_game_data(&d, window, &cd);
	int i=0;
	for(; i < C_COUNT; i++) // загружаем кубики
	{
        cd.cubes[i] = create_cube("cube.txt",50+rand()%600,-50,1,1,1,1,false,false,3+rand()%3,1+rand()%6);
        // кубик загружается из файла с описанием точек и сторон, при чем вместо куба может быть загружена любая объемная фигура.
    }
    g_signal_connect (G_OBJECT (window), "delete_event", G_CALLBACK (gtk_main_quit), NULL); //
	g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (gtk_main_quit), NULL); // по закрытию окна - выходим из игры
	g_signal_connect (G_OBJECT (window), "expose_event", G_CALLBACK (draw), (gpointer)&d); // при любых манипуляциях с окном - вызываем draw() (перерисовку)
    g_signal_connect (G_OBJECT (window), "motion_notify_event", G_CALLBACK (mouse), (gpointer) &d); // при движении машкой вызываем функцию mouse()
    g_signal_connect (G_OBJECT (window), "button_release_event", G_CALLBACK (ButtonPressed), (gpointer) &d); // ButtonPressed ()- вызывается только когда кнопка мыши отпускается
    g_signal_connect(G_OBJECT (window), "key_press_event", G_CALLBACK (key_pressed), (gpointer) &d); // при нажатии клавиатурной клавиши вызываем ф-цию key_pressed()
    gtk_widget_set_events (window, GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK); // "просим" окно следить за состоянием мышки=)
	gtk_widget_show_all(window);
	gtk_main();
	return 0;
}

Ключевые слова: 
gtk gtk+ cairo анимация игра трехмерные фигуры столкновения
ВложениеРазмер
cube shooter.rar6.58 кб