C++游戏编程教程
Posted Visual Studio 2022
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++游戏编程教程相关的知识,希望对你有一定的参考价值。
这篇博客,我们说一下Actor、Component和DrawComponent类的代码。代码都很简单,所以写到了一篇里。
Actor
这个类是角色类,保存了角色的一些基本信息。
代码
Actor.h:
#pragma once
#include <vector>
#include "Math.h"
#include<SDL.h>
#include"Game.h"
class Actor
{
public:
enum State
{
EActive,
EPaused,
EDead
};
Actor(class Game* game);
virtual ~Actor();
void ProcessInput(const uint8_t* keyState);
virtual void ActorInput(const uint8_t* keyState);
// Update function called from Game (not overridable)
void Update(float deltaTime);
// Updates all the components attached to the actor (not overridable)
void UpdateComponents(float deltaTime);
// Any actor-specific update code (overridable)
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; }
void SetState(State state) { mState = state; }
class Game* GetGame() { return mGame; }
// Add/remove components
void AddComponent(class Component* component);
void RemoveComponent(class Component* component);
private:
// Actor's state
State mState;
// Transform
Vector2 mPosition;
float mScale;
float mRotation;
std::vector<class Component*> mComponents;
class Game* mGame;
};
Actor.cpp:
#include "Actor.h"
#include "Component.h"
#include <algorithm>
Actor::Actor(Game* game)
:mState(EActive)
, mPosition(Vector2::Zero)
, mScale(1.0f)
, mRotation(0.0f)
, mGame(game)
{
mGame->AddActor(this);
}
Actor::~Actor()
{
mGame->RemoveActor(this);
// Need to delete components
// Because ~Component calls RemoveComponent, need a different style loop
while (!mComponents.empty())
{
delete mComponents.back();
}
}
void Actor::ProcessInput(const uint8_t* keyState)
{
if (mState == EActive)
{
for (auto comp : mComponents)
{
comp->ProcessInput(keyState);
}
ActorInput(keyState);
}
}
void Actor::ActorInput(const uint8_t* keyState)
{
}
void Actor::Update(float deltaTime)
{
if (mState == EActive)
{
UpdateComponents(deltaTime);
UpdateActor(deltaTime);
}
}
void Actor::UpdateComponents(float deltaTime)
{
for (auto comp : mComponents)
{
comp->Update(deltaTime);
}
}
void Actor::UpdateActor(float deltaTime)
{
}
void Actor::AddComponent(Component* component)
{
// Find the insertion point in the sorted vector
// (The first element with a order higher than me)
int myOrder = component->GetUpdateOrder();
auto iter = mComponents.begin();
for (;
iter != mComponents.end();
++iter)
{
if (myOrder < (*iter)->GetUpdateOrder())
{
break;
}
}
// Inserts element before position of iterator
mComponents.insert(iter, component);
}
void Actor::RemoveComponent(Component* component)
{
auto iter = std::find(mComponents.begin(), mComponents.end(), component);
if (iter != mComponents.end())
{
mComponents.erase(iter);
}
}
代码分析
代码非常简单,我们现在来分析一下。
成员变量
mState:角色状态。
mPosition:角色坐标。
mScale:角色缩放比例。
mRotation:角色旋转角度。
mComponents:角色的组件。
mGame:与之关联的Game类。
构造函数
构造函数非常简单,就是初始化成员变量,然后调用Game类的AddActor函数。
析构函数
析构函数也很简单,就是调用Game类的RemoveActor函数,然后删除所有组件。
ProcessInput
这是角色的处理输入函数,但它并不是虚函数,不能被重写(虽然说语法上可以,但重写也没用,因为是通过基类指针调用的函数)。这只是个框架函数,遍历所有组件,执行ProcessInput,然后执行ActorInput函数。ActorInput才是可以被重写的虚函数。
ActorInput
用于处理角色的输入,可以被子类重写。代码实现为空。
Update
角色的更新。它调用UpdateComponents函数更新所有组件,然后调用虚函数UpdateActor。
UpdateComponents
遍历所有组件进行更新。
UpdateActor
用于更新角色,可以被子类重写。代码实现为空。
AddComponent
添加一个组件。这个函数和Game::AddDrawComponent函数实现基本相同,都是按照更新次序插入。
RemoveComponent
删除一个组件。这个函数和Game::RemoveDrawComponent函数实现基本相同,都是先find再用erase删除。
Getter/Setter函数
剩下的都是一些非常简单的获取信息和设置信息函数,这里不作讲解。
Component
这个类是组件类。
代码
Component.h:
#pragma once
#include<cstdint>
#include"Actor.h"
class Component
{
public:
// Constructor
// (the lower the update order, the earlier the component updates)
Component(class Actor* owner, int updateOrder = 100);
// Destructor
virtual ~Component();
virtual void ProcessInput(const uint8_t* keyState);
virtual void Update(float deltaTime);
int GetUpdateOrder() const { return mUpdateOrder; }
protected:
// Owning actor
class Actor* mOwner;
// Update order of component
int mUpdateOrder;
};
Component.cpp:
#include "Component.h"
Component::Component(Actor* owner, int updateOrder)
:mOwner(owner)
,mUpdateOrder(updateOrder)
{
// Add to actor's vector of components
mOwner->AddComponent(this);
}
Component::~Component()
{
mOwner->RemoveComponent(this);
}
void Component::ProcessInput(const uint8_t* keyState)
{
}
void Component::Update(float deltaTime)
{
}
代码分析
这个类太简单了,其实也没啥好说的。
DrawComponent
绘画组件类。
代码
DrawComponent.h:
#pragma once
#include"Component.h"
class DrawComponent:
public Component
{
public:
DrawComponent(class Actor* actor, int drawOrder = 100);
~DrawComponent();
virtual void Draw(SDL_Renderer* renderer);
};
DrawComponent.cpp:
#include "DrawComponent.h"
#include "Actor.h"
#include"Game.h"
DrawComponent::DrawComponent(Actor* actor, int drawOrder):Component(actor,drawOrder)
{
mOwner->GetGame()->AddDrawComponent(this);
}
DrawComponent::~DrawComponent()
{
mOwner->GetGame()->RemoveDrawComponent(this);
}
void DrawComponent::Draw(SDL_Renderer* renderer)
{
}
代码分析
这个类更简单,没啥好说的。
RTTI(运行时类型识别)
为什么要介绍RTTI呢?因为这个功能也是在游戏中经常用到的。回忆一下,在Game类中,所有的角色都以Actor类的指针存储在一个容器里,无法区分角色是哪一种类型的。比如一个简单的飞机大战的游戏,会有炸弹角色和飞机角色,如果飞机角色碰到炸弹角色就会爆炸。但如果简单地遍历所有的Actor,判断是否碰撞,会发生这样一个现象:飞机会检测到自己和自己碰撞,或者和一些无关紧要的东西(比如己方的其他东西)碰撞,都会引发爆炸!这时候,我们就需要区分一下,只有飞机和炸弹碰撞,才会引发爆炸。可是怎么判断呢?这就需要运行时类型识别了。
RTTI的代码并不深奥,相反,只需要一行代码就可以。这里就用到了C++内置的一个关键字(或者说运算符):typeid。关于typeid,我们不需要深入了解,只要知道它是获取一个对象的类型信息就可以了。重要的地方是,即使是基类的指针指向子类对象,它也能正确判断出类型!所以,刚才那个飞机大战的代码可以这么写:
void 敌方炸弹::UpdateActor(float deltaTime)
{
Vector2 pos = GetPosition();
pos.y += deltaTime * mSpeed;
if (pos.y > 768)
SetState(EDead);
SetPosition(pos);
for (auto i : GetGame()->mActors)
{
if (typeid(*i) == typeid(己方的子弹))//运行时类型检查,如果碰到子弹就消失
{
Vector2 bPos = i->GetPosition();
if (碰到子弹)
{
SetState(EDead);
i->SetState(EDead);
}
}
else if (typeid(*i) == typeid(飞机))//碰到飞机就结束游戏
{
Vector2 bPos = i->GetPosition();
if (碰到飞机)
{
SetState(EDead);
i->SetState(EDead);
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "游戏结束", "游戏结束,你输了!", GetGame()->mWindow);
GetGame()->mIsRunning = false;
}
}
}
}
但是,如果只是这么写,是无法正常运行的,因为我们没有为项目开启运行时类型识别的功能。我们要找到项目-属性-C/C++所有选项,搜索“启用运行时类型信息”,选择“是(/GR)”,才能正常运行。
总结
到现在,我们的项目模板终于完成了!我们把它导出为模板,以后就可以使用这套框架代码了!如果没有错误,运行出来的结果应该这样的:
写了这么多代码,却只运行出这个,可能心里会有亿点点失望,不过我们的项目真正的意义是,我们创建出了一个非常完善的程序框架!在下一篇,我会带大家用这个框架代码做一个简单的小游戏,那时你就会发现,有了这个框架,扩展代码是多么简单。大家编程的时候,也要养成这种高度模块化的习惯,这样后期增加功能会非常方便哦!
以上是关于C++游戏编程教程的主要内容,如果未能解决你的问题,请参考以下文章