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 Structure
s and Player
s, and Structure
s maintain a list of Unit
s 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?
1 Answer 1
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.
-
\$\begingroup\$ This would leave the problem that when a
Unit
gets removed, theStructure
orWorld
is still keeping it alive. How would I go about making sureUnit
s are removed from allIEnterable
s when they're removed from the main ID lookup table? \$\endgroup\$Layl Conway– Layl Conway2014年03月06日 20:38:17 +00:00Commented Mar 6, 2014 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– Jeff Vanzella2014年03月06日 21:43:32 +00:00Commented Mar 6, 2014 at 21:43