2012年3月29日星期四

HTML5人工智能基础及OO实践

HTML5人工智能基础及OO实践



简介

人工智能(Artificial Intelligence) ,英文缩写为AI。它是研究、开发用于模拟、延伸和扩展智能的理论、方法、技术及应用系统的一门新的技术科学。本篇从严格意义上说属于人工智能的范畴,但也是基础中的基础。本篇的目的是要赋予小球解散和集合两项基本指令(智商),本篇内容子弹追踪等塔防类游戏当中。

基础类

二维向量(2D vector)可谓2D游戏或是动画里最常用型别了。这里三维向量用Vector2类实现,用(x, y)表示。 Vector2亦用来表示空间中的点(point),而不另建类。先看代码:

(function(window) {
    var Vector2 = function(x, y) {
        this.x = x || 0;
        this.y = y || 0;
    };
    Vector2.prototype = {
        set: function(x, y) {
            this.x = x;
            this.y = y;
            return this;
        },
        sub: function(v) {
            return new Vector2(this.x - v.x, this.y - v.y);
        },
        multiplyScalar: function(s) {
            this.x *= s;
            this.y *= s;
            return this;
        },
        divideScalar: function(s) {
            if (s) {
                this.x /= s;
                this.y /= s;
            } else {
                this.set(0, 0);
            }
            return this;
        },
        length: function() {
            return Math.sqrt(this.lengthSq());
        },
        normalize: function() {
            return this.divideScalar(this.length());
        },
        lengthSq: function() {
            return this.x * this.x + this.y * this.y;
        },
        distanceToSquared: function(v) {
            var dx = this.x - v.x,
            dy = this.y - v.y;
            return dx * dx + dy * dy;
        },
        distanceTo: function(v) {
            return Math.sqrt(this.distanceToSquared(v));
        },
        setLength: function(l) {
            return this.normalize().multiplyScalar(l);
        }
    };
    window.Vector2 = Vector2;
} (window));

使用该类需要特别注意和区分的地方是:

它什么时候代表点、什么时候代表向量。

当其代表向量的时候,它的几何意义是什么?

不能把其当成一个黑盒来调用,需要知其然并知其所以然。

在下面的使用的过程当中,我会特别标注其代表点还是向量;代表向量时,其几何意义是什么?

给小球赋予智商,顾名思义需要小球类:

(function (window) {
    var Ball = function (r, v, p, cp) {
        this.radius = r;
        this.velocity = v;
        this.position = p;
        this.collectionPosition = cp;
    }
    Ball.prototype = {
        collection: function (v) {
            this.velocity = this.collectionPosition.sub(this.position).setLength(v);
        },
        disband: function () {
            this.velocity = new Vector2(MathHelp.getRandomNumber(-230, 230), MathHelp.getRandomNumber(-230, 230));
        }
    }
    window.Ball = Ball;
} (window));

其中

小球拥有4个属性,分别是:radius半径、velocity速度(Vector2)、position位置(Vector2)、collectionPosition集合点/小球的家(Vector2)。

小球拥有2个方法,分别是:collection集合、disband解散。

小球的集合方法所传递的参数为集合的速度,因为小球都有一个集合点的属性,所以这里不用再传入集合点/家给小球。

这里详细分析一下collection方法,这也是整个demo的关键代码。

collection: function (v) {
     this.velocity = this.collectionPosition.sub(this.position).setLength(v);
},

因为setLength设置向量的长度:

setLength: function (l) {
      return this.normalize().multiplyScalar(l);
 }

所以collection可以改成:

this.velocity = this.collectionPosition.sub(this.position).normalize().multiplyScalar(v);

normalize是获取单位向量,也可以改成:

this.collectionPosition.sub(this.position).divideScalar(this.length()).multiplyScalar(v);

整个Vector2黑盒就全部展现出来,其整个过程都是向量的运算,代表含义如下所示:

this.collectionPosition

                          .sub(this.position)                获取小球所在位置指向小球的向量;

                          .divideScalar(this.length()) 获取小球所在位置指向小球的向量的单位向量;
                           .multiplyScalar(v);               设置该向量的长度。

最后把所得到的向量赋给小球的速度。
上面我们还是用到了解散方法,其过程是帮小球生成一个随机速度,用到了MathHelp类的一个静态方法:

(function (window) {
    var MathHelp = {};
    MathHelp.getRandomNumber = function (min, max) {
        return (min + Math.floor(Math.random() * (max - min + 1)));
    }
    window.MathHelp = MathHelp;
} (window));

粒子生成

写了Vector2、Ball、MathHeper三个类之后,终于可以开始实现一点东西出来!

var ps = [], balls = [];
function init(tex) {
    balls.length = 0;
    ps.length = 0;
    cxt.clearRect(0, 0, canvas.width, canvas.height);
    cxt.fillStyle = "rgba(0,0,0,1)";
    cxt.fillRect(0, 0, canvas.width, canvas.height);
    cxt.fillStyle = "rgba(255,255,255,1)";
    cxt.font = "bolder 160px 宋体";
    cxt.textBaseline = 'top';
    cxt.fillText(tex, 20, 20);
    //收集所有像素
    for (y = 1; y < canvas.height; y += 7) {
        for (x = 1; x < canvas.width; x += 7) {
            imageData = cxt.getImageData(20 + x, 20 + y, 1, 1);
            if (imageData.data[0] > 170) {
                ps.push({
                    px: 20 + x,
                    py: 20 + y
                })
            }
        }
    };
    cxt.fillStyle = "rgba(0,0,0,1)";
    cxt.fillRect(20, 20, canvas.width, canvas.height);
    //像素点和小球转换
    for (var i in ps) {
        var ball = new Ball(2, new Vector2(0, 0), new Vector2(ps[i].px, ps[i].py), new Vector2(ps[i].px, ps[i].py));
        balls.push(ball);
    };
    cxt.fillStyle = "#fff";
    for (i in balls) {
        cxt.beginPath();
        cxt.arc(balls[i].position.x, balls[i].position.y, balls[i].radius, 0, Math.PI * 2, true);
        cxt.closePath();
        cxt.fill();
    }
    //解散:生成随机速度
    for (var i in balls) {
        balls[i].disband();
    }
}

其中分三个步骤:收集所有像素、 像素点和小球转换、生成随机速度。整个demo我们需要一个loop

var time = 0;
var cyc = 15;
var a = 80;
var collectionCMD = false;
setInterval(function () {
    cxt.fillStyle = "rgba(0, 0, 0, .3)";
    cxt.fillRect(0, 0, canvas.width, canvas.height);
    cxt.fillStyle = "#fff";
    time += cyc;
    for (var i in balls) {
        if (collectionCMD === true && balls[i].position.distanceTo(balls[i].collectionPosition) < 2) {
            balls[i].velocity.y = 0;
            balls[i].velocity.x = 0;
        }
    }
    if (time === 3000) {
        collectionCMD = true;
        for (var i in balls) {
            balls[i].collection(230);
        }
    }
    if (time === 7500) {
        time = 0;
        collectionCMD = false;
        for (var i in balls) {
            balls[i].disband();
        }
    }
    for (var i in balls) {
        cxt.beginPath();
        cxt.arc(balls[i].position.x, balls[i].position.y, balls[i].radius, 0, Math.PI * 2, true);
        cxt.closePath();
        cxt.fill();
        balls[i].position.y += balls[i].velocity.y * cyc / 1000;
        balls[i].position.x += balls[i].velocity.x * cyc / 1000;
    }
},
cyc);

这里使用time整体控制,使其无限loop。ps:这里还有一点不够OO的地方就是应当为ball提供一个draw方法。

其中的balls[i].position.distanceTo(balls[i].collectionPosition) 代表了点与点之间的距离,这里判断小球是否到了集合点或家。这里其几何意义就不再向量了。

在线演示



TAG: