Agar 流体效果的 Cocos2d-JS 实现

代码取自 Agar 原版源码,移植到 Cocos2d-JS v3.7 上。
使用方法,新建一个 HelloWorld 工程,将以下代码覆盖新创建的工程的 app.js 文件中的代码即可看到效果,代码中已经有充分的注释说明。

// by WM, QQ: 348915654
// cocos2d-js v3.10
// app.js
// PosNode 为实际的点
// AreaNode 为表示空间的点

var g_versionStr = "v1.0";
//var g_timestamp = +new Date; // 时间戳,原本用于细胞的旋转,目前用不到

// 墙壁的最小点和最大点 // 坐标系为 →x ↑y
var g_leftPos = 50; // min x
var g_bottomPos = 50; // min y
var g_rightPos = 600; // max x
var g_topPos = 600; // max y

// 四叉树
var Quad = {
    init: function (args) {
        function Node(x, y, w, h, depth) {
            this.x = x;
            this.y = y;
            this.w = w;
            this.h = h;
            this.depth = depth; // 当前的深度
            this.items = []; // 实际存储的位置节点 // Array<PosNode>
            this.nodes = []; // 用于分割空间的节点,简称空间节点 // Array<AreaNode>
        }

        var m_maxChildren = args.maxChildren || 2,
            m_maxDepth = args.maxDepth || 4;

        Node.prototype = {
            x: 0,
            y: 0,
            w: 0,
            h: 0,
            depth: 0,
            items: null,
            nodes: null,

            // selector: {x: Number, y: Number, w: Number, h: Number} // 判断 selector 所表示的区域内有没有 PosNode,如果有则返回 true,否则返回 false
            exists: function (selector) {
                for (var i = 0; i < this.items.length; ++i) {
                    var item = this.items[i];
                    if (item.x >= selector.x && item.y >= selector.y && item.x < selector.x + selector.w && item.y < selector.y + selector.h) return true
                }
                if (0 != this.nodes.length) {
                    var self = this;
                    return this.findOverlappingNodes(selector, function (dir) {
                        return self.nodes[dir].exists(selector)
                    })
                }
                return false;
            },

            // item: {x: Number, y: Number, w: Number, h: Number}; callback: function (PosNode); // 判断 item 所表示的区域内有没有 PosNode,并对所有符合条件的 PosNode 依次调用 callback() 函数
            retrieve: function (item, callback) {
                for (var i = 0; i < this.items.length; ++i) callback(this.items[i]);
                if (0 != this.nodes.length) {
                    var self = this;
                    this.findOverlappingNodes(item, function (dir) {
                        self.nodes[dir].retrieve(item, callback)
                    })
                }
            },

            // a: {x: Number, y: Number}
            insert: function (a) {
                if (0 != this.nodes.length) {
                    this.nodes[this.findInsertNode(a)].insert(a);
                } else {
                    // 如果这个节点下的位置节点过多且未达到最大深度,则将这个节点分割,将这些位置节点放入分割的节点中
                    if (this.items.length >= m_maxChildren && this.depth < m_maxDepth) {
                        this.devide();
                        this.nodes[this.findInsertNode(a)].insert(a);
                    } else {
                        this.items.push(a);
                    }
                }
            },

            // 找这个 a 节点属于这个 this 的哪个象限 // 空间区域号 airId, →x ↑y, 左下为 0,左上为 2,右下为 1,右上为 3
            findInsertNode: function (a) {
                return a.x < this.x + this.w / 2 ? a.y < this.y + this.h / 2 ? 0 : 2 : a.y < this.y + this.h / 2 ? 1 : 3;
            },

            // a: {x: Number, y: Number, w: Number, h: Number}; b: function (dir); // 这个函数的操作是判断 a 所表示的区域是哪一块就调用哪个 b(airId) 并在最后返回一个布尔值
            findOverlappingNodes: function (a, b) {
                return a.x < this.x + this.w / 2 && (a.y < this.y + this.h / 2 && b(0) || a.y >= this.y + this.h / 2 && b(2)) || a.x >= this.x + this.w / 2 && (a.y < this.y + this.h / 2 && b(1) || a.y >= this.y + this.h / 2 && b(3)) ? true : false;
            },

            devide: function () {
                var a = this.depth + 1,
                    c = this.w / 2,
                    d = this.h / 2;
                this.nodes.push(new Node(this.x, this.y, c, d, a));
                this.nodes.push(new Node(this.x + c, this.y, c, d, a));
                this.nodes.push(new Node(this.x, this.y + d, c, d, a));
                this.nodes.push(new Node(this.x + c, this.y + d, c, d, a));
                a = this.items;
                this.items = [];
                for (c = 0; c < a.length; c++) this.insert(a[c]);
            },

            clear: function () {
                for (var a = 0; a < this.nodes.length; a++) this.nodes[a].clear();
                this.items.length = 0;
                this.nodes.length = 0;
            }
        };

        var internalSelector = {
            x: 0,
            y: 0,
            w: 0,
            h: 0
        };

        return {
            root: new Node(args.minX, args.minY, args.maxX - args.minX, args.maxY - args.minY, 0),

            insert: function (a) {
                this.root.insert(a);
            },

            retrieve: function (a, b) {
                this.root.retrieve(a, b);
            },

            retrieve2: function (a, b, c, d, callback) {
                internalSelector.x = a;
                internalSelector.y = b;
                internalSelector.w = c;
                internalSelector.h = d;
                this.root.retrieve(internalSelector, callback);
            },

            exists: function (a) {
                return this.root.exists(a);
            },

            clear: function () {
                this.root.clear();
            }
        }
    }
};

