Unity肉鸽卡牌开发记录01-卡牌SO的构建和CSV文件读取

构建卡牌SO

  • 使用ScriptableObject来保存卡牌的数据
  • 卡牌有多种类型,一些类型的卡牌有自身独有的属性(如怪兽卡有怪兽种族)
  • 构建卡牌基类SO,之后每种类型的卡牌继承他并创建各自独有的属性

  • 卡牌的类型、所属阵营、品质、怪兽卡种族、怪兽职业使用枚举类型表示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 卡牌基类
/// </summary>
public class Card : ScriptableObject
{
/// <summary>
/// 卡牌Id
/// </summary>
public int cardId;

/// <summary>
/// 法力点数消耗
/// </summary>
public int pointCost;

/// <summary>
/// 卡牌名称
/// </summary>
public string cardName;

/// <summary>
/// 卡牌图片
/// </summary>
public Sprite icon;

/// <summary>
/// 卡牌类型
/// </summary>
public CardType type;

/// <summary>
/// 卡牌能力描述文本
/// </summary>
public string ability;

/// <summary>
/// 卡牌简介描述文本(风味文字)
/// </summary>
public string introduce;

/// <summary>
/// 卡牌阵营
/// </summary>
public Camp camp;

/// <summary>
/// 卡牌品质
/// </summary>
public Quality cardQuality;
}

/// <summary>
/// 卡牌类别枚举
/// </summary>
public enum CardType
{
Monster, //怪物卡
Magic, //魔法卡
Equipment, //装备卡
Weapon, //武器卡
Trap, //陷阱卡
Arena //场地卡
}

/// <summary>
/// 卡牌阵营枚举
/// </summary>
public enum Camp
{
General, //通用
WeaponMaster, //武器大师
Summoner, //召唤师
Necromancer, //死灵术士
TrapArtisan, //陷阱工匠
Rouge //盗贼
}

/// <summary>
/// 卡牌品质枚举
/// </summary>
public enum Quality
{
Common, //普通
Rare, //非普通
Epic, //史诗
Legend //传说
}

读取CSV文件,自动创建卡牌SO

  • 从CSV文件读取卡牌数据,自动创建或更新SO
  • Resources.Load<TextAsset>("")读取CSV文件
  • Resources.Load<Monster>("")加载已有的SO,File.Exists(cardPath)判断卡牌是否已经存在
  • ScriptableObject.CreateInstance<Monster>()创建SO实例
  • AssetDatabase.CreateAsset将内存中的SO保存为asset文件,保存后记得AssetDatabase.SaveAssets()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;

