Gamasutra is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Gamasutra: The Art & Business of Making Gamesspacer
View All     RSS
September 19, 2019
arrowPress Releases







If you enjoy reading this site, you might also want to check out these UBM Tech sites:


 

My experience with Unity in creating an arcade style game

by Martino Wullems on 07/26/16 06:25:00 pm   Featured Blogs

4 comments Share on Twitter    RSS

The following blog post, unless otherwise noted, was written by a member of Gamasutra’s community.
The thoughts and opinions expressed are those of the writer and not Gamasutra or its parent company.

 

My name is Martino, I run a one-man company called Eendhoorn Games and I have just released my first game SpiritSphere on steam;

http://store.steampowered.com/app/458170

A short description shamelessly pulled from the steam page;

SpiritSphere is a local multiplayer game that draws inspiration from classics like Zelda and Windjammers and mashes it together with the nostalgic feel of playing air hockey at your local arcade.

I am often approached on events regarding the development tools I use, and which hurdles I had to overcome, so I though I'd write it all down here!

About me
Age : 27
Residence: Haarlem, Netherlands
Prior experience: 7 years programming (3 years proffesionally)
3 years hobbiest pixel-art

A side note is that I have problems with my health, and cannot use a mouse. The entire game was created on a Windows Surface Pro 3. I can spend about 5 hours a day behind a pc, sometimes a bit more, sometimes less. 

I will mostly be talking about the tech, since I'm a programmer by trade. But I did do all the art as well so I will talk about how I managed to ship a game with programmer art later. The gameplay still needs to be adjusted after I have received feedback from the community, so I'll keep that part for the release!

Tech stuff
Engine: Unity3D
Language: C#

Why Unity3D?
I chose Unity for several reasons;

  • Prior experience, so no learning curve

  • Multi-platform

  • Fast development cycle

Unity might not be the wisest choice for a game with arcade gameplay, but I wanted to get a game out as fast as possible. I had some savings, but not much, so I simply did not have the luxury to write a game engine. Besides that, my math skills are quite poor so I tend to steer away from writing a custom engine.

The good

  • Getting stuff done

Unity's is great for rapid prototyping, no need to write your own physics engine, sprite animation or state-machines. It's all included!

  • The editor

Setting stuff up in the editor is super convenient, you can change all the values on the fly and generally have a good overview of what's going on. I always created a bunch of custom editors that I will discuss later.

The bad

Yes "the good" was pretty short, here's why;

- Closed sourced
Unity's source code is closed off, and what this is means is that if you want to do something differently than how Unity intended it you're gonna have a problem. I can split this problem up in several sections;

  • The physics

SpiritSphere has pretty arcady gameplay, it's pretty much air-hockey combined with an old action-RPG. Unity's 2D physics are based on box2D, and therefore way too realistic for a game like this.

Physics prediction

There is a singleplayer mode in the game, and the AI needs to know where the ball will end up in order to defend their goal. However, there is no built in way to simulate physics, or to look ahead.

I ended up guestimating where the ball would end up. And while it worked out, it felt very iffy and was all written based on assumptions on the physics, since I cannot take a peek inside how things are working.

Gif of ball trajectory and AI response

(ai is top player)
Overal it just felt like I was fighting against Unity's physics all the time, it's not a pleasant feeling.

Serialization

Unity's editor is generally great, but there is one gripe that is bothering me to no end.

In addition to gameobjects, you can add classes to the editor with the [System.Serializeable] attribute.

[System.Serializable]
public class Loot
{
    public UnityEngine.Object item;
    public Sprite thumbnail;
    public int probabillity = 1;
    public int quanity = 1;
    public int value = 1;
}

[System.Serializable]
public class LootTable
{
    private int maxProbabillity = -1;
    public bool debug = false;

    public List<Loot> possibleDrops;
    public List<int> lootProbabbillities = new List<int>();
}

This is a screenshot of one of the patches of grass in the forest stage, I can specify what kind of items it should drop in the editor, pretty awesome right?

serialization grass loot drops image

Now let's say I create a new class that wants to inherrit from Loot, let's call it SpecialLoot for now.

The grass object has a List<Loot>. As soon as I add a SpecialLoot instance into the array Unity just thinks; "SpecialLoot? What the heck is this, I'll just turn it into a regular Loot instance" and throws away all the data from SpecialLoot. Unity's editor does not support Inherritance, which is a real pain in the butt.

Tilegrid

