Jump to content

Scenes, prefabs, and the Lua state serialization that loves them


Josh

1,354 views

 Share

The beta testers and I are are discussing game programming in the new engine. I want to see C++, Lua, and C# all take a near-identical approach that steals the best aspects of modern game programming and ditches the worst, to create something new and unique. To that end, we are developing the same simple game several times, with several different methodologies, to determine what really works best. One thing I realized quickly was we really need a way to load prefabs from files.

I started implementing a JSON scene format using the wonderful nlohmann::json library, and found the whole engine can easily serialize all information into the schema. You can save a scene, or save a single entity as a prefab. They're really the same thing, except that prefabs contain a single top-level entity.

{
	"scene": {
		"entities": [
			{
				"angularVelocity": [
					0.0,
					0.0,
					0.0
				],
				"castShadows": true,
				"collisionType": 0,
				"color": [
					0.7529412508010864,
					1.2352941036224365,
					1.5,
					1.0
				],
				"floatPrecision": 32,
				"guid": "53f8d368-24da-4fba-b343-22afb4237d1b",
				"hidden": false,
				"light": {
					"cacheShadows": true,
					"coneAngles": [
						45.0,
						35.0
					],
					"range": [
						0.019999999552965164,
						12.0
					],
					"type": 0
				},
				"mass": 0.0,
				"matrix": 52,
				"name": "Point Light 2",
				"physicsMode": 1,
				"pickMode": 0,
				"position": 0,
				"quaternion": 24,
				"rotation": 12,
				"scale": 40,
				"static": false,
				"velocity": [
					0.0,
					0.0,
					0.0
				]
			}
		}
	}
}

For fast-loading binary data I save an accompanying .bin file. The values you see for position, rotation, etc. are offsets in the binary file where the data is saved.

From there, it wasn't that much if a stretch to implement Lua State serialization. Lua tables align pretty closely to JSON tables. It's not perfect, but it's close enough I would rather deal with the niggles of that than implement an ASCII data structure that doesn't show syntax highlighting in Visual Studio Code.

"luaState": {
	"camera": "ab65bd91-153d-47fb-a11b-ff40c19cd8f4",
	"cameraheight": 1.7,
	"camerarotation": "<Vec3>::-4.2,39.1,0",
	"carriedobject": "9daf54a7-c3b5-4b4b-979b-6b034d6b80fd",
	"carriedobject_damping": "<Vec2>::0.1,0.1",
	"carriedobject_gravitymode": true,
	"carryposition": "<Vec3>::-0.259477,-0.372455,1.95684",
	"carryrotation": "<Quat>::0.0635833,0.755824,-0.649477,-0.0535437",
	"interactionrange": 2.5,
	"listener": "45c93683-ca3b-493a-ad06-b16fe14e4175",
	"looksmoothing": 2.0,
	"lookspeed": 0.1,
	"maxcarrymass": 10.0,
	"modelfile": "Models/Weapons/Ronan Rifle/scene.gltf",
	"mouselost": false,
	"mousemovement": "<Vec2>::-5.00474e-19,3.1102e-22",
	"movespeed": 5.0,
	"throwforce": 1500.0,
	"weapon": "2f8e3d74-26be-4828-9053-a455a9fd05fd",
	"weaponposition": "<Vec3>::0.12,-0.4,0.42",
	"weaponrotation": "<Vec3>::-89.9802,-0,0",
	"weaponswayspeed": 0.1,
	"weaponswayticks": 2443.5362155621197
}

As a result, we now have the ability to easily add quick save of any game, and loading of the game state, automatically, without any special code. The only exception is for entities that are created in code, since they do not have a GUID to trace back to the original loaded scene. This is easily handled with a LoadState() function that gets executed in Lua after a saved game is loaded. In my FPSPlayer script I create a kinematic joint to make the player carry an object when they select it by looking at it and pressing the E key. Since this joint is created in code, there is no way to trace it back to the original scene file. So what I do is first remove the existing joint, if an object is currently being picked up, and then create a new joint, if one has been loaded in the game save file.

function entity:LoadState()
	if self.carryjoint ~= nil then
		self.carryjoint.child:SetGravityMode(self.carriedobject_gravitymode)
		self.carryjoint.child:SetDamping(self.carriedobject_damping.x, self.carriedobject_damping.y)
		self.carryjoint:Break()
		self.carryjoint = nil
	end
	if self.carriedobject ~= nil then
		local pos = TransformPoint(self.carryposition, self.camera, nil)
		self.carryjoint = CreateKinematicJoint(pos, self.carriedobject)
		self.carryjoint:SetFriction(1000,1000)
	end
end

Here is the result. The player rotation, camera angle, and other settings did not have to be manually programmed. I just saved the scene and reloaded the entity info, and it just works. You can see even the weapon sway timing gets restored exactly the way it was when the game is reloaded from the saved state.

For most of your gameplay, it will just work automatically. This is a game-changing feature because it enables easy saving and loading of your game state at any time, something that even AAA games sometimes struggle to support.

  • Like 8
  • Thanks 1
 Share

0 Comments


Recommended Comments

There are no comments to display.

Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...