6
\$\begingroup\$

In an example game engine server, the game world could be represented by a World object. The game might have multiple game worlds and a player who is in a world only needs to receive position data of units in that world.

class World
{
    public List<Unit> Units { get; set; }
}

However a piece of code also needs to be able to look up what world a unit is in easily, so the unit object itself keeps track of a world reference.

class Unit
{
    public World World { get; set; }
}

This works for lookup, but quickly becomes problematic when changing data when the programmer isn't aware of the relationship between objects going on, so I changed Units in World to be readonly and have the following code in Unit.

public virtual World World
{
    get { return _world; }
    set
    {
        // If already this, don't do anything
        if (value == _world) return;

        var oldWorld = _world;
        _world = value;

        if(oldWorld != null) oldWorld.UpdateUnitEntry(this);
        _world.UpdateUnitEntry(this);
    }
}

This works, but it feels like there's a better way to do this. Especially as I add more stuff that needs to be linked the same way (a World also has Structures and Players, and Structures maintain a list of Units as well), a lot of repeated functionality comes in. As well when I remove a unit from the game, it continues to be kept alive by this, so I have to remember to set the unit's structure to null every time (which I already forgot multiple times, resulting in weird bugs). Is there a better way to achieve this one-to-many relationship without manually updating both sides?

\$\endgroup\$
6
\$\begingroup\$

The best way to accomplish this is to have each World, Structure, and anywhere else a unit can move to inherit an interface IEnterable, which looks like

public interface IEnterable
{
    void Enter(Unit unit);
    void Leave(Unit unit);
}

Your unit would then have this as a property, and a MoveTo(IEnterable location) method

public class Unit
{
     // ...

     public IEnterable CurrentLocation { get; set; }

     // ...

     public void MoveTo(IEnterable location)
     {
         if (CurrentLocation != null)
         {
              CurrentLocation.Leave(this);
         }

         location.Enter(this);
     }
}

The Enter and Leave would handle all the logic for the Unit movement.

If you wanted to get more fancy, you could have a Movement class that could describe the movement a little more accurately.

public class Movement
{
     private readonly IEnterable _destination;

     public Movement(IEnterable destination)
     {
         if (destination== null) throw new ArgumentNullException("destination"); 

         _destination = destination;
     }

     public MoveResult MoveUnit(Unit unit)
     {
         if (unit == null) throw new ArgumentNullException("unit");

         var currentLocation = unit.CurrentLocation;
         if (currentLocation  == _destination) return MoveResult.SameLocation;

         if (!IsValidMove(currentLocation, _destination) return MoveResult.InvalidMovment;

         currentLocation.Leave(unit);

         _destination.Enter(unit);
     }
}

This will get the movement logic out of the Unit class because a unit should not care about how it has to move, just that it has.

I might be missing something, but my main point is to use an interface for each object that a Unit can enter, and the ease of moving a Unit becomes much better.

\$\endgroup\$
  • \$\begingroup\$ This would leave the problem that when a Unit gets removed, the Structure or World is still keeping it alive. How would I go about making sure Units are removed from all IEnterables when they're removed from the main ID lookup table? \$\endgroup\$ – Layl Conway Mar 6 '14 at 20:38
  • \$\begingroup\$ @LaylConway easiest way would be to add a Method RemoveUnit which stores the current location in a temp variable, sets it to Null in the unit, then updates the location with the new value. The location update would see the null and remove it from its internal list. \$\endgroup\$ – Jeff Vanzella Mar 6 '14 at 21:43

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.