PointlessMan
Have you ever wanted to play Pac-Man in a Unity3D Editor window? No, me neither!
Yet I needed to write this pointless satirical plugin because the Unity3D editor scripting “framework” relies on an Update based loop. I think it is pointless for the Unity3D editor itself and it should have a nice event based system. So, enjoy playing PointlessMan straight in your editor!
Rationale
The idea for PointlessMan grew out of a discussion I had with a colleague. He was rather new to Unity3D and he wanted to know if he could make some component he wrote more easy to work with. I told him he should look into custom inspectors, editor windows or maybe property drawers. I also told him the API to get this going is a real mess.
He gave these API’s a look and agreed with me. His motivation to write some custom editor support for his component disappeared as quickly as it appeared. However, I couldn’t ignore this chance to introduce him into writing custom editors because it’s a great skill to have as a Unity3D dev.
So I challenged him to write a very simple game, like a tic-tac-toe game in an editor window with a Test Driven Development (TDD) process, since he wanted to learn TDD as well. But unfornutately he didn’t really want to spend his free time on such thing and never made it.
But this idea stuck with me and as I started thinking about it. This seemed like a fun little project to do at home for me as well. Yet, a tic-tac-toe game was not going to cut it since it’s just far too boring. A Pac-Man clone however, seemed like for fun, plus, it has real-time updates which makes it more difficult. I’ve written my fair share of Pac-Man clones over the years, in multiple languages (it feels more like a code-kata at this point), so I’m familiar with the challenges it brings.
DevLog
The text below is copied from an earlier blog.
Some time ago I was having a discussion at work with a new coworker, more like a rant to be honest, about how un-intuitive the Unity3D editor (plugin) API is. He wanted to extend some existing plugins we wrote with some localization, but he had never wrote plugins before. The API consists of multiple static classes that do not seem to hold any reference to each other. Also you can use the old GUI scripting API in the editor. So yeah, you can use the old GUI API we had pre-unity 4.0 (for run-time) for writing editor plugins. And then to top it all off we were discussing the fact that even the Unity3D editor UI relies on a frame based system instead of a nice event-based system. So this means, that even the editor itself, runs on frames per second (FPS).
So as a suggestion for a little project for him to do at home, was to write a small plugin where you can play tic-tac-toe in an editor window, and since he wanted to learn about Unit-Testing too, do it in a TDD manner. We laughed it off, and of course he did not make any such project in his own time, sadly enough.
But this little project started gnawing on my conscious and about a month ago I wrote a small unit-test to check whether I could render simple sprites/textures in an editor window based on a rectangle I define. And of course, I made the test pass and now… I needed to write this plugin. But I wanted to take it up a level slightly since I did not feel like writing a tic-tac-toe game.
Insert: PointlessMan
So, I chose to write a Pac-Man Clone called PointlessMan, and here it is. I had written a Pac-Man clone before (I think multiple.. who hasn’t?) so I knew about my time investment and the end result. Another reason I chose to write a Pac-Man clone is that it relies on FPS more heavily than tic-tac-toe. You really need those moving sprites to get it to work properly, and tic-tac-toe does not have that.
In this small blog I want to write about the different API’s I used to finish this project and maybe shed some light on the confusing Editor API.
Gui API’s…
The Unity3D editor allows you to use and combine multiple API’s to write and design your editor plugins. Let’s quickly summarize them:
Api Class / NameSpace | Description |
GUI | The OG of Gui scripting as of pre-unity 4.0 (pre UGUI). Does somewhat the same as EditorGui, except you can use it at run-time as well |
EditorGui | API for rendering fields of any kinds that Unity3D supports. |
EditorGuiLayout | Same as EditorGUI, but then with automatic layout applied. |
EditorUtility | Some utility functions you can use in your plugins. |
EditorWindow | Base class for editor windows, that also has some useful utility functions like showing notifications. |
Editor | Base class for any editor window or custom inspector where you can use Unity’s new UIElement API. Here you can use AXML and design your windows in more like a CSS style manner. See the developer guide here. The fun part here is that you can also use the UIElement + AXML API for run-time UI as well. |
There are some more classes for Editor Gui related stuff but I think I have listed enough, and made my point clear. There are many classes that look like they do the exact same thing. Now why are there so many classes? Well my theory is that this is just due to evolution of the Unity3D editor and they do not want to deprecate old editor scripting API’s because it will break many, many… editor plugins.
Except, these classes are not marked as deprecated and there is no real “definitive” guide on editor scripting anywhere on the Unity3D website. So people are doomed to keep using the old API’s for their plugins, like I did with PointlessMan. Now, I know we should probably all use the UIElement + AXML style for writing editor plugins since it is their latest generation of editor scripting utilities, but I never really got into this due to lack of incentive. We have 1 in-house written plugin that uses this API but it is a real pain in the ass to change anything in the plugin. It seems to be too complex and fragile for writing custom editor windows. This could also simply be due to the problem we are trying to solve with it, but yeah… Maybe, just maybe, I will write my next plugin using this API just to familiarize myself with it some more.
GUI
So my first Unit-Test involved a simple question; How do I render custom sprites or textures on a specific position in an editor window. Also, I want to be able to control the dimensions. I would expect there is a function in the EditorGui static class, but no there is not. I needed to use the old GUI class for rendering a Texture2D given a Rect. I’ve also searched the Unity3D forums for another solution and some people were suggesting to render it as a Button, and simply override the GuiStyle to not show any active, hover or clicked state… but this felt like cheating. So I went with the classic GUI class for rendering my image. This also complies with the second requirement I had, controlling it’s dimensions. This finished my base-class for any simple image that just needed to be rendered, like cells of the maze.
OnGUI Loop
To make an image support “animation”, changing sprites based on some interval, I needed to tap into the OnGUI or the Update loop of the Editor window. Which is easy, you simply implement the function and it’s done (I could also have used the EditorApplication.update delegate, but I needed to render it in a window anyway, so I had access to the OnGUI or Update loop). What is slightly more difficult however, is getting the correct delta-time between frames… in an editor window of course. I could not get this to work properly / reliably so I ended up calculating the delta time myself with some basic DateTime objects. So this feels like cheating a bit, and I can probably fix it but maybe some other time..
Moving through the maze
The next thing I needed was the maze and since I could already draw images on the screen I simply needed to run them all through a nested for-loop and I could draw my 28×36 grid (the classic Pac-Man level has 28×36 cells). Next I needed to create some start position for the player which was also done in a couple of minutes. I made it so that you can put the player in any position you want. The player will simply look for an id and instantiate himself at the position of the grid. From here on I could draft up some way of moving the player. Moving the player went through a couple of iterations. The first one only moved the player OnButtonDown which led to me tapping the arrow/wasd keys but that did not quite feel right. So I searched for some place to play Pac-Man online and quickly discovered that in these games, pacman always moves automatically, and you simply adjust his direction. The only way to stop him is to run pacman into a wall. So I changed the movement and it felt much smoother already.
It was also quite easy to check for traversable and non-traversable tiles in the grid. I could just use the players cell’s x and y position on the grid and inspect it’s neighbours, which are always only 4 since pacman cannot move diagonally. I’ve written this part so many times, plus I remembered my graduation project from long time ago where I implemented A-Star pathfinding so this was common ground.
Ghosts and pathfinding
For putting the ghosts in the level I took a similar approach as for the player; they just have some id and they will spawn on that specific location in the grid. As I said, I’ve implemented some sort of A-Star pathfinding algorithm many times before in other pacman clones, or 2D games. This time, I chose a more basic approach, just a simple breadth first search. I thought A-Star would be a bit too much for this small project, and performance seemed good for now. So I just went with a breadth first search instead.
What was more difficult was finding out how the different ghosts work. There are some really generalized descriptions on the internet, without any specifics in regard to their algorithm. The descriptions I used were the ones from wikipedia, why not..?
Blinky(red ghost) gives direct chase to Pac-Man, Pinky(pink ghost) and Inky(cyan ghost try to position themselves in front of Pac-Man, usually by cornering him, and Clyde(orange ghost) will switch between chasing Pac-Man and fleeing from him.
So I made some very, very basic versions of this and called it a day.
Pellets, Energizers, fruits and eating ghosts
My quest for finding the specifics on how scoring used to work in the original Pac-Man game was also quite lengthy and difficult. There are so many different descriptions floating around the internet for many different versions of the game. The only real consensus I could find is the default number of score on each pellet, which is 10, energizers which is 50, fruit starts with cherry for 100 points and ends with the key for 5000 points. Eating the ghosts will reward you with 200 points, and if eaten in succession will double per ghost eaten to a maximum of 1600 points. Reaching 10000 or 100000 points, rewards you with 2 extra lives.
Now there are descriptions about the fruits being multipliers for score and different orderings of fruits. I could not find anything about this, specifically mentioning the classic Pac-Man game so I did not implement it.
Creating the code for eating the pellets was pretty easy, I just made traversable tiles spawn the pellets and was done. I gave the powerpellets (energizers) a specific id and they were also done. I noticed that the powerpellets needed to blink so I made them derive from the UpdatableImage class. The fruit always spawns on the same spot in the maze, so I also needed some id to mark this. Then spawning the fruits is based on the amount of pellets eaten. There are two chances per level to spawn fruits, one after eating 64 pellets in succession, without dying, two when you have only 66 pellets left in the maze. Which fruit spawns apparently depending on what level you are in. Fruits start to spawn after one level and then specific levels, or ranges of levels spawn specific fruits, until reaching the key. Then only the key spawns for the rest of the game.
Next I needed to be able to make the ghosts act scared, and allow for PointlessMan to eat them while they were in this state. So I conjured up a very basic gamecontroller that would track the position of the ghosts and the player. The controller will compare the ghosts and player positions when any of them made a successful move. This way I could easily detect if their position was equal, and PointlessMan would either, loose the game, when ghosts were not scared, or eat them if they were.
When PointlessMan eats an energizer the controller will simply call a function on the ghosts called “ActScared” forcing them to change their pathfinding algorithm, sprites and allow them to be eaten. When PointlessMan eats them they find their way back to their starting position and reward the player his points.
Teleporting
Teleporting seemed like an easy requirement as well. I simply needed to know if PointlessMan was on any of the four edges of the grid, and check the opposite of the grid for a traversable tile. If there is one, teleporting is enabled. So, I added this to the ghost and player pathfinding logic. And… All the tests broke since now my test mazes we full of teleporters. So I needed to change the old mazes to not have any teleporting cells and they were fixed. Then I wrote some simple tests for teleporting and I got another requirement done.
Audio
Playing audio in the Unity3D editor seemed like an easy to do task. Unfortunately, the Unity3D editor does not provide any API to play audio in the editor, which is pretty strange… but yeah… Luckily I found some decompiled sources of the Unity3D editor itself on GitHub, however, even at the time of writing, these source files are deleted already. Nonetheless, I was able to call these functions thanks to some simple reflection magic and I wrote a simple EditorAudioSource.
So now I was able to play audio in the editor using Unity’s Utility class. I could probably also have solved it by using some native .NET audio player or something but I did not want to pull in any dependencies and worry about the different audio file formats. Unity does all this, so let it worry about that.
Pulling it all together
Now I had all the requirements and gameplay aspects done. But I tested it all using simple unit-tests and I had no actual maze yet. So I started on a simple InMemoryMaze, which is just some static class that spits out a Maze object based on some string input. This string is just a simple, multi-line, text file with cell id’s (numbers) separated by comma’s.
I started designing the maze, and oh boy, this was the most boring thing I have done in quite a while… This process was so slow and in hindsight I could probably have written some level editor and create the maze that way. Plus I would have had a nice editor now as well, but yeah, choices… I made the wrong one here haha.
After a couple of hours, spread across multiple days.. I was finally done with my maze and I could play PointlessMan in my editor window.
There was only one thing left to do and that was to support custom maze layouts by parsing some .txt file. So I added a simple upload editor window and allowed the game window to parse it. This way, people can create more pointless mazes themselves.
Conclusion
Writing this pointless plugin was quite fun, and sad at the same time. I still think writing editor plugins is a horrible experience. There are too many API’s available that achieve the exact same thing. Plus, not being able to play audio in the editor was astonishing to me. Unity should really add some default player for this. However, it was quite fun to read about what these API’s do, I never actually read them so I gained some more knowledge on that, which will probably come in handy some time.
Let’s just hope, the next iteration of Editor Scripting, provides better experience.