В данной задаче реализована двухмерная игра с трехмерными элементами. Описание игры: На вас падают трехмерные кубики! ваша цель уничтожать их как можно быстрее при помощи пушки Алгоритм: 0) а) Загружаем кубик(может быть заменен на любую фигуру) из файла: b) Рисуем фон, сохраняем его в PixBuf. {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 анимация игра трехмерные фигуры столкновения
|
|||||||