If I were to go in the direction that Unity is pushing me, a tilegrid would simply be a collection of gameobjects with SpriteRenderers. But we all know this is a big no-no performance wise. I believe Unity is working on a tilegrid system, but until that is done I had to write my own.

The tilegrid is simply a square mesh with a lot of extra vertices to compose the tiles. By setting the UV coordinates on the vertices we can decide which tile to display

My tilesheet is 16 tiles wide, which is the amount of tiles I need to fit in all the corners for a specific tile type.Based on that, I can pick the correct UV coordinate based on the current tile

float indexY = Mathf.Floor( tile.ID / 16 );
float indexX = Mathf.Floor( tile.ID - (indexY * 16 ) ); //using 16x16 tilesheet

//calculate the size of one pixel
Texture2D texture = Application.isPlaying ? (Texture2D)renderer.material.mainTexture : (Texture2D)renderer.sharedMaterial.mainTexture ;

float textureScale = 1 / (texture.width / (float)parent.tileSize);
float pixelSize = 1f / texture.width;

float tileLeft = (indexX * textureScale);
float tileRight = ((indexX * textureScale ) + textureScale) - seamMargin;
float tileTop = (1f - (indexY * textureScale) ) - seamMargin;
float tileBottom = (1f - ( (indexY * textureScale) + textureScale ) );

uv[ (i * 4) + 0 ] = new Vector2(tileLeft, tileTop ); //0,0
uv[ (i * 4) + 1 ] = new Vector2(tileRight, tileTop ); //1, 0
uv[ (i * 4) + 2 ] = new Vector2(tileRight, tileBottom ); //1,1
uv[ (i * 4) + 3 ] = new Vector2(tileLeft, tileBottom ); //0,1

Works pretty well! To create the stages more easily I have added some custom editors to draw the tilegrid straight in the editor

I divided the tilegrid in chunks, so whenever a tile is changed I don't have to adjust the entire tilegrid, but just a small portion.


 

There is one small "but"; At certain camera positions there seams between the tiles become visible. I believe this has something to do with renderering inaccuracy when the camera's position is at a half pixel.

In order to fix this I need at a 1 pixel extrude around each tile. This would mean my tilesheet would no longer be power of 2, which just feels horrible to me. For now I will have to see how many users encounter the seams between the tile, otherwise I will have to re-do a whole lot of tilesheets.


Input

Do I need to mention this? Unity's input system is horrible, no way to customize the controls in-game. I had some reports from users that wanted to play with 1 person on the keyboard, and one on the gamepad. I would have to write up some super hacky fix for this, so I decided just to wait until Unity's new input system is out. In the meanwhile I just mapped player one and player two their keyboard input to seperate keysets.

Pixel-perfect

I managed to get the pixel art to look decent by setting the camera's orthographic size to (screenheight / 2 ) / pixelsToUnit. But when the camera moves it still looks kind of wacky. (camera is mostly static in this game fortunately)

I could use a rendertexture and render the entire screen in it's native resolution, and then scale up the texture. But this came with a whole bunch of other implications and in the end I just kind of gave up.

UI

Unity's UI system is okay, but like everything they do , it just feels half assed. They tried to make this input system where you can automatically scroll through the UI elements.

But it just feels plain horrible. Whenever I accidentally mouse-out from a UI element, the gamepad loses focus on the UI element as well.

Whenever you enable and disable an element, the selection get's a messed up and you end up with 2 items appearing like they are selected.

And I could not get some of the text to appear crisp, some there is a whole lot of blurry text in the game right now.

The only time I used a 3rd party asset was for localisation, it's called LeanLocalisation and it's pretty awesome. I wrote an extension so I can load in exported excel sheets and it saved me a ton of work.
 

State machines

Unity's state-machine is pretty nifty, you have a good amount of control under which condition a certain animation should start; 


It's funny how you can look at the state machine and see which features were cut or experimented with; I removed the abillity to attack during a dash, but decided it didn't feel right. Hence the red arrow between those states.

State-machine for other things that animation

Unity had stated, that you can also use state-machines for other things than animation. I don't think the functionality is quite there yet; The animation-controller is completly seperate from the gameobject, so it's not possible to reference any properties or components on the gameobject itself.

Fortunately I use my own messaging system, so I was able to get it to work using that;

When entering the "dashing" state, disable the movement component. When exiting, enable the movement component again. Pretty simple.

I still think it would be better if I was just able to reference the Dash Component directly, but oh well.


General tips and tricks

I think that was enough ranting about Unity.

If there is one thing I learned is that you just have to embrace it's quirk and try to work with it's (sometimes strange) workflow;


Cache often used components

Calling gameobject.transform is suprisingly heavy, since it's a getter. It's better to just cache it. Goes for pretty much all components that you need to access reguarly.

Animator.SetBool/SetInt etc is super heavy

You don't want to call those function every frame, for some reason Unity does not check if the value has changed, so you have to do it yourself

if ( dirtyX )
{
   anim.SetFloat( AnimConstants.directionX, direction.x );
}

Instead of static classic, use singletons

Unity prefers to have things attached to gameobjects, so it's best to listen.

GameObject projectile = ResourcePool.instance.Fetch( projectilePrefab );

 

    

public void Pool( GameObject obj, int maxSize = 20 )
    {
        if ( !pool.ContainsKey( obj.name ) )
        {
            pool.Add( obj.name, new List<GameObject>() );
        }

        if ( pool[ obj.name ].Count > 0 && maxSize != -1 && pool[ obj.name ].Count >= maxSize )
        {
            //Debug.Log( "can't fit " + obj.name + " in pool, destroying " );
            Destroy( obj );
            return;
        }

        if ( pool[ obj.name ].Contains( obj ) ) return;
        obj.SetActive( false );

#if UNITY_EDITOR
        obj.transform.SetParent( gameObject.transform );
#endif

    }

    public GameObject Fetch( GameObject ob )
    {
        if ( pool.ContainsKey( ob.name ) == false || pool[ ob.name ].Count < 1 )
        {
            GameObject clone = GameObject.Instantiate( ob ) as GameObject;
            clone.name = ob.name;
            return clone;
        }
        else
        {
            GameObject obj = pool[ ob.name ][ pool[ ob.name ].Count - 1 ];
            int instanceID = obj.GetInstanceID();
            pool[ ob.name ].Remove( obj );

            obj.SetActive( true );
#if UNITY_EDITOR
            obj.transform.SetParent( null );
#endif

            return obj;
        }
        return null;
    }

Go out of your way to build custom editors

Building the tilegrid editor saved me so much time, it might seem like a lot of work, but it saves you a whole lot of time in the end.

Will I use Unity again for my next project?

While I did just write a 10 page rant about Unity, it still is the best way to get a project up and running across a whole bunch of platforms. The short development cycle is important to me, since I cannot spend a whole work day behind the computer.

However, if I can afford it I'd like to try something else for my next project. (Monogame specifically). I can always turn back to unity if I want!

Pixel (programmer) art

Now I'm no artist, but I did do all the art for Spiritsphere and I wanted to give some tips to fellow programmers who cannot afford, or do not want to hire an artist.

Learn with constraints

The best tip I can give you is to constrain yourself, don't try too high of a resolution, start at 8x8 and move up from there.

Also, those millions of colors you can choose from can be intimidating, use a color palette! I used the NES palette myself, along with 3 colors per sprite limitation (with a few exceptions).

You can still make things look decent while keeping it simple

Marketing
I was fortunate enough to have someone help me with the marketing. This meant I could put my mind at ease the last 2 weeks before the release and focus on other stuff.
While the game has just come out, I do have some tips based on the experience with the press so far;

  • Don't be too generous with keys

Even with the press, you don't want to be too generous. I handed out some keys to reviewers, but the reviews/footage was so bad, it just gives the game a bad face. They were positive about the game, but the quality of the content they made was just plain horrible. So don't be afraid to be greedy, and take some time to look into the person requesting keys.

  • Don't be afraid to apply constraints

SpiritSphere is a local-multiplayer game, and even though this is mentioned in the first sentence of the press release, press still put out reviews and videos based on the singleplayer, which does not do the game justice at all. I'll be sure to note that multiplayer coverage is required for the full release.

That's about it! 

Long story short, Unity is great for getting out a game fast, but don't expect it to cooperate when you want to do something that it's not intended to do.



 


Related Jobs

innogames
innogames — Hamburg, Germany
[09.18.19]

Unity Game Developer - Warlords of Aternum
innogames
innogames — Hamburg, Germany
[09.18.19]

Senior Java Software Developer - New Mobile Game
Wargaming Mobile
Wargaming Mobile — Berlin, Germany
[09.18.19]

Senior Game Designer
Wargaming Mobile
Wargaming Mobile — Berlin, Germany
[09.18.19]

Technical Director





Loading Comments

loader image