[经验分享] 【平头哥RVB2601创意应用开发】掌上游戏机(4)一按就蹦

nemon   2022-6-6 12:45 楼主

这次来介绍下游戏逻辑控制部分。

/*================= 华丽的干货分割线再次出现了 ==================*/

YoC 平台(Yun on Chip)包含对整个系统架构的底层进行抽象包装,提供了CPU架构、芯片平台、操作系统、云服务和开发套件。

有了这个平台,我的游戏掌机,只需要响应按键、定时器、屏幕刷新请求等事件,并在事件驱动下进行逻辑处理并更新输出,就可以完整地执行预定义功能。

在前面分析的基础上,介绍一下本项目最燃的部分Game.h:

#ifndef _APP_GAME_H_
#define _APP_GAME_H_

typedef enum _GAME_EVENT{
	BTN_A  = 1<<0,  // A
	BTN_B  = 1<<1,  // B
	BTN_1  = 1<<2,  // up
	BTN_2  = 1<<3,  // left
	BTN_3  = 1<<4,  // down
	BTN_4  = 1<<5,  // right
	TIMEER = 1<<15, // timer
	DISPLAY= 1<<16  // display refresh
}game_event;

typedef enum _GAME_STAT{
	game_stat_welcome   = 0 ,  // 显示欢迎界面
	game_stat_playing   = 1 ,  // 游戏中
	game_stat_pause     = 2 ,  // 游戏暂停
	game_stat_game_over = 3 ,  // 游戏结束(失败)
	game_stat_game_win  = 4 ,  // 游戏结束(胜利)
	game_stat_force_exit= 1<<4 // 强制退出
}GAME_STAT;

typedef struct _GAME{
	char* name;
	char* author;
	char* label;
	char* version;
	GAME_STAT game_stat ;
	struct _GAME* (*game_init_handle)(void);
	GAME_STAT (*game_event_handle)(game_event event);
	void (*game_loop_handle)(void);
	void* data;
}game;

#endif

game_event预定义了按键A、B、1、2、3、4按下,以及定时器触发和请求刷新显示事件。父过程通过结构体_GAME中的(*game_event_handle)(game_event event)向子过程传递事件,并获取最新的GAME_STAT。

一个game,在不同的阶段有不同的状态。状态d代码、含义以及转移如下图所示:

状态转移.png

从面向对象的角度看,每个游戏都是一个单例对象。启动一个游戏时,首先执行该游戏的init函数,获得指向该游戏的结构体指针game。

之后有2种处理方法:用中断或是while(1);

如果用中断,则(1)执行game->game_loop_handle直到退出;(2)在事件对应的处理函数中调用game->game_event_handle,将事件传递给game;(3)维护一个game*的栈,把当前的game压进去,并在game退出时,恢复最新的顶部game。

如果使用while(1),则(1)while(1)要写在在main函数一级,也就是整个系统的最高层;(2)通过PIO、延时等方法判断是否触发事件,然后用game->game_event_handle,将事件传递给game,同时根据返回的GAME_STAT值,判断是否游戏已经退出。(3)由于这种情况下,需要父级game管理子级game,并在子级game退出时,接管输入输出。

 

我首先完成的是game_FlappyBird,从头文件可以看出,大部分的过程都是在画界面:

#ifndef _APP_game_FlappyBird_H_
#define _APP_game_FlappyBird_H_

#include "Image.h"
#include "Screen.h"
#include "Game.h"

//#define _APP_game_FlappyBird_DEBUG_ 

#define _GAME_FLAPPYBIRD_PILLAR_HEAD_WIDTH   (8)
#define _GAME_FLAPPYBIRD_PILLAR_HEAD_HEIGHT  (4)
#define _GAME_FLAPPYBIRD_PILLAR_BODY_WIDTH   (6)
#define _GAME_FLAPPYBIRD_PILLAR_SHADOW_WIDTH (1)

#define _APP_game_FlappyBird_RAISE_DISTANCE  (3)
#define _APP_game_FlappyBird_DROP_DISTANCE  (1)

void game_FlappyBird_draw_GAME_WELCOM_function();
void game_FlappyBird_draw_GAME_WIN_function();
void game_FlappyBird_draw_GAME_OVER_function();
void game_FlappyBird_set_position_flying_bird_function(int x0,int y0);
void game_FlappyBird_draw_flying_bird_function();
void game_FlappyBird_draw_ground_function();
void game_FlappyBird_draw_score_function(int score);
int game_FlappyBird_draw_and_check_hit_pillar_function();

void game_FlappyBird_restart_function();
game* game_FlappyBird_init_function();
GAME_STAT game_FlappyBird_event_function(game_event event);
void game_FlappyBird_loop_function(void);


#endif

有一个用于判断碰撞的函数不在头文件里,判断方法可称暴力加粗糙:

int game_FlappyBird_hit_test_function(int x0,int y0,int w0,int h0,int x1,int y1,int w1,int h1){
	if( ( x0<=x1+w1 && x1<=x0+w0 ) && ( y0<=y1+h1 && y1<=y0+h0 ) ){
		return 1 ;
	}else{
		return 0 ;
	}
}

其他的,昨天实际上已经讲到了,A、B按键,也就是板子上key2、key1,负责引发按键事件,按键事件里让小鸟跳上去 _APP_game_FlappyBird_RAISE_DISTANCE 个像素,Timer时间里让小鸟落下_APP_game_FlappyBird_DROP_DISTANCE个像素。

