HTML5 Canvas камера/акно прагляду - як выношваў гэта зрабіць?

Я ўпэўнены, што гэта быў solven 1000 разоў да таго: я атрымаў палатно ў памеры 960 * 560 і нумар у памеры 5000 * 3000 з якіх заўсёды павінна быць звернута толькі 960 * 560, у залежнасці ад таго, дзе гулец. Гулец павінен быць заўсёды ў цэнтры, але калі побач з межамі - то лепшы выгляд павінен быць разлічаны). Гулец можа рухацца зусім свабодна з WASD або клавішы са стрэлкамі. І ўсе аб'екты павінны рухацца самі - замест таго, каб я перамясціць ўсё астатняе, акрамя гульца, каб стварыць ілюзію, што гулец рухаецца.

Цяпер я знайшоў гэтыя два quesitons:

HTML5 - Creating a viewport for canvas works, but only for this type of game, i can't reproduce the code for mine.

Changing the view "center" of an html5 canvas seems to be more promising and also perfomant, but i only understand it for drawing all other objects correctly relative to the player and not how to scroll the canvas viewport relative to the player, which i want to achieve first of course.

Мой код (спрошчана - гульня логіка асобна):

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-рэчы?

Я спадзяюся, вы разумееце маё апісанне, і што хто-то ёсць ідэя. З найлепшымі пажаданнямі

37

7 адказы

<�моцны> Прамы DEMO at jsfiddle.net

Гэтая дэманстрацыйная ілюструе выкарыстанне краявідных ў рэальным сцэнары гульні. З дапамогай клавіш са стрэлкамі для перамяшчэння гульца па пакоі. Вялікі пакой генеруецца на лета, выкарыстоўваючы прастакутнікі і вынік захоўваецца ў выглядзе малюнка.

Звярніце ўвагу на тое, што гулец заўсёды знаходзіцца ў сярэдзіне, за выключэннем, калі побач з межамі (як вы хочаце).


Зараз я паспрабую растлумачыць асноўныя часткі кода, па меншай меры, часткі, якія больш цяжка зразумець, проста гледзячы на ​​яго.


Выкарыстанне DrawImage маляваць вялікія выявы ў адпаведнасці са становішчам акна прагляду

Варыянт метаду DrawImage мае восем новых параметраў. Мы можам выкарыстоўваць гэты метад, каб нарэзаць часткі зыходнага малюнка і зрабіць іх на палатно.

<�Р> DrawImage (малюнак, Sx, Sy, sWidth, sHeight, дх, ду, dWidth, dHeight) </р>

Першы параметр малюнак, гэтак жа як і з іншымі варыянтамі, з'яўляецца альбо спасылкай на аб'ект малюнка або спасылку на іншы палатно элемент. Для астатніх васьмі параметраў, лепш глядзець на малюнку ніжэй. Першыя чатыры параметру вызначаюць размяшчэнне і памер зрэзу на зыходным малюнку. Апошнія чатыры параметру вызначаюць становішча і памер на палатне прызначэння.

Canvas drawImage

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

<�Моцны> Як гэта працуе ў дэма:

У нас ёсць вялікае выява, якое прадстаўляе нумар, і мы хочам паказаць на палатне толькі частка ўнутры краявіднага экрана. Становішча культур (Sx, Sy) адно і тое ж становішча камеры (XView, yView) і памераў культур такія ж, як акно прагляду (палатно), так што sWidth = canvas.width і sHeight = canvas.height .

Мы павінны клапаціцца аб памерах ўраджаю, таму што DrawImage нічога не малюе на палатне, калі памеры пазіцыі культур або культур, заснаваныя на пазіцыі, з'яўляюцца несапраўднымі. Вось чаму нам трэба , калі секцыі ніжэй.

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);

Малюнак гульні аб'екты, звязаныя з краявідным

Пры напісанні гульні гэта добрая практыка падзелу логікі і рэндэрынгу для кожнага аб'екта ў гульні. Такім чынам, у дэма мы маем Абнаўленне і маляваныя <�код /> функцыі. <�Код> Абнаўленне змены метаду аб'екта статус як пазіцыі на «гульнявым свеце», прымяніць фізіку, анімацыю стану і г.д. маляваныя метад на самай справе робяць аб'ект і зрабіць гэта правільна, улічваючы краявідная акно, аб'ект павінен ведаць рэндэру кантэкст і ўласцівасці краявіднага экрана.

Звярніце ўвагу, што гульнявыя аб'екты абнаўляюцца з улікам пазіцыі гульнявога свету. Гэта азначае, што становішча (х, у) аб'екта з'яўляецца становішча ў свеце. Нягледзячы на ​​гэта, так як акно прагляду мяняецца, аб'екты павінны быць аказаны належным чынам, і пазіцыя візуалізацыі будзе адрознівацца ад пазіцыі ў свеце.

Пераўтварэнне проста:

object position in world(room): (x, y)
viewport position: (xView, yView)

render position: (x-xView, y-yView)

Гэта працуе для ўсіх відаў каардынатаў, нават адмоўных.


гульня камеры

Нашы гульнявыя аб'екты маюць аддзелены спосаб абнаўлення. У рэалізацыі дэманстрацыйнай камера разглядаюцца як аб'ект гульні, а таксама метады разнесеных абнаўлення.

Аб'ект камеры трымае левую верхнюю пазіцыю краявіднага (Xview, yView) , аб'ект для пераймання, прастакутнік, якое прадстаўляе акно прагляду, прастакутнік, які ўяўляе сабой мяжу гульнявога міру і мінімальная адлегласць ад кожнай мяжы што гулец можа быць, перш чым пачнецца перасоўванне камеры (xDeadZone, yDeadZone). Акрамя таго, мы вызначылі ступень камеры волі (восі). Для верхняй гульні выгляд стылю, як RPG камера можа перамяшчацца ў абодвух х (па гарызанталі) і ў (вертыкальнай) восі.

Для таго, каб трымаць гульца ў сярэдзіне акна прагляду мы ўсталёўваем мёртвую зону кожнай восі сыходзіцца з цэнтрам палатна. Паглядзіце на функцыі назірання ў кодзе:

<�Р> camera.follow (гулец, canvas.width/2, canvas.height/2) </р>

сусветныя межы

Паколькі кожны аб'ект, у тым ліку камеры, мае сваю ўласную функцыю абнаўлення, яго лёгка праверыць мяжы гульнявога свету. Толькі не забудзьцеся паставіць код, які блакуе рух у фінале функцыі абнаўлення.


дэманстрацыя

Глядзіце поўны код і паспрабуйце самі. Нашмат лепш, чым растлумачыць словамі. Можа быць, пасля чытання кода, гэта шмат інфармацыі будзе удакладняцца.

<�моцны> Прамы DEMO

Поўны код:

<!DOCTYPE HTML>
<html>
<body>
<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>


Feel free to report any errors or to add suggestions.

74
дададзена
Біт позна да партыі, але як адладжваць код, як гэта? Проста з падпальшчык ці ў вас ёсць іншыя інструменты?
дададзена аўтар Madmenyo, крыніца
@Zahrec прабачце, я гэтую працу толькі ў JS і HTML5 палатно. Ва ўсякім выпадку, паспрабуйце задаць новае пытанне на SO. Я ўпэўнены, што вы можаце атрымаць дапамогу ад супольнасці.
дададзена аўтар Gustavo Carvalho, крыніца
@HoneyBadger: Глядзіце адрэдагаваны скрыпку: jsfiddle.net/gfcarv/tAwQV Выбачайце за гэтую велізарную затрымку ў адказ.
дададзена аўтар Gustavo Carvalho, крыніца
Ці ёсць у вас выпадкова ёсць падобны код камеры, напісаны на Java?
дададзена аўтар AndyCini, крыніца
Дзякуй за ваш адказ.
дададзена аўтар AndyCini, крыніца
@ Густава-Карвалью, не маглі б вы ласкава паказаць, як ваш код будзе працаваць, выкарыстоўваючы requestAnimationFrame замест гэтага? Дзякуй!
дададзена аўтар Honey Badger, крыніца
Я паспрабаваў дэма, а таксама стварыў свой уласны, выкарыстоўваючы свой метад. Але ёсць памылка. Кожны раз, калі рух камеры абнаўляецца і clearRect называецца белыя лініі мігцелі на палатне.
дададзена аўтар Zoe Lynn, крыніца
Вы геній! :) Хоць я не разумею код, таму што я паняцця не маю, як прататыпы працаваць і гэтак далей ... Але гэта не твая віна. Вы пышныя і я павінен навучыцца, як рэалізаваць гэта ў маю гульню. Дзякуючы кучу! : D
дададзена аўтар user2337969, крыніца

