JustWork 参考文档
Game Principle of <JustWork>

Game Principle of <JustWork>

参考

Besiege

核心功能

机械建造

需要更高的建造自由度:例如刚体零件的顶点自定义,3轴移动,3轴旋转,3轴缩放。
可视化内容更丰富:例如碰撞箱,连接点。

物理优化

目前还是使用phsX引擎,考虑一下GPU加速这块功能。
零件之间的连接考虑使用joint以外的方式,特别是某些刚性连接。

逻辑电路体系

参考我写的BesiegeModern Mod

零件材质自定义

{金属,木,玻璃}
需要自己写shader material实现该功能。

次要功能

更好的机械破坏系统

譬如撞击变形,高速击穿之类的
很难
在已有父刚体在运行中,更改碰撞箱布置是非常耗费性能的。

场景建造

自定义场景元素

教程系统

参考mc机械动力模组的“思索”
animation制作

故事模式

起部分教程作用,添加沉浸感,吸引一些非硬核玩家

三渲二

难,可能对电脑性能有较高要求。

目前需要的技术栈

unity shader
unity animation
美术相关工具
物理引擎Game Physics Engine Development
unity URP

ONI Modding

ONI Modding

Basic

Useful tools

  • Dnspy: decompile the runtime library of ONI
  • Visual Studio (though it’s too fat)
  • Poedit: read the .po file for zh-en translation

Hello world

Apply patches on the Initialize() method in Db class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Patches
{
[HarmonyPatch(typeof(Db))]
[HarmonyPatch("Initialize")]
public class Db_Initialize_Patch
{
public static void Prefix()
{
Debug.Log("Hello! Welcome to CuppyUniverse! [before Db.Initialize]");
}

public static void Postfix()
{
Debug.Log("CuppyUniverse initialized! [after Db.Initialize]");
}
}
[HarmonyPatch(typeof(ElectrolyzerConfig))]
[HarmonyPatch("CreateBuildingDef")]
public class ElectrolyzerConfig_Patch
{
public static void Postfix(ref BuildingDef __result)
{
__result.EnergyConsumptionWhenActive = 114f;
}
}
}

Add new building

Take gold washer (just like water purifier but output small amount of gold) as an example.

  1. Find the source code for water purifier.
    i.e.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    using System;
    using TUNING;
    using UnityEngine;

    // Token: 0x0200039A RID: 922
    public class WaterPurifierConfig : IBuildingConfig
    {
    // Token: 0x06001314 RID: 4884 RVA: 0x000665C4 File Offset: 0x000647C4
    public override BuildingDef CreateBuildingDef()
    {
    string id = "WaterPurifier";
    int width = 4;
    int height = 3;
    string anim = "waterpurifier_kanim";
    int hitpoints = 100;
    float construction_time = 30f;
    float[] tier = BUILDINGS.CONSTRUCTION_MASS_KG.TIER3;
    string[] all_METALS = MATERIALS.ALL_METALS;
    float melting_point = 800f;
    BuildLocationRule build_location_rule = BuildLocationRule.OnFloor;
    EffectorValues tier2 = NOISE_POLLUTION.NOISY.TIER3;
    BuildingDef buildingDef = BuildingTemplates.CreateBuildingDef(id, width, height, anim, hitpoints, construction_time, tier, all_METALS, melting_point, build_location_rule, BUILDINGS.DECOR.PENALTY.TIER2, tier2, 0.2f);
    ...
    return buildingDef;
    }

    // Token: 0x06001315 RID: 4885 RVA: 0x000666A8 File Offset: 0x000648A8
    public override void ConfigureBuildingTemplate(GameObject go, Tag prefab_tag)
    {
    ...
    }

    // Token: 0x06001316 RID: 4886 RVA: 0x0006688B File Offset: 0x00064A8B
    public override void DoPostConfigureComplete(GameObject go)
    {
    ...
    }

    // Token: 0x04000A88 RID: 2696
    public const string ID = "WaterPurifier";

    // Token: 0x04000A89 RID: 2697
    private const float FILTER_INPUT_RATE = 1f;

    // Token: 0x04000A8A RID: 2698
    private const float DIRTY_WATER_INPUT_RATE = 5f;

    // Token: 0x04000A8B RID: 2699
    private const float FILTER_CAPACITY = 1200f;

    // Token: 0x04000A8C RID: 2700
    private const float USED_FILTER_OUTPUT_RATE = 0.2f;

    // Token: 0x04000A8D RID: 2701
    private const float CLEAN_WATER_OUTPUT_RATE = 5f;

    // Token: 0x04000A8E RID: 2702
    private const float TARGET_OUTPUT_TEMPERATURE = 313.15f;
    }

