编辑代码

import fs from 'fs'
import path from 'path'

// 源文件夹
let filePath = "D:\\新建文件夹\\chromeDnowload\\UnityCNLive2DExtractor\\新建文件夹\\assets\\res\\character"
// 输出文件夹
let outPath = "D:\\新建文件夹\\chromeDnowload\\UnityCNLive2DExtractor\\新建文件夹\\assets\\res\\新建文件夹"
// 音频文件夹
let wavPath = "D:\\新建文件夹\\qq\\MobileFile\\Voice\\Voice\\新建文件夹"

// 提取角色名的正则
const characterNameRexg = /\\character\\([-\w]+)\\l2d/

// 鼠标控制和登录动画控制
const loginAndMouse = [
    {
        VarFloats: [
            {
                "Name": "login",
                "Type": 1,
                "Code": "lower 1"
            },
            {
                "Name": "mouse",
                "Type": 1,
                "Code": "lower 1"
            }
        ],
        Command: "mouse_tracking enable"
    },
    {
        VarFloats: [
            {
                "Name": "login",
                "Type": 1,
                "Code": "lower 1"
            },
            {
                "Name": "mouse",
                "Type": 1,
                "Code": "equal 1"
            }
        ],
        Command: "mouse_tracking disable"
    },
    {
        VarFloats: [
            {
                "Name": "login",
                "Type": 1,
                "Code": "equal 1"
            },
            {
                "Name": "mouse",
                "Type": 1,
                "Code": "lower 1"
            }
        ],
        Command: "mouse_tracking enable",
        File: ""
    },
    {
        VarFloats: [
            {
                "Name": "login",
                "Type": 1,
                "Code": "equal 1"
            },
            {
                "Name": "mouse",
                "Type": 1,
                "Code": "equal 1"
            }
        ],
        Command: "mouse_tracking disable",
        File: ""
    }
]
let motionToWav = {
    "login_morning": {
        Rexg: /MORNING/,
        TimeLimit: {
            "Hour": 5,
            "Minute": 0,
            "Sustain": 420
        },
    },
    "login_afternoon": {
        Rexg: /AFTERNOON/,
        "TimeLimit": {
            "Hour": 12,
            "Minute": 0,
            "Sustain": 420
        },
    },
    "login_evening": {
        Rexg: /EVENING/,
        "TimeLimit": {
            "Hour": 19,
            "Minute": 0,
            "Sustain": 300
        },
    },
    "login_night": {
        Rexg: /NIGHT/,
        "TimeLimit": {
            "Hour": 0,
            "Minute": 0,
            "Sustain": 300
        },
    },
    "login_morning": {
        Rexg: /MORNING/,
        TimeLimit: {
            "Hour": 5,
            "Minute": 0,
            "Sustain": 420
        },
    },
    "login_morning": {
        Rexg: /MORNING/,
        TimeLimit: {
            "Hour": 5,
            "Minute": 0,
            "Sustain": 420
        },
    },
    "login_1": {
        Rexg: /MORNING/,
        TimeLimit: {
            "Hour": 5,
            "Minute": 0,
            "Sustain": 420
        },
    },
    "login_2": {
        Rexg: /AFTERNOON/,
        "TimeLimit": {
            "Hour": 12,
            "Minute": 0,
            "Sustain": 420
        },
    },
    "login_3": {
        Rexg: /EVENING/,
        "TimeLimit": {
            "Hour": 19,
            "Minute": 0,
            "Sustain": 300
        },
    },
    "login_4": {
        Rexg: /NIGHT/,
        "TimeLimit": {
            "Hour": 0,
            "Minute": 0,
            "Sustain": 300
        },
    }
}

const HitAreas = [
]
let motionMenu = [
    {
        "Name": "动作欣赏",
        "Text": "动作欣赏",
        "Choices": [
        ],
        "TextDuration": 5000
    }
]

let controlloer = [
    {
        "Name": "控制面板",
        "Text": "控制面板",
        "Choices": [
            {
                "Text": "打开登录动画",
                "NextMtn": "控制:打开登录动画"
            },
            {
                "Text": "关闭登录动画",
                "NextMtn": "控制:关闭登录动画"
            },
            {
                "Text": "打开鼠标跟随",
                "NextMtn": "控制:打开鼠标跟随"
            },
            {
                "Text": "关闭鼠标跟随",
                "NextMtn": "控制:关闭鼠标跟随"
            },
            {
                "Text": "动作欣赏",
                "NextMtn": "动作欣赏:动作欣赏"
            }
        ],
        "TextDuration": 5000
    }
]

