Notifications
Article
How to make Space Invaders inside a Unity Editor
Updated 8 months ago
80
0
A fun way to abuse Unity Editor scripting
Last week I took some time to make a working (albeit rudimentary) version of Space Invaders inside a Unity Editor window. Why did I do this? Because what better way to learn new things than to make something fun but also completely useless. If you want to skip right to business you can checkout the code repository on GitHub here. Otherwise I’m going to try to explain the steps I took in relatively broad strokes over the course of this tutorial.

1. Opening a window

First things first, we’re going to create a new Editor window. To do that, create a new C# class, and add using UnityEditor; to the top so that we can access all the UnityEditor classes. Then have this new class inherit from EditorWindow.
Next we create a menu item in order to open our window. Create a new static function called ShowWindow and add Unity’s MenuItem attribute to it. The MenuItem attribute allows you to add an item to Unity’s menus in order to call any given static function. In our case we’ll put the menu item under “Window/Custom/Space Invaders” so our MenuItem attribute will look like this:
[MenuItem("Window/Custom/Space Invaders %#&0", false, 0)]
I’ve also added a shortcut to open our window (ctrl+alt+shift+1 or cmd+alt+shift+1) by adding the symbols %#&1 after the name of the menu item. To learn more about this attribute you can read the documentation here.
Lastly, we use GetWindow() to either create a new window, or find focus on one that’s already open somewhere in the editor. We have to specify the type of window we’re looking for, and whether we want to popup a floating window or a pane that’s docked in the editor. So at this point our file should look like this:
using UnityEditor; using UnityEngine; public class SpaceInvadersWindow : EditorWindow { [MenuItem("Window/Custom/Space Invaders %#&0", false, 0)] private static void ShowInvaders() { GetWindow<SpaceInvadersWindow>(true); } }
You can now open your blank editor window using the shortcut (ctrl+alt+shift+1 or cmd+alt+shift+1) or by navigating to the menu item under Window > Custom > Space Invaders.

2. Drawing in the window

In order to draw inside an editor window, we have to add an OnGUI() function to our class. As far as I understand it, this function gets called whenever the editor receives an event. Events can be button presses, mouse clicks, mouse movement, window size changes, layout changes, etc. This means that the editor window doesn’t update at 30 or 60 fps like our games do, but we’ll deal with that later. The most important thing right now is how are we going to draw our character. After investigating a bit I found the function GUI.DrawTexture(), which takes a Rect and a Texture, so let’s use that. So we add a Rect and Texture as member variables. Next we’ll add some fields in order to fill our variables. EditorGUILayout has built-in functions for displaying most types of properties, but unfortunately not Texture2D, so we use EditorGUILayout.ObjectField() and pass in typeof(Texture2D) as the object type. For the our rect we can simply use EditorGUILayout.RectField(). Now let’s go ahead and add GUI.DrawTexture() to the end of the function. At this point our class should look like this:
using UnityEditor; using UnityEngine; public class SpaceInvadersWindow : EditorWindow { private Texture m_PlayerTexture; private Rect m_PlayerPositionRect; [MenuItem("Window/Custom/Space Invaders %#&0", false, 0)] private static void ShowInvaders() { GetWindow<SpaceInvadersWindow>(true); } private void OnGUI() { m_PlayerTexture = EditorGUILayout.ObjectField("Player Texture", m_PlayerTexture, typeof(Texture2D), false) as Texture2D; m_PlayerPositionRect = EditorGUILayout.RectField("Player Position", m_PlayerPositionRect); if (m_PlayerTexture != null) { GUI.DrawTexture(m_PlayerPositionRect, m_PlayerTexture); } } }
And if we open our window and fill our properties, we can see it working:

3. Handling Input

The next most important step to playing our game is handling input. To get input in the editor, we need to look at the current event and decide how we want to use it. To do this we grab the current event from Event.current and inspect it to see what kind of event it was. We can use isKey to see if the event was a type of keypress, and then we can check keyCode to see which key was pressed. In our case we’ll use A and D to move left and right. Finally, it’s important to call Use() on the event to prevent the rest of editor from using an event we already handled. Now we can wrap this an UpdateGame() function and call that at the end of OnGUI(). So now our code should look like this:
private void OnGUI() { ... UpdateGame(); } private void UpdateGame() { var evt = Event.current; if (evt.isKey) { if (evt.keyCode == KeyCode.D) { m_PlayerPositionRect.x += 1; evt.Use(); } else if (evt.keyCode == KeyCode.A) { m_PlayerPositionRect.x -= 1; evt.Use(); } } }
It’s good to know that the EditorWindow class can also have an Update() method, like MonoBehaviours do. However, I haven’t found a way to get any keyboard input inside the EditorWindow version of Update() so I’m not using it.

4. Updating every frame

At this point our window only draws when the editor calls OnGUI() which is only when the Unity editor receives an event, as opposed to a game which will update at a consistent 30 or 60 frames per second. In order to fix this we’re going to call Repaint() on our editor window at the end of OnGUI() which creates a Repaint event, thus forcing the editor to call OnGUI() again on our window next frame. Now if you open the window and fill the properties, you can use A and D to move the player left and right.
private void OnGUI() { ... Repaint(); }

5. Polish

The rest of the code is simply repeating the same patterns and ideas we’ve already covered so I’m going to skip over it. The code is available in it’s entirety here if necessary. I will, however, cover some interesting APIs we can use to polish up the window. The first of which is EditorGUI.BeginChangeCheck() and EditorGUI.EndChangeCheck() which is an easy way to check if the user changed a value in the GUI. I used this to automatically fill the m_PlayerPositionRect when the user fills the m_PlayerTexture. To do this we call EditorGUI.BeginChangeCheck() before EditorGUILayout.ObjectField() and then check if EditorGUI.EndChangeCheck() returns true. If so, fill the m_PlayerPositionRect with default values. This looks like this:
private void OnGUI() { EditorGUI.BeginChangeCheck(); m_PlayerTexture = EditorGUILayout.ObjectField("Player Texture", m_PlayerTexture, typeof(Texture2D), false) as Texture2D; if (EditorGUI.EndChangeCheck() && m_PlayerTexture != null) { m_PlayerPositionRect.width = m_PlayerTexture.width; m_PlayerPositionRect.height = m_PlayerTexture.height; m_PlayerPositionRect.x = position.width / 2; m_PlayerPositionRect.y = position.height - m_PlayerPositionRect.height; } m_PlayerPositionRect = EditorGUILayout.RectField("Player Position", m_PlayerPositionRect); if (m_PlayerTexture != null) { GUI.DrawTexture(m_PlayerPositionRect, m_PlayerTexture); } UpdateGame(); Repaint(); }
The last thing we’ll do is fill our textures with default values. I did this using AssetDatabase.LoadAssetAtPath() and passing in the path to my Ship, Bullet and Enemy textures. I simply put this at the beginning of OnGUI() after checking if any of our textures are null, like so:
private void OnGUI() { if (m_PlayerTexture == null) { m_PlayerTexture = AssetDatabase.LoadAssetAtPath<Texture2D>("Assets/Editor/SpaceInvadersWindow/Ship.png"); m_PlayerPositionRect.width = m_PlayerTexture.width; m_PlayerPositionRect.height = m_PlayerTexture.height; } EditorGUI.BeginChangeCheck(); m_PlayerTexture = EditorGUILayout.ObjectField("Player Texture", m_PlayerTexture, typeof(Texture2D), false) as Texture2D; if (EditorGUI.EndChangeCheck() && m_PlayerTexture != null) { m_PlayerPositionRect.width = m_PlayerTexture.width; m_PlayerPositionRect.height = m_PlayerTexture.height; m_PlayerPositionRect.x = position.width / 2; m_PlayerPositionRect.y = position.height - m_PlayerPositionRect.height; } m_PlayerPositionRect = EditorGUILayout.RectField("Player Position", m_PlayerPositionRect); if (m_PlayerTexture != null) { GUI.DrawTexture(m_PlayerPositionRect, m_PlayerTexture); } UpdateGame(); Repaint(); }
At this point you have a good overview of everything that’s going on in my codebase, so you can either finish writing the game on your own, or checkout the code here to see how I did it. If anything needs further clarification please let me know and of course thanks for reading.
Bronson Zgeb
Game Developer - Programmer
2
Comments