代码取自 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);
}
});