就是用 luojun1 大大的脚本解析成文本后再一行行匹配。
我只是用不上文本。文本跟语音的映射键是一样的,带参数的配有语音。
就是用 luojun1 大大的脚本解析成文本后再一行行匹配。
我只是用不上文本。文本跟语音的映射键是一样的,带参数的配有语音。
在luojun1大大的脚本的基础上我和游戏播放效果对比猜测得到了如下信息:
新增指令映射:
-18: “比较栈顶值与给定值”,
-17: “(选项)写入栈顶”,
-10: “设置下一条指令位置”,
-8: “栈顶为给定值跳转指定地址”, #一般跟一个-18指令和偏移量
-7: “弹出(清空)栈顶”,
-6: “设置脚本继续与否的标志”,
以及转换为我自己写的播放脚本格式的部分代码,其中注释什么的可能和真实参数有区别,以变量命名为准,很多参数也没有猜出来有什么用,不过基本上涵盖了绝大多数播放代码,用来播放效果已经很接近了:
def convert_commands(commands, evsc_data):
“”“打印命令列表(包含中文指令解释)”“”
if not commands:
print(“\n===== 没有命令 =====”)
return
print("\n===== 命令列表 =====")
print(f"共 {len(commands)} 条命令:")
print("-" * 150)
print(f"{'索引':<5} {'偏移':<10} {'类型':<5} {'调试偏移':<10} {'参数':<100}")
print("-" * 150)
is_unknown_instruction = False
files = []
processes = []
instructions = []
instruction = []
for i, cmd in enumerate(commands):
if cmd.param.instruction:
is_unknown_instruction = False
if len(instruction) > 0:
instructions.append(instruction)
# 关键修改:用指令编号减30作为字典的键
mapped_key = cmd.param.instruction.no - 30
if mapped_key not in INSTRUCTION_EXPLANATIONS:
is_unknown_instruction = True
instruction = []
continue
instruction = []
instruction.append(cmd.data_offset)
instruction.append(mapped_key)
elif not is_unknown_instruction:
if cmd.param.integer:
instruction.append(cmd.param.integer.value)
elif cmd.param.float_val:
instruction.append(cmd.param.float_val.value)
elif cmd.param.string:
str_val = evsc_data._get_string_from_pool(cmd.param.string.string_offset)
instruction.append(str_val)
elif cmd.param.stack_variable:
instruction.append(cmd.param.stack_variable.variable_offset)
else:
instruction.append(f"0x{cmd.param.data:X}")
if len(instruction) > 0:
instructions.append(instruction)
time = 0
text = []
textIndex = 0
show_stack = []
wait_jump_list = []
for i, instruction in enumerate(instructions):
offset_t = instruction.pop(0)
for j in wait_jump_list:
if j["payload"]["to"] == offset_t - header_size:
j["payload"]["to"] = len(processes)
wait_jump_list = list(filter(lambda x: x["payload"]["to"] != len(processes), wait_jump_list))
if len(instruction) == 0 or instruction[0] not in INSTRUCTION_EXPLANATIONS:
continue
explain = INSTRUCTION_EXPLANATIONS[instruction[0]]
# 一种一种处理了
if instruction[0] == 0: # 无操作(NOP)
continue
elif instruction[0] == -6: # 设置脚本继续与否的标志
continue
elif instruction[0] == -7: # 清理栈顶/丢弃临时值 (pop discard)
processes.append({
'time': time,
'type': "clear_stack",
'payload':{
'param': instruction[1],
}
})
elif instruction[0] == -8: # 比较跳转
print(instruction)
print(instructions[i+1])
if instructions[i+1][1] == -18:
ele = {
'time': time,
'type': "compare_jump",
'payload':{
'stack_position': instructions[i+1][2],
'value': instructions[i+1][3],
'to': instructions[i+1][4]
}
}
processes.append(ele)
wait_jump_list.append(ele)
elif instruction[0] == -10: # 跳转
ele = {
'time': time,
'type': "jump",
'payload':{
'to': instruction[1]
}
}
processes.append(ele)
wait_jump_list.append(ele)
elif instruction[0] == -17: # 写入栈顶
ele = {
'time': time,
'type': "write_stack",
'payload':{
'stack_position': instruction[1]
}
}
processes.append(ele)
elif instruction[0] == -18: # 比较栈顶值与给定值
continue
elif instruction[0] == 1: # 读取消息
# 字符串(偏移: 888, 值: 'アスカ') 名字
# 字符串(偏移: 900, 值: 'ご主人!$nやっと着いたっスよ!$n陰陽寮っス!') 内容
# 整数: 23
# 整数: 0
# 整数: 9100092 语音
if len(instruction) < 6:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "show_message",
'payload':{
'name': textIndex,
'content': textIndex,
'speed': instruction[3],
'param4': instruction[4],
'voiceId': instruction[5],
},
'blocking': True
})
text.append([instruction[1], instruction[2]])
textIndex+=1
elif instruction[0] == 2: # 等待工作
time += instruction[1]
elif instruction[0] == 3: # 打开章节
# 60 0 不清楚
continue
elif instruction[0] == 4: # 关闭章节
# 40 0 不清楚,应该就是等这么多时间
if len(instruction) < 3:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "close_chapter",
'payload': {
'duration': instruction[1]
}
})
if instruction[2] == 0:
time += instruction[1]
continue
elif instruction[0] == 5: # 角色删除
# 9002 删除元素
if len(instruction) < 2:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "delete_character",
'payload': {
'id': instruction[1]
}
})
elif instruction[0] == 6: # 角色显示注册
# 整数: 9003 id
# 整数: 9003 人物id advcharacter9003
# 整数: 1 人物表情 advcharacter9003facial0001
# 整数: 1 四个参数确定左中右(012)
# 整数: 0
# 整数: 3
if len(instruction) < 7:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
ele = {
'time': time,
'type': "show_character",
'payload': {
'id': instruction[1],
'characterId': instruction[2],
'facialId': instruction[3],
'position': instruction[4], # 0: 左, 1: 中, 2: 右
'param5': instruction[5],
'param6': instruction[6]
}
}
processes.append(ele)
show_stack.append(ele)
elif instruction[0] == 7: # 角色动作跳转
# 9002 15 8 1 -》表情变换的同时进行一些动作 这个是跳了一下
if len(instruction) < 5:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "character_action_jump",
'payload': {
'id': instruction[1],
'dy': instruction[2],
'duration': instruction[3],
}
})
if instruction[4] == 0:
time += instruction[3]
elif instruction[0] == 8: # 文件预加载
# 预加载文件,通常是背景或角色
if len(instruction) < 4:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
files.append([instruction[1], instruction[2], instruction[3]])
elif instruction[0] == 9: # 角色移动
# 9003 0 -10 10 0 向下移动一点然后回原位 第二三个参数我位移距离和方向(x和y轴) 第四个参数代表时间 推测最后一个惨表示是否回原位
# 9003 0 -20 8 1 向下移动一点然后不回原位
if len(instruction) < 6:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "move_character",
'payload': {
'id': instruction[1],
'dx': instruction[2],
'dy': instruction[3],
'duration': instruction[4]
}
})
if instruction[5] == 0:
time += instruction[4]
elif instruction[0] == 10: # 显示执行
# 0 0 0 -》不知道 ; 生成时间 ;是否异步(1的时候不会影响time)
if len(instruction) < 4:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
for ele in show_stack:
ele["time"] = time
ele["payload"]['duration'] = instruction[2]
show_stack.clear()
if instruction[3] == 0:
time += instruction[2]
elif instruction[0] == 11: # 角色退出
# 9003 4 0 -》id 中间可能是时间 第三个参数表示是否异步(1的时候不会影响time)
if len(instruction) < 4:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "exit_character",
'payload': {
'id': instruction[1],
'duration': instruction[2]
}
})
if instruction[3] == 0:
time += instruction[2]
elif instruction[0] == 12: # 角色滑动
# 整数: 9002
# 整数: 20 感觉是滑动的距离
# 整数: 3
# 整数: 5
# 整数: 1
# 推测为左右震动
if len(instruction) < 6:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "slide_character",
'payload': {
'id': instruction[1],
'distance': instruction[2],
'times': instruction[3],
'duration': instruction[4]
}
})
if instruction[5] == 0:
time += instruction[4] * instruction[3]
elif instruction[0] == 13: # 白场淡入
# 20 0 整个场景去白 第一个参数为时间
if len(instruction) < 3:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "fade_in",
'payload': {
'color': "white", # 白色淡入
'duration': instruction[1]
}
})
if instruction[2] == 0:
time += instruction[1]
elif instruction[0] == 14: # 白场淡出
# 20 0 整个场景变白
if len(instruction) < 3:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "fade_out",
'payload': {
'color': "white", # 白色淡入
'duration': instruction[1]
}
})
if instruction[2] == 0:
time += instruction[1]
elif instruction[0] == 15: # 黑场淡入
# 30 0 同上
if len(instruction) < 3:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "fade_in",
'payload': {
'color': "black", # 黑色淡入
'duration': instruction[1]
}
})
if instruction[2] == 0:
time += instruction[1]
elif instruction[0] == 16: # 黑场淡出
# 30 0
if len(instruction) < 3:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "fade_out",
'payload': {
'color': "black", # 黑色淡出
'duration': instruction[1]
}
})
if instruction[2] == 0:
time += instruction[1]
elif instruction[0] == 17: # 震动效果
# 第一个参数0代表整个背景1代表对话框 第二个参数0代表上下1代表左右,第三个参数是振幅 第四个参数应该是时间/次数 第五个参数应该是次数/时间 第六个参数我是否反方向运动 (0表示反方向也要动,反方向也算一次运动)
# 1 0 30 2 5 1 对话框上下震荡
if len(instruction) < 7:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
# todo times和duration具体谁是谁还是不确定
processes.append({
'time': time,
'type': "shake",
'payload': {
'target': "dialog" if instruction[1] == 1 else "background",
'direction': "y" if instruction[2] == 0 else "x",
'distance': instruction[3],
'times': instruction[4],
'duration': instruction[5],
}
})
if instruction[6] == 0:
time += instruction[5] * instruction[4]
elif instruction[0] == 18: # 相机缩放重置
# 0 0 代表立刻
if len(instruction) < 3:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "reset_camera",
'payload': {
"duration": instruction[1] # 立即重置
}
})
if instruction[2] == 0:
time += instruction[1]
elif instruction[0] == 19: # 相机缩放
# -200 -50 2 0 0 前两个为距离中心点的左边 x y , 第三个参数代表缩放倍数, 后两个参数代表如何变换, 0 表示无过度 0 表示消耗时间
# 原点为中心
if len(instruction) < 6:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "zoom_camera",
'payload': {
'dx': instruction[1],
'dy': instruction[2],
'scale': instruction[3],
'duration': instruction[4]
}
})
if instruction[5] == 0:
time += instruction[4]
elif instruction[0] == 20: # 停止音效(SE)
# 100003 -》10003音效停止
if len(instruction) < 2:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "stop_se",
'payload': {
'id': instruction[1]
}
})
elif instruction[0] == 21: # 播放音效(SE)
# 100003 0 -》10003音效 渐变时间
if len(instruction) < 3:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "play_se",
'payload': {
'id': instruction[1],
'duration': instruction[2]
}
})
time += instruction[2]
elif instruction[0] == 22: # 停止背景音乐(BGM)
# 60 -》渐变消失的时间?
if len(instruction) < 2:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "stop_bgm",
'payload': {
'duration': instruction[1]
}
})
time += instruction[1]
elif instruction[0] == 23: # 播放背景音乐(BGM)
# 30002 60 对应30002的引用,60不清楚干什么
if len(instruction) < 3:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "play_bgm",
'payload': {
'id': instruction[1],
'duration': instruction[2]
}
})
time += instruction[2]
elif instruction[0] == 24: # 背景显示注册
# 138 -> 准备显示 adventure/advbg/advbg0138.assetbundle作为背景
if len(instruction) < 2:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
ele = {
'time': time,
'type': "show_background",
'payload': {
'id': instruction[1],
}
}
processes.append(ele)
show_stack.append(ele)
elif instruction[0] == 25: # 分支显示
# 两个字符串,为空只有一个结果
if len(instruction) < 3:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "branch_show",
'payload': {
'option1': textIndex,
'option2': textIndex
},
'blocking': True
})
text.append([instruction[1], instruction[2]])
textIndex+=1
elif instruction[0] == 26: # 分支结果
# 分支结果 todo
print(f"指令 {i}:{instruction}({explain}) 未处理,跳过")
pass
elif instruction[0] == 27: # 消息窗口显示切换
# 0 -》0隐藏 1显示
if len(instruction) < 2:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "toggle_message_window",
'payload': {
'show': instruction[1] == 1
}
})
elif instruction[0] == 28: # 表情切换
# todo
print(f"指令 {i}:{instruction}({explain}) 未处理,跳过")
pass
elif instruction[0] == 29: # 播放影片
if len(instruction) < 3:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "play_movie",
'payload': {
'characterId': instruction[1],
'sceneId': instruction[2]
}
})
pass
elif instruction[0] == 30: # 对话亮度调整
# 9002 0 0
# 0 0 9003 应该是三个参数代表左中右,为0的表示该位置的变暗
if len(instruction) < 4:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "character_brightness",
'payload': {
'left': 1 if instruction[1] > 0 else 0.3,
'middle': 1 if instruction[2] > 0 else 0.3,
'right': 1 if instruction[3] > 0 else 0.3
}
})
elif instruction[0] == 31: # 颜色淡入(H)
# 10 1 0 为从左往右的黑色消失 todo
# 15 1 0
if len(instruction) < 4:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "fade_in_horizon",
'payload': {
'color': "black" if instruction[2] == 1 else "white", # 黑色淡入
'duration': instruction[1],
}
})
if instruction[3] == 0:
time += instruction[1]
elif instruction[0] == 32: # 颜色淡出(H)
# 整数: 15 代表到遮住的时间,同理上面的代表完全显示出来的时间
# 整数: 1
# 整数: 0
# 10 1 0 为从左往右的黑色(最后遮住全屏)
# 15 1 0
if len(instruction) < 4:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "fade_out_horizon",
'payload': {
'color':"black" if instruction[2] == 1 else "white", # 黑色淡出
'duration': instruction[1],
"param2": instruction[2]
}
})
if instruction[3] == 0:
time += instruction[1]
elif instruction[0] == 33: # 文件加载
# 0为advcharacter 0 9002 0 -》 ui/standing_chara/full/full_chara9002.assetbundle
# 0为advcharacter 0 9002 1 -》 adventure/advcharacter/advcharacter9002.assetbundle
# 1为advbg 1 118 1 -》 adventure/advbg/advbg0118.assetbundle
# 1为advbg 1 118 0 -》 adventure/advbg/advbg0118.assetbundle
# 3为静态adv场景(9000 2 -》adventure/advstill/advstill9000/still9000_scene02.assetbundle)
# 4为音效sound/se/adv/ 4 100004 1 -> 首先根据第二个参数作为id去SoundSeMasterDatas中找,然后根据对应的参数和formatId去SoundSeFormatMasterDatas中找
# 5为sound/voice/adv/ 5 9100022 1 -> 首先根据第二个参数作为id去SoundVoiceMasterDatas中找,然后根据对应的参数和formatId去SoundVoiceFormatMasterDatas中找
# 6为加载se 6 2 0 -> ss/adv/adveffect0002.assetbundle
# 7为加载音乐 7 30001 1 -> 首先根据第二个参数作为id去SoundBgmMasterDatas中找,然后根据对应的参数和formatId去SoundBgmFormatMasterDatas中找
if len(instruction) < 4:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
files.append([instruction[1], instruction[2], instruction[3]])
elif instruction[0] == 34: # 播放特效动画
# 无法播放,忽略
if len(instruction) < 2:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
print(f"指令 {i} ({explain}) 无法播放特效动画,跳过")
elif instruction[0] == 35: # 停止影片
if len(instruction) < 1:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "stop_movie",
'payload': {
}
})
pass
elif instruction[0] == 36: # 设置阿尔法遮罩
# 9002 2 1 0 1 为9002元素设置遮罩 其他应该是遮罩的设置 todo
if len(instruction) < 6:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "set_alpha_mask",
'payload': {
'id': instruction[1],
'param2': instruction[2],
'param3': instruction[3],
'param4': instruction[4]
}
})
elif instruction[0] == 37: # 黑框开启
if len(instruction) < 3:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "black_frame_on",
'payload': {
'duration': instruction[1]
}
})
if instruction[2] == 0:
time += instruction[1]
elif instruction[0] == 38: # 黑框关闭
# todo
if len(instruction) < 3:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "black_frame_off",
'payload': {
'duration': instruction[1]
}
})
if instruction[2] == 0:
time += instruction[1]
elif instruction[0] == 39: # 暗效果开启
# 15 0 -> 第一个应该是变换时间,效果为整个场景变暗
if len(instruction) < 3:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "dark_effect_on",
'payload': {
'duration': instruction[1]
}
})
if instruction[2] == 0:
time += instruction[1]
elif instruction[0] == 40: # 暗效果关闭
# 15 0 -> 第一个应该是变换时间,效果为整个场景恢复原样
if len(instruction) < 3:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "dark_effect_off",
'payload': {
'duration': instruction[1]
}
})
if instruction[2] == 0:
time += instruction[1]
elif instruction[0] == 41: # 静态画面删除
# todo
if len(instruction) < 2:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
print(f"指令 {i}:{instruction}({explain}) 未处理,跳过")
pass
elif instruction[0] == 42: # 静态画面显示注册
# 9000 9000 2 1 -》adventure/advstill/advstill9000/still9000_scene02.assetbundle
if len(instruction) < 5:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
ele = {
'time': time,
'type': "register_static_scene",
'payload': {
'id': instruction[1],
'stillId': instruction[2],
'sceneId': instruction[3],
'param4': instruction[4]
}
}
processes.append(ele)
show_stack.append(ele)
elif instruction[0] == 43: # 静态画面退出
if len(instruction) < 4:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
ele = {
'time': time,
'type': "delete_static_scene",
'payload': {
'id': instruction[1],
'param2': instruction[2],
'param3': instruction[3],
}
}
processes.append(ele)
pass
elif instruction[0] == 47: # 角色退出并删除
# 9003 4 0 -》id 中间可能是时间 第三个参数表示是否异步(1的时候不会影响time)
if len(instruction) < 4:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "exit_and_delete_character",
'payload': {
'id': instruction[1],
'duration': instruction[2]
}
})
if instruction[3] == 0:
time += instruction[2]
elif instruction[0] == 48: # 角色相对移动
# 9003 0 -10 10 0 向下移动一点然后回原位 第二三个参数我位移距离和方向(x和y轴) 第四个参数代表时间 推测最后一个惨表示是否回原位
# 9003 0 -20 8 1 向下移动一点然后不回原位
if len(instruction) < 6:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "relative_move_character",
'payload': {
'id': instruction[1],
'dx': instruction[2],
'dy': instruction[3],
'duration': instruction[4]
}
})
if instruction[5] == 0:
time += instruction[4]
elif instruction[0] == 49: # 读取消息窗口
if len(instruction) < 7:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "show_message",
'payload':{
'name': textIndex,
'content': textIndex,
'speed': instruction[3],
'param4': instruction[4],
'voiceId': instruction[5],
},
'blocking': True
})
text.append([instruction[1], instruction[2]])
textIndex+=1
if instruction[6] == 0:
time += instruction[4]
elif instruction[0] == 56: # 播放动作影片
if len(instruction) < 4:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "play_movie",
'payload': {
'characterId': instruction[1],
'sceneId': instruction[2]
}
})
elif instruction[0] == 58: # 切换消息窗口
# 0 -》0隐藏 1显示
if len(instruction) < 2:
print(f"指令 {i} ({explain}) 参数不足,跳过")
continue
processes.append({
'time': time,
'type': "toggle_message_window",
'payload': {
'show': instruction[1] == 1
}
})
# todo
else:
print(f"指令 {i}:{instruction}({explain}) 未处理,跳过")
result = {
'files':files,
'processes':processes,
'text':text
}
return result
前30条好像是系统自带的,30后的是自定义,所以才减去30。实际脚本中用的好像都是30后的指令。
涉及到分支的地方用到了我增加的那些指令,还好有你这个解析脚本,不然不知道到怎么从evsc文件获取播放信息
我看了下,我写逻辑的时候没有注意这么多,省略了镜头缩放、角色移动什么的(主要是没对比不清楚),就保证了基础的显示播放啥的。
大佬太厉害了,我都难以想象怎么从这二进制文件搞出来这个解析脚本的,我也是用你这个脚本对照着游戏里的播放效果一个个猜的参数,还好有你的INSTRUCTION_EXPLANATIONS,不然光个值猜也没法猜了
老哥你好,请问您的这个解析脚本效果怎么样,可以分享一下吗?
基本上就是上边发的那些,很多参数也没猜出来
应该就是了
我用前端写的播放器大概逻辑是这样:
export class FramePlayer {
public events: ScenePlayProcess[] = []
public text: [string,string][] = []
public currentState:{
character: [{
id: any;
},{},{}]
bg:number,
camera:{
x:number,
y:number,
scale:number
},
se: [],
bgm: number,
dialog:{
name:string,
content:string,
voicePath:string,
}
} =
{
character: [{
},{},{}],
bg:0,
camera:{
x:0,
y:0,
scale:1
},
se:[],
bgm: 0,
dialog: {
name: "",
content: "",
voicePath: ""
}
}
public end:boolean = false
public canPlay:boolean = false
public stack_template = []
public stack = [0,0,0,0,0,0,0,0,0,0,0,0,0,0]
public seInfo = {}
public lastNow = 0
public voiceInfo = {}
public bgmInfo = {}
public urls: string[] = []
public fps: number = 30
public speedUpMultiplier: number = 2
public frameTime: number = 1000 / this.fps
public speedUpFrameTime: number = 1000 / this.fps / this.speedUpMultiplier
public commonFrameTime: number = 1000 / this.fps
public playing = false
public skip = false
public speedUp = false
public auto = false
public log: [string,string][] = []
public state:[] = [] // 存储每次阻塞时的静态状态用于跳转,比较复杂
public elements: PlayerElements = {}
public elementsState: AnimationStateObj = {}
public cursor = 0 // 事件指针
public logicTime = 0 // 逻辑时间
public raf = 0
public breakpoint = ref<number|null>(null)
constructor(processes: Processes, focus:boolean = false) {
try{
if(!processes){
ElMessage.error("没有传入合理处理数据");
throw new Error("没有传入合理处理数据");
}
let files = processes.files
files.forEach((file)=>{
if(file[0] == 0){
this.urls.push(AyarabuManagement.ABUrlFormatter.charaStandFull.format(file[1]))
this.urls.push(AyarabuManagement.ABUrlFormatter.charaAdvIllu.format(file[1]))
}
else if (file[0] == 1) {
this.urls.push(AyarabuManagement.ABUrlFormatter.advBG.format(file[1]))
}
else if (file[0] == 2) {
let characterId = file[1]
let sceneId = file[2]
this.urls.push(AyarabuManagement.ABUrlFormatter.charaSceneMovie.format(characterId,sceneId))
}
else if (file[0] == 3) {
this.urls.push(AyarabuManagement.ABUrlFormatter.charaAdvStill.format(file[1], file[2]))
}
else if (file[0] == 4) {
// {
// "soundSeId": 100004,
// "formatId": 100001,
// "argument1": 4,
// "argument2": 0,
// "loop": false
// }
let soundSeMasterData = AyarabuManagement.getMockDataById(AyarabuManagement.MockFileName.SoundSeMasterDatas,"soundSeId",file[1])
if(soundSeMasterData){
// {
// "seFormatId": 100001,
// "directoryPath": "adv",
// "assetBundleName": "sescenario_{1:D4}",
// "assetDataName": "SeScenario_{1:D4}"
// }
let soundSeFormatMasterData = AyarabuManagement.getMockDataById(AyarabuManagement.MockFileName.SoundSeFormatMasterDatas,"seFormatId",soundSeMasterData.formatId)
if(soundSeFormatMasterData["assetBundleName"]){
this.urls.push(`sound/se/${soundSeFormatMasterData["directoryPath"]}/${soundSeFormatMasterData["assetBundleName"].format(
soundSeMasterData.argument1, soundSeMasterData.argument2,
)}.assetbundle`)
}
this.seInfo[file[1]] = {
path:`${AyarabuManagement.resourceSoundPath}/se/${soundSeFormatMasterData["directoryPath"]}/${soundSeFormatMasterData["assetDataName"].format(
soundSeMasterData.argument1, soundSeMasterData.argument2,
)}.m4a`,
loop: soundSeMasterData.loop
}
}else {
this.seInfo[file[1]] = {
path:``,
loop: false
}
}
}
else if (file[0] == 5) {
// {
// "soundVoiceId": 9100022,
// "formatId": 9100001,
// "argument1": 0,
// "argument2": 0
// }
let soundVoiceMasterData = AyarabuManagement.getMockJsonData(AyarabuManagement.MockFileName.SoundVoiceMasterDatas, 1)[file[1]]
if(!soundVoiceMasterData){
this.voiceInfo[file[1]] = ``
}else {
// {
// "voiceFormatId": 9100001,
// "directoryPath": "adv/asuka/common",
// "assetBundleName": "vocommon_asuka_{1:D2}",
// "assetDataName": "VoCommon_Asuka_{1:D2}"
// }
let soundVoiceFormatMasterData = AyarabuManagement.getMockJsonData(AyarabuManagement.MockFileName.SoundVoiceFormatMasterDatas, 1)[soundVoiceMasterData.formatId]
let directoryPath = soundVoiceFormatMasterData["directoryPath"].format(soundVoiceMasterData.argument1, soundVoiceMasterData.argument2)
let assetBundleName = soundVoiceFormatMasterData["assetBundleName"].format(soundVoiceMasterData.argument1, soundVoiceMasterData.argument2)
let assetDataName = soundVoiceFormatMasterData["assetDataName"].format(soundVoiceMasterData.argument1, soundVoiceMasterData.argument2)
if(soundVoiceFormatMasterData["assetBundleName"]){
this.urls.push(`sound/voice/${directoryPath}/${assetBundleName}.assetbundle`)
}
this.voiceInfo[file[1]] = `${AyarabuManagement.resourceSoundPath}/voice/${directoryPath}/${assetDataName}.m4a`
}
}
else if (file[0] == 6) {
this.urls.push(`ss/adv/adveffect{1:D4}.assetbundle`.format(file[1]))
}
else if (file[0] == 7) {
// {
// "soundBgmId": 10002,
// "formatId": 10001,
// "argument1": 2,
// "argument2": 0,
// "loop": true
// }
let soundBgmMasterData = AyarabuManagement.getMockDataById(AyarabuManagement.MockFileName.SoundBgmMasterDatas,"soundBgmId",file[1])
// {
// "bgmFormatId": 10001,
// "directoryPath": "ui",
// "assetBundleName": "bgmpage_{1:D4}",
// "assetDataName": "BgmPage_{1:D4}"
// }
if(!soundBgmMasterData){
soundBgmMasterData = AyarabuManagement.getMockDataById(AyarabuManagement.MockFileName.SoundBgmMasterDatas,"soundBgmId",10001)
}
let soundBgmFormatMasterData = AyarabuManagement.getMockDataById(AyarabuManagement.MockFileName.SoundBgmFormatMasterDatas,"bgmFormatId",soundBgmMasterData.formatId)
if(soundBgmFormatMasterData["assetBundleName"]){
this.urls.push(`sound/bgm/${soundBgmFormatMasterData["directoryPath"]}/${soundBgmFormatMasterData["assetBundleName"].format(
soundBgmMasterData.argument1, soundBgmMasterData.argument2,
)}.assetbundle`)
}
this.bgmInfo[file[1]] = `${AyarabuManagement.resourceSoundPath}/bgm/${soundBgmFormatMasterData["directoryPath"]}/${soundBgmFormatMasterData["assetDataName"].format(
soundBgmMasterData.argument1, soundBgmMasterData.argument2,
)}.m4a`
}
else {
console.log("无法解析");
}
})
this.urls = [...new Set(this.urls)]
AyarabuManagement.downloadFiles(this.urls, null, true, focus, ()=>{
this.canPlay = true
})
// AyarabuManagement.downloadFile("adventure/advcharacter/advcharacter9001.assetbundle",true)
// AyarabuManagement.downloadFile("ui/standing_chara/full/full_chara9001.assetbundle",true)
this.events = processes.processes // 第二步的预处理
this.text = processes.text
this.text.forEach(text=>{
text[0] = text[0].replaceAll("$n","\n").replaceAll("<name></name>",useGlobalStore().userName)
text[1] = text[1].replaceAll("$n","\n").replaceAll("<name></name>",useGlobalStore().userName).replace(/<[^>]*name[^>]*>/g, useGlobalStore().userName);
})
useGlobalStore().eventPlayer = this
} catch (error){
console.log(error.message);
console.log(error.stack);
}
}
play() {
this.playing = true
if(this.raf){
cancelAnimationFrame(this.raf)
this.raf = 0
}
this.lastNow = performance.now() / this.commonFrameTime
useGlobalStore().components.textDialog.nextTextShow = false
this.raf = requestAnimationFrame(this.tick)
}
playBranch(branchId:number) {
// if(useGlobalStore().components.textDialog.showChoices){
// let validateChoiceCount = useGlobalStore().components.textDialog.branchText.filter(e=>e).length;
// if(validateChoiceCount > 1){
// useGlobalStore().components.textDialog.showChoices = false
// this.play()
// }else {
// this.stack_template.push(branchId)
// }
// }
this.stack_template.push(branchId)
useGlobalStore().components.textDialog.showChoices = false
this.play()
}
pause() {
this.playing = false
cancelAnimationFrame(this.raf)
}
skipToNextBreakpoint() {
const next = this.events.findIndex(
(ev, i) => i > this.cursor && ev.blocking
)
this.jump(next === -1 ? this.events.length : next)
}
jump(cursor: number) {
this.cursor = cursor
this.logicTime = this.events[cursor]?.time ?? Infinity
// 全局 rollback + apply 到 logicTime
// rollbackToTime(this.logicTime)
this.play()
}
private applyEvent(ev, index = 0){
this.log.push([ev.type, JSON.stringify(ev.payload)])
let duration = 0
let path = ""
let position = 0
let color = "black"
let seId = -1
let stillId = -1
let sceneId = -1
let bgId
let characterId
let to
let stack_position
let value
let text
switch (ev.type) {
case "write_stack": // 写入栈
stack_position = ev.payload.stack_position
if(this.stack_template.length > 0){
this.stack[this.stack.length + stack_position] = this.stack_template.pop()
}
break;
case "jump": // 直接跳转
to = ev.payload.to
this.cursor = to
this.logicTime = this.events[this.cursor].time
break;
case "compare_jump": // 比较跳转
stack_position = ev.payload.stack_position
value = ev.payload.value
to = ev.payload.to
if(this.stack[this.stack.length + stack_position] == value){
this.cursor = to
this.logicTime = this.events[this.cursor].time
}
break;
case "clear_stack": // 清空栈
for (let i = 0; i < this.stack.length; i++) {
this.stack[i] = 0;
}
break;
case "show_message": // 读取消息
let text = this.text[ev.payload.name]
useGlobalStore().components.textDialog.speaker = text[0]
useGlobalStore().components.textDialog.showContent(text[1], this.speedUp, ev.payload.speed*this.frameTime)
this.currentState.dialog.name = text[0]
this.currentState.dialog.content = text[1]
if(ev.payload.voiceId > 0){
let voicePath = AyarabuManagement.getIosnotOrR18PathIfNotExist(this.voiceInfo[ev.payload.voiceId])
useGlobalStore().components.textDialog.voicePath = voicePath
this.currentState.dialog.voicePath = voicePath
}
useGlobalStore().components.textDialog.dialogRefShow = true
break;
case 2: // 等待工作
break;
case 3: // 打开章节
break;
case "close_chapter": // 关闭章节
ElMessage.success("章节已结束")
this.end = true
break;
case "delete_character": // 角色删除
position = this.currentState.character.findIndex(c=> c.id == ev.payload.id)
if(position > -1){
useGlobalStore().components.eventPlay[`characterPath${position}`] = ""
this.currentState.character[position]["id"] = 0
this.currentState.character[position]["characterId"] = 0
this.currentState.character[position]["facialId"] = 0
this.currentState.character[position]["path"] = ''
this.currentState.character[position]["opacity"] = 0
}
break; // 无需进行具体的操作
case "show_character": // 角色显示注册
path = `${AyarabuManagement.resourceAdvcharacterPath}/advcharacter{1:D4}/advcharacter{1:D4}facial{2:D4}.png`.format(
ev.payload.characterId, ev.payload.facialId
)
let path2 = AyarabuManagement.getIosnotOrR18PathIfNotExist(path)
if(path2 == null){
path = `${AyarabuManagement.resourceAdvcharacterPath}/advcharacter{1:D4}/advcharacter{1:D4}.png`.format(ev.payload.characterId)
path = AyarabuManagement.getIosnotOrR18PathIfNotExist(path)
} else {
path = path2
}
this.currentState.character[ev.payload.position]["id"] = ev.payload.id
this.currentState.character[ev.payload.position]["characterId"] = ev.payload.characterId
this.currentState.character[ev.payload.position]["facialId"] = ev.payload.facialId
this.currentState.character[ev.payload.position]["path"] = path
this.currentState.character[ev.payload.position]["opacity"] = 0
useGlobalStore().components.eventPlay.characterBrightness[ev.payload.position] = 1
useGlobalStore().components.eventPlay[`characterPath${ev.payload.position}`] = path
nextTick(()=>{
if((this.currentState.camera.y !=0 || this.currentState.camera.x !=0) && ev.payload.position == 1){
this.elements[`character${ev.payload.position}`].value.playAnimation([animationUtils.animationNames.opacity],[
[0,1]
],ev.payload.duration*this.frameTime/1000, 0)
}else {
this.elements[`character${ev.payload.position}`].value.playAnimation([animationUtils.animationNames.opacity,animationUtils.animationNames.x,animationUtils.animationNames.y],[
[0,1],
[0],
[0]
],ev.payload.duration*this.frameTime/1000, 0)
}
})
break;
case "character_action_jump": // 角色动作跳转
position = this.currentState.character.findIndex(c=> c.id == ev.payload.id)
duration = ev.payload.duration ?? 0
if(position == -1){
if(this.currentState.character[1]["id"] != 0){
position = 1
}else {
break
}
}
this.elements[`character${position}`].value.playAnimation([animationUtils.animationNames.y],[
[0,ev.payload.dy,0],
],ev.payload.duration*this.frameTime/1000, 0)
break;
case "move_character": // 角色移动
console.log(this.logicTime, ev);
position = this.currentState.character.findIndex(c=> c.id == ev.payload.id)
if(position > -1){
let dx = ev.payload.dx ?? 0
let dy = ev.payload.dy ?? 0
duration = ev.payload.duration ?? 0
this.elements[`character${position}`].value.playAnimation([animationUtils.animationNames.x,animationUtils.animationNames.y],[
[0,`calc(${30 - position * 30}% - ${dx}px)`],
[0,-dy],
],duration*this.frameTime/1000, 0, true)
}
break;
case "relative_move_character": // 角色相对移动
console.log(this.logicTime, ev);
position = this.currentState.character.findIndex(c=> c.id == ev.payload.id)
if(position > -1){
let dx = ev.payload.dx ?? 0
let dy = ev.payload.dy ?? 0
duration = ev.payload.duration ?? 0
this.elements[`character${position}`].value.playAnimation([animationUtils.animationNames.x,animationUtils.animationNames.y],[
[0,dx],
[0,-dy],
],duration*this.frameTime/1000, 0, false, true)
}
break;
case "exit_character": // 角色退出
position = this.currentState.character.findIndex(c=> c.id == ev.payload.id)
if(position > -1){
duration = ev.payload.duration ?? 0
this.elements[`character${position}`].value.playAnimation([animationUtils.animationNames.opacity],[
[1,0],
],duration*this.frameTime/1000, 0)
this.currentState.character[position]["id"] = 0
this.currentState.character[position]["characterId"] = 0
this.currentState.character[position]["facialId"] = 0
this.currentState.character[position]["path"] = ''
this.currentState.character[position]["opacity"] = 0
}
break;
case "exit_and_delete_character": // 角色退出并删除
if(ev.payload.id == 0){
for(position in this.currentState.character){
useGlobalStore().components.eventPlay[`characterPath${position}`] = ""
this.currentState.character[position]["id"] = 0
this.currentState.character[position]["characterId"] = 0
this.currentState.character[position]["facialId"] = 0
this.currentState.character[position]["path"] = ''
this.currentState.character[position]["opacity"] = 0
}
}else {
position = this.currentState.character.findIndex(c=> c.id == ev.payload.id)
if(position > -1){
duration = ev.payload.duration ?? 0
this.elements[`character${position}`].value.playAnimation([animationUtils.animationNames.opacity],[
[1,0],
],duration*this.frameTime/1000, 0)
useGlobalStore().components.eventPlay[`characterPath${position}`] = ""
this.currentState.character[position]["id"] = 0
this.currentState.character[position]["characterId"] = 0
this.currentState.character[position]["facialId"] = 0
this.currentState.character[position]["path"] = ''
this.currentState.character[position]["opacity"] = 0
}
}
break;
case "slide_character": // 角色滑动
position = this.currentState.character.findIndex(c=> c.id == ev.payload.id)
if(position > -1){
let duration = ev.payload.duration ?? 0
let dx = ev.payload.distance ?? 0
let times = ev.payload.times ?? 0
this.elements[`character${position}`].value.playAnimation([animationUtils.animationNames.x],[
[0,dx,0,-dx,0],
],duration*this.frameTime/1000, times - 1)
}
break;
case "fade_in": // 淡入
color = ev.payload.color ?? 'white'
duration = ev.payload.duration ?? 0
if(color in colors){
this.elements.fade.value.playAnimation([animationUtils.animationNames.background, animationUtils.animationNames.opacity], [
[`radial-gradient(circle, ${colors[color].toRgba(1)}, ${colors[color].toRgba(1)})`, `radial-gradient(circle, ${colors[color].toRgba(0)}, ${colors[color].toRgba(1)})`],
[1, 0]
], duration * this.frameTime / 1000, 0)
}
break;
case "fade_out": // 淡出
color = ev.payload.color ?? 'white'
duration = ev.payload.duration ?? 0
if(color in colors){
this.elements.fade.value.playAnimation([animationUtils.animationNames.background, animationUtils.animationNames.opacity], [
[`radial-gradient(circle, ${colors[color].toRgba(0)}, ${colors[color].toRgba(1)})`, `radial-gradient(circle, ${colors[color].toRgba(1)}, ${colors[color].toRgba(1)})`],
[0, 1]
], duration * this.frameTime / 1000, 0)
}
break;
case "shake": // 震动效果
if(ev.payload.target == "dialog"){
let distance = ev.payload.distance
let times = ev.payload.times
duration = ev.payload.duration ?? 0
useGlobalStore().components.textDialog.dialogRef.playAnimation([ev.payload.direction],[
[0,distance/2,0,-distance/2,0],
],duration*this.frameTime/1000, times-1)
}else {
let distance = ev.payload.distance
let times = ev.payload.times
duration = ev.payload.duration ?? 0
this.elements.bg.value.playAnimation([ev.payload.direction], [
[0, distance / 2, 0, -distance / 2, 0]
], duration * this.frameTime / 1000, times - 1)
}
break;
case "reset_camera": // 重置摄像机
duration = ev.payload.duration ?? 0
this.elements.bg.value.playAnimation([animationUtils.animationNames.x, animationUtils.animationNames.y, animationUtils.animationNames.scale], [
[this.currentState.camera.x, 0],
[this.currentState.camera.y, 0],
[this.currentState.camera.scale, 1]
], duration * this.frameTime / 1000, 0)
this.elements.character1?.value.playAnimation([animationUtils.animationNames.x, animationUtils.animationNames.y, animationUtils.animationNames.scale], [
[0, 0],
[0, 0],
[0, 1]
], duration * this.frameTime / 1000, 0, true)
this.currentState.camera.x = 0
this.currentState.camera.y = 0
this.currentState.camera.scale = 1
break;
case "zoom_camera": // 摄像机缩放
duration = ev.payload.duration ?? 0
let dx = ev.payload.dx ?? 0;
let dy = ev.payload.dy ?? 0;
let scale = ev.payload.scale ?? 1
this.elements.bg.value.playAnimation([animationUtils.animationNames.x, animationUtils.animationNames.y, animationUtils.animationNames.scale], [
[this.currentState.camera.x, -dx],
[this.currentState.camera.y, dy],
[this.currentState.camera.scale, scale]
], duration * this.frameTime / 1000, 0)
this.elements.character1?.value.playAnimation([animationUtils.animationNames.x, animationUtils.animationNames.y, animationUtils.animationNames.scale], [
[this.currentState.camera.x, -dx],
[this.currentState.camera.y, this.elements.character1?.value.getElement().clientHeight/385.8*dy],
[this.currentState.camera.scale, scale]
], duration * this.frameTime / 1000, 0)
this.currentState.camera.x = -dx
this.currentState.camera.y = dy
this.currentState.camera.scale = scale
break;
case "stop_se": // 停止音效(SE)
seId = ev.payload.id
if(this.currentState.se.includes(seId)){
useGlobalStore().components.eventPlay.sePath = ""
this.currentState.se = this.currentState.se.filter(id => id !== seId)
}
break;
case "play_se": // 播放音效(SE)
seId = ev.payload.id
duration = ev.payload.duration ?? 0
if(seId in this.seInfo){
useGlobalStore().components.eventPlay.playSeFadeIn(
this.seInfo[seId].path,
duration * this.frameTime / 1000,this.seInfo[seId].loop
)
this.currentState.se.push(seId)
}
break;
case "stop_bgm": // 停止背景音乐(BGM)
duration = ev.payload.duration ?? 0
if(this.currentState.bgm){
useGlobalStore().components.eventPlay.playBgmFadeOut(
duration * this.frameTime / 1000
)
this.currentState.bgm = 0
}
break;
case "play_bgm": // 播放背景音乐(BGM)
let bgmId = ev.payload.id;
duration = ev.payload.duration ?? 0
if(bgmId in this.bgmInfo){
useGlobalStore().components.eventPlay.playBgmFadeIn(
this.bgmInfo[bgmId],duration * this.frameTime / 1000
)
this.currentState.bgm = bgmId
}
break;
case "show_background": // 背景显示注册
bgId = ev.payload.id;
duration = ev.payload.duration ?? 0
if(useGlobalStore().components.eventPlay.previewBackgroundPath != useGlobalStore().components.eventPlay.backgroundPath){
useGlobalStore().components.eventPlay.previewBackgroundPath = useGlobalStore().components.eventPlay.backgroundPath
}
if(duration > 0){
nextTick(()=>{
useGlobalStore().components.eventPlay.backgroundPath = AyarabuManagement.LocalResourcePathFormatter.advBG.format(bgId)
this.elements.bg.value.playAnimation([animationUtils.animationNames.opacity, animationUtils.animationNames.x, animationUtils.animationNames.y], [[0, 1], [0], [0]], duration * this.frameTime / 1000, 0)
})
}else {
useGlobalStore().components.eventPlay.backgroundPath = AyarabuManagement.LocalResourcePathFormatter.advBG.format(bgId)
this.elements.bg.value.playAnimation([animationUtils.animationNames.opacity, animationUtils.animationNames.x, animationUtils.animationNames.y], [[0, 1], [0], [0]], duration * this.frameTime / 1000, 0)
}
this.currentState.bg = bgId
break;
case "branch_show": // 分支显示
useGlobalStore().components.textDialog.branchText = [this.text[ev.payload.option1][0],this.text[ev.payload.option1][1]]
useGlobalStore().components.textDialog.showChoices = true
break;
case 26: // 分支结果
break;
case "toggle_message_window": // 消息窗口显示切换
useGlobalStore().components.textDialog.dialogRefShow = ev.payload.show
break;
case "play_movie": // 播放影片
characterId = ev.payload.characterId
sceneId = ev.payload.sceneId
path = AyarabuManagement.getIosnotOrR18PathIfNotExist(AyarabuManagement.LocalResourcePathFormatter.charaSceneMovie.format(characterId,sceneId))
useGlobalStore().components.eventPlay.moviePath = path
break;
case "character_brightness": // 对话亮度调整
useGlobalStore().components.eventPlay.characterBrightness = [ev.payload.left, ev.payload.middle, ev.payload.right]
break;
case "fade_in_horizon": // 颜色淡入(H)
color = ev.payload.color ?? 'white'
duration = ev.payload.duration ?? 0
if(color in colors){
this.elements.fade.value.playAnimation([animationUtils.animationNames.background], [
[
`linear-gradient(to right, ${colors.black.toRgba(1)},0%, ${colors.black.toRgba(1)}`,
`linear-gradient(to right, ${colors.black.toRgba(0)},100%, ${colors.black.toRgba(1)}`
]
], duration * this.frameTime / 1000, 0)
}
break;
case "fade_out_horizon": // 颜色淡出(H)
color = ev.payload.color ?? 'white'
duration = ev.payload.duration ?? 0
if(color in colors){
this.elements.fade.value.playAnimation([animationUtils.animationNames.background], [
[
`linear-gradient(to right, ${colors.black.toRgba(0)} 0%, ${colors.black.toRgba(0)}`,
`linear-gradient(to right, ${colors.black.toRgba(1)} 100%, ${colors.black.toRgba(0)}`
]
], duration * this.frameTime / 1000, 0)
}
break;
case "stop_movie": // 停止影片
useGlobalStore().components.eventPlay.moviePath = ""
useGlobalStore().components.eventPlay.previewMoviePath = ""
break;
case "set_alpha_mask": // 设置阿尔法遮罩
position = this.currentState.character.findIndex(c=> c.id == ev.payload.characterId)
if (position > -1){
this.elements[`character${position}`].value.playAnimation([animationUtils.animationNames.mask],[
[
[`radial-gradient(circle at center, black,80%, transparent)`],
],
],0, 0)
}
break;
case "black_frame_on": // 黑框开启
// color = ev.payload.color ?? 'white'
duration = ev.payload.duration ?? 0
this.elements.frame.value.playAnimation(["backgroundImage",animationUtils.animationNames.opacity], [
["",`url(${AyarabuManagement.resourceAdventureCommonPath.replaceAll("\\","/")}/black_frame.png)`],
[0, 1]
], duration * this.frameTime / 1000, 0)
break;
case "black_frame_off": // 黑框关闭
// color = ev.payload.color ?? 'white'
duration = ev.payload.duration ?? 0
this.elements.frame.value.playAnimation(["backgroundImage",animationUtils.animationNames.opacity], [
[`url(${AyarabuManagement.resourceAdventureCommonPath.replace("\\","/")}/black_frame.png)`,""],
[1, 0]
], duration * this.frameTime / 1000, 0)
break;
case "dark_effect_on": // 暗效果开
// color = ev.payload.color ?? 'white'
duration = ev.payload.duration ?? 0
this.elements.fade.value.playAnimation([animationUtils.animationNames.background, animationUtils.animationNames.opacity], [
[`radial-gradient(circle, ${colors[color].toRgba(0)}, ${colors[color].toRgba(1)})`, `radial-gradient(circle, ${colors[color].toRgba(0.5)}, ${colors[color].toRgba(0.8)})`],
[0, 1]
], duration * this.frameTime / 1000, 0)
break;
case "dark_effect_off": // 暗效果关
// color = ev.payload.color ?? 'white'
duration = ev.payload.duration ?? 0
this.elements.fade.value.playAnimation([animationUtils.animationNames.background, animationUtils.animationNames.opacity], [
[`radial-gradient(circle, ${colors[color].toRgba(0.5)}, ${colors[color].toRgba(0.8)})`, `radial-gradient(circle, ${colors[color].toRgba(0)}, ${colors[color].toRgba(1)})`],
[1, 0]
], duration * this.frameTime / 1000, 0)
break;
case "delete_static_scene": // 静态画面显示删除
nextTick(()=>{
if(useGlobalStore().components.eventPlay.moviePath == ""){
useGlobalStore().components.eventPlay.showFullBackground = false
}
})
break;
case "register_static_scene": // 静态画面显示注册
bgId = ev.payload.id;
stillId = ev.payload.stillId ?? 0
sceneId = ev.payload.sceneId ?? 0
duration = ev.payload.duration ?? 0
useGlobalStore().components.eventPlay.showFullBackground = true
if(useGlobalStore().components.eventPlay.previewBackgroundPath != useGlobalStore().components.eventPlay.backgroundPath){
useGlobalStore().components.eventPlay.previewBackgroundPath = useGlobalStore().components.eventPlay.backgroundPath
}
path = AyarabuManagement.LocalResourcePathFormatter.charaAdvStill.format(stillId,sceneId)
if(!fs.existsSync(path)){
path = AyarabuManagement.LocalResourcePathFormatter.charaR18AdvStill.format(stillId,sceneId)
}
if(duration > 0){
nextTick(()=>{
useGlobalStore().components.eventPlay.backgroundPath = path
this.elements.bg.value.playAnimation([animationUtils.animationNames.opacity, animationUtils.animationNames.x, animationUtils.animationNames.y], [[0, 1], [0], [0]], duration * this.frameTime / 1000, 0)
})
}else {
useGlobalStore().components.eventPlay.backgroundPath = path
this.elements.bg.value.playAnimation([animationUtils.animationNames.opacity, animationUtils.animationNames.x, animationUtils.animationNames.y], [[0, 1], [0], [0]], duration * this.frameTime / 1000, 0)
}
this.currentState.bg = bgId
break;
default:
console.warn(`Unknown event type: ${ev.type}`);
}
}
private tick = () => {
if (!this.playing) return
const now = performance.now() / this.commonFrameTime
const dt = now - (this.lastNow ?? now)
this.lastNow = now
this.logicTime += this.speedUp ? (dt * this.speedUpMultiplier) : dt
while (this.cursor < this.events.length && this.events[this.cursor].time <= this.logicTime) {
const ev = this.events[this.cursor++]
this.applyEvent(ev) // 各轨道 reducer
if (ev.blocking) {
if(!this.speedUp || ev.type == "branch_show"){
this.pause()
}
break
}else {
}
}
if (this.cursor >= this.events.length) {
this.pause()
} else {
this.raf = requestAnimationFrame(this.tick)
}
}
}
和之前发的解析脚本结合起来然后前端相关元素创建好,赋值进行播放就行,因为第一次做所以代码写得很烂,我倒是想写一个通用的播放器,但是不同游戏各有特点,感觉挺难的
43樓提到前30條是系統指令,確實有個AdvEVSCSystemInstructionNo的Enum,最大值剛好是30。超過30好像會變成AdvUserInstructionNo。
如果EVSC Command Type是Instruction,Param Count是指要往下讀取多少個Command(參數)。
跟據Param Count的數值,猜測可能和AdvCommandExecution這個Class裡面的Method有關,但有些Instruction Param Count的值和Method Arguments數量對不起來(基本上會多或會少1個)。
可能因为有的指令是由几个方法共同完成的,所以会对不上,具体的可以看看AdvEventScriptCtrl。不过挺久没看我也忘差不多了。