JS小型游戏框架coquette学习(持续更新)

coquette游戏框架学习

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

框架的使用

1. 补充知识

canvas2D上下文坐标

开始于左上角,原点坐标(0,0);x越大表示越靠右,y越大表示越靠下

fillRect()绘制矩形

接受四个参数,分别是:矩形的x坐标,矩形的y坐标,矩形的宽,矩形的高。

2. 创建游戏区

c现在代表新建的coquette实例,是一个全局变量,下文会用到

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

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

Coquette的第三和第四个参数分别指定游戏区的宽和高,第五个参数指定背景颜色。

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的原生方法,在每次鼠标移动的时候就会调用传入的匿名函数。匿名函数的功能是:在鼠标按下,并且处于拖拽状态的情况下,更新拖拽实体的中心坐标。

 

转载于:https://my.oschina.net/u/3400107/blog/1831159

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值