AdvancED ActionScript 3.0 Animation 是Keith Peters大师继"Make Things Move"之后的又一力作,网上已经有中文翻译版本了,打算下一阶段开始啃这本书。

今天开始学习高级碰撞检测,所用到的预备知识:

原作者:菩提树下的杨过
出处:http://yjmyzz.cnblogs.com

1、BitmapData的透明与不透明区别

位图数据(BitmapData)有二种模式,一种支持透明(即每个像素的值采用AARRGGBB这种32位颜色表示);另一种不支持透明度(即传统的RRGGBB这种24位颜色表示,简单点讲就是alpha分量默认为FF,且不能修改),下面这个示例说明了区别:

 package {
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.geom.Rectangle; [SWF(height="400",width="300")]
public class BitmapCompare extends Sprite {
public function BitmapCompare() {
stage.align=StageAlign.TOP_LEFT;
stage.scaleMode=StageScaleMode.NO_SCALE; //随机在舞台上划一些线条
graphics.lineStyle(0);
for (var i:int=0; i<100; i++) {
graphics.lineTo(Math.random()*300,Math.random()*400);
} //创建一个不透明的位图
var bmpd1:BitmapData=new BitmapData(300,200,false,0xffff99);
bmpd1.fillRect(new Rectangle(100,50,100,100),0xff0000);//注意:因为不透明的,所以颜色是24位的,没有alpha分量
var bmp1:Bitmap=new Bitmap(bmpd1);
addChild(bmp1); //创建一个支持透明的位图
var bmpd2:BitmapData=new BitmapData(300,200,true,0x80ffff99);//注:默认为50%透明的ff9颜色
bmpd2.fillRect(new Rectangle(100,50,100,100),0x80ff0000);//注:此处为32位颜色
var bmp2:Bitmap=new Bitmap(bmpd2);
bmp2.y=200;
addChild(bmp2);
}
}
}

可以看到,上半部分的位图因为不支持透明,所以将背后的线条全部挡住了。

2、五角星的画法

先来看一个beginFill方法的神奇之处

 graphics.lineStyle(0);
graphics.beginFill(0xffff99);
graphics.moveTo(10,10);
//注意下面只划了二条边
graphics.lineTo(10,100);
graphics.lineTo(100,100);
//graphics.lineTo(10,10); //注:正是因为上面的graphics.beginFill(0xffff99);所以这条线flash会为我们自动补齐

注意:虽然只画了二条线,但由于应用了begeinFill方法,flash自动生成了第三条线,形成了一个封闭的三角形.

回到正题,将一个圆周等分为10份,然后交替用不同的半径值结合三角函数,就能画出一个五角星

 var angleBase:Number=Math.PI*2/10;
var radius:uint=100;
var r2:uint;
var i:uint;
var px,py:Number; var starline:Sprite = new Sprite();
starline.graphics.lineStyle(0);
for (i=0; i<10; i++) {
r2=radius;
if (i%2==0) {
r2=radius/2;
}
starline.graphics.moveTo(0,0);
px=r2*Math.cos(angleBase*i);
py=r2*Math.sin(angleBase*i);
starline.graphics.lineTo(px,py);
} addChild(starline);
starline.x=stage.stageWidth/2;
starline.y=stage.stageHeight/2; var star:Sprite = new Sprite();
star.graphics.lineStyle(1,0xff0000);
star.graphics.beginFill(0xffff99);
star.graphics.moveTo(radius/2,0);
for (i=0; i<10; i++) {
r2=radius;
if (i%2==0) {
r2=radius/2;
}
px=r2*Math.cos(angleBase*i);
py=r2*Math.sin(angleBase*i);
star.graphics.lineTo(px,py); }
//star.graphics.lineTo(radius/2,0);
addChild(star);
star.x=stage.stageWidth/2;
star.y=stage.stageHeight/2;
star.alpha = 0.5;

当然,封装成一个单独的类会更好,下面是Star.as的完整代码,以后会经常用到这个类

3、矩阵的运用(将上面的五角星转化为BitmapData)

可能有人注意到了,上面的五角星图形,其注册中心点是五角星正中心,所以直接用bitmapData的draw把它画出来,将只能显示一部分:

 var bmd1:BitmapData = new BitmapData(100,100,false,0xffefefef);
bmd1.draw(star1);
var bmp1:Bitmap = new Bitmap(bmd1);
addChild(bmp1);
bmp1.x = bmp1.y = 10; var bmd2:BitmapData = new BitmapData(100,100,false,0xffefefef);
var m:Matrix = new Matrix();
trace(m.a,m.b,m.c,m.d,m.tx,m.ty);//1 0 0 1 0 0
m.tx = 50;
m.ty = 50;
trace(m.a,m.b,m.c,m.d,m.tx,m.ty);//1 0 0 1 50 50
bmd2.draw(star1,m);
//等效于
//bmd2.draw(star1,new Matrix(1,0,0,1,50,50))
var bmp2:Bitmap = new Bitmap(bmd2);
addChild(bmp2);
bmp2.x = bmp1.x + 110;
bmp2.y = bmp1.y;

如上,为了能完整的用位图"画"出五星,需要将星星向左、向下移动一定的位置,即前面提到的矩阵变换

ok,下面才是真正的开始,先来看下位图之间的碰撞检测:

 package {
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.MouseEvent;
import flash.filters.GlowFilter;
import flash.geom.Matrix;
import flash.geom.Point;
public class BitmapCollision1 extends Sprite { private var bmpd1:BitmapData;
private var bmp1:Bitmap;
private var bmpd2:BitmapData;
private var bmp2:Bitmap; public function BitmapCollision1() {
stage.align=StageAlign.TOP_LEFT;
stage.scaleMode=StageScaleMode.NO_SCALE;
var matrix:Matrix = new Matrix();
var radius:uint = 50;
matrix.tx = radius;
matrix.ty = radius;
var star:Star=new Star(radius); bmpd1=new BitmapData(100,100,true,0);
bmpd1.draw(star,matrix);
bmp1=new Bitmap(bmpd1);
bmp1.x=200;
bmp1.y=200;
addChild(bmp1); bmpd2=new BitmapData(100,100,true,0);
bmpd2.draw(star,matrix);
bmp2=new Bitmap(bmpd2);
addChild(bmp2); stage.addEventListener(MouseEvent.MOUSE_MOVE,onMouseMoving);
} private function onMouseMoving(event:MouseEvent):void { bmp2.x=mouseX-50;
bmp2.y=mouseY-50; if (bmpd1.hitTest(new Point(bmp1.x,bmp1.y),255,bmpd2,new Point(bmp2.x,bmp2.y),255)) {
bmp1.filters=[new GlowFilter];
bmp2.filters=[new GlowFilter];
} else {
bmp1.filters=[];
bmp2.filters=[];
}
}
}
}

