- Home /
Optimal architecture for card effects, prefabs or scriptableobjects?
Hi all, I am making a turn based CCG for fun and educational purposes. At the moment I have a system in place for creating card effects, it is usable but not up to the standard I would like to have.
Current System:
Each effect contains 3 main properties:
- A list of triggers
A trigger is an event in the game world
Example: When a card is played
- A list of conditions
Conditions are the necessary states needed in order to execute the effect action
Example: If the player has more than x life
- A list of effect actions
An effect action alters the state of the game/player/card in some way
Example: Deal x damage to the opponent
The problem with the current system:
This system requires me to create a new game object for each card effect. There is no need for this to be a game object. Also, any trigger,condition or effect action that the card uses must be attached to the game object. This can lead to a very cluttered inspector view on more complex effects. As previously stated, this works but I feel it can be improved upon and can't seem to figure out how.
Alternatives:
I have played around with scriptable objects without much success. For example, if I had an EffectAction class called DealDamageToPlayer, anytime I want to use this class on an effect; I would need to create a new instance because it would alter the original value. This would leave me with multiple copies of the same class all with different values. Also, I understand that this also occurs with the current system, when I attach the same script to multiple effect game objects. The difference is that it is not visible in the project explorer and does not result in clutter.
Similar Questions:
My original system was inspired by this question 'What is the most effective way to structure Card Effects in a Single Player game?' They ran into the same issue I mentioned in the alternatives section with regards to scriptable objects.
This question: 'Card Game, cards as ScriptableObject, card effects as ScriptableObjects, effects have different parameters. How to make an inspector UI support this structure?' includes the type of UI I am after. However, because they use scriptable objects there will be many duplicate instances.
If anything I said is incorrect, please do correct me.
Any and all assistance would be greatly appreciated.
Scripts:
Trigger.cs
public abstract class Trigger : MonoBehaviour
{
public abstract void SubscribeToEvent(Action<EventInfo> checkConditions);
public abstract void UnsubscribeFromEvent(Action<EventInfo> checkConditions);
}
Condition.cs
public abstract class Condition : MonoBehaviour
{
public abstract bool Requirement(EventInfo eventInfo);
}
EffectAction.cs
public abstract class EffectAction : MonoBehaviour
{
public abstract void ApplyEffectAction();
}
Effect.cs
public class Effect : MonoBehaviour
{
[Header("WHEN")]
public List<Trigger> triggers;
[Header("IF")]
public List<Condition> conditions;
[Header("DO")]
public EffectAction effectAction;
public void SetUpTriggers()
{
foreach (Trigger trigger in triggers)
{
trigger.SubscribeToEvent(CheckConditions);
}
}
public void RemoveTriggers()
{
foreach (Trigger trigger in triggers)
{
trigger.UnsubscribeFromEvent(CheckConditions);
}
}
public void CheckConditions(EventInfo eventInfo)
{
if (conditions.Count != 0)
{
foreach (Condition condition in conditions)
{
if (!condition.Requirement(eventInfo))
{
Debug.Log("CheckConditions: false");
return;
}
}
}
effectAction.ApplyEffectAction();
}
}
Hello, I am currently trying to make such a game too. Are you still active on this one? Is there a way I can communicate with you further?
Answer by xxmariofer · Jul 29, 2020 at 07:44 AM
Hello, i would go with a little different approach, but this is just an idea, i would make Effect class a ScriptableObject and add the createassetlabel to create instance of effects in the project folder. Second i would NOT create an instance for each EffectAction for example, step by step for what i would do, this example is only based on the EffectAction class, i would first create an enum with all the effects posible
public enum EffectDataType
{
heal,
damage,
powerup
}
second, when you are using instance of EffectAction i would simply use a struct, serializable so that can be edited in the scriptablobject inspector with all the damage/cost and data needed
[System.Serializable]
public struct EffectActionData
{
public EffectDataType type;
public int damage;
public int cost;
public string name;
}
then i would create your effectaction parent class and a damage child for example
public abstract class EffectAction
{
public abstract void ApplyEffectAction(EffectActionData data);
}
public abstract class DamageAction: EffectAction
{
public virtual void ApplyEffectAction(EffectActionData data)
{
Debug.Log("logic");
}
}
and finally create the effect scriptable object class
[CreateAssetMenu(fileName = "Data", menuName = "ScriptableObjects/Effect", order = 1)]
public class Effect : ScriptableObject
{
public EffectActionData cardEffect;
public void CheckConditions()
{
//if (conditions.Count != 0)
{
// foreach (Condition condition in conditions)
{
// if (!condition.Requirement(eventInfo))
{
Debug.Log("CheckConditions: false");
// return;
}
}
}
switch (cardEffect.type)
{
case EffectDataType.damage:
Debug.Log("Here you would call to the ApplyEffectAction method that could even be static so you dont need instances");
break;
case EffectDataType.heal:
break;
case EffectDataType.powerup:
break;
}
}
}
Thank you for the detailed response. After looking at your answer and discussing the problem with other game developers, I now see the initial problem with my implementation.
Because the EffectAction and value were included in one class, this required me to create a new instance of the class each time I wanted to use a different value. This is unnecessary as the only thing that is changing is the value that the action uses and not the actual function.
If I remove the value from the EffectAction class, I am now required to pass in a value to the ApplyEffect function. This creates a new problem as different EffectAction classes require different input parameters. As you demonstrated this can be solved by creating a struct to hold the required data, now we can pass the same object type to each EffectAction.
The next problem to tackle is how do we know which EffectAction to call. At the moment I am debating between your suggested method (Solution 1) and Solution 2.
Solution 1: In your answer you used a switch case and EffectActionType enum. The EffectActionType would allow me to select the type of EffectAction to call. To add a new EffectAction I would be required to add a new entry to the enum and the switch case.
Solution 2: $$anonymous$$ake the EffectAction a scriptable object, this allows me to drag the type of EffectAction I wish to use in the inspector. Although, now to use an EffectAction you are required to have a direct reference. Whether this might cause issues later on, I don't know. To add a new EffectAction I would only be required to create the new EffectAction class.
I am curious what you think of Solution 2. If you see any obvious problems with it, please do let me know.
i dont see any issues with your second approach, looks totally fine to me
Sounds good.
Thanks for the help I appreciate it.
Your answer
![](https://koobas.hobune.stream/wayback/20220613004305im_/https://answers.unity.com/themes/thub/images/avi.jpg)