Skip to content

Instantly share code, notes, and snippets.

@LuizMoratelli
Last active March 26, 2024 13:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save LuizMoratelli/cda917a960c9ddfb413e6f9bb4e17504 to your computer and use it in GitHub Desktop.
Save LuizMoratelli/cda917a960c9ddfb413e6f9bb4e17504 to your computer and use it in GitHub Desktop.
[TcgEngine] Custom Mana System
public class AbilityData {
public int mana_cost
{
get
{
return manas.Sum(mana => mana.quantity);
}
}
public CardManaData[] manas;
// ...
}
private void CalculateNode(Game data, NodeState node)
{
//...
bool is_full_mana = HasAction(action_list, GameAction.PlayCard) && player.ManaMax() >= player.Mana();
//...
}
public class Card
{
public List<CardMana> manas = new List<CardMana>();
public List<CardMana> manas_ongoing = new List<CardMana>();
public virtual void ClearOngoing()
{
// ...
manas_ongoing.Clear();
}
public virtual int GetGenericMana()
{
int total = 0;
foreach (var mana in manas)
{
if (mana.id == "")
total += GetMana(mana.id);
}
return Mathf.Max(total, 0);
}
public virtual int GetMana()
{
int total = 0;
foreach (var mana in manas)
{
total += GetMana(mana.id);
}
return Mathf.Max(total, 0);
}
public virtual int GetMana(string mana_id)
{
var mana = manas.FirstOrDefault(mana => mana.id == mana_id)?.quantity ?? 0;
var mana_ongoing = manas_ongoing.FirstOrDefault(mana => mana.id == mana_id)?.quantity ?? 0;
return Mathf.Max(mana + mana_ongoing, 0);
}
public virtual void SetCard(CardData icard, VariantData cvariant)
{
// ...
SetManas(icard);
}
public void SetManas(CardData icard)
{
manas.Clear();
foreach (CardManaData mana in icard.manas)
{
var cmana = new CardMana(mana?.mana?.id ?? "", mana.quantity);
manas.Add(cmana);
}
}
public string MainMana(ManaData mana_data)
{
return mana_data != null ? mana_data.id : manas.FirstOrDefault()?.id;
}
public void AddMana(ManaData mana_data, int value, bool add = true)
{
var mana_key = MainMana(mana_data);
if (mana_key == null) return;
var mana_index = manas.FindIndex(mana => mana.id == mana_key);
if (mana_index == -1)
{
manas.Add(new CardMana(mana?.mana?.id ?? "", value));
}
else
{
if (add)
manas[mana_index].quantity += value;
else
manas[mana_index].quantity = value;
manas[mana_index].quantity = Mathf.Max(manas[mana_index].quantity, 0);
}
}
public void AddOngoingMana(ManaData mana_data, int value, bool add = true)
{
var mana_key = MainMana(mana_data);
if (mana_key == null) return;
var mana_index = manas_ongoing.FindIndex(mana => mana.id == mana_key);
if (mana_index == -1)
{
manas_ongoing.Add(new CardMana(mana?.mana?.id ?? "", value));
}
else
{
if (add)
manas_ongoing[mana_index].quantity += value;
else
manas_ongoing[mana_index].quantity = value;
manas_ongoing[mana_index].quantity = Mathf.Max(manas_ongoing[mana_index].quantity, 0);
}
}
public static void Clone(Card source, Card dest)
{
// ...
CardMana.CloneList(source.manas, dest.manas);
CardMana.CloneList(source.manas_ongoing, dest.manas_ongoing);
}
// ...
}
public class CardStatus
{
public string mana_id; // only used for StatusType.Mana
public CardStatus(StatusType type, int value, int duration, string mana_id)
{
// ...
this.mana_id = mana_id;
}
public static void Clone(CardStatus source, CardStatus dest)
{
//...
dest.mana_id = source.mana_id;
}
}
[System.Serializable]
public class CardMana
{
public string id;
public int quantity;
public CardMana(string id, int quantity)
{
this.id = id;
this.quantity = quantity;
}
public static CardMana CloneNew(CardMana copy)
{
CardMana mana = new CardMana(copy.id, copy.quantity);
return mana;
}
public static void Clone(CardMana source, CardMana dest)
{
dest.id = source.id;
dest.quantity = source.quantity;
}
public static void CloneList(List<CardMana> source, List<CardMana> dest)
{
for (int i = 0; i < source.Count; i++)
{
if (i < dest.Count)
Clone(source[i], dest[i]);
else
dest.Add(CloneNew(source[i]));
}
if (dest.Count > source.Count)
dest.RemoveRange(source.Count, dest.Count - source.Count);
}
}
public class CardData {
public int mana
{
get
{
return manas.Sum(mana => mana.quantity);
}
}
public CardManaData[] manas;
}
[Serializable]
public class CardManaData
{
public ManaData mana;
public int quantity;
}
public class CardUI : MonoBehaviour, IPointerClickHandler
{
// ...
public Image cost_icon2;
public Text cost2;
public void SetCard(Card card)
{
// ...
if (cost != null)
cost.text = card.manas.Count() > 0 ? card.manas.First().quantity.ToString() : "0";
if (cost2 != null && card.manas.Count() > 1)
cost2.text = card.manas.Last().quantity.ToString();
//...
}
public void SetCard(CardData card, VariantData variant)
{
//...
if (cost_icon != null)
{
if (card.manas.Count() > 0 && card.manas.First().mana != null)
cost_icon.sprite = card.manas.First().mana.icon;
else
cost_icon.sprite = GameplayData.Get().default_mana_icon;
cost_icon.enabled = card.type != CardType.Hero;
}
if (cost_icon2 != null)
{
if (card.manas.Count() > 1)
{
if (card.manas.Last().mana != null)
cost_icon2.sprite = card.manas.Last().mana.icon;
else
cost_icon2.sprite = GameplayData.Get().default_mana_icon;
cost_icon2.enabled = card.type != CardType.Hero;
}
else
{
cost_icon2.enabled = false;
}
}
//...
if (cost2 != null)
cost2.enabled = card.type != CardType.Hero && card.manas.Count() > 1;
if (cost != null)
cost.text = card.manas.Count() > 0 ? card.manas.First().quantity.ToString() : "0";
if (cost2 != null && card.manas.Count() > 1)
cost2.text = card.manas.Last().quantity.ToString();
//...
}
}
public class CollectionPanel: UIPanel
{
public ManaButton[] manas;
public void RefreshCards() {
// ...
foreach (ManaButton btn in manas)
btn.Deactivate();
// ...
foreach (ManaButton btn in manas)
{
int amount = deck.manas.Where(dmana => dmana.tid == btn.value).Count();
if (amount > 0)
{
btn.Activate();
btn.amount = amount;
btn.amount_txt.text = amount.ToString();
}
}
}
private void SaveDeck()
{
// ...
udeck.manas = GetSelectedManasId();
}
private UserManaData[] GetSelectedManasId()
{
var deck_manas = new List<UserManaData>();
foreach (ManaButton btn in manas)
{
if (btn.IsActive())
{
for (int i = 0; i < btn.amount; i++)
{
deck_manas.Add(new UserManaData(btn.value));
}
}
}
return deck_manas.ToArray();
}
}
// This is an example for condition, you must update all of them that related to Mana like ConditionPlayerStat, ConditionStat, ...
public class ConditionPlayerStat {
public ManaData mana_data;
// ...
public override bool IsTargetConditionMet(Game data, AbilityData ability, Card caster, Player target)
{
if (type == ConditionStatType.Mana)
{
var mana_key = target.MainMana(mana_data);
return CompareInt(target.manas[mana_key], oper, value);
}
}
}

My Custom Mana System, currently in progress.

  • UI in general could be improved and all feedback is appreciated;
  • CardUI do not show correct costs yet;
  • Not all features were fully tested.

Copy/Adapt all following scripts below.

On UI/Prefabs the changes were:

  • Create your Manas image
  • Create an UI for each cost, like: image
  • In PlayerUI (both) setup the mana UI image
  • Create Mana Buttons (attach the ManaButton Script) image
  • Add Mana Buttons to CollectionPanel image
  • Update CardUI and HandCard (In my case a card will have up to 2 different mana costs) image

Results:

  • Setup mana in deck image image
  • Receive more mana based on your deck image
  • Card Preview image
  • Generic Mana (will consume from 1st to last mana setup in deck) image
public void LoadData()
{
//...
public void LoadData() {
ManaData.Load();
//...
}
}
public class DeckData : ScriptableObject
{
//...
[Header("Mana")]
public ManaData[] manas;
//...
public bool IsValid()
{
return cards.Length >= GameplayData.Get().deck_size && manas.Length == GameplayData.Get().mana_deck;
}
//...
}
// This is an example for effect, you must update all of them that related to Mana like EffectAddStat, EffectAddStatRoll, ...
public class EffectAddStat: EffectData
{
public ManaData mana_data;
//...
if (type == EffectStatType.Mana)
target.AddManaMax(mana_data, ability.value);
//...
if (type == EffectStatType.Mana)
target.AddMana(mana_data, ability.value);
//...
if (type == EffectStatType.Mana)
target.AddOngoingMana(mana_data, ability.value);
}
public class GameLogic
{
public virtual void StartGame()
{
// ...
//Init each players
foreach (Player player in game_data.players)
{
// ...
foreach (var mana in player.deck_manas)
{
var dmana = player.deck_manas.FirstOrDefault(dmana => dmana.mana_id == mana.mana_id);
if (dmana != null)
{
player.manas_max.Add(mana.mana_id, GameplayData.Get().mana_start * dmana.quantity);
player.manas.Add(mana.mana_id, GameplayData.Get().mana_start * dmana.quantity);
}
}
}
// ...
}
public virtual void StartTurn()
{
//...
foreach (var mana in player.manas_max.ToList())
{
// Only upgrade max mana if associated with deck
var dmana = player.deck_manas.FirstOrDefault(dmana => dmana.mana_id == mana.Key);
if (dmana != null && dmana.in_deck)
{
player.manas_max[mana.Key] = Mathf.Min(mana.Value + (dmana.quantity * GameplayData.Get().mana_per_turn), GameplayData.Get().mana_max * dmana.quantity);
}
player.manas[mana.Key] = player.manas_max[mana.Key];
}
// ...
}
public virtual void SetPlayerDeck(Player player, DeckData deck)
{
//...
player.manas.Clear();
player.manas_max.Clear();
//...
foreach (ManaData mana in ManaData.mana_list)
{
if (mana != null)
{
bool in_deck = deck.manas.FirstOrDefault(dmana => dmana.id == mana.id) != null;
int amount = deck.manas.Where(dmana => dmana.id == mana.id).Count();
Mana amana = Mana.Create(mana, in_deck, amount);
player.deck_manas.Add(amana);
}
}
//...
}
public virtual void SetPlayerDeck(Player player, UserDeckData deck)
{
player.manas.Clear();
player.manas_max.Clear();
foreach (ManaData mana in ManaData.mana_list)
{
if (mana != null)
{
bool in_deck = deck.manas.FirstOrDefault(dmana => dmana.tid == mana.id) != null;
int amount = deck.manas.Where(dmana => dmana.tid == mana.id).Count();
Mana amana = Mana.Create(mana, in_deck, amount);
player.deck_manas.Add(amana);
}
}
}
protected virtual void AfterAbilityResolved(AbilityData iability, Card caster)
{
//...
//Pay cost
if (iability.HasTrigger(AbilityTrigger.Activate) || iability.HasTrigger(AbilityTrigger.None))
{
foreach (var mana in iability.manas)
{
if (mana.mana == null)
{
var mana_left = mana.quantity;
foreach (var pmana in player.manas.ToList())
{
int mana_to_pay = Mathf.Min(mana_left, pmana.Value);
player.manas[pmana.Key] -= mana_to_pay;
mana_left -= mana_to_pay;
}
}
else
{
var mana_key = player.MainMana(mana.mana);
player.manas[mana_key] -= mana.quantity;
}
}
caster.exhausted = caster.exhausted || iability.exhaust;
}
//...
}
//...
protected virtual void AddOngoingStatusBonus(Card card, CardStatus status)
{
if (status.type == StatusType.AddAttack)
card.attack_ongoing += status.value;
if (status.type == StatusType.AddHP)
card.hp_ongoing += status.value;
if (status.type == StatusType.AddManaCost)
{
ManaData mana_data = ManaData.mana_list.FirstOrDefault(mana => mana.id == status.mana_id);
card.AddOngoingMana(mana_data, status.value);
}
}
//...
}
public class GameplayData : ScriptableObject
{
// ...
public int mana_deck = 2; // Quantity of manas, like Coin + Mana
public Sprite default_mana_icon; // For Generic Mana
// ...
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
namespace TcgEngine.UI
{
public class ManaButton : MonoBehaviour
{
public string group;
public string value;
public Image active_img;
public Text amount_txt;
public int amount = 0;
public UnityAction<ManaButton> onClick;
private bool active = false;
private Button button;
private static List<ManaButton> toggle_list = new List<ManaButton>();
void Awake()
{
toggle_list.Add(this);
button = GetComponent<Button>();
button.onClick.AddListener(OnClick);
}
private void OnDestroy()
{
toggle_list.Remove(this);
}
void Start()
{
}
private void Update()
{
}
void OnClick()
{
amount = (amount + 1) % (GameplayData.Get().mana_deck + 1);
amount_txt.text = amount.ToString();
if (amount > 0)
Activate();
else
Deactivate();
if (onClick != null)
onClick.Invoke(this);
}
public void SetActive(bool act)
{
if (act) Activate();
else Deactivate();
}
public void Activate()
{
active = true;
if (active_img != null)
active_img.enabled = true;
}
public void Deactivate()
{
active = false;
amount_txt.text = "";
if (active_img != null)
active_img.enabled = false;
}
public bool IsActive()
{
return active;
}
public static int GetTotal(string group)
{
int count = 0;
foreach (ManaButton toggle in toggle_list)
{
if (toggle.group == group)
count += toggle.amount;
}
return count;
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Sirenix.OdinInspector;
using Sirenix.Utilities.Editor;
namespace TcgEngine
{
[CreateAssetMenu(fileName = "ManaData", menuName = "TcgEngine/ManaData", order = 6)]
public class ManaData : ScriptableObject
{
public string id;
public Sprite icon;
public static List<ManaData> mana_list = new List<ManaData>();
public static void Load(string folder = "")
{
if (mana_list.Count == 0)
{
mana_list.AddRange(Resources.LoadAll<ManaData>(folder));
}
}
}
[System.Serializable]
public class Mana
{
public string mana_id;
public string uid;
public bool in_deck;
public int quantity;
public Mana(string mana_id, string uid, bool in_deck, int quantity = 1)
{
this.mana_id = mana_id;
this.uid = uid;
this.in_deck = in_deck;
this.quantity = quantity;
}
public static Mana Create(ManaData imana, bool in_deck, int quantity = 1)
{
return Create(imana, GameTool.GenerateRandomID(11, 15), in_deck, quantity);
}
public static Mana Create(ManaData imana, string uid, bool in_deck, int quantity = 1)
{
Mana mana = new Mana(imana.id, uid, in_deck, quantity);
return mana;
}
}
}
public class Player
{
public Dictionary<string, int> manas = new Dictionary<string, int>();
public Dictionary<string, int> manas_max = new Dictionary<string, int>();
public virtual bool CanPayMana(Card card)
{
foreach (var mana in card.manas)
{
if (mana.id == "")
{
var mana_left = card.GetGenericMana();
foreach (var pmana in manas)
{
int mana_to_pay = Mathf.Min(mana_left, pmana.Value);
mana_left -= mana_to_pay;
}
if (mana_left > 0)
return false;
}
else
{
var mana_key = MainMana(mana.id);
if (manas[mana_key] < card.GetMana(mana_key))
return false;
}
}
return true;
}
public virtual void PayMana(Card card)
{
foreach (var mana in card.manas)
{
if (mana.id == "")
{
var mana_left = card.GetGenericMana();
foreach (var pmana in manas.ToList())
{
int mana_to_pay = Mathf.Min(mana_left, pmana.Value);
manas[pmana.Key] -= mana_to_pay;
mana_left -= mana_to_pay;
}
} else
{
var mana_key = MainMana(mana.id);
manas[mana_key] -= card.GetMana(mana_key);
}
}
}
public virtual bool CanPayAbility(Card card, AbilityData ability)
{
bool exhaust = !card.exhausted || !ability.exhaust;
foreach (var mana in ability.manas)
{
if (mana.mana == null)
{
var mana_left = mana.quantity;
foreach (var pmana in manas)
{
int mana_to_pay = Mathf.Min(mana_left, pmana.Value);
mana_left -= mana_to_pay;
}
if (mana_left > 0)
return false;
}
else
{
var mana_key = MainMana(mana.mana.id);
if (manas[mana_key] < mana.quantity)
return false;
}
}
return exhaust;
}
public string MainMana(ManaData mana_data)
{
return mana_data != null ? mana_data.id : deck_manas.First(dmana => dmana.in_deck).mana_id;
}
public string MainMana()
{
return deck_manas.First(dmana => dmana.in_deck).mana_id;
}
public string MainMana(string mana_id)
{
if (mana_id == "") return MainMana();
return deck_manas.First(dmana => dmana.in_deck && dmana.mana_id == mana_id).mana_id;
}
public void AddMana(ManaData mana_data, int amount, bool add = true)
{
var mana_key = MainMana(mana_data);
if (add)
manas[mana_key] += amount;
else
manas[mana_key] = amount;
manas[mana_key] = Mathf.Max(manas[mana_key], 0);
}
public int Mana()
{
int total = 0;
foreach (var mana in manas)
{
total += mana.Value;
}
return total;
}
public int ManaMax()
{
int total = 0;
foreach (var mana in manas_max)
{
total += mana.Value;
}
return total;
}
public int ManaMax(ManaData mana_data)
{
var mana_key = MainMana(mana_data);
var dmana = deck_manas.FirstOrDefault(dmana => dmana.mana_id == mana_key);
return GameplayData.Get().mana_max * dmana.quantity;
}
public void AddManaMax(ManaData mana_data, int amount, bool add = true, bool current = true)
{
var mana_key = MainMana(mana_data);
var dmana = deck_manas.FirstOrDefault(dmana => dmana.mana_id == mana_key);
if (current)
{
if (add)
manas[mana_key] += amount;
else
manas[mana_key] = amount;
manas[mana_key] = Mathf.Clamp(manas[mana_key], 0, GameplayData.Get().mana_max * dmana.quantity);
}
if (add)
manas_max[mana_key] += amount;
else
manas_max[mana_key] = amount;
manas_max[mana_key] = Mathf.Clamp(manas_max[mana_key], 0, GameplayData.Get().mana_max * dmana.quantity);
}
public static void Clone(Player source, Player dest)
{
// ...
CloneDict(source.manas_max, dest.manas_max);
CloneDict(source.manas, dest.manas);
}
public static void CloneDict(Dictionary<string, int> source, Dictionary<string, int> dest)
{
foreach (KeyValuePair<string, int> pair in source)
{
dest[pair.Key] = pair.Value;
}
}
// ...
}
// Update imports
using System.Linq;
using System;
// ...
public class PlayerUI : MonoBehaviour
{
public ManaUI[] manas;
// ...
void Update()
{
// ...
if (player != null)
{
pname.text = player.username;
UpdateMana(); // <- Added
// ...
}
}
private void UpdateMana()
{
Player player = GetPlayer();
if (ManaData.mana_list.Count == 0)
return;
for (int i = 0; i < player.deck_manas.Count(); i++)
{
if (player.manas.Count <= i) continue;
var mana_id = player.deck_manas[i].mana_id;
if (player.deck_manas[i].in_deck || player.manas[mana_id] > 0)
{
manas[i].mana_txt.text = player.manas[mana_id].ToString();
manas[i].mana_max_txt.text = "/" + player.manas_max[mana_id].ToString();
manas[i].mana_icon.sprite = ManaData.mana_list.First(mana => mana.id == mana_id).icon;
manas[i].mana_obj.SetActive(true);
}
else
{
manas[i].mana_obj.SetActive(false);
}
}
}
}
[Serializable]
public class ManaUI
{
public Text mana_txt;
public Text mana_max_txt;
public Image mana_icon;
public GameObject mana_obj;
}
[System.Serializable]
public class UserManaData : INetworkSerializable
{
public string tid;
public UserManaData() { tid = ""; }
public UserManaData(string id) { tid = id; }
public UserManaData(ManaData mana)
{
this.tid = mana != null ? mana.id : "";
}
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref tid);
}
}
public class UserDeckData : INetworkSerializable
{
//...
public UserManaData[] manas;
//...
public UserDeckData(string tid, string title)
{
//...
manas = new UserManaData[0];
}
public UserDeckData(DeckData deck)
{
//...
for (int i = 0; i < deck.manas.Length; i++)
{
manas[i] = new UserManaData(deck.manas[i]);
}
}
public bool IsValid()
{
return !string.IsNullOrEmpty(tid) && !string.IsNullOrWhiteSpace(title) && GetQuantity() >= GameplayData.Get().deck_size && manas?.Length == GameplayData.Get().mana_deck;
}
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
//...
NetworkTool.NetSerializeArray(serializer, ref manas);
}
public static UserDeckData Default
{
get
{
//...
deck.manas = new UserManaData[0];
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment