-
Notifications
You must be signed in to change notification settings - Fork 3
Unity pointers
Just a couple observations I've had in my (very brief) experience with Unity. This guide alone is not sufficient for mastery (or even understanding) of the IDE.
The idea of ECS is that every single "thing" in your game can be described by its list of attributes. For example, the hero of the game can be described by a transform (position, rotation, scale), a sprite (some graphic), a rigid body (a physics description), a collider (the area that interacts with other objects in the game, which may or may not be related to the size of its sprite), and various scripts that enable user control and behaviors which have not been defined by the Unity IDE. Using this idea, Unity considers every "thing" a GameObject, and the only attribute a GameObject is required to have is a transform.
It may not seem useful to have GameObjects which only have transforms, but with the concept of a hierarchy for our collection of entities (GameObjects) in our scene, these "blank" objects become very powerful. A "hierarchy" just means that some entities can have children (and likewise some have parents), and that entities with a common parent share some association. This is useful for organization when the scene gets populated with hundreds of objects, and it's also useful when you want to tweak the values (particularly those of the transform) of some set of objects in tandem. For example, if a level has a series of ramps that all need to be moved down a little to work with the rest of the level, you can adjust the parent GameObject of those ramps instead of adjusting each ramp individually.
Unity already has functionality for common mechanics needed in games (e.g. rendering sprites and calculating physics); however, it cannot implement game-specific functions like player controls, spawning enemies, keeping an inventory, etc. We write scripts for these functions. Unity allows three languages for scripts: C#, JavaScript, and Boo (Python-esque) -- we will use C#, which is very similar to Java in terms of syntax. Scripts implement some abstract methods that are inherited from the parent class (MonoBehavior) and each method is called during some part of the game's lifecycle. Awake() is called when the scene loads, Update() is called before each frame is rendered, etc. Look at the scripting manual for more info. Once a script is written, it can be attached to a GameObject much like a Unity-created component.
A GameObject can have multiple scripts attached to it for multiple behaviors. Keep in mind, though, that each script should be implementing independent behaviors, i.e. scripts should not need to talk to each other. If scripts are dependent then we run into the race condition (i.e. not knowing which script will run first when they run in parallel).
There isn't too much algorithmic design needed for writing most scripts; oftentimes it's just pulling together different things from Unity's API. An interesting feature is that public variables will appear in the Inspector, where you can adjust values on the fly. This is especially useful for play-testing.
Like any IDE (Eclipse, Visual Studio), Unity has multiple windows on each side. They can be moved around, but for the purposes of this guide I'll assume they're in the default positions:
- Scene View - (center) a graphical rendering of the scene, consisting of all the objects in the Hierarchy. The camera is freely moving--you can control it with arrow keys, scroll wheel, alt-click (right and left)... there're a lot of options. Along the top are several buttons that modify the view. The "2D" button toggles between an orthographic (2D) and perspective (3D) view. When designing backgrounds or foregrounds, 3D view will be more useful.
- Game View - (center) on a tab next to scene view by default; when you run the game you will switch to this window automatically. Game view displays what the game will actually look like from the view of the main camera.
- Hierarchy - (left) described a bit above; everything in the scene view (and even things that are not visible) has a corresponding object that can be found in the hierarchy. The parent-child relationship between objects is often key for rapid level design, so make use of it when you can.
- Project - (bottom) details all the assets that you see in source control. I'll explain the structure of this folder below.
- Console - (bottom) on a tab next to Project by default; useful for debugging messages and error messages when scripts don't compile.
- Inspector - (right) when you click on a GameObject (in Hierarchy or in Scene View), its components will be displayed here, where you can adjust values or add new components. Alternatively, when you click on an asset in Project, its properties will be displayed here.
A short description of what's inside the "Assets" folder, in the Project window at the bottom. I stole this layout from the 2D Platformer Game on the Asset Store:
- Animation - we won't focus on this until later in project development, but Unity allows for some pretty sophisticated animation via finite state machines. The animations themselves have to be developed through an external application though.
- Audio - for sound effects and music. Not much more organization will be needed here since we will have relatively little sound.
- Fonts - for fonts, in the conventional font formats (.ttf I think?); we will pull free ones from online.
- Materials - usually used for 3D games to tile objects; not much use for this project, but we'll see.
- Physics Materials - contains descriptions of physical properties (i.e. how objects interact with other objects on collision). The only two properties in a material are bounciness and friction.
- Prefabs - useful for "saving" GameObjects, sometimes for later use. I'll describe it more below.
- Scenes - all the different scenes in our project, i.e. all the worlds/stages.
- Scripts - all the scripts, written in C#. As the number of scripts grows this folder will need more organization.
- Sprites - all the sprite art, with appropriate organization.
To elaborate a little more, prefabs are GameObjects that have been saved and can be reused. For example, if our environment uses lots of rocks, we would create an empty GameObject at the location we want our rock, add a Sprite Renderer component with the appropriate sprite, add a Polygon Collider 2D and resize/reshape the collider if needed, and possibly add a Rigidbody 2D if we want the hero to push it around. Once we do all that, we'd like to avoid repeating that process for the next rock. To do this, we drag our rock (from Hierarchy or Scene View) into the appropriate subfolder of the "Prefabs" folder in the Project window. That object, with all its attributes, will now be saved as a prefab (the name of that object in the hierarchy will turn blue to reflect this fact). Now to reuse this object, we drag the prefab from Project into either the Scene View or Hierarchy.
Sometimes it may be necessary to select an object, or a group of objects, from all the objects in the scene. For example, a script attached to a pickup (i.e. some object the player picks up) needs to know if the player, as opposed to some environmental object, has touched it. Unity provides two main ways of attaching information to an object in this respect: Tag and Layer. Both can be adjusted through the Inspector--if you click on an object, the top will show Tag and Layer properties (their values will appear alongside them). New values can be created, but they should be meaningful. Layer should be used for categories like background, foreground, collide-able objects, etc. (be wary that it's primary purpose is determining the order to render objects). Tag should be used for game-specific categories like spawn points, NPCs, pickups, etc. (it's likely you'll use this more).
Once the object has the appropriate information attached, it can be found through a script by GameObject.FindWithTag("TagValue") or... some function we'd have to write for layers (there's no easy way). The find operation is not quick, so cache the result when possible (i.e. try not to call it every frame).