0
\$\begingroup\$

I'm making a RTS in which units can get several cyber-upgrades (individual, not as a global tech-tree upgrade like in most RTS games) before they're created. Now, I'm trying to decide how to do this, logic-wise. The system I have right now is that each unit type (walker, car, etc.) is a class, and when I initiate an instance of the class I add all the modifiers to the constructor, and let the constructor handle all the modifications.

This seems to me like the wrong way to do it - it means copy-pasted code in all the constructors. The other idea I had was to have each modification as a static method that takes a Unit class and modifies it - but the modifications might need access to private memebers (health, speed, etc.), and if I write the methods per-class, again I achieved copy-pasted code. As far as I know there's no way that a external static method can access private fields, and I don't want to create properties for every field, so as not to make the classes too cumbersome to use.

Example, to clarify: let's say I create a new Agent, and I want him to have bionic feet (+5 speed), x-ray vision (change of sight-function) and armored body (change of hit effects). I could use a constructor like Agent(speed, vision, armor), but if I'd later want to create new upgrades, such as fast reactions (+5 initiative), I'd have to change the constructor's signature. I could use a constructor like Agent(List), and then in the constructor create a giant swtich/case for every available upgrade, but then, I'd have to copy it also to the constructor of Vehicle, and make sure I update both of them, exactly the same. What I'd like to do is have a basic constructor and after it have a:

internal delegate void Effect(Unit ent);

void setup(List<Effect> list)
{
    foreach(Effect effect in list) effect(this);
}

method, and then run every effect in the list on the new Unit, but then the Effect can't access the unit's private members.

Is there an elegant solution to the problem?

\$\endgroup\$

3 Answers 3

1
\$\begingroup\$

Using your idea of a switch statement to apply the upgrades, you could achieve what you want here by having the walker and vehicle classes derive from a parent "Upgradeable" or "Stat" class. This can be used to handle all of the stats for both unit types, which will make changes easier to maintain.

For example:

Upgradeable Class:

public enum Upgrade {
    ARMOUR_MKI,
    ENGINE_MKII
}

public class Upgradeable {
    protected int hp;
    protected float speed;

    protected Upgradeable() {}
    protected virtual void ApplyUpgrades(List<Upgrade> upgrades) {
        foreach (Upgrade u in upgrades) {
            switch (u) {
                case Upgrade.ARMOUR_MKI:
                    hp += 100;
                break;          

                case Upgrade.ENGINE_MKII:
                    speed += 5f;
                break;
            }
        }
    }
}

Then derive your other classes from this:

public class Walker : Upgradeable {
    public Walker(List<Upgrade> upgrades) : base() {
        hp = 100;
        speed = 4f;
        ApplyUpgrades(upgrades);
    }

    protected override void ApplyUpgrades(List<Upgrade> upgrades) {
        base.ApplyUpgrades(upgrades);
        // Class Specific Stuff
    }
}

public class Vehicle : Upgradeable {
    public Vehicle(List<Upgrade> upgrades) : base() {
        hp = 200
        speed = 2f;
        ApplyUpgrades(upgrades);
    }

    protected override void ApplyUpgrades(List<Upgrade> upgrades) {
        base.ApplyUpgrades(upgrades);
        // Class Specific Stuff
    }
}

This provides the type of interface you want, whilst maintaining protection of your object's members and upgrade method calls. The centralisation of the Upgradeable class also makes maintainability of the upgrades between unit types much simpler. And with the ApplyUpgrades method calls in the derived classes, you can implement specific upgrades or effects per-class too.

\$\endgroup\$
2
  • \$\begingroup\$ This seems like a sound idea, but isn't the loop in Upgradeable a bit heavy, computability-wise? If I have n potential upgrades and m actual upgrades to a unit, then every unit creation is O(n*m) actions. Now, m usually will be negligible, but I wonder - if n grows, isn't this a bit too complex? \$\endgroup\$
    – nihohit
    Commented Apr 25, 2012 at 13:57
  • 1
    \$\begingroup\$ In this case, you could categorise the upgrades; which would cut down the number of checks per upgrade added. For example, the upgrade application method could go like: If upgrade type is EngineUpgrade, ApplyEngineUpgrade(). Then this method could have a list of engine upgrades, which again could further be broken down into sub-categories such as "Power Upgrades", "Fuel Capacity Upgrades" etc. This should keep the structure feasible when dealing with a large number of upgrade types. \$\endgroup\$
    – Jason H
    Commented Apr 25, 2012 at 14:18
1
\$\begingroup\$

There are some things to consider: Are these upgrades just about modifying values or do we need things like adding new visual effects or new behavior when something is triggered. Jason's solution is alright for the limited requirements of the former. Although you would want to keep list of upgrades available too.

For the latter requirements, there are many different design patterns available. For instance, you could have base class Upgrade (or a more general name like "Attachable") which has events OnAttach or OnDetach which are evoked upon adding the upgrade to an object, and then it adds handlers to the object's events; and later removes them OnDetach.

Latter solution could be unwieldy and more inefficient if your requirements are smaller. Optimization is probably your least concern for now anyway.

And you can have some Factory-objects or methods to ease the generation of these composite-objects.

\$\endgroup\$
0
\$\begingroup\$

Why don't you add Effects as a property of your Unit class?

Effect armor = new Armor(5);
Agent a = new Agent("Name");
a.Augments.Add(armor);

Something like that.

\$\endgroup\$
2
  • \$\begingroup\$ OK, this seems like a good idea, but then If I understood you correctly,Agent.Augments is a list, and to act on an Agent I'd need to cycle through all his augments to see whether he has a relevant one or not. right? \$\endgroup\$
    – nihohit
    Commented Apr 25, 2012 at 10:52
  • \$\begingroup\$ @nihohit That's correct. \$\endgroup\$
    – pabdulin
    Commented Apr 26, 2012 at 2:16

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .