一、前言

上文通过菜单设计演示U8G2图形库的基础用法(【STM32】U8G2图形库应用–菜单设计与开发 ),本文将借助两个小游戏案例,对比多种显示控制方案,从而理解U8G2图形库的优势领域使用场景

在这里插入图片描述


二、系统介绍


1. 项目实现的功能


(1)小恐龙游戏:使用IIC通信和U8G2图形库显示方式;只使用IIC通信分别测试“全屏刷新”与“区域刷新”两种显存更新方式。

(2)贪吃蛇游戏:使用IIC通信和U8G2图形库显示方式;只使用IIC通信“区域刷新”显存更新方式。


2. 项目硬件组成


(1)主控芯片:该项目使用的是STM32F103RCT6。

(2)OLED显示屏:本模块搭载的是SSD1306驱动芯片,采用IIC接口,分辨率是128*64。

(3)按键:机械式轻触开关。



三、项目软件开发


1. 小恐龙游戏的显示控制方案对比


(1)下文基于U8G2图形库的全缓冲模式,简要说明小恐龙游戏的代码思路。

在这里插入图片描述

小恐龙游戏显示思路:

清全屏缓冲
绘制分数变量到缓冲。由于U8G2图形库没有直接显示变量参数,需要将变量转成字符串,再通过绘制字符串实现分数显示。
绘制背景元素(地面、云、太阳)到缓冲。通过偏移量循环绘制地面与云层以实现左移滚动效果;同样随机绘制太阳。
根据按键操作绘制恐龙跳跃障碍物或平地走动的图形到缓冲。跳跃时按轨迹更新垂直坐标实现上下移动动画;行走时交替绘制两帧位图实现循环走动效果。
绘制仙人掌图形到缓冲。更新仙人掌水平坐标实现左移,当完全移出屏幕后,重置其位置并随机选择新的仙人掌形态重新生成。
最后发送缓冲区数据,完成画面刷新

//======================================//
/*          绘制恐龙游戏页面             */
//======================================//
void Display_Dinosaur_Game(void)
{
    char string_buff[11];  /* 用于存储变量转换的字符串 */	
	
    Dino_Init();	
			
    while(1)		
    {		
    	// 游戏结束
    	if(dino_failed_flag==1)
    	{			
    	    Dino_Draw_Restart();

    	    if(key_value == KEY_CONFIRM_PRESSED5)	/* 重启游戏 */
    	    {
    	    	Dino_Init();
	    	key_value=0;				
	    }		
	}
	// 开始游戏
	else
	{
	    u8g2_ClearBuffer(&u8g2);			
			
	    // --- 显示当前分数和最高分数
	    dino_score++;			

	    snprintf(string_buff,sizeof(string_buff),"%d",dino_score);  /* %d : 10进制 */
	    u8g2_DrawStr(&u8g2,0,12,string_buff);
			
	    snprintf(string_buff,sizeof(string_buff),"%d",dino_highest_score);
	    u8g2_DrawStr(&u8g2,64,12,string_buff);			
			
	    // --- 复位标志位
	    if(dino_height==0)
	    	dino_reset_flag = 0;
			
	    // --- 显示地面
	    Dino_Draw_Ground();	
			
	    // --- 显示云
	    Dino_Draw_Cloud();			
			
	    // --- 显示太阳
	    if(rand()%500==250||sun_over_flag==1)  /* 生成0到499之间整数是否为250 */
	    {	
	    	if(Dino_Draw_Sun())
	    	    sun_over_flag=1;
	    	else 
	    	    sun_over_flag=0;
	    }
			
	    // --- 显示恐龙
	    if(dino_height > 0 || key_value == KEY_UP_PRESSED1)  /* 恐龙跳起来 */
	    {			
	    	dino_height = Dino_Draw_Jump(dino_reset_flag);
	   	key_value = 0;
	    }
	    else  /* 恐龙不跳 */
	    {
	   	 Dino_Draw_Walk();
	    }
			
	    // --- 显示仙人掌
	    if(cactus_type==0 )
	    	cactus_width=8;
	    else if(cactus_type==1 || cactus_type==3)
	    	cactus_width=16;
	    else if(cactus_type==2 || cactus_type==4)
	    	cactus_width=24;			
	
	    cactus_pos_x=Dino_Draw_Rand_Cactus(cactus_type,0);	/* 获取仙人掌坐标并显示仙人掌 */			
			
	    if(cactus_pos_x + cactus_width < 0) 	/* 仙人掌左移超出显示范围 */
	    {
	   	cactus_type = rand()%5;  		/* 0~4随机生成 */
	    	Dino_Draw_Rand_Cactus(cactus_type,1);
	    }
			
	    // --- 判断是否触发失败条件
	    if(dino_height<16 && cactus_pos_x<=24)   /* 恐龙跳起高度小于阻挡物体高度 && 阻挡物体起始x坐标小于恐龙末x坐标 */
	    {
	   	 if(cactus_type==0 && cactus_pos_x>=0)  /* 阻挡物末x坐标大于恐龙起始x坐标 */
	    	    dino_failed_flag=1;
	    	else if((cactus_type==1 || cactus_type==3) && cactus_pos_x>=-8)
	    	    dino_failed_flag=1;
	    	else if((cactus_type==2 || cactus_type==4) && cactus_pos_x>=-16)
	    	    dino_failed_flag=1;
	    }					
	}

	u8g2_SendBuffer(&u8g2);
    }
}

