图片渲染知识

加载图片

查看EasyX文档,以加载test.jpg为例

IMAGE img;

loadimage(&img, _T("test.jpg"));

渲染图片

putimage(100,200,&img);//图片左上角坐标

动画基础

动画的数据逻辑

确保动画序列帧的能够间隔固定的时间进行切换,类比定时器的概念实现一个计数器

  • 定义idx_current_anim来存储当前动画的帧索引

  • 定义static int counter变量用来记录当前动画帧一共播放了几个游戏帧

==使用static确保计数器只在第一个游戏帧被初始化为0==

  • 通过if判断语句使得每五个游戏帧切换一个动画帧;随后考虑到动画帧序列播放结束后的行为,当动画的帧索引到达动画帧总数时,将索引重置为0,从而使得动画循环播放

  • 定义const int PLAYER_ANIM_NUM = 6;

  • 使动画循环播放,可书写下列代码

  • ```c++
    if (++counter % 5 == 0)

    index_current_anim++;
    

    index_current_anim = index_current_anim % PLAYER_ANIM_NUM;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26

    ### 动画的渲染

    首先将每一张图片加载到程序中

    - 定义IMAGE对象数组

    `IMAGE img_player_left[PLAYER_ANIM_NUM];`
    `IMAGE img_player_right[PLAYER_ANIM_NUM];`

    - 图片命名十分有规律可使用循环来加载图片.在使用Unicode字符集的情况下,可以使用wstring来拼凑出文件的路径,再传递给loadimage函数,将图片传递到数组中

    ```c++
    void LoadAnimation()
    {
    for (size_t i = 0; i < PLAYER_ANIM_NUM; i++)
    {
    std::wstring path = L"img/paimon_left_" + std::to_wstring(i) + L".png";
    loadimage(&img_paimon_left[i], path.c_str());
    }
    for (size_t i = 0; i < PLAYER_ANIM_NUM; i++)
    {
    std::wstring path = L"img/paimon_right_" + std::to_wstring(i) + L".png";
    loadimage(&img_paimon_right[i], path.c_str());
    }
    }

    之前定义的动画帧索引此时边可以直接当做IMAGE数组的索引来使用

    putimage(500, 500, &img_paimon_left[index_current_anim]);

处理带有透明度的图片素材

解决思路:类比putimage函数封装一个putimage_alpha函数,借助系统绘图函数比较轻巧的实现

1
2
3
4
5
6
7
8
9
#pragma comment(lib, "MSIMG32.LIB")

inline void putimage_alpha(int x, int y, IMAGE* img)
{
int w = img->getwidth();
int h = img->getheight();
AlphaBlend(GetImageHDC(NULL), x, y, w, h,
GetImageHDC(img), 0, 0, w, h, { AC_SRC_OVER,0,255,AC_SRC_ALPHA });
}

将函数替换putimage_alpha(500, 500, &img_paimon_left[index_current_anim]);

键盘控制角色移动

常规思路

定义POINT player_pos = { 500, 500 };用来存储玩家的位置,并将动画渲染的位置改为player_pos的坐标putimage_alpha(player_pos.x, player_pos.y, &img_paimon_left[index_current_anim]);

定义速度 int PLAYER_SPEED = 5;

在游戏主循环中通过函数判断键盘按下状态从而实现角色移动效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if (msg.message == WM_KEYDOWN)
{
switch(msg.vkcode)
{
case 'W':
player_pos.y -= PLAYER_SPEED;
break;
case 'S':
player_pos.y += PLAYER_SPEED;
break;
case 'A':
player_pos.x -= PLAYER_SPEED;
break;
case 'D':
player_pos.x += PLAYER_SPEED;
break;
}
}

问题

  • 断续:由于当我们按下方向键时,WM_KEYDOWN消息进入事件队列,当保持按键按下一定时间后才会有接连不断的消息被触发
  • 卡顿:keydown消息的产生与游戏主循环异步进行,且触发的频率与操作系统和硬件设备相关,导致有些游戏帧中时间处理部分对多个WM_KEYDOWN消息进行了处理,其余游戏帧中KEY_DOWN较少或几乎没有,导致角色在某些游戏帧中走得远有些走得近

实际需求:按键按下时,保证角色在某一个游戏帧中都能连续的移动;即玩家按键按下时,KEYD_DOWN消息触发,标志着角色开始移动,玩家按键抬起时,KEYUP消息触发,标志着角色结束移动

解放方法

解决思路:定义布尔变量标识玩家运动状态,不直接对玩家的位置数据进行操作而是改变布尔变量的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//定义布尔变量用于表示玩家状态
bool is_move_up = false;
bool is_move_down = false;
bool is_move_left = false;
bool is_move_right = false;

//位于游戏主循环中,检测玩家状态
if (msg.message == WM_KEYDOWN)
{
switch(msg.vkcode)
{
case 'W':
is_move_up = true;
break;
case 'S':
is_move_down = true;
break;
case 'A':
is_move_left = true;
break;
case 'D':
is_move_right = true;
break;
}
}
else if (msg.message == WM_KEYUP)
{
switch (msg.vkcode)
{
case 'W':
is_move_up = false;
break;
case 'S':
is_move_down = false;
break;
case 'A':
is_move_left = false;
break;
case 'D':
is_move_right = false;
break;
}
}

//对玩家移动进行操作
if (is_move_up) player_pos.y -= PLAYER_SPEED;
if (is_move_down) player_pos.y += PLAYER_SPEED;
if (is_move_left) player_pos.x -= PLAYER_SPEED;
if (is_move_right) player_pos.x -= PLAYER_SPEED;

优化动画数据逻辑

思路:由于代码相似,可使用类的思想进行封装,定义类用于封装动画相关的数据和逻辑

1