Create a new csharp class file in VS and copy the content into it.
Remember to rename

  • the class name
  • id in method CreateBuildingDef()
  • ID in class
    In this example change them to GoldWasher
  1. Add GoldWasher to build menu.
    create a new patch for GeneratedBuildings
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    using System;
    using HarmonyLib;

    namespace ONI_CuppyUniverse_Mod
    {
    [HarmonyPatch(typeof(GeneratedBuildings), "LoadGeneratedBuildings")]
    public class GeneratedBuildings_LoadGeneratedBuildings
    {
    public static void Prefix()
    {
    Strings.Add(new string[] {
    "STRINGS.BUILDINGS.PREFABS.GOLDWASHER.NAME",
    "淘金器"
    });
    Strings.Add(new string[] {
    "STRINGS.BUILDINGS.PREFABS.GOLDWASHER.EFFECT",
    "从纯净水中淘取黄金"
    });
    Strings.Add(new string[] {
    "STRINGS.BUILDINGS.PREFABS.GOLDWASHER.DESC",
    "你是信无中生有的"
    });
    ModUtil.AddBuildingToPlanScreen("Food", "GoldWasher");
    }
    }
    }

Reference

m1

m1

Task

  • Load car configuration from .json file
  • Compose DevDocs
  • Investigate csharp comment documentation
  • use assimp to load .glb model

Vehicle Physics Pro

Starting point
The unity’s build-in physics engine PhysX is far from being precise enough for a vehicle simulator.

Needs

  • Closer to reality vehicle physics
  • Runtime performance
  • Not so complicated to config so that the composition of the savings and editor will not be a headache

Official

Install

Through Asset Store


GLTFUtility

Starting point
The car model should be customizable, i.e. the user drag a car model into a directory and the game should be able to load it.
Filetype .glb is nice but unity doesn’t have build-in support

Needs

  • Runtime import
  • support mesh and material

GLTFUtility

Install

In tactics project:

1
git submodule add https://github.com/Siccity/GLTFUtility.git ./Tactics/Assets/ThirdParty/GLTFUtility

Runtime API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
using Siccity.GLTFUtility

// Single thread
void ImportGLTF(string filepath){
GameObject res = Importer.LoadFromFile(filepath);
}

// Multi thread
void ImportGLTFAsync(string filepath){
Importer.ImportGLTFAsync(filepath, new ImportSettings(), OnFinish);
}
void OnFinish(){
...
}

here I use Application.streamingAssetsPath+"/Model/"+JsonReader.vehicle.model.carBody[0].dir as filepath

Trouble shooting

  1. In the built game, encounter ArgumentNullException: Value cannot be null. Parameter name: shader
    Github Issue
  2. The file management of the game changed after being built. How to determine where to place the Save and Model folders?
    Use Application.streamingAssetsPath, stands for Assets/StreamingAssets in Unity editor and Game_Data/StreamingAssets in exported game.

Csharp’s interpretation of Json

First construct the structure of the json file in c#.
Notice: the name of the member variable should be the same with those in json file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
[System.Serializable]
public class VehiclePhysicsPara
{
public float bodyMass;
public Vector3 centerOfMass;
public List<BodyColliderPara> collider;
public List<WheelColliderPara> wheel;
}

[System.Serializable]
public class BodyColliderPara
{
public string type; // box | sphere
public Vector3 position;
public Vector3 eulerRotation;
public Vector3 scale;
}
[System.Serializable]
public class SteeringWheelTypePara
{
public bool use;
public bool inverse;
}
[System.Serializable]
public class WheelTypePara
{
public bool powered;
public SteeringWheelTypePara steering;
}
[System.Serializable]
public class WheelSuspensionPara
{
public float spring;
public float damper;
public float distance;
public float initialPosition;
}
[System.Serializable]
public class WheelFrictionPara
{
public float extremumSlip;
public float extremumValue;
public float AsymptoteSlip;
public float AsymptoteValue;
}

