创建卡牌游戏原型

First Post:

Last Update:

创建卡牌游戏原型

开发日志

日志4.14

没想到一个卡牌游戏项目的复杂程度能这么大,我感觉无从下手。

日志4.15

制作了简陋的UI界面,甚至几乎没有任何可以装载的图片。

跑去制作了一个Python程序,用来批量处理md文件。

日志4.16

开始参考NueDeck代码模板进行开发。

日志4.17

创建卡牌行为枚举,创建了能够打出去并造成影响的卡牌。

实现了卡牌行为中间间,并在GameApp中进行初始化。

战斗的基本框架搭建完成。

接下来的任务:

1、制作抽牌、打牌动画,为卡牌添加特效、mana消耗

2、制作玩家和关卡敌人的数据注入。

日志4.18

制作了卡牌、玩家、敌人的数据注入。

并且对卡牌造成的影响能够正确做到。

编写UI更新的逻辑。

维护玩家卡组、抽牌堆、弃牌堆。

4.19

制作了敌人血条UI、敌人AI(固定行为模式和随机行为模式)

尝试维护玩家抽牌堆、弃牌堆。

4.20

教训:如果你在尝试运行时修改ScriptableObject数据,将会引起一场灾难。

最好只用来做数据的初始化。

问题来源ScriptableObject:在运行时修改meta文件并保存上一次Play的运行结果,当你退出编辑器后(大退),修改会重置;如果SetDirty的话,会将数据保存。

开发过程中遇到的困难

1、种子seed是怎么做的?这使得我难以复现同一局游戏。

肉鸽的随机:拿一个初始随机数种子,只要遇到需要用到随机数的地方,就使用这个种子通过某种去生成一个新的种子,然后再将这个新种子保存起来,然后后面再用到的时候就用这个生成的新种子去生成,重复上面的。

2、怎么提供保存机制?使得我能够从上次的进度中加载并继续。

3、没有开发资源(图片、icon、音效等

4、反射相关的内容。

5、如何搭建策划也能使用的框架?

->CardData为CardBase注入数据,让自定义卡牌不再充满苦难(悲

6、程序执行顺序总是有问题,报空指针是最难受的(悲

7、UI的更新逻辑使我头疼。

8、Debug

highlight the text

开发使用的版本/包/资源

Universal RenderPine

TextMeshPro

Cinemachine

DOTween

TrueShadow

Spine-Unity=>创建小人动画

参考项目:

NueDeck(一个代码模板,提供了Roguelike卡牌类游戏的基础功能)

Arefnue/NueDeck: Open source, roguelike deck-building card game template (github.com)

导入开发使用的资源

Spine骨骼动画(构成了角色的所有动画)

一些卡牌图片、UI、GUI

特效、粒子效果等

icon来自Orders | Fanatical的RPG Game Builder Assets Kit($15)

创建并搭建场景

1、主菜单场景

2、GamePlay场景

3、地图线路选择场景(Map)

可以通过SceneLoader在场景中进行切换。

创建Menu场景

放置开始和退出按钮

创建GamePlay场景

搭建游玩时的UI界面

包括顶部的通用设置界面

  • 血量

  • 主动道具

  • coins,用于购买的硬币

  • settings,设置按钮

  • collections,收藏品背包

和底部的战斗界面

  • mana,打出卡牌需要消耗能量

  • end turn按钮,进入下一回合的按钮

  • cardsCanvas,用于存放卡牌的位置

  • 抽牌堆

  • 弃牌堆

创建地图线路选择场景

提供一个能够选择随机行进路线的地图。

研究NueDeck框架内容

1、大量使用ScriptableObject作为数据文件存储,用以快捷地配置数据。

2、可以了解,卡牌类的游戏UI交互是不可或缺的一部分,甚至说比重相当大。

3、回合制战斗:

枚举实现

1
2
3
4
5
6
enum CombatType{
prepareCombat,->0,代表战斗前
playerTurn,->1,代表玩家回合
enemyTurn,->2,代表敌人回合
endCombat->3,代表战斗结束
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public UnityAction OnPlayerTurnStart();//往里面添加监听

public UnityAction OnEnemyTurnStart();//往里面添加方法


switch(currentCombatType){
case CombatType.prepareCombat:
break;
case CombatType.playerTurn:
//监听玩家回合事件
//大概是抽卡drawCard、更新能量(打出手牌需要消耗能量)
case CombatType.enemyTurn:
//监听敌人回合事件
//case any other CombatType:
//在这里添加任何其他的战斗类型枚举
}
1
2
3
4
//想必这里是其他的类,

获取OnPlayerTurnStart事件
为玩家订阅上述的方法:

绘制框架图

完成回合制卡牌GameLoop

制作可以打出的卡牌UI

  • 控制卡牌能够选中、缩放、拖拽

  • 发牌动画以及打出卡牌后的动画

卡牌管理

  • 管理玩家的牌组(想必是一个 List 结构来存储相应的数据)。

  • 管理玩家的抽牌堆、弃牌堆,提供抽牌、弃牌等方法。

  • 调用UIManager来实现上述方法。

  • 提供使用UseCard方法,调用DoAction来使用卡牌。

  • 查看抽牌堆,查看弃牌堆

制作UI管理器UIManager

  • 控制玩家UI的显示=>获取UI的画布组件,通过enable/disable,显示/隐藏UI
  • 绘制卡牌,提供动画。
  • 编写UI更新逻辑。

制作回合制卡牌战斗系统CombatMananger

  • 依据CombatType控制进入不同的回合。

  • 做战斗的初始化工作:

    1. 生成敌人(√)

    2. 生成玩家(√)

    3. 显示战斗UI(√)

    4. (待定)使用收藏品被动()

    5. 初始化CombatType为玩家回合。(√)

有一个机制:存储和加载

某个类创建的ScriptableObject存放了玩家的所有信息。

在每次战斗的时候重新从那里加载信息。包括剩余血量、存放的内容等。

创建一张能够打出去并造成影响的牌!

  • 定义卡牌的行为抽象类,包含parameters和可能用到的组件

  • 一个行为枚举,代表不同的行为

  • 定义基础的几个卡牌行为

    1. Attack

    2. Defend

    3. DrawCard

    4. RestoreHealth

    5. Strength

    6. …other

  • 定义使用卡牌方法,依据行为枚举来指定打出的牌

  • 1、指定目标(包括self和target)

  • 2、需要一个中间件来打出卡牌

定义这个中间件:

开始时自动初始化它。

设置一个字典来存储所有的行为枚举-行为类实例。

当打出卡牌时,给出字典[行为类型]即可获取行为类实例,执行DoAction来实现影响。

  • 实现这个中间件

  • 为这个中间件Debug

  • 当什么时候使用卡牌方法?鼠标OnDrag的时候选择目标!!!

  • 动态地构建Params?

    使用输入检测检测在onDrag的时候开启检测协程。

    检测协程在射线检测到hit2D的目标时,为param设置卡牌作用目标target

    否则只设置其他的参数:

    CardBase(当前打出的卡),

    BaseCharacter Self(指玩家);

    在endDrag的时候关闭该协程。并将params置为null

创建角色BaseCharacter

创建玩家Player

  • 数据注入

创建敌人Enemy

  • 同上

完善卡牌Action(数据交互方面的内容)

  • 1、消耗mana

  • 2、使用AudioManager播放音效,指定卡牌Params为其提供audio

  • 3、使用EffectManager播放特效,并在打出卡牌时播放动画。

  • 4、为卡片添加描述等参数内容。

  • 5、实现抽牌效果。

  • 6、抽牌堆、弃牌堆等内容

创建敌人AI

  • 类似于创建卡牌的行为,创建敌人行为。
1
![]()   --->  type this to add an image