gtk自制小游戏Jumpman

⌚Time: 2022-09-27 23:50:53

👨‍💻Author: Jack Ge

使用cairo库进行游戏画面绘制

界面分为游戏人物、路、障碍物,分别定义成结构体,记录坐标,状态,颜色等信息

游戏人物结构体


typedef struct _MAN{

    gint x;

    gint y;

    ManStatus status;

}Man;

对于画面的绘制,没有使用多线程,而是使用单次和循环计时器进行绘制

使用单次定时器绘制开始界面


gboolean start_window(GtkWidget *widget){

    cairo_t *cr;

    cr = gdk_cairo_create (widget->window);

    //background

    cairo_set_source_rgb(cr,0.6,0,0.7);

    cairo_move_to(cr,0, 0);

    cairo_line_to(cr,X_WIDTH,0);

    cairo_line_to(cr,X_WIDTH,X_HEIGHT);

    cairo_line_to(cr,0,X_HEIGHT);

    cairo_stroke_preserve(cr);//save line path,cairo_fill can fill

    cairo_fill(cr);



    //game reference

    //char info[1000] = {'\0'};

    //sprintf(info,"Ctrl to start\nspace to jump\n p to pause/continue\n");

    cairo_set_source_rgb(cr,0.0,0.7,0.3);

    cairo_set_font_size(cr,24);

    cairo_move_to(cr,30, 40);

    cairo_show_text(cr,"Ctrl to start");

    cairo_move_to(cr,30, 90);

    cairo_show_text(cr,"Space to jump");

    cairo_move_to(cr,20, 130);

    cairo_show_text(cr,"p to pause/continue");

    cairo_stroke(cr);



    cairo_destroy(cr);

    //run only once

    return false;

}

游戏分数、时间、速度等信息的绘制


gint score;//游戏分数

gint timeElapse;//游戏时间

gint gameSpeed;//游戏速度





    //time

    char strTime[10] = {'\0'};

    cairo_set_source_rgb(cr,0.4,0.4,0.7);

    cairo_set_font_size(cr,14);

    cairo_move_to(cr,20, 30);

    sprintf(strTime,"Time:%d",timeElapse);

    cairo_show_text(cr,strTime);

    cairo_stroke(cr);

使用定时器实时移动障碍物坐标


gboolean move_obstacle(){

    for(int i = 0;i<5;i++){

        obstacles[i].x -= gameSpeed;

        //if obstacle go end,move it as farest obstacle

        if(obstacles[i].x<0){

            int xMax = 0;

            for(int j =0;j<5;j++){

                if(obstacles[j].x>xMax){

                    xMax = obstacles[j].x;

                }

            }

            obstacles[i].x = xMax + rand()%50 +100;

            obstacles[i].r = (float)(rand()%100)/100;

            obstacles[i].g = (float)(rand()%100)/100;

            obstacles[i].b = (float)(rand()%100)/100;

            

            score++;

        }

    }

    if(hit_check()){

        game_over();

    }

    return true;

}

通过按键Ctrl开始游戏,space跳跃,P暂停/继续游戏


void my_key_press(GtkWidget* widget,GdkEventKey *event,gpointer data){



        switch(event->keyval){

        case GDK_KEY_Control_L:

        case GDK_KEY_Control_R:

            game_start();

            break;

        case GDK_KEY_space:

            g_thread_create(man_jump,(void*)&man_0,FALSE,NULL);

            break;

        case GDK_KEY_p:

        case GDK_KEY_P:

            game_pause_continue();

            break;

        }

}

对于按键键值,在gdkkeysyms.h有详细定义


#define GDK_Delete 0xffff

#define GDK_Multi_key 0xff20

#define GDK_Codeinput 0xff37

#define GDK_SingleCandidate 0xff3c

#define GDK_MultipleCandidate 0xff3d

#define GDK_PreviousCandidate 0xff3e

#define GDK_Kanji 0xff21

#define GDK_Muhenkan 0xff22

#define GDK_Henkan_Mode 0xff23

#define GDK_Henkan 0xff23

#define GDK_Romaji 0xff24

#define GDK_Hiragana 0xff25

#define GDK_Katakana 0xff26

#define GDK_Hiragana_Katakana 0xff27

...

编译参数的设置:

使用以下命令获得g++编译gtk程序的参数


pkg-config --cflags --libs gtk+-2.0

另外,使用到g_thread_init函数,因此额外的链接库参数


-lgthread-2.0

游戏有闪烁,使用双缓冲技术可以消除闪烁

源码

common.h

#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include <stdio.h>
//time
#include <time.h>
//rand
#include <stdlib.h>
#include <math.h>

typedef enum _MAN_STATUS{RUN,JUMP}ManStatus;//人物状态
typedef enum _GAME_STATUS{GOING,PAUSE,STOP}GameStatus;//游戏状态
//人物结构体
typedef struct _MAN{
    gint x;
    gint y;
    ManStatus status;
}Man;
//路结构体
typedef struct _ROAD{
    gint startPointX;
    gint startPointY;
    gint endPointX;
    gint endPointY;
    gboolean valiable;
}Road;
//障碍物结构体
typedef struct _OBSTACLE{
    gint x;
    float r;
    float g;
    float b;
}Obstacle;

void* man_jump(void *man);
void my_key_press(GtkWidget* widget,GdkEventKey *event,gpointer data);
gboolean show_graph(GtkWidget *widget);
void init_road();
gboolean move_road();
void init_obstacle();
gboolean move_obstacle();
gboolean set_time();
gboolean set_game_speed();
void game_start();
void game_pause_continue();
void game_over();
gboolean start_window(GtkWidget *widget);
gboolean hit_check();

//定义屏幕大小
#define X_WIDTH 300
#define X_HEIGHT 200
//最大游戏速度
#define MAX_GAME_SPEED 5

extern Man man_0;
extern Road roads[10];
extern Obstacle obstacles[5];
extern gint score;
extern gint gameSpeed;
extern gint timeElapse;
extern GameStatus gameStatus;
extern guint timer_0,timer_1,timer_2,timer_score,timer_game_speed;
extern GtkWidget *window;


q.cpp

#include "common.h"

//win32下的延时函数支持
#ifdef WIN32
#include <windows.h>
#define SLEEP_TIME_1 Sleep(1000*0.003);
#define SLEEP_TIME_2 Sleep(1000*0.001);
#else
#define SLEEP_TIME_1 usleep(1000*1000*0.003);
#define SLEEP_TIME_2 usleep(1000*1000*0.001);
#endif

//键值定义
#ifndef GDK_KEY_space
#define GDK_KEY_space GDK_space 
#endif
#ifndef GDK_KEY_P
#define GDK_KEY_P GDK_P
#endif
#ifndef GDK_KEY_p
#define GDK_KEY_p GDK_p
#endif
#ifndef GDK_KEY_Control_L
#define GDK_KEY_Control_L GDK_Control_L
#endif
#ifndef GDK_KEY_Control_R
#define GDK_KEY_Control_R GDK_Control_R
#endif

//人物跳跃函数
void* man_jump(void *man){
    
    
    Man *pMan = (Man*)man;
    if(pMan->status == JUMP){
        return NULL;
    }
    pMan->status = JUMP;
    for(int i = 0;i<50;i++){
        pMan->y -= 1;
        SLEEP_TIME_1
    }
    for(int i = 0;i<50;i++){
        pMan->y += 1;
        SLEEP_TIME_2 
    }
    pMan->status = RUN;

}
//按键检测
void my_key_press(GtkWidget* widget,GdkEventKey *event,gpointer data){

        switch(event->keyval){
        case GDK_KEY_Control_L:
        case GDK_KEY_Control_R:
            game_start();
            break;
        case GDK_KEY_space:
            g_thread_create(man_jump,(void*)&man_0,FALSE,NULL);
            break;
        case GDK_KEY_p:
        case GDK_KEY_P:
            game_pause_continue();
            break;
        }
}

