Trending
Opinion: How will Project 2025 impact game developers?
The Heritage Foundation's manifesto for the possible next administration could do great harm to many, including large portions of the game development community.
In this blog post I'm going to talk about the process of developing a Lemmings clone in Unity. Covering topics for Pixel Perfect "Collisions", "Liquids" and dynamic level modifications.
In the end we will have something like this :D
Hello everybody,
I’m pretty sure I’m not alone when I was growing up I fired up the amiga and played Lemmings. Fast forward decades later and I found myself being, among other things, a game developer, while also maintaining a youtube channel with Unity tutorials.
Then one free afternoon I came across these two videos (part 1 , part 2) by Mike Dailly recreating Lemmings using Game Maker 2. That lit the nostalgia muscle and I had to do something about it. So I started making my own version in Unity using original assets (for obvious reasons).
Following, I’m going to talk about the process and how I did it. However, to keep things tight, I’m only going to cover the important parts. If you still feel that it’s not enough to go on, then the videos here cover the entire process, line by line, as it got developed, and best of all they are free to watch and right on Youtube.
You can also find a lot more tutorial courses to watch in our recently launched site. You can still use the site even if you are not a Patron. Furthermore, each week, a few lessons per course become available for everybody to watch. This one has all lessons public as to celebrate our site launch :D
You can also play the project here in WebGL. Expect bugs.
The challenges for the project were recreating both the feeling and the mechanics of Lemmings. That included but not limited to, having pixel perfect collision while multiple agents move around a level, which they can also modify as per ability.
From the start it was obvious that the level needs to be a grid where we can do edits. The problem was, how to render that grid with each separate node while still keeping peak performance.
So the solution eventually was to use a single texture for the entire level, where every pixel will be one node, while any changes on the nodes will actually be changes to the texture itself.
To do this, we need to know that when modifying a Texture from the disk Unity will do just that, modify the actual texture, not an instance of it. Therefore we have to manually make an instance of the texture. This is very simple to do with the following code :
textureInstance = Instantiate(levelTexture) as Texture2D;
However, we not only want to make an instance of the texture, we also want to set our nodes based on the color information we get from the texture. Thus we will create a small Node class such as :
public class Node { public int x; public int y; public bool isEmpty; }
We can later store a bit more info in this class but this will do for now. Then we can create a grid with this class from the following loop:
//maxX is the width of the texture, you can get it directly from Texture2D //maxY is the height for (int x = 0; x < maxX; x++) { for (int y = 0; y < maxY; y++) { //We setup a new node Node n = new Node(); n.x = x; n.y = y; //and then we set each address //here we read the current texture on a per pixel basis Color c = levelTexture.GetPixel(x, y); //then we actually set the color on the texture instance textureInstance.SetPixel(x, y, c); //here we actually create information from the texture, if it’s a completely transparent texture, the color of the pixel would have 0 on it’s alpha channel. Thus we can safely say it’s an empty node. n.isEmpty = (c.a == 0); //Here we do the same thing, but this time for the minimap. Any pixel that’s opaque, is rendered as green Color mC = minimapColor; if (n.isEmpty) mC.a = 0; miniMapInstance.SetPixel(x, y, mC); //and finally we assign the node to our grid grid[x, y] = n; } } //After the for loop, we also need to run .Apply() on our textures textureInstance.Apply(); miniMapInstance.Apply();
Note: It is not necessary to set the pixels for each texture Instance like this here, an instantiation before the loop would work as well but you can use this technique to affect the pixels and give them a different color from their normal one, for example where there is an opaque pixel, assign green instead of the normal color. This way we also make a minimap.
Note: The above for loop needs to run only once. So even with large textures (I was using a 1000x200 texture) the overhead would just be in the loading. Anytime we need to do changes on the map from now on, we will just be using the address we store on a node.
Now that we have our texture, we are going to render it. We add a gameobject with a SpriteRenderer (stored below as levelRenderer ) and convert our Texture2D to a sprite to assign it. We can do this with the following lines:
Rect rect = new Rect(0, 0, maxX, maxY); levelRenderer.sprite = Sprite.Create(textureInstance, rect, Vector2.zero,100,0, SpriteMeshType.FullRect);
You can do the same for the minimap but instead of a Sprite Renderer, I used an Image UI component
The Vector2.zero, is the pivot point, the 0,0 position is the bottom left. The 100 next to it it’s the pixel to point ratio, Unity’s default is 1 point per 100 pixels. This is also important to know when doing any calculations with world coordinates, for example, to find the world position of the node(5,6) we multiply both x and y with our ratio, thus (x *(1 / 100)). An alternative to this would be to set all our sprites in the import settings to have a 1:1 to ratio.
Finally, the sprite mesh type is important to be a FullRect. That is because Unity will optimize sprites making islands of opaque pixels. This is not a problem when removing pixels from your map but we also need to be adding pixels in empty areas. Assigning the type as FullRect will force Unity to keep the sprite as an entire rectangle the size of the original image.
The above image showcases the problem with non FullRect sprites.
With all the above we saw how to recreate a texture as our map.
I’m going to skip the process of making the animations for our units inside Unity in this part but if you are not sure how they can be created the process is covered in the videos.
So how do we do pixel perfect “collisions”?.
We already know on our grid where there are empty nodes and ground nodes. Let’s establish what is a node that we can walk on. The “rule” for our game would be that if a node is ground while its above node is air, the above node is a walkable node. As a result to that, we are moving from an empty node to another empty node, but if we reach a node that doesn’t have ground underneath, we are falling. Thus making a pathfinder for each unit is relative easy.
We only need to do the following and in this order (probably):
Check if our current node is null. If it’s true, we probably fell of the map.
Check if our curNode is an exit node, if it is, then this unit escaped (more on that later).
Check if the below node is empty as that means we are on the air, so we are falling. If we are falling for more than 4 frames (If the last 4 nodes we moved on, were all empty nodes), then switch to the falling animation, this eliminates changing animations when you’re just going downhill.
If below us we have ground, then we need to look forward. We first look forward, if there’s an empty node, we move there.
If forward is not empty, we start looking 4 nodes up till we find one that is empty.
If we can’t find an empty node, we just turn around and go the other way.
As you can see it’s really rudimentary pathfinding but for a game like Lemmings, it’s pretty much exactly what we need.
There are also a few more things that come into play and have to do with the pathfinding, for example the “Umbrella” ability that is used if we are falling. Or if we are currently using the “Digging” ability and we digged that far down there’s no ground below, etc.
That’s it for pathfinding. To actually move our Units, we just interpolate from one pixel to another. Alternatively, we could just add a certain amount of points in our transform’s position over time but with the interpolation we achieve two things. We limit the amount of pathfinding operations we are using instead of doing it every frame, that is because Units will only use the pathfinder when they have reached a pixel. Even if it’s a simple operation, this way when scaled up it’s going to have a lot more performance, thus increasing the amount of units that can actually move at the same time in our level.
Finally all units are controlled by a single manager that manually executes their Update(). Because Lemmings also had a “sped up” button, to recreate this we have a fake timeScale (which if is sped up, is at value 2 or 3) where it is passed on the units along with a scaled version of delta time. The units use the scaled deltaTime (instead of Time.deltaTime) to interpolate between each position and they use the timeScale to change the speed of their Animator, thus making the game appear that is in the fast mode, while still keeping Unity’s time scale at the normal speed.
Here’s where the fun stuff begin. We have our level but a major part in the Lemmings gameplay was to dynamically interact with the levels.
Breaking down what we need is to either remove or add a pixel. In both cases we use the below code:
textureInstance.SetPixel(x,y,c);
Where c = color. The only difference between adding and removing a pixel is the value of the alpha channel. Note that a node is considered empty if it’s alpha value is at 0.
However, as you remember from before, when we use .SetPixel() we also need to call .Apply() on the texture for it to actually be updated. We don’t need to do this every time we change a pixel because we can change multiple pixels on a frame. So we refrain from using .Apply() until the end of our frame. Thus at the end of our Update() loop we have a simple bool which when true the .Apply() is executed in both the textureInstance and our minimap:
if(applyTexture) { applyTexture = false; textureInstance.Apply(); miniMapInstance.Apply(); }
With the above system ready, it’s just a simple matter of identifying which pixels get affected and how. I’m only going to cover the high level logic for the abilities here but you can see the code in the videos. Following when mentioning if there’s a pixel or if there isn’t, relates to if the node is empty or node and unless you fall out of the map, there’s always going to be an actual pixel.
Walker
This is the base ability. Move along, there’s nothing to see here.
Stopper
STOP! Hammer time! Or just divert your Units the other way.
Umbrella
When a unit falls from a great height it dies but with this ability it Marry Poppins its way out of it. How this ability works is simple, it just checks if it is active. It also changes the falling speed on the pixel to pixel interpolation to make the relaxed falling effect.
Dig Forward
In the original game this was the “Basher”, it just digs a tunnel forward. Logic wise it overrides the pathfinder, so if there’s even a pixel forward, it keeps digging for a certain amount of pixels, then for each pixel forward it gets 8 pixels above and sends all that to be “removed”. The 8 pixels come from the height of our characters.
Dig Down
Similar to dig forward but this time going downwards. Instead of getting just the forward and up pixels, it takes 2-3 behind and 2-3 forward and of course, one row below.
Exploder
This is the basic suicide unit, it just explodes while leaving a hole in it’s place. The pixels that are removed are just in a defined radius around its position.
Builder
This is another classic ability, it builds a row of pixels diagonally up from where it’s looking. This is achieved by getting 4-5 pixels in front and upwards of the unit then sending them to be “added” while also affecting the properties for the nodes (changing them from empty to full). After that is done, it interpolates to the next diagonal position and repeats until it hits a wall or the amount of pixels that are allowed to be build ran out.
Filler
Another fun ability which is also a nice segway to the next chapter. It first appeared in Lemmings 2 and it’s basically a unit that drops “sand” pixels, which in turn they have a “liquid” ability. Always going downwards until they can find a spot to rest. More on this later.
Obviously there are a lot more abilities through the multiple Lemmings games but I believe (although I haven’t checked all of them) they would just be an abbreviation from these ones. The miner for example is a variation of the basher but instead of forward, it’s forward and down.
Marry Poppins to safety
A mix of a few abilities is showcased above
Or as I ended up calling them, fill nodes. This is another fun addition. The high level logic for this is to have dynamic nodes that also have their own “pathfinding”. Add a lot of them together and you’ll get a liquid effect. As showcased in the video, you can also make a snowing effect.
As for how it works, we have the following class:
public class FillNode { public int x; public int y; public int t; }
The x and y is the address of the node as you might have guessed. The t here is how many times the fill node was in a “dead end position”. You can play with the numbers here but I made it so that if it’s over 15 times, then the node has been set, thus turning it from a fill node to just a node.
Fill nodes don’t update every frame, as it would make them change position 60 times per second, which will make them move way to fast. Instead we have a universal timer just for them. I personally used an update of 0.05 seconds but feel free to experiment with this to make different liquid effects.
So when they do get updated, their “pathfinding” looks like this:
Check if the node below actually exists, if it does not, then this fill node has fallen off the map
If it exists, then check if it’s empty. If it is indeed empty, clear the pixel we are currently in, while adding on the pixel(node) below. Thus we keep moving downwards.
However if it’s not empty, check forward and down. If that’s empty we move there.
If forward and down is full, then we move backwards and down.
If we are in the position that there’s nowhere to go, we add on the t of our fill node.
If the t is greater than the amount we have set, then we remove the fill node and leave the pixel full.
The t here does three things. First it makes sure we don’t end up with a huge list of fill nodes by removing the ones that are not moving. Second, it makes the nodes avoid getting stuck to each other when falling down. Third, if a node is on a dead end but hasn’t set down yet, a unit that is digging below it will actually make the fill node keep falling on the next update thus making a really nice dynamic effect.
With all the above in mind, you might think that this would give a hit in the performance but that is not the case here. As showcased in the videos too, I tested it with huge amounts of numbers, more than 10000 fill nodes allowed. Although there wasn’t a chance that all 10000 will be “live” at the same time, it made for a really nice effect as showcased below.
With this additional system. The above Filler ability only needs a spawn position and it will create a few fill nodes over time and let them take their course.
You might have guessed this was coming but basically what we were doing most of this time was changing colors on pixels. Thus the next logical step for it was to make a level editor, which is of course a pixel painter. I don’t believe there’s any reason to dwell deep into the level editor since it’s only using the exact same functions as the ones described above but targeted on the mouse position rather than a unit’s. Of course if you want to see the full code, you can do so in the videos.
But, I want to say a few more words about the serialization, thus…
As showcased in the video, there are a few couple ways of doing it. You don’t even have to use the level editor to make levels. We can load a .png image right from the disk and use that for a level. The systems we have in place open a lot of doors. However, we still need to make a game out of a texture, thus we have the need to also save a spawn position and an exit position (let’s call these two events from now on). Sequels to the original Lemmings had more than one of both events but in this one we will just focus on one each, the base for the logic would be the same though.
There are of course a lot of ways to do this too, from making two textures per level, one with the actual level and one with color coded pixels of where the events would be, but this will make it so that players would actually be able to modify the level events, among other things, externally. Not necessarily a bad thing but it might not be what you want, for example you might have some campaign levels.
What I ended up doing then, was to serialize everything a level needs in a single file. The only thing I’m going to note here, is that of course you can’t serialize a Texture2D directly but you can turn it into a byte array by encoding it. Unity made our life easier here because we can just do:
byte[] levelTexture = textureInstance.EncodeToPng();
Although a level editor is a really nice touch for a game, most likely it would be annoying to start every time from scratch. As mentioned before, we already showcased how to load a .png from a file in the videos but I knew I wanted this to run on a WebGL as well without opening a can of worms.
The solution to this, is to let the users paste a link in the Level Editor and then load it as the texture instance. Afterwards it is only a matter of setting the events in the level and saving.
How do we do this then? Unity has the WWW class, so making a coroutine to load the texture like this:
IEnumerator LoadTextureFromWWW(string url) { WWW www = new WWW(url); yield return www; //when the www has loaded, check if there’s a texture if(www.texture == null) { //we dont have a valid link :( } else { //we have a texture thus textureInstance = www.texture; } }
Note: Although the above code is correct, it is still pseudo code because there’s a few extra lines in the actual code that have to do with the UI and re initializing the level editor etc. I’m not adding them here so we can focus on what’s important. Of course you’ll find everything in the videos.
The above works like a charm on Desktop builds, since you can also paste the user’s clipboard in Unity’s own input fields. However…
[image]
...in WebGL it’s not allowed. The work around for this is to include a small chunk of javascript in the index of the WebGL build. Something along these lines:
//right beneath this line (it’s already in place in the WebGL build) var gameInstance = UnityLoader.instantiate("gameContainer", "Build/lennys.json"); //add function GetUserInput(){ var inp = prompt("link"); gameInstance.SendMessage("GameManager","ReceiveLink",inp); }
This will make the browser pop up a window with a textbox where the user can paste a link. Clicking ok, the script is going to send a message searching for the “GameManager” game object and for the function “ReceiveLink” which has a string inp on it’s signature. The function itself looks like this :
public void ReceiveLink(string url) { linkField.text = url; }
linkField here is an InputField UI element. Nothing really special.
A few things to note:
Even though we have a script called GameManager, the actual javascript looks for a game object that’s called that. Not! For the class. The function we are calling is situated in the UIManager class (which is on the same gameobject as well).
The texture isn’t loaded until the player presses load texture in the game UI
To actually run the JavaScript function you need to include this when clicking the button to load a url:
Application.ExternalEval("GetUserInput()");
We also have one more limitation in WebGL. Yeap you guessed it, we can’t save a user’s level without opening again a can of worms. So what do we do about this then? Simple, identify the MVP for our project. As this is an example project, it would be highly unlikely that somebody is going to invest in making their own Mona Lisa of levels and with the ability to just load any image from the web it falls under the case of, using technical terms, “Meh, who cares...” .
We still want our users to be able to play their levels though, even if they loaded an image or if they painted it. So we will save them “on the fly”. Meaning that any levels made will be lost on a new session. Not really a big deal when you can load images online. First we will detect if we are on a WebGL platform, this is done by:
if (Application.platform == RuntimePlatform.WebGLPlayer) isWebGl = true;
Note: that this is mostly for the purposes of the tutorial, in reality i could just turn everything to WebGL since I never intent to make builds for Desktops or other platforms. So instead of saving files locally, in WebGL we will store them in memory.
This was a fun project to do. It was made in my free time at the spawn of 1-2 weeks but the actual working time is about the same length as you see in the videos, not counting the amount of research and making the “art” and “animations” for the Units. Of course there’s going to be bugs in it.
You will also notice that apart from a few API calls, this is mostly basic coding.
The pixel perfect approach showcased here is also a launching point for a few more games from that era. I’m not going to spoil it for you here though but be sure to follow what I’m doing and of course support it if you like it.
Read more about:
BlogsYou May Also Like