Game Object Architecture Question

This is a very general, high-level question about how to architect my game object collections.

SCENARIO:
I have an ocean map with numerous ship sprites on it that the user can maneuver around the map.
Note: There will also be an enemy player controlling their own ships/sprites on the map but that’s not pertinent to my question.

So, my game needs to keep track of a variable number of ship “objects” for the player.
Up to now, I have architected my game tests using an array list called “ShipList” that holds a custom C# “ship” class.
The “ship” class contains many properties including a pointer to the 2D Unity sprite that is the visible representation of the ship on the map.

QUESTION (finally):
Do you think it would be better to architect it so that my array list holds Unity GameObjects with my custom “ship” class attached to the GO as a script?

I’m open to any insights (pros/cons) that people care to share.
Thanks for reading.
– Paul

public class Ship
{
    public GameObject shipInstance;
}

or

public class Ship : MonoBehaviour
{ }

From a collection point of view, they pretty much do the same thing. The question here is, does your class “Ship” has a reason to exist as a MonoBehaviour on your GameObject? Does it needs to receive any of the internal method invocation (Update, Start, Awake, etc)? If you were not using anything from MonoBehaviour, for clarity, I wouldn’t derive from it.

Striker,
Thanks for the reply.
I don’t think I was entirely clear in my explanation of the 2nd case so
here’s the two options I was thinking about initially (in pseudo-code):

CASE #1:
ArrayList[ ] shipList of ShipClass; <=== inside a public scene manager script

public class ShipClass
{
public Sprite shipSprite;
string name;
float maxSpeed;

}

==== OR ====

CASE #2:
ArrayList[ ] goList of GameObject <=== inside a public scene manager script

public class Ship : MonoBehaviour <=== attached as a component to each instance of “objectList”.
{
string name;
float maxSpeed;

}

For case #1, I call most “ship” operations like: shipList[instance].selected = true
and that code would display another sprite as a “highlight” to show the "ship is selected (for example).
There would be no “Update()” event or any of the std behavior events.
The handling of input controls would reside in a game-level script that would keep track of the “selected”
ship and change its velocity based on inputs.

For case #2, the “ship” operations would be called via the script component on the goList[instance].
Making the ship selected could a direct call against the goList[instance] to display the “highlight” sprite".
The script component on each “ship” would have “Update()” events and would move the ship ONLY if
its “highlight” sprite was visible.

I’m not sure if that is any more clear than my first explanation.
I apologize for a lack of clarity. I’m still getting used to Unity’s use of GameObject/component hierarchies
and I’m just not sure of the ins-and-outs of the custom class array versus a gameobject array.
Perhaps I just need to try rewriting a portion of my scene using gamobject arrays and see which leads to cleaner code.

– Paul

In your example 2, why would you do

(Let’s not used ArrayList, shall we?)

GameObject[] ships;

over

Ship[] ships;

Why would you store GameObject over a MonoBehaviour? Just so you know, a MonoBehaviour always has a reference towards it’s GameObject owner.

ships[10].gameObject.transform.position = Vector3.zero;

Frankly, if you’re to control a GameObject, you’re better using a MonoBehaviour, and keeping a list of that.

Striker,

Thanks for your time with this.
I prefer ArrayList because the number of ships changes dynamically during the game and it is very easy to add/remove items from an ArrayList.

I am new to Unity so pardon my ignorance during this discussion.
That being said …

I’m not even sure what to ask about your idea of storing a list of MonoBehaviour (MB).
From my minor knowledge and incomplete study, a MB is a base class for scripts.

Would a list of MBs be a list of scripts (i.e. list of class objects)?

If a MB has a reference to its “GameObject owner” why is that better than a list of GameObjects (GO) that can have a reference to its attached scripts?

=======================================================================
The model I have in my head (and may be totally messed up) for a list of GOs is something like this:

GO[ ] ships ==> script: shipHandler (added component)
±- sprite: shipSprite (child object of GO; visual representation for the ship)
±- sprite: highlightSprite (child object of shipSprite so it moves with it; displayed when ship is selected)
±- 3D-Text: shipLabel (child object of shipSprite so it moves with it; used to show info about ship)

