|
上篇:
一. 前言
本篇文章主要讲述引擎内部的设计思路,已实现和未实现的内容都会介绍,我会尽量讲述我为什么这么设计,并且标注一下暂时在代码仓库中是否已经实现。
在设计本引擎时,我已经预设自己在开发类似CS1.6这类游戏,在此基础上尽可能的考虑在网游(类似cso, cf的网游)上的可扩展性。
二. 局内和局外
以CS1.6为例,一打开游戏处于“局外状态”, 玩家可在这个状态下搜索服务器,创建本地服务器,进行一些设置等。
CS1.6 创建本地服务器
创建本地服务器或者加入到其他服务器后则进入“局内状态”,此时游戏地图将被加载进来,玩家可以对游戏中的角色进行交互。
CS1.6 控制恐怖分子
局外状态主要显示一些界面控件,我们以后可能会接入一些基于GL的界面库,目前已知的有: ImGui, NoesisGui等库,然后将GUI库暴露给Game.dll,由游戏的开发者来编写游戏局外界面, 同时我们也可以提供一套默认的界面, 或者也可以让游戏开发者在Game.dll自己接入或开发一套自己的GUI库,我们需要将渲染循环、GL函数接口、窗口事件(窗口尺寸变化,按键输入,鼠标事件等)等接口暴漏给Game.dll。
局外思路暂时是这样的,还没有开始进行详细设计,本篇文章主要中心在于局内的基础设施设计。
三. 局内的设计
局内主要涉及到三个类: World世界,Actor演员,Component组件。
首先我得承认这里多多少少有些借鉴UE4了,但是可能借鉴的不是很多,因为UE4我也没有看过源码,所以如果有些设计比较雷同,可能真的比较巧合。
World类主要是管理局内的Actor,Actor被创建出来都会被添加到World中,除此之外,像Camera这些有特殊功能的组件也会被World类管理,这个类全局唯一实例,应该在创建服务器或者连入服务器时创建。
Actor类是游戏中基本的单位,Actor拥有WorldLocation, WorldScale以及WorldRotation三个位置相关属性,同时拥有一个RootComponent组件,其他Component都会挂载到RootComponent组件上。
Component类为组件类,Actor不存在子Actor,但是Component是存在子组件的,所有可渲染的内容也将由Component来实现,例如类似UE4的SkeletalMeshComponent,Component除了有WorldLocation, WorldScale以及WorldRotation三个属性外,还会有RelativeLocation, RelativeScale以及RelativeRotation三个相对位置属性。为了设计上的简单,引擎中仅允许在Actor的构造函数中将Component加入到Actor中,且不允许动态的拆离(不想显示设置为不可见即可)。
代码如下:
public class World
{
// Actors列表
private List<Actor> Actors;
// 待添加的Actor
private List<Actor> AddActors;
// 待删除的DelActors
private List<Actor> DelActors;
/**
* 因为可能会在Update时添加Actor,而Update时,正好在通过Foreach遍历Actors此时不能对List插入或者删除实例,所以先添加到临时List里,等待下一次循环,进入foreach之前对Actors进行修改。
* 这也是禁止Component动态增减的原因,没必要为了动态增减添加思维负担。
**/
public void AddActor(Actor actor)
{
AddActors.Add(actor);
}
public World ()
{
Actors = new List<Actor> ();
AddActors = new List<Actor> ();
DelActors = new List<Actor> ();
}
public void Init()
{
var camera = new CameraActor();
}
public void Fini()
{;
}
public void DestoryActor(Actor actor)
{
DelActors.Add(actor);
}
public void Render()
{
}
public void Update(float deltaTime)
{
AddActors.ForEach(actor => Actors.Add(actor));
DelActors.ForEach(actor => Actors.Remove(actor));
AddActors.Clear();
DelActors.Clear();
Actors.ForEach(actor => actor.Update(deltaTime));
}
}
public class Actor
{
public Actor()
{
World.AddActor(this);
Name = &#34;Actor&#34;;
RootComponent = new RootComponent(this);
}
public string Name { get; set; }
public void Destory()
{
RootComponent.Destory();
World.DestoryActor(this);
}
public RootComponent RootComponent { get; private set; }
/// <summary>
/// 相对世界的位置
/// </summary>
public Vector3 WorldLocation { get; set; }
/// <summary>
/// 相对世界的方向
/// </summary>
public Quaternion WorldRotation { get; set; }
/// <summary>
/// 相对世界的缩放
/// </summary>
public Vector3 WorldScale { get; set; }
/// <summary>
/// 矩阵
/// </summary>
public Matrix4x4 WorldTransform { get; set; }
public Engine EngineInstance { get => Engine.Instance; }
public World World {
get {
if (EngineInstance.World == null)
throw new Exception(&#34;世界尚未创建&#34;);
return EngineInstance.World;
}
}
protected GL gl { get => Engine.Instance.Gl; }
public virtual void Update(float deltaTime)
{
var scaleMat4 = Matrix4x4.CreateScale(WorldScale);
var rotationMat4 = Matrix4x4.CreateFromQuaternion(WorldRotation);
var translateMat4 = Matrix4x4.CreateTranslation(WorldLocation);
WorldTransform = scaleMat4 * rotationMat4 * translateMat4;
RootComponent.Update(deltaTime);
}
}
public class Component
{
protected List<Component> SubComponents = new List<Component>();
public Component(string name)
{
Name = name;
}
public string Name { get; set; }
private Component? _Parent = null;
public void RecursionSubComponent(Action<Component> action)
{
action(this);
foreach(var subComponent in SubComponents)
{
subComponent.RecursionSubComponent(action);
}
}
public Component? Parent { get => _Parent; }
public void Attach(Component parent)
{
if (this.Parent != null)
throw new Exception(&#34;该组件已经有父级了!&#34;);
parent.SubComponents.Add(this);
this._Parent = parent;
}
public virtual void Update(float deltaTime)
{
var scaleMat4 = Matrix4x4.CreateScale(RelativeScale);
var rotationMat4 = Matrix4x4.CreateFromQuaternion(RelativeRotation);
var translateMat4 = Matrix4x4.CreateTranslation(RelativeLocation);
RelativeTransform = scaleMat4 * rotationMat4 * translateMat4;
if (Parent != null)
{
WorldTransform = RelativeTransform * Parent.WorldTransform;
}
SubComponents.ForEach((x) => x.Update(deltaTime));
}
public Engine EngineInstance { get => Engine.Instance; }
public Vector3 WorldLocation { get; set; }
public Vector3 WorldScale { get; set; }
public Quaternion WorldRotation { get; set; }
public Vector3 RelativeLocation { get; set; }
public Vector3 RelativeScale { get; set; }
public Quaternion RelativeRotation { get; set; }
public Matrix4x4 RelativeTransform { get; set; }
public Matrix4x4 WorldTransform { get; set; }
public virtual void Destory()
{
SubComponents.ForEach((x) => x.Destory());
}
}
四、总结
展示代码浪费了大量篇幅,所以不打算再本篇文章中讨论渲染以及摄像机的设计有关内容了。
本篇主要是介绍了三个重要的类的关系,以及代码里还有些世界坐标以及相对坐标相关的矩阵计算, (主要是调用库)。其中个人认为比较重要的细节是: 计算WorldTransform的时候,本来我是通过递归然后遍历所有的父节点计算,如果访问这个属性次数过多应该性能很差,而且在同一帧中进行访问的话,他的值应该是不变的,所以为了优化这部分的代码,我们在update中计算WorldTransform, 由于update的调用是从父节点递归来的,所以在某个子节点的update调用时,他的父节点的WorldTransform是必然被算出来的。有效减少了递归的使用。
细心的朋友会发现,虽然WorldTransform被算出来了,但是RelativeXXXX的属性没有被计算, 这里确实忘记写了,大概从WorldTransform这个四维矩阵中就能获取到位移旋转和缩放三个分量,待我后续优化一下。 |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|