在SDL2窗口中加载图片
上节记录了如何利用SDL2库去创建一个黑洞洞的窗口,游戏主体是显示各种图片以及对图片的移动,旋转,缩放等操作,所以这节来记录如何去加载显示图片。
显示星星
准备好一张图片,png,bmp,jpeg等等都可以,sdl2的扩展库SDL_image支持非常多的图片格式,具体可以参考官方文档或者SDL_image.h文件。
这里提供的是一张带有黑色背景的jpg格式星星图片,不喜欢的可以自己去替换,正好我们目前创建的是黑色背景的窗口,放进去后看着还是毫无违和感的。
加载以及显示图片的三个关键函数是:
IMG_LoadTexture:加载图片,两个参数,第一个是上节创建的渲染结构SDL_Render*;第二个参数就是图片资源的路径,可以是绝对路径也可以是相对路径
SDL_QueryTexture:查询纹理信息,主要用来查询宽度和高度,显示以及切割图片的时候会用。(这个就没有sfml方便,多一步操作)
SDL_RenderCopy:拷贝纹理到渲染器(双缓冲机制,先拷贝数据,然后一次性绘制,避免闪烁)。这里的第二个参数是刚才加载出来的纹理指针,第三个参数是图片裁剪采用到的位置尺寸信息(就是只渲染用到图片的从指定位置开始的一个多长多宽的矩形,一般做游戏都会用到,将多张图片集合放在一张图片上,能够加快加载速度),不裁剪的时候传NULL就可以,第四个参数就是渲染到哪个位置,可以在这个时候更改显示的宽高,这样就可以自由设置图片显示的大小(像我提供的星星图片太大,128个像素,显示出来不好看,我就会设置成最大宽高都是68个像素,可以自由设置)。
这里先只介绍这个接口,后面渲染图片,需要旋转,镜像等操作时,会用到更复杂点的接口,后面再介绍。
上代码:
//...这里接上节代码,不重复了
SDL_Renderer* render = SDL_CreateRenderer(m_window, -1, SDL_RENDERER_ACCELERATED);
//加载图片
SDL_Texture* tex = IMG_LoadTexture(render, "star.jpg");
if (tex == nullptr) return -1;
//查询图片尺寸(SDL大部分接口是返回0为成功,具体在使用时看接口申明)
int texw = 0, texh = 0;
if (0 != SDL_QueryTexture(tex, NULL, NULL, &texw, &texh))
return -1;
//...这里接上节开始渲染部分,中间省略
//更新完可以渲染界面了
SDL_RenderClear(render);
SDL_Rect des = { 100, 100, texw, texh };
SDL_RenderCopy(render, tex, NULL, &des);
编译运行,好了,星星被创造显示出来了,关键代码就这么多,看起来还是很简单是不是。but…这只是最简单的显示一张图片。既然是游戏,当然不能只是这么静静的显示一张图片,不急,一步步来,先让星星闪起来。
让星星忽大忽小的闪烁
//星星坐标
SDL_Point stars[] = {
{ 100, 100 },
{ 90 , 247 },
{ 156 , 25 },
{ 298 , 325 }
};
//半衰期,最大尺寸
const int halfLife = 8, maxSize = 48;
//当前缩放
Uint8 zoom = 0;
//上次更新时间
Uint32 tickLast = 0;
先随意选择几个点来显示星星。
简单的逻辑就是随着时间的迁移,星星的尺寸慢慢变小,到我们设定的最小值时再慢慢变大,到最大值时再慢慢变小,当然你也可以自定义自己的条件。
//...这里接上节在更新的地方
//这里可以开始更新事件
//onUpdate();
auto tick = SDL_GetTicks();
// 每100ms变化一次大小
if (SDL_TICKS_PASSED(tick, tickLast + 100)) {
zoom++;
zoom %= halfLife * 2;
tickLast = tick;
}
// 计算星星当前大小
SDL_Rect des;
int z = halfLife - abs(halfLife - zoom);
des.w = maxSize - z * 2;
des.h = des.w;
// 循环显示每个星星
for (int i = 0; i < sizeof(stars) / sizeof(SDL_Point); i++) {
des.x = stars[i].x - des.w / 2;
des.y = stars[i].y - des.h / 2;
engine.displayTexture(tex, des);
SDL_RenderCopy(render, tex, NULL, &des);
}
这样就能看到星星在随着时间的迁移而忽大忽小了,是不是有了点感觉?
完整代码
分段代码可能大家看的不习惯,下面贴上完整代码,只是这代码跟上面有点区别,我把渲染相关的集合到一个类里面去了,然后就是这也只是自己的demo,加了一个Star类去更新星星,在鼠标点击的时候在鼠标点击位置生成一个星星,有几个参数都是待完善的功能,感兴趣的可以看看,大神可以跳走也可以指导下。
目前都放在一个cpp,是为了方便复制过来,方便大家看代码,后面如果demo写的多会分类整理,放到git仓库(如果大家感兴趣且需要)
#include "stdafx.h"
#include "SDL.h"
#include "SDL_image.h"
#include "SDL_ttf.h"
#include <string>
#include <unordered_map>
#include <vector>
#pragma comment(lib,"SDL2.lib")
#pragma comment(lib,"SDL2main.lib")
#pragma comment(lib,"SDL2_image.lib")
#pragma comment(lib,"SDL2_ttf.lib")
/*
********* Engine **************************************************************/
struct TextureInfo {
SDL_Texture* texture = nullptr;
int w, h;
};
class Engine {
public:
bool onInit(std::string title, int w, int h);
inline SDL_Renderer* getRender() { return m_render; }
void shutdown();
inline bool isQuit() { return m_quit; }
inline void setFrame(int frame) { m_frame = frame; }
void setClearColor(Uint8 r, Uint8 g, Uint8 b);
void setClearColor(SDL_Color color);
void startRender();
void endRender();
void displayTexture(TextureInfo* tex, SDL_Point point);
void displayTexture(TextureInfo* tex, SDL_Rect rect);
void drawRect(SDL_FRect rect, SDL_Color color = { 255, 255, 255, 255 });
int pollEvent(SDL_Event& event);
TextureInfo* getTextureInfo(std::string file);
private:
SDL_Window* m_window = nullptr;
SDL_Renderer* m_render = nullptr;
SDL_Color m_clearColor = { 0, 0, 0, 255 };
bool m_quit = false;
int m_frame = 60;
Uint32 m_lastFrameTick = 0;
std::unordered_map<std::string, TextureInfo> m_textureList;
};
bool Engine::onInit(std::string title, int w, int h)
{
SDL_Init(SDL_INIT_EVERYTHING);
m_window = SDL_CreateWindow(title.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
w, h, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_SHOWN);
if (m_window == nullptr) return false;
m_render = SDL_CreateRenderer(m_window, -1, SDL_RENDERER_ACCELERATED);
if (m_render == nullptr) return false;
return true;
}
void Engine::shutdown()
{
for (auto it = m_textureList.begin(); it != m_textureList.end(); it++) {
if (it->second.texture) {
SDL_DestroyTexture(it->second.texture);
it->second.texture = nullptr;
}
}
m_textureList.clear();
if (m_render) SDL_DestroyRenderer(m_render);
if (m_window) SDL_DestroyWindow(m_window);
SDL_Quit();
}
void Engine::setClearColor(Uint8 r, Uint8 g, Uint8 b)
{
m_clearColor.r = r;
m_clearColor.g = g;
m_clearColor.b = b;
}
void Engine::setClearColor(SDL_Color color)
{
m_clearColor.r = color.r;
m_clearColor.g = color.g;
m_clearColor.b = color.b;
}
void Engine::startRender()
{
if (!m_render) return;
SDL_SetRenderDrawColor(m_render, m_clearColor.r, m_clearColor.g, m_clearColor.b, m_clearColor.a);
SDL_RenderClear(m_render);
}
void Engine::endRender()
{
if (!m_render) return;
SDL_RenderPresent(m_render);
auto tick = SDL_GetTicks();
int tickStep = tick - m_lastFrameTick;
tickStep = 1000 / m_frame - tickStep;
if (tickStep > 0)
SDL_Delay(tickStep);
m_lastFrameTick = tick;
}
void Engine::displayTexture(TextureInfo* tex, SDL_Point point)
{
if (!m_render) return;
SDL_Rect des = { point.x, point.y, tex->w, tex->h };
SDL_RenderCopy(m_render, tex->texture, NULL, &des);
}
void Engine::displayTexture(TextureInfo* tex, SDL_Rect rect)
{
if (!m_render) return;
SDL_RenderCopy(m_render, tex->texture, NULL, &rect);
}
void Engine::drawRect(SDL_FRect rect, SDL_Color color)
{
if (!m_render) return;
Uint8 a, r, g, b;
SDL_GetRenderDrawColor(m_render, &r, &g, &b, &a);
SDL_SetRenderDrawColor(m_render, color.r, color.g, color.b, color.a);
SDL_RenderDrawRectF(m_render, &rect);
SDL_SetRenderDrawColor(m_render, r, g, b, a);
}
int Engine::pollEvent(SDL_Event & event)
{
int ret = SDL_PollEvent(&event);
if (event.type == SDL_QUIT)
m_quit = true;
return ret;
}
TextureInfo* Engine::getTextureInfo(std::string file)
{
if (m_render == nullptr) return nullptr;
auto itFind = m_textureList.find(file);
if (itFind == m_textureList.end()) {
SDL_Texture* tex = IMG_LoadTexture(m_render, file.c_str());
if (tex == nullptr) return nullptr;
int texw = 0, texh = 0;
if (0 != SDL_QueryTexture(tex, NULL, NULL, &texw, &texh)) return nullptr;
m_textureList[file].texture = tex;
m_textureList[file].w = texw;
m_textureList[file].h = texh;
return &m_textureList[file];
}
return &itFind->second;
}
// 单例引擎,便于全局获取
class SingleEngine {
public:
static Engine& getEngine();
};
Engine& SingleEngine::getEngine() {
static Engine engine;
return engine;
}
/********** Engine **************************************************************/
/********** Star **************************************************************/
class Star {
public:
Star();
void setPoint(int x, int y);
void onUpdate();
SDL_Rect getDesRect();
private:
SDL_Point m_point; //中心位置
Uint8 m_maxSize = 68; //最大尺寸
Uint8 m_minSize = 12; //最小尺寸
Uint8 m_curSize = 0; //当前尺寸
Uint32 m_createTime = 0; //创建时间
Uint32 m_flashCycle = 3000; //闪烁周期(最大到最小然后恢复到最大的时间,单位:ms)
Uint8 m_flashCount = 0; //闪烁次数
Uint32 m_lastTick = 0; //上次更新时间
Uint8 m_interval = 0; //更新间隔
bool m_add = false; //当前是否递增状态
};
Star::Star()
{
m_createTime = SDL_GetTicks();
m_lastTick = 0;
m_curSize = m_maxSize;
}
void Star::setPoint(int x, int y)
{
m_point.x = x;
m_point.y = y;
}
void Star::onUpdate()
{
auto tick = SDL_GetTicks();
//每次衰减/递增2个像素
const Uint8 unit = 2;
if (m_interval == 0) {
//需要的帧数
Uint8 frame = (m_maxSize - m_minSize) * 2 / unit;
//每帧的间隔
m_interval = m_flashCycle / frame;
}
if (SDL_TICKS_PASSED(tick, m_lastTick + m_interval)) {
if (m_curSize <= m_minSize) m_add = true;
if (m_curSize >= m_maxSize) m_add = false;
if (m_add) m_curSize += unit;
else m_curSize -= unit;
m_lastTick = tick;
}
}
SDL_Rect Star::getDesRect()
{
SDL_Rect rect;
rect.w = m_curSize;
rect.h = m_curSize;
rect.x = m_point.x - m_curSize / 2;
rect.y = m_point.y - m_curSize / 2;
return rect;
}
/********** Star **************************************************************/
int main(int, char**)
{
auto& engine = SingleEngine::getEngine();
if (false == engine.onInit("", 800, 600))
return -1;
auto render = engine.getRender();
auto tex = engine.getTextureInfo("images/star.jpg");
if (nullptr == tex)
return -1;
//engine.setClearColor(255, 255, 255);
engine.setFrame(60);
SDL_Point stars[] = {
{ 100, 100 },
{ 90 , 247 },
{ 156 , 25 },
{ 298 , 325 }
};
std::vector<Star> starList;
const int halfLife = 8, maxSize = 48;
Uint8 zoom = 0;
Uint32 tickLast = 0;
SDL_Event event;
while (!engine.isQuit())
{
while (engine.pollEvent(event))
{
if (event.type == SDL_KEYDOWN
&& event.key.keysym.sym == SDLK_1)
;
else if (event.type == SDL_MOUSEBUTTONDOWN) {
int mousex = 0, mousey = 0;
Uint32 buttons = SDL_GetMouseState(&mousex, &mousey);
Star star;
star.setPoint(mousex, mousey);
starList.push_back(star);
}
}
// update
for (auto& it : starList)
it.onUpdate();
// render
engine.startRender();
auto tick = SDL_GetTicks();
if (SDL_TICKS_PASSED(tick, tickLast + 100)) {
zoom++;
zoom %= halfLife * 2;
tickLast = tick;
}
for (int i = 0; i < sizeof(stars) / sizeof(SDL_Point); i++) {
SDL_Rect des;
int z = halfLife - abs(halfLife - zoom);
des.w = maxSize - z * 2;
des.h = des.w;
des.x = stars[i].x - des.w / 2;
des.y = stars[i].y - des.h / 2;
engine.displayTexture(tex, des);
}
for (auto& it : starList)
engine.displayTexture(tex, it.getDesRect());
engine.drawRect(SDL_FRect{ 10, 10, 15, 10 });
engine.endRender();
}
engine.shutdown();
return 0;
}