There would be other components; rigidbody2D, colliders, etc.
There would be other child objects of the shipSprite but you get the idea.

=======================================================================
My other model is like this:

shipClass[ ] ships
±- shipSprite (property of shipClass)
±- sprite: highlightSprite (child object of shipSprite so it moves with it; displayed when ship is selected)
±- 3D-Text: shipLabel (child object of shipSprite so it moves with it; used to show info about ship)

=======================================================================
What would the model look like for a list of MBs?

– Paul

Don’t use ArrayList… use List<>. The generic allow you to stored typed object, which means it saves you from casting them around to the right type.

That being said, you would store a list of Ship.

public class Ship : MonoBehaviour { }

public List<Ship> ships = new List<Ship>();

It’s easier to store the instances of the script, over the instances of the GameObject, because to get the script on the GameObject, you would need to do GameObject.GetComponent(), which is a search in a list of all the component in the GameObject. Let’s say you have 100 GameObjects with 10 Components each, if you have to get all the ships around, it’s like searching a list of 1000 items.

How, or what you store in your code has no impact (or little) on how your GameObject would be built. In the end, you will always need some GameObject with some components on them. However, what you reference in your code is up to you.

Striker,

Yep, I just read up on it and a list would be better. I will definitely implement that in ALL my future code instead of ArrayLists.
Thanks.

I have some questions regarding your last idea of storing “Ship : MonoBehaviour” in the list.

What is the advantage of storing “MonoBehaviour” scripts in the list as opposed to a std non-MonoBehaviour C# classes?

The “MonoBehaviour” scripts won’t receive any Unity events like Update() because they are not attached to any GameObject, or am I missing a step (or two, or three)?

Perhaps this goes back to your earlier statement, [quote]
Just so you know, a MonoBehaviour always has a reference towards it’s GameObject owner.
[/quote]That confused me. Was my “Ship” script supposed to be a component on a game object?
Were you suggesting that, I would create an instance of a game object, get a reference to the Ship script at that time and store it in the list?

Thanks again for taking the time for this discussion.

– Paul

Ah! I think I see what you’re missing. A MonoBehaviour is a bit special. You cannot create one as a free-floating object. You can ONLY create a class deriving from MonoBehaviour in a GameObject. (MonoBehaviour derive from Component)

Your “real” ship will need to be a GameObject, to be able to hold visual, sprite, FX, sound, position and orientation in space and so on. And since your “ship” script is there to handle an instance of a ship and its behaviour (collection of member methods and variable) it makes sense that said script should be a component of that GameObject. Both from a logical design point of view, and the easier access you get from your script to handle how the GameObject behave.

Programmaticly speaking, there’s only one way to create a MonoBehaviour (or any Component derived class), it is with GameObject.AddComponent();

public class Ship : MonoBehaviour 
{
    public void MoveShip(Vector3 position)
    {
        gameObject.transform.position = position; // Every MonoBehaviour always has a ref to its own GameObject.
    }

    public void FireMissile() { }

    public void AllHandsToBattleStation() { }

    public void Evacuate() { }

    public void SelfDestruct() { }
}

public class Fleet
{
    public List<Ship> ships = new List<Ship>();

    public void AddShip()
    {
        GameObject go = new GameObject("ANewShip");
        Ship ship = go.AddComponent<Ship>(); // Only way to create a MonoBehaviour, by asking a GameObject to make one.
        ships.Add(ship);
    }
}

There’s also the great advantage that if you select a GameObject, you can see and edit fields of its component. Let’s say “ship” got also “public float shields”, you can see and edit that value in the editor.

Frankly, you could also make a free-floating class that derive from nothing, but it simply gives you no advantages over the utilities brought forth by the MonoBehaviour.

LightStriker,

Thank you for the very clear and thorough explanation.
AND, the source code! That really helped and now I feel like we’re on the same page.

A last questions, just to verify one aspect …

FYI: Right now, my ship game object (a sprite) is a prefab that I already instantiate on-the-fly.
That being said, is there any reason I can’t have the “Ship” class already added as a component to my prefab?

