14 KiB
14 KiB
name, overview, todos, isProject
| name | overview | todos | isProject | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 塔罗牌小游戏实现 | 基于 Sprite + EventTriggerEx 实现塔罗牌小游戏,Yarn 命令细粒度控制每个阶段(展示牌堆、展开、等待选牌、翻牌、设置朝向),与对话深度配合。 |
|
false |
塔罗牌小游戏实现计划
设计目标
Yarn 脚本能细粒度控制塔罗牌的每个视觉阶段,每个命令之间可以穿插对话,实现对话与交互的深度配合。
Yarn 脚本示例(对应 FP_Day1_night 第 364-395 行的改造)
ql: 我觉得也许你需要这个。
<<show_tarot_deck>>
【它拿出一套崭新的卡牌。】
me: 这是什么?
ql: "塔罗牌"。
ql: 试试吧。抽一张。
<<spread_tarot 5>>
-> 抽牌
<<wait_tarot_select>>
【你看了看牌。】
<<flip_tarot "inverted">>
// 翻到正面,但预设为倒置朝向(inverted)
【上面画着一个张开双臂的机体...像是...在把它吸过去。】
me: 这画的是什么?
ql: 倒吊人。你拿反了。
<<rotate_tarot "normal">>
// Yarn 命令改变朝向:倒置 -> 正向(旋转 180 度动画)
【你把牌转过来。】
【这下你看明白了。这是一个倒吊着的机体...】
// ...更多对话...
<<hide_tarot>>
架构概览
flowchart TD
Yarn["Yarn 脚本"] -->|"<<show_tarot_deck>>"| CMD["TarotYarnCommand\n(静态 Yarn 命令类)"]
Yarn -->|"<<spread_tarot 5>>"| CMD
Yarn -->|"<<wait_tarot_select>>"| CMD
Yarn -->|"<<flip_tarot inverted>>"| CMD
Yarn -->|"<<rotate_tarot normal>>"| CMD
Yarn -->|"<<hide_tarot>>"| CMD
CMD --> TM["TarotManager\n(SystemDic 注册)"]
TM --> TD["TarotDeck\n牌堆/扇形/选牌"]
TD --> TC["TarotCard x N\nSprite + EventTriggerEx"]
TC -->|IInteraction| ES["EventSystemEx"]
新增文件结构
Assets/Scripts/MiniGame/Tarot/
TarotManager.cs -- 管理器:SystemDic 注册、视图开关、阶段协调
TarotCard.cs -- 单张牌:IInteraction + 翻牌 + 朝向
TarotDeck.cs -- 牌堆逻辑:生成、展开、悬停、选牌
TarotYarnCommand.cs -- 细粒度 Yarn 命令集
核心设计
1. TarotCard -- 单张牌
参考 KnobController 的 EventTriggerEx 注册模式(行 165-199)。
- 实现
IInteraction接口(IsActive,IsAvailable,GetGameObject()) - 需要
Collider2D(BoxCollider2D,牌面大小) SpriteRenderer+ 两个 Sprite 引用:frontSprite/backSprite
朝向系统(Orientation):
- 枚举
TarotOrientation { Normal, Inverted } Normal:正面朝上(localScale.y = 1)Inverted:倒置(localScale.y = -1,即上下颠倒)SetOrientation(orientation, animated)方法,animated 时用 DOTween 做 Y 轴 scale 过渡
翻牌动画:
Flip(targetOrientation, duration)协程- DOTween 缩放 localScale.x: 1 -> 0(前半),切换 Sprite,再 0 -> 1(后半)
- 翻牌完成时根据
targetOrientation设置 localScale.y
交互注册(Awake):
- 获取/添加
EventTriggerEx EnsureEventTriggerEntries()确保 PointerEnter/PointerExit/PointerClick 条目Register(PointerEnter, OnHoverEnter)/Register(PointerExit, OnHoverExit)/Register(PointerClick, OnClick)- 回调通过
System.Action委托给TarotDeck
状态:
bool IsFaceUp-- 当前是否正面朝上TarotOrientation Orientation-- 当前朝向bool IsInteractable-- 是否接受交互(由 Deck 控制)
2. TarotDeck -- 牌堆管理
动态生成:
Setup(int cardCount, Sprite front, Sprite back)-- 创建 N 个 TarotCard 子物体- 每张牌初始背面朝上,叠在一起(牌堆状态)
扇形展开:
SpreadCards(float duration)协程- 配置项:
fanAngleRange(扇形总角度,如 120 度)、fanRadius(扇形半径) - 每张牌的目标角度:
centerAngle + (i - (count-1)/2f) * angleStep - 每张牌的目标位置:以旋转角度沿圆弧分布
- DOTween
DOLocalMove+DOLocalRotate同步动画
悬停推出:
- 牌注册的
OnHoverEnter-> 沿牌面法线方向(局部 Y 轴上方)推出一段距离 OnHoverExit-> 回到扇形位置- 用 DOTween
DOLocalMove,设短 duration(0.15s)
选牌:
OnCardClicked(TarotCard card)-- 锁定交互,触发选牌序列- 其余牌
SpriteRenderer.DOFade(0, 0.3f)渐隐 - 选中牌移至检视位置(屏幕中央偏上),放大到检视尺寸
- 设置
_selectedCard,通知 Manager 选牌完成
检视位置:
[SerializeField] Transform inspectPosition-- 检视锚点[SerializeField] float inspectScale-- 检视缩放
3. TarotManager -- 管理器
Start()中FixSystemCenter.SystemDic.Register(this)- 持有
TarotDeck引用(GetComponentInChildren) - 状态标记:
_isWaitingForSelection(是否在等待玩家选牌)、_isCardSelected OpenView()/CloseView()控制根 GameObject 显隐WaitForSelection()协程 -- while 循环等待_isCardSelected,供 YarnCommand 使用SelectedCard属性 -- 获取当前选中的牌
4. TarotYarnCommand -- 细粒度 Yarn 命令集
所有命令通过 FixSystemCenter.SystemDic.Get<TarotManager>() 获取 Manager 实例。
| Yarn 命令 | 方法签名 | 说明 |
|---|---|---|
<<show_tarot_deck>> |
ShowTarotDeck(int count = 5) |
显示牌堆(叠放状态),可指定牌数 |
<<spread_tarot>> 或 <<spread_tarot 5>> |
IEnumerator SpreadTarot(int count = -1) |
扇形展开,count=-1 用已有牌数;协程等待展开动画完成 |
<<wait_tarot_select>> |
IEnumerator WaitTarotSelect() |
阻塞 Yarn 直到玩家点击选中一张牌;选中后执行选牌动画(其余牌淡出、选中牌居中);结果写入 $tarotSelectedIndex |
<<flip_tarot "normal">> |
IEnumerator FlipTarot(string orientation = "normal") |
翻牌到正面,同时设置朝向;"normal" = 正向,"inverted" = 倒置 |
<<rotate_tarot "normal">> |
IEnumerator RotateTarot(string orientation) |
改变已翻开牌的朝向(旋转动画),不翻面 |
<<hide_tarot>> |
IEnumerator HideTarot() |
淡出并隐藏所有塔罗元素 |
5. 完整交互流程
stateDiagram-v2
[*] --> DeckVisible: show_tarot_deck
DeckVisible --> DeckVisible: 对话继续\n(牌堆静态展示)
DeckVisible --> FanOut: spread_tarot
FanOut --> WaitSelect: wait_tarot_select
WaitSelect --> WaitSelect: hover 推出/回位
WaitSelect --> Selected: 玩家点击选牌
Selected --> InspectBack: 其余牌淡出\n选中牌居中(背面)
InspectBack --> InspectBack: 对话继续\n(背面检视)
InspectBack --> InspectFront: flip_tarot\n(预设朝向)
InspectFront --> InspectFront: 对话继续
InspectFront --> InspectRotated: rotate_tarot\n(改变朝向)
InspectRotated --> InspectRotated: 对话继续
InspectRotated --> [*]: hide_tarot
InspectFront --> [*]: hide_tarot
关键:每个状态之间 Yarn 对话可以自由穿插,命令只推进视觉阶段,不阻塞对话本身(除了 wait_tarot_select 是阻塞等待玩家交互)。
6. Sprite 资源
当前使用 Assets/RawResources/Art/CG/塔罗牌/塔罗牌正面.png 和 塔罗牌背面.png,所有牌共用同一对正反面。后续可扩展为每张牌不同正面。
场景配置
层级结构
TarotSystem -- 空 GameObject,放在需要的场景中
├── TarotManager (Component) -- 管理器脚本
│
├── TarotView -- 视图容器(OpenView/CloseView 控制这个)
│ ├── TarotDeck (Component) -- 牌堆逻辑脚本
│ │ └── [运行时动态生成的牌] -- TarotCard x N
│ │
│ └── InspectAnchor -- 检视锚点(空物体,标记牌选中后居中的位置)
│ (Transform 位置设在画面中央偏上)
│
└── (可选) Cinemachine Camera -- 如果塔罗需要独立相机视角
需要手动配置的部分
1. TarotSystem 根物体
- 放在对应场景中(如 Day1_night 所在场景)
- 位置在世界空间中合适的地方(与当前相机视角对齐)
2. TarotManager 组件(Inspector 面板)
[Header("视图")]
TarotView -- 拖入 TarotView 子物体(或自动 Find)
[Header("牌面素材")]
FrontSprite -- 拖入 塔罗牌正面 Sprite
BackSprite -- 拖入 塔罗牌背面 Sprite
[Header("牌 Prefab")]
CardPrefab -- 拖入预制的单张牌 Prefab(见下方)
3. 单张牌 Prefab(预制体)
需要提前制作一个 Prefab,结构如下:
TarotCard (Prefab)
Components:
- SpriteRenderer (默认 sprite = 背面,sortingOrder 按需)
- BoxCollider2D (Size 匹配牌面大小,用于点击/悬停检测)
- EventTriggerEx (Inspector 中添加 3 个 Trigger 条目:
PointerEnter, PointerExit, PointerClick)
- TarotCard.cs (脚本自动注册事件)
EventTriggerEx 的 Inspector 配置要点:
- 点击 "Add New Event Type"
- 分别添加
PointerEnter、PointerExit、PointerClick三个条目 - 回调列表留空(代码中
Register动态绑定)
这与 KnobController 的模式一致:EventTriggerEx 需要 Inspector 中预配好条目,代码中
TryGetTriggerEvent才能找到对应的 callback list。代码中也会EnsureEventTriggerEntries()作为保底自动添加。
4. TarotDeck 组件(Inspector 面板)
[Header("扇形展开参数")]
FanAngleRange = 120 -- 扇形总角度(度)
FanRadius = 3.0 -- 扇形半径(世界单位)
FanCenter = (0,0,0) -- 扇形圆心偏移
[Header("悬停")]
HoverOffset = 0.5 -- 悬停推出距离
HoverDuration = 0.15 -- 悬停动画时长
[Header("检视")]
InspectAnchor -- 拖入 InspectAnchor 子物体
InspectScale = 1.5 -- 检视模式缩放倍数
[Header("动画时长")]
SpreadDuration = 0.6 -- 展开动画时长
FadeOutDuration = 0.3 -- 未选中牌淡出时长
MoveToInspectDuration = 0.4 -- 选中牌移至检视位置时长
运行时流程
sequenceDiagram
participant Y as Yarn
participant M as TarotManager
participant D as TarotDeck
participant C as TarotCard
Y->>M: show_tarot_deck(5)
M->>M: OpenView()
M->>D: Setup(5, frontSprite, backSprite)
D->>D: Instantiate CardPrefab x 5
D->>C: 初始化 (背面, 叠放)
Note over Y: 对话继续...
Y->>D: spread_tarot
D->>C: DOTween 扇形展开动画
Note over Y: 对话继续...
Y->>M: wait_tarot_select (阻塞)
Note over C: 玩家悬停/点击
C-->>D: OnCardClicked
D->>D: 其余牌淡出, 选中牌居中
D-->>M: 选牌完成
M-->>Y: 协程返回
Note over Y: 对话继续...
Y->>M: flip_tarot("inverted")
M->>C: Flip(inverted)
C->>C: X轴缩放动画 + 切换Sprite + Y轴倒置
Note over Y: 对话: "你拿反了"
Y->>M: rotate_tarot("normal")
M->>C: SetOrientation(normal, animated=true)
C->>C: Y轴旋转动画 (倒置->正向)
Note over Y: 对话继续...
Y->>M: hide_tarot
M->>D: 淡出所有牌
M->>M: CloseView()
不需要手动做的部分(代码自动处理)
- 牌的动态实例化和销毁(
TarotDeck负责) - EventTriggerEx 事件绑定(
TarotCard.Awake自动注册) - SystemDic 注册(
TarotManager.Start自动注册) - 扇形位置计算(
TarotDeck按参数自动排布)
关键技术点
- EventTriggerEx 注册模式:参考
KnobController.Awake()(行 165-199),需先EnsureEventTriggerEntries()确保 triggers 列表有对应条目,再调用Register(EventTriggerType, callback) - DOTween 动画:项目已引入
DG.Tweening,用于扇形展开、悬停推出、翻牌、FadeOut - IInteraction 实现:
IsActive/IsAvailable控制交互可用性,配合EventSystemEx.isLocked在对话期间自动禁用交互(对话进行时isLocked=true,wait_tarot_select时对话暂停所以isLocked=false,交互自然可用) - SystemDic 注册:
FixSystemCenter.SystemDic.Register(this)使 YarnCommand 能通过Get<TarotManager>()获取实例 - Yarn 变量写入:
StorageSystem.Instance.SetValue("$tarotSelectedIndex", value)将选牌结果传回 Yarn