其中Dino_Init函数是将参数恢复至初始值;Dino_Draw_Restart函数是绘制游戏结束界面。

碰撞检测逻辑(同时满足以下条件触发游戏失败):

垂直方向判定:恐龙的Y坐标低于障碍物顶部高度。
水平方向判定:恐龙右侧末端坐标大于障碍物左侧起始坐标,且恐龙左侧起始坐标小于障碍物右侧末端坐标。

在这里插入图片描述

(2)仅采用IIC通信实现的“全屏刷新”不需要清全屏显存,只需要修改要更新的显存,其他的逻辑与U8G2库底层机制一致,因此刷新频率相当。

主要区别在于开发效率:而仅采用IIC通信的需手动编写底层显示驱动代码,而U8G2通过封装好的API函数简化了流程,无需关注底层细节,从而显著提升开发速度。

(3)仅采用IIC通信的“区域刷新”显存更新方式,核心思路与前述逻辑类似,但通过摒弃全屏清屏与整帧更新操作,仅针对变动区域进行局部数据写入与即时显示,显著降低了数据传输量,从而实现了比前两种方式更快的刷新速率。

不过,这种控制方式要求开发者手动编写底层显示驱动代码,无法直接依赖现成库函数,因此在提升性能的同时也增加了开发复杂度


2. 贪吃蛇的显示控制方案对比


(1)下文基于U8G2图形库的全缓冲模式,简要说明贪吃蛇游戏的代码思路。

在这里插入图片描述

贪吃蛇游戏显示思路

清全屏缓冲
绘制分数变量到缓冲
绘制边界到缓冲。明确界定蛇移动区域范围。
绘制食物和蛇到缓冲。需要获取最新的坐标参数。
最后发送缓冲区数据,完成画面刷新

//=======================================//
/*          绘制贪吃蛇游戏页面            */
//=======================================//
#define MOVE_DELAY 5  		/* 控制移动速度 */

void Display_Snake_Game(void)
{
    uint8_t snake_move_delay=0;	/* 移动速度 */
    char string_buff[3];		/* 用于存储变量转换的字符串 */	
		
    Snake_Init();
	
    while(1)
    {		
    	// 游戏结束
    	if(snake_failed_flag)
    	{
    	    Snake_Draw_Restart();			

    	    if(key_value == KEY_CONFIRM_PRESSED5) /* 重启游戏 */
    	    {	
    	    	snake_move_delay=0;
    	    	Snake_Init();				
    	    	key_value=0;				
    	    }	
    	}
    	// 开始游戏
    	else
    	{	
    	    u8g2_ClearBuffer(&u8g2);			
			
    	    // --- 显示当前分数和最高分数
    	    snprintf(string_buff,sizeof(string_buff),"%d",snake_score);  /* %d : 10进制 */
    	    u8g2_DrawStr(&u8g2,0,12,string_buff);
			
    	    snprintf(string_buff,sizeof(string_buff),"%d",snake_highest_score);
    	    u8g2_DrawStr(&u8g2,64,12,string_buff);
	
    	    // --- 显示边界
    	    u8g2_DrawFrame(&u8g2,0,15,u8g2_GetDisplayWidth(&u8g2),u8g2_GetDisplayHeight(&u8g2)-15);	
			
    	    // --- 显示食物
    	    u8g2_DrawXBMP(&u8g2,snake_food.x*8,snake_food.y*8,8,8,BMP_SNAKE_FOOD); 			
			
    	    // --- 显示蛇
    	    Snake_Draw_Body();
			
    	    // --- 确定移动方向
    	    Snake_Move_Direction();
			
    	    // --- 移动蛇
    	    snake_move_delay++;			
    	    if(snake_move_delay > MOVE_DELAY) /* 每隔MOVE_DELAY移动一次 */
    	    {
    	    	snake_move_delay=0;
    	    	Snake_Move();		/* 移动一个单位 */
    	    }
			
    	}		
    	u8g2_SendBuffer(&u8g2);			
    }	
}

其中各函数功能的描述如下:
Snake_Init‌初始化游戏状态,重置食物坐标,蛇身坐标、长度及方向等参数至初始值。
Snake_Draw_Restart‌:用于显示游戏结束界面。
Snake_Move_Direction‌:响应按键输入以更新移动方向,并执行反向校验,禁止蛇头直接掉头(即新方向不得与当前方向相反)。
Snake_Move‌计算蛇头下一帧坐标,执行碰撞检测(判断是否撞墙或自撞)及食物检测;若吃到食物,则触发增长逻辑并生成新食物坐标。

在这里插入图片描述

(2) 仅采用IIC通信下的“全屏刷新”与“区域刷新”在底层传输效率上存在差异,但对于低频或静态画面而言,这种性能差距并不显著。通过合理调节软件延时(如代码中的MOVE_DELAY参数),即可平衡视觉帧率,使不同刷新模式在人眼感知上达到基本一致的流畅度。



四、总结


以下为三种方式的优劣势对比。

在这里插入图片描述

从表格可见, 根据不同场景选择合适的控制显示方式

测试视频




在这里插入图片描述

搜索关注「YouziTech」 ,获取完整代码!

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