It would allow me to alter public properties at design-time and then my Ship class could look like this:
NOTE: The only change is the addition of the Awake() event.
public class Ship : MonoBehaviour
{
public void Awake()
{
Fleet.ships.Add(this);
}

public void MoveShip(Vector3 position)
{
gameObject.transform.position = position;
}

public void FireMissile() { }
public void AllHandsToBattleStation() { }
public void Evacuate() { }
public void SelfDestruct() { }
}

Do you see any problem with doing it that way?

– Paul

P.S. By the way, how did you get nicely formatted code in your message?

You can use the code tag

 to encapsulate code snippet in the forum.

And I see no issue with what you said. Unity was build for that; to have prefab ready with all the script already in place on them.
We rarely "build" a GameObject and its Components by code since it can be done directly in the editor and placed in a prefab.

AFAIK, lists that change size add performance issues due to garbage collection (RAM has to be recovered and returned to the system when objects are removed and this can happen at any point, making for unpredictable performance responses). Everything I’ve ever seen on this matter regards games suggests using static arrays and just keep a tab on how many active elements you have. This static array will be set to the maximum number of objects you’ll allow by design, such as an array of 20 ships and the player is limited to that.

The issue with list only arise if you’re using many lists, and add/remove elements almost every frames. Usually, you use an array only if you know the number of elements or the maximum it can have. Frankly, the overhead of a list is negligible when uses properly.

@ShiftyGeezer You are confusing two issues: you can implement a cache of active/deactive objects using a list or an array (that pattern has nothing to do with the collection used). You can also preallocate space to a list.

Generally I’d say don’t optimize too early, outside of mobile development and very complex projects you aren’t likely to run into GC issues, and regardless its generally not hard to do an optimization pass which removes allocation after you get a feature complete build.

More to the point the OP is asking pretty basic questions, so its likely more useful for them to get an effective but easy to use approach, rather than applying more obfuscated patterns without understanding the reason for them.

  • John A

@imgodot Hope that doesn’t condescending its not meant to.

Generally I would agree with you, make your game first - optimize later. But I’ve run into GC issues in rather simple projects (for PC/Mac/Linux projects) in Unity, never had any GC issues with XNA…

I guess it depends on what you are building more than complexity (lots of entities vs lots of logic), but I still disagree with the recommendation that statically allocated arrays should be used for every collection, or more generally that you should try to write optimized code while developing features.

There are so many optimization recommendations around (don’t use SendMessage, cache instead of GetComponent, avoid dynamic instantition, don’t do raycasts per frame, etc), and most of them have 0 practical effect in most situations. You end up writing more code, and its harder to understand, yet the practical benefits are zero (e.g. if you are GPU bound then there’s no point optimizing CPU usage).

Personally I think its a better path to get the functionality done using clean modular code, and do optimisation later. This has an added benefit for newer users that they will learn why things are optimized a certain way instead of blindinly following a set of rules. Eventually you may come to have enough understanding to apply certain optimizations while developing features because you understand the situations in which they should be applied, but again I don’t think it should be the default position.

Almost all optimization I’ve come across have been caused by

  • Physics (moving static mesh colliders)
  • Draw Calls
  • Fill Rate
  • Not enough memory
  • Bad loops running thousands of times over.

If you’re trying to optimize without using data from the Profiler, I can guarantee you’ll be wasting a lot of time optimizing things that don’t really matter!

Yup. I never been on any project that at the end, we needed to optimized a List. Usually, there’s thousand things to do before that.

Just to add a bit to your list;

  • Environment Mesh Collider being the same as the Visual Mesh
  • Non-compressed texture. (Hello memory!)
  • Overdraw (Especially on iOS)
  • Loading (Sadly, Unity isn’t bright enough to do Resources.Load on a separated thread)
  • FX running off-screen.
  • Post Effect (Glow, Lens Flare, etc)
  • Some fancy shader; Parallax, Offset mapping, Self-Shadowing/Self-Occluding

John A, no offense taken.
I am a long-time programmer but a neophyte when it comes to game development and an absolute noob with Unity (only been playing with it for a few weeks).

We’ve got a bit off topic, but I don’t mind because I got what I needed from LightStriker’s comments.

And, based on the optimization-related comments posted here, when that time comes somewhere down the road it looks like I will have plenty of knowledgeable help in that area.

Thanks to you all.

– Paul