做完后,效果是这样的:

ad0ecd57af0bf26b36c292ed169e6844

后来想了一下,发现其实Game是个通用的应用管理结构,于是又做了一个game_MainMenu用于显示菜单和启动游戏。因为现在只做了1个Flappy Bird,所以10个菜单挂的是它。

看game_MainMenu.h,只定义了2个常量:屏幕最多显示7行选项、选项左边空出24像素(只为了不太难看)。

#ifndef _APP_game_MainMenu_H_
#define _APP_game_MainMenu_H_

#include "Image.h"
#include "Screen.h"
#include "Game.h"

#define _GAME_MAIN_MENU_LINE_COUNT_   (7)
#define _GAME_MAIN_MENU_LEFT_SPACE_   (24)

void game_MainMenu_restart_function();
game* game_MainMenu_init_function();
GAME_STAT game_MainMenu_event_function(game_event event);
void game_MainMenu_loop_function(void);

#endif

由于需要管理游戏,game_MainMenu的逻辑要复杂一些,先看最简单的,如何画菜单(十只鸟的菜单,label还一样,太乱了看不出在滚动,前边强行加一个序号区分一下):

		if( 0<(event & DISPLAY) ){
			screen_set_auto_refresh_flag_function(SCREEN_FALSE);
			screen_clean_function();
			//显示菜单
			screen_draw_string_function("PRESS\"A\"TO EXECUTE,\"B\"TO SLEECT",-1,3,8);
			int i,j;
			for(i=0;i<_GAME_MAIN_MENU_LINE_COUNT_;i++){
				if(i+1>game_MainMenu_GAMES_count){
					break;
				}
				j = ( i + game_MainMenu_GAMES_select_index + game_MainMenu_GAMES_count )%game_MainMenu_GAMES_count ;
				if(0==i){
					screen_draw_bitmap_function(  (bit_map*)&game_MainMenu_option_checked_img , 0 + _GAME_MAIN_MENU_LEFT_SPACE_ , 8*(1+i) ) ;
				}else{
					screen_draw_bitmap_function(  (bit_map*)&game_MainMenu_option_empty_img   , 0 + _GAME_MAIN_MENU_LEFT_SPACE_ , 8*(1+i) ) ;
				}
				screen_draw_Int32_function( j ,8 + _GAME_MAIN_MENU_LEFT_SPACE_ , 8*(1+i) );
				screen_draw_string_function( game_MainMenu_GAMES_array[j]->label , -1 , 16 + _GAME_MAIN_MENU_LEFT_SPACE_ , 8*(1+i)+8 );
			}
			screen_set_auto_refresh_flag_function(SCREEN_TRUE);
		}

当选中时,将gameMenu状态设置成游戏中,同时初始化对应的game:

		if( 0<(event & BTN_A) ){
			//run select game
			game_MainMenu_this.game_stat = game_stat_playing ;
			game_MainMenu_GAMES_array[game_MainMenu_GAMES_select_index]->game_init_handle();
		}

一旦进入到“游戏中”状态,就说明,所有的事件都应该传给当前的game,顺便检查一下,要是已经退出了,那就把gameMenu的状态改回来:

	if(game_stat_playing == game_MainMenu_this.game_stat){
		//已启动子过程
#ifndef _APP_RUN_GAME_AS_INTER_MODE_
		int ev = 0;
        //aos_msleep(100);//#include "aos/kernel.h"
		ev = event | TIMEER ; //TIMEER | DISPLAY;
		
		if(GPIO_PIN_LOW == csi_gpio_pin_read(&key2)) {//当低电平(按下时)
			ev = BTN_A | ev;
		}
		if(GPIO_PIN_LOW == csi_gpio_pin_read(&key1)) {//当低电平(按下时)
			ev = BTN_B | ev;
		}
		int x = game_MainMenu_GAMES_array[game_MainMenu_GAMES_select_index]->game_event_handle( ev );
		if(game_stat_force_exit == x){
			game_MainMenu_this.game_stat = game_stat_welcome;
		}
#else
		int x = game_MainMenu_GAMES_array[game_MainMenu_GAMES_select_index]->game_event_handle( event );
		if(game_stat_force_exit == x){
			game_MainMenu_this.game_stat = game_stat_welcome;
		}
#endif	
	}

经过这么一番改造,掌机就不是只能玩一种游戏了。效果如下:


 

 

暂时还是只能玩鸟,但是雏形已经出来了。

顺便说一下,已经处理了一个声音文件,大小只有4608字节。本来想把声音效果加进去,后来在播放时发现喇叭的通断破音,比原本的音频还响。一时没有解决办法。哪位朋友能帮忙指点一下吗?

 

 

 

本帖最后由 nemon 于 2022-6-6 12:43 编辑

回复评论 (3)

通断是声音文件控制的还是其他逻辑控制的?

点赞  2022-6-6 13:03
引用: littleshrimp 发表于 2022-6-6 13:03 通断是声音文件控制的还是其他逻辑控制的?

是用player_play播放mem://addr协议

点赞  2022-6-6 14:19
引用: nemon 发表于 2022-6-6 14:19 是用player_play播放mem://addr协议

你看一下是不是低层的DAC或者PA的PA_MUTE引脚时序没控制好。

虾扯蛋,蛋扯虾,虾扯蛋扯虾
点赞  2022-6-6 16:26
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复