2 Questions (Structs, and Events)

Hello, I just tore through this whole course in about a week and enjoyed every moment of it. I feel ilke I learned a lot, but right now have two questions about general workflow and how you approach problem solving.

  1. Structs. I understand how they work, but I’m not entirely clear on when you want to use them. When you’re looking at a problem, what’s the thought process that leads you to think that a struct is the proper approach to solving it? Or in what kind of situations are structs the most useful solution?

  2. Events. I get how these work too, but am wondering when it’s more beneficial to use events over public functions. For example, when you know something is likely going to touch multiple scripts (OnEnemyDeath, OnTurnEnded, etc…), that seems like a good reason to have an event. But then take hte ScreenShakeActions, for example. That one seemed a little bit excessive in that now every Action will need to evoke a its own event which more or less does the same thing on ScreenShakeActions. Would this just be better served with a public function? Similar to above, what’s the thought process that goes into deciding to use events vs. public functions.

Thanks! Again I really enjoyed the course and feel like I learned a lot. I’m making some gameplay modifications here and there before I work on and show a final level.

1 Like

My general rule of thumb is that if a container (class or struct) has all value types (int, float, bool, or another struct), I’ll use a struct. If it has reference values (i.e. other classes), then I’ll use a class. That being said, I also avoid using a struct if it’s going to have an exorbantly large number of members… For example: A struct with 30 integers means if you pass that struct into a method call, it’s going to push all of those integers onto the stack. This can add up, and stack memory is more precious than heap memory.

That leads to the other consideration when dealing with structs and classes when being passed to method calls. All classes are reference types, all structs are value types. Consider the following:

public struct StructExample
{
    public int a;
    public int b;
}

public class ClassExample
{
    public int a;
    public int b;
}

public void AlterStructExample(StructExample s)
{
    s.a ++;
    s.b --;
}

public void AlterClassExample(ClassExample c)
{
    c.a ++;
    c.b --;
}

void Start()
{
     StructExample structExample = new StructExample() {a=5, b=5};
     ClassExample classExample = new ClassExample() {a=5, b=5};
     AlterStructExample(structExample);
     AlterClassExample(classExample);
     Debug.Log($"StructExample {structExample.a}, {structExample.b}");
     Debug.Log($"ClassExample {classExample.a}, classExample.b}");
}

output:

StructExample 5,5
ClassExample 6,4

When a struct is passed to a method call, as in AlterStructExample, a copy of the struct is pushed to the stack. Any changes you make to the struct within the method do not propogate back to the original struct as we’re only working on a copy of the data. When we Debug Log on the original struct, you’ll find the data is unchanged.
When a class is passed to a method call, the actual data is in the heap, and a pointer to that data is passed into the function. This means that any operations we do on the members of that class actually happen on the original class, rather than the copy of the data. When we Debug Log on the class object, we find that the data has changed.

2 Likes

@Brian_Trotter got you covered on structs

You got the right idea to start; multiple scripts can listen to these events and react appropriately. I use events when I want to notify ‘the system’ that some action has taken place (like a bullet was shot), or some state has changed (like a turn ended).

I, personally, tend to not put the ‘critical path’ in the hands of events. If something must happen, I make it happen. If it may happen, I can probably use an event. This is not a hard-and-fast rule, though. If the player dies, I could still fire an event that a GameManager can listen to to end the game (it must happen) but I am more likely to just call EndGame() on the GameManager myself. It depends on how I structured everything to start off with, and may not be the right approach. Just how I think about it.

As for ScreenShakeActions; That’s not actually the event - it’s the listener. The event is OnAnyShoot and, as you mentioned, can touch multiple scripts. Want to play a ‘shoot’ sound effect? An AudioManager component could listen to the same event and play the clip. Want to flash the screen? A ScreenFlashActions component could listen to it and do the flash.

You can use a public function. It’s there. But then you’d be making the ShootAction dependant on ScreenShake. Remove ScreenShake for any reason, and all the actions that call the function breaks. Using the event decouples them from each other. You could argue that the ScreenShakeActions does not solve that problem because it is dependant on ScreenShake and ShootAction and removing any would break ScreenShakeActions and you’d be right, but it does mitigate the affected area. Removing ScreenShake will only break ScreenShakeActions and not every action that wants to trigger a shake. Also, not every actions would want to trigger a screen shake, so in terms of ‘excessive’, I don’t think that’s the case.

3 Likes

Hmm, you’re right about ScreenShakeActions. I was getting a bit tunnel visioned with it because I was making additional exploding objects (barrels) and it seemed redundant to need to make a separate OnAnyExplosion event for any sort of exploding object (initially we just had the grenades), but I guess the better solution here would be to keep the ScreenShakeAction as a listener and then make an ExplodingObject class with the OnAnyExplosion event that all of the exploding objects derive from instead, right?

Thanks!

Aweome, that’s a very good desscription, the example was super helpful too. Thanks!

Absolutely!

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms