水果忍者

本示例演示了如何开发一个简单的“水果忍者” APP ,您可以从中学习和了解开发 UI 应用程序的基本方法和流程。在一分钟内通过切割水果获得分数,切到炸弹游戏直接结束。观看下面的演示视频,了解其全部功能。



环境需求

请参考 入门指南 的安装部分内容。

源文件

为了帮助您学习和熟悉开发,您可以在 example\application\screen_410_502 路径下找到您可能需要的所有源文件。本演示的源文件是 app_box2d_fruit_ninja.cpp,您可以在上述路径中找到它,了解更多详情。

配置

#define SCREEN_WIDTH (int16_t)gui_get_screen_width()
#define SCREEN_HEIGHT (int16_t)gui_get_screen_height()
#define HEIGHT_OFFSET 100

调用步骤

步骤 1: Declare Ui Design Function

static void app_fruit_ninja_design(gui_view_t *view)
{
    gui_obj_add_event_cb(view, click_button_back_2_watchface_or_menu, GUI_EVENT_KB_SHORT_PRESSED, NULL);
    gui_obj_focus_set(view);

    gui_obj_t *obj = GUI_BASE(view);
    app_fruit_ninja::fruit_ninja_design(obj);
}

步骤 2: Call Function

GUI_VIEW_INSTANCE(CURRENT_VIEW_NAME, false, app_fruit_ninja_design, close_FN);

设计思路

  • 在该 APP 中,使用到了 box2d 创建固体模拟在重力环境中的物体运动,在初始化时给定一个 x 轴与 y 轴的初速度等参数。

    // Add dynamic bodys
    b2BodyDef ballBodyDef;
    ballBodyDef.type = b2_dynamicBody;
    ballBodyDef.position.Set(4, (SCREEN_HEIGHT + HEIGHT_OFFSET) * P2M);
    ballBodyDef.angularVelocity = -314;    //-PI rad/s
    ballBodyDef.linearVelocity.Set(5, -20); // move up
    body_st = world->CreateBody(&ballBodyDef);
    
  • 因为相互碰撞不利于游戏的游玩,为了减小物体间相互碰撞的影响,将固体的半径设置为一个较小的值。

    //creat body shape and attach the shape to the Body
    b2CircleShape circleShape;
    circleShape.m_radius = 0.01; //RADIUS_ST * P2M; small number reducing the impact of collisions
    
  • 在回调函数中利用固体的中心点映射更新水果(及炸弹)的位置与旋转角度并用图片组件显示。水果位置在显示界面外会对固体的位置与初速度进行复位。

    b2Vec2 position = body_st->GetPosition();
    if (position_refresh((int)(position.x * M2P - RADIUS_ST), (int)(position.y * M2P - RADIUS_ST),
                         img_strawberry, body_st) == 1)
    
  • 切割水果使用了 touch_info 结构体,检测到触控点释放说明完成了一次切割(得到触屏初始点与 x 轴与 y 轴的位移),对切割内容进行判断。

    cut_judgment(img_strawberry, 0, FRUIT_NINJA_STRAWBERRY_HALF_1_BIN);
    
  • 若切割线与图片矩形有两个交点,则说明切割有效。

    if (line_has_two_intersections_with_rectangle(img_coordinate, img_w, img_h, tp_start, tp_end,
                                                  img_rotate_angle))
    
  • 注意在计算交点时图片的旋转后端点信息需要将旋转角度带入计算才会与显示一致,如此可以提高切割判断准确度。

    /* calculate rectangular's four rotated points */
    
    Point rotated_rect_min = rotate_point(rect_min, center, angle); // left-up
    Point rotated_rect_max = rotate_point(rect_max, center, angle); // right-down
    Point rotated_rect_p2 = rotate_point(rect_p2, center, angle); // left-down
    Point rotated_rect_p3 = rotate_point(rect_p3, center, angle); // right-top
    
  • 将水果图片更新为切割后的两张图(对应两个 gui_img_t 指针),并计分,一次切割可以切到多个不同物体。

    // Refresh half-cut fruits pos
    if (fruit_cut_flag[0])
    {
        gui_img_translate(img_cut_array[0], img_strawberry->t_x + 10,
                          img_strawberry->t_y + 10);
        gui_img_rotation(img_cut_array[0], gui_img_get_transform_degrees(img_strawberry));
    }
    
  • 注意可以使用 flag 标记水果的切割状况,防止计分错误以及方便更新切割后的图片位置。

  • 当切割后的水果移动到显示界面之外会对固体的位置与初速度进行复位,并将切割效果复原。

    gui_img_set_src(img_strawberry, (const uint8_t *)FRUIT_NINJA_STRAWBERRY_BIN,
                    img_strawberry->storage_type);
    gui_img_refresh_size(img_strawberry);
    gui_img_set_focus(img_strawberry, img_strawberry->base.w / 2, img_strawberry->base.h / 2);
    
    fruit_cut_flag[0] = false;
    gui_obj_hidden(GUI_BASE(img_cut_array[0]), true);