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

SDL游戏开发教程10(场景管理器)

阅读更多

    本节将模仿ogre的ScreenManager编写一个SDL的ScreenManager。效果图如下


    这是利用场景管理器创建的一个扫雷游戏界面,为后面的扫雷游戏做准备。

 

    这里的场景管理器主要有四个类,SDLEntity(实体)、SDLSceneNode(节点)、SDLLayer(层)、SDLSceneManager(场景管理器)。

 

    他们的关系是:一个SDLSceneManager管理多个SDLLayer,一个SDLLayer有多个SDLSceneNode,一个SDLSceneNode上面关联一个SDLEntity。

 

    SDLSceneManager管理多个SDLLayer是为了将不同的层分离开来,并且通过索引号来决定哪个层显示在下面,哪个层显示在上面。这样做有利用分开管理,比如将游戏界面中不变的背景和游戏中经常变化的前景分开,设置背景的索引号小于前景索引号,这样就可以保证前景在上面显示,背景在后面显示,同时也可以通过整体平移背景层,来达到整个游戏场景的向前移动的效果。

#ifndef SDLSCENEMANAGER_H_
#define SDLSCENEMANAGER_H_

#include <map>
#include "SDL/SDLSurface.h"
class SDLLayer;
class SDLSceneManager
{
	friend class SDL;
private:
	SDLSceneManager();
public:
	virtual ~SDLSceneManager();
public:
	//获取默认层,默认层为最底层
	SDLLayer* getDefaultLayer();

	//获取索引号对应的层,索引号必须大于0,索引号大的显示在前面
	SDLLayer* getLayer(int index);

	//移除索引号对应的层
	void removeLayer(int index);

	//绘制整个场景
	void	draw(SDLSurfacePtr screen);
private:
	std::map<int, SDLLayer*> layers;
};

#endif /* SDLSCENEMANAGER_H_ */
 

    SDLLayer负责管理层中的SDLSceneNode,每个层都有一个根节点,根节点位于整个层的左上角,通过根节点可以创建子节点,然后子节点可以创建自己的子节点,以此类推,就可以得到一棵节点树。整棵节点树就构成了当前层要显示内容的布局。

#ifndef SDLLAYER_H_
#define SDLLAYER_H_
#include "SDLSceneNode.h"
class SDLSceneManager;
class SDLLayer
{
	friend class SDLSceneManager;
private:
	SDLLayer(SDLSceneManager * sceneManager, int id);
public:
	virtual ~SDLLayer();
public:
	//获得当前层的根节点
	SDLSceneNode* 	getRootSceneNode();

	//获取当前层的索引号
	int				getID();

private:
	//绘制整个层
	void			draw(SDLSurfacePtr screen);
private:
	SDLSceneNode * 		rootNode;
	int					id;
	SDLSceneManager *	sceneManager;
};

#endif /* SDLLAYER_H_ */
 

    SDLSceneNode代表SDLLayer中的一个具体位置,他记录有相对与父节点的相对坐标,通过父节点的绝对坐标,可以获取当前节点的绝对坐标,根节点的绝对坐标为(0,0)。一个节点可以有多个子节点,不同的子节点通过名称来区分;一个节点可以关联一个实体(以后可能会扩充到关联多个),当关联了实体后,场景管理器就会在节点的当前位置绘制实体。

#ifndef SDLSCENENODE_H_
#define SDLSCENENODE_H_
#include "SDL/SDLCore.h"
#include "SDLEntity.h"
class SDLLayer;
class SDLSceneNode
{
	friend class SDLLayer;
private:
	SDLSceneNode();
public:
	SDLSceneNode(SDLSceneNode *parent, std::string name, int x = 0, int y = 0);
public:
	virtual ~SDLSceneNode();
public:
	//获取节点名称
	std::string getName();

	//回去节点相对位置
	SDL_Point getPosition();

	//获取节点绝对位置
	SDL_Point getAbsolutePosition();

	//关联实体
	void attachEntity(SDLEntity *entity);

	//取消与实体的关联
	void detachEntity();

	//创建子节点
	SDLSceneNode* createChildSceneNode(std::string name, int x = 0, int y = 0);

	//创建一个与自己关联的实体
	SDLEntity* createAttachedEntity(const std::string name, SDLSurfacePtr surface);

