找回密码
 立即注册
查看: 263|回复: 0

跨平台引擎开发记录2:有关游戏局内的设计

[复制链接]
发表于 2022-3-28 15:56 | 显示全部楼层 |阅读模式
上篇:
一. 前言

    本篇文章主要讲述引擎内部的设计思路,已实现和未实现的内容都会介绍,我会尽量讲述我为什么这么设计,并且标注一下暂时在代码仓库中是否已经实现。
    在设计本引擎时,我已经预设自己在开发类似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 = "Actor";
        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("世界尚未创建");  
          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("该组件已经有父级了!");
        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这个四维矩阵中就能获取到位移旋转和缩放三个分量,待我后续优化一下。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2025-5-8 05:44 , Processed in 0.136124 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表