SDL简介(译文)
By robot-v1.0
本文链接 https://www.kyfws.com/games/introduction-to-sdl-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 10 分钟阅读 - 4912 个词 阅读量 0SDL简介(译文)
原文地址:https://www.codeproject.com/Articles/39178/Introduction-to-SDL
原文作者:MatthewCasperson
译文由本站 robot-v1.0 翻译
前言
See how to use SDL to create a simple visual application.
了解如何使用SDL创建简单的可视应用程序.
介绍(Introduction)
本文逐步指导您使用SDL和C ++创建基本的游戏框架.最终结果非常简单,但是它为构建功能更强大的视觉应用程序和游戏提供了坚实的基础.(This article steps you through the creation of a basic game framework using SDL and C++. The end result is quite simple, but it provides a solid foundation to build more functional visual applications and games.)
背景(Background)
这段代码被用作为(This code was used as the basis for a shoot’em’up created for an) SDL游戏编程竞赛(SDL game programming competition) .(.)
使用代码(Using the code)
我们将从(We will start with the) EngineManager
类.此类将负责初始化SDL,维护将构成游戏的对象以及分发事件.(class. This class will be responsible for initialising SDL, maintaining the objects that will make up the game, and distributing events.)
EngineManager.h(EngineManager.h)
#ifndef _ENGINEMANAGER_H__
#define _ENGINEMANAGER_H__
#include <sdl.h>
#include <list>
#define ENGINEMANAGER EngineManager::Instance()
class BaseObject;
typedef std::list<baseobject*> BaseObjectList;
class EngineManager
{
public:
~EngineManager();
static EngineManager& Instance()
{
static EngineManager instance;
return instance;
}
bool Startup();
void Shutdown();
void Stop() {running = false;}
void AddBaseObject(BaseObject* object);
void RemoveBaseObject(BaseObject* object);
protected:
EngineManager();
void AddBaseObjects();
void RemoveBaseObjects();
bool running;
SDL_Surface* surface;
BaseObjectList baseObjects;
BaseObjectList addedBaseObjects;
BaseObjectList removedBaseObjects;
Uint32 lastFrame;
};
#endif
EngineManager.cpp(EngineManager.cpp)
#include "EngineManager.h"
#include "ApplicationManager.h"
#include "BaseObject.h"
#include "Constants.h"
#include <boost/foreach.hpp>
EngineManager::EngineManager() :
running(true),
surface(NULL)
{
}
EngineManager::~EngineManager()
{
}
bool EngineManager::Startup()
{
if(SDL_Init(SDL_INIT_EVERYTHING) < 0)
return false;
if((surface = SDL_SetVideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, BITS_PER_PIXEL,
SDL_HWSURFACE | SDL_DOUBLEBUF | SDL_ANYFORMAT)) == NULL)
return false;
APPLICATIONMANAGER.Startup();
lastFrame = SDL_GetTicks();
while (running)
{
SDL_Event sdlEvent;
while(SDL_PollEvent(&sdlEvent))
{
if (sdlEvent.type == SDL_QUIT)
running = false;
}
AddBaseObjects();
RemoveBaseObjects();
Uint32 thisFrame = SDL_GetTicks();
float dt = (thisFrame - lastFrame) / 1000.0f;
lastFrame = thisFrame;
BOOST_FOREACH (BaseObject* object, baseObjects)
object->EnterFrame(dt);
SDL_Rect clearRect;
clearRect.x = 0;
clearRect.y = 0;
clearRect.w = SCREEN_WIDTH;
clearRect.h = SCREEN_HEIGHT;
SDL_FillRect(surface, &clearRect, 0);
BOOST_FOREACH (BaseObject* object, baseObjects)
object->Draw(this->surface);
SDL_Flip(surface);
}
return true;
}
void EngineManager::Shutdown()
{
APPLICATIONMANAGER.Shutdown();
surface = NULL;
SDL_Quit();
}
void EngineManager::AddBaseObject(BaseObject* object)
{
addedBaseObjects.push_back(object);
}
void EngineManager::RemoveBaseObject(BaseObject* object)
{
removedBaseObjects.push_back(object);
}
void EngineManager::AddBaseObjects()
{
BOOST_FOREACH (BaseObject* object, addedBaseObjects)
baseObjects.push_back(object);
addedBaseObjects.clear();
}
void EngineManager::RemoveBaseObjects()
{
BOOST_FOREACH (BaseObject* object, removedBaseObjects)
baseObjects.remove(object);
removedBaseObjects.clear();
}
我们要做的第一件事就是打电话(The first thing we need to do is call) SDL_Init
.这将加载SDL库,并初始化我们指定的任何子系统.在这种情况下,我们已指定通过提供(. This loads the SDL library, and initialises any of the subsystems that we specify. In this case, we have specified that everything be initialised by supplying the) SDL_INIT_EVERYTHING
旗.您可以选择仅初始化所需的子系统(音频,视频,输入等),但是由于随着游戏的进行我们将使用这些子系统中的大多数子系统,因此初始化所有东西现在可以节省一些时间.(flag. You could choose to initialise only the subsystems you need (audio, video, input etc.), but since we will be making use of most of these subsystems as the game progresses, initialising everything now saves some time.)
if(SDL_Init(SDL_INIT_EVERYTHING) < 0)
return false;
如果SDL及其子系统已正确加载和初始化,则我们将创建一个窗口.的(If SDL and its subsystems were loaded and initialised correctly, we then create a window. The) SCREEN_WIDTH
,(,) SCREEN_HEIGHT
和(, and) BITS_PER_PIXEL
定义窗口的大小和颜色深度.这些值在(define the size of the window and the colour depth. These values are defined in the)**常数h(Constants.h)**文件.下一个参数是选项的集合,这些选项进一步指定了窗口的工作方式.(file. The next parameter is a collection of options that further specify how the window is to work.)
的(The) SDL_HWSURFACE
选项告诉SDL将视频界面放置在视频内存(即视频卡上的内存)中.大多数系统都有专用的视频卡,具有足够的存储空间,当然足以容纳我们的2D游戏.(option tells SDL to place the video surface in video memory (i.e., the memory on your video card). Most systems have dedicated video cards with plenty of memory, and certainly enough to hold our 2D game.)
的(The) SDL_DOUBLEBUF
选项告诉SDL设置两个视频界面,并通过调用来在两个视频界面之间交换(option tells SDL to set up two video surfaces, and to swap between the two with a call to) SDL_Flip()
.这样可以避免在写入视频内存时刷新显示器时引起的视觉撕裂.它比单个缓冲的渲染方案要慢,但同样,大多数系统都足够快,不会对性能造成任何影响.(. This stops the visual tearing that can be caused when the monitor is refreshing while the video memory is being written to. It is slower than a single buffered rendering scheme, but again, most systems are fast enough for this not to make any difference to the performance.)
的(The) SDL_ANYFORMAT
选项告诉SDL,如果无法设置具有请求的色深的窗口,则可以自由使用可用的最佳色深.我们请求的颜色深度为32位,但是某些台式机可能仅以16位运行.这意味着我们的应用程序不会因为桌面未设置为32位色深而失败.(option tells SDL that if it can’t set up a window with the requested colour depth, that it is free to use the best colour depth available to it. We have requested a 32 bit colour depth, but some desktops may only be running at 16 bit. This means that our application won’t fail just because the desktop is not set to 32 bit colour depth.)
if((surface = SDL_SetVideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, BITS_PER_PIXEL,
SDL_HWSURFACE | SDL_DOUBLEBUF | SDL_ANYFORMAT)) == NULL)
return false;
的(The) ApplicationManager
然后启动.的(is then started up. The) ApplicationManager
包含定义应用程序运行方式的逻辑.它与(holds the logic that defines how the application is run. It is kept separate from the) EngineManager
将初始化和管理SDL所需的代码与管理应用程序本身所需的代码分开.(to separate the code required to initialise and manage SDL from the code required to manage the application itself.)
APPLICATIONMANAGER.Startup();
当前系统时间已获取并存储在(The current system time is taken and stored in the) lastFrame
变量.稍后将使用它来计算自渲染最后一帧以来已经过了多长时间.帧之间的时间用于允许游戏对象以可预测的方式移动,而不管帧速率如何.(variable. This will be used later on to work out how long it has been since the last frame was rendered. The time between frames is used to allow the game objects to move in a predictable way regardless of the frame rate.)
lastFrame = SDL_GetTicks();
下一段代码定义了渲染循环.渲染循环是每帧执行一次的循环.(The next block of code defines the render loop. The render loop is a loop that is executed once per frame.)
我们在渲染循环中要做的第一件事是处理可能在最后一帧期间触发的任何SDL事件.目前,我们唯一感兴趣的事件是(The first thing we do in the render loop is deal with any SDL events that may have been triggered during the last frame. For now, the only event we are interested in is the) SDL_Quit
事件,在关闭窗口时触发.在这种情况下,我们将运行变量设置为(event, which is triggered when the window is closed. In that event, we set the running variable to) false
,这会使我们退出渲染循环.(, which will drop us out of the render loop.)
SDL_Event sdlEvent;
while(SDL_PollEvent(&sdlEvent))
{
if (sdlEvent.type == SDL_QUIT)
running = false;
}
接下来,任何新的或删除的(Next, any new or removed) BaseObject
s与主同步(s are synced up with the main) baseObjects
采集.的(collection. The) BaseObject
class是将成为游戏一部分的所有对象的基类.当一个新的(class is the base class for all the objects that will be a part of the game. When a new) BaseObject
类被创建或销毁,它将自身添加或删除到由(class is created or destroyed, it adds or removes itself from the main collection maintained by the) EngineManager
.但是,他们无法修改(. But, they cannot modify the) baseObjects
直接收集-将任何新对象或已删除的对象放入称为(collection directly – any new or removed objects are placed into temporary collections called) addedBaseObjects
和(and) removedBaseObjects
,以确保(, which ensures the) baseObjects
集合在循环时不会被修改.当您遍历一个集合时,修改一个集合绝不是一个好主意.的电话(collection is not modified while it is being looped over. It is never a good idea to modify a collection while you are looping over its items. The calls to the) AddBaseObjects
和(and) RemoveBaseObjects
功能允许(functions allow the) baseObjects
当我们确定我们没有遍历它时,将对其进行更新.(collection to be updated when we can be sure we are not looping over it.)
AddBaseObjects();
RemoveBaseObjects();
自上一帧以来的时间以秒(或几分之一秒)计算,当前系统时间保存在(The time since the last frame is calculated in seconds (or a fraction of a second), and the current system time is saved in the) lastFrame
变量.(variable.)
Uint32 thisFrame = SDL_GetTicks();
float dt = (thisFrame - lastFrame) / 1000.0f;
lastFrame = thisFrame;
每一个(Every) BaseObject
然后有它(then has its) Update
函数调用.的(function called. The) Update
功能是游戏对象可以执行他们需要做的任何内部更新的地方,例如移动,旋转或射击武器.提供了之前计算的帧时间,因此游戏对象可以每秒更新相同数量的帧,而不考虑帧速率.(function is where the game objects can perform any internal updates they need to do, like moving, rotating, or shooting a weapon. The frame time calculated just before is supplied, so the game objects can update themselves by the same amount every second, regardless of the frame rate.)
BOOST_FOREACH (BaseObject* object, baseObjects)
object->EnterFrame(dt);
然后,通过使用(The video buffer is then cleared by painting a black rectangle to the entire screen using the) SDL_FillRect
功能.(function.)
SDL_Rect clearRect;
clearRect.x = 0;
clearRect.y = 0;
clearRect.w = SCREEN_WIDTH;
clearRect.h = SCREEN_HEIGHT;
SDL_FillRect(surface, &clearRect, 0);
现在,要求游戏对象通过调用其将自己吸引到视频界面(Now the game objects are asked to draw themselves to the video surface by calling their) Draw
功能.在这里,游戏对象用来表示自己的任何图形都被绘制到后台缓冲区.(function. It’s here that any graphics that the game objects use to represent themselves are drawn to the back buffer.)
BOOST_FOREACH (BaseObject* object, baseObjects)
object->Draw(this->surface);
最后,翻转后缓冲区,将其显示在屏幕上.(Finally, the back buffer is flipped, displaying it on the screen.)
SDL_Flip(surface);
的(The) Shutdown
函数清除所有内存.(function cleans up any memory.)
的(The) ApplicationManager
有其关闭函数,该函数将清除其创建的所有对象.(has its shutdown function called, where it will clean up any objects it has created.)
APPLICATIONMANAGER.Shutdown();
然后我们打电话(We then call) SDL_Quit()
,它会卸载SDL库.(, which unloads the SDL library.)
surface = NULL;
SDL_Quit();
接下来的四个功能(The next four functions,) AddBaseObject
,(,) RemoveBaseObject
,(,) AddBaseObjects
和(and) RemoveBaseObjects
,全部用于添加或删除(, are all used to either add or remove) BaseObject
到临时(s to the temporary) addedBaseObjects
和(and) removedBaseObjects
集合,或将这些临时集合中的对象同步到主集合(collections, or to sync up the objects in these temporary collections to the main) baseObjects
采集.(collection.)
void EngineManager::AddBaseObject(BaseObject* object)
{
addedBaseObjects.push_back(object);
}
void EngineManager::RemoveBaseObject(BaseObject* object)
{
removedBaseObjects.push_back(object);
}
void EngineManager::AddBaseObjects()
{
BOOST_FOREACH (BaseObject* object, addedBaseObjects)
baseObjects.push_back(object);
addedBaseObjects.clear();
}
void EngineManager::RemoveBaseObjects()
{
BOOST_FOREACH (BaseObject* object, removedBaseObjects)
baseObjects.remove(object);
removedBaseObjects.clear();
}
如前所述,(As we mentioned earlier, the) ApplicationManager
包含定义应用程序运行方式的代码.在这个非常简单的演示中,我们将创建一个新的(holds the code that defines how the application is run. In this very simple demo, we are creating a new instance of the) Bounce
中的对象(object in the) Startup
功能,然后将其删除(function, and removing it in the) Shutdown
功能.(function.)
ApplicationManager.h(ApplicationManager.h)
#ifndef _APPLICATIONMANAGER_H__
#define _APPLICATIONMANAGER_H__
#define APPLICATIONMANAGER ApplicationManager::Instance()
#include "Bounce.h"
class ApplicationManager
{
public:
~ApplicationManager();
static ApplicationManager& Instance()
{
static ApplicationManager instance;
return instance;
}
void Startup();
void Shutdown();
protected:
ApplicationManager();
Bounce* bounce;
};
#endif
ApplicationManager.cpp(ApplicationManager.cpp)
#include "ApplicationManager.h"
ApplicationManager::ApplicationManager() :
bounce(NULL)
{
}
ApplicationManager::~ApplicationManager()
{
}
void ApplicationManager::Startup()
{
try
{
bounce = new Bounce("../media/image.bmp");
}
catch (std::string& ex)
{
}
}
void ApplicationManager::Shutdown()
{
delete bounce;
bounce = NULL;
}
的(The) BaseObject
class是所有游戏对象的基类.它定义了(class is the base class for all game objects. It defines the) EnterFrame
和(and) Draw
的功能(functions that the) EngineManager
类在渲染循环中使用.除了定义这些功能,(class uses during the render loop. Apart from defining these functions, the) BaseObject
也向(also registers itself with the) EngineManager
通过调用(when it is created by calling the) AddBaseObject
函数,并通过调用(function, and removes itself by calling the) RemoveBaseObject
当它被摧毁.(when it is destroyed.)
BaseObject.h(BaseObject.h)
#ifndef _BASEOBJECT_H__
#define _BASEOBJECT_H__
#include <SDL.h>
class BaseObject
{
public:
BaseObject();
virtual ~BaseObject();
virtual void EnterFrame(float dt) {}
virtual void Draw(SDL_Surface* const mainSurface) {}
};
#endif
BaseObject.cpp(BaseObject.cpp)
#include "BaseObject.h"
#include "EngineManager.h"
BaseObject::BaseObject()
{
ENGINEMANAGER.AddBaseObject(this);
}
BaseObject::~BaseObject()
{
ENGINEMANAGER.RemoveBaseObject(this);
}
的(The) VisualGameObject
扩展(extends the) BaseObject
类,并增加了在屏幕上显示图像的功能.(class, and adds the ability to display an image to the screen.)
VisualGameObject.h(VisualGameObject.h)
#ifndef _VISUALGAMEOBJECT_H__
#define _VISUALGAMEOBJECT_H__
#include <string>
#include <SDL.h>
#include "BaseObject.h"
class VisualGameObject :
public BaseObject
{
public:
VisualGameObject(const std::string& filename);
virtual ~VisualGameObject();
virtual void Draw(SDL_Surface* const mainSurface);
protected:
SDL_Surface* surface;
float x;
float y;
};
#endif
VisualGameObject.cpp(VisualGameObject.cpp)
#include "VisualGameObject.h"
VisualGameObject::VisualGameObject(const std::string& filename) :
BaseObject(),
surface(NULL),
x(0),
y(0)
{
SDL_Surface* temp = NULL;
if((temp = SDL_LoadBMP(filename.c_str())) == NULL)
throw std::string("Failed to load BMP file.");
surface = SDL_DisplayFormat(temp);
SDL_FreeSurface(temp);
}
VisualGameObject::~VisualGameObject()
{
if (surface)
{
SDL_FreeSurface(surface);
surface = NULL;
}
}
void VisualGameObject::Draw(SDL_Surface* const mainSurface)
{
SDL_Rect destRect;
destRect.x = int(x);
destRect.y = int(y);
SDL_BlitSurface(surface, NULL, mainSurface, &destRect);
}
SDL表面是通过加载的BMP文件创建的,其中包含(An SDL surface is created from a loaded BMP file with the) SDL_LoadBMP
功能((function () SDL_LoadBMP
从技术上讲是一个宏).(is technically a macro).)
SDL_Surface* temp = NULL;
if((temp = SDL_LoadBMP(filename.c_str())) == NULL)
throw std::string("Failed to load BMP file.");
加载表面后,它的颜色深度可能与屏幕不同.尝试绘制颜色深度不同的表面会花费很多额外的CPU周期,因此我们使用(When the surface is loaded, it may not have the same colour depth as the screen. Trying to draw a surface that is not the same colour depth takes a lot of extra CPU cycles, so we use the) SDL_DisplayFormat
函数创建与当前屏幕深度匹配的刚加载的表面的副本.通过这样做一次,而不是每帧,我们可以获得一些额外的性能.(function to create a copy of the surface we just loaded which matches the current screen depth. By doing this once, as opposed to every frame, we gain some extra performance.)
surface = SDL_DisplayFormat(temp);
以正确的格式创建新曲面后,可以删除通过加载BMP文件创建的曲面.(After the new surface has been created with the correct format, the surface that was created by loading the BMP file can be removed.)
SDL_FreeSurface(temp);
的(The) VisualGameObject
析构函数清理由构造函数创建的表面.(destructor cleans up the surface that was created by the constructor.)
VisualGameObject::~VisualGameObject()
{
if (surface)
{
SDL_FreeSurface(surface);
surface = NULL;
}
}
最后,(Finally, the) Draw
函数将被覆盖,并使用x和y坐标作为图像的左上角位置,提供绘制绘制在屏幕构造函数中加载的表面所需的代码.(function is overridden, and provides the code necessary to draw the surface we loaded in the constructor of the screen, using the x and y coordinates as the top left position of the image.)
void VisualGameObject::Draw(SDL_Surface* const mainSurface)
{
SDL_Rect destRect;
destRect.x = int(x);
destRect.y = int(y);
SDL_BlitSurface(surface, NULL, mainSurface, &destRect);
}
的(The) Bounce
类是所有其他这些类如何组合在一起的示例.(class is an example of how all of these other classes come together.)
弹跳(Bounce.h)
#ifndef _BOUNCE_H__
#define _BOUNCE_H__
#include <SDL.h>
#include <string>
#include "VisualGameObject.h"
class Bounce :
public VisualGameObject
{
public:
Bounce(const std::string filename);
~Bounce();
void EnterFrame(float dt);
protected:
int xDirection;
int yDirection;
};
#endif
Bounce.cpp(Bounce.cpp)
#include "Bounce.h"
#include "Constants.h"
static const float SPEED = 50;
Bounce::Bounce(const std::string filename) :
VisualGameObject(filename),
xDirection(1),
yDirection(1)
{
}
Bounce::~Bounce()
{
}
void Bounce::EnterFrame(float dt)
{
this->x += SPEED * dt * xDirection;
this->y += SPEED * dt * yDirection;
if (this->x < 0)
{
this->x = 0;
xDirection = 1;
}
if (this->x > SCREEN_WIDTH - surface->w)
{
this->x = float(SCREEN_WIDTH - surface->w);
xDirection = -1;
}
if (this->y < 0)
{
this->y = 0;
yDirection = 1;
}
if (this->y > SCREEN_HEIGHT - surface->h)
{
this->y = float(SCREEN_HEIGHT - surface->h);
yDirection = -1;
}
}
的(The) EnterFrame
功能被覆盖,并通过修改基数来移动图像(function is overridden, and moves the image around by modifying the base) VisualGameObject
x
和(and) y
变量,当屏幕碰到边缘时会从屏幕的侧面弹起.(variables, bouncing off the sides of the screen when it hits the edge.)
void Bounce::EnterFrame(float dt)
{
this->x += SPEED * dt * xDirection;
this->y += SPEED * dt * yDirection;
if (this->x < 0)
{
this->x = 0;
xDirection = 1;
}
if (this->x > SCREEN_WIDTH - surface->w)
{
this->x = float(SCREEN_WIDTH - surface->w);
xDirection = -1;
}
if (this->y < 0)
{
this->y = 0;
yDirection = 1;
}
if (this->y > SCREEN_HEIGHT - surface->h)
{
this->y = float(SCREEN_HEIGHT - surface->h);
yDirection = -1;
}
}
如您所见,通过定义在SDL中使用SDL所需的基本逻辑(As you can see, by defining the underlying logic required to use SDL in the) EngineManager
,(,) BaseObject
和(, and) VisualGameObject
类,类(classes, classes like) Bounce
可以专注于定义其行为的代码,而不必担心诸如将自身绘制到屏幕之类的低级问题.随着游戏的开发,我们将更多地使用这些基类.(can focus on the code that defines their behaviour rather than worry about the lower level issues like drawing itself to the screen. We will use these base classes more as the game is developed.)
现在,我们对SDL进行了简单介绍,以及可以建立的框架的开始.(For now, we have a simple introduction to SDL, as well as the beginnings of a framework that we can build on to.)
历史(History)
- 2009年8月21日-最初职位.(21 August 2009 - Initial post.)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
VC7.0 VC7.1 VC8.0 C++ VC6 Dev 新闻 翻译