# eduwin 游戏开发指南

eduwin 的画布组件（Canvas）不仅仅是绘图工具——它是一套完整的 **2D 游戏引擎**，内置了游戏开发所需的一切核心机制：

| 能力         | 说明                                                                |
| ------------ | ------------------------------------------------------------------- |
| **帧循环**   | 定时器驱动，帧率可设（30/60fps 等）                                 |
| **键盘输入** | 实时按键查询 + 按下/释放事件回调                                    |
| **鼠标输入** | 实时坐标查询 + 移动/按下/释放回调                                   |
| **精灵系统** | 图像/纯色精灵，支持平移、缩放、旋转、克隆、碰撞检测                 |
| **精灵事件** | 一键注册精灵的 click、down、up、hover 回调，无需手动处理坐标        |
| **音效**     | WAV 播放、蜂鸣器                                                    |
| **双缓冲**   | GDI+ 离屏位图，所有绘制先在位图上完成再一次性显示到屏幕，天然无闪烁 |

其中 **双缓冲** 是一个很大的优势：eduwin 的 Canvas 内部使用 `GpBitmap` 作为离屏画布，所有 `canvas_draw_*` 操作都在位图上进行，仅在 `InvalidateRect` 时才触发 `WM_PAINT` 刷新屏幕——不会有传统的闪烁问题，也无需手动管理缓冲切换。

- 打靶等低速游戏：**30fps** 即可
- 动作游戏、像素滚动：**60fps**
- 不建议超过 60fps，Windows 定时器精度有限

`samples\shooting_demo\shooting_demo.c` 是一个完整的打靶游戏示例，编译运行就能玩。

---

## 游戏开发核心 API

### 帧循环（游戏主循环）

```c
void canvas_start_timer(HWND canvas, int fps, void (*callback)(void *), void *data);
void canvas_stop_timer(HWND canvas);
```

- `canvas_start_timer` 启动一个定时器，每秒调用 `fps` 次 `callback`。通常设为 60（60fps）
- 回调中执行游戏逻辑：更新状态 → 清理画布 → 重新绘制
- `canvas_stop_timer` 停止帧循环

```c
void game_loop(void *data)
{
    // 1. 处理输入
    // 2. 更新游戏状态
    // 3. 绘制画面
}

canvas_start_timer(canvas, 60, game_loop, NULL);
```

### 键盘输入

```c
int canvas_is_key_down(HWND canvas, int vkey);
void canvas_on_key(HWND canvas, void (*down)(int), void (*up)(int));
```

两种方式获取键盘状态：

| 方式         | 适用场景               | 用法                                       |
| ------------ | ---------------------- | ------------------------------------------ |
| **实时查询** | WASD 移动、持续按住    | 每帧调用 `canvas_is_key_down(canvas, 'W')` |
| **事件回调** | 跳跃（按下一次）、菜单 | `canvas_on_key(canvas, on_down, NULL)`     |

常用虚拟键码：

| 键       | 常量          | 数值  |
| -------- | ------------- | ----- |
| 方向键左 | `VK_LEFT`     | 37    |
| 方向键上 | `VK_UP`       | 38    |
| 方向键右 | `VK_RIGHT`    | 39    |
| 方向键下 | `VK_DOWN`     | 40    |
| 空格     | `VK_SPACE`    | 32    |
| ESC      | `VK_ESCAPE`   | 27    |
| A-Z      | `'A'` — `'Z'` | 65—90 |

### 鼠标输入

```c
void canvas_on_mouse(HWND canvas, void (*move)(int, int), void (*down)(int, int, int), void (*up)(int, int, int));
int canvas_get_mouse_x(HWND canvas);
int canvas_get_mouse_y(HWND canvas);
int canvas_is_mouse_down(HWND canvas, int btn);
```

鼠标回调参数约定：

```
void on_down(int button, int x, int y);
```

- `button`：0=左键, 1=右键
- `x`, `y`：鼠标在画布客户区的坐标

### 精灵系统（Sprite）

eduwin 提供了完整的精灵系统，支持**图像精灵**和**形状精灵**，所有精灵内部统一为位图处理。内置平移、缩放、旋转、克隆、碰撞检测：

