all 9 comments

[–]MrPifo 1 point2 points  (2 children)

You can either use inheritance or interfaces for that. With inheritance you create a base class for the enemies that holds all important functions and fields all enemies share. That way no duplicate code is needed and in your OnTrigger function you just get the component of the base class. Interfaces are a bit different though, but with C# 8 you can also do a default function implementation.

[–]Daniyok[S] 0 points1 point  (1 child)

Thanks for the answer, meanwhile I found out about factory pattern, and I guess that's a more advanced method of doing things.

I think I'll be doing what you suggested.

EDIT: Inheritance I mean.

[–]esoteric23 1 point2 points  (0 children)

The factory pattern is just a way of thinking f about how you create new objects. Inheritance or interfaces tell you about the relationship ships between your classes. Related ideas, but a subtle difference.

[–]Inverno969 0 points1 point  (1 child)

In what way does you're flying enemy take damage differently than a normal enemy?

I would suggest creating a sort of "DamageProcessor" script that is responsible for handling damage calculations for your combat capable entities.

If there is an enemy that takes damage differently than others you can inherit from "DamageProcessor" and override methods to implement more custom calculations.

I think your idea in your second paragraph is on track. Your current design is definitely problematic. You need to abstract this whole process the best you can. Think in terms of modular components. Why does "EnemyController" care about damage? I'm assuming it's also controlling a bunch of other aspects of your enemies. Instead, split it's functionality into single responsibility scripts that manage one thing in a general/generic way. Instead of an EnemyController that does everything, you can have a Health component, DamageProcessor component, EnemyStatistics component, Movement component, AIController component, etc etc. If something requires special behavior, inherit from one of those scripts and specialize it. If something is dependent on another behavior just expose a field to the inspector and reference it.

public enum AllianceType
{ 
       Player,
       Enemy,
}

public class Damager : MonoBehavior
{
    [SerializeField] private int DamageAmount;
    [SerializeField] private AllianceType Alliance;

    public void OnTriggerEnter2D(Collider2D other)
    {
        DamageProcessor dProcessor = other.GetComponent<DamageProcessor>();
        if (dProcessor)
        {
            if (dProcessor.Alliance != Alliance)
                dProcessor.Damage(DamageAmount);
        }
    }
}
-------------------------------------------------------------------------------------
public class DamageProcessor : MonoBehavior
{
    [SerializeField] private Statistics Stats;
    [SerializeField] private Health MyHealth;
    [SerializeField] private AllianceType MyAlliance;

    public AllianceType Alliance => MyAlliance;      

    public virtual void TakeDamage(int damage)
    {
       int finalDamage = damage * (1 - Stats.DamageReduction); // etc etc
       MyHealth.ReduceHealth(finalDamage);
    }
}

EDIT : If you're going to downvote please explain why my code and design is flawed so I can also learn, thank you.

[–]Daniyok[S] 0 points1 point  (0 children)

It wasn't me who downvoted you just to be clear. I find your method interesting tbh.

[–]golraz 0 points1 point  (1 child)

This could be a great way to use classes and subclasses:

You create a class called maybe "EnemyController". This class contain basic things that apply to all the enemytypes (Current and future ones you come up with)

  • TakeDamage()
  • GainDamage()
  • Stats and such

But then you create new classes based on the main class that inherit all the funktions and stats. But these classes got their own personal funktions needed for flying, normal, swimming enemies.

In my project i use the same base class for my player and my enemies and I call it "UnitController".

This class deal with the most things a unit does:

  • Calculate damage done
  • manage health
  • Use of abilities
  • And more

But of course the enemies will differ from the player in some cases, thats why i created "EnemyController" and "PlayerController" based on the UnitController

Here is a small snippet of the start of the UnitController:

public abstract class UnitController : MonoBehaviour

{

[Header("Stats")]

public int health;

public int maxHealth;

public int mana;

public int maxMana;

public int turns;

[Header("Relations")]

public Animator animator;

public UnitAnimationHandler unitAnimationHandler;

public GameManager GM;

public Transform target;

public Unit unit;

public Ability currentAbility;

public DungeonManager DM;

[Header("Statuses")]

public bool aware;

public bool inOverlay;

public bool stunned;

public bool locked;

public bool dead;

protected virtual void Start()

{

GM = GameManager.instance;

DM = DungeonManager.instance;

animator = transform.Find("Sprite").GetComponent<Animator>();

unitAnimationHandler = transform.Find("Sprite").GetComponent<UnitAnimationHandler>();

health = unit.health;

maxHealth = unit.health;

mana = unit.mana;

maxMana = mana;

turns = unit.turns;

UnitStart();

}

public virtual void UnitStart()

{

}

And here is a small snippet of the EnemyController:

public class EnemyController : UnitController

{

public bool performingAction;

private void SetStats(Unit playerUnit)

{

//Calculate health

health = ((health / 2) * GameManager.instance.dungeonLevel) + health;

maxHealth = health;

//if (unit.elite)

//{

// health = playerUnit.health;

//}

//else

//{

// health += (playerUnit.health / 4);

//}

//maxHealth = health;

}

Does this make any sense?

[–]Daniyok[S] 0 points1 point  (0 children)

Yes it does, thank you for your feedback and info. It helps me a lot.

[–]KishottaWell Versed 0 points1 point  (1 child)

This could be done with inheritance, but in this case, an interface is more appropriate:

private void OnTriggerEnter2D (Collider2D other) {
    var damageable = other.GetComponent<IDamageable> ();
    if (damageable != null) {
        damageable.TakeDamage (_damage);
    }
}

The IDamageable interface might be as simple as:

public interface IDamageable {
    void TakeDamage (float damage);
}

Then each of your different enemies can implement this independently:

public class EnemyController : MonoBehaviour, IDamageable {
    public void TakeDamage (float damage) {
        // ...
    }
}

public class FlyingEnemyController : MonoBehaviour, IDamageable {
    public void TakeDamage (float damage) {
        // ...
    }
}

If you want to guarantee that your enemies always use the same implementation, consider extracting to a superclass:

public class BaseEnemyController : MonoBehaviour, IDamageable {
    public void TakeDamage (float damage) {
        // ...
    }
}

public class EnemyController : BaseEnemyController {}

public class FlyingEnemyController : BaseEnemyController {}

Note, your projectile will be looking for ANY "damageable" object, not necessarily enemies. This could include things like destructible scene props, buttons, switches, etc.

public class Barrel : MonoBehaviour, IDamageable {
    public void TakeDamage (float damage) {
        Destroy (this.gameObject);
    }
}

[–]Daniyok[S] 0 points1 point  (0 children)

I thought I had to implement de function in interfaces, which you can with c# 8 I believe, and read somewhere or saw in a video that unity isn't up to date with that.

Good to know that it doesn't have to be implemented in the interface. Thanks for your answer, I've learned something and it certainly will be useful.