HTML5 Canvas相机/视口 - 如何实际操作?

时间:2013-06-04 13:47:45

标签: javascript html5 canvas html5-canvas viewport

我确定之前已经解决了1000次:我得到了一个尺寸为960 * 560的画布和一个大小为5000 * 3000的房间,其中总是只画出960 * 560,具体取决于球员是。玩家应该始终位于中间,但是当接近边界时 - 应该计算最佳视图)。玩家可以使用WASD或箭头键完全免费移动。并且所有物体都应该自己移动 - 而不是我移动除了玩家之外的其他所有东西,以创造玩家移动的错觉。

我现在找到了这两个问题:

HTML5 - Creating a viewport for canvas有效,但仅限于此类游戏,我无法重现我的代码。

Changing the view "center" of an html5 canvas似乎更有前途,也更有说服力,但我只是理解相对于播放器正确绘制所有其他对象而不是如何相对于播放器滚动画布视口,我想实现当然首先。

我的代码(简化 - 游戏逻辑是单独的):

var canvas = document.getElementById("game");
canvas.tabIndex = 0;
canvas.focus();
var cc = canvas.getContext("2d");

// Define viewports for scrolling inside the canvas

/* Viewport x position */   view_xview = 0;
/* Viewport y position */   view_yview = 0;
/* Viewport width */        view_wview = 960;
/* Viewport height */       view_hview = 560;
/* Sector width */          room_width = 5000;
/* Sector height */         room_height = 3000;

canvas.width = view_wview;
canvas.height = view_hview;

function draw()
{
    clear();
    requestAnimFrame(draw);

    // World's end and viewport
    if (player.x < 20) player.x = 20;
    if (player.y < 20) player.y = 20;
    if (player.x > room_width-20) player.x = room_width-20;
    if (player.y > room_height-20) player.y = room_height-20;

    if (player.x > view_wview/2) ... ?
    if (player.y > view_hview/2) ... ?
}

我试图让它工作的方式感觉完全错误,我甚至不知道我是如何尝试的......任何想法?您如何看待context.transform-thing?

我希望你理解我的描述并且有人有想法。亲切的问候

7 个答案:

答案 0 :(得分:79)

jsfiddle.net

LIVE DEMO

此演示说明了真实游戏场景中的视口使用情况。使用箭头键将播放器移动到房间上方。使用矩形动态生成大房间,结果保存为图像。

请注意,除非接近边界(如您所愿),否则播放器始终处于中间位置。


现在我将尝试解释代码的主要部分,至少只是看到它时更难理解的部分。


使用drawImage根据视口位置绘制大图像

drawImage方法的一个变体有八个新参数。我们可以使用此方法切片源图像的部分并将它们绘制到画布上。

  

drawImage(image,sx,sy,sWidth,sHeight,dx,dy,dWidth,dHeight)

第一个参数图像与其他变体一样,是对图像对象的引用或对不同画布元素的引用。对于其他八个参数,最好查看下图。前四个参数定义源图像上切片的位置和大小。最后四个参数定义目标画布上的位置和大小。

Canvas drawImage

字体:https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Canvas_tutorial/Using_images

它在演示中的工作原理:

我们有一个代表房间的大图像,我们想在画布上只显示视口内的部分。裁剪位置(sx,sy)与相机的位置相同(xView,yView),裁剪尺寸与视口(画布)相同,因此sWidth=canvas.widthsHeight=canvas.height

我们需要注意裁剪尺寸,因为如果基于位置的裁剪位置或裁剪尺寸无效,drawImage在画布上不会绘制任何内容。这就是我们需要if部分的原因。

var sx, sy, dx, dy;
var sWidth, sHeight, dWidth, dHeight;

// offset point to crop the image
sx = xView;
sy = yView;

// dimensions of cropped image          
sWidth =  context.canvas.width;
sHeight = context.canvas.height;

// if cropped image is smaller than canvas we need to change the source dimensions
if(image.width - sx < sWidth){
    sWidth = image.width - sx;
}
if(image.height - sy < sHeight){
    sHeight = image.height - sy; 
}

// location on canvas to draw the croped image
dx = 0;
dy = 0;
// match destination with source to not scale the image
dWidth = sWidth;
dHeight = sHeight;          

// draw the cropped image
context.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

绘制与视口相关的游戏对象

在编写游戏时,将游戏中每个对象的逻辑和渲染分开是一个很好的做法。因此,在演示中,我们有updatedraw个函数。 update方法更改对象状态,如“游戏世界”中的位置,应用物理,动画状态等。draw方法实际渲染对象并在考虑到视口时正确渲染它,对象需要了解渲染上下文和视口属性。

请注意,考虑到游戏世界的位置,游戏对象会更新。这意味着对象的(x,y)位置是世界中的位置。尽管如此,由于视口正在改变,因此需要正确渲染对象,渲染位置将与世界位置不同。

转换很简单:

世界(房间)中的物体位置:(x, y)
视口位置:(xView, yView)

呈现位置(x-xView, y-yView)

这适用于所有类型的坐标,甚至是负坐标。


游戏相机

我们的游戏对象有一个单独的更新方法。在Demo实现中,摄像机被视为游戏对象,并且还具有单独的更新方法。

摄像机对象保持视口(xView, yView)的左上角位置,要跟踪的对象,表示视口的矩形,表示游戏世界边界的矩形以及玩家可能的每个边界的最小距离在相机开始移动之前(xDeadZone,yDeadZone)。我们还定义了相机的自由度(轴)。对于像RPG这样的顶视式游戏,摄像机可以在x(水平)和y(垂直)轴上移动。

为了让玩家保持在视口中间,我们将每个轴的deadZone设置为与画布中心会聚。查看代码中的follow函数:

  

camera.follow(player,canvas.width / 2,canvas.height / 2)


世界的极限

由于每个对象(包括摄像头)都有自己的更新功能,因此很容易检查游戏世界的边界。只记得将阻止移动的代码放在更新函数的最后。


示范

查看完整代码并亲自试用。比用文字解释要好得多。也许在阅读完代码后,这些信息将被澄清。

LIVE DEMO

完整代码:

<!DOCTYPE HTML>
<html>
<body>
<canvas id="gameCanvas" width=400 height=400 />
<script>
// wrapper for our game "classes", "methods" and "objects"
window.Game = {};

// wrapper for "class" Rectangle
(function(){
    function Rectangle(left, top, width, height){
        this.left = left || 0;
        this.top = top || 0;
                    this.width = width || 0;
        this.height = height || 0;
        this.right = this.left + this.width;
        this.bottom = this.top + this.height;
    }

    Rectangle.prototype.set = function(left, top, /*optional*/width, /*optional*/height){
        this.left = left;
        this.top = top;
        this.width = width || this.width;
        this.height = height || this.height
        this.right = (this.left + this.width);
        this.bottom = (this.top + this.height);
    }

    Rectangle.prototype.within = function(r) {
        return (r.left <= this.left && 
                r.right >= this.right &&
                r.top <= this.top && 
                r.bottom >= this.bottom);
    }       

    Rectangle.prototype.overlaps = function(r) {
        return (this.left < r.right && 
                r.left < this.right && 
                this.top < r.bottom &&
                r.top < this.bottom);
    }

    // add "class" Rectangle to our Game object
    Game.Rectangle = Rectangle;
})();   

