#Converting SO to plain class?
1 messages · Page 1 of 1 (latest)
Well, why do you want to convert the events to non SO? Or am I misunderstanding what you're trying to do?
I was told not to use SO in my code
Oof... It was not a single message..
Except from using it for immutable data
Why not? I think you were told to avoid using SOs for mutable data.
Okay, but from what I see so far, your events are immutable data.
Honestly, everyone in code-begginer told me to not use SOs for that
Saying it could have problems in memory and such
I don't really understand why
Oh
I remember
sorry, i'm so sleepy
Essentially, I wanted to make my CardDataSO mutable to use it as my main way of handling mutable card data
And that's not a good thing, as stated before.
So I uncoupled the CardData logic from CardDataSO
So far so good
problem is when I try to pass the EventsSO to the Events
Ok, now i get the problem
I DON'T have to have a non SO Events xD
Because, like you said, my events are not mutable
theoreticaly, at least
so it' okay to construct SOs and pass them around in the code, as long as they don't mutate?
I had understood that it would lead to major memory leaks later on so that's why I stopped using them
Ok, maybe i got it
Hold on
So when building a scriptable object directly into the inspector the Awake() gets called instead of the constructor, right?
OKAY
I found the problem
Thing is, each Event does something for a player
or against a player
But that can't always be known beforehand
at least not in the way I had structured my events
Because I have this:
[CreateAssetMenu(fileName = "Summon Card Play Event", menuName = "Game Events/Summon Card Play Event")]public class SummonCardPlayEvent : GameEvent
{
private readonly Player player;
[SerializeField] private CardData cardData;
public SummonCardPlayEvent(Player player, CardData cardData)
{
this.player = player;
this.cardData = cardData;
}
public override IEnumerator ExecuteCoroutine()
{
Board.Instance.SummonCard(player, cardData);
yield return null;
}
public override IEnumerator UndoCoroutine()
{
throw new System.NotImplementedException();
}
}
I know which card to summon (because CardData is serializable) but not to which player (because Player is not)
And my logic resolves that at RunTime
That's the problem I was trying to solve!
Thing is, I should decide beforehand which player I should summon my card to!
That is why I was having all this trouble in the first place
Bad architecture
The big question is, why can't I solve the problem on my own but when someone asks me questions suddenly I crack it down?
Always the same thing
MAkes me think something is wrong with me
You should probably avoid using constructors with unity objects(including SO).
You could have a method ExecuteEvent In the GameEvent that takes both the invoking and the target player as a parameter.
Because questions from outside make you change your way of thinking and notice things you wouldn't otherwise.
As you get more experienced, you'll learn to notice this kind of things on your own more.
Okie dokie!
You're right! I don't know everything and sometimes outside help is all I need.
Thank you for your help, friend!
Why do you recommend that I avoid using constructors in Unity?
I've been recommended that before but I don't understand it
Not in unity. Just with whatever inherits from unity Object.
Because you don't create unity objects like that. Unity calls the constructor internally as part of initializing the object. There's a lot of processing involved behind the scene that is not exposed to us.
You calling the constructor manually, bypasses some of the necessary initialization process.
Oh ok
So, theoreticaly, I should always have an INitiate function if I want to initialize my values??
Yes.
Understood.
Again, that only applies to Objects. So mostly MonoBehaviour and ScriptableObject.
Got it.
I really like your suggestion of implementing a Execute methods that takes both players as a parameter
It really solves the problem of having to decide who will be the caller of the event and who will be target at run time, even if there are neither caller or target.
Huge problem, though!
That breaks the Command Pattern I implemented
We go back to my starting problem
How does it break it?
private IEnumerator DispatchActions()
{
while (GameEventsQueue.Count > 0)
{
eventsDispatching = true;
GameEvent nextEvent = PopEvent();
EventsRecorder.Instance.Record(nextEvent);
yield return StartCoroutine(nextEvent.ExecuteCoroutine());
}
eventsDispatching = false;
yield break;
}
There's no way to know at runtime who the invoker and target are without specifying it on the command!
It's a command!
IT's written already.
X kills Y.
BeginTurn.
X Damages Y.
The variables should already be known right at the creation of the command.
Which means, my commands should be more specific!
I should really have a factory.
I see, so you need the command to be mutable.
Maybe that solves the problem, because the factory jsut takes the caller and target as input and outputs a immutable command.
But then that's not a command!
I'd need something else which is more malleable than a command.
Why not?
Because a command is concrete
A command carrying some context is still a command
What I need is a command template.
I think I know what to do: A command factory Scriptable Object!
What I'd do, is either have a runtime wrapper for the command that would hold additional runtime data, like who dispatched the event and who it's target.
Or have that same data in a Context object passed with the command.
That should solve the problem nicely.
Could be.
I mean, it is that.
Because what I really need is a wrapper, like you described.
If you think too much in terms of design patterns, you'll miss the particular details required for your specific use case.
Got it.
This is a bad vice I have!
IT's a lot easier with a factory tho
I mean, whatever.
I'll just make a wrapper and see what I get.
I'm not saying you shouldn't use it. It's just that mentioning a design pattern doesn't really tell me anything about the way you want to implement it.
Thing is. I have two options:
- I change the Scriptable Object to the Wrapper and maintain my code logic.
- I maintain the Scriptable Object I have and modify my code logic to work with the wrapper instead.
- is Factory.
I MUCH prefer it, because I only have to modify the creation part of the Command.
Then all I have to do is modify the parts of the code in which the Commands are being instantiated.
Sounds easy enough.
I'll go with that.
So, basically, what I'm gonna do:
Step1:
Create an abstract 'factory' class to instatiate concrete 'event factories'.
Example: abstract Event'Factory' <---- BeginTurnEvent'Factory'
Step 2:
Transform it into an SO and modify CardDataSO and CardData.
Would be easier to just implement a CreateRuntimeEvent inside each of the SO events and passing in whatever they might need. It would then return a wrapper of the event.
Step 3:
Modify each part of the code that instantiates a new command so that my factory instantiates it, to maintain SRP.
Sounds like a good idea!
But that would need me to change all the code that deals with GameEvent Objeccts
Unless!
CreateRuntimeEvent returned Game Event Objects!
How do I instantiate a SO without a constructor tho?
It needs a template, right?
But then if I create it like this I'll have to modify it!
And I can't, because Scriptable Objects are not good for mutability, are they?
I think I should create another Class for this.
Yes. That's what the wrapper should be for.
Again
I ran into the same problem
How do I know which wrapper to use?
All I have is General GameEvent Objects
I think I need to tag them with a variable then select which wrap I'll use
Probably with a switch statement or something like this.
You'll need to share some code for me to provide any answer
!code
📃 Large Code Blocks
Use links to services like:
https://gdl.space/, https://paste.ofcode.org/, https://hatebin.com/, https://paste.myst.rs/, https://hastebin.com/
📃 Inline Code
Surround code with three backquotes. Not quotation marks.
To format as C#, add cs to the first line:
```cs
// Your code here
```
Add a comment with a line number if there is an error message.
At the Wrap method
I can't decipher which gameEvent is which so I can wrap them properlu
public class SummonEventWrapper : GameEventWrapper
{
private Player invoker;
private CardData cardData;
public SummonEventWrapper(Player invoker, CardData cardData)
{
this.invoker = invoker;
this.cardData = cardData;
}
public override IEnumerator ExecuteCoroutine()
{
Board.Instance.SummonCard(invoker, cardData);
yield return null;
}
public override IEnumerator UndoCoroutine()
{
throw new System.NotImplementedException();
}
}
[CreateAssetMenu(fileName = "Summon Event", menuName = "Game Events/Summon Card Play Event")]
public class SummonEvent : ScriptableObject { }
Example of a Event and EventWrapper classes 👆
The CardData class is the one that's going to store which behaviours it has.
It essentialy reads a CardDataSO object to do that.
Because, CardData is mutable.
//Command Pattern
public abstract class GameEventWrapper
{
public abstract IEnumerator ExecuteCoroutine();
public abstract IEnumerator UndoCoroutine();
}
public abstract class GameEvent : Scripta
Game Event and GameEventWrapper classes 👆
GameEvent should have a method that creates the corresponding runtime event/wrapper.
Also, I'm not sure what you're doing with this method:
private List<GameEventWrapper> Wrap(List<GameEvent> gameEvents)
{
return gameEvents.Select(x => new GameEventWrapper(x)).ToList();
}
I'm just wrapping the event
IT's not complete, I just stopped like that because I realized it wasn't going nowhere
How do I proceeed from this?
public abstract class GameEvent : ScriptableObject
{
private object obj;
private EVENT_TYPE type;
public GameEventWrapper Wrap()
{
return type switch
{
EVENT_TYPE.beginTurn => new BeginTurnEventWrapper(),
EVENT_TYPE.cardDiscard => new CardDiscardEventWrapper(),
_ => null
};
}
}
CardDiscardEventWrapper requires different parameters than BeginTurnEventWrapper to be constructed
But the one building it is a GameEvent object, which has no clue which event it is and which parameters it has received
Maybe it can have a big dict of Event types that lead to a list of those event types
But then their order relation would be lost
I could keep their original order in another array as the Event is created
maybe I could have a big array with all items I could need rn, but that doesn't really scale
The dict it is then?
Wait I"m thinking about the entire CardData
I need to think of just the parameters of the Event
Why return a type when you can return an instance of the runtime event?
I don't follow.
That's where inheritance comes into play
Wrap in GameEvent should be abstract.
And the inheriting type should provide the correct implementation.
What kind of parameters are they? Can they be accessed from the player data or something?
for example, DamageEvent has a Invoker, Target and Damage parameters
BeginTurnEvent has no parameters
while DiscardCardEvent has Invoker and numberOfDiscards parameters
What determines the Damage?
whoever creates the Command decides that
Or number of discards
So it can be retrieved from either the card data or the player data?
Yes, Or summons on board
You have several options:
- Pass the player/card/board data into every
Wrap(bad naming) method, so that each specific event can retrieve the data needed for it. - Create an abstract
EventParameterclass and inherit it with specific parameter classes that contain the require data and pass it into theWrapmethod
Ok I like the second option better
I was gonna suggest the first
Also, why is Wrap a bad naming convention?
Not a convention. But I would name it something like CreateEvent, since that's what it does basically.
I think I'll go with WrapEvent
Wrapper and Wrap doesn't really explain what that class does in the specific context
I think the issue is that you consider the SO and Event.
Main thing is that there is this thing that the card does that does not quite have a name yet.
I'd rather call it an EventDefinition or something
While the runtime class is the actual event
Now how am i gonna know which parameter to pass lmaooo
this looks like I'm passing responsability ever forward
like an endless hotel
I"m thinking just checking which kind of event it is with an event tag
or maybe going with the first suggestion, because I'm seeing that I'm only enqueuing events for each player
maybe something like (Player, generic event, Target)
The first and third are always known, the ssecond can be inside de class implementation
Anyways, I'm done for today
Thank you for the help!
Wdym? Provide some code.
private void EnqueueBeginTurnEvents()
{
foreach (Player player in players)
{
foreach (Canvas canvas in Board.Instance.summons[player.id])
{
CardData data = canvas.GetComponentInChildren<Summon>().data;
EventsQueue.Instance.EnqueueEvents(data.BeginTurnCardEffects); // this line won't work because Card Effects haven't been wrapped by an Event
}
}
}
The problem is all I have is a List<EventPrototype> interface in BeingTurnCardEffects.
And each EventPrototype has an CreateEvent which expects a specific implementation of EventParameters.
I can't know that implementation here.
But I need to know what Event I'm gonna execute right before I enqueue.
And, in this case, the 'BeginTurnEvents' being enqueued need to be determined right when EnqueueBeginTurnEvents function is called.
So one option would be to implement something inside EventPrototype to get what kind of parameters it's expecting and how to generate them. I could split the generation part outside of the EventPrototype code.
So, something like an EventParameterGenerator that has a EventParameters GenerateParameters(EventPrototype prototype) method.
I know, what if, everytime an Event is Executed, it has a ReceiveInput() which will start an input collection?
Then each event can collect input according to its requirements.
oh btw I think I found a way