0
\$\begingroup\$

I'm making an RTS game in Unity with C#. However, the interaction I'm going to have with player-created units interacting with other player-created units is what has me stumped. The player and his opponent can purchase units when they have the resources, and it instantiates them in the game. The main thing is checking whether an opponent's unit is within range to attack. Obviously, you can't have every unit asking every other unit what its position is, or it would get exponentially more CPU-consuming the more units are built, right? I'm trying to figure out how I'm going to go about this.

I figured I could have every unit report its position to a sort of master 'tracker', and then every unit could ask the tracker if any opponents' units have a position within X of their own. How I would actually implement this in Unity, though, I'm not sure about.

Moreover, I'm trying to get my head around Instantiated objects. If every unit is telling its position to the master 'tracker', what does it call itself? And let's say a unit asks the tracker if an enemy is in range and one is, what does the tracker tell it so it can locate the right GameObject and attack it? Sorry if I'm talking about it all wrong, I'm sure there's a simple methodology that exists on how to do it, but I can't think of any. Thank you.

\$\endgroup\$
  • 1
    \$\begingroup\$ It sounds like what you're looking for is a spatial partition. Unity's physics engines already have this type of structure implemented under the hood, so they can efficiently answer overlap queries. You could potentially leverage that as a first, easy step to reduce the quadratic explosion of distance checks. \$\endgroup\$ – DMGregory Apr 13 at 16:08
0
\$\begingroup\$

Using a manager class is absolutely how I would go about implementing this. As you instantiate each object your manager class can add that object to a list. As for CPU usage, you could optimize as comments suggest but unless you have literally millions of units per side something like asking for a position on a modern computer should be absolutely possible even with the quadratic scaling, an easy fix would be not asking for this every frame - only asking when a unit is about to attack etc.


Under the hood unity will keep a separate reference to each instantiated game object, so by passing an object's this to your manager class will absolutely allow the manager to use the game object, or select the correct object out of a list.

// Manager class
void func(Unit unit){
    unit.member; // you can do this!
    unit.function(); // and this
    unit == unitList[0]; // this too!
}

Then in your unit class you can just do

unitManager.func(this);
or
unitManager.func(gameObject.getComponent<Unit>());

I would implement this so your manager class returns a list of units within a range of a position, something like this

// manager class
List<Unit> GetUnitsInRange(Vector3 position, float range){
    List<Unit> unitsInRange = new List<Unit>(); // list that will be returned
    float rangeSquared = range*range // this will allow us to avoid an expensive sqrt() later
    foreach(Unit unit in unitList){
        float distanceSquared = (unit.transform.position - position).sqrMagnitude;
        if(distanceSquared <= rangeSquared){
            unitsInRange.Add(unit);
        }
    }
    return unitsInRange; // return this to your unit
}

You could then in your Unit class, simply ask your manager class for the units in range like

// in Unit class
List<Unit> unitsInMyRange = unitManager.GetUnitsInRange(transform.position, range);

So long as you don't have hundreds of thousands of units, or you aren't calling this each frame, this should absolutely be something your machine can handle - it's only one for loop per unit, however if you're having performance issues you can have different unitLists per segment of your map, or look into the way the physics system optimizes its calculations as suggested in the comments.


As for if your unit then wants to attack one of the enemies in range, returned by this function, you can simply treat everything in the list as a reference to a gameObject. So something like

// in Unit
int randomInt =  Random.Range(0,unitsInMyRange.Count-1); // -1 so don't index outside list
Attack(unitsInMyRange[randomInt]);

or

FireAt(unitsInMyRange[randomInt].transform.position);

Would be completely valid

| improve this answer | |
\$\endgroup\$
  • 1
    \$\begingroup\$ This is awesome thank you for walking me through this. \$\endgroup\$ – MattWolc24 Apr 15 at 4:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.