```c
typedef struct Sprite Sprite;

/* 形状类型 */
#define SHAPE_RECT      0
#define SHAPE_ELLIPSE   1
#define SHAPE_TRIANGLE  2
#define SHAPE_DIAMOND   3
#define SHAPE_PENTAGON  4
#define SHAPE_HEXAGON   5
#define SHAPE_STAR      6
#define SHAPE_ARROW     7
#define SHAPE_PIE       8

Sprite *sprite_create_from_shape(int type, int w, int h);
Sprite *sprite_create_from_file(const WCHAR *filepath);
Sprite *sprite_create_from_image(void *image);
void sprite_destroy(Sprite *s);
void sprite_set_pos(Sprite *s, float x, float y);
void sprite_move(Sprite *s, float dx, float dy);
void sprite_set_scale(Sprite *s, float sx, float sy);
void sprite_set_rotation(Sprite *s, float degrees);
void sprite_set_color(Sprite *s, int r, int g, int b, int a);
void sprite_set_image(Sprite *s, void *image);
float sprite_get_x(Sprite *s);
float sprite_get_y(Sprite *s);
float sprite_get_w(Sprite *s);
float sprite_get_h(Sprite *s);
float sprite_get_sx(Sprite *s);
float sprite_get_sy(Sprite *s);
Sprite *sprite_clone(Sprite *s);
void sprite_draw(HWND canvas, Sprite *s);
int sprite_collide(Sprite *a, Sprite *b);
int sprite_hit_test(Sprite *s, float px, float py);
```

| 函数                                   | 说明                                                         |
| -------------------------------------- | ------------------------------------------------------------ |
| `sprite_create_from_shape(type, w, h)` | 创建形状精灵（矩形/椭圆/三角等），自动渲染为位图             |
| `sprite_create_from_file(filepath)`    | 从图片文件创建精灵，尺寸自动匹配                             |
| `sprite_create_from_image(image)`      | 从 `load_image_from_file` 返回的句柄创建精灵（内部克隆副本） |
| `sprite_destroy(s)`                    | 销毁精灵，引用计数归零时释放资源                             |
| `sprite_set_pos(s, x, y)`              | 设置位置（左上角）                                           |
| `sprite_move(s, dx, dy)`               | 平移                                                         |
| `sprite_set_scale(s, sx, sy)`          | 缩放因子（1.0=原大，2.0=放大一倍）                           |
| `sprite_set_rotation(s, deg)`          | 设置旋转角度（度），绕原点（默认中心）旋转                   |
| `sprite_set_color(s, r, g, b, a)`      | 正片叠底混色，透明像素不受影响，多次调用不累积               |
| `sprite_set_image(s, image)`           | 替换精灵图像，内部克隆为新副本                               |
| `sprite_get_x/w/h/...`                 | 读取精灵坐标、尺寸、缩放值                                   |
| `sprite_clone(s)`                      | 克隆精灵（从原始图克隆独立工作副本）                         |
| `sprite_draw(canvas, s)`               | 在画布上绘制精灵，自动处理旋转/缩放/位置                     |
| `sprite_collide(a, b)`                 | AABB 碰撞检测，返回 1 表示碰撞                               |
| `sprite_hit_test(s, px, py)`           | 检测点是否在精灵范围内（含缩放）                             |
| `on_sprite_click(canvas, s, cb, d)`    | 注册精灵点击事件回调                                         |
| `on_sprite_down(canvas, s, cb, d)`     | 注册精灵按下事件回调                                         |
| `on_sprite_up(canvas, s, cb, d)`       | 注册精灵释放事件回调                                         |
| `on_sprite_hover(canvas, s, cb, d)`    | 注册精灵悬停事件回调                                         |

Sprite 结构体字段可以直接读写：

```c
sprite->x = 100;       // 直接设坐标
sprite->visible = 0;   // 隐藏
sprite->ox = 0;        // 原点移到左上角
sprite->oy = 0;
```

Sprite 内部引用计数机制：克隆时共享同一份 `GpImage*`，最后一个精灵销毁时才释放图像内存。

`load_image_from_file` 配合 `sprite_create_from_image` 可以实现图像复用：

```c
void *img = load_image_from_file(L"player.png", NULL, NULL);  // 加载一次

Sprite *player1 = sprite_create_from_image(img);  // 创建两个精灵共享同一张图
Sprite *player2 = sprite_create_from_image(img);
sprite_set_pos(player1, 50, 50);
sprite_set_pos(player2, 200, 50);

// 每个精灵独立销毁，但图像在最后一个销毁时才释放
```

### 碰撞检测

```c
int sprite_collide(Sprite *a, Sprite *b);
```

AABB 轴对齐矩形碰撞检测，自动考虑缩放：

```c
if (sprite_collide(player, enemy))
{
    player->visible = 0;          // 玩家消失
    sprite_set_pos(player, 0, 0); // 重置位置
}
```

在游戏中，精灵本身就充当按钮——用 `on_sprite_click` 处理点击，用 `on_sprite_hover` 做悬停反馈，完全不需要额外的按钮控件。详见下文「精灵事件（推荐）」。

### 精灵事件

更简洁的方式是直接注册精灵事件回调，无需手动处理鼠标坐标：

```c
void on_sprite_click(HWND canvas, Sprite *s, void (*cb)(Sprite *, void *), void *data);
void on_sprite_down(HWND canvas, Sprite *s, void (*cb)(Sprite *, void *), void *data);
void on_sprite_up(HWND canvas, Sprite *s, void (*cb)(Sprite *, void *), void *data);
void on_sprite_hover(HWND canvas, Sprite *s, void (*cb)(Sprite *, void *), void *data);
```

注册后，框架自动检测精灵上的鼠标事件并调用回调：

```c
Sprite *btn = sprite_create_from_shape(SHAPE_RECT, 120, 40);
sprite_set_pos(btn, 100, 100);
sprite_set_color(btn, 80, 160, 240, 255);

void on_btn_click(Sprite *s, void *data)
{
    sprite_set_color(s, 0, 200, 80, 255);    // 点击变绿
    play_beep(1000, 100);
}

void on_btn_hover(Sprite *s, void *data)
{
    sprite_set_color(s, 120, 200, 255, 255);  // 悬停高亮
}

on_sprite_click(canvas, btn, on_btn_click, NULL);
on_sprite_hover(canvas, btn, on_btn_hover, NULL);
```

事件生命周期自动管理：销毁精灵时会自动清理注册的事件。同一个精灵可以同时注册 click、down、up、hover 中的任意组合。

### 辅助工具

```c
int canvas_get_width(HWND canvas);
int canvas_get_height(HWND canvas);
void canvas_clear_rect(HWND canvas, int x, int y, int w, int h, int r, int g, int b, int a);
int rand_int(int min, int max);
```

- `canvas_clear_rect` 局部清除并填充颜色，适合需要保留背景的游戏场景
- `rand_int` 生成 `[min, max]` 闭区间随机整数

### 音效

```c
int play_sound(const WCHAR *filepath);
int play_sound_loop(const WCHAR *filepath);
int stop_sound(void);
int play_beep(int freq, int duration);
```

`play_sound` 播放音频文件（异步，不阻塞游戏循环），成功返回 1，失败返回 0。

`play_sound_loop` 循环播放音频文件。

`stop_sound` 停止当前正在播放的音频。

`play_beep` 通过 PC 喇叭发出指定频率和时长的蜂鸣声。`freq` 为频率（Hz），`duration` 为时长（毫秒）。成功返回 1。

适合游戏的用法：

```c
play_sound(L"hit.wav");           // 击中音效（异步，不卡帧）
play_beep(1200, 80);               // 高音短促蜂鸣代替
```

---

## 游戏模板

创建一个新游戏的最简模板：

```c
#include "eduwin.h"

static HWND canvas;

static void game_tick(void *data)
{
    int cw = canvas_get_width(canvas);
    int ch = canvas_get_height(canvas);

    canvas_clear(canvas, 0, 0, 0);          // 黑色背景

    /* 游戏逻辑写在这里 */
    canvas_draw_text(canvas, L"Hello Game!", 10, 10, 255, 255, 255, 16);
}

int main(void)
{
    init_app();
    HWND win = create_window(L"我的游戏", 800, 600);
    canvas = create_canvas(win, 0, 0, 800, 600);

    canvas_start_timer(canvas, 60, game_tick, NULL);
    canvas_on_key(canvas, NULL, NULL);       // 让画布获得键盘焦点

    show_window(win);
    run_app();
    return 0;
}
```

编译命令（参考 `samples\shooting_demo\build.bat`）：

```
cd samples\shooting_demo
build.bat
```
