'Elroids' Developer Code Notes.
Details on how/why the code works:
Structure
Top level classes work something like
- Game - Stuff related to game play. Not the simulated universe.
- Player - Stuff related to the current player.
- MyScene - Stuff related to sound and graphics rendering.
- Displays - Screen display graphics (Maybe should go under Ship).
- Purchase lists - Component and goods shoping lists.
- Main animation loop.
- Universe - Everything related to the in game universe.
- In game time keeping
- Ship - Details of the player's ship.
- Ship components
- Ship cargo
- List and positions of all star systems.
- StarSystem - Details about a specific star system. If not the 'current' active system some data
(e.g. rock details) will be dicarded (see 'Activation' below).
- System stats.
- Law, tech and magic levels.
- Mineral abundancies.
- Rock and saucer counts.
- Items - The game objects within a system.
- Rocks
- Saucers
- Stations
- Wormholes
- Floating minerals and goods crates.
- List of stations.
- List of wormholes
- Total
- Hyperspace - A subclass of System.
- Pointer to 'current' star system.
Save/Load mechanism.
Game images are saved as JSON files.
Each object persists only none recreatable members. i.e NOT stuff, like meshes, that don't change and can be
dynamically re-created.
Each persistable object has two methods:
- toJSON() - Converts the non-recreateble members into JSON objects. This is built into one big JSON image and
then Stringified.
- static fromJSON() - This is given the JSON object, invokes the object's normal constructor and and returns a
new version of the object.
Object sets can be handled with the utility class JSONSet.
Item/System IDs
During game operation references between objects are handled by standard javascript reference. However these
won't survive save/load operations.
When a reference need to be saved the target object is assigned a unique ID. This is converted back to the new
reference during re-load.
Item activation
In attempt to reduce memory/cpu footprint most threejs derived graphical object can exist in two states:
-
Active - When they are part of the current System:
Item has a grapical Mesh.
Item specific Textures are loaded.
Item is added to scene
Item get animate() calls.
-
Inactive - When they are not int the current System:
Graphical mesh is deleted.
Item specific Textures are unloaded.
Item is removed from scene.
Item does not get animate() calls. If is 'frozen' until system is re-activated.
When Systems are constructed their Items are created with a populate() method. The Items are created in
'inactive' state.
When a System becomed 'active', generally because th Ship has moved into it, its setActive(true) method is
called. This invokes the setActive() of it's child Items. Each sets up it's graphical components (generally by
calling setumMesh()) and adds itself to the scene.
When a System become 'inactive' its setActive(false) method is called. The child Items must remove themselves
from the scene and null all theit grapical components. The graphical parts should then be GCed.
ToDo: Maybe 'System' should extend threejs.Group. Objects contained in the System could be added to it. The the
whole Group could be added/removed from scene.
Learnings
- Three JS is oriented towards 'landscape' scenes.
odd things (flipings and unexpected rotations)
can happen if a camera flies over the 'poles'.
After a lot of effort I worked round this by making the 'chase/pilot
cameras' part of the ship.
- Two threejs 'renderers' are used:
- A THREE.WebGLRenderer handles the 3D rendering.
- A separate CSS2DRenderer handles 2D objects, like labels, that we want to be flat on to camera.
This as described in the exapmle here.
- 1D conservation of momentum, during collisions, is handled as described in the equasions
here.
Most discussion on extending this to 3D decends into a mass of 'Euler angles' and 'trig'. However since we
have both the
velocities and impact direction as x, y and z components it can be handled using
dot products (and less CPU) ... but it took a while to get the maths right.
- There are problems with 'quantized' time. A slower moving object can catch up with and collide with a faster
one if it gets a move phase 'first'. Some messy work round have been required.
- Sound support on Chrome is a pain. In an attempt to block audio spam audio components can only ne
initialized from within user events. So sounds have to be loaded on the first key/mouse click. Which means
they are not yet ready if this event wans to make a sound,
- 'statics' are slow. Require an instance of the class to be created? For better performance create a single
instance as part of a top level object (e.g. Game) and call by reference.
Units and dimensions
This project was coded with arbitary units of distance, mass and time. I have since modified it to use something
resembling
SI.
- Distance:
- For short distance (within systems) is in Meters.
- For long distance (between systems) is in some larger unit (lightyears?).
- Mass is in Tonnes (1000KG).
- Time is in seconds.
- Damage (HPs) is an arbitary unit. However, from the 'ram damage' calculation we can conclude that its units
are M kg m/s
- Rocks have a mass equal to the cube of their size. i.e (assuming cubic rocks) a density of about 1
tonne/m^3. Probably should be a bit higher and related to their composition. However it will do for now.
There are a few incorrect 'artifacts' to make the game playable.
- Rock mass is proportional to the cube of its size. When a rock splits it make 2 rocks of half the size. So
3/4 of the mass is lost. However this makes for a mangable amounts of minerals once a rock is broken down.
(The spare mass becomes 'dark matter')
- Universe is too small. Only a few km across. Necessary to keep a reasonable rock density wihtout a huge
number of rocks.
- Distances and sizes are far too small for a 'realistic' universe. However if made realistic objects would
only be points of light in the distance. For playability everything is moved closer together. (Excuse:
StarTrek/Wars battles where ships engage a few hundred meters apart and travelling at a few 10s of kph.)
- At present angular momentum is not conserved (for anything) ... If it were ship contol would be difficult.
- Deceleration doesn't really make sense. Engines fire in a ramdom direction. But it is necessary for game
play.
Why not Typescript?
As a Java coder I prefer stongly typed languages. However...
- I only found out about Typescript late in the development of this project.
- I want the code available warts/comments and all. Not an additional build step with modified output.