// 用于保存四叉树的全局变量
var g_qTree = null;

// 细胞
var Cell = cc.Class.extend({
    m_id: 0,
    m_x: 0,
    m_y: 0,
    m_size: 100, // 细胞的半径
    m_pointsNum: 128, // 点的数量
    m_points: null, // @private
    m_pointsAcc: null, // @private
    m_isVirus: false, // 如果为 true 细胞将呈现锯齿状
    m_isAgitated: false, // 如果是激活状态细胞将振动得更加激烈
    m_drawNode: null,

    ctor: function () {
        this.m_points = [];
        this.m_pointsAcc = [];
        this.CreatePoints();
    },

    Destroy: function () {
        this.m_points = null;
        this.m_pointsAcc = null;
    },

    GetNumPoints: function () {
        return this.m_pointsNum;
    },

    CreatePoints: function () {
        var sampleNum = 0;
        var rand = 0;
        var rand2 = 0;
        var point = null;

        sampleNum = this.GetNumPoints();
        for (; this.m_points.length > sampleNum;) {
            rand = ~~(Math.random() * this.m_points.length);
            this.m_points.splice(rand, 1);
            this.m_pointsAcc.splice(rand, 1);
        }
        if (this.m_points.length == 0 && sampleNum > 0) {
            this.m_points.push({
                ref: this,
                size: this.m_size,
                x: this.m_x,
                y: this.m_y
            });
            this.m_pointsAcc.push(Math.random() - 0.5);
        }
        while (this.m_points.length < sampleNum) {
            rand2 = ~~(Math.random() * this.m_points.length);
            point = this.m_points[rand2];
            this.m_points.splice(rand2, 0, {
                ref: this,
                size: point.size,
                x: point.x,
                y: point.y
            });
            this.m_pointsAcc.splice(rand2, 0, this.m_pointsAcc[rand2]);
        }
    },

    MovePoints: function () {
        this.CreatePoints();
        var points = null;
        var pointsAcc = null;
        var numPoints = 0;
        var i = 0;
        var posAcc1 = 0;
        var posAcc2 = 0;
        var ref = null;
        var isVirus = 0;
        var j = 0;
        var f = 0;
        var e = 0;
        var m = 0;
        var isCollide = false;
        var posX = 0;
        var posY = 0;

        for (points = this.m_points, pointsAcc = this.m_pointsAcc, numPoints = points.length, i = 0;
             i < numPoints; i++) {
            posAcc1 = pointsAcc[(i - 1 + numPoints) % numPoints];
            posAcc2 = pointsAcc[(i + 1 + numPoints) % numPoints];
            pointsAcc[i] += (Math.random() - 0.5) * (this.m_isAgitated ? 3 : 1); // 半径随机的变化从这里来
            pointsAcc[i] *= 0.7;
            if (pointsAcc[i] > 10) {
                pointsAcc[i] = 10;
            }
            if (pointsAcc[i] < -10) {
                pointsAcc[i] = 10;
            }
            pointsAcc[i] = (posAcc1 + posAcc2 + 8 * pointsAcc[i]) / 10;
        }

        // isVirus 用于这个循环的累加角度,如果是病毒则不计算累加角度
//        for (ref = this, isVirus = this.m_isVirus ? 0 : (this.m_id / 1000 + g_timestamp / 10000) % (2 * Math.PI), j = 0; j < numPoints; j++) {
        for (ref = this, j = 0; j < numPoints; j++) {
            f = points[j].size;
            e = points[(j - 1 + numPoints) % numPoints].size;
            m = points[(j + 1 + numPoints) % numPoints].size;

            { // 判断是否与其它点产生碰撞 //...
                isCollide = false;
                posX = points[j].x;
                posY = points[j].y;
                // 与其它节点的碰撞 //...
                if (g_qTree != null) {
                    g_qTree.retrieve2(posX - 5, posY - 5, 10, 10, function (node) {
                        if (node.ref != ref &&
                            ((posX - node.x) * (posX - node.x) + (posY - node.y) * (posY - node.y) < 25)) {
                            isCollide = true;
                        }
                    })
                }
                // 与墙壁的碰撞
                if (!isCollide && posX < g_leftPos || posX > g_rightPos || posY < g_bottomPos || posY > g_topPos) {
                    isCollide = true;
                }
                if (isCollide) {
                    if (pointsAcc[j] > 0) {
                        pointsAcc[j] = 0;
                    }
                    pointsAcc[j] -= 1;
                }
            }

            f += pointsAcc[j]; // 半径变化
            if (f < 0) {
                f = 0;
            }
            f = this.m_isAgitated ? (19 * f + this.m_size) / 20 : (12 * f + this.m_size) / 13; // 半径向 this.m_size 变化
            points[j].size = (e + m + 8 * f) / 10; // 半径根据两旁的半径来变化 // 两旁的各占一份,自己占八份

            e = 2 * Math.PI / numPoints; // 单份的角度
            m = this.m_points[j].size; // 半径
            // 如果是病毒还可以呈现出锯齿效果
            if (this.m_isVirus && 0 == j % 2) {
                m += 5;
            }
            // 这是圆的公式
            points[j].x = this.m_x + Math.cos(e * j + isVirus) * m;
            points[j].y = this.m_y + Math.sin(e * j + isVirus) * m;
        }
    },

    ShouldRender: function () {
        return true;
    },

    DrawOneCell: function (drawNode) {
        if (this.ShouldRender()) {
            this.MovePoints();
            var d = this.GetNumPoints();
            var c = 0;
            var e = 0;
            var verts = [];

            for (c = 1; c <= d; c++) {
                e = c % d;
                verts.push(cc.p(this.m_points[e].x, this.m_points[e].y));
            }
            drawNode.clear();
            drawNode.drawPoly(verts, cc.color.WHITE, 2, cc.color.BLACK);
        }
    },

    HaHaTest: function () {

    }
});

