这次来介绍下游戏逻辑控制部分。
/*================= 华丽的干货分割线再次出现了 ==================*/
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代码、含义以及转移如下图所示:
从面向对象的角度看,每个游戏都是一个单例对象。启动一个游戏时,首先执行该游戏的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个像素。
做完后,效果是这样的:
看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 编辑
通断是声音文件控制的还是其他逻辑控制的?
引用: littleshrimp 发表于 2022-6-6 13:03 通断是声音文件控制的还是其他逻辑控制的?
是用player_play播放mem://addr协议
引用: nemon 发表于 2022-6-6 14:19 是用player_play播放mem://addr协议
你看一下是不是低层的DAC或者PA的PA_MUTE引脚时序没控制好。