Let's talk about nerd stuff! Because there are some interesting changes and developments behind the scenes with the introduction of cars land, one of them being our new ride system, which we’ll be talking bout in this (probably) one-off article revealing some of our magic.
Most of IF’s rides are based on train carts and fairly straight forward, there’s a track that a vehicle follows until it reaches the end where it kicks you off, throws you your coins and then dooms itself to the special circle in hell where all despawned vehicles go. This is a system that has served us well over the years but started showing its technical limitations when pushing it too far in big or flat rides (causing some to be buggy or take big tolls on server performance, degrading the player experience for everyone)
So we started looking beyond TrainCarts for some of our newer rides that are in development, the first of them being Luigi’s Rollickin’ Roadsters. We wanted something flexible in the workflow, could be used for a wide variety of ride types, and reliable while also being as efficient as possible. We ended up developing our own system, which is called Tracks, and I think that it’s pretty dope, so let's dive into the inner workings to see how it ticks.
The first challenge after the building is getting the path in-game, which is vital for movement, since, well, everything would be moving in random directions, a vehicle doesn’t know where to go on its own.
The first step of making a ride functional is importing the ride in a piece software called Blender, which is usually used for 3d animations, modelling or shading but also has an amazing Python scripting feature which you can use to add custom functionality. We use this to import a schematic from the specific ride and then add a bunch of objects that will later represent vehicles. We can then animate them like you usually would in Blender (through keyframes, bezier splines, or whatever) to get a rough digital version of the ride with the accurate and smooth movements of real vehicles.
We then run a custom script to render the animation with full details of every moving object (location and orientation per frame) and save it in a data format that we can easily parse and process for Minecraft.
So we now have one big file containing every moving element in the ride, as well as its (relative) location in every frame. All we have to do now is teleport each armour stand over all the frames and boom! Animation! Well, no, we aren’t quite there yet.
We still have two things to figure out, A: how on earth will you even ride the thing and B: how can we re-use this system for more rides in the future.
We’ll have to dive into some code before we can answer either of those questions. Because every ride is resembled by a BakedRide object which already contains the parsed data that we re-rendered from blender along with some minor processing to find the first, last and milliseconds between frames since this is logic that's shared between all rides, but that’s about where the similarities stop. Because very ride has unique vehicles with unique rules (dispatch times, seat count and location or even moving parts). This is why every ride registers its own AbstractVehicle and VehicleProvider.
The first thing that a ride does after loading is initializing its vehicles; it basically goes like
Oi! I’ve got <insert blender object name here>!
This is useful because that will allow us to create different vehicle types as part of one bigger train (front/back train, or extra moving elements like animatronics). But let's follow Luigi's example, which only has one type of vehicle which has two seats and one out of 4 random colours, this looks like this in code.
There’s quite a bit to go over here; we first define 3 armour stands (one that contains a random resource pack model, and two interactable ones that function as rideable seats)
but the most interesting stuff happens in setLocatoin. This method gets called for every frame of animation and moves the main armour stand (the one with the model) to the new location, but we also have to seats to worry about, because they need some special love and care. You don’t want to sit on the lap of another player in the middle of the model after all (except for the two of you who would find that funny)
We can’t just add or subtract from the new location since that would mean that you would sit on a fixed location, floating outside of the vehicle like a magic carpet ride instead of inside a sentient (which might be even weirder, thinking of it)
But luckily for us, there’s some useful math to help us out!
Hold on; it’s not boring, I promise.
Let's backtrack for a second, we have a central location with orientation (X, Y, Z, Pitch, Yaw) and want to get a relative location (in front of it, for example). We can’t just take the easy route and do x-1 since that would break when the object rotates on its yaw axis, since a 90-degree rotation would make our wonderful x-1 sideways instead of in front. We could calculate a circle and define our seat locations as a certain radius from the middle point with a certain offset, but that too would break when applying vertical movement or more complex structures.
But a nicer way to do it would be with Normal’s, which basically represents a relative location, often used to calculate relative points or angles to another object, which is exactly what we’re doing! so one utility math function later, and we can define our seats as X, Y, Z offset vectors from our middle location (the armour stand with the model), and our seats will always properly rotate along with it (regardless of the axis) (see line 41 and 42 of the provided code above)
It’s starting to look a lot like the ride as it turned out, the only things left to do from here were implementing passenger logic, chunk loading and ride dispatching but I won’t go in-depth about that since it’s pretty straight forward anyway.
And just like that, we have a finished ride for you all to enjoy, with a system that we can build up from in the future for more complex and realistic effects and mechanics with performance less overhead and more flexibility.
That has been it for this article, thingy, whatever you may call it about a nerd having too much fun with blender and a block game.
Let me know what you thought of it, I tried not to make it too boring while still going pretty in-depth on how it all works because some of you might find it interesting or even light a spark to start experimenting for yourself since the world of server modding really is wonderful and a lot of fun.
Hope to see y’all in the server soon! :)
(Don’t forget to send your love to Mouske, who spend a few too many hours looking at youtube on rides to animate the ride in blender lol)
None of this is a shot at TrainCarts, BK or the bergerhealer team, we will continue to use their wonderful work for our existing rides and systems and are grateful for their work and effort that they donate to the Minecraft community as a whole.
We don’t have any plans as of right now to opensource any of the mentioned systems nor do I promise any more articles like this, just wanted to share something that I personally find really cool and enjoyed working on :) (but might end up doing more if y’all enjoy reading this kind of garbage, who knows)