fix: repair parallax collisions and add play guide
This commit is contained in:
@@ -1,11 +0,0 @@
|
|||||||
节奏输入有效范围规则
|
|
||||||
|
|
||||||
节奏条的中心位置为核心判定锚点。
|
|
||||||
|
|
||||||
在核心判定锚点左右两侧,各存在一段有效输入范围。该范围可以理解为“中心判定区”。
|
|
||||||
|
|
||||||
只有当普通节奏点或时间锚点进入这个中心判定区时,玩家按键才会被视为有效输入,并进入后续的节奏判定流程。
|
|
||||||
|
|
||||||
如果当前中心判定区内没有任何普通节奏点或时间锚点,则玩家按键视为无效输入。
|
|
||||||
|
|
||||||
无效输入不会触发动作,不会进入 BAD / MISS / GOOD / PERFECT 判定流程,也不会消耗节奏点。
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
开始界面标题固定
|
|
||||||
* 开始界面的背景可以进行循环切换。
|
|
||||||
* 但游戏标题不参与切换,不跟随背景变化。
|
|
||||||
* 标题位置始终固定,避免背景切换时导致标题位置发生变化。
|
|
||||||
3. 玩家初始生成位置
|
|
||||||
* 游戏正式开始后,玩家角色应生成在地图最左侧。
|
|
||||||
* 该位置作为关卡起点,玩家从左向右推进关卡。
|
|
||||||
-569
@@ -1,569 +0,0 @@
|
|||||||
怪物与 Boss 行为设定方案
|
|
||||||
|
|
||||||
1. 核心原则
|
|
||||||
|
|
||||||
* 近战怪:近距离、高伤害、主动压迫玩家。
|
|
||||||
* 远程怪:远距离、低伤害、保持距离输出。
|
|
||||||
* Boss:根据相位切换近战 / 远程行为组。
|
|
||||||
* 强势相位怪物的攻击可以打断玩家动作。
|
|
||||||
* 弱势相位怪物的攻击不能打断玩家动作。
|
|
||||||
* 玩家动作带霸体时,不会被怪物攻击打断。
|
|
||||||
|
|
||||||
⸻
|
|
||||||
|
|
||||||
2. 通用行为状态
|
|
||||||
|
|
||||||
所有敌人使用以下基础状态:
|
|
||||||
|
|
||||||
待机
|
|
||||||
追踪 / 调整距离
|
|
||||||
攻击预备
|
|
||||||
攻击触发
|
|
||||||
攻击后摇
|
|
||||||
受击
|
|
||||||
脱离 / 后撤
|
|
||||||
死亡
|
|
||||||
|
|
||||||
所有攻击必须有三段:
|
|
||||||
|
|
||||||
攻击预备 → 攻击触发 → 攻击后摇
|
|
||||||
|
|
||||||
状态切换尽量按节拍发生,避免怪物连续无间隔攻击。
|
|
||||||
|
|
||||||
⸻
|
|
||||||
|
|
||||||
3. 伤害设定
|
|
||||||
|
|
||||||
3.1 近战怪伤害
|
|
||||||
|
|
||||||
近战怪攻击伤害:高
|
|
||||||
推荐值:20
|
|
||||||
|
|
||||||
特点:
|
|
||||||
|
|
||||||
* 攻击距离短。
|
|
||||||
* 命中代价高。
|
|
||||||
* 强势相位下可打断玩家。
|
|
||||||
* 弱势相位下只造成伤害,不打断玩家。
|
|
||||||
|
|
||||||
3.2 远程怪伤害
|
|
||||||
|
|
||||||
远程怪攻击伤害:低
|
|
||||||
推荐值:8
|
|
||||||
|
|
||||||
特点:
|
|
||||||
|
|
||||||
* 攻击距离远。
|
|
||||||
* 命中频率较高。
|
|
||||||
* 主要制造站位压力。
|
|
||||||
* 强势相位下可打断玩家。
|
|
||||||
* 弱势相位下只造成伤害,不打断玩家。
|
|
||||||
|
|
||||||
3.3 Boss 伤害
|
|
||||||
|
|
||||||
Boss 根据当前相位切换攻击倾向:
|
|
||||||
|
|
||||||
近战相位:攻击力高
|
|
||||||
远程相位:攻击力低
|
|
||||||
|
|
||||||
推荐值:
|
|
||||||
|
|
||||||
Boss 近战攻击伤害:25
|
|
||||||
Boss 远程攻击伤害:12
|
|
||||||
|
|
||||||
⸻
|
|
||||||
|
|
||||||
4. 近战怪行为逻辑
|
|
||||||
|
|
||||||
4.1 行为定位
|
|
||||||
|
|
||||||
近战怪负责贴身压迫玩家。
|
|
||||||
|
|
||||||
它不需要频繁后撤,也不需要复杂逃跑逻辑。
|
|
||||||
|
|
||||||
⸻
|
|
||||||
|
|
||||||
4.2 距离规则
|
|
||||||
|
|
||||||
警戒距离:360 px
|
|
||||||
追踪停止距离:120 px
|
|
||||||
攻击距离:120 px
|
|
||||||
攻击判定范围:前方 140 px
|
|
||||||
|
|
||||||
⸻
|
|
||||||
|
|
||||||
4.3 行为流程
|
|
||||||
|
|
||||||
玩家进入警戒距离
|
|
||||||
↓
|
|
||||||
近战怪向玩家移动
|
|
||||||
↓
|
|
||||||
进入攻击距离
|
|
||||||
↓
|
|
||||||
停止移动
|
|
||||||
↓
|
|
||||||
攻击预备
|
|
||||||
↓
|
|
||||||
攻击触发
|
|
||||||
↓
|
|
||||||
攻击后摇
|
|
||||||
↓
|
|
||||||
重新判断距离
|
|
||||||
|
|
||||||
⸻
|
|
||||||
|
|
||||||
4.4 受击逻辑
|
|
||||||
|
|
||||||
弱势相位
|
|
||||||
|
|
||||||
受到高伤害
|
|
||||||
可被普通攻击打出硬直
|
|
||||||
不具备明显霸体
|
|
||||||
可以被玩家快速击败
|
|
||||||
|
|
||||||
强势相位
|
|
||||||
|
|
||||||
受到伤害降低
|
|
||||||
硬直减弱
|
|
||||||
普通攻击不一定打断动作
|
|
||||||
攻击触发阶段具备霸体
|
|
||||||
|
|
||||||
⸻
|
|
||||||
|
|
||||||
4.5 墙角逻辑
|
|
||||||
|
|
||||||
近战怪被逼到墙角时,不后撤。
|
|
||||||
|
|
||||||
处理方式:
|
|
||||||
|
|
||||||
如果玩家距离 < 120 px
|
|
||||||
↓
|
|
||||||
近战怪优先攻击
|
|
||||||
↓
|
|
||||||
强势相位下攻击可打断玩家
|
|
||||||
↓
|
|
||||||
命中后对玩家造成小幅击退
|
|
||||||
|
|
||||||
推荐击退距离:
|
|
||||||
|
|
||||||
玩家被击退:80–120 px
|
|
||||||
|
|
||||||
⸻
|
|
||||||
|
|
||||||
5. 远程怪行为逻辑
|
|
||||||
|
|
||||||
5.1 行为定位
|
|
||||||
|
|
||||||
远程怪负责远距离压制玩家。
|
|
||||||
|
|
||||||
它不能被玩家贴身无限连死,所以需要后撤和脱离机制。
|
|
||||||
|
|
||||||
⸻
|
|
||||||
|
|
||||||
5.2 距离规则
|
|
||||||
|
|
||||||
危险距离:0–220 px
|
|
||||||
理想距离:320–480 px
|
|
||||||
最大攻击距离:720 px
|
|
||||||
|
|
||||||
⸻
|
|
||||||
|
|
||||||
5.3 基础行为流程
|
|
||||||
|
|
||||||
玩家进入攻击范围
|
|
||||||
↓
|
|
||||||
远程怪寻找理想距离
|
|
||||||
↓
|
|
||||||
保持 320–480 px 距离
|
|
||||||
↓
|
|
||||||
攻击预备
|
|
||||||
↓
|
|
||||||
发射远程攻击
|
|
||||||
↓
|
|
||||||
攻击冷却
|
|
||||||
↓
|
|
||||||
重新判断距离
|
|
||||||
|
|
||||||
⸻
|
|
||||||
|
|
||||||
5.4 玩家过近时的后撤逻辑
|
|
||||||
|
|
||||||
触发条件:
|
|
||||||
|
|
||||||
玩家距离远程怪 < 220 px
|
|
||||||
|
|
||||||
执行流程:
|
|
||||||
|
|
||||||
停止攻击
|
|
||||||
↓
|
|
||||||
进入短暂脱离保护
|
|
||||||
↓
|
|
||||||
向远离玩家方向后撤
|
|
||||||
↓
|
|
||||||
恢复到 320–420 px 距离
|
|
||||||
↓
|
|
||||||
短暂停顿
|
|
||||||
↓
|
|
||||||
重新进入远程攻击循环
|
|
||||||
|
|
||||||
推荐数值:
|
|
||||||
|
|
||||||
单次后撤距离:220 px
|
|
||||||
最小后撤后距离:320 px
|
|
||||||
最大后撤后距离:420 px
|
|
||||||
后撤后停顿:0.4 秒
|
|
||||||
|
|
||||||
⸻
|
|
||||||
|
|
||||||
5.5 被攻击后的脱离逻辑
|
|
||||||
|
|
||||||
触发条件满足任一即可:
|
|
||||||
|
|
||||||
远程怪被玩家攻击命中
|
|
||||||
玩家距离远程怪 < 160 px
|
|
||||||
远程怪在 1 秒内连续受击 2 次
|
|
||||||
|
|
||||||
执行流程:
|
|
||||||
|
|
||||||
受击
|
|
||||||
↓
|
|
||||||
短暂无敌
|
|
||||||
↓
|
|
||||||
后撤 / 脱离
|
|
||||||
↓
|
|
||||||
恢复到安全距离
|
|
||||||
↓
|
|
||||||
取消无敌
|
|
||||||
↓
|
|
||||||
重新进入攻击循环
|
|
||||||
|
|
||||||
推荐数值:
|
|
||||||
|
|
||||||
短暂无敌时间:0.35 秒
|
|
||||||
后撤距离:220–260 px
|
|
||||||
脱离冷却:3 秒
|
|
||||||
|
|
||||||
限制:
|
|
||||||
|
|
||||||
后撤过程中不攻击
|
|
||||||
无敌时间只用于脱离
|
|
||||||
不能连续触发无敌后撤
|
|
||||||
|
|
||||||
⸻
|
|
||||||
|
|
||||||
5.6 墙角脱离逻辑
|
|
||||||
|
|
||||||
触发条件:
|
|
||||||
|
|
||||||
玩家距离远程怪 < 220 px
|
|
||||||
远程怪背后距离墙体 < 120 px
|
|
||||||
脱离冷却已结束
|
|
||||||
|
|
||||||
执行流程:
|
|
||||||
|
|
||||||
短暂无敌
|
|
||||||
↓
|
|
||||||
穿越玩家 / 闪避到玩家另一侧
|
|
||||||
↓
|
|
||||||
落点与玩家保持约 320 px
|
|
||||||
↓
|
|
||||||
短暂停顿
|
|
||||||
↓
|
|
||||||
恢复攻击循环
|
|
||||||
|
|
||||||
推荐数值:
|
|
||||||
|
|
||||||
穿越距离:玩家身后 320 px
|
|
||||||
无敌时间:0.35 秒
|
|
||||||
墙角脱离冷却:4 秒
|
|
||||||
落地停顿:0.5 秒
|
|
||||||
|
|
||||||
限制:
|
|
||||||
|
|
||||||
墙角脱离不造成伤害
|
|
||||||
不能连续触发
|
|
||||||
不能直接接攻击
|
|
||||||
|
|
||||||
⸻
|
|
||||||
|
|
||||||
6. Boss 行为逻辑
|
|
||||||
|
|
||||||
6.1 Boss 核心设定
|
|
||||||
|
|
||||||
Boss 拥有两套行为组:
|
|
||||||
|
|
||||||
近战相位:近战技能组
|
|
||||||
远程相位:远程技能组
|
|
||||||
|
|
||||||
Boss 不采用小怪的弱势相位秒杀逻辑。
|
|
||||||
|
|
||||||
Boss 的相位切换主要改变:
|
|
||||||
|
|
||||||
攻击方式
|
|
||||||
攻击距离
|
|
||||||
攻击力
|
|
||||||
追踪策略
|
|
||||||
霸体强度
|
|
||||||
|
|
||||||
⸻
|
|
||||||
|
|
||||||
6.2 Boss 近战相位
|
|
||||||
|
|
||||||
定位
|
|
||||||
|
|
||||||
主动靠近玩家
|
|
||||||
使用高伤害近战攻击
|
|
||||||
压迫玩家站位
|
|
||||||
|
|
||||||
目标距离
|
|
||||||
|
|
||||||
目标距离:100–160 px
|
|
||||||
攻击距离:180 px
|
|
||||||
|
|
||||||
行为流程
|
|
||||||
|
|
||||||
判断玩家距离
|
|
||||||
↓
|
|
||||||
如果距离 > 180 px,向玩家靠近
|
|
||||||
↓
|
|
||||||
如果距离 ≤ 180 px,进入近战攻击预备
|
|
||||||
↓
|
|
||||||
攻击触发
|
|
||||||
↓
|
|
||||||
攻击后摇
|
|
||||||
↓
|
|
||||||
重新判断距离
|
|
||||||
|
|
||||||
伤害
|
|
||||||
|
|
||||||
Boss 近战伤害:25
|
|
||||||
|
|
||||||
打断
|
|
||||||
|
|
||||||
近战相位攻击可打断玩家
|
|
||||||
玩家霸体动作除外
|
|
||||||
|
|
||||||
⸻
|
|
||||||
|
|
||||||
6.3 Boss 远程相位
|
|
||||||
|
|
||||||
定位
|
|
||||||
|
|
||||||
主动拉开距离
|
|
||||||
使用低伤害远程攻击
|
|
||||||
制造空间压制
|
|
||||||
|
|
||||||
目标距离
|
|
||||||
|
|
||||||
目标距离:360–520 px
|
|
||||||
最小安全距离:240 px
|
|
||||||
最大攻击距离:760 px
|
|
||||||
|
|
||||||
行为流程
|
|
||||||
|
|
||||||
判断玩家距离
|
|
||||||
↓
|
|
||||||
如果玩家距离 < 240 px,Boss 后撤
|
|
||||||
↓
|
|
||||||
如果玩家距离在 360–520 px,使用远程攻击
|
|
||||||
↓
|
|
||||||
如果玩家距离 > 760 px,Boss 小幅靠近
|
|
||||||
↓
|
|
||||||
重新判断距离
|
|
||||||
|
|
||||||
伤害
|
|
||||||
|
|
||||||
Boss 远程伤害:12
|
|
||||||
|
|
||||||
打断
|
|
||||||
|
|
||||||
远程相位攻击可打断玩家
|
|
||||||
玩家霸体动作除外
|
|
||||||
|
|
||||||
⸻
|
|
||||||
|
|
||||||
6.4 Boss 相位切换规则
|
|
||||||
|
|
||||||
当前为近战相位
|
|
||||||
↓
|
|
||||||
切换到远程相位
|
|
||||||
↓
|
|
||||||
Boss 拉开距离
|
|
||||||
↓
|
|
||||||
使用远程攻击组
|
|
||||||
当前为远程相位
|
|
||||||
↓
|
|
||||||
切换到近战相位
|
|
||||||
↓
|
|
||||||
Boss 主动靠近玩家
|
|
||||||
↓
|
|
||||||
使用近战攻击组
|
|
||||||
|
|
||||||
相位切换时不立刻攻击。
|
|
||||||
|
|
||||||
推荐流程:
|
|
||||||
|
|
||||||
相位切换提示
|
|
||||||
↓
|
|
||||||
短暂停顿
|
|
||||||
↓
|
|
||||||
调整距离
|
|
||||||
↓
|
|
||||||
进入新相位攻击循环
|
|
||||||
|
|
||||||
推荐数值:
|
|
||||||
|
|
||||||
相位切换停顿:0.5 秒
|
|
||||||
|
|
||||||
⸻
|
|
||||||
|
|
||||||
6.5 Boss 受击逻辑
|
|
||||||
|
|
||||||
Boss 不应被普通连招无限压制。
|
|
||||||
|
|
||||||
普通受击
|
|
||||||
|
|
||||||
正常掉血
|
|
||||||
播放受击反馈
|
|
||||||
不进入完整硬直
|
|
||||||
|
|
||||||
韧性机制
|
|
||||||
|
|
||||||
推荐设定:
|
|
||||||
|
|
||||||
Boss 韧性值:100
|
|
||||||
普通攻击削韧:10
|
|
||||||
技能攻击削韧:20
|
|
||||||
时间锚点强化攻击削韧:30
|
|
||||||
|
|
||||||
韧性归零时:
|
|
||||||
|
|
||||||
Boss 进入短暂硬直
|
|
||||||
硬直后恢复韧性
|
|
||||||
|
|
||||||
推荐数值:
|
|
||||||
|
|
||||||
Boss 硬直时间:1.2 秒
|
|
||||||
韧性恢复值:100
|
|
||||||
|
|
||||||
⸻
|
|
||||||
|
|
||||||
6.6 Boss 霸体逻辑
|
|
||||||
|
|
||||||
普通移动:轻霸体
|
|
||||||
攻击预备:中霸体
|
|
||||||
攻击触发:强霸体
|
|
||||||
攻击后摇:霸体降低
|
|
||||||
硬直状态:无霸体
|
|
||||||
|
|
||||||
玩家主要输出窗口:
|
|
||||||
|
|
||||||
Boss 攻击后摇
|
|
||||||
Boss 韧性破防硬直
|
|
||||||
Boss 相位切换后的短暂停顿
|
|
||||||
|
|
||||||
⸻
|
|
||||||
|
|
||||||
6.7 Boss 后撤逻辑
|
|
||||||
|
|
||||||
只在远程相位下启用。
|
|
||||||
|
|
||||||
触发条件:
|
|
||||||
|
|
||||||
Boss 当前为远程相位
|
|
||||||
玩家距离 Boss < 240 px
|
|
||||||
|
|
||||||
执行流程:
|
|
||||||
|
|
||||||
停止攻击
|
|
||||||
↓
|
|
||||||
后撤
|
|
||||||
↓
|
|
||||||
恢复到 360–480 px 距离
|
|
||||||
↓
|
|
||||||
短暂停顿
|
|
||||||
↓
|
|
||||||
继续远程攻击
|
|
||||||
|
|
||||||
推荐数值:
|
|
||||||
|
|
||||||
Boss 后撤距离:280 px
|
|
||||||
后撤后目标距离:360–480 px
|
|
||||||
后撤冷却:4 秒
|
|
||||||
后撤后停顿:0.5 秒
|
|
||||||
|
|
||||||
限制:
|
|
||||||
|
|
||||||
Boss 后撤过程中不攻击
|
|
||||||
Boss 后撤不造成伤害
|
|
||||||
Boss 后撤不能连续触发
|
|
||||||
|
|
||||||
⸻
|
|
||||||
|
|
||||||
6.8 Boss 墙角处理
|
|
||||||
|
|
||||||
触发条件:
|
|
||||||
|
|
||||||
Boss 当前为远程相位
|
|
||||||
玩家距离 Boss < 240 px
|
|
||||||
Boss 背后距离墙体 < 160 px
|
|
||||||
后撤冷却已结束
|
|
||||||
|
|
||||||
处理方式:
|
|
||||||
|
|
||||||
Boss 短暂无敌
|
|
||||||
↓
|
|
||||||
穿越玩家 / 跳到另一侧
|
|
||||||
↓
|
|
||||||
与玩家重新拉开 400 px 左右距离
|
|
||||||
↓
|
|
||||||
短暂停顿
|
|
||||||
↓
|
|
||||||
恢复远程攻击循环
|
|
||||||
|
|
||||||
推荐数值:
|
|
||||||
|
|
||||||
Boss 墙角脱离冷却:6 秒
|
|
||||||
短暂无敌时间:0.4 秒
|
|
||||||
落点距离玩家:400 px
|
|
||||||
落地停顿:0.6 秒
|
|
||||||
|
|
||||||
限制:
|
|
||||||
|
|
||||||
墙角脱离不造成伤害
|
|
||||||
不能连续触发
|
|
||||||
不能直接接攻击
|
|
||||||
|
|
||||||
⸻
|
|
||||||
|
|
||||||
7. 最终规则汇总
|
|
||||||
|
|
||||||
近战怪
|
|
||||||
|
|
||||||
短距离
|
|
||||||
高伤害
|
|
||||||
主动靠近
|
|
||||||
强势相位可打断玩家
|
|
||||||
弱势相位容易被击败
|
|
||||||
被逼墙角时不逃跑,使用攻击反压
|
|
||||||
|
|
||||||
远程怪
|
|
||||||
|
|
||||||
长距离
|
|
||||||
低伤害
|
|
||||||
保持 320–480 px 距离
|
|
||||||
被近身后后撤
|
|
||||||
被连续攻击后短暂无敌脱离
|
|
||||||
被逼墙角后穿越玩家脱离
|
|
||||||
脱离动作不造成伤害
|
|
||||||
|
|
||||||
Boss
|
|
||||||
|
|
||||||
拥有近战 / 远程两套行为组
|
|
||||||
随相位切换行为
|
|
||||||
近战相位高伤害,主动压迫
|
|
||||||
远程相位低伤害,主动拉开距离
|
|
||||||
Boss 有韧性和阶段性霸体
|
|
||||||
Boss 远程相位被近身时后撤
|
|
||||||
Boss 被逼墙角时可脱离
|
|
||||||
Boss 脱离动作不造成伤害
|
|
||||||
+663
@@ -0,0 +1,663 @@
|
|||||||
|
# 《Anchor》试玩说明
|
||||||
|
|
||||||
|
这是一款节奏横板动作游戏。你不需要像传统动作游戏那样一直按方向键移动,角色的前进、后退、突进、起跳、下坠和攻击位移,主要都来自你在音乐节奏上的出招。
|
||||||
|
|
||||||
|
一句话理解玩法:
|
||||||
|
|
||||||
|
> 听节奏按键,用招式带动角色移动,在过去与未来两个时间相位之间寻找敌人的弱点,打完小怪后进入 Boss 战。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 基本目标
|
||||||
|
|
||||||
|
试玩关卡分为两个部分:
|
||||||
|
|
||||||
|
1. 前半段清理小怪。
|
||||||
|
2. 进入右侧 Boss 区域,击败 Boss。
|
||||||
|
|
||||||
|
前场一共会出现 6 只小怪。全部击败后,通往 Boss 区域的道路打开。进入 Boss 区域后,Boss 战正式开始。
|
||||||
|
|
||||||
|
胜利条件:
|
||||||
|
|
||||||
|
- 击败 Boss。
|
||||||
|
|
||||||
|
失败条件:
|
||||||
|
|
||||||
|
- 玩家生命归零。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 基本操作
|
||||||
|
|
||||||
|
| 按键 | 用途 |
|
||||||
|
| --- | --- |
|
||||||
|
| A | 向左攻击,也可长按蓄力 |
|
||||||
|
| D | 向右攻击,也可长按蓄力 |
|
||||||
|
| W | 上挑,让敌人或自己进入空中连段节奏 |
|
||||||
|
| S | 防御;在空中时可下砸;也可长按蓄力 |
|
||||||
|
| Space | 特殊键,用来派生技能或释放部分蓄力招式 |
|
||||||
|
| E | 显示或隐藏招式表 |
|
||||||
|
| Esc | 暂停游戏 |
|
||||||
|
|
||||||
|
最重要的一点:
|
||||||
|
|
||||||
|
> A、D、W、S、Space 不是自由移动键。它们都是“节奏动作键”。
|
||||||
|
|
||||||
|
角色移动主要来自动作本身,比如向右攻击会带你向右推进,冲刺斩会穿过敌人,上挑和空中攻击会改变你的高度与落点。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 节奏判定
|
||||||
|
|
||||||
|
每次按键都会和音乐节拍比较,得到一个判定。
|
||||||
|
|
||||||
|
| 判定 | 含义 |
|
||||||
|
| --- | --- |
|
||||||
|
| Perfect | 非常准,效果最好 |
|
||||||
|
| Good | 比较准,效果稳定 |
|
||||||
|
| Bad | 勉强命中节奏,动作仍可成立,但收益下降 |
|
||||||
|
| Miss | 没有踩中节奏,动作失败 |
|
||||||
|
|
||||||
|
判定会影响这些内容:
|
||||||
|
|
||||||
|
- 你是否成功出招。
|
||||||
|
- 造成多少伤害。
|
||||||
|
- 获得多少能量。
|
||||||
|
- 使用技能时消耗多少能量。
|
||||||
|
- 是否能稳住时间相位。
|
||||||
|
|
||||||
|
大致规则:
|
||||||
|
|
||||||
|
- Perfect:标准伤害,标准回能,技能消耗更低。
|
||||||
|
- Good:伤害和回能略低。
|
||||||
|
- Bad:伤害和回能明显降低,技能消耗更高。
|
||||||
|
- Miss:不出招,连段被打断。
|
||||||
|
|
||||||
|
连续乱按不会带来优势。同一个节奏点被使用后,短时间内重复按键会被忽略;继续乱按会变成 Miss,并让你进入短暂的按键失效状态。这个设计是为了鼓励你跟着节奏打,而不是疯狂连点。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 招式记忆与连段
|
||||||
|
|
||||||
|
游戏会记住你最近几次有效输入,并根据输入顺序决定招式。
|
||||||
|
|
||||||
|
你可以把它理解成一小段“招式记忆”:
|
||||||
|
|
||||||
|
```text
|
||||||
|
A → 普通左攻击
|
||||||
|
A A → 第二段左攻击
|
||||||
|
A A A → 第三段左攻击
|
||||||
|
A Space → 左冲刺斩
|
||||||
|
A A Space → 左终结技
|
||||||
|
A A A Space → 左重击
|
||||||
|
```
|
||||||
|
|
||||||
|
右方向同理:
|
||||||
|
|
||||||
|
```text
|
||||||
|
D → 普通右攻击
|
||||||
|
D D → 第二段右攻击
|
||||||
|
D D D → 第三段右攻击
|
||||||
|
D Space → 右冲刺斩
|
||||||
|
D D Space → 右终结技
|
||||||
|
D D D Space → 右重击
|
||||||
|
```
|
||||||
|
|
||||||
|
当这段记忆里出现 Miss 时,连段会断开。你需要重新踩节奏开始下一轮输入。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 能量与技能
|
||||||
|
|
||||||
|
玩家有能量条。
|
||||||
|
|
||||||
|
能量主要通过基础攻击获得:
|
||||||
|
|
||||||
|
- 普通攻击命中节奏可以回复能量。
|
||||||
|
- Perfect 回能最多。
|
||||||
|
- Good 次之。
|
||||||
|
- Bad 回能较少。
|
||||||
|
|
||||||
|
技能通常需要消耗能量:
|
||||||
|
|
||||||
|
- 冲刺斩消耗较少,适合突进、穿越敌人和调整位置。
|
||||||
|
- 终结技消耗中等,伤害和击退更明显。
|
||||||
|
- 重击消耗较高,伤害高,并且更容易把敌人打飞或击退。
|
||||||
|
- 蓄力技能根据蓄力等级消耗不同,等级越高,威力越强,消耗也越高。
|
||||||
|
|
||||||
|
能量不足时,技能不会释放成功。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 常用招式
|
||||||
|
|
||||||
|
### 普通攻击
|
||||||
|
|
||||||
|
A 和 D 是最基础的攻击键。
|
||||||
|
|
||||||
|
- A:向左攻击。
|
||||||
|
- D:向右攻击。
|
||||||
|
- 连续按同方向可以打出多段攻击。
|
||||||
|
- 普通攻击会带有轻微位移,可以用来接近敌人或调整站位。
|
||||||
|
- 普通攻击是最稳定的回能方式。
|
||||||
|
|
||||||
|
### 冲刺斩
|
||||||
|
|
||||||
|
输入:
|
||||||
|
|
||||||
|
```text
|
||||||
|
A Space
|
||||||
|
D Space
|
||||||
|
```
|
||||||
|
|
||||||
|
特点:
|
||||||
|
|
||||||
|
- 向对应方向快速突进。
|
||||||
|
- 可以穿过敌人。
|
||||||
|
- 适合躲开夹击、穿到敌人背后、靠近远程敌人。
|
||||||
|
- 冲刺结束后,角色会重新恢复正常身体阻挡,不会长期站在敌人体内。
|
||||||
|
|
||||||
|
### 终结技
|
||||||
|
|
||||||
|
输入:
|
||||||
|
|
||||||
|
```text
|
||||||
|
A A Space
|
||||||
|
D D Space
|
||||||
|
```
|
||||||
|
|
||||||
|
特点:
|
||||||
|
|
||||||
|
- 比普通攻击伤害更高。
|
||||||
|
- 有更明显的击退效果。
|
||||||
|
- 适合在敌人露出破绽时使用。
|
||||||
|
|
||||||
|
### 重击
|
||||||
|
|
||||||
|
输入:
|
||||||
|
|
||||||
|
```text
|
||||||
|
A A A Space
|
||||||
|
D D D Space
|
||||||
|
```
|
||||||
|
|
||||||
|
特点:
|
||||||
|
|
||||||
|
- 消耗更高。
|
||||||
|
- 伤害高。
|
||||||
|
- 击退和打飞效果更强。
|
||||||
|
- 出手和收招更重,需要找准时机。
|
||||||
|
|
||||||
|
### 上挑
|
||||||
|
|
||||||
|
输入:
|
||||||
|
|
||||||
|
```text
|
||||||
|
W
|
||||||
|
```
|
||||||
|
|
||||||
|
特点:
|
||||||
|
|
||||||
|
- 可把战斗带入空中节奏。
|
||||||
|
- 可衔接空中攻击。
|
||||||
|
- 适合打断地面节奏,制造新的攻击角度。
|
||||||
|
|
||||||
|
### 空中攻击
|
||||||
|
|
||||||
|
在空中按:
|
||||||
|
|
||||||
|
```text
|
||||||
|
A
|
||||||
|
D
|
||||||
|
S
|
||||||
|
```
|
||||||
|
|
||||||
|
效果:
|
||||||
|
|
||||||
|
- A / D:向左或向右进行空中攻击,并带有明显横向移动。
|
||||||
|
- S:向下攻击,适合从空中压回地面。
|
||||||
|
|
||||||
|
### 防御
|
||||||
|
|
||||||
|
输入:
|
||||||
|
|
||||||
|
```text
|
||||||
|
S
|
||||||
|
```
|
||||||
|
|
||||||
|
特点:
|
||||||
|
|
||||||
|
- 地面状态下按 S 会进入防御动作。
|
||||||
|
- 正面防御可以减轻或挡住伤害。
|
||||||
|
- 背后受到攻击时,防御效果会变差。
|
||||||
|
- 防御仍然需要跟节奏配合,乱按不会稳定保命。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 蓄力招式
|
||||||
|
|
||||||
|
部分按键可以长按蓄力。
|
||||||
|
|
||||||
|
### A / D 蓄力:剑雨
|
||||||
|
|
||||||
|
用法:
|
||||||
|
|
||||||
|
1. 按 A 或 D 打出一次对应方向的普通攻击。
|
||||||
|
2. 继续按住不放,进入蓄力。
|
||||||
|
3. 松开按键,释放剑雨。
|
||||||
|
|
||||||
|
特点:
|
||||||
|
|
||||||
|
- A 蓄力:向左释放剑雨。
|
||||||
|
- D 蓄力:向右释放剑雨。
|
||||||
|
- 蓄力越久,等级越高。
|
||||||
|
- 等级越高,伤害越高,消耗能量越多。
|
||||||
|
- 剑雨适合攻击一片区域,尤其适合压制小怪。
|
||||||
|
|
||||||
|
注意:
|
||||||
|
|
||||||
|
- A / D 蓄力过程中按 Space,会优先变成普通连段派生,例如冲刺斩。
|
||||||
|
- 也就是说,如果你正在按住 A,途中按 Space,可能会打出 `A Space` 的冲刺斩,而不是剑雨。
|
||||||
|
|
||||||
|
### S 蓄力:斩波
|
||||||
|
|
||||||
|
用法:
|
||||||
|
|
||||||
|
1. 按住 S 进入蓄力。
|
||||||
|
2. 蓄力过程中按 Space 释放斩波。
|
||||||
|
|
||||||
|
特点:
|
||||||
|
|
||||||
|
- 斩波是远程攻击。
|
||||||
|
- 会朝角色当前面对方向发射。
|
||||||
|
- 蓄力越久,等级越高。
|
||||||
|
- 等级越高,射程、伤害和击退越强。
|
||||||
|
- 适合攻击不方便接近的敌人。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 过去与未来
|
||||||
|
|
||||||
|
游戏中有两个时间相位:
|
||||||
|
|
||||||
|
- 过去
|
||||||
|
- 未来
|
||||||
|
|
||||||
|
相位会影响:
|
||||||
|
|
||||||
|
- 背景与画面色调。
|
||||||
|
- 音乐层次。
|
||||||
|
- 敌人的强弱状态。
|
||||||
|
- 界面提示。
|
||||||
|
|
||||||
|
相位不会改变地形。你能站的位置、墙和地面不会因为过去或未来而变化。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 时间锚点
|
||||||
|
|
||||||
|
战斗中会出现特殊节奏点,叫做时间锚点。
|
||||||
|
|
||||||
|
当时间锚点出现时:
|
||||||
|
|
||||||
|
- 如果你在窗口内按出 Perfect、Good 或 Bad,当前相位会保持。
|
||||||
|
- 如果你 Miss,或者没有及时输入,相位会切换到另一边。
|
||||||
|
|
||||||
|
时间锚点不是单独的按键。你可以用当前任何合适的动作输入去稳住它。
|
||||||
|
|
||||||
|
举例:
|
||||||
|
|
||||||
|
- 时间锚点出现时,你按 D,既可能打出右攻击,也可能同时稳住相位。
|
||||||
|
- 如果这次按键是 Miss,动作失败,相位也可能切换。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 攻击提升与回血
|
||||||
|
|
||||||
|
成功守住时间锚点会给你奖励。
|
||||||
|
|
||||||
|
### 攻击提升
|
||||||
|
|
||||||
|
- Perfect 或 Good 守住时间锚点:获得 1 层攻击提升。
|
||||||
|
- Bad 守住时间锚点:只保持相位,不增加攻击提升。
|
||||||
|
- 攻击提升最多 7 层。
|
||||||
|
- 每层都会让你的攻击更强。
|
||||||
|
- 相位切换时,会失去 2 层。
|
||||||
|
- 受到伤害进入受击状态时,也会失去一部分层数。
|
||||||
|
|
||||||
|
### 回血
|
||||||
|
|
||||||
|
成功守住时间锚点时会回血。
|
||||||
|
|
||||||
|
- Perfect 回血最多。
|
||||||
|
- Good 次之。
|
||||||
|
- Bad 较少。
|
||||||
|
- 如果你的血量很低,回血效果会更明显。
|
||||||
|
|
||||||
|
简单理解:
|
||||||
|
|
||||||
|
> 节奏越稳,越能维持当前相位,也越能把角色养强。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 敌人的强弱状态
|
||||||
|
|
||||||
|
小怪会随着过去与未来改变强弱。
|
||||||
|
|
||||||
|
### 近战小怪
|
||||||
|
|
||||||
|
近战小怪在过去更强,在未来更弱。
|
||||||
|
|
||||||
|
过去时:
|
||||||
|
|
||||||
|
- 攻击更危险。
|
||||||
|
- 更容易打断你。
|
||||||
|
- 承受你的伤害更低。
|
||||||
|
|
||||||
|
未来时:
|
||||||
|
|
||||||
|
- 攻击威胁下降。
|
||||||
|
- 更容易被你击杀。
|
||||||
|
- 更适合主动输出。
|
||||||
|
|
||||||
|
### 远程小怪
|
||||||
|
|
||||||
|
远程小怪在未来更强,在过去更弱。
|
||||||
|
|
||||||
|
过去时:
|
||||||
|
|
||||||
|
- 远程压力较低。
|
||||||
|
- 更容易被处理。
|
||||||
|
|
||||||
|
未来时:
|
||||||
|
|
||||||
|
- 射击更危险。
|
||||||
|
- 更容易打断你。
|
||||||
|
- 需要优先压制或躲避。
|
||||||
|
|
||||||
|
### 推荐打法
|
||||||
|
|
||||||
|
- 过去:优先小心近战怪,可以趁机处理远程怪。
|
||||||
|
- 未来:优先小心远程怪,可以趁机处理近战怪。
|
||||||
|
- 如果场上两种怪都有,优先攻击当前较弱的那一类,能更快减少场面压力。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. 小怪行为
|
||||||
|
|
||||||
|
### 近战小怪
|
||||||
|
|
||||||
|
近战小怪会:
|
||||||
|
|
||||||
|
- 在区域内巡逻。
|
||||||
|
- 看到你后追击。
|
||||||
|
- 靠近后按节奏出手。
|
||||||
|
- 强势状态下攻击更容易打断你。
|
||||||
|
|
||||||
|
应对方式:
|
||||||
|
|
||||||
|
- 不要一直站在它正面吃招。
|
||||||
|
- 用 A / D 普攻调整距离。
|
||||||
|
- 用冲刺斩穿过它,换到另一侧。
|
||||||
|
- 在它处于弱势相位时集中输出。
|
||||||
|
|
||||||
|
### 远程小怪
|
||||||
|
|
||||||
|
远程小怪会:
|
||||||
|
|
||||||
|
- 保持距离射击。
|
||||||
|
- 被你贴近后尝试后撤。
|
||||||
|
- 连续被打时,会短暂闪避脱离。
|
||||||
|
|
||||||
|
应对方式:
|
||||||
|
|
||||||
|
- 用冲刺斩快速接近。
|
||||||
|
- 用斩波远距离反击。
|
||||||
|
- 不要长期放任远程怪在屏幕边缘输出。
|
||||||
|
- 在它处于弱势相位时尽快解决。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. 第一关流程
|
||||||
|
|
||||||
|
第一关小怪部分一共 3 个阶段。
|
||||||
|
|
||||||
|
### 阶段一:单只近战怪
|
||||||
|
|
||||||
|
开局你会从地图偏左位置进入,向右推进后遇到第一只近战怪。
|
||||||
|
|
||||||
|
这个阶段主要让你熟悉:
|
||||||
|
|
||||||
|
- 节奏攻击。
|
||||||
|
- 普通连段。
|
||||||
|
- 近战怪追击。
|
||||||
|
- 过去/未来对敌人强弱的影响。
|
||||||
|
|
||||||
|
### 阶段二:单只远程怪
|
||||||
|
|
||||||
|
击败第一只近战怪后,会出现一只远程怪。
|
||||||
|
|
||||||
|
这个阶段主要让你熟悉:
|
||||||
|
|
||||||
|
- 远程攻击压力。
|
||||||
|
- 接近远程怪的方法。
|
||||||
|
- 冲刺斩和斩波的使用时机。
|
||||||
|
|
||||||
|
### 阶段三:混合增援
|
||||||
|
|
||||||
|
击败远程怪后,会陆续出现 4 只增援:
|
||||||
|
|
||||||
|
- 近战怪
|
||||||
|
- 远程怪
|
||||||
|
- 近战怪
|
||||||
|
- 远程怪
|
||||||
|
|
||||||
|
它们不会一瞬间全部出现,而是分批进入战斗。
|
||||||
|
|
||||||
|
这个阶段的重点是:
|
||||||
|
|
||||||
|
- 根据当前相位选择优先目标。
|
||||||
|
- 不要被近战和远程同时压住。
|
||||||
|
- 用技能快速减少敌人数。
|
||||||
|
- 保持节奏,尽量不要让时间相位失控。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. Boss 战
|
||||||
|
|
||||||
|
清完全部小怪后,右侧道路会打开。进入 Boss 区域后,Boss 战开始。
|
||||||
|
|
||||||
|
Boss 和小怪不同:
|
||||||
|
|
||||||
|
- Boss 不会像小怪那样有明显弱势相位。
|
||||||
|
- Boss 的攻击一般都能打断你。
|
||||||
|
- Boss 血量更高。
|
||||||
|
- 普通攻击可以造成伤害,但不容易压制 Boss。
|
||||||
|
- 消耗能量的技能更适合压制 Boss。
|
||||||
|
|
||||||
|
### 过去相位的 Boss
|
||||||
|
|
||||||
|
过去相位下,Boss 更偏向近战。
|
||||||
|
|
||||||
|
常见表现:
|
||||||
|
|
||||||
|
- 慢一些。
|
||||||
|
- 攻击更重。
|
||||||
|
- 会使用近身连击和突刺。
|
||||||
|
- 部分强力招式前会给你时间锚点压力。
|
||||||
|
|
||||||
|
应对方式:
|
||||||
|
|
||||||
|
- 不要贪刀。
|
||||||
|
- 观察它准备动作。
|
||||||
|
- 用冲刺斩调整位置。
|
||||||
|
- 用技能抓它收招后的空隙。
|
||||||
|
|
||||||
|
### 未来相位的 Boss
|
||||||
|
|
||||||
|
未来相位下,Boss 更偏向远程。
|
||||||
|
|
||||||
|
常见表现:
|
||||||
|
|
||||||
|
- 出手更频繁。
|
||||||
|
- 会使用射击和弹幕类攻击。
|
||||||
|
- 贴得太近时可能后撤拉开距离。
|
||||||
|
|
||||||
|
应对方式:
|
||||||
|
|
||||||
|
- 不要只站远处挨打。
|
||||||
|
- 找节奏接近它。
|
||||||
|
- 注意躲开弹道。
|
||||||
|
- 用斩波或冲刺斩寻找反击机会。
|
||||||
|
|
||||||
|
### Boss 战核心建议
|
||||||
|
|
||||||
|
- 普通攻击用于回能和维持节奏。
|
||||||
|
- 技能用于压制和打出爆发。
|
||||||
|
- 时间锚点尽量不要丢。
|
||||||
|
- 相位切换后,先观察 Boss 行为变化,不要立刻乱按。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 15. 防御、受击和击退
|
||||||
|
|
||||||
|
### 防御
|
||||||
|
|
||||||
|
地面按 S 可以防御。
|
||||||
|
|
||||||
|
防御适合:
|
||||||
|
|
||||||
|
- 你来不及闪开时。
|
||||||
|
- Boss 或强势敌人即将出手时。
|
||||||
|
- 想稳住节奏而不是继续贪攻击时。
|
||||||
|
|
||||||
|
注意:
|
||||||
|
|
||||||
|
- 防御更适合挡正面攻击。
|
||||||
|
- 背后被打时,防御效果会变差。
|
||||||
|
- 防御不是无敌。
|
||||||
|
|
||||||
|
### 受击
|
||||||
|
|
||||||
|
受到强势攻击或 Boss 攻击时,你可能会:
|
||||||
|
|
||||||
|
- 被打断当前动作。
|
||||||
|
- 进入短暂硬直。
|
||||||
|
- 被击退。
|
||||||
|
- 掉一部分攻击提升层数。
|
||||||
|
|
||||||
|
受到弱势小怪攻击时,通常只会掉血,不一定打断你。
|
||||||
|
|
||||||
|
### 击退
|
||||||
|
|
||||||
|
很多重攻击和技能都会击退敌人。
|
||||||
|
|
||||||
|
你也可能被敌人击退。被击退后,角色会短暂滑行一段距离,这段时间要等角色稳定后再重新找节奏出手。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 16. 推荐试玩思路
|
||||||
|
|
||||||
|
第一次试玩可以按这个顺序熟悉:
|
||||||
|
|
||||||
|
1. 先只用 A / D 跟节奏打普通攻击。
|
||||||
|
2. 观察 Perfect、Good、Bad、Miss 的区别。
|
||||||
|
3. 尝试 A A A 或 D D D 的三段攻击。
|
||||||
|
4. 尝试 A Space / D Space 的冲刺斩。
|
||||||
|
5. 试着用 W 把战斗带入空中。
|
||||||
|
6. 学会看过去/未来相位,优先打当前较弱的小怪。
|
||||||
|
7. 清小怪后进入 Boss 区域。
|
||||||
|
8. Boss 战中用普通攻击回能,用技能找机会压制。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 17. 简单打法建议
|
||||||
|
|
||||||
|
### 面对近战怪
|
||||||
|
|
||||||
|
- 等它靠近后,用普通攻击或冲刺斩换边。
|
||||||
|
- 它强势时少贪刀。
|
||||||
|
- 它弱势时集中输出。
|
||||||
|
|
||||||
|
### 面对远程怪
|
||||||
|
|
||||||
|
- 不要让它一直在远处安全射击。
|
||||||
|
- 用冲刺斩靠近。
|
||||||
|
- 用斩波对射。
|
||||||
|
- 它未来相位更危险,优先处理。
|
||||||
|
|
||||||
|
### 面对多只怪
|
||||||
|
|
||||||
|
- 先处理当前相位下较弱的怪。
|
||||||
|
- 不要站在两只怪中间。
|
||||||
|
- 用冲刺斩穿出去,避免被夹住。
|
||||||
|
- 能量够时,用重击或蓄力技能快速减少敌人数。
|
||||||
|
|
||||||
|
### 面对 Boss
|
||||||
|
|
||||||
|
- 普通攻击主要用于回能。
|
||||||
|
- 耗能技能更适合压制 Boss。
|
||||||
|
- Boss 近战相位不要硬拼太久。
|
||||||
|
- Boss 远程相位要主动寻找靠近机会。
|
||||||
|
- 时间锚点出现时优先稳住节奏。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 18. 常见问题
|
||||||
|
|
||||||
|
### 为什么按了方向键角色不走?
|
||||||
|
|
||||||
|
这是游戏设计。角色没有传统自由移动,移动来自动作。A、D、W、S、Space 都是节奏动作键。
|
||||||
|
|
||||||
|
### 为什么我按了键却没出招?
|
||||||
|
|
||||||
|
可能原因:
|
||||||
|
|
||||||
|
- 没踩中节奏,变成 Miss。
|
||||||
|
- 能量不足,技能放不出来。
|
||||||
|
- 正在上一招的收招阶段,还不能接下一招。
|
||||||
|
- 刚刚乱按触发了短暂按键失效。
|
||||||
|
|
||||||
|
### 为什么敌人有时候很硬,有时候很脆?
|
||||||
|
|
||||||
|
因为过去/未来相位会改变小怪强弱。
|
||||||
|
|
||||||
|
- 近战怪:过去强,未来弱。
|
||||||
|
- 远程怪:未来强,过去弱。
|
||||||
|
|
||||||
|
强势时敌人更难打,也更危险;弱势时更适合输出。
|
||||||
|
|
||||||
|
### 为什么相位突然切换?
|
||||||
|
|
||||||
|
你可能没有守住时间锚点。时间锚点出现后,如果没有及时输入,或者输入 Miss,就会切换过去/未来。
|
||||||
|
|
||||||
|
### 为什么我的攻击突然变强?
|
||||||
|
|
||||||
|
你可能连续守住了时间锚点,获得了攻击提升层数。层数越高,攻击越强。
|
||||||
|
|
||||||
|
### 为什么攻击提升又掉了?
|
||||||
|
|
||||||
|
相位切换会掉层,受到伤害进入受击状态也会掉层。
|
||||||
|
|
||||||
|
### 为什么冲刺斩可以穿过敌人?
|
||||||
|
|
||||||
|
这是冲刺斩的特点。它允许你穿过敌人,用来调整站位、躲避夹击或靠近远程敌人。
|
||||||
|
|
||||||
|
### 为什么普通攻击打 Boss 感觉压不住?
|
||||||
|
|
||||||
|
Boss 对普通攻击更难被压制。普通攻击适合回能和找节奏,真正压制 Boss 更依赖消耗能量的技能。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 19. 一句话小抄
|
||||||
|
|
||||||
|
- 跟节奏,不要乱按。
|
||||||
|
- A / D 普攻回能。
|
||||||
|
- Space 派生技能。
|
||||||
|
- A / D 长按松开是剑雨。
|
||||||
|
- S 长按后按 Space 是斩波。
|
||||||
|
- W 进入空中节奏。
|
||||||
|
- 过去重点小心近战怪。
|
||||||
|
- 未来重点小心远程怪。
|
||||||
|
- 时间锚点尽量守住。
|
||||||
|
- Boss 用普通攻击回能,用技能压制。
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
[ext_resource type="Resource" path="res://resources/effects/effect_on_landed_haste.tres" id="30_landed_haste"]
|
[ext_resource type="Resource" path="res://resources/effects/effect_on_landed_haste.tres" id="30_landed_haste"]
|
||||||
|
|
||||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_player"]
|
[sub_resource type="RectangleShape2D" id="RectangleShape2D_player"]
|
||||||
size = Vector2(18, 48)
|
size = Vector2(34, 52)
|
||||||
|
|
||||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_hitbox"]
|
[sub_resource type="RectangleShape2D" id="RectangleShape2D_hitbox"]
|
||||||
size = Vector2(48, 40)
|
size = Vector2(48, 40)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
[ext_resource type="Script" path="res://scenes/components/frame_collision_driver.gd" id="18_frame_collision"]
|
[ext_resource type="Script" path="res://scenes/components/frame_collision_driver.gd" id="18_frame_collision"]
|
||||||
|
|
||||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_body"]
|
[sub_resource type="RectangleShape2D" id="RectangleShape2D_body"]
|
||||||
size = Vector2(24, 58)
|
size = Vector2(52, 64)
|
||||||
|
|
||||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_hurtbox"]
|
[sub_resource type="RectangleShape2D" id="RectangleShape2D_hurtbox"]
|
||||||
size = Vector2(28, 62)
|
size = Vector2(28, 62)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
[ext_resource type="Script" path="res://scenes/components/overhead_health_bar.gd" id="18_overhead_health_bar"]
|
[ext_resource type="Script" path="res://scenes/components/overhead_health_bar.gd" id="18_overhead_health_bar"]
|
||||||
|
|
||||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_body"]
|
[sub_resource type="RectangleShape2D" id="RectangleShape2D_body"]
|
||||||
size = Vector2(20, 46)
|
size = Vector2(36, 50)
|
||||||
|
|
||||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_hurtbox"]
|
[sub_resource type="RectangleShape2D" id="RectangleShape2D_hurtbox"]
|
||||||
size = Vector2(24, 50)
|
size = Vector2(24, 50)
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ script = ExtResource("1_ground_background")
|
|||||||
[node name="GroundAnchor" type="Marker2D" parent="."]
|
[node name="GroundAnchor" type="Marker2D" parent="."]
|
||||||
position = Vector2(2047, 560)
|
position = Vector2(2047, 560)
|
||||||
|
|
||||||
|
[node name="SkyParallaxLayer" type="Node2D" parent="."]
|
||||||
|
position = Vector2(896.5, 0)
|
||||||
|
z_index = -11
|
||||||
|
|
||||||
[node name="ArtLayer" type="Node2D" parent="."]
|
[node name="ArtLayer" type="Node2D" parent="."]
|
||||||
position = Vector2(896.5, 0)
|
position = Vector2(896.5, 0)
|
||||||
z_index = -10
|
z_index = -10
|
||||||
@@ -56,4 +60,4 @@ position = Vector2(3213.5, 150)
|
|||||||
collision_mask = 0
|
collision_mask = 0
|
||||||
|
|
||||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="RightBoundaryBody"]
|
[node name="CollisionShape2D" type="CollisionShape2D" parent="RightBoundaryBody"]
|
||||||
shape = SubResource("RectangleShape2D_right_boundary")
|
shape = SubResource("RectangleShape2D_right_boundary")
|
||||||
|
|||||||
@@ -1,27 +1,26 @@
|
|||||||
extends Node2D
|
extends Node2D
|
||||||
|
|
||||||
## 2026-07-06 视差定案(策划:远景夕阳要时刻在画面内):背景是"远景天幕",
|
## 2026-07-07 视差修正:当前美术图里地面/步道和远景烙在同一张图上。
|
||||||
## 仅以 0.2 倍速随世界滚动(即 80% 跟随相机)——此前 0.92/0.8 的"近景层"
|
## 为了让角色脚下参照物保持 1:1 世界坐标,ArtLayer 先作为 WorldGroundLayer
|
||||||
## 参数方向反了,背景几乎钉在世界上,所以一直"看不出视差"。
|
## 固定不随相机做视差。未来美术拆出天空/夕阳后,把远景节点放入
|
||||||
## 锚点 1734.5 = 让夕阳(原画 x≈760,世界 x≈1656)在出生点镜头(可见中心
|
## SkyParallaxLayer,即可继续按 0.2 倍世界滚动(80% 跟随相机)。
|
||||||
## 1340.8)恰好居中;相机全程 [1340.8, 2753.2] 内夕阳屏幕位置从中心缓移到
|
## 勿用 Parallax2D(历史上因 CanvasModulate/wipe 兼容性移除过,见 decisions.md)。
|
||||||
## 左 282px,始终在画面内(已验算含光晕不出屏)。两端背景覆盖也已验算:
|
|
||||||
## 极左窗 [880,1801.6]⊂[581.5,2882.5]、极右窗 [2292.4,3214]⊂[1711.5,4012.5]。
|
|
||||||
## 已知取舍:步道砖烙在原画里会明显随镜头走(角色脚下滑动感),单张烙死图
|
|
||||||
## 无解;美术把"近景步道"拆成独立 1:1 层后即可消除。勿用 Parallax2D
|
|
||||||
## (历史上因 CanvasModulate/wipe 兼容性移除过,见 decisions.md)。
|
|
||||||
const PARALLAX_SCROLL_SCALE := 0.2
|
const PARALLAX_SCROLL_SCALE := 0.2
|
||||||
const PARALLAX_ANCHOR_X := 1734.5
|
const PARALLAX_ANCHOR_X := 1734.5
|
||||||
|
|
||||||
@onready var past_background: CanvasItem = $ArtLayer/PastBackground
|
@onready var past_background: CanvasItem = $ArtLayer/PastBackground
|
||||||
@onready var future_background: CanvasItem = $ArtLayer/FutureBackground
|
@onready var future_background: CanvasItem = $ArtLayer/FutureBackground
|
||||||
@onready var _art_layer: Node2D = $ArtLayer
|
@onready var _world_ground_layer: Node2D = $ArtLayer
|
||||||
|
@onready var _sky_parallax_layer: Node2D = get_node_or_null("SkyParallaxLayer") as Node2D
|
||||||
|
|
||||||
var _art_base_x := 0.0
|
var _world_ground_base_x := 0.0
|
||||||
|
var _sky_parallax_base_x := 0.0
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
_art_base_x = _art_layer.position.x
|
_world_ground_base_x = _world_ground_layer.position.x
|
||||||
|
if _sky_parallax_layer != null:
|
||||||
|
_sky_parallax_base_x = _sky_parallax_layer.position.x
|
||||||
_set_time_phase(_current_time_phase())
|
_set_time_phase(_current_time_phase())
|
||||||
var bus := _event_bus_or_null()
|
var bus := _event_bus_or_null()
|
||||||
if bus != null and bus.has_signal("time_phase_changed") and not bus.is_connected("time_phase_changed", _on_time_phase_changed):
|
if bus != null and bus.has_signal("time_phase_changed") and not bus.is_connected("time_phase_changed", _on_time_phase_changed):
|
||||||
@@ -29,14 +28,17 @@ func _ready() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
func _process(_delta: float) -> void:
|
||||||
|
_world_ground_layer.position.x = _world_ground_base_x
|
||||||
|
if _sky_parallax_layer == null:
|
||||||
|
return
|
||||||
var camera := get_viewport().get_camera_2d()
|
var camera := get_viewport().get_camera_2d()
|
||||||
if camera == null:
|
if camera == null:
|
||||||
# headless 测试/无相机场景:背景保持原位。
|
# headless 测试/无相机场景:远景层也保持原位。
|
||||||
_art_layer.position.x = _art_base_x
|
_sky_parallax_layer.position.x = _sky_parallax_base_x
|
||||||
return
|
return
|
||||||
# get_screen_center_position 含 limit 夹取,玩家贴墙时背景不会继续滑。
|
# get_screen_center_position 含 limit 夹取,玩家贴墙时远景不会继续滑。
|
||||||
var center_x := camera.get_screen_center_position().x
|
var center_x := camera.get_screen_center_position().x
|
||||||
_art_layer.position.x = _art_base_x + (center_x - PARALLAX_ANCHOR_X) * (1.0 - PARALLAX_SCROLL_SCALE)
|
_sky_parallax_layer.position.x = _sky_parallax_base_x + (center_x - PARALLAX_ANCHOR_X) * (1.0 - PARALLAX_SCROLL_SCALE)
|
||||||
|
|
||||||
|
|
||||||
func _on_time_phase_changed(_previous: StringName, current: StringName, _reason: StringName) -> void:
|
func _on_time_phase_changed(_previous: StringName, current: StringName, _reason: StringName) -> void:
|
||||||
@@ -63,4 +65,4 @@ func _current_time_phase() -> StringName:
|
|||||||
func _event_bus_or_null() -> Node:
|
func _event_bus_or_null() -> Node:
|
||||||
if not is_inside_tree():
|
if not is_inside_tree():
|
||||||
return null
|
return null
|
||||||
return get_tree().root.get_node_or_null("EventBus")
|
return get_tree().root.get_node_or_null("EventBus")
|
||||||
|
|||||||
@@ -0,0 +1,216 @@
|
|||||||
|
extends SceneTree
|
||||||
|
|
||||||
|
const PLAYER_BODY_MIN := Vector2(34, 52)
|
||||||
|
const MINION_BODY_MIN := Vector2(36, 50)
|
||||||
|
const BOSS_BODY_MIN := Vector2(52, 64)
|
||||||
|
const ENEMY_BODY_MASK := 1 << 6
|
||||||
|
|
||||||
|
var failures: Array[String] = []
|
||||||
|
|
||||||
|
|
||||||
|
func _init() -> void:
|
||||||
|
_run.call_deferred()
|
||||||
|
|
||||||
|
|
||||||
|
func _run() -> void:
|
||||||
|
root.size = Vector2i(1152, 648)
|
||||||
|
await process_frame
|
||||||
|
await _check_background_art_layer_is_world_locked()
|
||||||
|
await _check_body_shapes_match_visual_blocking_contract()
|
||||||
|
await _check_scene_body_rects_overlap_before_visible_interpenetration()
|
||||||
|
await _check_dash_through_ghost_query_uses_scene_body_shapes("Minion", "res://scenes/enemies/minion.tscn", 26.0, (PLAYER_BODY_MIN.x + MINION_BODY_MIN.x) * 0.5 + 2.0)
|
||||||
|
await _check_dash_through_ghost_query_uses_scene_body_shapes("Boss", "res://scenes/enemies/boss.tscn", 40.0, (PLAYER_BODY_MIN.x + BOSS_BODY_MIN.x) * 0.5 + 2.0)
|
||||||
|
_finish()
|
||||||
|
|
||||||
|
|
||||||
|
func _check_background_art_layer_is_world_locked() -> void:
|
||||||
|
var container := Node2D.new()
|
||||||
|
root.add_child(container)
|
||||||
|
var camera := Camera2D.new()
|
||||||
|
root.add_child(camera)
|
||||||
|
camera.global_position = Vector2(1734.5, 324.0)
|
||||||
|
camera.make_current()
|
||||||
|
|
||||||
|
var ground := (load("res://scenes/ground/ground1.tscn") as PackedScene).instantiate() as Node2D
|
||||||
|
container.add_child(ground)
|
||||||
|
await process_frame
|
||||||
|
await process_frame
|
||||||
|
|
||||||
|
var art_layer := ground.get_node("ArtLayer") as Node2D
|
||||||
|
var base_x := art_layer.position.x
|
||||||
|
camera.global_position.x += 640.0
|
||||||
|
await process_frame
|
||||||
|
await process_frame
|
||||||
|
_expect_float(art_layer.position.x, base_x, "Ground ArtLayer should stay locked to world x while camera moves")
|
||||||
|
|
||||||
|
camera.free()
|
||||||
|
container.free()
|
||||||
|
|
||||||
|
|
||||||
|
func _check_body_shapes_match_visual_blocking_contract() -> void:
|
||||||
|
var player := _instantiate_body("res://scenes/characters/player.tscn", "Player") as CharacterBody2D
|
||||||
|
var minion := _instantiate_body("res://scenes/enemies/minion.tscn", "Minion") as CharacterBody2D
|
||||||
|
var boss := _instantiate_body("res://scenes/enemies/boss.tscn", "Boss") as CharacterBody2D
|
||||||
|
|
||||||
|
_expect_vector_min(_body_shape_size(player), PLAYER_BODY_MIN, "Player body shape should cover the visual torso/feet")
|
||||||
|
_expect_vector_min(_body_shape_size(minion), MINION_BODY_MIN, "Minion body shape should cover the visual torso/feet")
|
||||||
|
_expect_vector_min(_body_shape_size(boss), BOSS_BODY_MIN, "Boss body shape should cover the visual torso/feet")
|
||||||
|
|
||||||
|
player.free()
|
||||||
|
minion.free()
|
||||||
|
boss.free()
|
||||||
|
|
||||||
|
|
||||||
|
func _check_scene_body_rects_overlap_before_visible_interpenetration() -> void:
|
||||||
|
var player := _instantiate_body("res://scenes/characters/player.tscn", "Player") as CharacterBody2D
|
||||||
|
var minion := _instantiate_body("res://scenes/enemies/minion.tscn", "Minion") as CharacterBody2D
|
||||||
|
var boss := _instantiate_body("res://scenes/enemies/boss.tscn", "Boss") as CharacterBody2D
|
||||||
|
_disable_enemy_ai(minion)
|
||||||
|
_disable_enemy_ai(boss)
|
||||||
|
|
||||||
|
player.global_position = Vector2(500.0, 300.0)
|
||||||
|
minion.global_position = Vector2(526.0, 300.0)
|
||||||
|
boss.global_position = Vector2(540.0, 300.0)
|
||||||
|
await physics_frame
|
||||||
|
|
||||||
|
_expect(_body_rects_overlap(player, minion), "Player/Minion bodies should still overlap at a 26px center gap")
|
||||||
|
_expect(_body_rects_overlap(player, boss), "Player/Boss bodies should still overlap at a 40px center gap")
|
||||||
|
|
||||||
|
minion.global_position = Vector2(500.0 + (PLAYER_BODY_MIN.x + MINION_BODY_MIN.x) * 0.5 + 2.0, 300.0)
|
||||||
|
boss.global_position = Vector2(500.0 + (PLAYER_BODY_MIN.x + BOSS_BODY_MIN.x) * 0.5 + 2.0, 300.0)
|
||||||
|
await physics_frame
|
||||||
|
|
||||||
|
_expect(not _body_rects_overlap(player, minion), "Player/Minion bodies should separate beyond the enlarged half-width sum")
|
||||||
|
_expect(not _body_rects_overlap(player, boss), "Player/Boss bodies should separate beyond the enlarged half-width sum")
|
||||||
|
|
||||||
|
player.free()
|
||||||
|
minion.free()
|
||||||
|
boss.free()
|
||||||
|
|
||||||
|
|
||||||
|
func _check_dash_through_ghost_query_uses_scene_body_shapes(label: String, scene_path: String, overlap_gap: float, clear_gap: float) -> void:
|
||||||
|
var player := _instantiate_body("res://scenes/characters/player.tscn", "Player") as CharacterBody2D
|
||||||
|
var enemy := _instantiate_body(scene_path, label) as CharacterBody2D
|
||||||
|
_disable_enemy_ai(enemy)
|
||||||
|
await process_frame
|
||||||
|
|
||||||
|
_expect(player.get_node_or_null("MotionExecutor") != null, "Player scene should include MotionExecutor for dash-through")
|
||||||
|
_expect(player.get_node_or_null("FrameCollisionDriver") != null, "Player scene should include FrameCollisionDriver")
|
||||||
|
_expect(enemy.get_node_or_null("FrameCollisionDriver") != null, "%s scene should include FrameCollisionDriver" % label)
|
||||||
|
|
||||||
|
player.global_position = Vector2(700.0, 300.0)
|
||||||
|
enemy.global_position = Vector2(700.0 + overlap_gap, 300.0)
|
||||||
|
await physics_frame
|
||||||
|
|
||||||
|
var motion := player.get_node("MotionExecutor")
|
||||||
|
motion.call("execute", _dash_through_action(), Vector2.RIGHT, 0.5, 220.0)
|
||||||
|
await physics_frame
|
||||||
|
_expect((int(player.collision_mask) & ENEMY_BODY_MASK) == 0, "dash_through should ghost enemy bodies before restore")
|
||||||
|
_expect(bool(motion.call("_overlaps_ghosted_bodies")), "dash_through ghost query should detect overlapped %s body shape" % label)
|
||||||
|
|
||||||
|
enemy.global_position = Vector2(700.0 + clear_gap, 300.0)
|
||||||
|
await physics_frame
|
||||||
|
_expect(not bool(motion.call("_overlaps_ghosted_bodies")), "dash_through ghost query should clear after %s body separation" % label)
|
||||||
|
|
||||||
|
motion.call("cancel")
|
||||||
|
_expect(not bool(motion.call("is_ghosting")), "dash_through should restore collision after %s separation" % label)
|
||||||
|
player.free()
|
||||||
|
enemy.free()
|
||||||
|
|
||||||
|
|
||||||
|
func _dash_through_action() -> Resource:
|
||||||
|
var script: Script = load("res://resources/action_data.gd")
|
||||||
|
var action: Resource = script.new()
|
||||||
|
var action_tags: Array[StringName] = [&"dash_through"]
|
||||||
|
action.set("id", &"test_scene_dash_through")
|
||||||
|
action.set("action_tags", action_tags)
|
||||||
|
action.set("move_mult_x", 1.0)
|
||||||
|
action.set("startup_beats", 0.25)
|
||||||
|
action.set("active_beats", 0.25)
|
||||||
|
action.set("recovery_beats", 0.25)
|
||||||
|
return action
|
||||||
|
|
||||||
|
|
||||||
|
func _instantiate_body(scene_path: String, label: String) -> CharacterBody2D:
|
||||||
|
var scene := load(scene_path) as PackedScene
|
||||||
|
if scene == null:
|
||||||
|
failures.append("%s scene should load from %s" % [label, scene_path])
|
||||||
|
return null
|
||||||
|
var actor := scene.instantiate() as CharacterBody2D
|
||||||
|
if actor == null:
|
||||||
|
failures.append("%s scene root should be CharacterBody2D" % label)
|
||||||
|
return null
|
||||||
|
root.add_child(actor)
|
||||||
|
return actor
|
||||||
|
|
||||||
|
|
||||||
|
func _disable_enemy_ai(actor: CharacterBody2D) -> void:
|
||||||
|
if actor == null:
|
||||||
|
return
|
||||||
|
var behavior := actor.get_node_or_null("MinionBehavior")
|
||||||
|
if behavior != null:
|
||||||
|
behavior.set("enabled", false)
|
||||||
|
var boss_behavior := actor.get_node_or_null("BossBehaviorTree")
|
||||||
|
if boss_behavior != null:
|
||||||
|
boss_behavior.set("enabled", false)
|
||||||
|
actor.set_physics_process(false)
|
||||||
|
|
||||||
|
|
||||||
|
func _body_shape_size(actor: CharacterBody2D) -> Vector2:
|
||||||
|
if actor == null:
|
||||||
|
return Vector2.ZERO
|
||||||
|
var shape_node := actor.get_node_or_null("CollisionShape2D") as CollisionShape2D
|
||||||
|
if shape_node == null or shape_node.shape == null:
|
||||||
|
failures.append("%s should have a main CollisionShape2D" % actor.name)
|
||||||
|
return Vector2.ZERO
|
||||||
|
var rect := shape_node.shape as RectangleShape2D
|
||||||
|
if rect == null:
|
||||||
|
failures.append("%s main body shape should be RectangleShape2D" % actor.name)
|
||||||
|
return Vector2.ZERO
|
||||||
|
return rect.size
|
||||||
|
|
||||||
|
|
||||||
|
func _body_rects_overlap(left_actor: CharacterBody2D, right_actor: CharacterBody2D) -> bool:
|
||||||
|
var left_rect := _body_world_rect(left_actor)
|
||||||
|
var right_rect := _body_world_rect(right_actor)
|
||||||
|
return left_rect.intersects(right_rect, true)
|
||||||
|
|
||||||
|
|
||||||
|
func _body_world_rect(actor: CharacterBody2D) -> Rect2:
|
||||||
|
if actor == null:
|
||||||
|
return Rect2()
|
||||||
|
var shape_node := actor.get_node_or_null("CollisionShape2D") as CollisionShape2D
|
||||||
|
if shape_node == null or shape_node.shape == null:
|
||||||
|
failures.append("%s should have a main CollisionShape2D" % actor.name)
|
||||||
|
return Rect2()
|
||||||
|
var rect_shape := shape_node.shape as RectangleShape2D
|
||||||
|
if rect_shape == null:
|
||||||
|
failures.append("%s main body shape should be RectangleShape2D" % actor.name)
|
||||||
|
return Rect2()
|
||||||
|
var center := shape_node.global_position
|
||||||
|
return Rect2(center - rect_shape.size * 0.5, rect_shape.size)
|
||||||
|
|
||||||
|
|
||||||
|
func _expect(condition: bool, label: String) -> void:
|
||||||
|
if not condition:
|
||||||
|
failures.append(label)
|
||||||
|
|
||||||
|
|
||||||
|
func _expect_float(actual: float, expected: float, label: String) -> void:
|
||||||
|
if absf(actual - expected) > 0.5:
|
||||||
|
failures.append("%s: expected %.2f, got %.2f" % [label, expected, actual])
|
||||||
|
|
||||||
|
|
||||||
|
func _expect_vector_min(actual: Vector2, expected_min: Vector2, label: String) -> void:
|
||||||
|
if actual.x + 0.5 < expected_min.x or actual.y + 0.5 < expected_min.y:
|
||||||
|
failures.append("%s: expected at least %s, got %s" % [label, expected_min, actual])
|
||||||
|
|
||||||
|
|
||||||
|
func _finish() -> void:
|
||||||
|
if failures.is_empty():
|
||||||
|
print("PASS parallax and body collision contract")
|
||||||
|
quit(0)
|
||||||
|
else:
|
||||||
|
for failure: String in failures:
|
||||||
|
push_error(failure)
|
||||||
|
quit(1)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bdpqavi1hbe8t
|
||||||
@@ -7,9 +7,9 @@ extends SceneTree
|
|||||||
## 2. 趔趄门禁:击退滑行期间 Boss/小怪不得起手新动作(EnemyActionDriver
|
## 2. 趔趄门禁:击退滑行期间 Boss/小怪不得起手新动作(EnemyActionDriver
|
||||||
## 钩子),否则动作突进立刻接管 velocity 冲回玩家,把击退整个吃掉
|
## 钩子),否则动作突进立刻接管 velocity 冲回玩家,把击退整个吃掉
|
||||||
## (真实 stage 实测净位移为负);Boss 受击链强制撤退豁免(清残速标志)。
|
## (真实 stage 实测净位移为负);Boss 受击链强制撤退豁免(清残速标志)。
|
||||||
## 3. 关卡背景单层视差:背景水平速度为相机 0.8 倍(0.92 肉眼难辨已加强),
|
## 3. 关卡背景分层契约:当前整图里的地面/步道先锁在 1:1 世界层,避免脚下
|
||||||
## 锚点 2047,无相机保持原位;脚下地砖轻微滑动是单张烙死原画的已知
|
## 地砖相对角色滑动;美术拆出的天空/夕阳放入 SkyParallaxLayer 后继续按
|
||||||
## 取舍,无伤视差待美术拆层。
|
## 0.2 倍世界滚动(相机 0.8 跟随)。
|
||||||
## (Boss 击退 ×9 的加码钉在 test_round3_fixes。)
|
## (Boss 击退 ×9 的加码钉在 test_round3_fixes。)
|
||||||
|
|
||||||
var failures: Array[String] = []
|
var failures: Array[String] = []
|
||||||
@@ -147,24 +147,28 @@ func _check_background_parallax() -> void:
|
|||||||
await process_frame
|
await process_frame
|
||||||
await process_frame
|
await process_frame
|
||||||
var art_layer := ground.get_node("ArtLayer") as Node2D
|
var art_layer := ground.get_node("ArtLayer") as Node2D
|
||||||
|
var sky_layer := ground.get_node("SkyParallaxLayer") as Node2D
|
||||||
|
|
||||||
# 无相机:背景保持原位(headless/菜单兜底)。
|
# 无相机:世界层与可选远景层都保持原位(headless/菜单兜底)。
|
||||||
_expect_float(art_layer.position.x, 896.5, "无相机时背景保持基准位置")
|
_expect_float(art_layer.position.x, 896.5, "无相机时地面世界层保持基准位置")
|
||||||
|
_expect_float(sky_layer.position.x, 896.5, "无相机时远景视差层保持基准位置")
|
||||||
|
|
||||||
# 有相机:偏移 = (相机中心 - 1734.5) × 0.8——"远景天幕"语义,背景 80%
|
# 有相机:地面/步道保持 1:1 世界坐标;远景容器按
|
||||||
# 跟随相机,仅 0.2 倍速随世界滚动,保证远景夕阳时刻在画面内(策划定案)。
|
# (相机中心 - 1734.5) × 0.8 视差移动。
|
||||||
var camera := Camera2D.new()
|
var camera := Camera2D.new()
|
||||||
root.add_child(camera)
|
root.add_child(camera)
|
||||||
camera.make_current()
|
camera.make_current()
|
||||||
camera.global_position = Vector2(2547.0, 395.0)
|
camera.global_position = Vector2(2547.0, 395.0)
|
||||||
await process_frame
|
await process_frame
|
||||||
await process_frame
|
await process_frame
|
||||||
_expect_float(art_layer.position.x, 896.5 + (2547.0 - 1734.5) * 0.8, "背景 80% 跟随相机(天幕视差)")
|
_expect_float(art_layer.position.x, 896.5, "相机右移时地面世界层不做视差")
|
||||||
|
_expect_float(sky_layer.position.x, 896.5 + (2547.0 - 1734.5) * 0.8, "相机右移时远景层 80% 跟随")
|
||||||
|
|
||||||
camera.global_position = Vector2(1547.0, 395.0)
|
camera.global_position = Vector2(1547.0, 395.0)
|
||||||
await process_frame
|
await process_frame
|
||||||
await process_frame
|
await process_frame
|
||||||
_expect_float(art_layer.position.x, 896.5 + (1547.0 - 1734.5) * 0.8, "相机左移时天幕对称跟随")
|
_expect_float(art_layer.position.x, 896.5, "相机左移时地面世界层仍不做视差")
|
||||||
|
_expect_float(sky_layer.position.x, 896.5 + (1547.0 - 1734.5) * 0.8, "相机左移时远景层对称跟随")
|
||||||
|
|
||||||
camera.free()
|
camera.free()
|
||||||
ground.free()
|
ground.free()
|
||||||
|
|||||||
Reference in New Issue
Block a user