Xcode与C++之游戏开发:精灵(Sprite)

您所在的位置:网站首页 小精灵图片简单 Xcode与C++之游戏开发:精灵(Sprite)

Xcode与C++之游戏开发:精灵(Sprite)

2024-07-10 02:33| 来源: 网络整理| 查看: 265

上一篇:Xcode与C++之游戏开发:游戏对象 预备动作:请按照第一篇,搭好一个黑框窗口,然后按照第二篇完成基本渲染器,按第三篇完成增量时间。本篇有大量代码,请有一定的耐心。

精灵

精灵是2D游戏中的视觉对象,通常用来表示角色、背景或者其它动态对象。对于移动游戏而言,它占了大部分的游戏大小。大多数2D游戏具有数十个甚至数百个精灵。

每个精灵都有一个或多个与之关联的图像文件。有许多不同的图像文件格式,游戏使用基于平台和其它约束使用不同格式。例如,PNG 是压缩图像格式,使用这个格式可以减少一部分存储空间。但是,硬件不能原生地支持绘制 PNG 文件,所以加载时间会长一点。一些平台会使用一些对显卡(准确地说,图形硬件)特别优化过格式,比如PVR(ios)、DXT(PC和Xbox)。

因为它在游戏中大量的存在,因此高效地运用它们就显得非常重要了。

加载图像文件

仅仅使用 SDL 库的2D游戏,最简单的加载图像文件的方式是使用 SDL Image 库,因为 SDL 本身只支持 .bmp 格式。第一步就是用 IMG_Init 初始化 SDL 图像,并且用一个标志位参数来表明所需的文件格式。在 Game::Inititalize() 中加入:

if (IMG_Init(IMG_INIT_PNG) == 0) { SDL_Log("不能初始化SDL_image: %s", SDL_GetError()); return false; }

除了 PNG,还有其它的格式:

标志 格式 IMG_INIT_JPG JPEG IMG_INIT_PNG PNG IMG_INIT_TIF TIFF

完成 SDL Image 的初始化后,就可以使用 IMG_Load 加载图像文件到 SDL_Surface。在这之后,使用 SDL_CreateTextureFromSurface 将 SDL_Surface 转换到 SDL_Texture(这是 SDL 要求的)。将上述过程封装一下:

SDL_Texture* Game::LoadTexture(const char* fileName) { // 从文件中加载 SDL_Surface* surf = IMG_Load(fileName); if (!surf) { SDL_Log("加载图像文件 %s 失败", fileName); return nullptr; } // 从 surface 创建 texture SDL_Texture* tex = SDL_CreateTextureFromSurface(mRenderer, surf); SDL_FreeSurface(surf); if (!tex) { SDL_Log("%s surface 转换到 texture 失败!", fileName); return nullptr; } return tex; }

现在尝试加载一张图片试试,在 Game::Inititalize() 中加入下面的代码(注意,将Game::GenerateOutput() 函数清空,否则你会有惊喜)。

SDL_Texture* tex = LoadTexture("/XXX/XXX/.png"); SDL_RenderCopy(mRenderer, tex, 0, 0); SDL_RenderPresent(mRenderer);

SDL Image

嗯,你应该猜得出来,其实我按图片大小修改了初始化时候的窗体大小。(上述代码只是演示一下,不出现在后文的代码中,换句话说,本文最终产品可能不包含上述代码,请酌情处理。)

texture的存储与加载

接下来,又有一个很有意思的问题,怎么存储这些 texture(可以翻译成纹理,但感觉中文有点小歧义,纹理总是让我想到那些木纹、石头纹路之类的。图片属于纹理,有点小奇怪)。使用相同的图片在游戏中非常普遍,假如有20个行星,每个行星都用相同的行星图片,重复加载20次,显然是不能接受的(I/O开销很昂贵的)。

一个最简单的方式就是在游戏中创建一个文件名到 SDL_Texture 指针的映射。C++11 引入的 unordered_map 是一个很不错的选择。我们可以创建一个 GetTexture 函数,它通过 texture 名字返回对应的 SDL_Texture 指针。这个函数首先应该检查 texture 是否已经存在,如果不存在,那就从文件中加载 texture。

先在 Game.hpp 中声明函数还有映射 mTextures:

// .... #include #include class Game { public: //... SDL_Texture* GetTexture(const std::string& fileName); private: // ... // 已加载的 textures std::unordered_map mTextures; };

再到 Game.cpp 实现:

SDL_Texture* Game::GetTexture(const std::string& fileName) { SDL_Texture* tex = nullptr; // texture是否已经存在? auto iter = mTextures.find(fileName); if (iter != mTextures.end()) { tex = iter->second; } else { // 从文件中加载 SDL_Surface* surf = IMG_Load(fileName.c_str()); if (!surf) { SDL_Log("加载texture文件%s失败", fileName.c_str()); return nullptr; } // 从 surface 中创建 textures tex = SDL_CreateTextureFromSurface(mRenderer, surf); SDL_FreeSurface(surf); if (!tex) { SDL_Log("无法把%s从surface转化到texture", fileName.c_str()); return nullptr; } mTextures.emplace(fileName.c_str(), tex); } return tex; }

简单情况下将文件名映射到 SDL_Texture 指针是有意义的,但考虑到实际的游戏可能具有许多不同类型的 textures,比如声音效果,3D模型,字体等。因此,编写一个更健壮的系统来一般处理所有类型的 textures 是有必要的,但为了简单起见,就不打算开发什么资产管理系统了。

为了更清楚的划分责任,不妨创建一个 Load_data() 函数来加载游戏世界中的演员。好吧,暂时留白,待会等演员上场,再回过头来补充这个函数。老规矩,先头文件(private,放在私有里),然后.cpp文件。之后在 Initialize() 的最后调用这个函数。

// ... LoadData(); mTicksCount = SDL_GetTicks(); return true; } void Game::LoadData() { } 画家算法

假设游戏只有背景图片和游戏角色,那么绘制的顺序是先背景再角色。这很类似画家的绘图时候的方式,因此这也被称作画家算法(painter’s algorithm)。也就说,先画最底层,再一层层往上渲染。

混合游戏对象模型的实现

上一篇,已经介绍过游戏对象了,接下来会采用Actor/Component混合式的游戏对象模型。但由于上一篇没有上下文,没有完整的具体代码,但对游戏对象模型的实现已经给出了介绍,下面直接贴出代码(不清楚的,请参考上一篇)。

Component.hpp:

#ifndef Component_hpp #define Component_hpp class Component { public: // 构造函数 // (值越低的更新顺序,则组件越早更新) Component(class Actor* owner, int updateOrder = 100); // 析构函数 virtual ~Component(); // 通过增量时间更新组件 virtual void Update(float deltaTime); int GetUpdateOrder() const { return mUpdateOrder; } protected: // 所属的角色 class Actor* mOwner; // 组件的更新顺序 int mUpdateOrder; }; #endif /* Component_hpp */

Component.cpp:

#include "Component.hpp" #include "Actor.hpp" Component::Component(Actor* owner, int updateOrder) :mOwner(owner) ,mUpdateOrder(updateOrder) { // 添加到actor的组件向量 mOwner->AddComponent(this); } Component::~Component() { mOwner->RemoveComponent(this); } void Component::Update(float deltaTime) { }

Actor.hpp:

#ifndef Actor_hpp #define Actor_hpp #include #include "Math.hpp" class Actor { public: enum State { EActive, EPaused, EDead }; Actor(class Game* game); virtual ~Actor(); // 从 Game 调用 Update 函数 (不被继承重写) void Update(float deltaTime); // 更新属于该actor的所有组件 (不被继承重写) void UpdateComponents(float deltaTime); // actor特有更新代码 (可重写) virtual void UpdateActor(float deltaTime); // Getters/setters const Vector2& GetPosition() const { return mPosition; } void SetPosition(const Vector2& pos) { mPosition = pos; } float GetScale() const { return mScale; } void SetScale(float scale) { mScale = scale; } float GetRotation() const { return mRotation; } void SetRotation(float rotation) { mRotation = rotation; } State GetState() const { return mState


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3