【STM32】是否使用U8G2图形库?从小游戏项目看开发差异

【STM32】是否使用U8G2图形库?从小游戏项目看开发差异

  • 一、前言
  • 二、系统介绍
    • [1. 项目实现的功能](#1. 项目实现的功能)
    • [2. 项目硬件组成](#2. 项目硬件组成)
  • 三、项目软件开发
    • [1. 小恐龙游戏的显示控制方案对比](#1. 小恐龙游戏的显示控制方案对比)
    • [2. 贪吃蛇的显示控制方案对比](#2. 贪吃蛇的显示控制方案对比)
  • 四、总结

一、前言

上文通过菜单设计演示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图形库没有直接显示变量参数,需要将变量转成字符串,再通过绘制字符串实现分数显示。

● ++绘制背景元素(地面、云、太阳)到缓冲++ 。通过偏移量循环绘制地面与云层以实现左移滚动效果;同样随机绘制太阳。

● ++根据按键操作绘制恐龙跳跃障碍物或平地走动的图形到缓冲++ 。跳跃时按轨迹更新垂直坐标实现上下移动动画;行走时交替绘制两帧位图实现循环走动效果。

● ++绘制仙人掌图形到缓冲++ 。更新仙人掌水平坐标实现左移,当完全移出屏幕后,重置其位置并随机选择新的仙人掌形态重新生成。

● ++最后发送缓冲区数据,完成画面刷新++。

c 复制代码
//======================================//
/*          绘制恐龙游戏页面             */
//======================================//
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图形库的全缓冲模式,简要说明贪吃蛇游戏的代码思路。

贪吃蛇游戏显示思路 :

● ++清全屏缓冲++ 。

● ++绘制分数变量到缓冲++ 。

● ++绘制边界到缓冲++ 。明确界定蛇移动区域范围。

● ++绘制食物和蛇到缓冲++ 。需要获取最新的坐标参数。

● ++最后发送缓冲区数据,完成画面刷新++。

c 复制代码
//=======================================//
/*          绘制贪吃蛇游戏页面            */
//=======================================//
#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」 ,获取完整代码!