JS小型遊戲框架coquette學習(持續更新)

coquette遊戲框架學習

框架地址:https://github.com/maryrosecook/coquettegit

框架的使用

1. 補充知識

canvas2D上下文座標github

開始於左上角,原點座標(0,0);x越大表示越靠右,y越大表示越靠下canvas

fillRect()繪製矩形app

接受四個參數,分別是:矩形的x座標,矩形的y座標,矩形的寬,矩形的高。框架

2. 建立遊戲區

c如今表明新建的coquette實例,是一個全局變量,下文會用到dom

var game = function () {
    this.c = new Coquette(this, "canvas", 500, 150, "#000");
};

window.addEventListener('load',function (ev) {
    new game();
});

Coquette的第三和第四個參數分別指定遊戲區的寬和高,第五個參數指定背景顏色。ide

3. 實體

a.定義實體(建立一個定義實體的構造函數)函數

這個構造函數能夠有如下兩個參數學習

  1. window?暫時沒法肯定,須要以後的學習
  2. 配置對象,能夠在c.entities.create()的第二個參數中指定關於實體的配置對象。相似於:動畫

    var Person = function(game, settings) {
        this.c = game.c
    };

屬性

實體有如下幾個屬性:

  1. center:實體所處的2D上下文的位置座標eg:{x:10,y:20}
  2. size:實體的尺寸 eg:{x:50,y:30}
  3. angle:實體旋轉的角度 eg:30

指定了center和size後,能夠在draw方法中調用this.size/center來得到指定的size/center;指定了angle後,繪製出的實體會旋轉指定的角度

範例代碼:

this.draw = function(ctx) {
    ctx.fillStyle = settings.color;
    ctx.fillRect(this.center.x - this.size.x / 2,
        this.center.y - this.size.y / 2,
        this.size.x,
        this.size.y);
};

方法

draw:在每一個最小時間間隔(tick)調用一次,能夠在這個方法中指定如何繪製實體,好比指定填充顏色、繪製矩形(如上面的代碼) update(timeSinceLastTick):更新一些實體的屬性,以便在繪製的時候根據改變的屬性從新繪製實體,產生動畫的效果,參數是上一次tick距離如今過了多少時間。

b. 建立實體

調用c.entities.create()

接受以下參數:

  1. 參數1——定義實體的構造器函數;
  2. 參數2——開發者想傳遞給構造器函數的配置對象

返回建立的實體(能夠將這個實體賦值給一個變量之後使用)

c. 摧毀實體

調用c.entities.destroy(實體)

4. 輸入

與輸入相關的屬性和方法定義在c.inputter上

屬性

LEFT_ARROW:左箭頭
UP_ARROW:上箭頭
DOWN_ARROW:下箭頭
RIGHT_ARROW:右箭頭
RIGHT_MOUSE:右鼠標
LEFT_MOUSE:左鼠標

方法

  1. isDown(inputter):檢測該按鍵是否處於按下的狀態,只要一直按着,該方法返回true

  2. isPress(inputter):在按鍵按下的緊隨一個tick中返回true,以後返回false,直到鬆開後再次按下才再次返回一次true。

  3. bindMouseMove(function(position){}):每次鼠標移動以後都會調用傳給這個方法的函數:在函數的position參數中含有當前鼠標的位置(基於canvas2D上下文座標)。

  4. getMousePostion():獲得當前鼠標的位置,返回{x:,y:}這樣的對象

碰撞

在實體相撞的時候觸發

爲了讓實體支持碰撞,須要指定如下屬性

  1. center: The center of the entity, e.g. { x: 10, y: 20 }.
  2. size: The size of the entity, e.g. { x: 50, y: 30 }.
  3. boundingBox: The shape that best approximates the shape of the entity, either c.collider.RECTANGLE or c.collider.CIRCLE.(暫時不知道怎麼用)
  4. angle: The orientation of the entity in degrees, e.g. 30.
  5. collision(other):在實體A和另外一個實體B相撞的時候調用,other表明實體B