这里我们用二个BitmapData“画”出二个星星,再进一步得到二个Bitmap,并加入舞台上。然后调用BitmapData的hitTest方法,检测二个星星之间的碰撞。

注意这里的:if (bmpd1.hitTest(new Point(bmp1.x,bmp1.y),255,bmpd2,new Point(bmp2.x,bmp2.y),255)) {

对于这二个星星而言,画到的地方便是完整不透明,没画到的空白地方即是完整透明(不存在类似渐变中的过渡情况),这里的二个255,代表检测时的alpha分量依据,通俗点讲:即只有完全不透明的地方碰到了,才返回true。

为了对histTest方法中“alpha分量参数”有更好的理解,上面的示例可改进为下面这样:

 package {
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.GradientType;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.MouseEvent;
import flash.filters.GlowFilter;
import flash.geom.Matrix;
import flash.geom.Point;
import fl.events.SliderEvent; public class BitmapCollision2 extends Sprite {
private var bmpd1:BitmapData;
private var bmp1:Bitmap;
private var bmpd2:BitmapData;
private var bmp2:Bitmap;
public function BitmapCollision2() {
stage.align=StageAlign.TOP_LEFT;
stage.scaleMode=StageScaleMode.NO_SCALE; var star:Star=new Star(50); var matrix:Matrix = new Matrix();
matrix.createGradientBox(100, 100, 0, -50, -50); var circle:Sprite = new Sprite();
//画一个渐变填充的圆
circle.graphics.beginGradientFill(GradientType.RADIAL,[0, 0],[1, 0],[0, 255],matrix);
circle.graphics.drawCircle(0, 0, 50);
circle.graphics.endFill(); bmpd1=new BitmapData(100,100,true,0);
bmpd1.draw(star, new Matrix(1, 0, 0, 1, 50, 50));
bmp1=new Bitmap(bmpd1);
bmp1.x=stage.stageWidth/2 - bmp1.width/2;
bmp1.y=stage.stageHeight/2 - bmp1.height/2;
addChild(bmp1); bmpd2=new BitmapData(100,100,true,0);
bmpd2.draw(circle, new Matrix(1, 0, 0, 1, 50, 50));
bmp2=new Bitmap(bmpd2);
addChild(bmp2);
stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMoving); //slider1,slider2是舞台上用设计工具拖出来的二个滑动控件
slider2.addEventListener(SliderEvent.THUMB_DRAG,slider2Change);
slider1.addEventListener(SliderEvent.THUMB_DRAG,slider1Change); } private function slider1Change(e:SliderEvent):void
{
txt1.text = e.value.toString();
} private function slider2Change(e:SliderEvent):void
{
txt2.text = e.value.toString();
} private function onMouseMoving(event:MouseEvent):void { if (mouseY>320){return;}//防止小球拖到太下面,挡住了滑块 bmp2.x=mouseX-50;
bmp2.y=mouseY-50; if (bmpd1.hitTest(new Point(bmp1.x,bmp1.y),slider1.value,bmpd2,new Point(bmp2.x,bmp2.y),slider2.value)) {
bmp1.filters = [new GlowFilter()];
bmp2.filters = [new GlowFilter()];
} else {
bmp1.filters=[];
bmp2.filters=[];
}
}
}
}

调整第二个滑块,然后再测试碰撞效果,体会alpha参数在其中的作用,值得一提的是:因为星星没有类似渐变的填充,要么透明,要么不透明,所以第一个滑块在1-255之间的值,对碰撞结果没有影响,除非设置为0才会有变化.(设置为0时,相当于把星星所对应的矩形边界当做整体在检测)

通常在实际应用中,可能舞台上更多的是movieClip或sprite,而不是bitmap对象,如果您已经看懂了上面的二个示例,相信“对于MovieClip/Sprite之间的精确碰撞检测”也一定有思路了:构造对应的BitmapData,然后将movieclip或sprite,draw到bitmapData中,然后参考上面的代码处理。

不过,这里有一个小技巧:因为我们最终需要的可能只是碰撞检测的结果,而并不是真的想要在舞台上显示Bitmap,所以在实际操作中,bitmapData甚至都不用加入到显示列表

 package {
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.MouseEvent;
import flash.filters.GlowFilter;
import flash.geom.Matrix;
import flash.geom.Point;
public class BitmapCollision3 extends Sprite {
private var bmpd1:BitmapData;
private var bmpd2:BitmapData;
private var star1:Star;
private var star2:Star;
public function BitmapCollision3() {
stage.align=StageAlign.TOP_LEFT;
stage.scaleMode=StageScaleMode.NO_SCALE;
star1=new Star(50);
addChild(star1);
star2=new Star(50);
star2.x=200;
star2.y=200;
addChild(star2);
bmpd1=new BitmapData(stage.stageWidth,stage.stageHeight,true,0);
bmpd2=bmpd1.clone();
stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMoving);
//注:这里bmpd1,bmpd2都没被转成bitmap,更没有加入到舞台中.
}
private function onMouseMoving(event:MouseEvent):void { star1.x=mouseX;
star1.y=mouseY;
//先清空bitmapData中的数据,准备一个完全透明的黑"底板"。
bmpd1.fillRect(bmpd1.rect, 0);
bmpd2.fillRect(bmpd2.rect, 0);
//再把要检测的(movieclip或sprite)对象,画到里面.
bmpd1.draw(star1, new Matrix(1, 0, 0, 1, star1.x, star1.y));
bmpd2.draw(star2, new Matrix(1, 0, 0, 1, star2.x, star2.y));
//碰撞检测
//注意:因为bmpd1,bmpd2都没被加入到舞台上,所以默认都在同样的0坐标位置,因此下面的坐标,直接用默认的Point对象实例即可.
if (bmpd1.hitTest(new Point(), 255, bmpd2, new Point(), 255)) {
star1.filters = [new GlowFilter()];
star2.filters = [new GlowFilter()];
} else {
star1.filters=[];
star2.filters=[];
}
}
}
}

最终的运行效果,跟之前的示例没有区别,就不重复贴出了

继续,考虑更复杂的大量对象的碰撞问题,前一阵我们刚学习过“Flash/Flex学习笔记(41):碰撞检测”,但是没有考虑到大量对象时的性能问题。

计算一下:10个物体处理碰撞时,每个物体都要与其它物体做碰撞检测,最终需要的处理次数为 10*9/2 = 45次(数学中的组合问题) ;如果100个物体,就要处理 100*99/2 = 4950次!

这么大的计算量,每一帧都要处理一遍,AS3.0性能再强也是撑不住的!

但实际上,我们静下心来想想:大量对象随机分布在舞台上,实际上每个对象只有可能与自身附近的对象发生碰撞,对于那边离自己很远,甚至八杆子打不着的对象,根本没必要跟他们做碰撞检测计算。所以,其实真正需要的计算量应该可以减少很多!

如上图,首先可以先将舞台看成一个网格(每个单元格的大小,至少要大于舞台上尺寸最大的对象,即至少要能容纳下块头最大的一个对象)

这样的话,每个对象都会被划分到对应的格子里,而且只有可能与“身处在同一个格子里的其它对象”以及“相临格子里的其它对象”发生碰撞。

我们用遍历的思路(从左向右,从上到下)来分析一下:

先从第一行第一列开始(如上图中的第一排第一个示例),黑色的表示当前要考虑的单元格,很明显:在行1列1的位置,可能与之发生碰撞只有相临的浅灰色单元格,其它白色单元格是不可能与它发生碰撞的。

继续向右走,到了上图中第一排第二个小图的位置,这里能够与它发生单元格的只有其它4个浅灰色单元格(注:左侧的单元格在前面的检测中已经处理过了,所以这里就可以无视左侧相临的单元格!)

同理,继续向右,直到第一行全部遍历完成。

再继续向下,考查第二行:

因为第一行已经全部处理过了,所以在考查第二行时,可以继续无视上面的单元格,同时再忽略左侧的单元格(道理与第一行相同)

如此这般... 直到最后一行最后一列全部考查完毕。

总结:从刚才的分析可以知道,不管在哪一行哪一列,最多只需要关注(包含自身的)5个单元格--自身、右侧、下侧、左下、右下。

为了方便起见,我们还是用小球来做为基本对象,下面是Ball.cs的代码(相对以前的写法而言,更加OO了)

 package {
import flash.display.Sprite;
public class Ball extends Sprite {
private var _color:uint;
private var _radius:Number;
private var _vx:Number=0;
private var _vy:Number=0;
public function Ball(radius:Number, color:uint = 0xffffff) {
_radius=radius;
_color=color;
draw();
}
private function draw():void { graphics.clear();
graphics.lineStyle(0);
graphics.beginFill(_color, 1);
graphics.drawCircle(0, 0, _radius);
graphics.endFill();
graphics.drawCircle(0, 0, 1);//在中心画一个点
}
public function update():void {
x+=_vx;
y+=_vy;
}
public function set color(value:uint):void {
_color=value;
draw();
}
public function get color():uint {
return _color;
}
public function set radius(value:Number):void {
_radius=value;
draw();
}
public function get radius():Number {
return _radius;
}
public function set vx(value:Number):void {
_vx=value;
}
public function get vx():Number {
return _vx;
}
public function set vy(value:Number):void {
_vy=value;
}
public function get vy():Number {
return _vy;
}
}
}

ok,下面是完整的代码,请大家在仔细阅读/调试后,重点比较一下100个小球处理完毕所用的总次数。

 package {
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.MouseEvent;
import flash.text.TextField; public class GridCollision extends Sprite { private const GRID_SIZE:Number=30;//单元格大小(这里设置为小于的直径,即正好容纳一个小球)
private const RADIUS:Number=15;//小球的半径
private var _balls:Array;
private var _grid:Array;
private var _numBalls:int=100;//小球数量
private var _numChecks:int=0;//检测次数
private var _txt:TextField = new TextField(); public function GridCollision() {
stage.align=StageAlign.TOP_LEFT;
stage.scaleMode=StageScaleMode.NO_SCALE;
makeBalls();//创建一堆小球
makeGrid();//
drawGrid();
assignBallsToGrid();
checkGrid(); //显示计数器
trace(_numChecks);
addChild(_txt);
_txt.background = true;
_txt.backgroundColor=0xffff99;
_txt.height = 20;
_txt.width = 30;
_txt.alpha = 0.7; stage.addEventListener(MouseEvent.MOUSE_DOWN,mouseDownClick);
} private function mouseDownClick(e:MouseEvent):void{
for (var i:int=0; i<_numBalls; i++) {
var ball:Ball=_balls[i];
ball.x=Math.random()*stage.stageWidth;
ball.y=Math.random()*stage.stageHeight;
ball.color = 0xffffff;
}
_numChecks=0;
makeGrid();//
drawGrid();
assignBallsToGrid();
checkGrid(); } //创建_numBalls个小球实例,并随机摆放到舞台上
private function makeBalls():void {
_balls=new Array ;
for (var i:int=0; i<_numBalls; i++) {
var ball:Ball=new Ball(RADIUS);
ball.x=Math.random()*stage.stageWidth;
ball.y=Math.random()*stage.stageHeight;
addChild(ball);
_balls.push(ball);
}
} private function makeGrid():void {
_grid=new Array ; for (var i:int=0; i<stage.stageWidth/GRID_SIZE; i++) {//计算网格列数
_grid[i]=new Array ; for (var j:int=0; j<stage.stageHeight/GRID_SIZE; j++) {//计算网格行数
_grid[i][j]=new Array ;//每个单元格对应一个数组(用来存放该单元格中的小球)
}
}
} private function drawGrid():void {
// 画出行列线
graphics.lineStyle(0,.5);
for (var i:int=0; i<=stage.stageWidth; i+=GRID_SIZE) {
graphics.moveTo(i,0);
graphics.lineTo(i,stage.stageHeight);
}
for (i=0; i<=stage.stageHeight; i+=GRID_SIZE) {
graphics.moveTo(0,i);
graphics.lineTo(stage.stageWidth,i);
}
} private function assignBallsToGrid():void {
for (var i:int=0; i<_numBalls; i++) {
// 球的位置除以格子大小,得到该球所在网格的行列数
var ball:Ball=_balls[i] as Ball;
var xpos:int=Math.floor(ball.x/GRID_SIZE);
var ypos:int=Math.floor(ball.y/GRID_SIZE);
_grid[xpos][ypos].push(ball);//将小球推入对应单元格数组
}
} private function checkGrid():void {
for (var i:int=0; i<_grid.length; i++) {
for (var j:int=0; j<_grid[i].length; j++) { checkOneCell(i,j);//单元格cell_self自身的碰撞检测
checkTwoCells(i,j,i+1,j);//单元格cell_self与单元格cell_right(右侧)的碰撞检测
checkTwoCells(i,j,i-1,j+1);//单元格cell_self与单元格cell_left_bottom(左下角)的碰撞检测
checkTwoCells(i,j,i,j+1);//单元格cell_self与单元格cell_bottom(下侧)的碰撞检测
checkTwoCells(i,j,i+1,j+1);//单元格cell_self与单元格cell_right_bottom(右下角)的碰撞检测
}
}
} //cellSelf与自身的检测
private function checkOneCell(x:int,y:int):void {
// 检测当前格子内所有的对象
var cell:Array=_grid[x][y] as Array;
for (var i:int=0; i<cell.length-1; i++) {
var ballA:Ball=cell[i] as Ball;
for (var j:int=i+1; j<cell.length; j++) {
var ballB:Ball=cell[j] as Ball;
checkCollision(ballA,ballB);
}
}
} //cellSelf与其它单元格的检测
private function checkTwoCells(x1:int,y1:int,x2:int,y2:int):void {
//确保要检测的格子存在
if (x2<0) {
return;
}
if (x2>=_grid.length) {
return;
}
if (y2>=_grid[x2].length) {
return;
}
var cell0:Array=_grid[x1][y1] as Array;
var cell1:Array=_grid[x2][y2] as Array; // 检测当前格子和邻接格子内所有的对象
for (var i:int=0; i<cell0.length; i++) {
var ballA:Ball=cell0[i] as Ball;
for (var j:int=0; j<cell1.length; j++) {
var ballB:Ball=cell1[j] as Ball;
checkCollision(ballA,ballB);
}
}
} private function checkCollision(ballA:Ball,ballB:Ball):void {
// 判断距离的碰撞检测
_numChecks++;//计数器累加
_txt.text = _numChecks.toString();
var dx:Number=ballB.x-ballA.x;
var dy:Number=ballB.y-ballA.y;
var dist:Number=Math.sqrt(dx*dx+dy*dy);
if (dist<ballA.radius+ballB.radius) {
//碰撞的小球变红色
ballA.color=0xff0000;
ballB.color=0xff0000;
} }
}
}


在线演示

上面的示例中,左上角的textField显示的是处理总次数(可以看到,大概在100-150次之间,这比优化前的理论值100*99/2 = 4950减少了90%都不止!)

需要指出的是:计算次数具体能减少多少,取决于网络(单元格)大小、flash舞台(场景)大小、对象个数、对象的大小;改变其中一个或几个参数,上面的测试结果都将改变。

再来认真的考虑一下性能问题,虽然用网格算法有效减少了计算次数,但是却多出了创建网格,把对象分配进单元格,遍历网络等操作,这些处理也同样要占用CPU资源,那么到底这些多余的操作影响多大?(或者也可能理解为在什么情况下,网络算法相对传统的(基于每两个对象之间的)两两检测更适用)

 package {
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.MouseEvent;
import flash.text.TextField;
import fl.controls.Slider;
import flash.utils.getTimer;
import fl.events.SliderEvent; public class GridCollision extends Sprite { private const GRID_SIZE:Number=20;//单元格大小(这里设置为小于的直径,即正好容纳一个小球)
private const RADIUS:Number=10;//小球的半径
private var _balls:Array;
private var _grid:Array;
private var _numBalls:int=50;//小球数量
private var _txtGrid:TextField = new TextField();
private var _txtBasic:TextField = new TextField();
private var _slider:Slider = new Slider(); public function GridCollision() {
stage.align=StageAlign.TOP_LEFT;
stage.scaleMode=StageScaleMode.NO_SCALE; makeGrid();
drawGrid(); addChild(_slider);
addChild(_txtGrid);
addChild(_txtBasic); test(); _slider.addEventListener(SliderEvent.THUMB_DRAG,sliderGrag);
stage.addEventListener(MouseEvent.CLICK,stageClick); } private function test(isClear:Boolean=false):void {
var i:int=0;
if (isClear) {
for (i=numChildren-1; i>=0; i--) {
removeChild(getChildAt(i));
}
_balls.length=0;
}
_txtGrid.background=_txtBasic.background=true;
_txtGrid.backgroundColor=_txtBasic.backgroundColor=0xffff99;
_txtBasic.height=_txtGrid.height=20;
_txtBasic.width=_txtGrid.width=135;
_txtBasic.alpha=_txtGrid.alpha=0.9;
_txtBasic.x=stage.stageWidth-_txtBasic.width;
_slider.maximum=300;
_slider.minimum=30;
_slider.snapInterval=10;
_slider.y=10;
_slider.value=_numBalls;
_slider.width=200;
_slider.x=stage.stageWidth/2-_slider.width/2; makeBalls();//创建一堆小球 var startTime:int;
var elapsed:int; startTime=getTimer();
for (i=0; i<10; i++) {
makeGrid();
assignBallsToGrid();
checkGrid();
}
elapsed=getTimer()-startTime;
trace("网格检测:",elapsed);
_txtGrid.text=_numBalls+"个球网络检测:"+elapsed.toString(); startTime=getTimer();
for (i=0; i<10; i++) {
basicCheck();
}
elapsed=getTimer()-startTime;
trace("两两检测:",elapsed);
_txtBasic.text=_numBalls+"个球两两检测:"+elapsed.toString(); if (isClear) {
addChild(_txtBasic);
addChild(_txtGrid);
addChild(_slider);
}
} private function sliderGrag(e:SliderEvent):void {
_numBalls=e.value;
trace("sliderGrag");
} private function stageClick(e:MouseEvent):void {
trace("stageClick");
test(true);
} //创建_numBalls个小球实例,并随机摆放到舞台上
private function makeBalls():void {
_balls=new Array ;
for (var i:int=0; i<_numBalls; i++) {
var ball:Ball=new Ball(RADIUS);
ball.x=Math.random()*stage.stageWidth;
ball.y=Math.random()*stage.stageHeight;
addChild(ball);
ball.alpha=0.5;
_balls.push(ball);
}
} private function makeGrid():void {
_grid=new Array ;
for (var i:int=0; i<stage.stageWidth/GRID_SIZE; i++) {//计算网格列数
_grid[i]=new Array ;
for (var j:int=0; j<stage.stageHeight/GRID_SIZE; j++) {//计算网格行数
_grid[i][j]=new Array ;//每个单元格对应一个数组(用来存放该单元格中的小球)
}
}
} private function drawGrid():void {
// 画出行列线
graphics.lineStyle(0,0x999999);
for (var i:int=0; i<=stage.stageWidth; i+=GRID_SIZE) {
graphics.moveTo(i,0);
graphics.lineTo(i,stage.stageHeight);
}
for (i=0; i<=stage.stageHeight; i+=GRID_SIZE) {
graphics.moveTo(0,i);
graphics.lineTo(stage.stageWidth,i);
}
} private function assignBallsToGrid():void {
for (var i:int=0; i<_numBalls; i++) {
// 球的位置除以格子大小,得到该球所在网格的行列数
var ball:Ball=_balls[i] as Ball;
var xpos:int=Math.floor(ball.x/GRID_SIZE);
var ypos:int=Math.floor(ball.y/GRID_SIZE);
_grid[xpos][ypos].push(ball);//将小球推入对应单元格数组
}
} private function checkGrid():void {
for (var i:int=0; i<_grid.length; i++) {
for (var j:int=0; j<_grid[i].length; j++) {
checkOneCell(i,j);//单元格cell_self自身的碰撞检测
checkTwoCells(i,j,i+1,j);//单元格cell_self与单元格cell_right(右侧)的碰撞检测
checkTwoCells(i,j,i-1,j+1);//单元格cell_self与单元格cell_left_bottom(左下角)的碰撞检测
checkTwoCells(i,j,i,j+1);//单元格cell_self与单元格cell_bottom(下侧)的碰撞检测
checkTwoCells(i,j,i+1,j+1);//单元格cell_self与单元格cell_right_bottom(右下角)的碰撞检测
}
}
} //cellSelf与自身的检测
private function checkOneCell(x:int,y:int):void {
// 检测当前格子内所有的对象
var cell:Array=_grid[x][y] as Array;
for (var i:int=0; i<cell.length-1; i++) {
var ballA:Ball=cell[i] as Ball;
for (var j:int=i+1; j<cell.length; j++) {
var ballB:Ball=cell[j] as Ball;
checkCollision(ballA,ballB);
}
}
} //cellSelf与其它单元格的检测
private function checkTwoCells(x1:int,y1:int,x2:int,y2:int):void {
//确保要检测的格子存在
if (x2<0) {
return;
}
if (x2>=_grid.length) {
return;
}
if (y2>=_grid[x2].length) {
return;
}
var cell0:Array=_grid[x1][y1] as Array;
var cell1:Array=_grid[x2][y2] as Array; // 检测当前格子和邻接格子内所有的对象
for (var i:int=0; i<cell0.length; i++) {
var ballA:Ball=cell0[i] as Ball;
for (var j:int=0; j<cell1.length; j++) {
var ballB:Ball=cell1[j] as Ball;
checkCollision(ballA,ballB);
}
}
} private function checkCollision(ballA:Ball,ballB:Ball):void {
// 判断距离的碰撞检测
var dx:Number=ballB.x-ballA.x;
var dy:Number=ballB.y-ballA.y;
var dist:Number=Math.sqrt(dx*dx+dy*dy);
if (dist<ballA.radius+ballB.radius) {
//碰撞的小球变红色
ballA.color=0xff0000;
ballB.color=0xff0000;
}
} //(最原始的)两两检测
private function basicCheck():void {
for (var i: int=0; i < _balls.length - 1; i++) {
var ballA:Ball=_balls[i] as Ball;
for (var j: int=i+1; j < _balls.length; j++) {
var ballB:Ball=_balls[j] as Ball;
checkCollision(ballA, ballB);
}
}
}
}
}


在线演示

上面这个示例,我们把"网格检测算法"与传统的"两两检测算法"每个跑10次,然后输出所用的时间来进行比较,拖动滑块可以调整小球的数量,点击舞台可以重新计算。

反复比较可以发现,在小球数量接近100时,二种算法性能已经相差无已,在小球数量大于100的前提下,小球数量越多,网格算法性能越有优势。在对象数量较少的情况下,传统的两两检测算法反而更快!

所以网格算法仅适用于大量对象的碰撞检测!

如果考虑到代码重用,可以把这种算法封装一下:

 package {
import flash.display.DisplayObject;
import flash.display.Graphics;
import flash.events.EventDispatcher; public class CollisionGrid extends EventDispatcher { private var _checks:Vector.<DisplayObject>;//用于保存需要碰撞检测的对象(注:Vector.<T>相当于c#中的泛型数组)
private var _grid:Vector.<Vector.<DisplayObject>>;//网格(注:这里用“一维数组套一维数组”的方法替代了原来的二维数组)
private var _gridSize:Number;
private var _height:Number;
private var _numCells:int;
private var _numCols:int;
private var _numRows:int;
private var _width:Number; public function CollisionGrid(width:Number, height:Number, gridSize:Number) {
_width=width;
_height=height;
_gridSize=gridSize; _numCols=Math.ceil(_width/_gridSize);//计算总列数
_numRows=Math.ceil(_height/_gridSize);//计算总行数
_numCells=_numCols*_numRows;//单元格总数
} //画格子
public function drawGrid(graphics:Graphics):void {
graphics.lineStyle(0, .5);
for (var i:int = 0; i <= _width; i += _gridSize) {
graphics.moveTo(i, 0);
graphics.lineTo(i, _height);
}
for (i = 0; i <= _height; i += _gridSize) {
graphics.moveTo(0, i);
graphics.lineTo(_width, i);
}
} //将需要检测的对象(泛型)数组objects分配到网络
public function assign(objects:Vector.<DisplayObject>):void {
var numObjects:int=objects.length;
_grid=new Vector.<Vector.<DisplayObject>>(_numCells);
_checks = new Vector.<DisplayObject>();
for (var i:int = 0; i < numObjects; i++) {
var obj:DisplayObject=objects[i];
//注意:这里用“Grid.[索引]”(定位)的方式,替换了原来的“Grid.[列][行]”(单元格的定位)方式--回想一下bitmap位图中的像素索引就更容易理解了
var index:int=Math.floor(obj.y/_gridSize)*_numCols+Math.floor(obj.x/_gridSize);
//“单元格”--延时实例化"
if (_grid[index]==null) {
_grid[index]=new Vector.<DisplayObject> ;
}
//将对象推入"单元格"
_grid[index].push(obj);
} //检测需要碰撞的对象,并保存到_checks数组
checkGrid();
} //"单元格"检测
private function checkGrid():void {
for (var i:int = 0; i < _numCols; i++) {
for (var j:int = 0; j < _numRows; j++) {
checkOneCell(i, j);
checkTwoCells(i, j, i + 1, j);
checkTwoCells(i, j, i - 1, j + 1);
checkTwoCells(i, j, i, j + 1);
checkTwoCells(i, j, i + 1, j + 1);
}
}
} //(自身)单个单元格的检测
private function checkOneCell(x:int, y:int):void {
var cell:Vector.<DisplayObject>=_grid[y*_numCols+x];
if (cell==null) {
return;
}
var cellLength:int=cell.length; for (var i:int = 0; i < cellLength - 1; i++) {
var objA:DisplayObject=cell[i];
for (var j:int = i + 1; j < cellLength; j++) {
var objB:DisplayObject=cell[j];
_checks.push(objA, objB);
}
}
} //单元格(x1,y1)与单元格(x2,y2)的检测
private function checkTwoCells(x1:int, y1:int, x2:int, y2:int):void {
if (x2>=_numCols||x2<0||y2>=_numRows) {
return;
}
var cellA:Vector.<DisplayObject>=_grid[y1*_numCols+x1];
var cellB:Vector.<DisplayObject>=_grid[y2*_numCols+x2];
if (cellA==null||cellB==null) {
return;
}
var cellALength:int=cellA.length;
var cellBLength:int=cellB.length;
for (var i:int = 0; i < cellALength; i++) {
var objA:DisplayObject=cellA[i];
for (var j:int = 0; j < cellBLength; j++) {
var objB:DisplayObject=cellB[j];
_checks.push(objA, objB);
}
}
} public function get checks():Vector.<DisplayObject> {
return _checks;
}
}
}

