如何剪切绘制的画布,旋转并绘制到另一个画布?

时间:2018-01-30 06:59:18

标签: javascript canvas game-engine viewport

我有一个用于游戏世界的画布和一个用于显示屏幕的画布。我还有一个带有节点V(x,y)的多边形,用作跟随玩家和他的旋转的视口。我想知道如何沿着多边形从游戏世界剪辑,旋转并绘制到较小的画布。enter image description here`

//main looping function
var requestAnimFrame = (function(){
    return window.requestAnimationFrame       ||
        window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame    ||
        window.oRequestAnimationFrame      ||
        window.msRequestAnimationFrame     ||
        function(callback){
            window.setTimeout(callback, 1000 / 60);
        };
})();

//joystick setup
var leftManager = null;
var rightManager = null;

//precalculated math
var twoPi = Math.PI*2;
var halfPi = Math.PI/2;
var thirdOfCircleInRadians = twoPi/3;


//game canvas setup
var gameCvs = document.getElementById('gameCanvas');
gameCvs.width = 480;
gameCvs.height = 320;
//gameCvs.width - 960;
//gameCvs.height = 640;
var gameCtx = gameCvs.getContext("2d");

//game loop
var lastTime = 0;
function main() {
    var now = Date.now();
    var dt = lastTime==0? 0.016 : (now - lastTime) / 1000.0;
    update(dt);
    render(dt);
    lastTime = now;
    requestAnimFrame(main);
}

//collision class shorthand
var V = SAT.Vector;
var C = SAT.Circle;
var P = SAT.Polygon;
var R = new SAT.Response();

P.prototype.draw = function (ctx,type) {

    ctx.save();
    switch(type){
        case 'van': ctx.fillStyle = "rgba(66, 66, 66, 0.5)"; break;
        case 'col': ctx.fillStyle = "rgba(0, 0, 0, 1.0)"; break;
        default: ctx.fillStyle = "rgba(0, 0, 0, 1.0)"; break;
    }
    ctx.translate(this.pos.x, this.pos.y);
    ctx.beginPath();
    var points = this.calcPoints;
    ctx.moveTo(points[0].x, points[0].y);
    var i = points.length;
    while (i--) ctx.lineTo(points[i].x, points[i].y);
    ctx.closePath();
    //stroke to see through camera, when camera is not drawn use fill
    ctx.stroke();
    //ctx.fill();
    ctx.restore();
};

//first for collisions, second for vanity. first is black, second is grey
var O = function(colPolygon,vanPolygon){
    this.colPolygon = colPolygon;
    this.vanPolygon = vanPolygon;
    this.visible = false;
};


var objectVendor = function(type,position){
    switch(type){
        case 'tree': 
        return new O(new P(position,[
            new V(10.5,19.5),
            new V(20.5,9.5),
            new V(23,-4),
            new V(15,-16.5),
            new V(-4,-19.5),
            new V(-18,-14.5),
            new V(-23,-0.5),
            new V(-18.5,14.5),
            new V(-8,20)
            ]),new P(position,[
            new V(21,39),
            new V(41,19),
            new V(46,-8),
            new V(30,-33),
            new V(-8,-39),
            new V(-36,-29),
            new V(-46,-1),
            new V(-37,29),
            new V(-16,40)]));
        break;
        default: return false; break;
    }
    return false;
}

//Camera and Player Polygons

var cameraPoly = new P(new V(0,0),[
        new V(-240,-160),
        new V(240,-160),
        new V(240,160),
        new V(-240,160)
    ]);


var player = new P(new V(0,0),[
        new V(5,2.5),
        new V(7.5,2),
        new V(7.5,-2),
        new V(5,-2.5),
        new V(-5,-2.5),
        new V(-7.5,-2),
        new V(-7.5,2),
        new V(-5,2.5)
    ]);
//players start position on the screen, and starting angle, init velocity
player.pos = new V(240,160);
player.setAngle(1);
//players velocity for movement
player.vel = new V(0,0);

var world = {
    objects: [],
    visibleObjects: [],
    worldCvs: null,
    worldCtx: null,
    init: function(){
        //set up world canvas
        this.worldCvs = document.createElement('canvas');
        this.worldCvs.width = 480;
        this.worldCvs.height = 480;
        this.worldCtx = this.worldCvs.getContext("2d");
        //populate world with stuff
        this.objects.push(objectVendor('tree',new V(100,100)));
        this.objects.push(objectVendor('tree',new V(150,200)));
        this.objects.push(objectVendor('tree',new V(75,300)));
    },
    update: function(dt){
            this.visibleObjects = [];

            cameraPoly.setAngle(player.angle);
            //cameraPoly.pos = player.pos;
            cameraPoly.pos = new V(player.pos.x+(110*Math.cos(player.angle+halfPi)),player.pos.y+(110*Math.sin(player.angle+halfPi)));
            //update objects to mark if they are in view
            var i = this.objects.length;
            while(i--){
                if(SAT.testPolygonPolygon(this.objects[i].vanPolygon, cameraPoly, R)){
                    this.visibleObjects.push(this.objects[i]);
                }
            }
        //}
    },
    draw: function(dt){

        this.worldCtx.setTransform(1,0,0,1,0,0);
        this.worldCtx.clearRect(0,0,this.worldCvs.width,this.worldCvs.height);
        player.draw(this.worldCtx);
        var i = this.visibleObjects.length;
        while(i--){
            this.visibleObjects[i].colPolygon.draw(this.worldCtx,'col');
            this.visibleObjects[i].vanPolygon.draw(this.worldCtx,'van');
        }
        //for testing
        cameraPoly.draw(this.worldCtx);
        /*
        this.worldCtx.save();
        this.worldCtx.beginPath();
        var i = cameraPoly.calcPoints.length;
        this.worldCtx.moveTo(cameraPoly.calcPoints[0].x,cameraPoly.calcPoints[0].y);
        while(i--){
            this.worldCtx.lineTo(cameraPoly.calcPoints[i].x,cameraPoly.calcPoints[i].y);
        }
        this.worldCtx.clip();
        this.worldCtx.restore();
        */
    }

}

function render(dt){
    gameCtx.setTransform(1,0,0,1,0,0);
    gameCtx.clearRect(0,0,gameCvs.width,gameCvs.height);

    world.draw();
    //gameCtx.save();
    //gameCtx.translate(cameraPoly.pos.x,cameraPoly.pos.y);
    //gameCtx.translate(gameCtx.width/2,gameCtx.height/2);
    //gameCtx.rotate(-player.angle+halfPi);
    //gameCtx.translate(-world.worldCvs.width/2,-world.worldCvs.height/2);

    gameCtx.drawImage(world.worldCvs,0,0);
    //gameCtx.restore();
}

function update(dt){
    world.update();
}

function init(){

    //joystick setup
    leftManager = nipplejs.create({
        zone:document.getElementById("leftJoystick"),
        color:"black",
        size:75,
        threshold:1.0,
        position:{
            top:"50%",
            left:"50%"
        },
        mode:"static",
        restOpacity:0.75,
    });
    rightManager = nipplejs.create({
        zone:document.getElementById("rightJoystick"),
        color:"black",
        size:75,
        threshold:1.0,
        position:{
            top:"50%",
            right:"50%"
        },
        mode:"static",
        restOpacity:0.75,
    });

    //joystick event setup
    leftManager.get().on('move end', function(evt,data){
        //console.log(evt);
        //console.log(data);
    });
    rightManager.get().on('move end', function(evt,data){
        //console.log(evt);
        //console.log(data);
    });
    world.init();
    main();
}
init();

` 我目前正在使用库SAT.js和nipplejs.js。

2 个答案:

答案 0 :(得分:0)

通常情况下,这种做法与你想象中的方式略有不同。你不应该考虑世界某个地方存在的视口,而应该考虑修复视口,改变背后的世界;如果不将世界的一部分复制到视口,则绘制世界偏移并旋转一定量,并仅绘制视口内的部分。矩阵是表示这种转换的一种简单而通用的方式。您可能需要read more about them here

实际上,这只相当于在每个绘制框架的开头将现有的调用更改为worldCtx.setTransform()。该链接包含有关如何计算良好变换矩阵的信息,您可以在整个地方找到类似的资源,因为它是非常标准的数学。

特别是,您需要将rotation和翻译矩阵相乘。只有使用高于坐标空间的矩阵,才能使用平移矩阵;对于2D,3x3矩阵,对于3D,4x4矩阵。您可以选择在绘制时向坐标添加一些偏移,但worldCtx.setTransform已经采用带有第3列的矩阵来放置平坦偏移。

答案 1 :(得分:0)

将渲染功能更改为以下内容将解决问题,只是匆匆忙忙,并没有很好地思考问题。 `

function render(dt){
    gameCtx.setTransform(1,0,0,1,0,0);
    gameCtx.clearRect(0,0,gameCvs.width,gameCvs.height);
    world.draw();
    gameCtx.translate(gameCvs.width/2,gameCvs.height/2);
    gameCtx.rotate(-player.angle+Math.PI);
    gameCtx.translate(-cameraPoly.pos.x,-cameraPoly.pos.y);
    gameCtx.drawImage(world.worldCvs,0,0);
}`

这样做是重置上下文的任何转换,清除它以进行新的重绘,创建世界画布,转换为显示中心,旋转适当的参考点数量,转换为负轴上的参考中心点移动游戏画布适当的数量,以便在0,0的绘图位于正确的位置。感谢您参考资料!