//游戏画面绘制
gboolean show_graph(GtkWidget *widget){
            
    cairo_t *cr;
    cr = gdk_cairo_create (widget->window);
    
    //background
    cairo_set_source_rgb(cr,0,0,0);
    cairo_move_to(cr,0, 0);
    cairo_line_to(cr,X_WIDTH,0);
    cairo_line_to(cr,X_WIDTH,X_HEIGHT);
    cairo_line_to(cr,0,X_HEIGHT);
    cairo_stroke_preserve(cr);
    cairo_fill(cr);
    //road
    cairo_set_source_rgb(cr,0.6,0.4,0);
    cairo_move_to(cr,0, 110);
    cairo_line_to(cr,X_WIDTH,110);
    cairo_stroke(cr);

    for(int i = 0;i<10;i++){
        
        cairo_set_source_rgb(cr,0.2,0.4,0.6);
        cairo_move_to(cr,roads[i].startPointX, roads[i].startPointY);
        cairo_line_to(cr,roads[i].endPointX,roads[i].endPointY);
        cairo_stroke(cr);
    }
    
    
    //obstacle
    for(int i = 0;i<5;i++){
        cairo_set_source_rgb(cr,obstacles[i].r,obstacles[i].g,obstacles[i].b);
        cairo_move_to(cr,obstacles[i].x-5, 110);
        cairo_line_to(cr,obstacles[i].x-5,90);
        cairo_line_to(cr,obstacles[i].x+10,90);
        cairo_line_to(cr,obstacles[i].x+10,110);
        cairo_line_to(cr,obstacles[i].x-5,110);
        cairo_stroke_preserve(cr);
        cairo_fill(cr);
        //printf("%d \n",obstacles[i].x);
    }
    //score
    
    char strScore[10] = {'\0'};
    cairo_set_source_rgb(cr,0.2,0.9,0.4);
    cairo_set_font_size(cr,14);
    cairo_move_to(cr,X_WIDTH-70, 30);
    sprintf(strScore,"score:%d",score);
    cairo_show_text(cr,strScore);
    cairo_stroke(cr);
    
    //time
    
    char strTime[10] = {'\0'};
    cairo_set_source_rgb(cr,0.4,0.4,0.7);
    cairo_set_font_size(cr,14);
    cairo_move_to(cr,20, 30);
    sprintf(strTime,"Time:%d",timeElapse);
    cairo_show_text(cr,strTime);
    cairo_stroke(cr);
    //game speed
    
    char strSpeed[10] = {'\0'};
    cairo_set_source_rgb(cr,0.7,0.4,0.6);
    cairo_set_font_size(cr,14);
    cairo_move_to(cr,110, 30);
    sprintf(strSpeed,"speed:%d",gameSpeed-1);
    cairo_show_text(cr,strSpeed);
    cairo_stroke(cr);
    
    //man
    
    cairo_set_line_width(cr,0.1);
    cairo_set_source_rgb(cr,1,1,0);
    cairo_arc(cr,man_0.x, man_0.y,6,0,360);
    cairo_stroke_preserve(cr);
    cairo_fill(cr);
    
    cairo_destroy(cr);

    return true;
}

