The Lua Line is a digital recreation of the model railway I always dreamed of having when I was a kid. Containing a miniature countryside with villages, trees, a mountain and most important of all: train tracks weaving through the scenery and connecting the tiny villages. Following them, trains would chug along continuously to bring this isolated world to life.
As soon as the idea of creating a digital model railway system with scripting was on the table I started thinking about how I would be able to do this. Would it be purely scripts outputting a simple white screen showing moving dots? Or would I be able to make this in 3D using an existing engine? Perhaps the first option would have given a ‘purer’ Lua scripting experience but the allure of making something that would look like the real deal made me decide against it anyway. I chose to try out the CryEngine SDK because I knew it had great Lua support and I already wanted to learn working with this engine. This way I would be able to kill two birds with one stone.
So the tool was determined and based on that tool the high-level goals were set:
- Learning Lua
- Learning CryEngine
- Attaining experience in creating terrain
With the three high-level goals in mind I could get started on my other low-level goals and outlining the overall project by creating a one-page design document. As mentioned this document would list both types of goals but on top of that it should contain a short and concise summary of the idea.
An OPDD should be able to explain EVERYTHING about a project to someone not familiar with it. (So yes, depending on the project, this is the document you don’t want to see leaked)
You can see the one page design document as the 'red line' of the project. The red line is a Dutch saying that stands for the figurative thread you keep a hold on to make sure you don’t stray from your goals. This can be of tremendous help as it makes it easy to notice you are spending too much time on an aspect that is not that important to the end result.
As you can see on the OPDD my low-level goals for the Lua Line were:
- Sensory system to pinpoint train locations
- Signage that regulates train flow
- Train read and react to signage
- Crossings anticipate approaching trains
- Trains are able to move to set locations (stations)
- Diverting and converging tracks
- Trains are able to change speeds
- Trains are able to traverse varying heights
- Ability to spawn and remove trains on the fly
The terrain also received some guidelines that were mostly based on these goals. I will return to this subject in a later chapter where I talk in depth about the terrain.
After getting to grips with the editor (more on this at the end of this chapter) I started creating the terrain. When the tracks had been laid I moved onto scripting. The reasoning behind choosing to create the terrain first and leaving the scripting for last, was that I become more motivated when my current project starts looking real good. I always try to get something pretty as soon as possible, even if it won’t look anything like the final product.
I should have done it the other way round though. The system has a lot more to say about the layout than vice-versa. If for instance the system would dictate trains cannot climb slopes steeper than an x amount of degrees, the terrain would need to make sure there are no areas in which this occurs.
Larger projects will often force you to do a whole lot of different tasks. To efficiently do this you need to think about what tasks will affect other tasks. Take for example the modelling of a set piece for an environment you are creating. If you haven’t started on the environment yet it is hard to know how that model should look because it is still unsure where in the scene it will be placed. Yes, you may already have fleshed out a vivid image of how it’s going to in your mind but ask yourself: Has your project-view ever been the exact same from start to finish?
After creating the terrain and scripting the entities I finally dedicate a small chapter to how I experienced working with the CryEngine SDK.
With the features of the one page design document in mind I started drawing out the terrain and the tracks. I wanted the terrain to be interesting so I made sure there would be water, bridges, tunnels, height differences and a minimum of three stations.
The layout is consciously convoluted because I wanted to artificially make trains run into each other often. Look for instance at the path of a train traveling in counter clockwise direction from the top station to the most bottom platform of the left station: it is forced to go through two other stations before being able to arrive at its destination.
What you may also have noticed from the first image in this chapter is that the terrain is at its highest at the back and from there it slopes down to the front just like many real model railways. This kind of height distribution makes sure the whole terrain is visible from one point.
Only after having made the terrain with this in mind did I realise that a digital model railway has a big advantage over the ones found in the non-digital planes: You can fly around the terrain and see it from all angles without needing to step on the terrain. You don’t need to take the huge and clumsy human body into account!
But, for me, it was too late to benefit; I had created a terrain that looked good from one side, but fell flat when viewed from others.
Image: The mountains in the background of the first image are scaled up and mirrored instances of the exact same terrain that is used in the foreground.
Because of the circular nature of train tracks and the ability to move the camera around freely, the terrain should have facilitated viewing from any direction. Not only from one specific direction or, like in an FPS, from close to the ground. When I found this out I did experiment with other terrains but I was too far into dressing the less ideal terrain to change it. I had to make do.
Image: Here you see an experiment with a terrain that would fit better with a DIGITAL model railway. The basin-like terrain can be viewed from 360 degrees and can be also be used as a scaled up model in the skybox to provide background scenery.
The final terrain was created through using a combination of Autodesk Maya, Autodesk Mudbox and World Machine. Later on in the project I got a lot more proficient with the awesomeness that is World Machine and made full use of its power, which rendered the other two modelling programs obsolete. As an example, the above image was, unlike the other terrain, solely created in World Machine.
Image: The process of creating the terrain. Initially a simple plane in Maya, eventually a lit and textured terrain in the CryEngine SDK.
At this point the form of the terrain was done and imported into the CryEngine Sandbox. What happened here is that I immediately wanted to do something new: I wanted to recreate an area of Dover, England, and decided the scene would be set at night. I did have a good reason for it to be night, but it lead me to make a common mistake that should have been easy to avoid:
I started too big and wanted too much. I should have started by making something that already has assets, and examples of being used in the editor: I should have started with creating a believable tropical island. That way I would not need to create new assets and I would have a ton of references/tutorials to fall back on when inevitable issues arise.
The reasoning behind the decision to make it dark was that I wanted to stick lights to the trains so that they would become focal points when snaking through the countryside. It worked. See for yourself at the big image at the top of this page. But it did cause a lot of headache. I ran into a problem that movies have run into a long time ago: night is too dark. I needed a way to make it seem like the night but still have enough visibility so that models would not be black and flat and the greens and whites of the terrain remained vivid. I used the 'day for night' technique: making the ambient light more blue and the sun pure white.
Image: Example 'day for night' technique.
However, this caused a lot of problems with models and rocks seeming out of place because of the unnatural lighting. It is possible to pull this off and make it look good, of course, but as someone without experience it isn’t the smartest thing to start with as it will take up a lot of time to get right.
Image: Assets seeming out of place because of the artificial lighting.
To keep the feel of it being a model railway system, I decided to take the less realistic approach and stylize the different areas. Every station has a unique theme and the island is divided in a couple of different zones.
What I found is that when designing levels, built with a terrain as a basis, stylistic is the way to go. Realistic terrain is almost never flat, however the creation of important elements like rivers, roads, trains, require flat areas to be correctly utilised. Instead of having a realistic terrain it is important that the gameplay areas are more layered or consist of plateaus to make it a lot more designer friendly. The use of plateaus is also friendlier to the player as the distinction of areas helps them subconsciously interpret the layout of the map and aids them in getting their bearings.
This is all I have to say about the terrain, let us continue with how I took on the challenge of creating the train system and learnt to incorporate Lua.
Scripting is one big chain of walking into problems and solving them. However I would like to explain my process and take the time to walk you through two of the biggest problems I walked into: rotating the train and getting the signals to work. I will also try and mention some tips that would have made my process a lot quicker. For actual code check out the related tutorial I wrote: Lua in CryEngine: Scripting a train
CryEngine provides a lot of tools that can help you with the creation of systems like the one I aimed to create. There are vehicles that can follow paths, AI that can be commanded around and there is the Flow Graph editor, a node based visual scripting tool. However, because of my high-level goal of learning to script with Lua, I decided to minimise using those tools and instead, start from scratch.
Image: CryEngine’s Flow Graph editor.
The very start was already one of the largest challenges and this has in my case often been a reason to lose confidence in a project. You will not only need to learn the scripting language, but also how to use it together with the engine.
Finding out how they are incorporated and work together is a question of finding the right resources and spending a lot of time playing around.
The most important place is going to be the scripting reference for both CryEngine and Lua. This documentation, however, assumes you already understand how to integrate the code, so what if you’ve never done it before?
Praise tutorials! Look for starter tutorials and learn them. As soon as you understand how it works (this is not copying the tutorial and getting it to work) you’ll be able to use the scripting references and get into more advanced stuff like making your own model railway system.
As I was doing everything from scratch I had to make the train move before doing anything else. This felt a bit like reinventing the wheel but when programming/scripting, extra understanding will help you out a lot in the long run.
The system that moves and rotates the train uses the next waypoint on the path of the train. Based on both the coordinates of the train and the next waypoint, rotation is calculated so that the front of the train will point at the next waypoint. This is calculated on the z-axis, so the train is able to turn corners, and on the y-axis so that the train can climb and descend slopes. The rotational values of the train are used to calculate in which direction the train needs to be translated. As soon as the train comes within a certain distance from the waypoint the script will move onto the next waypoint on the path.
The whole railway consists of a multitude of paths that each contain a vast number of waypoints. The more waypoints, the more accurate the train follows the path.
As soon as a train reaches the final waypoint of a path it will continue with the first waypoint of the next path. To find out what that trains next path will become, the train sends a request to the control centre. This request must contain the current destination of the train. Based on the destination the control centre will return an array of coordinates (the next path) that the train will need to get to its destination. Using the control centre makes sure trains only need to keep hold of their current path, minimising memory usage by eliminating the need for every train to hold all information.
Inputting all coordinates by hand is a huge task so I used a shortcut: I made the paths with the AIPath tool in CryEngine, exported them as text files and imported the coordinates from those files with Lua. This method is really fast and accurate, but the downside is that every time the path is changed the tracks will need to be re-exported.
To make sure I and my scripts had total control over the trains I set them up with different states. These states were: Start(), Stop(), Stop-autoRestart(time) and ChangeSpeed(speed). This way the signage and stations could easily command the trains and kept the code clear and modular. I could change one state without affecting the others and easily add new ones.
The signage was especially hard to get right. The system would seem to work but eventually somewhere two trains always ended up in a collision. With a lot of tweaking and some fundamental changes I got it to work perfectly with the following system:
Areas between two signals are their own section. When a train passes into a section the signal at the start of the section is set to red. As soon as it is set to red, the signal sets its child to orange, and the now orange signal sets its child to green. Then, when the train continues into the next section this is done again, constantly overwriting the signals. A signal can have multiple children for when tracks converge.
Trains will only be allowed into the next section when the signal is not set to red (which means there can only be one per section). Orange signals halve the speed of a train minimising full stops on the track.
The signals detect trains through collision boxes and, depending on the state of the signal, relay a command to the train.
When a train enters the collision area, the signal can send a command to the train so that if it is orange or red it will make it slow down/stop. When a train leaves the collision area it sets the signal to red to stop the next train from advancing. This way, no matter how many trains are on the rails waiting for each other, they will never have the chance collide. When using this system it is important to be aware of the fact that a train is not allowed to be longer than a section.
The wagons that follow a train calculate their own rotation and collect other data from the locomotive they are following. This means that they are lightweight and within the editor there is no need to create a link.
That, in a nutshell, is my digital model railway system. Nothing of this would have been possible if it wasn’t for:
- The CryDev Documentation
- The Lua Reference Manual
- Tutorials on the CryDev forums, YouTube and many other sources
- Friends I could talk to when I was stuck
Working with CryEngine SDK
Compared to the Hammer Editor the entire workflow is different when using the CryEngine SDK. Using custom assets is really easy and being able to fly through the map without compiling is a breath of fresh air. What did take getting used to, is the difference between placing every piece of geometry, like doorsteps and windows in Hammer, and simply placing an instance of a house on terrain in CryEngine.
When I think of them in regard to level design I find it really hard to compare the editors as this project was rather unorthodox and I don’t have anything to compare it with. What I can say, however, is that the ease of use and Lua integration have made this project a pleasure to work on and I look onto this editor fondly.
I am content with the results and am able to add a diverse range of new skills to my tool belt: not only do I know a lot more about creating terrain, I learnt Lua and working with a tool that has taught me a whole new way of building levels.
The terrain could use changes, and as I mentioned in detail above, there are a lot of things I would do differently next time.
I am glad to say that I was able to attain all of my goals. It is perhaps interesting to note that with a unique project like this, it is really important to think about the presentation in advance. How am I going to show this to others? Screenshots don’t show the scripting effort, and it isn’t something playable that can easily put up for download. How can I explain this project and present it on my portfolio? Let me know if I did a good job on that part.
Thanks for reading! Feel free to contact me for questions, comments and discussion.