Skip to content

Latest commit

 

History

History
403 lines (360 loc) · 13.8 KB

example_tetris_cn.md

File metadata and controls

403 lines (360 loc) · 13.8 KB

C 库使用 - 俄罗斯方块

俄罗斯方块

以 tetris 游戏为例,介绍一下在 C 语言运行环境下,如何实现一个多 stage 的 tetris 游戏

效果图

  • 启动界面

  • 难度选择

  • 挑战级别

  • 游戏界面

参考数据

参考数据
-Os 优化 -Oz 优化 备注
配置 主频 80MHz 80MHz ——
优化等级 -Os -lto -Oz -lto ——
屏幕尺寸 240 * 240 240 * 240 ——
PFB 尺寸 240 * 4 240 * 4 ——
0x600 0x600 ——
0x200 0x200 ——
rt heap 0x2400 0x2400 ——
性能 启动界面 FPS 21:46ms 19:51ms 固定刷新区域:160 * 90
难度选择 FPS 24:41ms 21:46ms 固定刷新区域:160 * 90
挑战级别 FPS 71:14ms 62:16ms 固定刷新区域:30 * 66
游戏界面 FPS 62:15ms 59:16ms ——
程序大小 Code 44484 39248 ——
RO-data 138220 138224 240x240 的背景图片(115200)、16x24 字库文件、6x8 字库文件、A4_DIGITS_ONLY 字库文件
RW-data 308 312 ——
ZI-data 18908 18896 包括 LCD buffer、PFB pool、rt heap、rt stack、stack、heap、tetris resource 等

代码分析