例子:

var Player = function() {
  this.center = { x: 10, y: 20 };
  this.size = { x: 50, y: 50 };
  this.boundingBox = c.collider.CIRCLE;
  this.angle = 0;
};

Player.prototype = {
  collision: function(other) {
    console.log("Ow,", other, "hit me.");
  }
};

coquette的dome之一——spinning-shapes代碼分析

源碼地址https://github.com/maryrosecook/coquette/blob/master/demos/spinning-shapes/game.js

1. 其餘細節函數分析

CollisionCounter

一個構造函數,利用new建立出一個對象,擁有一個屬性和兩個方法。

  1. colliders:存儲與該實體發生碰撞的全部實體(事實上就是與該實體有區域存在重合的實體)。
  2. update方法:去掉與該實體已經再也不碰撞的實體(與該實體區域再也不重合的實體)。
  3. collision方法:將與該實體發生碰撞的那個實體放到colliders中存儲起來。

CollisionCounter代碼

var CollisionCounter = function(entity) {
    this.colliders = [];

    this.update = function() {
        this.colliders = this.colliders
            .filter(function(c) { return entity.c.collider.isColliding(entity, c); });
    };

    this.collision = function(other) {
        if (this.colliders.indexOf(other) === -1) {
            this.colliders.push(other);
        }
    };
};

randomDirection

  1. 給unitVector傳入0~0.5的隨機座標

    var randomDirection = function() {
        return Coquette.Collider.Maths.unitVector({ x:Math.random() - .5, y:Math.random() - .5 });
    };
  2. unitVector

    unitVector: function(vector) {
        return {
            x: vector.x / Maths.magnitude(vector),
            y: vector.y / Maths.magnitude(vector)
        };
    },
  3. magnitude

    magnitude: function(vector) {
        return Math.sqrt(vector.x * vector.x + vector.y * vector.y);
    },
  4. 最終結果,返回

    {x:ramX/sqrtramX*ramX+ramY*ramY),y:ramY/sqrt(ramX*ramX+ramY*ramY)}//利用座標表示從0到360的任意角度

    其中-0.5<x<0.5;-0.5<y<0.5

movingonscreenVec

代碼:

var movingOnscreenVec = function(dirFromCenter) {
    return { x: -dirFromCenter.x * 3 * Math.random(), y: -dirFromCenter.y * 3 * Math.random() }
};

假設傳入對象爲{x:x,y:y},返回:

{x:-3x*ram(0~1),y:-3y*ram(0~1)}

offscreenPosition

代碼:

var offscreenPosition = function(dirFromCenter, viewSize, viewCenter) {
    return {
        x: viewCenter.x + dirFromCenter.x * viewSize.x,
        y: viewCenter.y + dirFromCenter.y * viewSize.y
    };
};

其中viewSize和viewCenter分別是canvas的長寬和canvas中心點的座標。這個函數的做用是返回新生成的實體的中點座標,值得注意的是,中點座標必定在可視區以外,可是若是實體的長或者高太大,實體會有一部分一開始就在canvas可視區以內。

isOutOfView

isOutOfView

var isOutOfView = function(obj, viewSize, viewCenter) {
    return Coquette.Collider.Maths.distance(obj.center, viewCenter) >
        Math.max(viewSize.x, viewSize.y);
};

Maths.distance

distance: function(point1, point2) {
    var x = point1.x - point2.x;
    var y = point1.y - point2.y;
    return Math.sqrt((x * x) + (y * y));
},

判斷某個實體是否徹底在可視區以外,當某個實體的中點距離canvas中心的距離大於canvas可視區的最大邊長的時候,返回true。實體邊長過長可能會出現還沒實體徹底脫離可視區就被摧毀的狀況。

2. 主要函數SpinningShapeGame分析

var SpinningShapesGame = function() {
    var autoFocus = false;
    this.c = new Coquette(this, "spinning-shapes-canvas",
        500, 500 / GOLDEN_RATIO, "white", autoFocus);
    this.dragger = new Dragger(this.c); // controls dragging of shapes with mouse
};

SpinningShapesGame.prototype = {
    update: function() {
        this.dragger.update();
        var viewSize = this.c.renderer.getViewSize();
        var viewCenter = this.c.renderer.getViewCenter();

        if (this.c.entities.all().length < 15) { // not enough shapes
            var dirFromCenter = randomDirection();
            var Shape = Math.random() > 0.5 ? Rectangle : Circle;
            this.c.entities.create(Shape, { // make one
                center: offscreenPosition(dirFromCenter, viewSize, viewCenter),
                vec: movingOnscreenVec(dirFromCenter)
            });
        }

        // destroy entities that are off screen
        var entities = this.c.entities.all();
        for (var i = 0; i < entities.length; i++) {
            if (isOutOfView(entities[i], viewSize, viewCenter)) {
                this.c.entities.destroy(entities[i]);
            }
        }
    }
};
  1. 若是當前存在實體的個數小於15,不斷的生成實體,並將超出可視範圍的實體摧毀。
  2. offscreenPosition肯定實體一開始出生的位置,movingOnscreenVec肯定實體生成後移動的方向,仔細分析這兩個函數,能夠發現生成實體大體向可視區的方向移動,這是由於movingOnscreenVec函數中將座標方向取了負號。

3. 拖拽函數Dragger分析

Dragger的原型裏有一個update方法,這個方法在每一個tick都更新,它會檢測鼠標左鍵是否按下,若是按下,調用isDragging檢測是否處於拖拽狀態,若是爲否,檢測鼠標是否處於某個實體的範圍內,若是是的,調用startDrag方法給Dragger實例設置一個currentDrag屬性,該屬性是一個對象,含有拖拽目標實體和鼠標偏移座標。

Dragger函數原型

Dragger.prototype = {
    update: function() {
        if (this.c.inputter.isDown(this.c.inputter.LEFT_MOUSE)) {
            if (!this._isDragging()) {
                var mousePosition = this.c.inputter.getMousePosition();
                var target = this._getTarget(this.c.entities.all(), mousePosition);
                if (target !== undefined) {
                    this._startDrag(target, mousePosition);
                }
            }
        } else {
            this._stopDrag();
        }
    },

    _isDragging: function() {
        return this._currentDrag !== undefined;
    },

    _getTarget: function(targets, e) {
        for (var i = 0; i < targets.length; i++) {
            if (Coquette.Collider.Maths.pointInsideObj(e, targets[i])) {
                return targets[i];
            }
        }
    },

    _startDrag: function(target, e) {
        this._currentDrag = {
            target: target,
            centerOffset: {
                x: target.center.x - e.x,
                y: target.center.y - e.y
            }
        };

        if (target.startDrag !== undefined) {
            target.startDrag();
        }
    },

    _stopDrag: function() {
        if (this._isDragging()) {
            if (this._currentDrag.target.stopDrag !== undefined) {
                this._currentDrag.target.stopDrag();
            }

            this._currentDrag = undefined;
        }
    }
};

拖拽狀態是調用startDrag方法肯定的,實際上就是給Dragger實例添加一個currentDrag屬性。取消拖拽狀態是調用stopDrag方法肯定的,實際上就是將currentDrag屬性設置爲undefined。

Dragger函數

var Dragger = function(c) {
    this.c = c;
    this._currentDrag;
    var self = this;

    c.inputter.bindMouseMove(function(e) {
        if (c.inputter.isDown(c.inputter.LEFT_MOUSE)) {
            if (self._isDragging()) {
                self._currentDrag.target.center = {
                    x: e.x + self._currentDrag.centerOffset.x,
                    y: e.y + self._currentDrag.centerOffset.y
                };
            }
        }
    });
};

bindMouseMove是coquette的原生方法,在每次鼠標移動的時候就會調用傳入的匿名函數。匿名函數的功能是:在鼠標按下,而且處於拖拽狀態的狀況下,更新拖拽實體的中心座標。

相關文章
相關標籤/搜索