This little low-poly renderer is out of control! Since the last post I've added:
Building and snapping system
Physics - falling trees, collision
Inventory and crafting
Creature AI - salmon and raven
Combat - fight ravens
Infinite world generation
Coloured fog
Building System
A game can't be a survival game without building! This is my first foray into building systems that aren't voxel-based so there was a lot to learn here.
The first step was to figure out what the player can build. I searched for 'Modular Wood Building Pieces' on Sketchfab and found this free one, which has walls, floors, doors, pillars, rooves, etc. Everything a basic survival game needs!
All these model pieces are stored in the one 'model', so I split them up and gave them names like Wall, WallHalf, WallWindow, Floor, Angled45, etc. Now they can be rendered individually:
Now we need a way for the player to build them. I created a highly un-optimised raymarching function, which starts from the camera's position and steps forward until it's under the map. This lets the player build on the ground.
But building on the ground isn't enough! We need a snapping system so that when you're looking at the edge of a structure, you'll place the next structure nice and flush against it.
To do this, I use a list of 'snap positions'. Each time a structure is built, it adds snap positions to this list. For example when placing a floor, it creates a snap position on each edge:
Then I updated the raymarching function to also check if it's near a snap position. Now it's even more un-optimised, but it works!
However, it's not that simple. I realised that pillars shouldn't snap to the edges of floors; they should snap to the corners instead. This means that each structure can snap differently to every other structure.
So rather than having one list of Snap Positions, I need a list for every structure type. So when a player builds a floor, it adds 4 edge snap positions to the 'floor' list, and 4 corner snap positions to the 'pillar list'. Then the raymarch function uses the list of Snap Positions for the current structure type.
Sounds nice and simple until you realise there's 18 different types of wooden structures! Thankfully a lot of them are the same shape, but there's still a lot of different combinations.
Rotating
Rotating these structures was pretty nasty. And by nasty I mean math-heavy, which I'm not a fan of.
The reason this is nasty is because structures should rotate differently when snapping to different structure, **and** when snapping to different sides of a structure.
Let's say we placed a wall (orange), and we want to place another wall (blue) to the right of it. If we rotate this new wall, it should pivot around the right side of the existing wall (white):
But if we place a wall (green) on the left side of the wall, it should pivot around the left side:
This sounds good, but the implementation is tricky because I only have a list of snap positions, which don't remember which structures they're associated with.
So I changed this list of positions (Vector3) to a list of SnapPositions. This class stores:
type: what kind of structure they are (wall, floor, pillar, etc)
anchor: the position of the snap point
pivot: the center of the structure
left: whether the anchor is to the left of the pivot
Now you may assume that since I made this diagram, I understand it. But I don't. But it works!
We now have enough information to correctly rotate structures around their anchors. I think the left variable isn't needed, but I don't understand the maths enough to optimise this.
Check out the raw_videos/building folder on Dropbox for 19 more videos showing the progress of creating this building system
Physics
Building isn't satisfying if we didn't harvest the building materials ourselves!
The following is a summary of my arduous and ongoing battle with physics - fun fact, Sector's Edge had no physics!
After 2D physics defeated me in battle, I realised creating my own 3D physics engine is near impossible. I discovered BepuPhysics - an incredibly optimised C# physics engine - and decided to use it for the Vercidium Engine.
BepuPhysics handles a lot for me:
raycasting against objects
collision with the heightmap (terrain)
static (trees, terrain) vs dynamic objects (fallen trees, player)
It also supports multithreading - my favourite!
Currently the player's position is clamped to the heightmap (no jumping) so the first step was to create physics objects for the terrain. The terrain is split into chunks, so that means each chunk needs to have its own physics mesh.
The next step was to give each tree a cylindrical physics object, so the player can't run through it anymore.
Then the player needed to be controlled by physics too - no more 'If W pressed move forward' logic! Thankfully there's a CharacterController demo in the BepuPhysics repo, because I struggled writing my own and the player was sliding on ice all around the map (check out the raw_videos/physics folder on Dropbox for more funny physics fails).
To test everything was working, I spawned some cubes above the player and watched them bounce off the player and roll around the terrain. Physics achieved!
The next step was to be able to attack trees, which was surprisingly very easy! BepuPhysics has a Raycast function, which you pass a position to start the raycast, the direction of the raycast, and how far you want the ray to travel. Then if it hit anything, it tells you which object it hit and how far away it is.
So now when the player left clicks, the player plays a kicking animation and a ray is cast. If the object was a tree, that tree takes damage. After a few kicks, I remove the static cylinder object and replace it with a dynamic cylinder object.
Static objects never move (terrain) whereas dynamic objects do
The final step was to add physics object for the wooden structures. Even though each structure is composed of basic shapes (a wall is just a box) it was very tedious task to set up different object types for each structure.
But once it was done, I could build a tree houses and stand on it!
Inventory and Crafting
Players can still build infinitely, so we need a way to collect items and use them.
Let's start with the Inventory, which is an array of 40 Items:
Each Item has a type, amount and modifiers (e.g. cooked)
To craft new items, we need a Recipe, which has these variables:
station - e.g. a nearby campfire is needed to cook Salmon
type - basic vs cooking (might be skill related in future?)
ingredients - e.g. 3 wood and 1 rock
output - e.g. an axe
Example recipes are 2 Wood + 2 Rock = Campfire, and Salmon + nearby Campfire = Salmon (cooked)
Ingredients contain an item type, modifiers (e.g. uncooked vs cooked) and an amount.
Then to pick up an item, have a look at it and press E
The UI for this is terrible, that's next on my list!
Creature AI
This brings me to the next new feature: creatures! Smart creatures are essential to make this world feel more alive, and I call them 'creatures' not 'animals' because there may be other monsters...
So far there are two creatures: Salmon and Ravens.
Ravens are the hunters, they fly around looking for fish to eat! When they're hungry enough, they'll dive into the water, emerging with a salmon in its talons. Then it lands in a safe place for lunch.
Salmon swim around in lakes until they beach themselves. Then they flop around until they're back in the water.
Combat
If you manage to find a Raven while its feasting, you can kick it! It'll fly away and swoop you from above. Lucky for you, you can't die yet!
Watch raw_video/raven/fightRaven.mp4 to see them in action
I also thought it would be funny to be able to punt fish. I haven't added this yet.
Infinite Worlds
With all these creatures, physics and items, infinite worlds become pretty tricky to implement.
I've started by addressing the most important issue: memory leaks. When you move around the world, chunks that are outside your render distance shouldn't exist any more, since you can't see them. So they are recycled and are used for the chunks you're moving towards. I explain this in more detail in this Infinite Terrain post.
But it's not just terrain that needs to be recycled, we now have a lot more to manage:
Trees are removed and their physics meshes are disposed
Terrain physics meshes are recycled and use for new chunks
Items (like rocks or wood) are removed from the chunk
Creatures don't despawn yet
Wooden buildings never despawn
Also, if you chop down all the trees in a chunk, then run away and back, all the trees will be back! This is because I don't save anything to disk yet, and I'm not quite sure how I'll do it yet. I think I'll start with JSON so it's simple (but slow) and store a list of:
All trees the player has chopped down
All items that the player picked up (branches, rocks, mushrooms)
All items the player dropped on the floor
Creatures won't be saved, as they respawn all the time anyway
All buildings the player has built
Then when a chunk is loaded for the first time, it generates everything (trees, items) like normal, then removes the trees the player cut down and removes the items the player picked up.
I'll have to see how it goes, writing it out makes it seem easy but it may not be! Multiplayer will also complicate things, as chunks may be loaded for one player and not another.
Coloured Fog
When the sun sets you can clearly see where the world ends, because the trees and skybox are different colours.
To disguise where the world ends, the world needs to be sun-coloured when the sun is setting.
This is achieved by modifying the colour of the fog in the shader based on the current direction of the sun, e.g. if the sun is in the same direction as a tree, that tree should be more sun-coloured.
When the sun has nearly set, we get these beautiful scenes composed of different shades of gold:
Next Steps
The next thing I need to do is stop working on this! My next video is due to release on Saturday, and it's nearly done. I've programmed all the animations, recorded my voice, and now I need to update the animations to match the timing of my voice. I'll send a draft here for paid members for feedback this week.
Back to this survival game - I need to start optimising! My performance limit is 144 FPS at 3440x1440px on my PC at 50% GPU, and I'm currently not meeting that as it's running at 200 FPS at 90% GPU. The animated creatures and items on the ground are currently not instanced, which is killing FPS at the moment. The grass, terrain and trees render very quickly though!
I'd also like to get a save system working, as I'm building some pretty houses and losing them each time I restart the game.
View all 673 screenshots and 103 videos on Dropbox here
The code for this will be available on Github Sponsors soon, I'm still in the process of linking that to Patreon.