# 声明
本blog对7.0.1的Arc-mobile进行了逆向,请勿用于非法用途
# 正文
# 初步
Hopper 字符串表包含:
N7cocos2d14ParticleSystemEN7cocos2d18ParticleSystemQuadEN10cocostudio14ParticleReaderEN10cocostudio19ParticleDisplayDataEShader3DParticleColorShader3DParticleTexturemaxParticlesstartParticleSizefinishParticleSizeparticleLifespan
然后我们去资源文件的particle里找,可以看到:
particle/particle_arc.plist
particle/particle_arc_mirai_light.plist
particle/particle_arc_mirai_conflict.plist
particle/note_light.png
particle/note_conflict.png
particle/note_colorless.png
particle/note_sfx.png
# 初始化函数
# arc
这边可以看到701二进制的 0x100a5a460 这个sub读取了一大堆主题参数
不难注意到字符串里的plist
这些东西通过cocos2dx的vfunc配置并启动粒子对象,然后进行循环初始化
最后它访问里 0x124写入0x2

# hold
hold这边 0x100099adc和0x100c1ea80引用,不难注意到这个firstHoldAction:
这里hold里action的逻辑和particle的plist是拆分开的,用独立的particle system资源驱动
# 具体参考
# 首先
光说这些没用,我们不妨看看Arcade-Plus怎么写的(这里让ai画了个图):
ArcGameplayManager.Load(chart, audio)
├─ ArcTimingManager.Load(...)
├─ ArcHoldNoteManager.Load(chart.Holds)
│ └─ ArcHold.Instantiate() -> HoldNotePrefab
└─ ArcArcManager.Load(chart.Arcs)
├─ ArcArc.Instantiate()
├─ ArcArc.Rebuild()
└─ CalculateArcRelationship()
每帧:
ArcGameplayManager.Update()
├─ 推进 AudioTiming / ChartTiming
├─ ArcHoldNoteManager.Update()
│ ├─ JudgeHoldNotes()(Auto 模式)
│ └─ RenderHoldNotes()
├─ ArcArcManager.Update()
│ ├─ JudgeArcs()(Auto 模式)
│ └─ RenderArcs()
└─ ArcEffectManager.Update()
└─ 更新 6 个 Hold VisualEffect 的位置和 Play/Stop 状态
(这里Hold/Arc的开始、结束、判定窗口以及粒子开关都由同一条毫秒时间轴驱动,注意不是粒子系统自身的时间)
# hold
# 初始化
ArcEffectManager.Start() 创建Tap粒子对象池,并保存6个Hold粒子实例的初始世界坐标:
for (int i = 0; i < 6; ++i) //HoldNoteEffects是长度为6的VisualEffect
//这里是对应6个track
HoldNoteEffectPosition[i] = HoldNoteEffects[i].transform.position;
hold本体是ArcHoldNoteManager创建的
# 判定部分
然后ArcHoldNoteManager.JudgeHoldNotes每帧调用
ArcEffectManager.Instance.ResetHoldNoteEffect();
该函数把6条轨道的holdEffectStatus全部置为false然后遍历所有 Hold
注意两个特殊的跳过:NoInput、Judged
然后用下面的公式:
currentTiming >= Timing && currentTiming <= EndTiming
Judging = true;
然后首次进入播放一个Tap音效
用SetHoldNoteEffect(t.Track, true)打开对应track的hold粒子
超过EndTiming的部分:
Judging = false;
Judged = true;
没有到开始时间的:
ShouldPlayAudio = true
同一轨道上多个同时重叠的 Hold 只需要一个粒子实例,状态数组表达的是该轨道本帧是否至少有一个正在判定的 Hold,而不是 Hold 数量,这里注意下
# 判定面
ArcEffectManager.Update()对每个track执行
HoldNoteEffects[track].transform.position =
EffectPlane.GetPositionOnPlane(HoldNoteEffectPosition[track]);
这一步把预置点转换到ArcEffectPlane所在的渲染平面,保证粒子不会因为相机/轨道空间变化而停留在错误的世界位置
然后比较期望状态holdEffectStatus[track]与实际状态holdEffectPlaying[track],
false -> true:Play(),随后Simulate(1f / 60f, 60)
true -> false:Stop(),随后同样Simulate(1f / 60f, 60)
Simulate(1/60, 60)的作用是把效果推进1秒,避免Play/Stop后粒子从空状态或旧状态突变
注意它不是游戏计时器,粒子是否显示仍由holdEffectStatus决定
# arc
# arc的segment
首先要明确一点:arc本体是网格分段
这里ArcArcRenderer.BuildSegments() 根据arc持续时间、EffectiveSmoothness、曲线函数(ArcAlgorithm.X/Y)、起止高度、时间到空间的换算将arc拆成segment(代码内叫ArcArcSegmentComponent)
每个segment生成网格,并同时生成阴影网格
ArcArcSegmentComponent.BuildSegment()保存FromTiming、ToTiming、FromPos、ToPos,暂时不生成粒子
Arc.shader 通过 _From、_To 裁切 segment 的 UV Y 范围,通过 uv2.x 在低色与高色之间插值:
if (i.uv.y < _From || i.uv.y > _To) return 0;
float4 inColor = lerp(_LowColor, _HighColor, clamp(abs(i.uv2.x), 0, 1));
//其实这个是hlsl但是似乎我的blog不支持hlsl的代码高亮所以写的c#
所以arc的移动、裁切、渐变主体是确定性网格渲染,而粒子只是命中/跟随反馈
# arc粒子的注入点
ArcEffectManager.SetParticleArcColor(start, end)对6个Hold VisualEffect设置StartColor和EndColor,然后转发到ArcArcManager.SetParticleArcColor(),后者取得 ArcNotePrefab上的 ArcArcRenderer,并对其JudgeEffect设置同名颜色属性
(SetParticleArcTexture(texture) 具有同样的双路转发,hold的6个VisualEffect设置 Texture,然后Arc prefab的JudgeEffect设置Texture)
主题这部分先不写了
# 为啥写这东西
闲的
好吧,主要是因为oa next调这玩意调半天才调好,期间多次求助ai,所以记录下