// wrapper for "class" Camera (avoid global objects)
(function(){

    // possibles axis to move the camera
    var AXIS = {
        NONE: "none", 
        HORIZONTAL: "horizontal", 
        VERTICAL: "vertical", 
        BOTH: "both"
    };

    // Camera constructor
    function Camera(xView, yView, canvasWidth, canvasHeight, worldWidth, worldHeight)
    {
        // position of camera (left-top coordinate)
        this.xView = xView || 0;
        this.yView = yView || 0;

        // distance from followed object to border before camera starts move
        this.xDeadZone = 0; // min distance to horizontal borders
        this.yDeadZone = 0; // min distance to vertical borders

        // viewport dimensions
        this.wView = canvasWidth;
        this.hView = canvasHeight;          

        // allow camera to move in vertical and horizontal axis
        this.axis = AXIS.BOTH;  

        // object that should be followed
        this.followed = null;

        // rectangle that represents the viewport
        this.viewportRect = new Game.Rectangle(this.xView, this.yView, this.wView, this.hView);             

        // rectangle that represents the world's boundary (room's boundary)
        this.worldRect = new Game.Rectangle(0, 0, worldWidth, worldHeight);

    }

    // gameObject needs to have "x" and "y" properties (as world(or room) position)
    Camera.prototype.follow = function(gameObject, xDeadZone, yDeadZone)
    {       
        this.followed = gameObject; 
        this.xDeadZone = xDeadZone;
        this.yDeadZone = yDeadZone;
    }                   

    Camera.prototype.update = function()
    {
        // keep following the player (or other desired object)
        if(this.followed != null)
        {       
            if(this.axis == AXIS.HORIZONTAL || this.axis == AXIS.BOTH)
            {       
                // moves camera on horizontal axis based on followed object position
                if(this.followed.x - this.xView  + this.xDeadZone > this.wView)
                    this.xView = this.followed.x - (this.wView - this.xDeadZone);
                else if(this.followed.x  - this.xDeadZone < this.xView)
                    this.xView = this.followed.x  - this.xDeadZone;

            }
            if(this.axis == AXIS.VERTICAL || this.axis == AXIS.BOTH)
            {
                // moves camera on vertical axis based on followed object position
                if(this.followed.y - this.yView + this.yDeadZone > this.hView)
                    this.yView = this.followed.y - (this.hView - this.yDeadZone);
                else if(this.followed.y - this.yDeadZone < this.yView)
                    this.yView = this.followed.y - this.yDeadZone;
            }                       

        }       

        // update viewportRect
        this.viewportRect.set(this.xView, this.yView);

        // don't let camera leaves the world's boundary
        if(!this.viewportRect.within(this.worldRect))
        {
            if(this.viewportRect.left < this.worldRect.left)
                this.xView = this.worldRect.left;
            if(this.viewportRect.top < this.worldRect.top)                  
                this.yView = this.worldRect.top;
            if(this.viewportRect.right > this.worldRect.right)
                this.xView = this.worldRect.right - this.wView;
            if(this.viewportRect.bottom > this.worldRect.bottom)                    
                this.yView = this.worldRect.bottom - this.hView;
        }

    }   

    // add "class" Camera to our Game object
    Game.Camera = Camera;

})();

// wrapper for "class" Player
(function(){
    function Player(x, y){
        // (x, y) = center of object
        // ATTENTION:
        // it represents the player position on the world(room), not the canvas position
        this.x = x;
        this.y = y;             

        // move speed in pixels per second
        this.speed = 200;       

        // render properties
        this.width = 50;
        this.height = 50;
    }

    Player.prototype.update = function(step, worldWidth, worldHeight){
        // parameter step is the time between frames ( in seconds )

        // check controls and move the player accordingly
        if(Game.controls.left)
            this.x -= this.speed * step;
        if(Game.controls.up)
            this.y -= this.speed * step;
        if(Game.controls.right)
            this.x += this.speed * step;
        if(Game.controls.down)
            this.y += this.speed * step;        

        // don't let player leaves the world's boundary
        if(this.x - this.width/2 < 0){
            this.x = this.width/2;
        }
        if(this.y - this.height/2 < 0){
            this.y = this.height/2;
        }
        if(this.x + this.width/2 > worldWidth){
            this.x = worldWidth - this.width/2;
        }
        if(this.y + this.height/2 > worldHeight){
            this.y = worldHeight - this.height/2;
        }
    }

    Player.prototype.draw = function(context, xView, yView){        
        // draw a simple rectangle shape as our player model
        context.save();     
        context.fillStyle = "black";
        // before draw we need to convert player world's position to canvas position            
        context.fillRect((this.x-this.width/2) - xView, (this.y-this.height/2) - yView, this.width, this.height);
        context.restore();          
    }

    // add "class" Player to our Game object
    Game.Player = Player;

})();

