Search Unity

Tips for making a large game in Unity

Discussion in 'Scripting' started by maleone0487, Mar 30, 2015.

  1. maleone0487

    maleone0487

    Joined:
    Feb 23, 2011
    Posts:
    59
    Hello everyone. My name is Mike Leone, and I currently work as the programmer for Lifespark Entertainment LLC. We recently completed our game Rack n Ruin which will be shipping tomorrow (March 31) on PS4 with more platforms coming in the future. Since I largely went through this process blind, I figured it would be helpful if I were to pass what on what I learned while the experience is still fresh in my memory. I’m sure most of this will be second nature to anyone who has shipped a game before, so this is more for programmers who haven’t done so but are considering it. Without saying too much more, here are a few tips for anyone making a Unity game for the first time:

    1. Localization, Localization, Localization. Try to implement your localization logic as early as possible, and always design menus with this in mind. German in particular presents a few challenges you wouldn’t otherwise expect.
    2. Do not Use Application.LoadLevel, instead use Application.LoadLevelAsync. The main reason for this is that the load will simply be faster, plus you’ll get a couple additional benefits that will become clear as you start producing builds for different platforms or when incorporating loading screens. Related to this, there are a number of functions unity can perform one way or another, and generally one of these will be more difficult to work with but be better in the long run, such as using serialized properties in the editor vs. editor.target in custom inspectors

    3. Assume no significant operation is instantaneous. Structure your saving and loading, level loading, initialization, and all other major events as if they will be started on one frame and completed on another. For example, do not use a pathfinding algorithm that performs the entirety of it’s calculations in a single frame unless you know this grid will be very small. The reason for this actually has nothing to do with console hardware, rather it has to do with how Unity executes your scripts. This may no longer be the case in Unity 5 (I haven’t used it yet) but up to 4.3 all game logic runs on a single thread, and there is no method I am aware of which will get around this issue. Plus, if that Pathfinding network contains a large number of nodes, you will see a noticeable dip in performance with an instantaneous function.

    4. Do not use shaders you don’t understand. I can not emphasize this enough. Again this has little to do with hardware specifics, and instead is relative to the platform. Each platform you develop for will inevitably have specific requirements for rendering, and you must be able to adjust the code in these shaders accordingly or at the very least find a backup. The one exception here are the scripts unity provides, such as image effects where Unity takes care of this for you.

    5. Only use plugins for which the source files are provided. The sprite solution we used only provided the .dll files and appropriate editor content, which lead to more than a few headaches throughout the game’s development, and even having to write separate but nearly identical methods for things such as game settings, Input and saving/loading.

    6. Assume that no editor utility or script you write will be used in the way you expect. Make sure that each utility you write can have multiple panels opened, be used with multiple objects selected, will save all changes properly, and won’t pop null refs when used in combination with other panels. This won’t have a great impact on the game itself, but being able to identify these issues will really help improve the workflow for the designers and artists.

    7. Corollary to item 5 - Hide things you don’t want anyone but you to see. If for example you have a massive prefab that holds your game’s UI camera, menus, etc, always create and manage this item yourself. Basically the point here is to know when to take control of something in the game and not allow anyone who isn’t writing the actual code to modify it. For this you’ll want to make use of subversion locks (or whatever system you are using) as well as permissions in the svn. Also, it is generally a good idea during any initialization period to do a quick scene search and make sure all objects were cleaned up properly.

    8. You can’t always rely on Unity’s search functions or events to return all objects, such as is the case with disabled objects. If you need to keep a list of enemies, or some other object, make a public static list and add objects to it as they are created. Also it would probably be a good idea to not use Unity’s events directly, but instead to implement your own that are informed by unity’s events. For example, in Unity 4.3 Disabling an object will actually raise it’s OnDestroy Event after the OnDisable event. There are a few other oddities you’ll come across like this depending on your game, but in general you should be creating your own events that you subscribe to. That way you can control explicitly when that event fires, and easily determine the order in which methods are called via an event. Personally I recommend using C# delegates for this, as they are quite easy to implement and can be made to work with most visual scripting systems.

    9. Don’t over engineer things. Maybe making a single system that performs a lot of little functions is cool, but it will probably break or behave unexpectedly. Instead, as frustrating as it may be, it is sometimes better to simply write the function that is needed rather than one that could perform the needed function and a number of others. Of course you want to be making your scripts as full featured as possible, but just because a utility could be abstracted to do some ancillary function doesn’t mean it should.

    10. Don’t show anything in the inspector or editor that isn’t currently relevant to the state of the object. For example our game allows players the ability to freeze enemies, but some enemies cannot be frozen. In order to avoid confusion, I wrapped the ice related fields in a BeginToggleGroup() block to show that when an enemy is immune to freezing these values are meaningless. Also, you should be writing custom editors for just about everything, and provide tooltips in the inspector when appropriate. As much as it may make sense to the one writing the code, variable names and 1-2 word identifiers are sometimes not enough in describing what is going on.

    11. If something isn’t working but you can’t figure out the reason, First try running that logic on various types of objects and hierarchies. For example, OnCollision events are propagated up the object’s hierarchy until Unity finds a rigidbody, though OnTrigger events are not. Next try adjusting settings on various components. For example, a moving trigger volume will only send OnTrigger events when the rigidbody attached to it has IsKinematic set to true. Also, when all else fails, try giving unity a restart and in some rare cases even a reinstall. Unity is a massive piece of software, and you will see bugs when using it that result from one of a thousand different things going wrong or something on your machine interfering with it.

    12. Abstract all initialization logic. Most samples you see have the initialization happening in the start method, which is fine for most simple things. However you’ll at some point have to write components that rely on one another, and as such you’ll need to explicitly control the initialization order. Personally the best solution I found is to do all the in-script initialization in Awake(), all the inter-script initialization in Start(), and inter-object initialization in the first UpdateTick. For example here is how our Health Component initialization works
      • Awake() - Set up min/max/current health
        • The only thing you can guarantee at this stage is that the Health Component object has been created.
      • Start() - Grab references to other components such as the motor and AI Handlers and subscribe to relevant events
        • At this point all the components on the object should be created (assuming this object is a prefab generated with an Instantiate call)
      • First Update() Tick - Tell the Game Manager to generate a health bar for this object
        • Finally this object has been created, as well as all attached scripts, and all other objects created since the last frame have gone through their initialization periods. It is not until this point that you can be certain all necessary objects are in the scene (assuming they were all created on the same frame)
    13. Sometimes it is useful to differentiate between editable and non editable objects. Our pathfinding system uses this idea, where in editor I create an object with 1 child per each AStar node, which is a ton of data to hold while in game (1200-1500 Gameobjects total). This is fine for working in the editor, plus you’ll be able to leverage the editor functions and won’t have to say reimplement transform movement on a Vector3 and selection for those points. Then, once the AStar network has been edited, I just let the designer click a button and generate a much lighter weight and faster AStar network that only stores the minimum amount of information for it to function. Additionally, if performance is a concern, creating separate supporting objects to simply hold editor information is quite useful is reducing the memory footprint of those objects at runtime.
    14. Put one person in charge of the physics system and layers. Assuming your game uses Unity’s physics, choose one person and put them in charge of editing the collision matrix, and determining what type of physics interactions take place. You’d be surprised how many raycasts, spherecasts, overlap spheres, etc your game will need to function properly, particularly when it comes to the AI. These processes can be incredibly expensive, and in each case you should pare down what objects the cast will interact with to as little as possible. For example, one enemy in our game has a dodge ability which requires 1 overlap sphere, and 2 spherecasts per instance of that enemy per frame.
    15. Use some kind of global object. This should be either an asset file you load with Resources/Asset bundle or, as in our case, an object that contains all references that would be useful to grab from any place. It is much easier to simply reference the Player object through global data than to have to search for it and store a reference on each object.

    I’m sure I have some other stuff I could pass along, but those are the major elements. Feel free to ask me any other questions you may have relative to finaling a game in Unity. The final thought I’ll leave you with is just to say that on a number of occasions you will have to make a weird change or encounter an odd bug because of something Unity does that isn’t immediately clear. This can be quite frustrating, but the fix is usually pretty simple, and the issue/solution is usually explained somewhere in the documentation.
     
    Last edited: Mar 30, 2015
    HaiderBassim04, eses, TonyLi and 3 others like this.
  2. Bankler

    Bankler

    Joined:
    Apr 10, 2012
    Posts:
    66
    Thanks a lot for this! Good stuff.
     
  3. jtsmith1287

    jtsmith1287

    Joined:
    Aug 3, 2014
    Posts:
    787
    Move your localization advise to the top. I'm sure a lot of people won't make it to all 15 points. Points 15 is the one major show stopping thing people are going to overlook. :)
     
  4. maleone0487

    maleone0487

    Joined:
    Feb 23, 2011
    Posts:
    59
    Good point, if my experience is any indication not planning for localization will come back to bite you
     
  5. Dinosaurs

    Dinosaurs

    Joined:
    Aug 11, 2013
    Posts:
    17
    Can you elaborate more on this? In the pathfinding example, it seems like you're implying that you would break up the search across multiple Update()'s, and I'm very curious to hear more detail about how you're doing that as the single-threaded nature of Unity is something I've butted heads with in the past and I'm curious to hear possible solutions.
     
  6. blizzy

    blizzy

    Joined:
    Apr 27, 2014
    Posts:
    775
    Can you elaborate more on the localization point? Specifically, since I'm a native German speaker, I'd like to know what problems you were facing with the German localization. (Having already done German -> English localization in multiple products myself.)

    Also, the TOP point on your list should be: Document, document, document everything! In writing! Don't underestimate the truck factor.

    Use multiple threads, or try to break up things so that not the whole work is done in a single frame, but will be spanned over multiple frames.
     
  7. maleone0487

    maleone0487

    Joined:
    Feb 23, 2011
    Posts:
    59
    Well I'll give an example of how our pathfinding system works. In each of our scenes, we have a maximum of 1500 nodes available at any given time, so you'll never have to do more than that many loops in a worst-case scenario. However, in order for the pathfinding calculation to work (just a basic AStar algorithm) we must modify some variables on each of the nodes to know the distance from the previous node, distance to the goal, and overall distance of the path. Therefore, if one actor in the scene is currently calculating a path no one else can perform their calculations until his work is finished.

    So what happens if two or more actors want to calculate paths on the same frame? Well one option is to make them wait their turn, which can make the AI seem stupid/nonfunctional, cause performance hitches, and generally nothing good comes of it. This may not be an issue if you don't have a lot of enemies in the game, though it does apply some constraints to the game design. Also an overlong pathfinding calculation can still create problems if you have other things in the game that hit your performance pretty hard.

    In our game, since the pathfinding network is pretty large, we needed an interim solution. For us we broke this into 2 parts:
    1. If the actor can see the player (spherecast to player), or they won't bump into anything for a few seconds just walk directly at the player.
    2. If a path must be calculated, only calculate a portion of it. After all, if the player is moving, they will likely be somewhere far away by the time the actor would have finished their path.
    So yes, this can mean breaking up your pathfinding logic to work across multiple updates, but more than that it is a design consideration. A path leading directly to the player on frame 50 will likely be a path leading to nothing on frame 500. So if you're going to calculate the entire path each time the actor needs a new one, you'll be forcing Unity to do a lot of unnecessary work.
     
  8. maleone0487

    maleone0487

    Joined:
    Feb 23, 2011
    Posts:
    59
    Mainly the issue here is the length of German words vs. English ones when you are translating in the opposite direction, as well as phrases being compounded into single words. For example we have the english phrase "Text Speed" (10 chars) localized in german is "Textgeschwindigkeit" (19 chars). Since most of our menus use 2 columns (left side labels, right side clickable elements), we couldn't quite get that word in there without the text spilling off the page, or squishing down the word to be almost illegible. So once the german localization came in, we had to go through each menu panel of the game and tease things apart, make elements smaller, or redesign the menu entirely. We also saw this issue a few times related to word wrapping in the conversations, text spilling off buttons, etc.

    Also, our sprite solution uses Bitmap fonts for text, so when we did the localization we also had to update the font definition with all the characters that you see in spanish, german, french and italian that don't appear in english words.
     
    Last edited: Apr 1, 2015
  9. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,536
    Cool! Just saw your email about the release via kickstarter - congrats!

    Actually some really good advice in your points, thanks for taking the time to pass on lessons learned.
     
  10. twobob

    twobob

    Joined:
    Jun 28, 2014
    Posts:
    2,058
    I would add one tiny point to this, and you already made it well, consider your platform but with reference to your likely unexpected costs. Some of the parts you mentioned as potential solutions (and some of the mechanics you ultimately employed) do have an inherent garbage overhead, especially if used improperly on tiny devices. So Garbage, understand it, watch for it, learn to use the profiler, learn to use the frame debugger (if you have this luxury for your platform remotely), basically use the tools that have been provided early enough to prevent having to use them in anger later ;)

    Further to your point about global objects http://forum.unity3d.com/threads/universe-game-managers-made-easy-free-released.283330/

    Great article, I learned things, many thanks.