Skip to content

一些设计详解

Mouse edited this page Oct 2, 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

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

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

Render Progress Introduction

These are some background:

Current render process is naive. Just a collection of Renderer objects, which will be called void render(double partialTick) each render tick in order.

Some Helper Classes to manage the GL stuff

OpenGL managed things by its id. The GL allocated objects, like VAO, VBO, or textures, are all identified by ids. Therefore, we should have some unified wrapper for this.

Currently, we have wrapper for texture, the GLTexture class, which has bind function to bind texture, and dispose to deallocate this GL resource.

Also, another class called GLMesh is using to represent an model data. But, it's not a really good design. I might change it later...

Some Humble Design Thought

When we want to create some object. There always some precondition, re-configuration the object may have. Normally, we pass them as the parameter to the object. But, when the object is complex. This way won't really work.

These are three pattern to create a object:

Whent the object is really simple, like data class. We can use constuctor.

class A {
    public A(String paramA, int paramB) {
        this.a = paramA;
        this.b = paramB;
    }
}

If there are some transformation or other calculation which only related to this object creation, such as load resources from disk. We can have a static create method to manage that.

class A {
    public static A create(String paramA, int paramB) {
        // perform some transformation to paramA
        // perform some transformation to paramB
        // perform complex transformation
        return new A(transformedA, transformedB, transformed C);
    }
    private A(...) {
    }
}

If the object lifecycle is really complex, we usually pass a config to constuctor. Then, a initialization function there to trigger the complex initialization. (Maybe multiple init function).

This is the case that the object will pass through multiple stages changes.

class A {
    public A(Config config) {
        this.config = config;
    }

    public start(Context otherContext) throws Exception {
        preInit();
        init();
        postInit();
    }
}