let loginAndMouseMenu = [
    {
        "Name": "打开登录动画",
        "VarFloats": [
            {
                "Name": "login",
                "Type": 2,
                "Code": "assign 0"
            }
        ]
    },
    {
        "Name": "关闭登录动画",
        "VarFloats": [
            {
                "Name": "login",
                "Type": 2,
                "Code": "assign 1"
            }
        ]
    }, 
    {
        "Name": "打开鼠标跟随",
        "Command": "mouse_tracking enable",
        "VarFloats": [
            {
                "Name": "mouse",
                "Type": 2,
                "Code": "assign 0"
            }
        ]
    },
    {
        "Name": "关闭鼠标跟随",
        "Command": "mouse_tracking disable",
        "VarFloats": [
            {
                "Name": "mouse",
                "Type": 2,
                "Code": "assign 1"
            }
        ]
    }
]

let KeyTrigger = {
    "Items": [
      {
        "Input": 117,
        "DownMtn": "控制面板:控制面板"
      },
      {
        "Input": 118,
        "DownMtn": "动作欣赏:动作欣赏"
      }
    ],
    "Enabled": true
  }
let changeCosMenu = []

let wavFilePathList = []
// let motionFilePathList = []
let nowWavFilePathList = []
let nowMotionFilePathList = []
let allCharacterList = []

// 寻找model3.json所在文件夹的动作文件
function findNowMotionJson(dirPath) {
    const files = fs.readdirSync(dirPath);
    for (const file of files) {
        const filePath = path.join(dirPath, file);
        const stat = fs.statSync(filePath);
        if (stat.isDirectory()) {
            findNowMotionJson(filePath);
        } else if (filePath.endsWith(".motion3.json")) {
            nowMotionFilePathList.push(filePath)
        }
    }
}

// 判断文件夹路径是否存在,不存在则递归创建
function mkdirSure(destDir) {
    if (!fs.existsSync(destDir)) {
        fs.mkdirSync(destDir, { recursive: true });
    }
}

// 获取所有音频路径
function findWav(dirPath) {
    const files = fs.readdirSync(dirPath);
    for (const file of files) {
        const filePath = path.join(dirPath, file);
        const stat = fs.statSync(filePath);
        if (stat.isDirectory()) {
            findWav(filePath);
        } else if (filePath.endsWith(".wav") || filePath.endsWith(".ogg") || filePath.endsWith(".mp3")) {
            wavFilePathList.push(filePath)
        }
    }
}

// 获取当前角色的音频
function getNowCharacterWav(characterName) {
    // 角色名切割,并动态生成正则
    let name = characterName.split("_")[0]
    var nowCharacterWavRexg = new RegExp(name);
    nowWavFilePathList.length = 0
    for (let i = 0; i < wavFilePathList.length; i++) {
        if (nowCharacterWavRexg.test(wavFilePathList[i])) {
            nowWavFilePathList.push(wavFilePathList[i])
        }
    }
}

// 将一个文件复制到另外一个文件夹中
function copeFile(sourceFilePath, destinationFolderPath) {
    fs.copyFileSync(sourceFilePath, destinationFolderPath);
}

// 转移文件
function transFile(model3Json, characterName, dirPath) {
    // 复制moc文件
    mkdirSure(path.dirname(path.join(outPath, path.join(characterName, model3Json.FileReferences.Moc))))
    copeFile(path.join(dirPath, model3Json.FileReferences.Moc),
        path.join(outPath, path.join(characterName, model3Json.FileReferences.Moc)))
    model3Json.FileReferences.Moc =
        path.join(characterName, model3Json.FileReferences.Moc)
    // 复制贴图
    for (let i = 0; i < model3Json.FileReferences.Textures.length; i++) {
        mkdirSure(path.dirname(path.join(outPath, path.join(characterName, model3Json.FileReferences.Textures[i]))))
        copeFile(path.join(dirPath, model3Json.FileReferences.Textures[i]),
            path.join(outPath, path.join(characterName, model3Json.FileReferences.Textures[i])))
        model3Json.FileReferences.Textures[i] =
            path.join(characterName, model3Json.FileReferences.Textures[i])
    }
    // 复制物理文件
    if (model3Json.FileReferences.Physics) {
        mkdirSure(path.dirname(path.join(outPath, path.join(characterName, model3Json.FileReferences.Physics))))
        copeFile(path.join(dirPath, model3Json.FileReferences.Physics),
            path.join(outPath, path.join(characterName, model3Json.FileReferences.Physics)))
        model3Json.FileReferences.Physics =
            path.join(characterName, model3Json.FileReferences.Physics)
    }
    if (model3Json.FileReferences.PhysicsV2) {
        model3Json.FileReferences.PhysicsV2.File == model3Json.FileReferences.Physics
    }
}

