Skip to main content
added 63 characters in body
Source Link
Kevin
  • 7k
  • 1
  • 12
  • 32

I'm not even sure off the top of my head if that would workwork; I think you'd end up at a catch-22 somewhere in the implementation. Another possibility is to not try to make the generic scheme more complex, and instead do something like this:

I'm not even sure off the top of my head if that would work. Another possibility is to not try to make the generic scheme more complex, and instead do something like this:

I'm not sure off the top of my head if that would work; I think you'd end up at a catch-22 somewhere in the implementation. Another possibility is to not try to make the generic scheme more complex, and instead do something like this:

added 837 characters in body
Source Link
Kevin
  • 7k
  • 1
  • 12
  • 32

EDIT: Part of what makes the above answer complicated is that you've got double-coupling - the Settings is aware of the Ability and the Ability is aware of the Settings. You probably should not make InstantiateAbility and RemoveAbility functions of the AbilitySettingsBase; that way the Settings doesn't need to be generic at all. Instead, you might have a function somewhat like this on your AbilityManager:

public T AddAbility<T, U>(GameObject go) where T : Ability<U> where U : AbilitySettings {
    Assert.IsNotNull(go);
    T ability = go.AddComponent<T>();
    if (abilitySettingsDictionary.TryGetValue(typeof(U), out var settings)) {
        ability.Settings = settings;
    } else {
        Debug.LogError("Didn't find a settings asset for " + typeof(T));
    }
    return ability;
}

EDIT: Part of what makes the above answer complicated is that you've got double-coupling - the Settings is aware of the Ability and the Ability is aware of the Settings. You probably should not make InstantiateAbility and RemoveAbility functions of the AbilitySettingsBase; that way the Settings doesn't need to be generic at all. Instead, you might have a function somewhat like this on your AbilityManager:

public T AddAbility<T, U>(GameObject go) where T : Ability<U> where U : AbilitySettings {
    Assert.IsNotNull(go);
    T ability = go.AddComponent<T>();
    if (abilitySettingsDictionary.TryGetValue(typeof(U), out var settings)) {
        ability.Settings = settings;
    } else {
        Debug.LogError("Didn't find a settings asset for " + typeof(T));
    }
    return ability;
}
Source Link
Kevin
  • 7k
  • 1
  • 12
  • 32

Here's how I often build lists of generics:

public abstract class AbilitySettingsBase : ScriptableObject {
    public abstract IAbility InstantiateAbility(GameObject gameObject);
    public abstract void RemoveAbility(GameObject gameObject);
}

public abstract class AbilitySettings<T> : AbilitySettingsBase where T : Ability {
    public override IAbility InstantiateAbility(GameObject gameObject) {
    Assert.IsNotNull(gameObject);
        T ability = gameObject.AddComponent<T>();
        ability.Settings = this;
        return ability;
    }

    public override void RemoveAbility(GameObject gameObject) {
        T ability = gameObject.GetComponent<T>();
        if (ability != null) Destroy(ability);
    }
}

[CreateAssetMenu(menuName="Jump Settings")]
public class JumpSettings : AbilitySettings<JumpAbility> {
}

[CreateAssetMenu(menuName="Fight Settings")]
public class FightSettings : AbilitySettings<FightAbility> {
}

public class AbilityManager : MonoBehaviour
{
    //This will show up in the Inspector and we can assign the appropriate assets
    [SerializeField] private AbilitySettingsBase[] abilitySettings = { };    

    //...
}

Here we have a non-generic base type AbilitySettingsBase that defines some abstract functions. We can have a serialized list of AbilitySettingsBase because, even though it's abstract, it's not generic. This will expose the list in the Inspector and allow us to select asset files made from ScriptableObject scripts that extend AbilitySettingsBase.

Where it can get more complicated is if the Ability scripts need to understand the AbilitySettings type. If you try to use generics you might end up with a much more convoluted generic scheme, e.g.

public abstract class AbilitySettings<T, U> : AbilitySettingsBase where T : Ability<T, U> where U : AbilitySettings<T, U> {
    //code ommitted
}

public abstract class Ability<T, U> where T : Ability<T, U> where U : AbilitySettings<T, U> {
    //code ommitted
}

I'm not even sure off the top of my head if that would work. Another possibility is to not try to make the generic scheme more complex, and instead do something like this:

public class JumpAbility : Ability {
    private JumpAbilitySettings settings;

    override public AbilitySettingsBase Settings {
        get => settings;
        set {
            Assert.IsNotNull(value);
            JumpAbilitySettings settings = value as JumpAbilitySettings;
            Assert.IsNotNull(settings);
            this.settings = settings;
            //apply settings here
        }
    }
}

This isn't as type-safe but will save some headaches in generic design.