Skip to content

一些设计详解

cvrunmin edited this page Aug 23, 2018 · 5 revisions

一些设计详解

我们需要在游戏里定义什么?

讨论一下我们需要在游戏里定义什么东西。

名称 这关于什么? 如何定义? 为何要这样设计?
动作(Action) 像是指令一样的抽象动作,但这也包含“玩家向前移动(Player move forward)”的动作 以 string id 定义 (player.move.forward) 我们需要将这些与热键、指令等输入方式绑定去操控游戏
游戏物件类型(Game Object Type) 游戏内物件类型,例如方块原型,物品原型,实体类型。 以语义化 string id 定义 (block.stone) 主要在网络以及硬盘的序列化与反序列化中使用
游戏物件对象(Game Object) 游戏里其中一个重要部分,主要指世界生命周期里的物件(实体,方块,物品)。 以 hierarchy structure string 定义。 (block requires location, entity requires id) e.g. /<dimension id>/<world id>/<position> 我们需要有一个统一的游戏物件参考方法令客户端与服务端能更方便去沟通。
资源(Resource) 硬盘(或者远端)的游戏资源 以它的路径定义 我们很明显要读取资源。

我们需要以不同的方法去管理这些东西。

ActionsGameObjectType 需要由独立的注册表管理。

Resource 需要由 ResourceManager 管理。Resource 需要提供基础快取 (cache bytes), 这些资源会被处理成不同形态 (纹理,模型,声音,动画),然后连接到特定位置。 游戏读取阶段完成后所有资源已经处理完毕,所有 raw bytes 快取需要清除。

GameObject 需要由它的父物件管理。我们不需要做特别东西,只需要处理路径以从dimension,世界获得方块或者实体。

About the Registry Convenience

The ideal way to register a thing is only providing its id without any redundant information. The Mod should not consider too much about conflicting with other Mod.

Registry<BlockObject> registry;
registry.register(block.setRegistryName('air')); // infer the query full path is <current modid>.block.air
Registry<ItemObject> itemRegistry;
itemRegistry.register(item.setRegisterName('stone')); // infer the query full path is <current modid>.item.stone

RegistryManager manager;
manager.register(block.setRegistryName('stone'));  // infer the query full path is <current modid>.block.stone

// when we want to get a registered object
manager.get('unknowndomain.block.stone'); // get the stone block

关于注册名

我建议我们都为注册名使用蛇式命名法(用下划线"_"分割单词)。

注册名中不应当包含任何点 (.)

关于子注册表命名空间 (建议)

There could be sub-named blocks and items. For block, it might be produced by combining properties.

启动过程 (草稿)

  1. 初始化GLFW、OpenGL、窗口与其他钩子
  2. 初始化资源管理器,获得默认资源源
  3. 初始化模组管理器
  4. 初始化默认渲染器,这将需要载入默认的纹理和对象

开始游戏载入过程 (草稿)

  1. 初始化玩家数据,登录信息(如果没有本地缓存的数据,图形界面将让玩家登陆)
  2. 从服务器获取资源和模组列表
    1. 确认是否存在于本地
    2. 下载缺少的模组与资源
  3. 初始化Action管理器
  4. 初始化KeyBinding,需要Action管理器
  5. 初始化游戏环境
  6. Mod管理器载入所有Mod
    1. Mod注册所有方块/物品/实体
    2. 资源管理器载入所有模组需要的资源
    3. 使用自定义模组资源处理管线处理资源

关键算法与问题

  • 物理与碰撞
    • 使用JOML的AABB来处理碰撞
    • 也许需要一个World下的通用管理器来管理物理?
  • 射线追踪以选取方块或实体
    • 使用JOML的Ray来处理选取
  • 逻辑/渲染对象状态管理和更新周期
    • Naive的想法是在逻辑和渲染线程使用两种不同的数据格式。
    • 逻辑线程的数据由事件更改(事件可以在网络/用户输入之间切换)
    • 逻辑线程收到事件之后,它应该向渲染线程通知可公开的更改
    • 渲染线程中的数据仅接收逻辑线程提供的更改,理想情况下它不会主动查询逻辑线程的数据
  • 面剔除

渲染区块

A block model A has N vertices, then it vertex length is N * 3

[x0 y0 z0 x1 y1 z1 ... xn yn zn]

Bake the chunk as combination of block model:

blockAt (0,0,0) (0,0,1) ....

[x0 y0 z0 x1 y1 z1 ... xn yn zn] [x0 y0 z0 x1 y1 z1 ... xn yn zn] ...

Suppose len(m) is the vertices count of model m.

We maintains a summed list L where L[x] = L[x - 1] + len(model at index x)

When a block is changed in chunk, we update the render chunk data by swap the block vertex:

Suppose the block at position (x, y, z) changed,

float[] newVertices; // passed by param
int index = (x & 0xF) < 8 | (y & 0xF) < 4 | (z & 0xF);
int leftSum = L[index - 1];
int rightSum = L[index];
int totalLength = L[(0xF) < 8 | (0xF) < 4 | (0xF)];

int newRightSum = leftSum + newVertices.length;
int oldVerticesCount = rightSum - leftSum;

// these are not real gl commands
// shift the right vertices
glCopy(rightSum, newRightSum, totalLength - rightSum);
// upload the new block vertices to the correct position
glUpload(newVertices, leftSum);

渲染过程介绍

http://fragmentbuffer.com/gpu-performance-for-game-artists/

游戏循环

LWJGL book Fix Step

What is the point of update independent rendering in a game loop?

Dynamic Tick vs Fix Step Tick

Introduce Game Loop

游戏循环将被分割为逻辑和渲染两个循环

  • 逻辑
    • 独立地更新
    • 更新世界和其他世界子系统
      • 更新实体之间的物理效果
  • 渲染
    • 依据逻辑部分更新
    • 维护方块/实体变换
    • 维护粒子系统
    • 更新渲染