void init_road(){
    for(int i=0;i<10;i++){
        roads[i].startPointY = rand()%2?120:130;
        roads[i].startPointX = rand()%X_WIDTH;
        roads[i].endPointX = roads[i].startPointX + rand()%2+10;
        roads[i].endPointY = roads[i].startPointY;
    }
}
//路移动
gboolean move_road(){
    for(int i = 0;i<10;i++){
        roads[i].startPointX -= gameSpeed;
        roads[i].endPointX -= gameSpeed;
        if(roads[i].endPointX<0){
            roads[i].startPointY = rand()%2?120:130;
            roads[i].startPointX = X_WIDTH;
            roads[i].endPointX = roads[i].startPointX + rand()%2+10;
            roads[i].endPointY = roads[i].startPointY;
        }
    }
    
    return true;
}
void init_obstacle(){
    obstacles[0].x = X_WIDTH+20;
    obstacles[0].r = (float)(rand()%100)/100;
    obstacles[0].g = (float)(rand()%100)/100;
    obstacles[0].b = (float)(rand()%100)/100;
    for(int i = 1;i<5;i++){
        obstacles[i].x = obstacles[i-1].x + rand()%50 +100;
        obstacles[i].r = (float)(rand()%100)/100;
        obstacles[i].g = (float)(rand()%100)/100;
        obstacles[i].b = (float)(rand()%100)/100;
    }
}
//障碍物移动
gboolean move_obstacle(){

    for(int i = 0;i<5;i++){
        obstacles[i].x -= gameSpeed;
        
        if(obstacles[i].x<0){
            int xMax = 0;
            for(int j =0;j<5;j++){
                if(obstacles[j].x>xMax){
                    xMax = obstacles[j].x;
                }
            }
            obstacles[i].x = xMax + rand()%50 +100;
            obstacles[i].r = (float)(rand()%100)/100;
            obstacles[i].g = (float)(rand()%100)/100;
            obstacles[i].b = (float)(rand()%100)/100;
            
            score++;
        }
    }
    if(hit_check()){
        game_over();
    }
    return true;
}
gboolean set_time(){
    timeElapse += 1;
    return true;
}
gboolean set_game_speed(){
    if(gameSpeed < MAX_GAME_SPEED){
        
        gameSpeed++;
    }
    return true;
}
//游戏开始
void game_start(){

    if(gameStatus != STOP){
        return;
    }
    
    gameStatus = GOING;
    score = 0;
    timeElapse = 0;
    gameSpeed = 2;
    man_0.x = 30;
    man_0.y = 100;
    init_road();
    init_obstacle();
    timer_0 = g_timeout_add(30,(GSourceFunc)show_graph,window);
    timer_1 = g_timeout_add(30,(GSourceFunc)move_road,NULL);
    timer_2 = g_timeout_add(30,(GSourceFunc)move_obstacle,NULL);
    timer_score = g_timeout_add(1000,(GSourceFunc)set_time,NULL);
    timer_game_speed = g_timeout_add(20000,(GSourceFunc)set_game_speed,NULL);
    
}
//暂停/继续
void game_pause_continue(){
    if(gameStatus == PAUSE){
        gameStatus = GOING;
        timer_0 = g_timeout_add(30,(GSourceFunc)show_graph,window);
        timer_1 = g_timeout_add(30,(GSourceFunc)move_road,NULL);
        timer_2 = g_timeout_add(30,(GSourceFunc)move_obstacle,NULL);
        timer_score = g_timeout_add(1000,(GSourceFunc)set_time,NULL);
        timer_game_speed = g_timeout_add(20000,(GSourceFunc)set_game_speed,NULL);
    
    }else if(gameStatus == GOING){
        gameStatus = PAUSE;
        g_source_remove(timer_0);
        g_source_remove(timer_1);
        g_source_remove(timer_2);
        g_source_remove(timer_score);
        g_source_remove(timer_game_speed);
    }
}
//游戏结束
void game_over(){
    gameStatus = STOP;
    g_source_remove(timer_0);
    g_source_remove(timer_1);
    g_source_remove(timer_2);
    g_source_remove(timer_score);
    g_source_remove(timer_game_speed);
    //
    
    cairo_t *crr;
    crr = gdk_cairo_create (window->window);
    cairo_set_source_rgb(crr,0.5,0.7,0.9);
    cairo_set_font_size(crr,24);
    cairo_move_to(crr,50, 60);
    cairo_show_text(crr,"game over");
    cairo_stroke(crr);
    cairo_destroy(crr);
    
}
//开始界面
gboolean start_window(GtkWidget *widget){
    cairo_t *cr;
    cr = gdk_cairo_create (widget->window);
    //background
    cairo_set_source_rgb(cr,0.6,0,0.7);
    cairo_move_to(cr,0, 0);
    cairo_line_to(cr,X_WIDTH,0);
    cairo_line_to(cr,X_WIDTH,X_HEIGHT);
    cairo_line_to(cr,0,X_HEIGHT);
    cairo_stroke_preserve(cr);
    cairo_fill(cr);

    //game reference
    //char info[1000] = {'\0'};
    //sprintf(info,"Ctrl to start\nspace to jump\n p to pause/continue\n");
    cairo_set_source_rgb(cr,0.0,0.7,0.3);
    cairo_set_font_size(cr,24);
    cairo_move_to(cr,30, 40);
    cairo_show_text(cr,"Ctrl to start");
    cairo_move_to(cr,30, 90);
    cairo_show_text(cr,"Space to jump");
    cairo_move_to(cr,20, 130);
    cairo_show_text(cr,"p to pause/continue");
    cairo_stroke(cr);

    cairo_destroy(cr);
    //run only once
    return false;
}
//碰撞检测
gboolean hit_check(){
    gint nearestObstacleID;
    gint nearestDistance = 65535;
    for(int i=0;i<5;i++){
        if(abs(man_0.x-obstacles[i].x)<nearestDistance){
            nearestObstacleID = i;
            nearestDistance = abs(man_0.x-obstacles[i].x);
        }
    }
    //printf("%d\n",nearestObstacleID);
    //检测碰撞最近的障碍物
    if((man_0.x-obstacles[nearestObstacleID].x)*(man_0.x-obstacles[nearestObstacleID].x)+(man_0.y-100)*(man_0.y-100)<100){
        return true;
    }else{
        //printf("%d %d %d %d\n",man_0.x,obstacles[nearestObstacleID].x,man_0.y,100);
        return false;
    }
}



main.cpp

#include "common.h"
Man man_0;//游戏人物
Road roads[10];//路
Obstacle obstacles[5];//障碍物
GameStatus gameStatus;
gint score;//游戏分数
gint timeElapse;//游戏时间
gint gameSpeed;//游戏速度
guint timer_0,timer_1,timer_2,timer_score,timer_game_speed;//定时器
GtkWidget *window;
int main(int argc,char* argv[]){

    srand((unsigned int)time(0));
    gtk_init(&argc,&argv);
 
    if(!g_thread_supported())
    g_thread_init(NULL);//初始线程
    gdk_threads_init(); 
    window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(window),"Jumpman");
    gtk_window_set_default_size(GTK_WINDOW(window),X_WIDTH,X_HEIGHT);

    g_signal_connect(G_OBJECT(window),"destroy",G_CALLBACK(gtk_main_quit),NULL);
    //关联按键回调函数
    g_signal_connect(G_OBJECT(window),"key-press-event",G_CALLBACK(my_key_press),NULL);

    gtk_widget_show_all(window);
    gtk_widget_set_app_paintable(window,true);
    //启动游戏
    g_timeout_add(30,(GSourceFunc)start_window,window);
    game_over();
    
    gdk_threads_enter();
    gtk_main();
    gdk_threads_leave();
 
    return 0;
}