`
public0821
  • 浏览: 236161 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

SDL游戏开发教程04(C++封装SDL)

阅读更多

    前面的章节介绍了一个简单窗口的开发,这节将介绍如何把前面用到的东西用C++封装起来。

 

    为什么用C++封装起来:

1、C语言没有异常机制,每次调用一个函数都需要通过检查返回值来判断是否成功,比较麻烦。

2、对我个人而言,开发效率上C++要优于C语言,并且C++的代码要容易组织管理,写出来的代码也更容易理解。

 

    封装的主要部分:

1、将所有的SDL函数都用类包装起来,对于需要做返回值判断的函数,在包装的地方进行判断,然后决定是否抛出异常。这样对于调用者来说就不需要再做返回值判断了。

2、新建一个SDL类。该类用到了单例模式和工厂模式, 提供了访问所有SDL函数的入口。

3、将创建窗口和消息循环这部分代码封装成一个框架类,以后写代码时只要继承这个框架就可以了。

4、包装SDL_Surface结构体,使它可以像普通对象一样使用。因为通过SDL API获取一个SDL_Surface后需要要手动释放,否则会造成内存泄漏。

 

    封装之后的main函数:

#include <string>
#include "lessons/Lesson01.h"
int main( int argc, char* args[] )
{
	Lesson01 frame;

	frame.setSize(800, 600);		//设置窗口大小
	frame.setTitle("Lesson01");		//设置标题

	frame.open();					//打开窗口并开始循环

	return 0;
}

     封装成这样子之后,我们可以将不同课程中的例子代码写在不同的类中,完全隔离开,到时候想运行哪一课的例子修改一下main中的Lesson01就可以了。

 

    下面根据这份代码逐步介绍封装过程。上面main函数中用到了Lesson01类,下面先看看Lesson01里面有些什么东西。

    Lesson01.h

#ifndef LESSON01_H_
#define LESSON01_H_
#include "../SDLFrame.h"
class Lesson01 : public SDLFrame
{
public:
	Lesson01();
	virtual ~Lesson01();
protected:
	void onRender();	//渲染窗口
	void onInit();		//初始化
public:
	SDLSurfacePtr message;	//界面要显示的图片
};

#endif /* LESSON01_H_ */

    Lesson01.cpp

#include "Lesson01.h"

Lesson01::Lesson01()
{
	// TODO Auto-generated constructor stub

}

Lesson01::~Lesson01()
{
	// TODO Auto-generated destructor stub
}

void Lesson01::onRender()
{
	//将图片填充到screen
	SDL::video()->BlitSurface(message, NULL, screen, NULL);
}
void Lesson01::onInit()
{
	//加载图片
	SDLSurfacePtr loadedImage = SDL::video()->LoadBMP("E:\\code_picture\\javaeye.bmp");
	
	//将图片转换成适合程序的格式
	message = SDL::video()->DisplayFormat(loadedImage);
}
 

    这里可以看出Lesson01是继承自SDLFrame,而它本身只有两个函数,OnInit负责一些初始化工作,OnRender负责将要显示的内容填充到screen中去。

    Lesson01.cpp中用到了SDL::video(),这就是前面提到的SDL类,该类提供了所有SDL函数的入口,这里的SDL::video()->BlitSurface等于SDL_BlitSurface,只是包装了一下而以。

 

    下面看SDL类

    头文件

#ifndef SDLCORE_H_
#define SDLCORE_H_
#include "SDLException.h"
#include "SDLVideo.h"
#include "SDLWindow.h"
#include "SDLEvent.h"

class SDL
{
public:
	SDL();
	virtual ~SDL();
public:
	static void Init(Uint32 flags);		//初始化SDL环境,见SDL.h中以SDL_INIT_开头的宏定义
	static void Quit();					//退出SDL环境
public:
	static SDLVideo * video();			//SDLVideo封装了video相关的函数
	static SDLWindow * window();		//SDLWindow封装了窗口相关的函数
	static SDLEvent * event();			//SDLEvent封装了event相关的函数
};

#endif /* SDLCORE_H_ */

   CPP文件

#include "SDLCore.h"

SDL::SDL()
{
	// TODO Auto-generated constructor stub

}

SDL::~SDL()
{
	// TODO Auto-generated destructor stub
}

void SDL::Init(Uint32 flags)
{
	int ret =  SDL_Init(flags);
	if(ret == -1)
	{
		throw SDLException(std::string("初始化SDL错误:") + SDL_GetError());
	}
}
void SDL::Quit()
{
	SDL_Quit();
}

SDLVideo * SDL::video()
{
	static SDLVideo video;
	return &video;
}

SDLWindow * SDL::window()
{
	static SDLWindow window;
	return &window;
}

SDLEvent * SDL::event()
{
	static SDLEvent event;
	return &event;
}

   从上面的代码可以看出,SDL类只负责初始化和退出SDL环境,同时创建SDL相关的封装类对象,这里用到了C++静态成员变量的特性:全局生命周期且只被初始化一次。从而保证SDLVideo、SDLWindow、SDLEvent的对象全局唯一。

   SDLException是程序定义的一个异常类,由于很普通,所以在这里不再进行解释。

 

   SDLVideo、SDLWindow、SDLEvent都是SDL API函数的封装类,原理几乎是一样的,这里取其中的一个进行分析。

SDLVideo.h

#ifndef SDLVIDEO_H_
#define SDLVIDEO_H_
#include "SDLException.h"
#include "SDL/SDL.h"
#include "SDLSurface.h"

class SDLVideo
{
	friend class SDL;


private:
	SDLVideo();
public:
	virtual ~SDLVideo();
public:
	/**
	 * 设置窗口模式
	 * width	宽
	 * height	高
	 * bpp		颜色位数
	 * flags	SDL.h中以SDL_INIT_开头的宏定义
	 * return	窗口对应的内存块
	 */
	SDLSurfacePtr SetVideoMode(int width, int height, int bpp, Uint32 flags);

	/*
	 * 将内存中的内容显示到屏幕上
	 * screen	内存块
	 */
	void Flip(SDLSurfacePtr screen);

	/**
	 * 将图片转换成程序需要的格式(源图片和转换后的图片在不同的内存中)
	 * surface	源图片
	 * return	转换后的图片
	 */
	SDLSurfacePtr DisplayFormat(SDLSurfacePtr surface);

	/*
	 * 将硬盘上的图片加载到内存中(只支持BMP格式)
	 * file		图片文件路径
	 * return	加载后内存中的图片区域
	 */
	SDLSurfacePtr	LoadBMP(std::string file);

	/**
	 * 将源图片覆盖到目的图片区域上
	 * src		源图片
	 * srcrect	将要覆盖过去的源图片区域,NULL表示全部
	 * dst		目的图片
	 * dstrect	源图片要覆盖到目的图片的哪个地方,NULL表示左上角
	 */
	void BlitSurface(SDLSurfacePtr src, SDL_Rect *srcrect, SDLSurfacePtr dst, SDL_Rect *dstrect);
};

#endif /* SDLVIDEO_H_ */

 SDLVideo.cpp

#include "SDLVideo.h"

SDLVideo::SDLVideo() {
	// TODO Auto-generated constructor stub

}

SDLVideo::~SDLVideo() {
	// TODO Auto-generated destructor stub
}

SDLSurfacePtr SDLVideo::SetVideoMode(int width, int height, int bpp, Uint32 flags)
{
	SDL_Surface * surface = SDL_SetVideoMode(width, height, bpp, flags);
	if(NULL == surface)
	{
		throw SDLException(std::string("SDL_SetVideoMode初始化视频模式时发生错误:") + SDL_GetError());
	}

	return SDLSurfacePtr(new SDLSurface(surface));
}

void SDLVideo::Flip(SDLSurfacePtr screen)
{
	int ret = SDL_Flip(screen->value());
	if(ret == -1)
	{
		throw SDLException(std::string("SDL_Flip内存内容显示到屏幕时发生错误:") + SDL_GetError());
	}
}

SDLSurfacePtr SDLVideo::DisplayFormat(SDLSurfacePtr surface)
{
	SDL_Surface *newSurface = SDL_DisplayFormat(surface->value());
	if(NULL == newSurface)
	{
		throw SDLException(std::string("SDL_DisplayFormat转换图片格式为程序格式时发生错误:") + SDL_GetError());
	}

	return SDLSurfacePtr(new SDLSurface(newSurface));
}

SDLSurfacePtr	SDLVideo::LoadBMP(std::string file)
{
	SDL_Surface *surface = SDL_LoadBMP(file.c_str());
	if(NULL == surface)
	{
		throw SDLException(std::string("SDL_LoadBMP加载BMP图片时发生错误:") + SDL_GetError());
	}

	return SDLSurfacePtr(new SDLSurface(surface));
}

void SDLVideo::BlitSurface(SDLSurfacePtr src, SDL_Rect *srcrect, SDLSurfacePtr dst, SDL_Rect *dstrect)
{
	int ret = SDL_BlitSurface(src->value(), srcrect, dst->value(), dstrect);
	if(ret == -1)
	{
		throw SDLException(std::string("SDL_BlitSurface重叠图片时发生错误:") + SDL_GetError());
	}

}

     从上面的代码可以看出SDLVideo只是简单的将SDL中video相关的函数做一下包装,检查SDL函数的返回值,如果有错误就抛出异常。在头文件中,将SDL类声明成友元类并且将构造函数设置为private是为了避免在除SDL类以外的地方实例化该类的对象。

    这里用到了SDLSurfacePtr和SDLSurface。SDLSurfacePtr定义:

typedef boost::shared_ptr<SDLSurface> SDLSurfacePtr;

    构造SDLSurfacePtr的代码为:

SDLSurfacePtr(new SDLSurface(surface));

    可以看出,SDLSurfacePtr中有SDLSurface,SDLSurface中有SDL_Surface*。

    boost库的shared_ptr是一种带引用计数的智能指针,当shared_ptr对象的引用计数变成0的时候,会自动delete它里面保存的对象,所以当最后一个SDLSurfacePtr对象析构的时候,SDLSurfacePtr会调用delete SDLSurface。关于shared_ptr的详细介绍,可以通过GOOGLE搜到很多资料。

    SDLSurface的析构函数如下:

SDLSurface::~SDLSurface()
{
	if(surface != NULL)//surface是SDL_Surface *类型
	{
		SDL_FreeSurface(surface);
	}
}

    由于SDLSurface的析构函数中会调用SDL_Surface*的释放操作。从而使得内存中的SDL_Surface*被自动释放。这样就省去了手动释放SDL_Surface的麻烦。

 

    最后来看看SDLFrame类

头文件:

#ifndef SDLFRAME_H_
#define SDLFRAME_H_

#include "SDL/SDLCore.h"
class SDLFrame
{
public:
	static const std::string DEFAULT_TITLE;			//默认窗口标题
	static const int DEFAULT_SCREEN_WIDTH = 800;	//默认窗口宽
	static const int DEFAULT_SCREEN_HEIGHT = 600;	//默认窗口高
public:
	SDLFrame();
	virtual ~SDLFrame();
public:
	/*
	 * 打开窗口
	 * flags	窗口模式,见SDL_video.h中的宏定义
	 */
	void 	open(Uint32 flags = SDL_HWSURFACE | SDL_DOUBLEBUF);

	void 	setTitle(std::string title);
	void 	setSize(int width, int heigth);
protected:
	/**
	 * 消息处理函数,当有用户输入的时候,框架会调用此函数
	 * event	待处理的消息
	 * return	如果为false,则程序退出
	 */
	virtual	bool onEvent(const SDL_Event *event);

	/**
	 * 当需要绘制窗口时,框架会调用此函数
	 */
	virtual void onRender();

	/**
	 * 显示窗口前,框架会调用此函数
	 */
	virtual void onInit();
protected:
	SDLSurfacePtr screen;
	std::string title;
	int	width;
	int height;
};

#endif /* SDLFRAME_H_ */

 源文件:

#include "SDLFrame.h"

const std::string SDLFrame::DEFAULT_TITLE  = "SDL Tutorial";

SDLFrame::SDLFrame()
{
	title = DEFAULT_TITLE;
	width = DEFAULT_SCREEN_WIDTH;
	height = DEFAULT_SCREEN_HEIGHT;
}

SDLFrame::~SDLFrame()
{
	// TODO Auto-generated destructor stub
}

void 	SDLFrame::open(Uint32 flags)
{
	//初始化SDL环境
	SDL::Init(SDL_INIT_EVERYTHING);

	//设置屏幕模式
	screen = SDL::video()->SetVideoMode(width, height, 32, flags);

	//设置窗口标题
	SDL::window()->SetCaption(title);

	//初始化
	onInit();

	//开始事务循环
	SDL_Event event;
	bool bQuit = false;
	while(!bQuit)
	{
		while( SDL::event()->PollEvent( &event ) )
		{
			if(!onEvent(&event))
			{
				bQuit = true;
			}
		}

		//绘制
		onRender();

		//将在内存中的处理结果显示到屏幕上
		SDL::video()->Flip(screen);
	}

	//退出SDL环境
	SDL::Quit();
}
void 	SDLFrame::setTitle(std::string title)
{
	this->title = title;
}

void 	SDLFrame::setSize(int width, int heigth)
{
	this->width = width;
	this->height = heigth;
}
bool SDLFrame::onEvent(const SDL_Event *event)
{
	switch(event->type)
	{
	case SDL_KEYDOWN:
		if(event->key.keysym.sym == SDLK_ESCAPE)
		{
			return false;
		}
		break;
	case SDL_QUIT:
		return false;
		break;
	default:
		break;
	}

	return true;
}
void SDLFrame::onRender()
{

}
void SDLFrame::onInit()
{

}

     SDLFrame类封装了消息循环,通过在循环中调用成员函数的方式将消息循环中公共的部分与特殊的部分分离开,从而可以在基类中重载这些成员函数使不同的基类表现出不通的特性。其中onEvent负责处理用户输入,onInit负责窗口创建后的初始化,onRender负责窗口的绘制。

     这里onEvent只处理了窗口关闭和ESC键按下两个消息,子类可以通过重载来覆盖默认实现。onInit和onRender都是空实现。需要在子类中去实现具体的操作。

     结合消息循环,现在再回过头去看Lesson01的代码,就会发现只要程序一有空闲,就会调用onRender函数,而Lesson1的onRender函数中只有一行代码:SDL::video()->BlitSurface(message, NULL, screen, NULL);,并且这行代码中用到的message和screen永远不会变,你可能会想老这样调用同样的代码是不是很浪费资源,在这里确实是浪费资源,其实只要将这行代码放到onInit函数的末尾就可以了。这里可以这样做的原因是因为程序初始化好了之后内存中的内容不再会发生变化,所以每次调用SDL::video()->Flip(screen)都不会改变屏幕显示的内容。说的通俗点,就是这个程序太简单了,用不着定时去更新窗口。后面的章节中将会看到定时更新窗口的用处。

     这节的内容就介绍到这里,在以后的章节中,都将采用同Lesson01一样的方式来编写代码。附件中是本节内容的完整源代码。

 

  • src.rar (7.7 KB)
  • 下载次数: 92
分享到:
评论

相关推荐

    SDL入门教程中文(最好的SDL入门教程,自己手工整理)

    因为Linux的普及以及不受$M的牵制,SDL在过去的几年中,成为了跨平台开发PC游戏的首选。即使是在Windows平台下,SDL有具有自身的优势。与MFC使用不成熟的C++外表伪封装的win32api以及一家独唱推崇的COM风格和.net相...

    C++ 封装 ddraw渲染视频图像

    使用硬件加速,视频绘制,ddraw 完美封装类,C++类,视频播放器

    SDL入门教程.pdf

    们可以看出,SDL 基本上可以认为是为以电脑游戏为核心开发的多媒体库。 SDL 支持主流的操作系统,包括 Windows 和 Linux。在官方的介绍中,我们可以找到 它所支持的其他平台。(SDL supports Linux, Windows, ...

    ffplay windows 版本,去掉SDL并封装为单独类

    将 ffplay 更改为windows版本,去掉SDL,使用GDI+显示,并封装为单独的播放类,实现了快进,后退,暂停,其他请参考ffplay.c自己添加吧 ffmpeg 大概是 2015.4月的版本,每次重新打开文好像有句柄泄露, 发现问题的请...

    利用C++模板的C到Lua简易封装库LuaMe

    多处使用了条件编译,可以利用条件编译出不同特性的结构体(具体使用参考其中的代码),文件包括两个codeblocks工程,一个是LuaMe,另一个是使用LuaMe封装的一个SDL库,还是主要作为封装的C结构体的代码,但是加入了...

    利用C++模板的C到Lua简易封装库

    压缩包里的LuaMe是封装库,LuaSDL2是利用该库封装的SDL1.2的一些函数和结构体(CodeBlocks),可以作为参考代码。源代码行数相当小,可以作为自己实现封装的参考。想封装C++类的话原理类似。如果遇到疑问可以尝试阅读...

    基于SDL的俄罗斯方块

    基于SDL的俄罗斯方块,封装的不是很好,但是关于方块碰撞和旋转以及Map到GUI都是值得参考,直接运行./mk.sh,生成的文件在Target目录下。

    FFmpeg播放器实例代码下载

    使用C++封装FFmpeg,编写的播放音视频的实例,在MFC控件显示播放,可以播放本地文件,也可以播放网络流, 主要描述了ffmpeg音视频解码的详细步骤,SDL和MFC控件关联显示,音视频同步的控制,本实例使用VS2012开发。

    C++计算机图形学OpenGL编程套件合集(Windows 64位)

    含有如下库文件: 1. FreeImage(图像高级解析库) 2. GLEW(OpenGL函数指针初始化工具) 3. GLFW(一个OpenGL功能封装实现) ...同时支持MinGW64和Visual C++两种编译器,请任选其一进行学习与开发

    CipherCrypt C++:一个 C++ 程序,它封装了输入/输出和分组密码。-开源

    CipherCrypt C++ 是一个大学项目,旨在为将数据保存到外部文件的项目添加一层安全性。 该项目使用基本分组密码来加扰纯文本字段。 这些分组密码绝不是安全的,也不是安全的,CipherCrypt C++ 更倾向于以易于使用的...

    FFmpeg基础库编程开发

    ffdshow 源代码分析 6: 对解码器的dll的封装(libavcodec) 322 ffdshow 源代码分析 8: 视频解码器类(TvideoCodecDec) 344 ffdshow 源代码分析 9: 编解码器有关类的总结 352 9.2 LAV filters 357 LAV Filter 源...

    Mezzanine:一个支持高性能3D图形物理和声音的游戏引擎

    我们正在创建一个紧密集成的API,该API封装了许多开源库(Bullet3d,Ogre3d,SDL,OpenAL-soft,Ogg,Vorbis,Lua和PugiXML)。 似乎有许多将某些库组合在一起的常用方法,并且对于使用它们进行自我射击的开发人员...

    easy_lib:使开发更快的 C 库集合!

    使开发更快的 C 库集合! 这个库的目标是每个人都希望他们可以做一些真正的编程,而不会遇到内存管理和/或所有繁琐的系统调用的麻烦...... 该库包括: 简单的正则表达式:Unix 正则表达式库的封装 简单清单 将包括...

    FFmpeg4.3系列之26:视频监控之H265多路摄像头播控项目实战

    二、FFmpeg4.3+SDL2+Qt5开发环境的搭建 三、FFmpeg的SDK编程回顾总结并操练 四、SDL2.0的编程回顾总结并操练 五、颜色空间转换RGB和YUV的原理与实战 六、Qt5+FFmpeg本地摄像头采集预览实战 七、代码封装:摄像头h264...

Global site tag (gtag.js) - Google Analytics