<�моцны> Прамы DEMO at jsfiddle.net

Гэтая дэманстрацыйная ілюструе выкарыстанне краявідных ў рэальным сцэнары гульні. З дапамогай клавіш са стрэлкамі для перамяшчэння гульца па пакоі. Вялікі пакой генеруецца на лета, выкарыстоўваючы прастакутнікі і вынік захоўваецца ў выглядзе малюнка.

Звярніце ўвагу на тое, што гулец заўсёды знаходзіцца ў сярэдзіне, за выключэннем, калі побач з межамі (як вы хочаце).


Зараз я паспрабую растлумачыць асноўныя часткі кода, па меншай меры, часткі, якія больш цяжка зразумець, проста гледзячы на ​​яго.


Выкарыстанне DrawImage маляваць вялікія выявы ў адпаведнасці са становішчам акна прагляду

Варыянт метаду DrawImage мае восем новых параметраў. Мы можам выкарыстоўваць гэты метад, каб нарэзаць часткі зыходнага малюнка і зрабіць іх на палатно.

<�Р> DrawImage (малюнак, Sx, Sy, sWidth, sHeight, дх, ду, dWidth, dHeight) </р>

Першы параметр малюнак, гэтак жа як і з іншымі варыянтамі, з'яўляецца альбо спасылкай на аб'ект малюнка або спасылку на іншы палатно элемент. Для астатніх васьмі параметраў, лепш глядзець на малюнку ніжэй. Першыя чатыры параметру вызначаюць размяшчэнне і памер зрэзу на зыходным малюнку. Апошнія чатыры параметру вызначаюць становішча і памер на палатне прызначэння.

Canvas drawImage

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

<�Моцны> Як гэта працуе ў дэма:

У нас ёсць вялікае выява, якое прадстаўляе нумар, і мы хочам паказаць на палатне толькі частка ўнутры краявіднага экрана. Становішча культур (Sx, Sy) адно і тое ж становішча камеры (XView, yView) і памераў культур такія ж, як акно прагляду (палатно), так што sWidth = canvas.width і sHeight = canvas.height .

Мы павінны клапаціцца аб памерах ўраджаю, таму што DrawImage нічога не малюе на палатне, калі памеры пазіцыі культур або культур, заснаваныя на пазіцыі, з'яўляюцца несапраўднымі. Вось чаму нам трэба , калі секцыі ніжэй.

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);

Малюнак гульні аб'екты, звязаныя з краявідным

Пры напісанні гульні гэта добрая практыка падзелу логікі і рэндэрынгу для кожнага аб'екта ў гульні. Такім чынам, у дэма мы маем Абнаўленне і маляваныя <�код /> функцыі. <�Код> Абнаўленне змены метаду аб'екта статус як пазіцыі на «гульнявым свеце», прымяніць фізіку, анімацыю стану і г.д. маляваныя метад на самай справе робяць аб'ект і зрабіць гэта правільна, улічваючы краявідная акно, аб'ект павінен ведаць рэндэру кантэкст і ўласцівасці краявіднага экрана.

Звярніце ўвагу, што гульнявыя аб'екты абнаўляюцца з улікам пазіцыі гульнявога свету. Гэта азначае, што становішча (х, у) аб'екта з'яўляецца становішча ў свеце. Нягледзячы на ​​гэта, так як акно прагляду мяняецца, аб'екты павінны быць аказаны належным чынам, і пазіцыя візуалізацыі будзе адрознівацца ад пазіцыі ў свеце.

Пераўтварэнне проста:

object position in world(room): (x, y)
viewport position: (xView, yView)

render position: (x-xView, y-yView)

Гэта працуе для ўсіх відаў каардынатаў, нават адмоўных.


гульня камеры

Нашы гульнявыя аб'екты маюць аддзелены спосаб абнаўлення. У рэалізацыі дэманстрацыйнай камера разглядаюцца як аб'ект гульні, а таксама метады разнесеных абнаўлення.

Аб'ект камеры трымае левую верхнюю пазіцыю краявіднага (Xview, yView) , аб'ект для пераймання, прастакутнік, якое прадстаўляе акно прагляду, прастакутнік, які ўяўляе сабой мяжу гульнявога міру і мінімальная адлегласць ад кожнай мяжы што гулец можа быць, перш чым пачнецца перасоўванне камеры (xDeadZone, yDeadZone). Акрамя таго, мы вызначылі ступень камеры волі (восі). Для верхняй гульні выгляд стылю, як RPG камера можа перамяшчацца ў абодвух х (па гарызанталі) і ў (вертыкальнай) восі.

Для таго, каб трымаць гульца ў сярэдзіне акна прагляду мы ўсталёўваем мёртвую зону кожнай восі сыходзіцца з цэнтрам палатна. Паглядзіце на функцыі назірання ў кодзе:

<�Р> camera.follow (гулец, canvas.width/2, canvas.height/2) </р>

сусветныя межы

Паколькі кожны аб'ект, у тым ліку камеры, мае сваю ўласную функцыю абнаўлення, яго лёгка праверыць мяжы гульнявога свету. Толькі не забудзьцеся паставіць код, які блакуе рух у фінале функцыі абнаўлення.


дэманстрацыя

Глядзіце поўны код і паспрабуйце самі. Нашмат лепш, чым растлумачыць словамі. Можа быць, пасля чытання кода, гэта шмат інфармацыі будзе удакладняцца.

<�моцны> Прамы DEMO

Поўны код:

<!DOCTYPE HTML>
<html>
<body>
<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>


Feel free to report any errors or to add suggestions.

74
дададзена
Біт позна да партыі, але як адладжваць код, як гэта? Проста з падпальшчык ці ў вас ёсць іншыя інструменты?
дададзена аўтар Madmenyo, крыніца
@Zahrec прабачце, я гэтую працу толькі ў JS і HTML5 палатно. Ва ўсякім выпадку, паспрабуйце задаць новае пытанне на SO. Я ўпэўнены, што вы можаце атрымаць дапамогу ад супольнасці.
дададзена аўтар Gustavo Carvalho, крыніца
@HoneyBadger: Глядзіце адрэдагаваны скрыпку: jsfiddle.net/gfcarv/tAwQV Выбачайце за гэтую велізарную затрымку ў адказ.
дададзена аўтар Gustavo Carvalho, крыніца
Ці ёсць у вас выпадкова ёсць падобны код камеры, напісаны на Java?
дададзена аўтар AndyCini, крыніца
Дзякуй за ваш адказ.
дададзена аўтар AndyCini, крыніца
@ Густава-Карвалью, не маглі б вы ласкава паказаць, як ваш код будзе працаваць, выкарыстоўваючы requestAnimationFrame замест гэтага? Дзякуй!
дададзена аўтар Honey Badger, крыніца
Я паспрабаваў дэма, а таксама стварыў свой уласны, выкарыстоўваючы свой метад. Але ёсць памылка. Кожны раз, калі рух камеры абнаўляецца і clearRect называецца белыя лініі мігцелі на палатне.
дададзена аўтар Zoe Lynn, крыніца
Вы геній! :) Хоць я не разумею код, таму што я паняцця не маю, як прататыпы працаваць і гэтак далей ... Але гэта не твая віна. Вы пышныя і я павінен навучыцца, як рэалізаваць гэта ў маю гульню. Дзякуючы кучу! : D
дададзена аўтар user2337969, крыніца

Код у прынятым адказе зашмат. Яго гэта проста:

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;
}
18
дададзена
Хоць, код заціск павінен быў следам нешта больш ўздоўж ліній тут: stackoverflow.com/questions/11409895/…
дададзена аўтар Andrew, крыніца
@AaronHarding Гэта на самай справе вельмі зразумела. У асноўным, палатно пастаўляецца з наборам функцый, якія могуць маніпуляваць малюнак на фактычным палатне. IE: паварот, маштабаванне, перакладаць і г.д. Ён выкарыстоўвае іх у сваіх інтарэсах, каб перамясціць ўвесь свет (і гэта значна больш інтуітыўным). У прынцыпе, зрабіць палатно Маніпуляцыі скіду для нармальнага малюнка. Высветліць, дзе зрабіць каардынаты камеры, і, нарэшце, перавесці ўвесь чарцёж пазіцыі.
дададзена аўтар Andrew, крыніца
Было б выдатна, калі б мы маглі бачыць гэта, загорнутыя ў рэальны свет -example. Можа быць, на jsfiddle.net або jsbin.com ?!
дададзена аўтар yckart, крыніца
@Colton ці можна спыніцца на гэтым?
дададзена аўтар bitten, крыніца
Я не ўпэўнены, што yourWorld ў гэтым выпадку. У мяне ёсць два палатна, гэтыя толькі лічбы? Demoable прыклад можа быць выратавальнікам.
дададзена аўтар Johannes, крыніца
Дзякуй, гэта лягчэй зразумець, з кароткім скрыптам!
дададзена аўтар leonziyo, крыніца

<�Моцны> Вось як выкарыстоўваць палатно, каб быць акно прагляду на іншым большым, чым палатно малюнак

Вобласць прагляду сапраўды проста абразаецца частка больш буйнога малюнка, які адлюстроўваецца карыстальніку.

У гэтым выпадку акно прагляду будзе адлюстроўвацца карыстачу на палатне (палатно акна прагляду).

Па-першае, код функцыі перасоўванне, панарамаванне акно прагляду вакол павелічэння малюнка.

Гэта функцыя перамяшчае верхні/левы кут акна прагляду з дапамогай 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);
}

Функцыя руху выклікае функцыю выцяжкі.

У дро (), то DrawImage функцыя будзе абрэзаць паказаную частка большага малюнка.

drawImage will also display that “cropped background” to the user on the canvas.

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

У гэтым прыкладзе,

Фон з'яўляецца поўным фонавым малюнкам (звычайна не адлюстроўваецца, але гэта хутчэй крыніца для вырошчвання сельскагаспадарчых культур)

cropLeft & cropTop define where on the background image the cropping will begin.

cropWidth & cropHeight define how large a rectangle will be cropped from the background image.

0,0 кажуць пра тое, што суб-малюнак, якое было абрэзанае ад фону будзе звернута на 0,0 на палатне акна прагляду.

viewWidth & viewHeight are the width and height of the viewport canvas

Дык вось прыклад DrawImage з дапамогай лікаў.

Скажам, наш відавы экран (= наш сьпіс палатно) 150 пікселяў у шырыню і 100 пікселяў.

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

The 75 & 50 say that cropping will start at position x=75/y=50 on the background image.

150100 сказаць, што прастакутнік абрэзаны будзе 150 шырокіх і 100 высокімі.

0,0,150,100 сказаць, што абрэзаныя прастакутнік малюнка будзе адлюстроўвацца з выкарыстаннем поўнага памеру акна прагляду палатна.

Вось гэта для механікі малявання акна прагляду ... проста дадаць ключавыя элементы кіравання!

Here is code and a Fiddle: 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>

<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>
    


<button id="moveLeft">Left</button> <button id="moveRight">Right</button> <button id="moveUp">Up</button> <button id="moveDown">Down</button> </body> </html>
5
дададзена
Вельмі дакладна (я ведаў пра гэта). Код прызначаны ў якасці зыходнага прыкладу, а не як вытворчага кода. Дзякуй за даданне да абмеркавання, хоць. :)
дададзена аўтар markE, крыніца
+1 Nice тлумачэнне. Тым не менш, калі каля граніц, акно прагляду не павінна змяніцца, і гулец павінен адысці ад сярэдзіны.
дададзена аўтар Gustavo Carvalho, крыніца

<�Моцны> Вось як выкарыстоўваць палатно, каб быць акно прагляду на іншым большым, чым палатно малюнак

Вобласць прагляду сапраўды проста абразаецца частка больш буйнога малюнка, які адлюстроўваецца карыстальніку.

У гэтым выпадку акно прагляду будзе адлюстроўвацца карыстачу на палатне (палатно акна прагляду).

Па-першае, код функцыі перасоўванне, панарамаванне акно прагляду вакол павелічэння малюнка.

Гэта функцыя перамяшчае верхні/левы кут акна прагляду з дапамогай 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);
}

Функцыя руху выклікае функцыю выцяжкі.

У дро (), то DrawImage функцыя будзе абрэзаць паказаную частка большага малюнка.

drawImage will also display that “cropped background” to the user on the canvas.

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

У гэтым прыкладзе,

Фон з'яўляецца поўным фонавым малюнкам (звычайна не адлюстроўваецца, але гэта хутчэй крыніца для вырошчвання сельскагаспадарчых культур)

cropLeft & cropTop define where on the background image the cropping will begin.

cropWidth & cropHeight define how large a rectangle will be cropped from the background image.

0,0 кажуць пра тое, што суб-малюнак, якое было абрэзанае ад фону будзе звернута на 0,0 на палатне акна прагляду.

viewWidth & viewHeight are the width and height of the viewport canvas

Дык вось прыклад DrawImage з дапамогай лікаў.

Скажам, наш відавы экран (= наш сьпіс палатно) 150 пікселяў у шырыню і 100 пікселяў.

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

The 75 & 50 say that cropping will start at position x=75/y=50 on the background image.

150100 сказаць, што прастакутнік абрэзаны будзе 150 шырокіх і 100 высокімі.

0,0,150,100 сказаць, што абрэзаныя прастакутнік малюнка будзе адлюстроўвацца з выкарыстаннем поўнага памеру акна прагляду палатна.

Вось гэта для механікі малявання акна прагляду ... проста дадаць ключавыя элементы кіравання!

Here is code and a Fiddle: 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>

<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>
    


<button id="moveLeft">Left</button> <button id="moveRight">Right</button> <button id="moveUp">Up</button> <button id="moveDown">Down</button> </body> </html>
5
дададзена
Вельмі дакладна (я ведаў пра гэта). Код прызначаны ў якасці зыходнага прыкладу, а не як вытворчага кода. Дзякуй за даданне да абмеркавання, хоць. :)
дададзена аўтар markE, крыніца
+1 Nice тлумачэнне. Тым не менш, калі каля граніц, акно прагляду не павінна змяніцца, і гулец павінен адысці ад сярэдзіны.
дададзена аўтар Gustavo Carvalho, крыніца

Гэта простае пытанне ўстаноўкі акна прагляду для х мэты і ў каардынаты, , як Колтон сцвярджае , на кожным кадры. Трансфармацыі не з'яўляюцца неабходнымі, але могуць быць выкарыстаны па жаданні. Формула, якая працавала для мяне было:

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;

Here's an example: https://jsfiddle.net/ggorlen/7yv7u572/

0
дададзена

Тое, як вы збіраецеся пра гэта прама цяпер, здаецца правільным для мяне. Я хацеў бы змяніць «20» мяжы да зменнай, хоць, так што вы можаце лёгка змяніць межы ўзроўню або ўсю гульню, калі вы калі-небудзь спатрэбіцца так.

Вы маглі б абстрактныя гэтую логіку ў пэўны метад «Viewport», што было б проста апрацоўваць разлікі, неабходныя для вызначэння, дзе ваша «камера» павінна быць на карце, а затым пераканайцеся, што X і Y каардынаты вашага персанажа супадаюць з цэнтрам камера.

Можна таксама пстрыкнуць гэты метад і вызначыць месцазнаходжанне камеры на аснове пазіцыі сімвалаў (напрыклад: (position.x - (desired_camera_size.width/2)) ) і зрабіць камеру адтуль з ,

Калі ў вас ёсць ваша пазіцыя камеры высвятляла, вы можаце пачаць турбавацца аб маляванні памяшкання сябе ў якасці першага пласта палатна.

0
дададзена