0
\$\begingroup\$

I'm trying to make a class selection system for learning and I've decided to use scriptable objects for the classes since it seems like scriptable objects are good for that(?) however I start to get confused when it comes to setting the class and using the values.

This is my scriptable object for a class.

[CreateAssetMenu(fileName = "New Class", menuName = "Classes/New Class")]
public class BaseClass : ScriptableObject
{
    public float health;
    public float walkSpeed;
    public float fallSpeed;
    public float jumpForce;
    
    public string displayName;

    public List<BaseAbility> abilites;
}

I currently have 2 instances one is called "No Class" which is just default values for when no class is selected (which I dragged into the Player component so it's there when the game starts) and then I have a Warrior one.

This is my player script which uses an event to set the players class when they press a button

public class Player : MonoBehaviour
{
    public BaseClass selectedClass;

    void Start()
    {
        ClassSelectButton.OnClassSelected += SetPlayerClass;
    }

    void SetPlayerClass(BaseClass newClass)
    {
        selectedClass = newClass;
    }
}

And here's where I start to get confused. So I set the players class to the new class in the Player script, but now I need to get the new values in the scripts that need them like my movement script so my solution was to just use the same event.

    public BaseClass playerClass;

    void Start()
    {
       playerClass = GetComponent<Player>().playerClass;
        ClassSelectButton.OnClassSelected += (BaseClass newClass) => { playerClass = newClass; };
    }

    void FixedUpdate()
    {
        rigidBody.MovePosition(transform.position + moveDirection * playerClass.walkSpeed * Time.fixedDeltaTime);   
    }

Although this works I feel like it's not a good approach (perhaps I'm wrong though) so I'm trying to figure out other methods I can do.

\$\endgroup\$
2
  • \$\begingroup\$ Have you tried to run the exact piece of code you posted without adding the listener in the movement script? Because it should get the same class over the reference you have to the player \$\endgroup\$
    – Zibelas
    Commented May 27, 2022 at 17:17
  • \$\begingroup\$ I have done it without the listener, however, I think the issue I encountered was when it gets changed to a new SO (select a different class) it still uses the old one. \$\endgroup\$ Commented May 27, 2022 at 18:04

2 Answers 2

0
\$\begingroup\$

Don't have everything in your game listen to the ClassSelectButton directly! On that path lies headaches. If you ever decide you'd like to change how classes are selected in the future (or let people advance to subclasses halfway through the game, or anything of that sort), you'll have to update every single consumer.

There are two ways to do this which I've used in different contexts, and the advantages of each.

Option 1: Store a handle to Player

Your movement script wants to know what the player's class is, and it wants to always have the current value of that. Have it look up the current value every time it needs it.

class Movement
{
    private Player Player;

    private BaseClass PlayerClass => this.Player.playerClass;

    void Awake()
    {
        this.Player = this.GetComponent<Player>();
    }

    void FixedUpdate()
    {
        rigidBody.MovePosition(transform.position + moveDirection * this.PlayerClass.walkSpeed * Time.fixedDeltaTime);   
    }
}

Advantages of this approach relative to option 2:

  • It's intuitive. I didn't have to add any API/documentation links to explain what I did here.
  • It's easy to implement - you can re-use half the code in the sample for everything else that consumes PlayerClass.
  • It's easy to maintain - the property will always be correct, and will update instantly. No need to worry about accidentally changing class and forgetting to notify the consumers.

Option 2: Dispatch an event within the event

public class Player : MonoBehaviour
{
    public BaseClass selectedClass;

    void Start()
    {
        ClassSelectButton.OnClassSelected += SetPlayerClass;
    }

    void SetPlayerClass(BaseClass newClass)
    {
        if(selectedClass != newClass)
        {
            selectedClass = newClass;

            // Tell all the other classes to update using the SendMessage API. See https://docs.unity3d.com/ScriptReference/Component.SendMessage.html for details.
            SendMessage("ClassChange", newClass);
        }
    }
}
class Movement
{
    private float WalkSpeed;

    void FixedUpdate()
    {
        rigidBody.MovePosition(transform.position + moveDirection * this.WalkSpeed * Time.fixedDeltaTime);   
    }

    void ClassChange(BaseClass newClass)
    {
        this.WalkSpeed = newClass.walkSpeed;
    }
}

Advantages of this approach relative to Option 1:

  • Easy to add visual/audio cues on class change, since the event already exists.
  • Decouple movement from character classes - you can re-use the movement script on an object that doesn't have a character class without getting NullReferenceExceptions.
  • You can see and change the walk speed in the inspector of the movement script while debugging, without needing to open up the character class data.
  • Very slightly more performant (like, very slightly. You won't notice and neither will your players)
\$\endgroup\$
-1
\$\begingroup\$

Basically, you can use the same event of course. Multiple objects can subscribe to the same event and do their respective things. For example, EnemyDied is an event and Sound Manager along with ScoreManager subscribes to this event.

When OnEnemyDied() is called within these classes, SoundManager plays the sound and ScoreManager updates the score using the same event which is the correct approach so no need to worry about another approach.

Using the same event isn't the wrong approach, in fact, it is probably a very good approach if not the best (I am not the expert yet). However, try to use listeners so each class can do its own unique thing upon an event that it is subscribed to.

\$\endgroup\$
0

You must log in to answer this question.

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