Interactive Reptile Cursor

Interactive creature animation that follows mouse

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         Interactive Reptile Cursor
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  Interactive creature animation that follows mouse
// @author       You
// @match        *://*/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Wait for page to load
    window.addEventListener('load', function() {
        // Create and inject styles
        const style = document.createElement('style');
        style.textContent = `
            #creatureCanvas {
                position: fixed !important;
                top: 0 !important;
                left: 0 !important;
                width: 100vw !important;
                height: 100vh !important;
                z-index: 999999 !important;
                pointer-events: none !important;
                background-color: transparent !important;
            }
        `;
        document.head.appendChild(style);

        // Input handling
        var Input = {
            keys: [],
            mouse: {
                left: false,
                right: false,
                middle: false,
                x: 0,
                y: 0
            }
        };

        for (var i = 0; i < 230; i++) {
            Input.keys.push(false);
        }

        document.addEventListener("keydown", function(event) {
            Input.keys[event.keyCode] = true;
        });

        document.addEventListener("keyup", function(event) {
            Input.keys[event.keyCode] = false;
        });

        document.addEventListener("mousedown", function(event) {
            if (event.button === 0) {
                Input.mouse.left = true;
            }
            if (event.button === 1) {
                Input.mouse.middle = true;
            }
            if (event.button === 2) {
                Input.mouse.right = true;
            }
        });

        document.addEventListener("mouseup", function(event) {
            if (event.button === 0) {
                Input.mouse.left = false;
            }
            if (event.button === 1) {
                Input.mouse.middle = false;
            }
            if (event.button === 2) {
                Input.mouse.right = false;
            }
        });

        document.addEventListener("mousemove", function(event) {
            Input.mouse.x = event.clientX;
            Input.mouse.y = event.clientY;
        });

        // Create canvas
        var canvas = document.createElement("canvas");
        canvas.id = "creatureCanvas";
        document.body.appendChild(canvas);

        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;

        // Handle window resize
        window.addEventListener('resize', function() {
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight;
        });

        var ctx = canvas.getContext("2d");

        // Necessary classes
        var segmentCount = 0;

        class Segment {
            constructor(parent, size, angle, range, stiffness) {
                segmentCount++;
                this.isSegment = true;
                this.parent = parent;
                if (typeof parent.children == "object") {
                    parent.children.push(this);
                }
                this.children = [];
                this.size = size;
                this.relAngle = angle;
                this.defAngle = angle;
                this.absAngle = parent.absAngle + angle;
                this.range = range;
                this.stiffness = stiffness;
                this.updateRelative(false, true);
            }

            updateRelative(iter, flex) {
                this.relAngle = this.relAngle - 2 * Math.PI * Math.floor((this.relAngle - this.defAngle) / 2 / Math.PI + 1 / 2);
                if (flex) {
                    this.relAngle = Math.min(
                        this.defAngle + this.range / 2,
                        Math.max(
                            this.defAngle - this.range / 2,
                            (this.relAngle - this.defAngle) / this.stiffness + this.defAngle
                        )
                    );
                }
                this.absAngle = this.parent.absAngle + this.relAngle;
                this.x = this.parent.x + Math.cos(this.absAngle) * this.size;
                this.y = this.parent.y + Math.sin(this.absAngle) * this.size;
                if (iter) {
                    for (var i = 0; i < this.children.length; i++) {
                        this.children[i].updateRelative(iter, flex);
                    }
                }
            }

            draw(iter) {
                ctx.beginPath();
                ctx.moveTo(this.parent.x, this.parent.y);
                ctx.lineTo(this.x, this.y);
                ctx.stroke();
                if (iter) {
                    for (var i = 0; i < this.children.length; i++) {
                        this.children[i].draw(true);
                    }
                }
            }

            follow(iter) {
                var x = this.parent.x;
                var y = this.parent.y;
                var dist = Math.sqrt((this.x - x) ** 2 + (this.y - y) ** 2);
                this.x = x + this.size * (this.x - x) / dist;
                this.y = y + this.size * (this.y - y) / dist;
                this.absAngle = Math.atan2(this.y - y, this.x - x);
                this.relAngle = this.absAngle - this.parent.absAngle;
                this.updateRelative(false, true);
                if (iter) {
                    for (var i = 0; i < this.children.length; i++) {
                        this.children[i].follow(true);
                    }
                }
            }
        }

        class LimbSystem {
            constructor(end, length, speed, creature) {
                this.end = end;
                this.length = Math.max(1, length);
                this.creature = creature;
                this.speed = speed;
                creature.systems.push(this);
                this.nodes = [];
                var node = end;
                for (var i = 0; i < length; i++) {
                    this.nodes.unshift(node);
                    node = node.parent;
                    if (!node.isSegment) {
                        this.length = i + 1;
                        break;
                    }
                }
                this.hip = this.nodes[0].parent;
            }

            moveTo(x, y) {
                this.nodes[0].updateRelative(true, true);
                var dist = Math.sqrt((x - this.end.x) ** 2 + (y - this.end.y) ** 2);
                var len = Math.max(0, dist - this.speed);
                for (var i = this.nodes.length - 1; i >= 0; i--) {
                    var node = this.nodes[i];
                    var ang = Math.atan2(node.y - y, node.x - x);
                    node.x = x + len * Math.cos(ang);
                    node.y = y + len * Math.sin(ang);
                    x = node.x;
                    y = node.y;
                    len = node.size;
                }
                for (var i = 0; i < this.nodes.length; i++) {
                    var node = this.nodes[i];
                    node.absAngle = Math.atan2(node.y - node.parent.y, node.x - node.parent.x);
                    node.relAngle = node.absAngle - node.parent.absAngle;
                    for (var ii = 0; ii < node.children.length; ii++) {
                        var childNode = node.children[ii];
                        if (!this.nodes.includes(childNode)) {
                            childNode.updateRelative(true, false);
                        }
                    }
                }
            }

            update() {
                this.moveTo(Input.mouse.x, Input.mouse.y);
            }
        }

        class LegSystem extends LimbSystem {
            constructor(end, length, speed, creature) {
                super(end, length, speed, creature);
                this.goalX = end.x;
                this.goalY = end.y;
                this.step = 0;
                this.forwardness = 0;
                this.reach = 0.9 * Math.sqrt((this.end.x - this.hip.x) ** 2 + (this.end.y - this.hip.y) ** 2);
                var relAngle = this.creature.absAngle - Math.atan2(this.end.y - this.hip.y, this.end.x - this.hip.x);
                relAngle -= 2 * Math.PI * Math.floor(relAngle / 2 / Math.PI + 1 / 2);
                this.swing = -relAngle + (2 * (relAngle < 0) - 1) * Math.PI / 2;
                this.swingOffset = this.creature.absAngle - this.hip.absAngle;
            }

            update(x, y) {
                this.moveTo(this.goalX, this.goalY);
                if (this.step == 0) {
                    var dist = Math.sqrt((this.end.x - this.goalX) ** 2 + (this.end.y - this.goalY) ** 2);
                    if (dist > 1) {
                        this.step = 1;
                        this.goalX = this.hip.x + this.reach * Math.cos(this.swing + this.hip.absAngle + this.swingOffset) + (2 * Math.random() - 1) * this.reach / 2;
                        this.goalY = this.hip.y + this.reach * Math.sin(this.swing + this.hip.absAngle + this.swingOffset) + (2 * Math.random() - 1) * this.reach / 2;
                    }
                } else if (this.step == 1) {
                    var theta = Math.atan2(this.end.y - this.hip.y, this.end.x - this.hip.x) - this.hip.absAngle;
                    var dist = Math.sqrt((this.end.x - this.hip.x) ** 2 + (this.end.y - this.hip.y) ** 2);
                    var forwardness2 = dist * Math.cos(theta);
                    var dF = this.forwardness - forwardness2;
                    this.forwardness = forwardness2;
                    if (dF * dF < 1) {
                        this.step = 0;
                        this.goalX = this.hip.x + (this.end.x - this.hip.x);
                        this.goalY = this.hip.y + (this.end.y - this.hip.y);
                    }
                }
            }
        }

        class Creature {
            constructor(x, y, angle, fAccel, fFric, fRes, fThresh, rAccel, rFric, rRes, rThresh) {
                this.x = x;
                this.y = y;
                this.absAngle = angle;
                this.fSpeed = 0;
                this.fAccel = fAccel;
                this.fFric = fFric;
                this.fRes = fRes;
                this.fThresh = fThresh;
                this.rSpeed = 0;
                this.rAccel = rAccel;
                this.rFric = rFric;
                this.rRes = rRes;
                this.rThresh = rThresh;
                this.children = [];
                this.systems = [];
            }

            follow(x, y) {
                var dist = Math.sqrt((this.x - x) ** 2 + (this.y - y) ** 2);
                var angle = Math.atan2(y - this.y, x - this.x);
                var accel = this.fAccel;
                if (this.systems.length > 0) {
                    var sum = 0;
                    for (var i = 0; i < this.systems.length; i++) {
                        sum += this.systems[i].step == 0;
                    }
                    accel *= sum / this.systems.length;
                }
                this.fSpeed += accel * (dist > this.fThresh);
                this.fSpeed *= 1 - this.fRes;
                this.speed = Math.max(0, this.fSpeed - this.fFric);
                var dif = this.absAngle - angle;
                dif -= 2 * Math.PI * Math.floor(dif / (2 * Math.PI) + 1 / 2);
                if (Math.abs(dif) > this.rThresh && dist > this.fThresh) {
                    this.rSpeed -= this.rAccel * (2 * (dif > 0) - 1);
                }
                this.rSpeed *= 1 - this.rRes;
                if (Math.abs(this.rSpeed) > this.rFric) {
                    this.rSpeed -= this.rFric * (2 * (this.rSpeed > 0) - 1);
                } else {
                    this.rSpeed = 0;
                }
                this.absAngle += this.rSpeed;
                this.absAngle -= 2 * Math.PI * Math.floor(this.absAngle / (2 * Math.PI) + 1 / 2);
                this.x += this.speed * Math.cos(this.absAngle);
                this.y += this.speed * Math.sin(this.absAngle);
                this.absAngle += Math.PI;
                for (var i = 0; i < this.children.length; i++) {
                    this.children[i].follow(true, true);
                }
                for (var i = 0; i < this.systems.length; i++) {
                    this.systems[i].update(x, y);
                }
                this.absAngle -= Math.PI;
                this.draw(true);
            }

            draw(iter) {
                var r = 4;
                ctx.beginPath();
                ctx.arc(this.x, this.y, r, Math.PI / 4 + this.absAngle, 7 * Math.PI / 4 + this.absAngle);
                ctx.moveTo(this.x + r * Math.cos(7 * Math.PI / 4 + this.absAngle), this.y + r * Math.sin(7 * Math.PI / 4 + this.absAngle));
                ctx.lineTo(this.x + r * Math.cos(this.absAngle) * Math.sqrt(2), this.y + r * Math.sin(this.absAngle) * Math.sqrt(2));
                ctx.lineTo(this.x + r * Math.cos(Math.PI / 4 + this.absAngle), this.y + r * Math.sin(Math.PI / 4 + this.absAngle));
                ctx.stroke();
                if (iter) {
                    for (var i = 0; i < this.children.length; i++) {
                        this.children[i].draw(true);
                    }
                }
            }
        }

        function setupLizard(size, legs, tail) {
            var s = size;
            var critter = new Creature(canvas.width / 2, canvas.height / 2, 0, s * 10, s * 2, 0.5, 16, 0.5, 0.085, 0.5, 0.3);
            var spinal = critter;

            // Neck
            for (var i = 0; i < 6; i++) {
                spinal = new Segment(spinal, s * 4, 0, 3.1415 * 2 / 3, 1.1);
                for (var ii = -1; ii <= 1; ii += 2) {
                    var node = new Segment(spinal, s * 3, ii, 0.1, 2);
                    for (var iii = 0; iii < 3; iii++) {
                        node = new Segment(node, s * 0.1, -ii * 0.1, 0.1, 2);
                    }
                }
            }

            // Torso and legs
            for (var i = 0; i < legs; i++) {
                if (i > 0) {
                    for (var ii = 0; ii < 6; ii++) {
                        spinal = new Segment(spinal, s * 4, 0, 1.571, 1.5);
                        for (var iii = -1; iii <= 1; iii += 2) {
                            var node = new Segment(spinal, s * 3, iii * 1.571, 0.1, 1.5);
                            for (var iv = 0; iv < 3; iv++) {
                                node = new Segment(node, s * 3, -iii * 0.3, 0.1, 2);
                            }
                        }
                    }
                }

                for (var ii = -1; ii <= 1; ii += 2) {
                    var node = new Segment(spinal, s * 12, ii * 0.785, 0, 8);
                    node = new Segment(node, s * 16, -ii * 0.785, 6.28, 1);
                    node = new Segment(node, s * 16, ii * 1.571, 3.1415, 2);
                    for (var iii = 0; iii < 4; iii++) {
                        new Segment(node, s * 4, (iii / 3 - 0.5) * 1.571, 0.1, 4);
                    }
                    new LegSystem(node, 3, s * 12, critter);
                }
            }

            // Tail
            for (var i = 0; i < tail; i++) {
                spinal = new Segment(spinal, s * 4, 0, 3.1415 * 2 / 3, 1.1);
                for (var ii = -1; ii <= 1; ii += 2) {
                    var node = new Segment(spinal, s * 3, ii, 0.1, 2);
                    for (var iii = 0; iii < 3; iii++) {
                        node = new Segment(node, s * 3 * (tail - i) / tail, -ii * 0.1, 0.1, 2);
                    }
                }
            }

            // Animation loop
            function animate() {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                ctx.strokeStyle = "white";
                critter.follow(Input.mouse.x, Input.mouse.y);
                requestAnimationFrame(animate);
            }

            animate();
        }

        // Start the animation with random parameters
        ctx.strokeStyle = "white";
        var legNum = Math.floor(1 + Math.random() * 12);
        setupLizard(8 / Math.sqrt(legNum), legNum, Math.floor(4 + Math.random() * legNum * 8));
    });
})();