// 处理动作文件和音乐文件
function handleMotion(model3Json, characterName, dirPath) {
    getNowCharacterWav(characterName)
    motionMenu.length = 1
    motionMenu[0].Choices.length = 0
    findNowMotionJson(dirPath)
    // 根据登录动画数量判断是那种登录类型
    let loginMotionNum = 0
    if(!model3Json.FileReferences.Motions) {
        model3Json.FileReferences.Motions = {}
    }
    model3Json.FileReferences.Motions["Start"] = []
    model3Json.FileReferences.Motions["Idle"] = []
    for (let i = 0; i < nowMotionFilePathList.length; i++) {
        if (path.basename(nowMotionFilePathList[i]).startsWith("login")) {
            loginMotionNum++
        }
    }
    for (let i = 0; i < nowMotionFilePathList.length; i++) {
        let motionName = path.basename(nowMotionFilePathList[i], ".motion3.json")
        // 拷贝动作文件
        let newMotionpath = path.join(path.join(outPath, characterName), path.join("Motions", path.basename(nowMotionFilePathList[i])))
        mkdirSure(path.dirname(newMotionpath))
        // console.log(newMotionpath);
        copeFile(nowMotionFilePathList[i], newMotionpath)
        // 闲置动作不需要语音
        if ((motionName.startsWith("normal") && !motionName.startsWith("normal_B") && !motionName.startsWith("normalB")) || motionName.startsWith("idle")) {
            model3Json.FileReferences.Motions["Idle"].push(
                {
                    "Name": motionName,
                    "File": path.join(characterName, path.join("Motions", path.basename(nowMotionFilePathList[i]))),
                    "Interruptable": true
                }
            )
            continue
        }
        if (motionToWav[motionName]) {
            for (let j = 0; j < nowWavFilePathList.length; j++) {
                if (motionToWav[motionName].Rexg.test(nowWavFilePathList[j])) {
                    let newWavpath = path.join(path.join(outPath, characterName), path.join("Sounds", path.basename(nowWavFilePathList[j])))
                    mkdirSure(path.dirname(newWavpath))
                    copeFile(nowWavFilePathList[j], newWavpath)
                    // 登陆动画
                    if (motionName.startsWith("login")) {
                        for (let z = 0; z < loginAndMouse.length; z++) {
                            model3Json.FileReferences.Motions["Start"].push({
                                "Name": motionName,
                                "Sound": loginAndMouse[z].File ?? path.join(characterName, path.join("Sounds", path.basename(nowWavFilePathList[j]))),
                                "File": loginAndMouse[z].File ?? path.join(characterName, path.join("Motions", path.basename(nowMotionFilePathList[i]))),
                                "Text": "",
                                "Command": loginAndMouse[z].Command,
                                "TimeLimit": loginMotionNum > 3 ? motionToWav[motionName].TimeLimit : null,
                                "VarFloats": loginAndMouse[z].VarFloats
                            })
                        }
                    }
                }
            }
        }
        else {
            // 随机选择音
            const randomNum = Math.floor(Math.random() * nowWavFilePathList.length)
            let newWavpath = path.join(path.join(outPath, characterName), path.join("Sounds", path.basename(nowWavFilePathList[randomNum])))
            mkdirSure(path.dirname(newWavpath))
            copeFile(nowWavFilePathList[randomNum], newWavpath)
            motionMenu.push({
                "Name": motionName,
                "File": path.join(characterName, path.join("Motions", path.basename(nowMotionFilePathList[i]))),
                "Sound": path.join(characterName, path.join("Sounds", path.basename(nowWavFilePathList[randomNum]))),
                "Text": "",
                "Interruptable": true
            })
            motionMenu[0].Choices.push({
                "Text": motionName,
                "NextMtn": `动作欣赏:${motionName}`
            })
        }
    }
    if (model3Json.FileReferences.Motions["Start"].length === 0) {
        for (let z = 0; z < loginAndMouse.length; z++) {
            model3Json.FileReferences.Motions["Start"].push({
                "Name": z,
                "Text": "",
                "Command": loginAndMouse[z].Command,
                "VarFloats": loginAndMouse[z].VarFloats
            })
        }
    }
    if (model3Json.FileReferences.Motions["Idle"].length === 0) {
        model3Json.FileReferences.Motions["Idle"].length === null
    }
    model3Json.FileReferences.Motions[""] = null
}

