作品名称:双人格斗游戏机
作者: 北方
一、作品简介
原计划使用lvgl制作一个双人格斗游戏机,实现双人对战的格斗模式。控制格斗采用光学ToF传感器阵列进行手势识别,输出上下左右的控制动作。但是经过测试,lvgl的动态性能表现不足,比较适合静态图像。如果提高动态效果,一般可以使用animation功能,或者占用过多的内存,或者动作很慢。所以最终采用自定义Simple Game Engine的方式使用STM32F70独有的LTDC新型双页面刷新。
制作两个动态形态,互相格斗,当接近有重叠后,就是击中,根据主动性判读胜负,积分。
二、系统框图
三、各部分功能说明
3.1 显示内存定义,使用两个数组指针,分别为38400和12800,接收两个图层的数据,使用lcd_config定义为RGB565格式并且设定交叠后的透明度。
/* Layer1 Configuration ------------------------------------------------------*/
/* Windowing configuration */
pLayerCfg.WindowX0 = 0;
pLayerCfg.WindowX1 = 320;
pLayerCfg.WindowY0 = 0;
pLayerCfg.WindowY1 = 80;
/* Pixel Format configuration*/
pLayerCfg.PixelFormat = LTDC_PIXEL_FORMAT_RGB565;
/* Start Address configuration : frame buffer is located at FLASH memory */
pLayerCfg.FBStartAdress = (uint32_t)&Image_background;
/* Alpha constant (255 totally opaque) */
pLayerCfg.Alpha = 200;
/* Default Color configuration (configure A,R,G,B component values) */
pLayerCfg.Alpha0 = 0;
pLayerCfg.Backcolor.Blue = 0;
pLayerCfg.Backcolor.Green = 0;
pLayerCfg.Backcolor.Red = 0;
/* Configure blending factors */
pLayerCfg.BlendingFactor1 = LTDC_BLENDING_FACTOR1_PAxCA;
pLayerCfg.BlendingFactor2 = LTDC_BLENDING_FACTOR2_PAxCA;
/* Configure the number of lines and number of pixels per line */
pLayerCfg.ImageWidth = 320;
pLayerCfg.ImageHeight = 240;
/* Layer2 Configuration ------------------------------------------------------*/
/* Windowing configuration */
pLayerCfg1.WindowX0 = 0;
pLayerCfg1.WindowX1 = 320;
pLayerCfg1.WindowY0 = 0;
pLayerCfg1.WindowY1 = 80;
/* Pixel Format configuration*/
pLayerCfg1.PixelFormat = LTDC_PIXEL_FORMAT_RGB565;
/* Start Address configuration : frame buffer is located at FLASH memory */
pLayerCfg1.FBStartAdress = (uint32_t)&Image_combat_320x80;
/* Alpha constant (255 totally opaque) */
pLayerCfg1.Alpha = 128;
/* Default Color configuration (configure A,R,G,B component values) */
pLayerCfg1.Alpha0 = 0;
pLayerCfg1.Backcolor.Blue = 0;
pLayerCfg1.Backcolor.Green = 0;
pLayerCfg1.Backcolor.Red = 0;
/* Configure blending factors */
pLayerCfg1.BlendingFactor1 = LTDC_BLENDING_FACTOR1_PAxCA;
pLayerCfg1.BlendingFactor2 = LTDC_BLENDING_FACTOR2_PAxCA;
/* Configure the number of lines and number of pixels per line */
pLayerCfg1.ImageWidth = 320;
pLayerCfg1.ImageHeight = 80;
3.2 运行逻辑
初始化文件后,启动图册,两个avatar分别移动,一个受机器随机函数控制,另一个受板载user button控制。
当碰撞后,执行碰撞监测的响应程序。
非常精简地在while loop中完成。
HAL_LTDC_ProgramLineEvent(&LtdcHandle, 0);
HAL_LTDC_SetWindowPosition_NoReload(&LtdcHandle, 80, 32, 0);
/* Infinite loop */
while (1)
{
//y1_ctl = 10 ; y1_ctl++; //Image_combat_320x80[y1_ctl]=0x00000000;
cnt = Render(x1_ctl , y1_ctl, x2_ctl, y2_ctl);
Collision(cnt);
//Show the Data
HAL_LTDC_SetWindowPosition_NoReload(&LtdcHandle, 80, 180, 1);
ReloadFlag = 0;
HAL_LTDC_Reload(&LtdcHandle,LTDC_RELOAD_VERTICAL_BLANKING);
while(ReloadFlag == 0) { }
HAL_Delay(200);
}
四、作品源码
部分核心代码如下
int main(void)
{
x1_ctl =0; y1_ctl =0; x2_ctl =120; y2_ctl =0;
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct;
//uint32_t index = 0;
/* Enable the CPU Cache */
CPU_CACHE_Enable();
/* STM32F7xx HAL library initialization:
- Configure the Flash ART accelerator on ITCM interface
- Systick timer is configured by default as source of time base, but user
can eventually implement his proper time base source (a general purpose
timer for example or other time source), keeping in mind that Time base
duration should be kept 1ms since PPP_TIMEOUT_VALUEs are defined and
handled in milliseconds basis.
- Set NVIC Group Priority to 4
- Low Level Initialization
*/
HAL_Init();
/* Configure the system clock to 216 MHz */
SystemClock_Config();
/*## LTDC Clock Configuration ###########################################*/
/* PLLSAI_VCO Input = HSE_VALUE/PLL_M = 1 Mhz */
/* PLLSAI_VCO Output = PLLSAI_VCO Input * PLLSAIN = 192 Mhz */
/* PLLLCDCLK = PLLSAI_VCO Output/PLLSAIR = 192/5 = 38.4 Mhz */
/* LTDC clock frequency = PLLLCDCLK / LTDC_PLLSAI_DIVR_4 = 38.4/4 = 9.6Mhz */
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LTDC;
PeriphClkInitStruct.PLLSAI.PLLSAIN = 192;
PeriphClkInitStruct.PLLSAI.PLLSAIR = 5;
PeriphClkInitStruct.PLLSAIDivR = RCC_PLLSAIDIVR_4;
HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct);
/* Configure LED1 */
BSP_LED_Init(LED1);
BSP_PB_Init(BUTTON_KEY, BUTTON_MODE_GPIO);
/*##-1- LCD Configuration ##################################################*/
/* Configure 2 layers w/ Blending and CLUT loading for layer 1 */
LCD_Config();
/*##-2- CLUT Configuration #################################################*/
//HAL_LTDC_ConfigCLUT(&LtdcHandle, (uint32_t *)L8_320x240_CLUT, 256, 0);
/*##-3- Enable CLUT For Layer 1 ############################################*/
//HAL_LTDC_EnableCLUT(&LtdcHandle, 0);
/*##-4- Configure line event ###############################################*/
HAL_LTDC_ProgramLineEvent(&LtdcHandle, 0);
HAL_LTDC_SetWindowPosition_NoReload(&LtdcHandle, 80, 32, 0);
/* Infinite loop */
while (1)
{
//y1_ctl = 10 ; y1_ctl++; //Image_combat_320x80[y1_ctl]=0x00000000;
cnt = Render(x1_ctl , y1_ctl, x2_ctl, y2_ctl);
Collision(cnt);
//Show the Data
HAL_LTDC_SetWindowPosition_NoReload(&LtdcHandle, 80, 180, 1);
ReloadFlag = 0;
HAL_LTDC_Reload(&LtdcHandle,LTDC_RELOAD_VERTICAL_BLANKING);
while(ReloadFlag == 0) { }
HAL_Delay(200);
}
}
/**
* @brief calculate pictures position.
* @param x1: picture1 x position
* @param y1: picture1 y position
* @param x2: picture2 x position
* @param y2: picture2 y position
* @param index:
* @retval None
*/
static int Render(uint32_t x1, uint32_t y1, uint32_t x2, uint32_t y2)
{
uint32_t cnt_collision=0;
uint32_t ix,iy;
for (iy = 0; iy < 80; iy++)
{
for (ix = 0; ix < 160; ix++)
{
Image_combat_320x80[160*iy + ix]=0xFFFFFFFF;
}
for (ix = 0; ix < 30; ix++)
{
Image_combat_320x80[ix + x1 + 160*iy] = Image02_60x80[ix+30*iy];
if ( Image_combat_320x80[ix + x2 + 160*iy] != 0xFFFFFFFF) {
cnt_collision = cnt_collision + 1;
}
Image_combat_320x80[ix + x2 + 160*iy] = Image01_60x80[ix+30*iy];
}
}
//BSP_LED_On(LED1);
return cnt_collision;
}
static void update_ctl(void)
{
//BSP_LED_On(LED1);
int btn =0;
int cnt_rand;
//srand(2);
cnt_rand = -1 + rand () % 3;
btn = BSP_PB_GetState(BUTTON_KEY);
y1_ctl =0;
y2_ctl =0;
x1_ctl=( x1_ctl + btn ) % 160;
x2_ctl=( x2_ctl + cnt_rand ) % 160;
}
static void Collision(uint32_t cnt)
{
//BSP_LED_On(LED1);
if ( cnt > 0 ){
//beep();
x1_ctl = x1_ctl - 10;
}
update_ctl();
}
五、作品功能演示视频
六、项目总结
6.1 基本动态制作。
初始测试,使用inkscape矢量生产动态图片。
使用lcd-image-converter转换成RGB565格式的头文件,
6.2 基于LTDC的页面加载两个avatar,效果如下
6.3 使用这个过程启动代码,生成两个图层,一个是背景,这里使用大赛logo,另一个是战斗平台。作为初始版本,在有限的空间,单轴二维输出,使用320x80的屏幕。
七、其他
这样基础版本的自定义gameEngine从通常的角度看非常简陋,但是这个完成了完整的GE引擎流程,从定义并建立显示存储控制空间,进行线性和二维转换,分区填充,分层绘图,建模,渲染,碰撞监测,外设驱动的输入和自动的Robot等待被锤(虽然只是用了随机函数,但是切换到AI对战可以无缝衔接),这样的流程大概有10多个步骤。这个步骤的实现,和Epic这样的巨著是没有根本区别的,只是性能,效率和效果差距巨大。
这样的玩法还不如用lvgl好使,但是有巨大的优势,在这个项目里,刷新的频率是可以自定义的。刷新频率已经可以定义到50ms,使用HAL_delay(50)简单完成任务。
这样的扩展性还有很多体验。本次提交算是基本作业,如果时间可以,可以完善这个项目到比较酷炫的情况。后续继续搞一下。
ps,主要函数和流程的代码还是贴出来的好
开发板的资料
https://www.st.com/en/evaluation-tools/stm32f7508-dk.html#st-also-like
数据特性可以参考一下。