Cocos Unity WebGL 速度修改器

为 Cocos2dx 和 Unity WebGL 页面启用速度控制功能, 帮你跳过太长的动画.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name           Cocos Unity WebGL 速度修改器
// @name:en        Cocos Unity WebGL Speed Controller
// @namespace      Violentmonkey Scripts
// @match          *://*/*
// @run-at         document-start
// @grant          none
// @version        1.2
// @author         BigWater
// @license        MIT
// @description    为 Cocos2dx 和 Unity WebGL 页面启用速度控制功能, 帮你跳过太长的动画.
// @description:en Enable speed control for Cocos2dx and Unity WebGL pages to help you skip overly long animations.
// ==/UserScript==

(function() {
    'use strict';

    // ===============================
    // Cocos and Unity detection
    // ===============================
    function isCocos() {
        // Global namespace
        if (window.cc || window.cocos2d || window.__ccGlobal) return true;

        // Script name patterns
        const scripts = [...document.scripts].map(s => s.src);
        if (scripts.some(src => /cocos2d|cocos|project\.js|settings\.js/i.test(src))) {
            return true;
        }

        return false;
    }

    function isUnity() {
        if (window.createUnityInstance || window.UnityLoader) return true;

        const scripts = [...document.scripts].map(s => s.src);
        if (scripts.some(src => /UnityLoader|\.framework\.js|\.data|\.wasm|build\.json/i.test(src))) {
            return true;
        }

        // Canvas detection
        if (document.querySelector('#unity-container, #unity-canvas')) return true;

        return false;
    }


    // ===============================
    // Configuration
    // ===============================
    let speedMultiplier = 1.0;
    const MIN = 0.1;
    const MAX = 32.0;

    // ===============================
    // Hook requestAnimationFrame
    // ===============================
    const originalRAF = window.requestAnimationFrame.bind(window);
    const callbackMap = new Map();
    let nextId = 1;

    window.requestAnimationFrame = function(callback) {
        const id = nextId++;
        const wrapped = function(timestamp) {
            const scaled = timestamp * speedMultiplier;
            callback(scaled);
            callbackMap.delete(id);
        };
        callbackMap.set(id, wrapped);
        return originalRAF(wrapped);
    };

    // ===============================
    // Hook performance.now
    // ===============================
    if (performance && typeof performance.now === 'function') {
        const originalNow = performance.now.bind(performance);
        const baseReal = originalNow();
        const baseFake = baseReal;

        performance.now = function() {
            const real = originalNow();
            return baseFake + (real - baseReal) * speedMultiplier;
        };
    }

    // ===============================
    // Create Floating UI Panel
    // ===============================
    function createUI() {
        // skip if not cocos or unity
        if (!isCocos() && !isUnity()) return;

        const panel = document.createElement('div');
        panel.id = 'speedhack-panel';
        panel.style.cssText = `
            position: fixed;
            top: 20px;
            left: 20px;
            z-index: 999999;
            background: rgba(0,0,0,0.75);
            color: #fff;
            padding: 10px;
            border-radius: 8px;
            font-family: sans-serif;
            font-size: 14px;
            user-select: none;
            width: 160px;
            cursor: move;
        `;

        panel.innerHTML = `
            <div style="margin-bottom:6px;font-weight:bold;">Speed Control</div>
            <input id="speedhack-input" type="number" step="0.1" min="0.1" max="10"
                value="${speedMultiplier}"
                style="width:70px; padding:3px; border-radius:4px; border:1px solid #ccc;">

            <!--<div style="margin-top:8px;">
                <button id="speedhack-slower"
                    style="width:45%; padding:3px; margin-right:2%; border-radius:4px; border:none; cursor:pointer;">
                    -
                </button>
                <button id="speedhack-faster"
                    style="width:45%; padding:3px; border-radius:4px; border:none; cursor:pointer;">
                    +
                </button>
            </div>-->

            <button id="speedhack-reset"
                style="margin-top:6px; width:100%; padding:3px; border-radius:4px; border:none; cursor:pointer;">
                Reset
            </button>
        `;

        document.body.appendChild(panel);

        // Hook events
        const input = document.getElementById('speedhack-input');
        // document.getElementById('speedhack-slower').onclick = () => {
        //     setSpeed(speedMultiplier - 0.5);
        //     input.value = speedMultiplier.toFixed(1);
        // };
        // document.getElementById('speedhack-faster').onclick = () => {
        //     setSpeed(speedMultiplier + 0.5);
        //     input.value = speedMultiplier.toFixed(1);
        // };
        document.getElementById('speedhack-reset').onclick = () => {
            setSpeed(1.0);
            input.value = '1.0';
        };
      input.addEventListener('wheel', e => {
        if (e.deltaY < 0) {
            setSpeed(speedMultiplier + 0.1);
            input.value = speedMultiplier.toFixed(1);
        } else {
            setSpeed(speedMultiplier - 0.1);
            input.value = speedMultiplier.toFixed(1);
        }
      });

        // Draggable panel
        makeDraggable(panel);
    }

    function setSpeed(v) {
        speedMultiplier = Math.max(MIN, Math.min(MAX, v));
        // console.log('[Speedhack] Multiplier:', speedMultiplier);
    }

    // ===============================
    // Make Panel Draggable
    // ===============================
    function makeDraggable(element) {
        let offsetX = 0, offsetY = 0, down = false;

        element.addEventListener('mousedown', e => {
            down = true;
            offsetX = e.clientX - element.offsetLeft;
            offsetY = e.clientY - element.offsetTop;
        });

        document.addEventListener('mousemove', e => {
            if (!down) return;
            element.style.left = (e.clientX - offsetX) + 'px';
            element.style.top = (e.clientY - offsetY) + 'px';
        });

        document.addEventListener('mouseup', () => down = false);
    }

    // ===============================
    // Inject UI when DOM is ready
    // ===============================
    document.addEventListener('DOMContentLoaded', createUI);
})();