	//获取已经关联的实体
	SDLEntity* getEntity();
private:

	//绘制本节点和所有的子节点
	void	draw(SDLSurfacePtr screen);
private:
	SDLEntity *entity;
	std::map<std::string, SDLSceneNode*> children;
	std::string name;
	SDLLayer *layer;
	SDLSceneNode *parent;
	SDL_Point	position;
};

#endif /* SDLSCENENODE_H_ */

 

    SDLEntity负责管理要显示在屏幕上的图像,一个SDLEntity只能包含一个surface,只能关联到一个节点。这里实体名称的作用主要是为了标示一个实体,便于跟踪调试,以后可能会有其他用途(在ogre里面能在场景管理器中通过实体的名称来获取实体,我们这里不行,因为暂时没有看出这样做有什么大的用途)。

#ifndef SDLENTITY_H_
#define SDLENTITY_H_
#include "SDL/SDLSurface.h"
#include <string>
class	SDLSceneNode;
class SDLEntity
{
private:
	SDLEntity();
public:
	SDLEntity(std::string name, SDLSurfacePtr surface, SDLSceneNode *node = NULL);
	virtual ~SDLEntity();
public:
	//获取关联的节点
	SDLSceneNode * getSceneNode();

	//获取名称
	std::string getName();

	//获取包含的Surface
	SDLSurfacePtr getSurface();

	//设置包含的Surface
	void setSurface(SDLSurfacePtr surface);

	//取消关联节点
	void detachSceneNode();

	//关联节点
	void attachSceneNode(SDLSceneNode *	node);
private:
	std::string name;
	SDLSurfacePtr surface;
	SDLSceneNode *	node;
};

#endif /* SDLENTITY_H_ */

 

    这里的逻辑比较简单,以后随着应用的深入,我们再来强化他的功能。

    有了场景管理器后,我们不需要在onRender函数中写任何代码了。

    在SDLFrame::open的消息循环中,去掉调用onRender的代码,加入场景绘制的代码,同时在场景绘制前和绘制后各加一个函数,便于在显示之前和之后做一些事情。

		beforeRender();

		SDL::sceneManager()->draw(screen);
		
		afterRender();
 

 

    下面是应用的代码