var g_mainLogic = null;

var HelloWorldLayer = cc.Layer.extend({
    sprite: null,
    m_cells: null,

    ctor: function () {
        //
        // 1. super init first
        this._super();

        /
        // 2. add a menu item with "X" image, which is clicked to quit the program
        //    you may modify it.
        // ask the window size
        var size = cc.winSize;

        // add a "close" icon to exit the progress. it's an autorelease object
        var closeItem = new cc.MenuItemImage(
            res.CloseNormal_png,
            res.CloseSelected_png,
            function () {
                cc.log("Menu is clicked!");
            }, this);
        closeItem.attr({
            x: size.width - 20,
            y: 20,
            anchorX: 0.5,
            anchorY: 0.5
        });

        var menu = new cc.Menu(closeItem);
        menu.x = 0;
        menu.y = 0;
        this.addChild(menu, 1);

        /
        // 3. add your codes below...
        // add a label shows "Hello World"
        // create and initialize a label
        var helloLabel = new cc.LabelTTF(g_versionStr, "Arial", 38);
        // position the label on the center of the screen
        helloLabel.x = size.width / 2;
        helloLabel.y = 0;
        // add the label as a child to this layer
        this.addChild(helloLabel, 5);

        // add "HelloWorld" splash screen"
        this.sprite = new cc.Sprite(res.HelloWorld_png);
        this.sprite.attr({
            x: size.width / 2,
            y: size.height / 2,
            scale: 0.5,
            rotation: 180
        });
        this.addChild(this.sprite, 0);

        this.sprite.runAction(
            cc.sequence(
                cc.rotateTo(2, 0),
                cc.scaleTo(2, 1, 1)
            )
        );
        helloLabel.runAction(
            cc.spawn(
                cc.moveBy(2.5, cc.p(0, size.height - 40)),
                cc.tintTo(2.5, 255, 125, 0)
            )
        );

        this.scheduleUpdate();
        g_mainLogic = this;
        this.MyInit();

        return true;
    },

    update: function (dt) {
        this._super(dt);
        var cell = null;

//        g_timestamp = +new Date; // +new Date 为新的时间戳

        this.BuildQTree(); // 构造四叉树,每次循环都要重新构造一遍

        for (var i = 0; i < this.m_cells.length; i++) {
            cell = this.m_cells[i];
            cell.DrawOneCell(cell.m_drawNode); // 更新细胞的节点位置和绘制细胞
        }
    },

    BuildQTree: function () {
        var a = Number.POSITIVE_INFINITY; // minX
        var b = Number.POSITIVE_INFINITY; // minY
        var c = Number.NEGATIVE_INFINITY; // maxX
        var d = Number.NEGATIVE_INFINITY; // maxY
        var e = 0; // maxRadius

        var cell = null;
        var posX = 0;
        var posY = 0;

        for (var i = 0; i < this.m_cells.length; i++) {
            cell = this.m_cells[i];
            if (cell.ShouldRender()) { // 判断条件还会扩展 //...
                a = Math.min(cell.m_x, a);
                b = Math.min(cell.m_y, b);
                c = Math.max(cell.m_x, c);
                d = Math.max(cell.m_y, d);
                e = Math.max(cell.m_size, e);
            }
        }
        g_qTree = Quad.init({
            minX: a - (e + 100),
            minY: b - (e + 100),
            maxX: c + (e + 100),
            maxY: d + (e + 100),
            maxChildren: 2,
            maxDepth: 4
        });
        for (var i = 0; i < this.m_cells.length; i++) {
            cell = this.m_cells[i];
            if (cell.ShouldRender()) { // 判断条件还会扩展 //...
                for (var j = 0; j < cell.m_points.length; j++) {
                    posX = cell.m_points[j].x;
                    posY = cell.m_points[j].y;
                    if (true) { // 判断条件还会扩展 //...
                        g_qTree.insert(cell.m_points[j]);
                    }
                }
            }
        }
    },

    MyInit: function () {
        var winSize = cc.winSize;
        var cell = null;
        var drawNode = null;

        this.m_cells = [];

        // 创建病毒
        cell = new Cell();
        cell.m_isVirus = true;
        cell.m_size = 100;
        cell.m_pointsNum = 128;
        cell.m_x = 120;
        cell.m_y = 120;
        drawNode = new cc.DrawNode();
        this.addChild(drawNode, 100);
        cell.m_drawNode = drawNode;
        this.m_cells.push(cell);

        // 创建细胞
        cell = new Cell();
        cell.m_isVirus = false;
        cell.m_size = 50;
        cell.m_pointsNum = 64;
        cell.m_x = 240;
        cell.m_y = 80;
        drawNode = new cc.DrawNode();
        this.addChild(drawNode, 90);
        cell.m_drawNode = drawNode;
        this.m_cells.push(cell);

        // 创建细胞
        cell = new Cell();
        cell.m_isVirus = false;
        cell.m_size = 120;
        cell.m_pointsNum = 128;
        cell.m_x = 400;
        cell.m_y = 300;
        drawNode = new cc.DrawNode();
        this.addChild(drawNode, 110);
        cell.m_drawNode = drawNode;
        this.m_cells.push(cell);

        // 创建细胞
        cell = new Cell();
        cell.m_isVirus = false;
        cell.m_size = 50;
        cell.m_pointsNum = 64;
        cell.m_x = 400;
        cell.m_y = 160;
        drawNode = new cc.DrawNode();
        this.addChild(drawNode, 90);
        cell.m_drawNode = drawNode;
        this.m_cells.push(cell);
    }
});


var HelloWorldScene = cc.Scene.extend({
    onEnter: function () {
        this._super();
        var layer = new HelloWorldLayer();
        this.addChild(layer);
    }
});


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值