-
Notifications
You must be signed in to change notification settings - Fork 19
Current Design Situation
- Current Design Situation
- Some Humble Design Thought
Discuss the things we need to identify in game.
Name | What does it referring? | How to identify? | Why? |
---|---|---|---|
Action | The abstract action, like command, but this should include the action like "player move forward" | By string id (player.move.forward) | We need to bind key or command or other input way to these to control game |
Game Object Type | The type of ingame object, like block prototype, item prototype, entity type. | By semantic string id (block.stone) | Majorly used for (de)serialization on network or disk |
Game Object | The important things ingame, mostly the object under the world life cycle (entity, block, item) | By hierarchy structure string. (block requires location, entity requires id) e.g. /<dimension id>/<world id>/<position> | We need to have a universal way to refer an ingame object which provides a easy way to communicate between client and server |
Resource | The game resource in disk (or remote) | By its string dir | We need to loadOrder resources obviously |
We have to manage these things differently though.
The Actions
and GameObjectType
should be managed by individual registries.
The Resource
should managed by ResourceManager
. It should provide the basic level of caching (cache bytes).
The resource will transform to other form (Texture, Model, Sound, Animation) and linked with its location.
After the loading stage of a game, all the resource transformed out. The raw bytes cache should be cleared.
The GameObject
should managed by its parent. We don't need to do anything special to it.
Just process the dir to get dimension, get world, and get block or entity.
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 dir is <current modId>.block.air
Registry<ItemObject> itemRegistry;
itemRegistry.register(item.setRegisterName('stone')); // infer the query full dir is <current modId>.item.stone
RegistryManager manager;
manager.register(block.setRegistryName('stone')); // infer the query full dir is <current modId>.block.stone
// when we want to get a registered object
manager.get('unknowndomain.block.stone'); // get the stone block
I suggest we all use the snake case (split word with low_dash) for register name.
The name should not contain any dot/period (.)
There might be sub-named blocks and items. For block, it might be produced by combining properties.
- initialize glfw opengl, window and other hook
- init engine
- initialize player profile, login information from disk (later GUI show to let player login if there is no local cached profile)
- grab game config from disk
- create game
- pull the resources/mod config from server
- construct stage: load mods (asm, classloading)
- register stage: register all block/item/entity/action/renderer factory
- resource stage: call mods to load resources
- finish stage: initialize world
- game loop start
- physics and collision
- use joml AABB to deal with bonding
- maybe a general manager under
World
to manage physics?
- logic/render object state management and update cycle
- naive idea is that we keep two different form of data in game and renderer thread.
- the data in logic thread are changed by event (the event could toggle by network/user input)
- after the logic thread receive event, it should emit the change which only exposed to renderer thread
- the data in renderer thread only receives the change provide by logic thread; ideally it won't query logic thread data by it self
- face culling
Narrowphase use use joml
package's Ray
to deal with picking.
Boardphase we use A Fast Voxel Traversal Algorithm with the implementation.
The basic idea is get three "faces" of blocks according to current entity motion vector and entity AABB, and test the theses blocks AABB with entity AABB.
e.g.
To get these blocks' positions. The entity's motion vector is (x,y,z)
.
For the x
axis, we first figure out that the direction of x
, positive or negative.
Then, pick the correct face from 6 faces of current entity AABB. If x
is positve, pick the AABB face with maxX
. Else we pick the minX
face.
Please notice that the AABB is represented in two vector: (minX, minY, minZ), (maxX, maxY, maxZ)
Suppose we pick the face by x
axis called X
. The face X
is defined by 4 vertices. We translate this face by the motion vector x
axis value.
Then we get all the block between the translated face area.
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);
Some reference articles:
- 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
Splitting Between Logic and Render
- Logic
- update independently
- update world and various world subsystem
- update world physics between entity
- Renderer
- update progress depending on the partial progress of logic
- maintains block/entity model part transformation
- maintains batch particles system
- render update
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.
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...
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();
}
}
Copyright © 2019-2020 UnknownDomainGames