// 处理换装
function handleCos(model3Json, characterName, dirPath) {
    model3Json.FileReferences.Motions["控制面板"] = controlloer
    model3Json.FileReferences.Motions["Tap换装"] = changeCosMenu
    model3Json.HitAreas = HitAreas
    model3Json.FileReferences.Motions["动作欣赏"] = motionMenu
    model3Json.FileReferences.Motions["控制"] = loginAndMouseMenu
}

// 从文件路径中寻找角色名
function getCharacterName(filePath) {
    const match = filePath.match(characterNameRexg);
    let characterName
    if (match) {
        // 提取到的随机字符
        characterName = match[1];
        // console.log(`提取到的随机字符是:${characterName}`);
    } else {
        console.log('提取角色名称错误');
    }
    return characterName
}

// 按键处理
function hanleKeyTrigger(model3Json, characterName, dirPath) {
    if(!model3Json.Controllers) {
        model3Json.Controllers = {}
    }
    model3Json.Controllers.KeyTrigger = KeyTrigger
}

function findModelJSON(dirPath) {
    const files = fs.readdirSync(dirPath);
    for (const file of files) {
        const filePath = path.join(dirPath, file);
        const stat = fs.statSync(filePath);
        if (stat.isDirectory()) {
            findModelJSON(filePath);
        } else if (filePath.endsWith(".model3.json")) {
            let characterName
            try {
                let model3Json = JSON.parse(fs.readFileSync(filePath, 'utf8'))
                characterName = getCharacterName(filePath)
                if (!characterName) {
                    return
                }
                nowMotionFilePathList.length = 0
                transFile(model3Json, characterName, dirPath)
                handleMotion(model3Json, characterName, dirPath)
                handleCos(model3Json, characterName, dirPath)
                hanleKeyTrigger(model3Json, characterName, dirPath)
                fs.writeFileSync(path.join(outPath, `${characterName}.model3.json`), JSON.stringify(model3Json, '\t'))
                console.log(`${characterName}已处理`);
            }catch(e) {
                console.log(e);
                console.log(`${characterName}转换出错`);
            }
        }
    }
}

// 获取所有角色
function findAllCharacter(dirPath) {
    const files = fs.readdirSync(dirPath);
    for (const file of files) {
        const filePath = path.join(dirPath, file);
        const stat = fs.statSync(filePath);
        if (stat.isDirectory()) {
            findAllCharacter(filePath);
        } else if (filePath.endsWith(".model3.json")) {
            let characterName = getCharacterName(filePath)
            if (characterName) {
                allCharacterList.push(characterName)
            }
        }
    }
}

// 获取换装菜单
function getChangeCharacterMenu() {
    const cen = Math.ceil(allCharacterList.length / 20)
    console.log(JSON.stringify(allCharacterList));
    let index = 0
    for (let i = 0; i < cen; i++) {
        let changeCosI = {
            "Name": `换装${i}`,
            "Text": `换装${i}`,
            "Choices": [
            ],
            "TextDuration": 5000
        }
        changeCosMenu.push(changeCosI)
        controlloer[0].Choices.push({
            "Text": `换装${i}`,
            "NextMtn": `Tap换装:换装${i}`
        })
        for (let j = 0; j < 20 && index < allCharacterList.length; index++, j++) {
            changeCosMenu.push({
                "Name": allCharacterList[index],
                "Command": `change_cos ${allCharacterList[index]}.model3.json`
            })
            changeCosI.Choices.push({
                "Text": allCharacterList[index],
                "NextMtn": `Tap换装:${allCharacterList[index]}`
            })
        }
    }
}

function init() {
    // 预处理
    findWav(wavPath)
    findAllCharacter(filePath)
    getChangeCharacterMenu()
    // 正式处理
    findModelJSON(filePath)
}

init()