void Lesson04::onInit()
{		

	SDLLayer * background = SDL::sceneManager()->getDefaultLayer();
	SDLSceneNode *root = SDL::sceneManager()->getLayer(1)->getRootSceneNode();

	//加载图片
	SDLSurfacePtr	mine_unknown = SDL::imageManager()->loadImage(MINE_UNKNOWN);
	SDLSurfacePtr	frame_outer_h = SDL::imageManager()->loadImage(FRAME_OUTER);
	SDLSurfacePtr	frame_outer_v = SDL::transform()->Rotate90Degrees(frame_outer_h, 1);
	SDLSurfacePtr	frame_inner_h = SDL::imageManager()->loadImage(FRAME_INNER);
	SDLSurfacePtr	frame_inner_v = SDL::transform()->Rotate90Degrees(frame_inner_h, 1);
	SDLSurfacePtr	frame_background_src = SDL::imageManager()->loadImage(FRAME_BACKGROUND);

	SDL_Rect		mine_unknown_rect = mine_unknown->value()->clip_rect;
	SDL_Rect		frame_outer_rect = frame_outer_h->value()->clip_rect;
	SDL_Rect		frame_inner_rect = frame_inner_h->value()->clip_rect;
	SDL_Rect		frame_background_rect = frame_background_src->value()->clip_rect;


	//计算扫雷区域的大小
	SDL_Rect mine_frame;
	mine_frame.w = MINE_COLS * mine_unknown_rect.w
						+ (MINE_COLS-1) * frame_inner_rect.h
						+ frame_outer_rect.h * 2;
	mine_frame.h = MINE_ROWS * mine_unknown_rect.h
						+ (MINE_ROWS-1) * frame_inner_rect.h
						+ frame_outer_rect.h * 2;
	SDLSurfacePtr frame_background = SDL::transform()->zoom(frame_background_src, mine_frame.w, mine_frame.h);
	frame_background_rect = frame_background->value()->clip_rect;

	//----------------------------------------------------------------------------------------
	//创建背景图
	SDLSurfacePtr imageSrc = SDL::imageManager()->loadImage(BACKGROUND_ROOT);
	SDLSurfacePtr image = SDL::transform()->zoom(imageSrc, screen->value()->clip_rect.w, screen->value()->clip_rect.h) ;
	background->getRootSceneNode()->createChildSceneNode("image", 0, 0)->createAttachedEntity("image",image);
	//----------------------------------------------------------------------------------------
	//创建雷区背景
	int	x = (screen->value()->clip_rect.w - frame_background_rect.w)/2;
	int	y = (screen->value()->clip_rect.h - frame_background_rect.h) - 20;
	SDLSceneNode *frame_background_node = root->createChildSceneNode(FRAME_BACKGROUND, x, y);
	frame_background_node->createAttachedEntity(FRAME_BACKGROUND, frame_background);

	//----------------------------------------------------------------------------------------
	//创建边框
	SDLSurfacePtr frame_outer_h_all = SDL::transform()->flat(frame_outer_h
			, frame_background_rect.w
			, frame_outer_rect.h);
	SDLSurfacePtr frame_outer_v_all = SDL::transform()->flat(frame_outer_v
				, frame_outer_rect.h
				, frame_background_rect.h);
	//上
	std::string FREAM_OUTER_TOP = FRAME_OUTER + "TOP";
	SDLSceneNode * frame_outer_top_node = frame_background_node->createChildSceneNode(FREAM_OUTER_TOP
				, 0
				, 0);
	frame_outer_top_node->createAttachedEntity(FREAM_OUTER_TOP, frame_outer_h_all);
	//下
	std::string FREAM_OUTER_BOTTOM = FRAME_OUTER + "BOTTOM";
	SDLSceneNode *	frame_outer_bottom_node = frame_background_node->createChildSceneNode(FREAM_OUTER_BOTTOM
				, 0
				, frame_background_rect.h - frame_outer_rect.h);
	frame_outer_bottom_node->createAttachedEntity(FREAM_OUTER_BOTTOM, frame_outer_h_all);
	//左
	std::string FREAM_OUTER_LEFT = FRAME_OUTER + "LEFT";
	SDLSceneNode *	frame_outer_left_node = frame_background_node->createChildSceneNode(FREAM_OUTER_LEFT
				, 0
				, 0);
	frame_outer_left_node->createAttachedEntity(FREAM_OUTER_LEFT, frame_outer_v_all);
	//右
	std::string FREAM_OUTER_RIGHT = FRAME_OUTER + "RIGHT";
	SDLSceneNode *frame_outer_right_node = frame_background_node->createChildSceneNode(FREAM_OUTER_RIGHT
			, frame_background_rect.w - frame_outer_rect.h
			, 0);
	frame_outer_right_node->createAttachedEntity(FREAM_OUTER_RIGHT, frame_outer_v_all);

	//-----------------------------------------------------------------------------------------
	//创建初始雷格
	for(int row = 0; row < MINE_ROWS; row++)
	{
		for(int col = 0; col < MINE_COLS; col++)
		{
			std::string name = MINE_UNKNOWN + boost::lexical_cast<std::string>(row*MINE_COLS + col);
			SDLSceneNode *node = frame_background_node->createChildSceneNode(name
					, frame_outer_rect.h + col * (mine_unknown_rect.w + frame_inner_rect.h)
					, frame_outer_rect.h + row * (mine_unknown_rect.h + frame_inner_rect.h));
			node->createAttachedEntity(name, mine_unknown);
			this->mines[row][col] = node;
		}
	}

	//-----------------------------------------------------------------------------------------
	//创建内部网格
	SDLSurfacePtr frame_iner_h_all = SDL::transform()->flat(frame_inner_h
				, frame_background_rect.w - frame_outer_rect.h*2
				, frame_inner_rect.h);
		SDLSurfacePtr frame_iner_v_all = SDL::transform()->flat(frame_inner_v
					, frame_inner_rect.h
					, frame_background_rect.h - frame_outer_rect.h*2);
	for(int col = 1; col < MINE_COLS; col++)
	{
		std::string name = FRAME_INNER + "v" + boost::lexical_cast<std::string>(col);
		SDLSceneNode *node = frame_background_node->createChildSceneNode(name
				, frame_outer_rect.h + col * mine_unknown_rect.w + (col-1)*frame_inner_rect.h
				, frame_outer_rect.h);
		node->createAttachedEntity(name, frame_iner_v_all);
	}
	for(int row = 1; row < MINE_ROWS; row++)
	{
		std::string name = FRAME_INNER + "r" + boost::lexical_cast<std::string>(row);
		SDLSceneNode *node = frame_background_node->createChildSceneNode(name
				, frame_outer_rect.h
				, frame_outer_rect.h + row * mine_unknown_rect.h + (row-1)*frame_inner_rect.h);
		node->createAttachedEntity(name, frame_iner_h_all);
	}
	//-----------------------------------------------------------------------------------------
	//创建上面的显示剩余雷数和所花时间数

	//剩余雷数
	int	xStart = 0, yStart = 0;
	SDLSurfacePtr mineAmountTitleSf = SDL::imageManager()->loadImageWithoutColor(MINE_AMOUNT, 255, 255, 255);
	xStart = frame_background_node->getPosition().x;
	yStart = (frame_background_node->getPosition().y - mineAmountTitleSf->value()->clip_rect.h)/2;
	SDLSceneNode *mineAmountTitelNode = root->createChildSceneNode(MINE_AMOUNT, xStart, yStart);
	mineAmountTitelNode->createAttachedEntity(MINE_AMOUNT, mineAmountTitleSf);

	SDLFontPtr font = SDL::fontManager()->OpenFont(FONT, 24);
	SDLSurfacePtr mineAmountSf = font->RenderUNICODEBlended("99", SDL::assistant()->makeColor(255, 0, 0));
	xStart = mineAmountTitleSf->value()->clip_rect.w + frame_background_node->getPosition().x + 5;
	yStart = (frame_background_node->getPosition().y - mineAmountSf->value()->clip_rect.h)/2;
	this->mineAmountNode = root->createChildSceneNode(MINE_AMOUNT+boost::lexical_cast<std::string>(1), xStart, yStart);
	mineAmountNode->createAttachedEntity(MINE_AMOUNT+boost::lexical_cast<std::string>(1), mineAmountSf);

	//重启图标
	SDLSurfacePtr restartSf = SDL::imageManager()->loadImageWithoutColor(RESTART, 255, 255, 255);
	xStart = (screen->value()->clip_rect.w - restartSf->value()->clip_rect.w)/2;
	yStart = (frame_background_node->getPosition().y - restartSf->value()->clip_rect.h)/2;
	SDLSceneNode *restartNode = root->createChildSceneNode(RESTART, xStart, yStart);
	restartNode->createAttachedEntity(RESTART, restartSf);

	//计时器
	SDLSurfacePtr timeCounterSf = font->RenderUNICODEBlended("000", SDL::assistant()->makeColor(255, 0, 0));
	xStart = frame_background_node->getPosition().x + frame_background_rect.w - timeCounterSf->value()->clip_rect.w;
	yStart = (frame_background_node->getPosition().y - timeCounterSf->value()->clip_rect.h)/2;
	SDLSceneNode *timeCounterNode = root->createChildSceneNode(CLOCK+boost::lexical_cast<std::string>(1), xStart, yStart);
	timeCounterNode->createAttachedEntity(CLOCK+boost::lexical_cast<std::string>(1), timeCounterSf);

	SDLSurfacePtr clockSf = SDL::imageManager()->loadImageWithoutColor(CLOCK, 255, 255, 255);
	xStart = frame_background_node->getPosition().x + frame_background_rect.w
				- timeCounterSf->value()->clip_rect.w - clockSf->value()->clip_rect.w - 5;
	yStart = (frame_background_node->getPosition().y - clockSf->value()->clip_rect.h)/2;
	this->clockNode = root->createChildSceneNode(CLOCK, xStart, yStart);
	clockNode->createAttachedEntity(CLOCK, clockSf);

	//插旗
	SDLSurfacePtr mine_flag = SDL::transform()->zoom(SDL::imageManager()->loadImage(MINE_FLAG), mine_unknown_rect.w, mine_unknown_rect.w);
	this->mines[4][5]->getEntity()->setSurface(mine_flag);
	this->mines[8][10]->getEntity()->setSurface(mine_flag);
	this->mines[10][3]->getEntity()->setSurface(mine_flag);
	//插问号
	SDLSurfacePtr mine_doubt_src = SDL::imageManager()->loadImageWithoutColor(MINE_DOUBT, 255, 255, 255);
	SDLSurfacePtr mine_doubt = SDL::video()->copySurface(mine_unknown);
	SDL::video()->BlitSurface(mine_doubt_src, NULL, mine_doubt, NULL);
	this->mines[8][5]->getEntity()->setSurface(mine_doubt);
	this->mines[4][10]->getEntity()->setSurface(mine_doubt);
	this->mines[5][3]->getEntity()->setSurface(mine_doubt);
	//插0,1,2,3,4,5,6,7,8
	SDLEntity *entity = this->mines[8][3]->getEntity();
	entity->detachSceneNode();
	delete entity;
	SDLSurfacePtr mine_number_1 = SDL::imageManager()->loadImageWithoutColor(MINE_NUMBER_1, 255, 255, 255);
	this->mines[8][6]->getEntity()->setSurface(mine_number_1);
	SDLSurfacePtr mine_number_2 = SDL::imageManager()->loadImageWithoutColor(MINE_NUMBER_2, 255, 255, 255);
	this->mines[9][10]->getEntity()->setSurface(mine_number_2);
	SDLSurfacePtr mine_number_3 = SDL::imageManager()->loadImageWithoutColor(MINE_NUMBER_3, 255, 255, 255);
	this->mines[2][3]->getEntity()->setSurface(mine_number_3);
	SDLSurfacePtr mine_number_4 = SDL::imageManager()->loadImageWithoutColor(MINE_NUMBER_4, 255, 255, 255);
	this->mines[5][6]->getEntity()->setSurface(mine_number_4);
	SDLSurfacePtr mine_number_5 = SDL::imageManager()->loadImageWithoutColor(MINE_NUMBER_5, 255, 255, 255);
	this->mines[12][10]->getEntity()->setSurface(mine_number_5);
	SDLSurfacePtr mine_number_6 = SDL::imageManager()->loadImageWithoutColor(MINE_NUMBER_6, 255, 255, 255);
	this->mines[15][3]->getEntity()->setSurface(mine_number_6);
	SDLSurfacePtr mine_number_7 = SDL::imageManager()->loadImageWithoutColor(MINE_NUMBER_7, 255, 255, 255);
	this->mines[13][12]->getEntity()->setSurface(mine_number_7);
	SDLSurfacePtr mine_number_8 = SDL::imageManager()->loadImageWithoutColor(MINE_NUMBER_8, 255, 255, 255);
	this->mines[11][15]->getEntity()->setSurface(mine_number_8);
}
 

 

    附件中是工程的完整代码和图片,大家可以通过跟踪代码来了解这节的内容。

  • 大小: 41 KB
