03-资源加载和动画类实现
1 动画翻转
再util.h中添加翻转图片的函数
inline void flip_image(IMAGE* src, IMAGE* dst)
{
int w = src->getwidth();
int h = src->getheight();
Resize(dst, w, h);
DWORD *src_buffer = GetImageBuffer(src);
DWORD *dst_buffer = GetImageBuffer(dst);
for (int x = 0; x < w; x++) {
for (int y = 0; y < h; y++) {
int idx_src = y * w + x;
int idx_dst = y * w + w - 1 - x;
dst_buffer[idx_dst] = src_buffer[idx_src];
}
}
}
再在main.cpp中实现翻转图集的函数
void flip_atlas(Atlas& src, Atlas& dst)
{
dst.clear();
for (int i = 0; i < src.get_size(); i++) {
IMAGE img_flipped;
flip_image(src.get_image(i), &img_flipped);
dst.add_image(img_flipped);
}
}
2 资源加载
首先定义所有资源变量
IMAGE img_menu_background; // 主菜单背景图片
IMAGE img_VS; // VS 艺术字图片
IMAGE img_1P; // 1P 文本图片
IMAGE img_2P; // 2P 文本图片
IMAGE img_1P_desc; // 1P 键位描述图片
IMAGE img_2P_desc; // 2P 键位描述图片
IMAGE img_gravestone_left; // 朝向左的墓碑图片
IMAGE img_gravestone_right; // 朝向右的墓碑图片
IMAGE img_selector_tip; // 选角界面提示信息文本图片
IMAGE img_selector_background; // 选角界面背景图
IMAGE img_1P_selector_btn_idle_left; // 1P 向左选择按钮默认状态图片
IMAGE img_1P_selector_btn_idle_right; // 1P 向右选择按钮默认状态图片
IMAGE img_1P_selector_btn_down_left; // 1P 向左选择按钮按下状态图片
IMAGE img_1P_selector_btn_down_right; // 1P 向右选择按钮按下状态图片
IMAGE img_2P_selector_btn_idle_left; // 2P 向左选择按钮默认状态图片
IMAGE img_2P_selector_btn_idle_right; // 2P 向右选择按钮默认状态图片
IMAGE img_2P_selector_btn_down_left; // 2P 向左选择按钮按下状态图片
IMAGE img_2P_selector_btn_down_right; // 2P 向右选择按钮按下状态图片
IMAGE img_peashooter_selector_background_left; // 选角界面朝向左的豌豆射手背景图片
IMAGE img_peashooter_selector_background_right; // 选角界面朝向右的豌豆射手背景图片
IMAGE img_sunflower_selector_background_left; // 选角界面朝向左的龙日葵背景图片
IMAGE img_sunflower_selector_background_right; // 选角界面朝向右的龙日葵背景图片
IMAGE img_sky; // 天空图片
IMAGE img_hills; // 山脉图片
IMAGE img_platform_large; // 大型平台图片
IMAGE img_platform_small; // 小型平台图片
IMAGE img_1P_cursor; // 1P 指示光标图片
IMAGE img_2P_cursor; // 2P 指示光标图片
Atlas atlas_peashooter_idle_left; // 豌豆射手朝向左的默认动画图集
Atlas atlas_peashooter_idle_right; // 豌豆射手朝向右的默认动画图集
Atlas atlas_peashooter_run_left; // 豌豆射手朝向左的奔跑动画图集
Atlas atlas_peashooter_run_right; // 豌豆射手朝向右的奔跑动画图集
Atlas atlas_peashooter_attack_ex_left; // 豌豆射手朝向左的特殊攻击动画图集
Atlas atlas_peashooter_attack_ex_right; // 豌豆射手朝向右的特殊攻击动画图集
Atlas atlas_peashooter_die_left; // 豌豆射手朝向左的死亡动画图集
Atlas atlas_peashooter_die_right; // 豌豆射手朝向右的死亡动画图集
Atlas atlas_sunflower_idle_left; // 龙日葵朝向左的默认动画图集
Atlas atlas_sunflower_idle_right; // 龙日葵朝向右的默认动画图集
Atlas atlas_sunflower_run_left; // 龙日葵朝向左的奔跑动画图集
Atlas atlas_sunflower_run_right; // 龙日葵朝向右的奔跑动画图集
Atlas atlas_sunflower_attack_ex_left; // 龙日葵朝向左的特殊攻击动画图集
Atlas atlas_sunflower_attack_ex_right; // 龙日葵朝向右的特殊攻击动画图集
Atlas atlas_sunflower_die_left; // 龙日葵朝向左的死亡动画图集
Atlas atlas_sunflower_die_right; // 龙日葵朝向右的死亡动画图集
IMAGE img_pea; // 豌豆图片
Atlas atlas_pea_break; // 豌豆破碎动画图集
Atlas atlas_sun; // 日光动画图集
Atlas atlas_sun_explode; // 日光爆炸动画图集
Atlas atlas_sun_ex; // 特殊日光动画图集
Atlas atlas_sun_ex_explode; // 特殊日光爆炸动画图集
Atlas atlas_sun_text; // "日" 文本动画图集
Atlas atlas_run_effect; // 奔跑特效动画图集
Atlas atlas_jump_effect; // 跳跃特效动画图集
Atlas atlas_land_effect; // 落地特效动画图集
IMAGE img_1P_winner; // 1P 获胜文本图片
IMAGE img_2P_winner; // 2P 获胜文本图片
IMAGE img_winner_bar;
IMAGE img_avatar_peashooter; // 豌豆射手头像图片
IMAGE img_avatar_sunflower; // 龙日葵头像图片
定义
load_game_resources()函数并在main函数中调用void load_game_resources()
{
AddFontResourceEx(_T("../resources/IPix.ttf"), FR_PRIVATE, NULL);
loadimage(&img_menu_background, _T("../resources/menu_background.png"));
loadimage(&img_VS, _T("../resources/VS.png"));
loadimage(&img_1P, _T("../resources/1P.png"));
loadimage(&img_2P, _T("../resources/2P.png"));
loadimage(&img_1P_desc, _T("../resources/1P_desc.png"));
loadimage(&img_2P_desc, _T("../resources/2P_desc.png"));
loadimage(&img_gravestone_right, _T("../resources/gravestone.png"));
flip_image(&img_gravestone_right, &img_gravestone_left);
loadimage(&img_selector_tip, _T("../resources/selector_tip.png"));
loadimage(&img_selector_background, _T("../resources/selector_background.png"));
loadimage(&img_1P_selector_btn_idle_right, _T("../resources/1P_selector_btn_idle.png"));
flip_image(&img_1P_selector_btn_idle_right, &img_1P_selector_btn_idle_left);
loadimage(&img_1P_selector_btn_down_right, _T("../resources/1P_selector_btn_down.png"));
flip_image(&img_1P_selector_btn_down_right, &img_1P_selector_btn_down_left);
loadimage(&img_2P_selector_btn_idle_right, _T("../resources/2P_selector_btn_idle.png"));
flip_image(&img_2P_selector_btn_idle_right, &img_2P_selector_btn_idle_left);
loadimage(&img_2P_selector_btn_down_right, _T("../resources/2P_selector_btn_down.png"));
flip_image(&img_2P_selector_btn_down_right, &img_2P_selector_btn_down_left);
loadimage(&img_peashooter_selector_background_right, _T("../resources/peashooter_selector_background.png"));
flip_image(&img_peashooter_selector_background_right, &img_peashooter_selector_background_left);
loadimage(&img_sunflower_selector_background_right, _T("../resources/sunflower_selector_background.png"));
flip_image(&img_sunflower_selector_background_right, &img_sunflower_selector_background_left);
loadimage(&img_sky, _T("../resources/sky.png"));
loadimage(&img_hills, _T("../resources/hills.png"));
loadimage(&img_platform_large, _T("../resources/platform_large.png"));
loadimage(&img_platform_small, _T("../resources/platform_small.png"));
loadimage(&img_1P_cursor, _T("../resources/1P_cursor.png"));
loadimage(&img_2P_cursor, _T("../resources/2P_cursor.png"));
atlas_peashooter_idle_right.load_from_file(_T("../resources/peashooter_idle_%d.png"), 9);
flip_atlas(atlas_peashooter_idle_right, atlas_peashooter_idle_left);
atlas_peashooter_run_right.load_from_file(_T("../resources/peashooter_run_%d.png"), 5);
flip_atlas(atlas_peashooter_run_right, atlas_peashooter_run_left);
atlas_peashooter_attack_ex_right.load_from_file(_T("../resources/peashooter_attack_ex_%d.png"), 3);
flip_atlas(atlas_peashooter_attack_ex_right, atlas_peashooter_attack_ex_left);
atlas_peashooter_die_right.load_from_file(_T("../resources/peashooter_die_%d.png"), 4);
flip_atlas(atlas_peashooter_die_right, atlas_peashooter_die_left);
atlas_sunflower_idle_right.load_from_file(_T("../resources/sunflower_idle_%d.png"), 8);
flip_atlas(atlas_sunflower_idle_right, atlas_sunflower_idle_left);
atlas_sunflower_run_right.load_from_file(_T("../resources/sunflower_run_%d.png"), 5);
flip_atlas(atlas_sunflower_run_right, atlas_sunflower_run_left);
atlas_sunflower_attack_ex_right.load_from_file(_T("../resources/sunflower_attack_ex_%d.png"), 9);
flip_atlas(atlas_sunflower_attack_ex_right, atlas_sunflower_attack_ex_left);
atlas_sunflower_die_right.load_from_file(_T("../resources/sunflower_die_%d.png"), 2);
flip_atlas(atlas_sunflower_die_right, atlas_sunflower_die_left);
loadimage(&img_pea, _T("../resources/pea.png"));
atlas_pea_break.load_from_file(_T("../resources/pea_break_%d.png"), 3);
atlas_sun.load_from_file(_T("../resources/sun_%d.png"), 5);
atlas_sun_explode.load_from_file(_T("../resources/sun_explode_%d.png"), 5);
atlas_sun_ex.load_from_file(_T("../resources/sun_ex_%d.png"), 5);
atlas_sun_ex_explode.load_from_file(_T("../resources/sun_ex_explode_%d.png"), 5);
atlas_sun_text.load_from_file(_T("../resources/sun_text_%d.png"), 6);
atlas_run_effect.load_from_file(_T("../resources/run_effect_%d.png"), 4);
atlas_jump_effect.load_from_file(_T("../resources/jump_effect_%d.png"), 5);
atlas_land_effect.load_from_file(_T("../resources/land_effect_%d.png"), 2);
loadimage(&img_1P_winner, _T("../resources/1P_winner.png"));
loadimage(&img_2P_winner, _T("../resources/2P_winner.png"));
loadimage(&img_winner_bar, _T("../resources/winnner_bar.png"));
loadimage(&img_avatar_peashooter, _T("../resources/avatar_peashooter.png"));
loadimage(&img_avatar_sunflower, _T("../resources/avatar_sunflower.png"));
mciSendString(_T("open ../resources/bgm_game.mp3 alias bgm_game"), NULL, 0, NULL);
mciSendString(_T("open ../resources/bgm_menu.mp3 alias bgm_menu"), NULL, 0, NULL);
mciSendString(_T("open ../resources/pea_break_1.mp3 alias pea_break_1"), NULL, 0, NULL);
mciSendString(_T("open ../resources/pea_break_2.mp3 alias pea_break_2"), NULL, 0, NULL);
mciSendString(_T("open ../resources/pea_break_3.mp3 alias pea_break_3"), NULL, 0, NULL);
mciSendString(_T("open ../resources/pea_shoot_1.mp3 alias pea_shoot_1"), NULL, 0, NULL);
mciSendString(_T("open ../resources/pea_shoot_2.mp3 alias pea_shoot_2"), NULL, 0, NULL);
mciSendString(_T("open ../resources/pea_shoot_ex.mp3 alias pea_shoot_ex"), NULL, 0, NULL);
mciSendString(_T("open ../resources/sun_explode.mp3 alias sun_explode"), NULL, 0, NULL);
mciSendString(_T("open ../resources/sun_explode_ex.mp3 alias sun_explode_ex"), NULL, 0, NULL);
mciSendString(_T("open ../resources/sun_text.mp3 alias sun_text"), NULL, 0, NULL);
mciSendString(_T("open ../resources/ui_confirm.wav alias ui_confirm"), NULL, 0, NULL);
mciSendString(_T("open ../resources/ui_switch.wav alias ui_switch"), NULL, 0, NULL);
mciSendString(_T("open ../resources/ui_win.wav alias ui_win"), NULL, 0, NULL);
}
由于我们使用了mciSendString()函数所以需要在CmakeLists.txt中添加额外的库文件链接
# 链接Winmm库和MSIMG32库
# 对于Windows平台,直接用库名(无需后缀.lib)
if(WIN32)
# 链接Winmm库(对应Winmm.lib)
target_link_libraries(${PROJECT_NAME} PRIVATE winmm)
# 链接MSIMG32库(对应MSIMG32.LIB)
target_link_libraries(${PROJECT_NAME} PRIVATE msimg32)
endif()
3 Animation类
class Animation
{
public:
Animation() = default;
~Animation() = default;
private:
};
增加下述私有变量
int timer = 0; //计时器
int interval = 0; //帧间隔
int idx_frame = 0; //真索引
bool is_loop = true; //是否循环
Atlas *atlas = nullptr;
之后我们实现了一些基本配置的方法
void reset()
{
timer = 0;
idx_frame = 0;
}
void set_atlas(Atlas* new_atlas)
{
reset();
atlas = new_atlas;
}
void set_loop(bool flag)
{
is_loop = flag;
}
void set_interval(int ms)
{
interval = ms;
}
之后再实现一些输出方法
int get_idx_frame()
{
return idx_frame;
}
IMAGE* get_frame()
{
return atlas->get_image(idx_frame);
}
bool check_finished()
{
if(is_loop)
return false;
return (idx_frame == atlas->get_size() - 1);
}
3.1 更新方法on_update()
然后我们实现on_update()方法
void on_update(int delta)
{
timer += delta;
if( timer >= interval) {
timer = 0;
idx_frame++;
if(idx_frame >=atlas->get_size()) {
idx_frame = is_loop ? 0 : atlas->get_size() - 1;
}
}
}
3.2 渲染方法on_draw()
之后我们实现渲染方法on_draw(),在此之前我们要先再util.h实现putimage_alpha()
inline void putimage_alpha(int dst_x, int dst_y, IMAGE* img)
{
int w = img->getwidth();
int h = img->getheight();
AlphaBlend(GetImageHDC(GetWorkingImage()), dst_x, dst_y, w, h,
GetImageHDC(img), 0, 0, w, h, {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA});
}
void on_draw(int x, int y) const
{
putimage_alpha(x, y, atlas->get_image(idx_frame));
}
3.3 死亡动画的播放
要想播放死亡动画,我们需要延后那些存在消失动画的物体被删除的时间。
当敌人生命值归零时,进入死亡状态,然后播放完死亡动画后,再从内存中删除。
这需要动画从而共勉提供一个动画播放结束的消息
我们可以通过已有的check_finished()方法检查动画是否播放结束,但是,我们可以使用一个更加优雅的概念:回调函数。
回调函数就是一个使用参数传递的函数对象,我们可以把它保存起来,然后在合适的时候调用它,这样就可以在函数内部的逻辑,在“合适的时候”才被执行。
以前面的场景为例,我们把删除敌人的逻辑定义为函数,以回调函数的形式保存在动画对象内部,当死亡动画播放结束后,再调用这个函数删除敌人对象。
这与c语言的函数指针类似,不过再cpp中我们可以使用一种全新的方式。
首先需要引入相关头文件
#include <functional>
我们在动画的私有成员中添加一个function类型的函数对象。
std::function<void()> callback;
function是一个模板类,尖括号中的内容表示这个函数对象存储的函数返回值为void并且没有任何参数。
之后我们在on_update()方法中,动画播放结束的分支语句中添加下述代码
if (!is_loop && callback) {
callback();
}
另外,我们还需要编写一个为Animation类设置callback的方法
void set_callback(std::function<void()> callback_function)
{
callback = callback_function;
}
这样,Animation类以及编写完成了
4 测试
4.1 动画播放测试
在Menu_Scene中引入对应的头文件,并使用extern声明外部的动画图集等资源
extern Atlas atlas_peashooter_run_right;
extern SceneManager scene_manager;
随后我们在MenuScene类的私有成员中定义Animation对象
Animation animation_peashooter_run_right;
在
on_enter()中设置动画的属性animation_peashooter_run_right.set_atlas(&atlas_peashooter_run_right);
animation_peashooter_run_right.set_interval(75);
animation_peashooter_run_right.set_loop(true);
在
on_update()中调用动画的on_update()方法void on_update(int delta) {
animation_peashooter_run_right.on_update(delta);
std::cout << "主菜单正在运行" << std::endl;
}
这是,我们发现之前没有把时间变化量作为参数传递进来。所以,只能小幅重构一下之前的代码了
首先,在基类Scene中为虚方法
on_update()添加参数virtual void on_update(int delta) { }
除此之外,GameScene类和SelectorScene类也需要进行这样的修改。
而且,SceneManager类也需要修改。
现在回到main函数,我们需要把计算好的时间间隔传递给场景管理器
static DWORD last_tick_time = GetTickCount();
DWORD current_tick_time = GetTickCount();
DWORD delta_tick = current_tick_time - last_tick_time;
scene_manager.on_update(delta_tick);
last_tick_time = current_tick_time;
下面我们继续回到MenuScene中,在on_draw()方法内部调用动画对象的on_draw()方法
animation_peashooter_run_right.on_draw(100, 100);
4.2 回调函数测试
在on_enter()方法中设置回调函数,这里使用了lambda函数作为参数
animation_peashooter_run_right.set_callback(
[]()
{
scene_manager.switch_to(SceneManager::SceneType::Game);
}
);
为了便于观察,我们将动画的interval设置为300
animation_peashooter_run_right.set_interval(300);
animation_peashooter_run_right.set_loop(false);
这时运行程序,我们可以看到在可以放慢速度的动画播放结束后,游戏成功跳转到了GameScene阶段。
评论
如果你已登录 GitHub,就可以直接在这里评论。