// wrapper for "class" Map
(function(){
    function Map(width, height){
        // map dimensions
        this.width = width;
        this.height = height;

        // map texture
        this.image = null;
    }

    // generate an example of a large map
    Map.prototype.generate = function(){
        var ctx = document.createElement("canvas").getContext("2d");        
        ctx.canvas.width = this.width;
        ctx.canvas.height = this.height;        

        var rows = ~~(this.width/44) + 1;
        var columns = ~~(this.height/44) + 1;

        var color = "red";              
        ctx.save();         
        ctx.fillStyle = "red";          
        for (var x = 0, i = 0; i < rows; x+=44, i++) {
            ctx.beginPath();            
            for (var y = 0, j=0; j < columns; y+=44, j++) {            
                ctx.rect (x, y, 40, 40);                
            }
            color = (color == "red" ? "blue" : "red");
            ctx.fillStyle = color;
            ctx.fill();
            ctx.closePath();            
        }       
        ctx.restore();  

        // store the generate map as this image texture
        this.image = new Image();
        this.image.src = ctx.canvas.toDataURL("image/png");                 

        // clear context
        ctx = null;
    }

    // draw the map adjusted to camera
    Map.prototype.draw = function(context, xView, yView){                   
        // easiest way: draw the entire map changing only the destination coordinate in canvas
        // canvas will cull the image by itself (no performance gaps -> in hardware accelerated environments, at least)
        //context.drawImage(this.image, 0, 0, this.image.width, this.image.height, -xView, -yView, this.image.width, this.image.height);

        // didatic way:

        var sx, sy, dx, dy;
        var sWidth, sHeight, dWidth, dHeight;

        // offset point to crop the image
        sx = xView;
        sy = yView;

        // dimensions of cropped image          
        sWidth =  context.canvas.width;
        sHeight = context.canvas.height;

        // if cropped image is smaller than canvas we need to change the source dimensions
        if(this.image.width - sx < sWidth){
            sWidth = this.image.width - sx;
        }
        if(this.image.height - sy < sHeight){
            sHeight = this.image.height - sy; 
        }

        // location on canvas to draw the croped image
        dx = 0;
        dy = 0;
        // match destination with source to not scale the image
        dWidth = sWidth;
        dHeight = sHeight;                                  

        context.drawImage(this.image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);            
    }

    // add "class" Map to our Game object
    Game.Map = Map;

})();

// Game Script
(function(){
    // prepaire our game canvas
    var canvas = document.getElementById("gameCanvas");
    var context = canvas.getContext("2d");

    // game settings:   
    var FPS = 30;
    var INTERVAL = 1000/FPS; // milliseconds
    var STEP = INTERVAL/1000 // seconds

    // setup an object that represents the room
    var room = {
        width: 5000,
        height: 3000,
        map: new Game.Map(5000, 3000)
    };

    // generate a large image texture for the room
    room.map.generate();

    // setup player
    var player = new Game.Player(50, 50);

    // setup the magic camera !!!
    var camera = new Game.Camera(0, 0, canvas.width, canvas.height, room.width, room.height);       
    camera.follow(player, canvas.width/2, canvas.height/2);

    // Game update function
    var update = function(){            
        player.update(STEP, room.width, room.height);
        camera.update();
    }

    // Game draw function
    var draw = function(){
        // clear the entire canvas
        context.clearRect(0, 0, canvas.width, canvas.height);

        // redraw all objects
        room.map.draw(context, camera.xView, camera.yView);     
        player.draw(context, camera.xView, camera.yView);       
    }

    // Game Loop
    var gameLoop = function(){                      
        update();
        draw();
    }   

    // <-- configure play/pause capabilities:

    // I'll use setInterval instead of requestAnimationFrame for compatibility reason,
    // but it's easy to change that.

    var runningId = -1;

    Game.play = function(){ 
        if(runningId == -1){
            runningId = setInterval(function(){
                gameLoop();
            }, INTERVAL);
            console.log("play");
        }
    }

    Game.togglePause = function(){      
        if(runningId == -1){
            Game.play();
        }
        else
        {
            clearInterval(runningId);
            runningId = -1;
            console.log("paused");
        }
    }   

    // -->

})();

// <-- configure Game controls:

Game.controls = {
    left: false,
    up: false,
    right: false,
    down: false,
};

window.addEventListener("keydown", function(e){
    switch(e.keyCode)
    {
        case 37: // left arrow
            Game.controls.left = true;
            break;
        case 38: // up arrow
            Game.controls.up = true;
            break;
        case 39: // right arrow
            Game.controls.right = true;
            break;
        case 40: // down arrow
            Game.controls.down = true;
            break;
    }
}, false);

window.addEventListener("keyup", function(e){
    switch(e.keyCode)
    {
        case 37: // left arrow
            Game.controls.left = false;
            break;
        case 38: // up arrow
            Game.controls.up = false;
            break;
        case 39: // right arrow
            Game.controls.right = false;
            break;
        case 40: // down arrow
            Game.controls.down = false;
            break;
        case 80: // key P pauses the game
            Game.togglePause();
            break;      
    }
}, false);

// -->

// start the game when page is loaded
window.onload = function(){ 
    Game.play();
}

</script>
</body>
</html>


随时报告任何错误或添加建议。

答案 1 :(得分:20)

接受答案中的代码有点多。这很简单:

function draw() {
    ctx.setTransform(1,0,0,1,0,0);//reset the transform matrix as it is cumulative
    ctx.clearRect(0, 0, canvas.width, canvas.height);//clear the viewport AFTER the matrix is reset

    //Clamp the camera position to the world bounds while centering the camera around the player                                             
    var camX = clamp(-player.x + canvas.width/2, yourWorld.minX, yourWorld.maxX - canvas.width);
    var camY = clamp(-player.y + canvas.height/2, yourWorld.minY, yourWorld.maxY - canvas.height);

    ctx.translate( camX, camY );    

    //Draw everything
}

夹子看起来像:

function clamp(value, min, max){
    if(value < min) return min;
    else if(value > max) return max;
    return value;
}

答案 2 :(得分:5)

以下是如何使用canvas作为另一个大于画布的图像的视口

视口实际上只是显示给用户的较大图像的裁剪部分。

在这种情况下,视口将在画布上显示给用户(画布是视口)。

首先,编写一个移动函数,将视口平移到较大的图像周围。

此函数在指定方向上将视口的左上角移动5px:

function move(direction){
    switch (direction){
        case "left":
            left-=5;
            break;
        case "up":
            top-=5;
            break;
        case "right":
            left+=5;
            break;
        case "down":
            top+=5
            break;
    }
    draw(top,left);
}

move函数调用draw函数。

在draw()中,drawImage函数将裁剪较大图像的指定部分。

drawImage还会在画布上向用户显示“裁剪的背景”。

context.clearRect(0,0,game.width,game.height);
context.drawImage(background,cropLeft,cropTop,cropWidth,cropHeight,
                     0,0,viewWidth,viewHeight);

在此示例中,

背景是完整的背景图像(通常不会显示,而是裁剪的来源)

cropLeft&amp; cropTop定义裁剪开始在背景图像上的位置。

cropWidth&amp; cropHeight定义从背景图像中裁剪矩形的大小。

0,0表示从背景中裁剪的子图像将在视口画布上以0,0绘制。

viewWidth&amp; viewHeight是视口画布的宽度和高度

所以这是一个使用数字的drawImage示例。

假设我们的视口(=我们的显示画布)宽150像素,高100像素。

context.drawImage(background,75,50,150,100,0,0,150,100);

75&amp; 50表示在背景图像上裁剪将从位置x = 75 / y = 50开始。

150,100表示​​要裁剪的矩形将是150宽和100高。

0,0,150,100表示​​将使用视口画布的完整大小显示裁剪的矩形图像。

这就是绘制视口的机制......只需添加关键控件!

这是代码和小提琴:http://jsfiddle.net/m1erickson/vXqyc/

<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>

<style>
    body{ background-color: ivory; }
    canvas{border:1px solid red;}
</style>

<script>
$(function(){

    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");
    var game=document.getElementById("game");
    var gameCtx=game.getContext("2d");

    var left=20;
    var top=20;

    var background=new Image();
    background.onload=function(){
        canvas.width=background.width/2;
        canvas.height=background.height/2;
        gameCtx.fillStyle="red";
        gameCtx.strokeStyle="blue";
        gameCtx.lineWidth=3;
        ctx.fillStyle="red";
        ctx.strokeStyle="blue";
        ctx.lineWidth=3;
        move(top,left);
    }
    background.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/game.jpg";


    function move(direction){
        switch (direction){
            case "left":
                left-=5;
                break;
            case "up":
                top-=5;
                break;
            case "right":
                left+=5;
                break;
            case "down":
                top+=5
                break;
        }
        draw(top,left);
    }

    function draw(top,left){
        ctx.clearRect(0,0,canvas.width,canvas.height);
        ctx.drawImage(background,0,0,background.width,background.height,0,0,canvas.width,canvas.height);
        gameCtx.clearRect(0,0,game.width,game.height);
        gameCtx.drawImage(background,left,top,250,150,0,0,250,150);
        gameCtx.beginPath();
        gameCtx.arc(125,75,10,0,Math.PI*2,false);
        gameCtx.closePath();
        gameCtx.fill();
        gameCtx.stroke();
        ctx.beginPath();
        ctx.rect(left/2,top/2,125,75);
        ctx.stroke();
        ctx.beginPath();
        ctx.arc(left/2+125/2,top/2+75/2,5,0,Math.PI*2,false);
        ctx.stroke();
        ctx.fill();
    }

    $("#moveLeft").click(function(){move("left");}); 
    $("#moveRight").click(function(){move("right");}); 
    $("#moveUp").click(function(){move("up");}); 
    $("#moveDown").click(function(){move("down");}); 

}); // end $(function(){});
</script>

</head>

<body>
    <canvas id="game" width=250 height=150></canvas><br>
    <canvas id="canvas" width=500 height=300></canvas><br>
    <button id="moveLeft">Left</button>
    <button id="moveRight">Right</button>
    <button id="moveUp">Up</button>
    <button id="moveDown">Down</button>
</body>
</html>

答案 3 :(得分:1)

你现在的方式对我来说似乎是正确的。我会将“20”界限更改为变量,因此如果您需要,可以轻松更改关卡或整个游戏的范围。

您可以将此逻辑抽象为特定的“视口”方法,该方法可以简单地处理确定“相机”需要在地图上的位置所需的计算,然后确保角色的X和Y坐标匹配相机的中心。

您也可以翻转该方法并根据角色位置确定相机的位置(例如:(position.x - (desired_camera_size.width / 2))),然后从那里开始拍摄相机。

当您计算出相机位置时,您可以开始担心将房间本身作为画布的第一层。

答案 4 :(得分:1)

这是一个简单的问题,即在每个帧上将视口设置为目标的x和y坐标as Colton states。变换不是必需的,但可以根据需要使用。对我有用的公式是:

function update() {

  // Assign the viewport to follow a target for this frame
  viewport.x = -target.x + canvas.width / 2;
  viewport.y = -target.y + canvas.height / 2;

  // Draw each entity, including the target, relative to the viewport
  ctx.fillRect(
    entity.x + viewport.x, 
    entity.y + viewport.y,
    entity.size,
    entity.size
  );
}

第二步是抓住地图:

function update() {

  // Assign the viewport to follow a target for this frame
  viewport.x = -target.x + canvas.width / 2;
  viewport.y = -target.y + canvas.height / 2;

  // Keep viewport in map bounds
  viewport.x = clamp(viewport.x, canvas.width - map.width, 0);
  viewport.y = clamp(viewport.y, canvas.height - map.height, 0);

  // Draw each entity, including the target, relative to the viewport
  ctx.fillRect(
    entity.x + viewport.x, 
    entity.y + viewport.y,
    entity.size,
    entity.size
  );
}

// Restrict n to a range between lo and hi
const clamp = (n, lo, hi) => n < lo ? lo : n > hi ? hi : n;

以下是一个例子:https://jsfiddle.net/ggorlen/7yv7u572/

答案 5 :(得分:1)

将以下代码另存为.HTM(.html)文件,然后在浏览器中打开。

结果应与该屏幕截图完全相符。

这是一些示例代码,它们将不同大小的视口相互映射。 尽管此实现使用像素,但您可以扩展此逻辑以渲染 瓷砖。我实际上将我的图块存储为.PNG文件。取决于颜色 像素,它可以代表不同的图块类型。这里的代码旨在示例 从视口1,2或3并将其粘贴到视口0中。

Youtube Video Playlist For The Screenshot and Code Directly Below : REC_MAP

 REC_MAP.HTM ScreenShot

编辑:REC_MAP.HTM代码已移至PASTEBIN: https://pastebin.com/9hWs8Bag

第2部分:BUF_VEW.HTM(从屏幕外缓冲区采样) 我们将重构先前演示中的代码,以便 我们的源视口对屏幕外的位图进行了采样。最终 我们将把位图上的每个像素颜色解释为唯一的图块值。 我们在这段代码中没有走太远,这只是重构一个 屏幕外的视口。我在这里记录了整个过程。 没有编辑。包括我在内的整个过程花了太多时间思考 变量名。

Youtube Video Playlist For The Screenshot and Code Directly Below : BUF_VEW

BUF_VEW.HTM

像以前一样,您可以获取此源代码,将其保存为.HTM(.html)文件,然后在浏览器中运行。

编辑:BUF_VEW.HTM代码已移动到粘贴箱: https://pastebin.com/zedhD60u

第3部分:UIN_ADA.HTM(用户输入适配器和捕捉相机) 现在,我们将从以下位置编辑先前的BUF_VEW.HTM文件 第2部分,并添加2个新功能。

1:用户输入处理

2:可以放大和缩小并移动的相机。

此相机将以其自己的视口增量移动 选择区域的宽度和高度,这意味着运动将 非常“贪婪”。本相机专为进行关卡编辑而设计, 并不是真正的游戏玩法。我们专注于关卡编辑器 摄像头优先。长期的最终目标是使编辑代码 与游戏中的代码相同。唯一的区别 应该是在游戏模式下,相机的行为 与此不同,将禁用图块地图编辑。

Youtube Video Playlist For The Screenshot And Code Directly Below: UIN_ADA

 UIN_ADA.PNG

在下面复制代码,另存为:“ UIN_ADA.HTM”并在浏览器中运行。

控件:箭头&“ +”“-”用于摄​​像机的放大,缩小。

编辑:UIN_ADA.HTM已移至PASTEBIN: https://pastebin.com/ntmWihra


第4部分:DAS_BOR.HTM(DAShed_BOaRders) 我们将进行一些计算以绘制一个1像素 每个瓷砖周围的薄木板房。结果不会花哨的, 但这将帮助我们验证我们是否能够获得 每个图块的局部坐标,并使用 他们。这些图块局部坐标对于 在以后的文章中,将位图图像映射到图块上。

Youtube_Playlist: DAS_BOR.HTM Source_Code: DAS_BOR.HTM Html Page Preview: DAS_BOR.HTM

第5部分:在WebGL Canvas片段着色器代码上缩放+平移: 这是缩放和平移一个 用GLSL编写的着色器。而不是获取屏幕外的子样本 数据,我们获取gl_FragCoord值的子样本。这里的数学 允许有一个内置的屏幕视口和一个可以 缩放和平移着色器。如果您已经完成了着色器教程 由“ Lewis Lepton”制作,您想要缩放和平移它, 您可以通过此逻辑过滤他的输入坐标 应该这样做。

JavaScript Code

Quick Video Explanation Of Code

 Ascii Diagram Explaining Viewport Layouts


第6部分:ICOG.JS:DAS_BOR.HTM的WebGL2端口 要运行此脚本,您需要将脚本包含在其他脚本中 空的.HTM文件。它复制在DAS_BOR.HTM中发现的相同行为, 除了所有渲染均使用GLSL着色器代码完成。 代码中也包含完整的游戏框架。

用法:

1:按“〜”告诉主编辑器读取输入。

2:按“ 2”进入编辑器#2,即图块编辑器。

3:WASD在512x512内存子节上移动。

4:箭头键可将摄像机移至正好1个摄像机的位置。

5:使用“ +”和“-”键可更改摄像机的“缩放级别”。

尽管此代码只是将每个图块值呈现为渐变正方形, 它展示了获得正确的图块值和内部 当前正在绘制的图块的坐标。配备当地坐标 着色器代码中的图块,就可以进行基础运算 将图像映射到这些图块上。

Full JavaScript Webgl2 Code

Youtube playlist documenting creation of ICOG.JS

 Screen Shot Of ICOG.JS editor #2

//|StackOverflow Says:
//|Links to pastebin.com must be accompanied by code. Please |//
//|indent all code by 4 spaces using the code toolbar button |//
//|or the CTRL+K keyboard shortcut. For more editing help,   |//
//|click the [?] toolbar icon.                               |//
//|                                                          |//
//|StackOverflow Also Says (when I include the code here)    |//
//|You are over you 30,000 character limit for posts.        |//
function(){ console.log("[FixingStackOverflowComplaint]"); }

答案 6 :(得分:0)

@ gustavo-carvalho的解决方案是惊人的,但是它涉及大量的计算和认知开销。 @Colton的方法是朝正确方向迈出的一步;太糟糕了,他的回答还不够详尽。我接受了他的想法,并尝试创建this CodePen。它可以准确实现@ user2337969使用context.translate的要求。这样做的好处是不需要偏移任何地图或玩家坐标,因此绘制它们就像直接使用其xy一样容易,这要简单得多。

将2D摄像机视为在较大地图内平移的矩形。它的左上角在地图上的(x, y)坐标处,其大小是画布的大小,即canvas.widthcanvas.height。这意味着x的范围可以从0map.width - canvas.width,而y的范围可以是0map.height - canvas.height(含)。这些是我们引入@Colton的min方法的maxclamp

但是,要使其正常工作,我必须在xy上翻转符号,因为使用context.translate时,正值会将画布向右移动(造成幻觉,好像摄像机向左平移)和负号-向左(好像摄像机向右平移)。