Solved: Serialization for dummies. Aka me. So I can use a list<MyClass> in a prefab

Hi.

I’ve taken no formal coding classes and have been working by the seat of my pants for some time now. Obviously, that means I will run into walls which seem basic, but are indecipherable for me. Today I ran into one such issue with serialization.

A warning, right out, that the code I’ll be posting below is probably ugly, sloppy, and all around inefficient. but It makes sense to me, and I’m working alone.

That said, my issue stems from a need to have a Prefab gameobject, which among other components includes a “Card” component which has a handful of variables. One of these is a List. Actions are how cards (and other parts of my game) actually interact with characters. So far, my Action class is working well!.. when I freshly make a card and immediately use it.

If I either duplicate/clone the card and try to use the clone version, the List is null and nothing happens. The same goes for if I save the made card into a prefab, then instantiate one into my hand. I did what I normally do here, googled similar cases for a few hours, but all I could see is that I need to Serialize my Action class, otherwise Unity editor can not/will not save the class information of each action in the list.

HOWEVER, besides some very basic save/load features which use persistentDataPath I have no idea what do to with serialization. Google lead me to documentation on the matter, but honestly the implementation was lost on me entirely.

SO any help would be Appreciated, I’m not sure if there needs to be multiple things injected into my code, so Ill put the entire action class below. again, sorry if it’s ugly code, and thankyou for your time.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Runtime.Serialization;
using System;

[Serializable]
public class Action
{
    public int value { get; set; }
    public int duration { get; set; }
    public string type { get; set; }

    GameObject source; //of the action, either passive or card

    public Action(int isValue, string isType)
    {
        value = isValue;
        type = isType;
    }


    public void Commit(Character sourceCharacter, List<Character> targets, Card cardSource)  //will need a 2nd void commit without card source
    {
        if (type == "Attack")
        {
            double dmg = value + sourceCharacter.stats[1].returnCurrentStat() + sourceCharacter.status[6];    //get damage with STR and empowered
            if (sourceCharacter.status[7] >= 1)
            {
                dmg = dmg * 1.25;
                Mathf.Round((float)dmg); //adds Enrage
            }
            foreach (Character M in targets)
            {
                M.takeDamage(dmg, sourceCharacter);
                foreach (Passive check in M.markedBy)
                {
                    if (check.trigger == "Attacked")
                    {
                        check.activate(sourceCharacter);
                    }
                }
            }
            foreach (Passive check in sourceCharacter.markedBy)
            {
                if (check.trigger == "PlayAttack")
                {
                    check.activate(sourceCharacter);
                }
            }
         

        }
        if (type == "SpellAttack")
        {
            double dmg = value + sourceCharacter.stats[3].returnCurrentStat() + sourceCharacter.status[5];    //get damage with INT and Enlightened
            if (sourceCharacter.status[3] >= 1)
            {
                dmg = dmg * 1.25;
                Mathf.Round((float)dmg); //add Clearminded
            }
            foreach (Character c in targets)
            {
                c.takeDamage(dmg, sourceCharacter);
                foreach (Passive check in c.markedBy)
                {
                    if (check.trigger == "SpellTargeted")
                    {
                        check.activate(sourceCharacter);
                    }
                }
            }
            foreach (Passive check in sourceCharacter.markedBy)
            {
                if (check.trigger == "PlaySpell")
                {
                    check.activate(sourceCharacter);
                }
            }
        }
        if (type == "Heal")
        {
            double heal = value + sourceCharacter.stats[3].returnCurrentStat() + sourceCharacter.status[5];    //get damage with INT and Enlightened
            if (sourceCharacter.status[3] >= 1)
            {
                heal = heal * 1.25;
                Mathf.Round((float)heal); //add Clearminded
            }
            foreach (Character c in targets)
            {
                c.healDamage((int)heal);
            }
        }
        if (type != "Attack" && type != "SpellAttack" && type != "ExtraDamage" && type != "Heal" && type != "LoseLife" &&type != "Block" && type != "SaveBlock" && type != "Draw" && type != "Discard" && type != "MoveCard" && type != "AddCard" && type != "ImrpoveCard" && type != "ChangeCost" && type != "Gain Energy" && type != "RetainCard" && type != "RepeatEffect" && type != "Exhaust" && type != "ReturnToDeck")   //STATUS
        {
            int statRef = 0;
            if (type == "Blind")
            { statRef = 0; }
            if (type == "Burned")
            { statRef = 1; }
            if (type == "Charmed")
            { statRef = 2; }
            if (type == "Clearminded")
            { statRef = 3; }
            if (type == "Elusive")
            { statRef = 4; }
            if (type == "Enlightened")
            { statRef = 5; }
            if (type == "Empowered")
            { statRef = 6; }
            if (type == "Enraged")
            { statRef = 7; }
            if (type == "Fragile")
            { statRef = 8; }
            if (type == "Frail")
            { statRef = 9; }
            if (type == "Haste")
            { statRef = 10; }
            if (type == "Incapacitated")
            { statRef = 11; }
            if (type == "Invisible")
            { statRef = 12; }
            if (type == "Paralyzed")
            { statRef = 13; }
            if (type == "Petrified")
            { statRef = 14; }
            if (type == "Poisoned")
            { statRef = 15; }
            if (type == "Regen")
            { statRef = 16; }
            if (type == "Slow")
            { statRef = 17; }
            if (type == "Weak")
            { statRef = 18; }
            foreach (Character c in targets)
            {
                c.takeStatus(statRef,value);
                foreach (Passive M in sourceCharacter.markedBy)
                {
                    if (M.trigger == "Apply" + type)
                    {
                        M.activate(c);
                    }
                }
                foreach (Passive M in c.markedBy)
                {
                    if (M.trigger == "Gain" + type)
                    {
                        M.activate(sourceCharacter);
                    }
                }
            }
        }
        if (type == "Draw")
        {
            foreach (Character Z in targets)
            {
                Z.drawCard(value);
                foreach (Passive M in Z.markedBy)
                {
                    if (M.trigger == "Draw")
                    {
                        M.activate(Z);
                    }
                }
            }
        }
        if (type == "Discard")
        {
            foreach (Character R in targets)
            {
                List<Card> toBeDiscarded = new List<Card>();
                //pick cards
                Card dummyCard = new Card("Dummy", 0);    // move this to Character
                toBeDiscarded.Add(dummyCard);
                foreach (Card U in toBeDiscarded)
                {
                    R.discardCard(U);
                }

            }
        }
        if (type == "MoveCard") //amount 0 = grave to hand, 1 = grave to deck, 2 = grave to exhausted, 3 = deck to hand, 4 = deck to discard, 5 = Deck to Exhausted, 6 = exhausted to hand,
                                //7 = exausted to deck, 8 = exausted to discard, 9 = hand to Deck, 10 = hand to discard, 11 = Hand to exhausted
        {
            foreach (Character P in targets)
            {
                Card dummycard = new Card("dummy", 0);
                //pick card to move
                if (value == 0)
                { P.deckDiscard.moveCard(dummycard, P.deckHand); }
                if (value == 1)
                { P.deckDiscard.moveCard(dummycard, P.deckMain);
                    foreach (Passive Y in P.markedBy)
                    {
                        if (Y.trigger == "MoveToDeck")
                        {
                            Y.activate(P);
                        }
                    }
                }
                if (value == 2)
                { P.deckDiscard.moveCard(dummycard, P.deckExausted); }
                if (value == 3)
                { P.deckMain.moveCard(dummycard, P.deckHand); }         //POSSIBLY MOVE TO CHARACTER
                if (value == 4)
                { P.deckMain.moveCard(dummycard, P.deckDiscard); }
                if (value == 5)
                { P.deckMain.moveCard(dummycard, P.deckExausted); }
                if (value == 6)
                { P.deckExausted.moveCard(dummycard, P.deckHand); }
                if (value == 7)
                { P.deckExausted.moveCard(dummycard, P.deckMain);
                    foreach (Passive Y in P.markedBy)
                    {
                        if (Y.trigger == "MoveToDeck")
                        {
                            Y.activate(P);
                        }
                    }
                }
                if (value == 8)
                { P.deckExausted.moveCard(dummycard, P.deckDiscard); }
                if (value == 9)
                {
                    P.deckHand.moveCard(dummycard, P.deckMain);
                    foreach (Passive Y in P.markedBy)
                    {
                        if (Y.trigger == "MoveToDeck")
                        {
                            Y.activate(P);
                        }
                    }
                }  //0 = shuffle, 1 = top, 2 = bottom   REMOVED WHERE, FIX
                if (value == 10)
                { P.deckHand.moveCard(dummycard, P.deckDiscard); }
                if (value == 11)
                { P.deckHand.moveCard(dummycard, P.deckExausted); }
            }
        }
        if (type == "Block")
        {
            sourceCharacter.gainBlock(value);
        }
        if (type == "GainEnergy")
        {
            foreach(Character T in targets)
            {
                T.gainEnergy(value);
            }
        }
        if (type == "Retain")
        {
            foreach (Character R in targets)     //value 0 = sourceCard      value 1 = choose card   value 2 = all cards
            {
                if(value == 0)
                {
                    cardSource.retain++;
                }
                if (value == 1)
                {
                    R.chooseCardInHand().retain++;
                }
                if (value == 2)
                {
                    foreach (Card I in R.deckHand.thisDeck)
                    {
                        I.retain++;
                    }
                }
            }
        }


    }

}

If you are not using polymorphism in your Action class you can directly serialize this class with Unity’s serializer. What you have to do is create a backing field for your properties, as properties are not supported.

[SerializeField]
private int m_Int;

public int M_Int {get {return m_Int;}  set {m_Int = value;}}

Of course in the way you use your properties you could always turn them to public fields which will get serialized without the need of the above.

All you need to know on how and what Unity can serialize.

was in the middle of marking solved. Ty for the reply, but Unity simply would not allow my to serialize the List itself. So I implemented a kind of janky work-around of separating the Action classes variables into separate lists, with matching indexes.

Unfortunately, now my profiler is hanging on editor exit and it shoots memory usage through the roof. So ive still got something to fix.