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++游戏编程教程的主要内容,如果未能解决你的问题,请参考以下文章

C++游戏编程教程——项目实战

编程入门教程

C++编程基础: 14. 文件的读写

C++零基础教程

学习游戏开发应该从哪些方面入手

「游戏引擎 浅入浅出」4.3 片段着色器