注:除了单纯的封装以外,上面的代码还有三个重要的优化措施

1.用Vector(泛型数组)代替了Array数组

2.用一维数组嵌套取代了原来的二维数组

3.延时实例化避免了创建无用的"单元格"

用封装并优化后的代码重新测试下:

 package {
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.utils.getTimer;
import flash.display.DisplayObject;
import flash.events.MouseEvent;
import flash.text.TextField; public class GridCollision2 extends Sprite {
private const GRID_SIZE:Number=20;
private const RADIUS:Number=10;
private var _balls:Vector.<DisplayObject>;//这里用Vector代替了Array
private var _grid:CollisionGrid;
private var _numBalls:int=50;
private var _text:TextField; public function GridCollision2() {
stage.align=StageAlign.TOP_LEFT;
stage.scaleMode=StageScaleMode.NO_SCALE;
_text = new TextField();
_text.background = true;
_text.backgroundColor = 0xffff99;
_text.width = 135;
_text.height = 20;
_text.alpha = 0.9; _grid=new CollisionGrid(stage.stageWidth,stage.stageHeight,GRID_SIZE);
_grid.drawGrid(graphics); makeBalls();
addChild(_text);
test(); stage.addEventListener(MouseEvent.CLICK,stageClick);
} private function stageClick(e:MouseEvent):void {
test(true);
} private function test(isRestart:Boolean=false):void {
if (isRestart) {
for (var i:int=0; i<_numBalls; i++) {
var ball:Ball=_balls[i] as Ball;
ball.x=Math.random()*stage.stageWidth;
ball.y=Math.random()*stage.stageHeight;
ball.color = 0xffffff;
}
} var startTime:int;
var elapsed:int;
startTime=getTimer();
for (i=0; i<10; i++) {
_grid.assign(_balls);//将所有需要检测的ball放入_grid.checks
var numChecks:int=_grid.checks.length;
for (var j:int=0; j<numChecks; j+=2) {
checkCollision(_grid.checks[j] as Ball,_grid.checks[j+1] as Ball);
}
}
elapsed=getTimer()-startTime;
trace("Elapsed:",elapsed);
_text.text = _numBalls + "个小球碰撞检测:" + elapsed.toString();
} //初始化小球实例
private function makeBalls():void {
_balls=new Vector.<DisplayObject>(_numBalls);
for (var i:int=0; i<_numBalls; i++) {
var ball:Ball=new Ball(RADIUS);
ball.x=Math.random()*stage.stageWidth;
ball.y=Math.random()*stage.stageHeight;
ball.alpha = 0.8;
addChild(ball);
_balls[i]=ball;
}
} //检测碰撞
private function checkCollision(ballA:Ball,ballB:Ball):void {
var dx:Number=ballB.x-ballA.x;
var dy:Number=ballB.y-ballA.y;
var dist:Number=Math.sqrt(dx*dx+dy*dy);
if (dist<ballA.radius+ballB.radius) {
//(碰撞后的小球变红色)
ballA.color=0xff0000;
ballB.color=0xff0000;
}
}
}
}


在线演示

对比之前未封装的示例,可以发现:执行时间缩短了近一半!说明优化的效果还是很不错的。

静态的碰撞检测可能比较没劲,可以再结合以前学到的知识,让小球动起来。

 package {
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.display.DisplayObject;
import flash.events.Event; public class GridCollision3 extends Sprite {
private const GRID_SIZE:Number=20;
private const RADIUS:Number=10;
private var _balls:Vector.<DisplayObject>;
private var _grid:CollisionGrid;
private var _numBalls:int=100;
public function GridCollision3() {
stage.align=StageAlign.TOP_LEFT;
stage.scaleMode=StageScaleMode.NO_SCALE;
_grid=new CollisionGrid(stage.stageWidth,stage.stageHeight,GRID_SIZE);
_grid.drawGrid(graphics);
makeBalls();
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
function onEnterFrame(event:Event):void {
updateBalls();
_grid.assign(_balls);
var numChecks:int=_grid.checks.length;
for (var j:int = 0; j < numChecks; j += 2) {
checkCollision(_grid.checks[j] as Ball, _grid.checks[j + 1] as Ball);
}
}
private function makeBalls():void {
_balls=new Vector.<DisplayObject>(_numBalls);
for (var i:int = 0; i < _numBalls; i++) {
var ball:Ball=new Ball(RADIUS);
ball.x=Math.random()*stage.stageWidth;
ball.y=Math.random()*stage.stageHeight;
ball.vx=Math.random()*4-2;
ball.vy=Math.random()*4-2;
addChild(ball);
_balls[i]=ball;
}
}
private function updateBalls():void {
for (var i:int = 0; i < _numBalls; i++) { var ball:Ball=_balls[i] as Ball;
ball.update();
if (ball.x<RADIUS) {
ball.x=RADIUS;
ball.vx*=-1;
} else if (ball.x > stage.stageWidth - RADIUS) {
ball.x=stage.stageWidth-RADIUS;
ball.vx*=-1;
}
if (ball.y<RADIUS) {
ball.y=RADIUS;
ball.vy*=-1;
} else if (ball.y > stage.stageHeight - RADIUS) {
ball.y=stage.stageHeight-RADIUS;
ball.vy*=-1;
}
ball.color=0xffffff;
}
}
private function checkCollision(ballA:Ball, ballB:Ball):void {
var dx:Number=ballB.x-ballA.x;
var dy:Number=ballB.y-ballA.y;
var dist:Number=Math.sqrt(dx*dx+dy*dy);
if (dist<ballA.radius+ballB.radius) {
ballA.color=0xff0000;
ballB.color=0xff0000;
}
}
}
}


在线演示

当然这种网格算法不仅仅只能用于上面提供的"实打实"的碰撞,其中只要是基于距离的对象检测,它都适用。

回顾一下以前做过的节点花园 (http://www.cnblogs.com/yjmyzz/archive/2010/04/28/1723003.html)示例,当时因为粒子数量比较少,还看不出有什么性能问题,让我们把粒子数量弄得多一点,比如500,再来测试下:

 package {
import flash.display.Sprite;
import flash.display.StageScaleMode;
import flash.display.StageAlign;
import flash.events.Event;
import flash.geom.Point; [SWF(backgroundColor=0x000000,width="600",height="600",frameRate=100)]
public class NodeGardenLines extends Sprite {
private var particles:Array;
private var numParticles:uint=300;
private var minDist:Number=50;
private var springAmount:Number=.001;
public function NodeGardenLines() {
init();
}
private function init():void {
stage.scaleMode=StageScaleMode.NO_SCALE;
stage.align=StageAlign.TOP_LEFT;
particles = new Array();
for (var i:uint = 0; i < numParticles; i++) {
var particle:Ball=new Ball(2,0x00ff00,false);
particle.x=Math.random()*stage.stageWidth;
particle.y=Math.random()*stage.stageHeight;
particle.vx=Math.random()*6-3;
particle.vy=Math.random()*6-3;
addChild(particle);
particles.push(particle);
}
addEventListener(Event.ENTER_FRAME, onEnterFrame); var fps:FPSshow = new FPSshow();
addChild(fps);
}
private function onEnterFrame(event:Event):void {
graphics.clear();
for (var i:uint = 0; i < numParticles; i++) {
var particle:Ball=particles[i];
particle.x+=particle.vx;
particle.y+=particle.vy;
if (particle.x>stage.stageWidth) { particle.x=0;
} else if (particle.x < 0) {
particle.x=stage.stageWidth;
}
if (particle.y>stage.stageHeight) {
particle.y=0;
} else if (particle.y < 0) {
particle.y=stage.stageHeight;
}
}
for (i=0; i < numParticles - 1; i++) {
var partA:Ball=particles[i];
for (var j:uint = i + 1; j < numParticles; j++) {
var partB:Ball=particles[j];
spring(partA, partB);
}
}
}
private function spring(partA:Ball, partB:Ball):void {
var dx:Number=partB.x-partA.x;
var dy:Number=partB.y-partA.y;
var dist:Number=Math.sqrt(dx*dx+dy*dy);
if (dist<minDist) {
graphics.lineStyle(1, 0xffffff, 1 - dist / minDist);
graphics.moveTo(partA.x, partA.y);
graphics.lineTo(partB.x, partB.y);
var ax:Number=dx*springAmount;
var ay:Number=dy*springAmount;
partA.vx+=ax;
partA.vy+=ay;
partB.vx-=ax;
partB.vy-=ay;
}
}
}
}


在线演示

留意一下现在的帧数,下面是采用网格算法后的代码:

 package {
import flash.display.DisplayObject;
import flash.display.Sprite;
import flash.display.StageScaleMode;
import flash.display.StageAlign;
import flash.events.Event;
import flash.geom.Point; [SWF(backgroundColor=0x000000,width="600",height="600",frameRate=100)]
public class NodeGardenGrid extends Sprite {
private var particles:Vector.<DisplayObject>;
private var numParticles:uint=300;
private var minDist:Number=50;
private var springAmount:Number=.001;
private var grid:CollisionGrid; public function NodeGardenGrid() {
init();
} private function init():void {
stage.scaleMode=StageScaleMode.NO_SCALE;
stage.align=StageAlign.TOP_LEFT;
grid=new CollisionGrid(stage.stageWidth,stage.stageHeight,52);
particles = new Vector.<DisplayObject>();
for (var i:uint = 0; i < numParticles; i++) {
var particle:Ball=new Ball(2,0x00ff00,false);
particle.x=Math.random()*stage.stageWidth;
particle.y=Math.random()*stage.stageHeight;
particle.vx=Math.random()*6-3;
particle.vy=Math.random()*6-3;
addChild(particle);
particles.push(particle);
}
addEventListener(Event.ENTER_FRAME, onEnterFrame); var fps:FPSshow = new FPSshow();
addChild(fps);
} private function onEnterFrame(event:Event):void {
graphics.clear();
for (var i:uint = 0; i < numParticles; i++) {
var particle:Ball=particles[i] as Ball;
particle.x+=particle.vx;
particle.y+=particle.vy;
if (particle.x>stage.stageWidth) {
particle.x=0;
} else if (particle.x < 0) {
particle.x=stage.stageWidth;
}
if (particle.y>stage.stageHeight) {
particle.y=0;
} else if (particle.y < 0) {
particle.y=stage.stageHeight;
}
}
grid.assign(particles);
var checks:Vector.<DisplayObject>=grid.checks;
trace(checks.length);
var numChecks:int=checks.length;
for (i=0; i < numChecks; i += 2) {
var partA:Ball=checks[i] as Ball;
var partB:Ball=checks[i+1] as Ball;
spring(partA, partB);
}
} private function spring(partA:Ball, partB:Ball):void {
var dx:Number=partB.x-partA.x;
var dy:Number=partB.y-partA.y;
var dist:Number=Math.sqrt(dx*dx+dy*dy);
if (dist<minDist) {
graphics.lineStyle(1, 0x00ff00, 1 - dist / minDist);
graphics.moveTo(partA.x, partA.y);
graphics.lineTo(partB.x, partB.y);
var ax:Number=dx*springAmount;
var ay:Number=dy*springAmount;
partA.vx+=ax;
partA.vy+=ay;
partB.vx-=ax;
partB.vy-=ay;
}
}
}
}


在线演示

如果用IE的朋友,貌似弹出窗口加载flash有些问题(偶尔会引发异常),建议用firefox或chrome浏览器浏览本文。

在firefox下加速效果最为明显,比较意外的是在chrome下居然二种算法帧数相差无已。(极度怀疑google与adobe协力对chrome浏览器上的flash插件做了极大的优化)

作者:菩提树下的杨过
出处:http://yjmyzz.cnblogs.com 
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
 

“AS3.0高级动画编程”学习:第一章高级碰撞检测的更多相关文章

  1. “AS3.0高级动画编程”学习:第二章转向行为(下)

    在上一篇里,我们学习了“自主角色”的一些基本行为:寻找(seek).避开(flee).到达(arrive).追捕(pursue).躲避(evade).漫游(wander).这一篇将继续学习其它更复杂, ...

  2. “AS3.0高级动画编程”学习:第二章转向行为(上)

    因为这一章的内容基本上都是涉及向量的,先来一个2D向量类:Vector2D.as (再次强烈建议不熟悉向量运算的童鞋,先回去恶补一下高等数学-07章空间解释几何与向量代数.pdf) 原作者:菩提树下的 ...

  3. “AS3.0高级动画编程”学习:第三章等角投影(上)

    什么是等角投影(isometric)? 原作者:菩提树下的杨过出处:http://yjmyzz.cnblogs.com 刚接触这个概念时,我也很茫然,百度+google了N天后,找到了一些文章: [转 ...

  4. “AS3.0高级动画编程”学习:第四章 寻路(AStar/A星/A*)算法 (下)

    在前一部分的最后,我们给出了一个寻路的示例,在大多数情况下,运行还算良好,但是有一个小问题,如下图: 很明显,障碍物已经把路堵死了,但是小球仍然穿过对角线跑了出来! 问题在哪里:我们先回顾一下ASta ...

  5. web学习第一章

    web学习第一章   我是大概9月10日开始走上IT之路的,一开始学习了小段时间的自动化办公软件, 昨天我开始学习客户端网页编程,我了解什么是WEB,一些比较老古董的计算模式和发展历史,印象最让我深刻 ...

  6. 《JavaScript高级程序设计》笔记——第一章到第三章

    2019年,新年伊始,我打算好好重读一下<JavaScript高级程序设计>这本前端必备经典书.每天半小时. 以下内容摘自<JavaScript高级程序设计> 2019-2-1 ...

  7. oracle学习 第一章 简单的查询语句 ——03

    1.1最简单的查询语句 例 1-1 SQL> select * from emp; 例 1-1 结果 这里的 * 号表示全部的列.它与在select 之后列出全部的列名是一样的.查询语句以分号( ...

  8. C#高级编程 (第六版) 学习 第一章:.Net体系结构

    第一章 .Net体系结构 1,公共语言运行库(Common Language Runtime, CLR) .Net Framework的核心是其运行库的执行环境,称为公共语言运行库,或.Net运行库. ...

  9. JavaScript DOM编程艺术第一章:JavaScript简史

    本系列的博客是由本人在阅读<JavaScript DOM编程艺术>一书过程中做的总结.前面的偏理论部分都是书中原话,觉得有必要记录下来,方便自己翻阅,也希望能为读到本博客的人提供一些帮助, ...

随机推荐

  1. MySQL面试题中:主从同步部署介绍

    主从同步部署1.两台相同版本的mysql数据库,一台做主库,一台从库 主库开启binlog 在配置文件中的[mysqld]模块中添加log-bin=mysql-bin和server-id=1,一定要保 ...

  2. CSS的块级元素和内联元素的概念

    三生有幸,偶然之下知道了<CSS世界>这本书,让我产生了探究 CSS 的想法. 这里对 CSS 中的块级元素和内联元素的概念做一个简单的整理. 可能对于我们前端开发人员来讲,一般接触到的元 ...

  3. 微信小程序云函数Windows下安装wx-server-sdk

    第一次上传部署云函数时,会提示这个,建议在这之前先安装一下node.js. https://nodejs.org/en/ 下载nodejs,然后直接安装,在cmd控制台输入node -v和npm -v ...

  4. 安装pytorch成功但cuda不可用

    贴上我看的教程https://zhuanlan.zhihu.com/p/26871672 一开始想用pycharm装pytorch,但不知道为什么一直失败.后来只能conda pip安装 但conda ...

  5. 为什么我说IPFS社区从卖矿机开始,就是错的

    要回答这个问题,首先要了解去中心化存储项目和传统的区块链项目有什么区别.其中去中心化存储项目包括IPFS,基于IPFS的FileCoin.PPIO.Storj等. 传统区块链项目没有供需问题 首先以比 ...

  6. LeetCode【101. 对称二叉树】

    对称二叉树,就是左节点的左节点等于右节点的右节点,左节点的右节点等于右节点的左节点. 很自然就想到迭代与递归,可以创建一个新的函数,就是另一个函数不断的判断,返回在主函数. class Solutio ...

  7. MAP File

    https://warpproject.org/trac/wiki/howto/Linker_scripts_MAP_files Description A MAP file is an output ...

  8. 转载:揪出MySQL磁盘消耗迅猛的真凶

    揪出MySQL磁盘消耗迅猛的真凶 背景 Part1:写在最前 当一张单表10亿数据量的表放在你面前,你将面临着什么? Part2:背景介绍 为了提升数据库资源利用率,一个实例中,在不互相影响,保证业务 ...

  9. 解析-ESP01模块开发Arduino物联网wifi开关模块

    本文将解析<完美图解物联网Iot实操 ESP8266>中 第五章 P177页 动手做的代码2(使用SPIFFS文件系统的代码) 首先我们先动手使用Arduino IDE编译并且上传代码,上 ...

  10. C语言,链表操作

    #include "stdafx.h" #include <stdio.h> #include <stdlib.h> #include <string ...