`
public0821
  • 浏览: 238056 次
  • 性别: 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游戏开发教程06(利用BOOST库实现简单的文件日志功能)

    在本教程中,我们将深入探讨如何使用Boost库在SDL游戏开发中实现文件日志功能。Boost库是一个流行的C++库集合,它提供了许多实用的工具和功能,包括日期时间处理、文件系统操作以及系统接口等。在游戏开发中,日志...

    sdl游戏开发中文文档

    - **相比MFC**:SDL采用了更纯粹的C语言风格,相比MFC的C++封装win32api更加灵活和简洁。 - **与.NET比较**:SDL不受特定平台或框架的限制,提供了更多的自由度和灵活性,尤其是在跨平台开发方面更具优势。 综上所...

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

    ### SDL入门教程中文知识点概述 ...本教程通过详细介绍SDL的基本概念、特点及其应用领域,为读者提供了一个全面的入门指南,旨在帮助他们快速建立起游戏开发的基础知识,并激发其进一步探索的兴趣。

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

    在IT行业中,C++是一种强大的编程语言,常用于开发高性能的应用程序,特别是在游戏开发和图形处理领域。在本话题中,我们将深入探讨如何利用C++的封装特性以及DirectDraw(ddraw)接口来实现视频图像的硬件加速绘制...

    sdl封装半成品

    3. **面向对象设计**:Qt以其面向对象的设计闻名,封装SDL时,可能会采用类和对象来组织代码,如创建一个`SDLWindow`类来封装窗口操作,`SDLEvent`类处理事件,以此提高代码的可读性和可维护性。 4. **组件化**:Qt...

    SDL入门教程.pdf

    教程指出,相比于Windows平台下的MFC和.net框架,SDL提供了一种更纯粹的C语言风格的编程体验,这让程序员能够以自己的方式封装SDL,无论是采用纯C风格还是面向对象的C++风格,都能得到清晰、易于阅读的代码。...

    SDL封装(SDL:二维图形类库)

    通过以上分析可以看出,通过C++对SDL进行封装,不仅可以提高代码的可读性和可维护性,还能有效减少开发过程中可能遇到的问题。良好的封装实践还能进一步提升开发者的编程技能,使项目更具竞争力。希望本文档能为您的...

    SDL读文件播放视频

    在IT领域,尤其是在游戏开发和多媒体应用中,Simple DirectMedia Layer (SDL) 是一个非常流行的开源库,它为开发者提供了跨平台的图形、音频和输入设备处理能力。本示例中,我们将深入探讨如何使用C++和SDL库来读取...

    SDL2 c#封装库

    这个C#封装库(SDLC#)的目标是提供一个易用且高效的接口,使得C#开发者能够充分利用SDL2的功能,而无需深入理解C++和SDL2的底层细节。在`SDL2-CS-master`这个压缩包中,包含了项目的源代码、示例、文档和其他相关...

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

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

    C++游戏设计案例教程

    在C++游戏设计案例教程中,我们可以探索C++这一强大编程语言在游戏开发中的应用。C++以其高效、灵活性和面向对象的特性,被广泛用于创建复杂的游戏引擎和游戏逻辑。本教程通过一系列实例,旨在帮助学习者掌握游戏...

    SDL的c++版本(BDSDL)

    "SDL的c++版本(BDSDL)" 这个标题指的是一个基于C++封装的Simple DirectMedia Layer (SDL) 库的项目,名为BDSDL。SDL是一个跨平台的多媒体库,主要用于游戏开发和其他实时多媒体应用。这里作者创建了一个C++的接口...

    基于C++与SDL的迷宫游戏.zip

    基于C++与SDL的迷宫游戏 C++是一种广泛使用的编程语言,它是由Bjarne Stroustrup于1979年在新泽西州美利山贝尔实验室开始设计开发的。C++是C语言的扩展,旨在提供更强大的编程能力,包括面向对象编程和泛型...

    好玩的C++小游戏,运行即可

    在本资源中,我们得到了一个名为“好玩的C++小游戏,运行即可”的压缩包,它包含了一个基于C++编程语言开发的小游戏。C++是一种强大的、面向对象的编程语言,常用于开发系统软件、游戏引擎、桌面应用以及各种高性能...

    SDL 图形界面程序设计.pdf

    SDL本身是用C语言开发的,因此它自然可以和C/C++一起使用,并且还提供了对其他语言如C#和Python的封装。 SDL的应用领域相当广泛,涉及游戏、模拟器、媒体播放器等多媒体应用软件的开发。著名的采用SDL开发的游戏...

    SDL2# - C# Wrapper for SDL2.zip

    综上所述,SDL2# 是一个将 C++ 编写的 SDL2 库转化为 C# 可以直接使用的工具,使得 .NET 开发者能够更容易地进行跨平台的游戏和其他多媒体应用开发。这涉及到对 C# 语言、SDL2 库的特性和使用、以及封装技术的理解和...

Global site tag (gtag.js) - Google Analytics