tetris 案例分为两个 C 文件,分别是:tnsq_tetris_draw.c 用于绘制游戏图层,tnsq_tetris_logic.c 处理游戏逻辑,以下讨论仅限于绘制游戏图层,不涉及游戏机制

  • TinySquare 提供了七个基础图层,分别是:背景颜色图层、背景图层、基本单元图层、用户单元图层、文本图层、菜单图层、数字列表图层

  • 在进行图层绘制前,需要先进行显示适配器的初始化

    disp_adapter0_init();
  • 同时,为界面申请一个 stage

    tnsq_gfx_stage_cfg_t tGameStageCFG = {
        .ptDispAdapter = {
            .ptPlayer = &DISP0_ADAPTER,
            .ptPlayerTask = disp_adapter0_task,
        },
    };
    
    tnsq_gfx_stage_t *ptGameStage = tnsq_gfx_stage_init(&tGameStageCFG);
    if (ptGameStage == NULL)
    {
        /* error handle */
        printf("game stage init failed\n");
        return NULL;
    }
    else
    {
        return ptGameStage;
    }
  • 接下来绘制开始界面,这里需要用到两个图层,一个是背景颜色图层、一个是菜单图层

    arm_2d_size_t tScreenSize = tnsq_gfx_get_screen_size(&DISP0_ADAPTER);
        
    arm_2d_size_t tItemSize = {
        .iWidth = 160,
        .iHeight = 30,
    };
    
    rt_uint8_t chShowItemNum = 3;
    
    do {
        tnsq_gfx_layer_bg_cl_cfg_t tGameBGCLCFG = {
            .tType = TNSQ_GFX_BG_CL_NORMAL,
            .chOpacity = 255,
            .tRegion = {
                .tSize = tScreenSize,
            },
            .tColor = __RGB(0x6d, 0x54, 0x84),
        };
        tnsq_gfx_layer_bg_cl_t *ptGameBGCL = tnsq_gfx_layer_bg_cl_init(&tGameBGCLCFG);
        if (ptGameBGCL == NULL)
        {
            printf("menu layer init failed\n");
        }
        
        tnsq_gfx_register_layer_to_stage(ptStage, ptGameBGCL);
    } while (0);
    
    do {
         static const char *pchStringTable[] = {
            "start",
            "difficulty",
            "challenge"
        };
        tnsq_gfx_layer_menu_cfg_t tMenuCFG = {
            .tItemGeneral = {
                .chStringCount = sizeof(pchStringTable) >> 2,
                .pchStringTable = pchStringTable,
                .tItemSize = tItemSize,
                .chShowItemNum = chShowItemNum,
                .nFinishInMs = 150,
                .ptFont = (struct arm_2d_font_t *)&ARM_2D_FONT_16x24,
            },
            .tItemNormal = {
                .tColor = {
                    .box = __RGB(0x6d, 0x54, 0x84),
                    .font = __RGB(0x94, 0xd2, 0x52),
                },
                .chOpacity = 255,
            },
            .tItemSelected = {
                .tColor = {
                    .box = __RGB(0xff, 0xff, 0xff),
                    .font = __RGB(0x6d, 0x54, 0x84),
                },
                .chOpacity = 255,
            },
        };
        
        tnsq_gfx_layer_menu_t *ptMenuLayer = tnsq_gfx_layer_menu_init(&tMenuCFG);
        if (ptMenuLayer == NULL)
        {
            printf("menu layer init failed\n");
        }
        
        tnsq_gfx_register_layer_to_stage(ptStage, ptMenuLayer);
    } while (0);
    • 对于背景颜色图层,通过 tGameBGCLCFG 指定了它的区域、颜色、不透明度。在初始化图层之后,将其注册到指定的 stage 中去
    • 对于菜单图层,通过 tMenuCFG 指定菜单项的内容(这是通过一个 const char **)来实现的。其次,需要指定每一个 item 的大小,上下的 padding,切换间隔、字体等,被选中与不被选中时,也可以自定义其表现形式
    • 当用户事件触发之后,调用相关的函数就能获取对应被选中的 item 了
      chItemIdx = tnsq_gfx_layer_menu_get_item_idx(menuLayerSelectPtr);
  • 对于数字列表图层,则如下进行初始化

    do {
        tnsq_gfx_layer_num_cfg_t tNumCFG = {
            .chNum = 10,
            .chShowItemNum = 3,
            .nFinishInMs = 100,
            .tColor = {
                .background = __RGB(0x6d, 0x54, 0x84),
                .font = __RGB(0x94, 0xd2, 0x52),
            },
            .tPadding = {
                .pre = 3,
                .next = 3,
            },
            .ptFont = (arm_2d_font_t *)&ARM_2D_FONT_A4_DIGITS_ONLY,
        };
        
        tnsq_gfx_layer_num_t *ptNumLayer = tnsq_gfx_layer_num_init(&tNumCFG);
        if (ptNumLayer == NULL)
        {
            printf("num layer init failed\n");
        }
        
        tnsq_gfx_register_layer_to_stage(ptStage, ptNumLayer);
    } while (0);
    • 相对于菜单图层来说,数字图层仅需要指定数字的个数即可,使用非常方便。当然,起始数字和数字之间的间隔也是可以通过 tnsq_gfx_layer_num_cfg_t 配置项指定的,这些就留给用户自己尝试了
  • 当需要进行 stage 切换时,需要先初始化一个stage,在进行切换,切换完成后,可以申请刷新查看效果

    ptStage = tetris_game_stage_init();
        
    arm_2d_scene_player_switch_to_next_scene(&DISP0_ADAPTER);
    
    tnsq_gfx_apply_for_refresh();
  • 对于背景图层的初始化,仅需要指定对应的资源和对应的 Region 即可。关于如何将图片转化为引擎可用的资源,可以参考 Arm-2D 的相关介绍文档

    tnsq_gfx_layer_bg_cfg_t tGameBGCFG = {
        .ptBackGround = &c_tilebg_tetrisRGB565,
        .ptBackGroundMask = NULL,
        .tRegion = {
            .tSize = c_tilebg_tetrisRGB565.tRegion.tSize,
        },
    };
    
    tnsq_gfx_layer_bg_t *ptGameBG = tnsq_gfx_layer_bg_init(&tGameBGCFG);
    if (ptGameBG == NULL)
    {
        printf("bg layer init failed\n");
    }
    
    tnsq_gfx_register_layer_to_stage(ptStage, ptGameBG);
  • 当需要显示游戏分数时,就需要引入文本图层。文本图层需要指定字体、颜色、不透明度,以及对应的 Region

    arm_2d_size_t tScreenSize = tnsq_gfx_get_screen_size(&DISP0_ADAPTER);
        
    tnsq_gfx_layer_text_cfg_t tTextCFG = {
        .chOpacity = 255,
        .ptFont = &ARM_2D_FONT_6x8.use_as__arm_2d_font_t,
        .tColour = {
            .tForeground = GLCD_COLOR_GREEN,
        },
        .tRegion = {
            .tLocation = {
                .iX = TETRIS_SCORE_Y * (tScreenSize.iWidth / TETRIS_X_COUNT),
                .iY = TETRIS_SCORE_X * (tScreenSize.iHeight / TETRIS_Y_COUNT),
            },
            .tSize = {
                .iWidth = (tScreenSize.iWidth / TETRIS_X_COUNT) * 4,
                .iHeight = (tScreenSize.iHeight / TETRIS_Y_COUNT) * 2,
            },
        }
    };
    
    tnsq_gfx_layer_text_t *ptGameTextLayer = tnsq_gfx_layer_text_init(&tTextCFG);
    if (ptGameTextLayer == NULL)
    {
        printf("text layer init failed\n");
    }
    
    tnsq_gfx_register_layer_to_stage(ptStage, ptGameTextLayer);
  • 文本图层的使用也非常简单,只需要使用类似 printf 的格式化输出即可

    tnsq_gfx_layer_text_printf(textLayerPtr, "%d", score);
  • 最后看一下用户单元图层。用户单元图层与基本单元图层类似,只不过用户单元图层有非常灵活的自定义空间,因为用户单元图层需要自己指定绘制函数,如下所示,只需要根据不同的 idx,即可绘制对应的内容

    static void _tetris_user_map_func(rt_uint8_t idx, arm_2d_tile_t const *ptTile, const rt_bool_t bIsNewFrame)
    {
        arm_2d_canvas(ptTile, __user_map_canvas)
        {
            switch (idx) {
            case 0:
                arm_2dp_fill_colour_with_opacity
                (
                    NULL,
                    ptTile,
                    &__user_map_canvas,
                    (__arm_2d_color_t){GLCD_COLOR_DARK_GREY},
                    128
                );
                break;
            case 1:
                draw_round_corner_box(ptTile, &__user_map_canvas, GLCD_COLOR_BLUE, 255, bIsNewFrame);
                break;
            case 2:
                draw_round_corner_box(ptTile, &__user_map_canvas, GLCD_COLOR_GREEN, 255, bIsNewFrame);
                break;
            case 3:
                draw_round_corner_box(ptTile, &__user_map_canvas, GLCD_COLOR_CYAN, 255, bIsNewFrame);
                break;
            case 4:
                draw_round_corner_box(ptTile, &__user_map_canvas, GLCD_COLOR_RED, 255, bIsNewFrame);
                break;
            case 5:
                draw_round_corner_box(ptTile, &__user_map_canvas, GLCD_COLOR_YELLOW, 255, bIsNewFrame);
                break;
            case 6:
                draw_round_corner_box(ptTile, &__user_map_canvas, GLCD_COLOR_OLIVE, 255, bIsNewFrame);
                break;
            case 7:
                draw_round_corner_box(ptTile, &__user_map_canvas, GLCD_COLOR_PURPLE, 255, bIsNewFrame);
                break;
            default:
                draw_round_corner_box(ptTile, &__user_map_canvas, GLCD_COLOR_WHITE, 0, bIsNewFrame);
                break;
            }
        }
    }
    tnsq_gfx_layer_user_cfg_t tInterfaceCFG = {
        .hwXCount = TETRIS_X_COUNT,
        .hwYCount = TETRIS_Y_COUNT,
        .pchUserMap = ptUserMap,
        .ptFunc = _tetris_user_map_func,
    };
    
    tnsq_gfx_layer_user_t *ptGameInterfaceLayer = tnsq_gfx_layer_user_init(&tInterfaceCFG);
    if (ptGameInterfaceLayer == NULL)
    {
        printf("interface layer init failed\n");
    }
    
    return tnsq_gfx_register_layer_to_stage(ptStage, ptGameInterfaceLayer);
  • 在实际的使用过程中,只需要调用对应 API,操作相应的 cell,引擎就会主动去刷新该区域啦

    tnsq_gfx_layer_user_draw(ptUserLayer, hwX, hwY, idx);
  • 以上就是各个图层最基本的使用方法啦,更多的配置项的功能,可以结合头文件和相关注释进行进一步探索