14
\$\begingroup\$

So, I'm starting to introduce attacking to our 2D space RTS (This is in Unity, so it's component driven). Initially it was as simple as "enemy in range, damage applied". However there will be multiple "types" of weapons/attacks that are associated with their particular ship or structure. As well as other factors involved past just raw damage such as the damage type, and possibly inertia in the future.

Would you guys have each unit and structure type have it's own attacking type. Meaning you make a script for each unit/structure that defines it's attack type, damage, effects, range, particles, sprites...etc And attach that as a component?

Or make a script that defines an attack type, a script for the projectile type associated with that...etc And then extend those and modify those for each unit, attaching each script to the unit/structure.


I hope I'm making some sense, I've been pondering this for so long I'm not sure if I'm solving a problem, or just making up my own problems and digging myself into a hole.

When you have a game that can have a multitude of attack types that may or may not be limited to a specific unit/structure, how do you design the framework that tied that together to the specific units/structures in a component-driven design environment?

If this is not clear enough let me know.

Edit: Great answers, thank you.

Expanded Question:

The answers seem to vary from "each object can have it's own attacking script" to "Have the attack types as their own scripts and assign that to each object for a more reusable solution". Lets say I have a "blaster" attack, it shoots a red projectile at a certain speed. It's damage, rate of fire, and projectile size are dependent on the unit firing it. Is it better to just make an attack script for that unit, or try and modify a "blaster attack" to fit the purpose of each unit that wants to use it?

\$\endgroup\$
12
\$\begingroup\$

Well, I'm honestly not an expert on this but... I think it depends on how complex and varied you think the attacks will become. Since it's an RTS I'm guessing you'll have maybe 10-50 or so different units or structures with their own attack types.

Option 1: If there is a relatively low number of units that will have attacks that are somewhat similar I would simply put everything in one big script and define the parameters used in the inspector.

Option 2: If, on the otherhand you envision a large number of attack types with dissimilar behavior you can break everything up so that each unit and building gets their own unique attack script. I'm thinking that if you do this, you might want to create a "helper" script that defines commonly used chunks of code that many of the individual scripts can grab from. This way you won't have to rewrite everything and you'll know where it all is.

Option 3: What you probably shouldn't do is have certain groupings of units share scripts, this will likely confuse you and will become a mess if the code you need for an attack is in 10 different scripts.

Here, I drew you a picture.

enter image description here

\$\endgroup\$
  • 2
    \$\begingroup\$ Thank you very much for the reply. I was for some reason starting to lean towards option 3, and I was having a hard time finding a way to justify it. I'll probably go the 2nd route, each unit gets it's own custom attacking script with common code being shared by having the common code attacked as a component of each unit/building. I'm not sure where I was going with the train of thought that led me to option 3, thank you. I'm leaving this open till I get up in the A.M. in case there are other posters that want to chime in. \$\endgroup\$ – Douglas Gaskell Mar 26 '15 at 9:12
  • \$\begingroup\$ No problem, it's not a definitive answer but hope it helps. \$\endgroup\$ – Mir Mar 26 '15 at 9:20
  • 1
    \$\begingroup\$ You can hybridize 1 and 2 by putting similar attacks in one big script and separate out dissimilar attacks \$\endgroup\$ – ratchet freak Mar 26 '15 at 12:12
  • 4
    \$\begingroup\$ I'm surprised #3 is recommended against? Isn't the entire point of modular/generic classes so that each unit doesn't have to define its own type? If a game is an RTS, and Siege damage (usually "long range") is a damage type, you would want to define it once and have multiple artillery-style units refer to it when performing their damage calcs, so that if Siege damage ever needed to be nerfed (rebalanced), you'd only have to update one class? \$\endgroup\$ – HC_ Mar 26 '15 at 16:24
  • 1
    \$\begingroup\$ "Here, I drew you a picture." reminded me of this \$\endgroup\$ – FreeAsInBeer Mar 26 '15 at 18:43
4
\$\begingroup\$

I don't know a whole lot about Unity and I haven't done game development in a while, so let me give you general programming answer to this question. I have based my answer on the knowledge I have about entity-component systems in general , where an entity is a number that is associated with N many components, a component only contains data, and a system operates on sets of components that are associated with the same entity.

Your problem space is this:

  • There are multiple ways of attacking an enemy in the game as whole.
  • Each ship, structure, etc, may have multiple ways of attacking (each determined by some manner)
  • Each attack may have it's own particle effects.
  • The attack must factor in some factors (such as inertia, or armor, for example), that are present on the target and on the user.

I would structure the solution like the following:

  • An attack has an identifier - this could be a string.
  • An entity 'knows' that it can use an attack (based on the identifier of the attack).
  • When the attack is used by the entity, the corresponding display component is added to the scene.
  • You have some logic that knows about the target of the attack, the attacker, and the attack being used - this logic should decide how much damage you do (and have access to the inertia or whatever of both entities).

