【2D平台跳跃游戏 - 02】绘图
摄像机类
摄像机作用
摄像机作为玩家观察游戏世界的工具:
- 当使用摄像机渲染世界的一部分,同时跟随玩家移动时,就可以做出“世界感”
- 摄像机可以用来制作一些特效,例如屏幕抖动
- 摄像机还可以进行一些画面的后处理
由于这次的游戏比较小,用不到摄像机跟随玩家移动的功能,所以此次只是用于实现一些特效
窗口坐标系与世界坐标系
在窗口中渲染时,实际上使用的坐标是物体相对于摄像机的坐标
整个游戏世界的所有运行逻辑,如玩家移动或碰撞检测等,都应该使用世界坐标系;
只有在需要绘图时,才会转换为窗口坐标系绘制;
这也正是数据与渲染分离的思想
两种坐标的换算公式为:
窗口坐标 = 世界坐标 - 摄像机坐标
窗口抖动
窗口抖动其实就是令摄像机快速变换坐标,就可以实现窗口抖动
如果想要更加平滑,可以使用柏林噪声等算法优化抖动函数
这里使用的是最简单的方法,设置一个抖动半径,摄像机坐标在抖动半径的圆内随机取点,实现抖动效果
实际实现
实际实现时,使用了自己封装的二维向量类与定时器类
在摄像机类中,封装了:
- 构造函数,实现了初始化摄像机状态
- 获取摄像机位置,使用了
const
修饰 - 重置摄像机方法
- 摄像机更新方法
- 窗口抖动方法
私有成员中,存储了四个变量:
- 二维向量记录摄像机位置
- 摄像机抖动定时器
- 布尔变量记录摄像机是否正在抖动
- 浮点变量记录摄像机抖动强度
#pragma once
#include "vector2.h"
#include "timer.h"
// 摄像机类
class Camera
{
public:
Camera()
{
timer_shake.set_one_shot(true);
timer_shake.set_callback([&]()
{
is_shaking = false;
reset();
});
}
~Camera() = default;
// 获取摄像机位置
const Vector2& get_position() const
{
return position;
} // 第一个const修饰返回值,设定返回值为常量引用
// 重置摄像机方法
void reset()
{
position.x = 0;
position.y = 0;
}
// 摄像机更新方法
void on_update(int delta)
{
timer_shake.on_update(delta);
if (is_shaking) // 在单位圆内随机取坐标完成抖动
{
position.x = (-50 + rand() % 100) / 50.0f * shaking_strength;
position.y = (-50 + rand() % 100) / 50.0f * shaking_strength;
}
}
// 窗口抖动方法
void shake(float strength, int duration)
{
is_shaking = true;
shaking_strength = strength;
timer_shake.set_wait_time(duration);
timer_shake.restart();
}
private:
Vector2 position; // 摄像机位置
Timer timer_shake; // 摄像机抖动定时器
bool is_shaking = false; // 摄像机是否正在抖动
float shaking_strength = 0; // 摄像机抖动幅度
};
图集类
图集类似于容器,用于批量加载和存储一套动画所用的素材
加载图片时,由于使用的素材命名都有规律,所以可以使用字符串拼接批量加载一类素材
#pragma once
#include <vector>
#include <graphics.h>
// 图集类
class Atlas
{
public:
Atlas() = default;
~Atlas() = default;
// 加载图片
void load_from_file(LPCTSTR path_template, int num)
{
img_list.clear();
img_list.resize(num);
TCHAR path_file[256];
for (int i = 0; i < num; i++)
{
_stprintf_s(path_file, path_template, i + 1);
loadimage(&img_list[i], path_file);
}
}
// 清空图集
void clear()
{
img_list.clear();
}
// 获取图集中图片数量
int get_size()
{
return (int)img_list.size();
}
// 获取实际渲染的动画帧
IMAGE* get_image(int idx)
{
if (idx < 0 || idx >= img_list.size())
return nullptr;
return &img_list[idx];
}
// 添加已有的图片
void add_image(const IMAGE& img)
{
img_list.push_back(img);
}
private:
std::vector<IMAGE> img_list;
};
动画类
实现思路
动画类似于一个只记录当前播放状态的轻量管理器
在渲染时需要实时地去对应的图集中获取具体的图片
动画类中同样使用了摄像机类与定时器类
动画渲染
图片渲染函数被封装在util.h
头文件中,支持使用摄像机和裁剪的重载
#include <graphics.h>
#include "camera.h"
#pragma comment(lib, "MSIMG32.LIB")
// 基础透明渲染
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 });
}
// 支持摄像机
inline void putimage_alpha(const Camera& camera, int dst_x, int dst_y, IMAGE* img)
{
int w = img->getwidth();
int h = img->getheight();
const Vector2& pos_camera = camera.get_position();
AlphaBlend(GetImageHDC(GetWorkingImage()), (int)(dst_x - pos_camera.x), (int)(dst_y - pos_camera.y), w, h,
GetImageHDC(img), 0, 0, w, h, { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA });
}
// 支持裁剪
inline void putimage_alpha(int dst_x, int dst_y, int width, int height, IMAGE* img, int src_x, int src_y)
{
int w = width > 0 ? width : img->getwidth();
int h = height > 0 ? height : img->getheight();
AlphaBlend(GetImageHDC(GetWorkingImage()), dst_x, dst_y, w, h,
GetImageHDC(img), src_x, src_y, w, h, { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA });
}
具体实现
动画类支持了回调函数,方便实现在动画播放结束后执行某些逻辑,如销毁子弹等
回调函数使用C++的模板库中的functional封装
#pragma once
#include <graphics.h>
#include <functional>
#include "atlas.h"
#include "util.h"
#include "camera.h"
// 动画类
class Animation
{
public:
Animation() = default;
~Animation() = default;
// 重置动画状态
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);
}
// 更新方法
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;
if (!is_loop && callback) // 如果动画没有设置循环且回调函数存在
{
callback();
}
}
}
}
// 绘制方法
void on_draw(const Camera& camera,int x, int y) const
{
putimage_alpha(camera, x, y, atlas->get_image(idx_frame));
}
// 设置回调函数
void set_callback(std::function<void()> callback)
{
this->callback = callback;
}
private:
int timer = 0; // 计时器
int interval = 0; // 帧间隔
int idx_frame = 0; // 帧索引
bool is_loop = true; // 是否循环
Atlas* atlas = nullptr;
std::function<void()> callback;
};
评论区