分享到:
评论

相关推荐

    sdl游戏开发中文文档

    ### SDL游戏开发中文文档知识点概览 #### 一、SDL简介 - **名称与定义**:SDL即Simple DirectMedia Layer(简易直控媒体层),是一个跨平台的多媒体库,主要用于直接控制多媒体硬件设备,如音频、键盘、鼠标、游戏...

    SDL系列教程

    **SDL**的应用场景非常广泛,除了游戏开发外,还包括多媒体播放器、模拟器等领域。其中值得一提的是《文明:权利的召唤》这款游戏,它就是使用**SDL**开发的一个典型案例,并因此赢得了Linux游戏开发大奖。 #### 三...

    SDL 教程.zip

    6. **时间管理**:掌握SDL的定时器功能,用于实现游戏循环、帧率控制以及其他需要计时的应用场景。 7. **视频和动画**:了解如何使用SDL处理视频流,并创建简单的动画效果。 8. **文件I/O**:学习如何使用SDL读取...

    SDL2.0跨平台多媒体开发库

    - **SDL_Input**:包括键盘、鼠标、触摸和游戏控制器等输入设备的管理。 ### 3. 使用SDL2.0开发流程 1. **初始化**:首先,需要调用`SDL_Init`函数来初始化SDL库,指定需要使用的子系统(如视频、音频、定时器等)...

    SDL Game Development

    ### SDL2.0游戏开发教程知识点解析 #### 一、SDL2.0简介与特性 Simple DirectMedia Layer(简称SDL)是一种跨平台的多媒体库,主要用于视频应用软件,特别是视频游戏开发领域。SDL2.0作为SDL的一个重大版本更新,...

    C语言课程设计与游戏开发实践教程-游戏代码.rar

    在本教程中,我们将深入探讨如何使用C语言进行游戏开发,这是计算机科学中一个充满挑战且有趣的话题。C语言以其高效、灵活性和底层控制而闻名,是许多游戏引擎的基础,尤其是那些对性能要求极高的游戏。这个压缩包...

    SDL.rar_sdl_sdl api

    **压缩文件内容推测:** 压缩包内的"SDL.pdf"很可能是一个关于SDL API的官方文档或者教程,详细介绍了如何使用这个库来开发多媒体应用,包括图形、音频、输入设备处理等功能。 **SDL知识详解:** SDL(Simple ...

    ffmpeg+sdl教程

    而Simple DirectMedia Layer (SDL) 是一个用于编写跨平台图形应用程序的库,特别适合游戏开发和多媒体应用。 本教程将重点讲解如何结合FFmpeg和SDL来创建一个简单的视频播放器。这个过程涉及到多个知识点,包括音...

    SDL_2.rar_SDL库_sdl

    4. **事件处理**:通过SDL,开发者可以轻松处理键盘、鼠标、游戏控制器等各种输入设备的事件,实现用户交互。 5. **文件I/O**:虽然SDL的主要关注点在于多媒体和图形,但它也提供了一些基本的文件读写功能,方便在...

    sdl 1.3.0.0 SDK+ffmpeg

    SDL是一个开源的跨平台开发库,主要用来处理图形、音频和输入设备,广泛应用于游戏开发和多媒体应用。FFmpeg则是一个强大的开源多媒体处理框架,支持音视频编码、解码、转换和流化。 **描述:“用于多媒体视频显示...

    window win32下配置好的SDL

    标签"SDL配置"、"window32"和"vs2013"进一步明确了关键信息:这是一个关于在32位Windows系统中使用Visual Studio 2013进行SDL开发的配置教程或资源。对于开发者来说,了解如何配置这些环境是至关重要的,因为不正确...

    SDL_ttf-2.0.10

    `SDL_ttf`是游戏开发和图形应用程序中常用的一个库,它扩展了Simple DirectMedia Layer (SDL) 库的功能,提供了对TrueType字体的支持。`SDL_ttf-2.0.10`是这个库的一个版本,它包含了开发人员在创建需要文本渲染的...

    SDL 显示图片和 openGL 3D 地球

    SDL是一个跨平台的多媒体库,它提供了丰富的功能,包括窗口管理、事件处理、图像渲染等,适用于游戏开发和图形界面应用。在我们的场景中,SDL用于加载和显示静态图片,为用户界面增添视觉元素。 首先,要使用SDL...

    linux-winows-跨平台开源图形库 SDL-1.2

    描述中提到,SDL 对于游戏开发者尤其有用,因为库提供了丰富的功能,如硬件加速的2D图形渲染、音频管理、键盘和鼠标输入处理,甚至支持游戏控制器等外设。同时,对于普通用户而言,这意味着他们可以在不同的操作系统...

    SDL2-2.28.1.zip

    8. **应用场景**:SDL2广泛应用于游戏开发、图形用户界面(GUI)开发、实时数据可视化等领域。由于其简洁的API和丰富的功能,许多开源游戏引擎如RPG Maker MV、LÖVE等都基于SDL或其扩展库。 9. **性能优化**:SDL2...

    SDL_ttf-devel-2.0.11-VC.zip

    SDL_ttf适用于任何需要在SDL应用中显示清晰、美观文本的场合,比如游戏的得分显示、菜单系统、教程文本,或者任何其他需要动态文字的图形用户界面。 通过这个压缩包,开发者可以轻松地在他们的Visual Studio项目中...

    C++学习游戏入门教程

    《C++学习游戏入门教程》是一份专为初学者设计的教育资源,旨在引导读者通过C++语言进入游戏开发的世界。本教程不仅涵盖了C++的基础知识,还深入探讨了2D和3D游戏开发的关键概念和技术。 C++是游戏开发领域广泛应用...

    网络游戏编程教程

    可能会介绍C++、Python或Unity等常用的游戏开发语言和引擎,讲解它们的特点和适用场景,帮助读者选择合适的入门路径。 第三章:基础理论 本章将深入讲解游戏开发的基础概念,如数据结构、算法、图形学基础和物理...

Global site tag (gtag.js) - Google Analytics