It is important that the point of contact between the attacks and the entities are as thin as possible - this will keep your code reusable and prevent you from having to come up with duplicate code for each different type of entity that uses the same type of attack. In other words, here's some JavaScript pseudo-code to give you an idea.

// components
var bulletStrength = { strength: 50 };
var inertia = { inertia: 100 };
var target = { entityId: 0 };
var bullets = {};
var entity = entityManager.create([bulletStrength, inertia, target, bullets]);

var bulletSystem = function() {
  this.update = function(deltaTime, entityId) {
    var bulletStrength = this.getComponentForEntity('bulletStrength', entityId);
    var targetComponent = this.getComponentForEntity('target', entityId);
    // you may instead elect to have the target object contain properties for the target, rather than expose the entity id
    var target = this.getComponentForEntity('inertia', targetComponent.entityId);

    // do some calculations based on the target and the bullet strength to determine what damage to deal
    target.health -= ....;
  }
};

register(bulletSystem).for(entities.with(['bullets']));

Sorry this answer is a bit 'watery'. I only have a half hour lunch break and it's hard to come up with something without knowing fully about Unity :(

\$\endgroup\$
3
\$\begingroup\$

When a unit/structure/weapon attacks, I would probably create an Attack (subclassed with all your fun details) that takes the attacker and the defender (or defenders). The Attack can then interact with the target/defender (slow, poison, damage, change state), draw itself (beam, ray, bullet), and dispose of itself when it's done. I can foresee some issues like multiple poison attacks, so maybe your targets would implement a Damageable interface that the Attack interacts with, but I think it's a workable approach that's modular and flexible to change.

Expanded Answer
This is how I'd approach the blaster attack with this approach. I'll let the others answer for themselves.

I would have my units implement an IAttacker interface or class with basic attack stats/methods. When an IAttacker attacks an IDamageable, it creates its specific Attack passing itself and its target (the IAttacker and the IDamageable, or perhaps a collection of IDamageables). The Attack grabs the stats it needs from the IAttacker (to avoid changes during upgrades or anything like that--we don't want the Attack to change its stats after it's already been launched) and if it needs specialized stats, casts the IAttacker to its needed type (ex. IBlasterAttacker) and gets the specialized stats that way.

Going with this approach, a BlasterAttacker just needs to create a BlasterAttack, and BlasterAttack takes care of the rest. You can subclass BlasterAttack or create separate FastBlasterAttacker, MegaBlasterAttacker, SniperBlasterAttacker, etc, and the attacking code for each is the same (and possibly inherited from BlasterAttack): Create the BlasterAttack and pass myself and my target(s) in. BlasterAttack handles the details.

\$\endgroup\$
  • \$\begingroup\$ Essentially, the unit inherits from an IAttacker interface (I have this already), and there is an IDamageable interface for the "enemy"(have this as well). When the attacker attacks, a BlasterAttack (or derived class) is called. This "attack" will retrieve the data it needs from the IAttacker, and apply it on the IDamageable when the projectile hits? Does the projectile itself contain the BlasterAttack class, so that once it's fired it is no longer affected by changes to the IAttacker, and can apply it's damage/effects on the IDamageable only if it's projectile actually hits. \$\endgroup\$ – Douglas Gaskell Mar 27 '15 at 18:07
  • \$\begingroup\$ When you say "a BlasterAttack (or derived class) is called" I would say a BlasterAttack is created. This newly created instance of BlasterAttack represents the beam (or bullet or ray or whatever), so it is the projectile. BlasterAttack copies whatever stats it needs from the IAttacker and IDamageable objects: the positions, the attacker stats, etc. The BlasterAttack then tracks its own position and, if applicable, applies damage on "contact." You'll need to figure out what to do if it misses or reaches its destination (the target's old position). Burn the ground? Disappear? Your call. \$\endgroup\$ – ricksmt Mar 27 '15 at 20:36
  • \$\begingroup\$ For an area-of-effect Attack, you might want access to a global collection of (enemy) units since who's in range and who's out of range can change between fire and impact. Of course, a similar argument could be made for the BlasterAttack: you miss your initial target, but hit the guy behind him. My only concern is that you could have a lot of Attacks iterating through a lot of Enemies trying to figure out if and what they hit. That's a performance concern. \$\endgroup\$ – ricksmt Mar 27 '15 at 20:39
  • \$\begingroup\$ Ah, that makes sense. For a missed attack the projectile will have it's own pre-set range/lifetime. If it hits something else before the end of that lifetime it will receive a reference to whatever object owns the rigidbody it collides with, and damage will be applied that way. Actually that's how all the projectiles will work, they don;t know "what" they are traveling towards, just that they are traveling (excluding homing-like projectiles like missiles). AEO effects can just enable a sphere collider upon destination and get all objects that are inside of it. Thanks for the help. \$\endgroup\$ – Douglas Gaskell Mar 27 '15 at 20:43
  • \$\begingroup\$ No problem. Glad I could. Forgot that Unity makes all this collision stuff easier. \$\endgroup\$ – ricksmt Mar 27 '15 at 20:49

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.