public class CsvToSO : MonoBehaviour
{
public CardList cardList;

private TextAsset csvData;

private void Start()
{
cardList.cardList.Clear(); //先清空cardList中的数据
ReadCsv();
}

/// <summary>
/// 从CSV文件读取数据
/// </summary>
void ReadCsv()
{
//从csv文件中读取数据
csvData = Resources.Load<TextAsset>("CardInfo");
//以\n划分csv数据,将每行划分出来
string[] csvRow = csvData.text.Split('\n');
//每行中包含的卡牌数据数组
string[] dataPerRow;
//循环处理每行数据,即处理每张卡牌
foreach (var row in csvRow)
{
//以逗号分隔
dataPerRow = row.Split(',');
//跳过第一行
if(dataPerRow[0] == "卡牌ID")
{
continue;
}
else
{
//对每种卡牌类型进行处理
switch (dataPerRow[3])
{
case "怪兽卡":
HandleMonsterCard(dataPerRow);
break;
case "魔法卡":
HandleMagicCard(dataPerRow);
break;
case "装备卡":
HandleEquipmentCard(dataPerRow);
break;
case "武器卡":
HandleWeaponCard(dataPerRow);
break;
case "陷阱卡":
HandleTrapCard(dataPerRow);
break;
case "场地卡":
HandleArenaCard(dataPerRow);
break;
default:
Debug.Log("卡牌ID:" + dataPerRow[0] + "数据有误");
break;
}
}
}
Debug.Log("CSV文件处理完毕");
}

/// <summary>
/// 处理怪兽卡数据行
/// </summary>
/// <param name="monsterRow"></param>
void HandleMonsterCard(string[] monsterRow)
{
string cardPath = Application.dataPath+"/Resources/ScriptableObj/Cards/MonsterCards/" + monsterRow[1]+".asset";
//如果该卡牌已经存在
if(File.Exists(cardPath))
{
Debug.Log("卡牌:" + monsterRow[2] + " 已经存在,正在更新数据");
Monster monster = Resources.Load<Monster>("ScriptableObj/Cards/MonsterCards/" + monsterRow[1]);
HandleGeneralData(monsterRow, monster, CardType.Monster);
monster.atk = int.Parse(monsterRow[9]);
monster.hp = int.Parse(monsterRow[10]);
monster.shield = int.Parse(monsterRow[11]);
//末尾去除换页符
monsterRow[13] = monsterRow[13].Split('\r')[0];
monster.race = CardEnumConvertor.GetInstance().StringToMonsterRaceEnum(monsterRow[12], monsterRow[0]);
monster.duty = CardEnumConvertor.GetInstance().StringToMonsterDutyEnum(monsterRow[13], monsterRow[0]);
//保存
EditorUtility.SetDirty(monster);
AssetDatabase.SaveAssetIfDirty(monster);
AssetDatabase.Refresh();

Debug.Log("卡牌:" + monsterRow[2] + " 数据更新完毕");
}
else //否则创建新的SO.asset
{
Monster monster = ScriptableObject.CreateInstance<Monster>();
//读取数据
HandleGeneralData(monsterRow, monster, CardType.Monster);
monster.atk = int.Parse(monsterRow[9]);
monster.hp = int.Parse(monsterRow[10]);
monster.shield = int.Parse(monsterRow[11]);
//末尾去除换页符
monsterRow[13] = monsterRow[13].Split('\r')[0];

monster.race = CardEnumConvertor.GetInstance().StringToMonsterRaceEnum(monsterRow[12], monsterRow[0]);
monster.duty = CardEnumConvertor.GetInstance().StringToMonsterDutyEnum(monsterRow[13], monsterRow[0]);
//存入AssetDatabase
AssetDatabase.CreateAsset(monster, "Assets/Resources/ScriptableObj/Cards/MonsterCards/" + monsterRow[1] + ".asset");
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}

//存入cardList
Monster monsterCard = Resources.Load<Monster>("ScriptableObj/Cards/MonsterCards/" + monsterRow[1]);
cardList.cardList.Add(monsterCard);
Debug.Log("卡牌ID"+monsterRow[0]+",卡牌名称:" + monsterRow[2] + " 处理完毕");
}

/// <summary>
/// 处理通用的数据
/// </summary>
void HandleGeneralData(string[] dataRow,Card card,CardType cardType)
{
//卡牌通用数据
card.cardId = int.Parse(dataRow[0]);
card.cardName = dataRow[2];
card.type = cardType;
card.pointCost = int.Parse(dataRow[4]);
switch (dataRow[5])
{
case "通用":
card.camp = Camp.General;
break;
case "武器大师":
card.camp = Camp.WeaponMaster;
break;
case "召唤师":
card.camp = Camp.Summoner;
break;
case "死灵术士":
card.camp = Camp.Necromancer;
break;
case "陷阱工匠":
card.camp = Camp.TrapArtisan;
break;
case "侠盗":
card.camp = Camp.Rouge;
break;
default:
card.camp = Camp.General;
break;
}
card.ability = dataRow[6];
card.introduce = dataRow[7];
switch (dataRow[8])
{
case "普通":
card.cardQuality = Quality.Common;
break;
case "稀有":
card.cardQuality = Quality.Rare;
break;
case "史诗":
card.cardQuality = Quality.Rare;
break;
case "传说":
card.cardQuality = Quality.Legend;
break;
default:
break;
}
}

}
  • 处理其他类型卡牌的逻辑类似

注意:对于已经存在的卡牌SO,在修改其属性后要标记其为脏数据并保存

1
2
3
4
//保存
EditorUtility.SetDirty(monster); //标记其为脏数据
AssetDatabase.SaveAssetIfDirty(monster); //将脏数据保存,写到磁盘
AssetDatabase.Refresh(); //刷新数据

CardListSO:用于存储所有的卡牌,在游戏中可以随时取用

1
2
3
4
5
6
7
8
9
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu(menuName ="CardList")]
public class CardList : ScriptableObject
{
public List<Card> cardList = new List<Card>();
}

MenuItem是在UnityEditor命名空间下的一个类,它允许你可以在Unity编辑器中添加自定义的菜单,同时也可以允许添加快捷键。

注意只适用于静态方法。

1
2
3
4
5
[MenuItem("ReadCsv/ReadCsvDataToSO")]
static void ReadCsvDataToSO()
{
ReadCsv();
}

TODO:

  • [ ] 目前暂无卡牌图片素材,待卡牌图片素材产出后添加自动设置Card类icon字段的功能
  • [ ] 优化CSVToSO脚本,使之在Editor中就可以运行