[System.Serializable]
public class WheelColliderPara
{
public WheelTypePara type;
public float mass;
public float radius;
public Vector3 position;
public WheelSuspensionPara suspension;
public WheelFrictionPara forwardFriction;
public WheelFrictionPara sidewayFriction;
}

[System.Serializable]
public class VehicleModelPara
{
public List<CarBodyPara> carBody;
public List<WheelModelPara> wheel;
}
[System.Serializable]
public class CarBodyPara
{
public string name;
public string dir;
public Vector3 position;
public Vector3 eulerRotation;
public Vector3 scale;
public string shader;
}
[System.Serializable]
public class WheelModelPara
{
public string name;
public string dir;
public Vector3 scale;
public string shader;
}
[System.Serializable]
public class VehiclePara
{
public VehicleModelPara model;
public VehiclePhysicsPara physics;
}

Notice: [System.Serializable] is for serialized display in Unity editor.

Then, directly use build-in json loader:

1
2
VehiclePara vehicle;
vehicle = JsonUtility.FromJson<VehiclePara>(textJson.text);

All the parameters will be stored in the instance vehicle
vehiclePara


Abstract the vehicle as a Json structure

Graph

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
flowchart RL
Main
Model --> Main
Physics --> Main
Controller --> Main
carBody --> Model
subgraph bodyPart
part1
part2
end
bodyPart --> carBody
wheelVis --> Model
wheelVisGroup --> wheelVis
subgraph bodyCollider
boxCol
sphereCol
end
bodyCollider --> collider
collider --> Physics
massProperty --> Physics
wheelProperty --> Physics
wheelColGroup --> wheelProperty
subgraph wheelVisGroup
tyreVis1
tyreVis2
tyreVis3
tyreVis4
end
subgraph wheelColGroup
wheelCol1
wheelCol2
wheelCol3
wheelCol4
end
wheelVisGroup -- oneToOne --> wheelColGroup

Json example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
{
"model":{
"carBody":[
{
"name":"body1",
"dir":"./CustomModel/car1/body1.obj",
"position":{"x":0,"y":0,"z":0},
"eulerRotation":{"x":0,"y":0,"z":0},
"scale":{"x":0,"y":0,"z":0},
"shader":"Standard"
},
{
"name":"body2",
"dir":"./CustomModel/car1/body2.obj",
"position":{"x":0,"y":0,"z":0},
"eulerRotation":{"x":0,"y":0,"z":0},
"scale":{"x":0,"y":0,"z":0},
"shader":"Standard"
}
],
"wheel":[
{
"name":"wheel1",
"dir":"./CustomModel/car1/wheel1.obj",
"scale":{"x":0,"y":0,"z":0},
"shader":"Standard"
}
]
},
"physics":{
"collider":[
{
"type":"box",
"position":{"x":0,"y":0,"z":0},
"eulerRotation":{"x":0,"y":0,"z":0},
"scale":{"x":0,"y":0,"z":0}
},
{
"type":"sphere",
"position":{"x":0,"y":0,"z":0},
"eulerRotation":{"x":0,"y":0,"z":0},
"scale":{"x":0,"y":0,"z":0}
}
],
"centerOfMass":{"x":0,"y":0,"z":0},
"bodyMass":700,
"wheel":[
{
"type":{"powered":true,"steering":{"use":true,"inverse":false}},
"mass":7,
"radius":0.5,
"position":{"x":0,"y":0,"z":0},
"suspension":{
"spring":25000,
"damper":9000,
"distance":0.3,
"initialPosition":0.5
},
"forwardFriction":{
"extremumSlip":0.1,
"extremumValue:":1,
"AsymptoteSlip":0.2,
"AsymptoteValue":0.8
},
"sidewayFriction":{
"extremumSlip":0.05,
"extremumValue:":1,
"AsymptoteSlip":0.2,
"AsymptoteValue":0.8
}
}
]
}
}