web-dev-qa-db-fra.com

Caméra / fenêtre HTML5 Canvas - comment faire?

Je suis sûr que cela a été résolu 1000 fois auparavant: j'ai une toile de 960 * 560 et une pièce de 5000 * 3000 dont toujours seulement 960 * 560 doivent être dessinées, selon l'endroit où se trouve le joueur. Le joueur doit toujours être au milieu, mais à proximité des frontières - la meilleure vue doit être calculée). Le joueur peut se déplacer entièrement gratuitement avec WASD ou les touches fléchées. Et tous les objets doivent se déplacer - au lieu de cela, je déplace tout le reste sauf le joueur pour créer l'illusion que le joueur se déplace.

J'ai maintenant trouvé ces deux questions:

HTML5 - Création d'une fenêtre d'affichage pour canvas fonctionne, mais uniquement pour ce type de jeu, je ne peux pas reproduire le code pour le mien.

Changer la vue "centre" d'une toile html5 semble être plus prometteur et aussi performant, mais je ne le comprends que pour dessiner correctement tous les autres objets par rapport au joueur et pas comment faire défiler la fenêtre de la toile par rapport au joueur, ce que je veux réaliser d'abord bien sûr.

Mon code (simplifié - la logique du jeu est séparée):

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) ... ?
}

La façon dont j'essaie de le faire fonctionner semble totalement erronée et je ne sais même pas comment je l'essaye ... Des idées? Que pensez-vous de la chose context.transform?

J'espère que vous comprenez ma description et que quelqu'un a une idée. Sincères amitiés

44
user2337969

DEMO EN DIRECT à jsfiddle.net

Cette démo illustre l'utilisation de la fenêtre d'affichage dans un scénario de jeu réel. Utilisez les touches fléchées pour déplacer le joueur dans la pièce. La grande salle est générée à la volée à l'aide de rectangles et le résultat est enregistré dans une image.

Notez que le joueur est toujours au milieu, sauf lorsqu'il est près des frontières (comme vous le souhaitez).


Maintenant, je vais essayer d'expliquer les principales parties du code, au moins les parties qui sont plus difficiles à comprendre simplement en le regardant.


Utilisation de drawImage pour dessiner de grandes images en fonction de la position de la fenêtre

Une variante de la méthode drawImage possède huit nouveaux paramètres. Nous pouvons utiliser cette méthode pour découper des parties d'une image source et les dessiner sur le canevas.

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

La première image de paramètre, comme pour les autres variantes, est soit une référence à un objet image, soit une référence à un élément de canevas différent. Pour les huit autres paramètres, il est préférable de regarder l'image ci-dessous. Les quatre premiers paramètres définissent l'emplacement et la taille de la tranche sur l'image source. Les quatre derniers paramètres définissent la position et la taille sur le canevas de destination.

Canvas drawImage

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

Comment ça marche en démo:

Nous avons une grande image qui représente la pièce et nous voulons afficher sur la toile uniquement la partie dans la fenêtre. La position de recadrage (sx, sy) est la même position de la caméra (xView, yView) et les dimensions du recadrage sont les mêmes que la fenêtre d'affichage (canevas) donc sWidth=canvas.width et sHeight=canvas.height.

Nous devons faire attention aux dimensions du recadrage car drawImage ne dessine rien sur le canevas si la position du recadrage ou les dimensions du recadrage basées sur la position ne sont pas valides. C'est pourquoi nous avons besoin des sections if ci-dessous.

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

Dessin d'objets de jeu liés à la fenêtre

Lors de l'écriture d'un jeu, il est recommandé de séparer la logique et le rendu de chaque objet du jeu. Donc en démo nous avons les fonctions update et draw. La méthode update modifie l'état de l'objet comme la position sur le "monde du jeu", applique la physique, l'état d'animation, etc. La méthode draw rend réellement l'objet et le rend correctement en considérant la fenêtre, le l'objet doit connaître le contexte de rendu et les propriétés de la fenêtre.

Notez que les objets du jeu sont mis à jour compte tenu de la position du monde du jeu. Cela signifie que la position (x, y) de l'objet est la position dans le monde. Malgré cela, étant donné que la fenêtre change, les objets doivent être rendus correctement et la position de rendu sera différente de la position du monde.

La conversion est simple:

position de l'objet dans le monde (pièce): (x, y)
position de la fenêtre d'affichage: (xView, yView)

position de rendu : (x-xView, y-yView)

Cela fonctionne pour toutes sortes de coordonnées, même négatives.


Caméra de jeu

Nos objets de jeu ont une méthode de mise à jour séparée. Dans la mise en œuvre de la démo, la caméra est traitée comme un objet de jeu et dispose également d'une méthode de mise à jour séparée.

L'objet de la caméra occupe la position supérieure gauche de la fenêtre (xView, yView), un objet à suivre, un rectangle représentant la fenêtre d'affichage, un rectangle qui représente la limite du monde du jeu et la distance minimale de chaque frontière que le joueur pourrait être avant que la caméra ne commence à se déplacer (xDeadZone, yDeadZone). Nous avons également défini les degrés de liberté de la caméra (axe). Pour les jeux de style vue de dessus, comme le RPG, la caméra est autorisée à se déplacer dans les deux axes x(horizontal) et y(vertical)).

Pour garder le joueur au milieu de la fenêtre, nous définissons la zone morte de chaque axe pour qu'elle converge avec le centre du canevas. Regardez la fonction suivante dans le code:

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

Remarque : Voir la section MISE À JOUR ci-dessous car cela ne produira pas le comportement attendu lorsqu'une dimension de la carte (pièce) est plus petite que le canevas.


Les limites du monde

Étant donné que chaque objet, y compris la caméra, a sa propre fonction de mise à jour, il est facile de vérifier les limites du monde du jeu. N'oubliez pas de mettre le code qui bloque le mouvement à la fin de la fonction de mise à jour.


Manifestation

Consultez le code complet et essayez-le vous-même. La plupart des parties du code contiennent des commentaires qui vous guident. Je suppose que vous connaissez les bases de Javascript et comment travailler avec des prototypes (parfois j'utilise le terme "classe" pour un objet prototype simplement parce qu'il a un comportement similaire à une classe dans des langages comme Java).

DEMO EN DIRECT

Code complet:

<!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: 1,
    HORIZONTAL: 2,
    VERTICAL: 3,
    BOTH: 4
  };

  // Camera constructor
  function Camera(xView, yView, viewportWidth, viewportHeight, 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 = viewportWidth;
    this.hView = viewportHeight;

    // 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;
  }

  // creates a prodedural generated map (you can use an image instead)
  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);*/

    // didactic way ( "s" is for "source" and "d" is for "destination" in the variable names):

    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: 500,
    height: 300,
    map: new Game.Map(500, 300)
  };

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

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

  // Old camera setup. It not works with maps smaller than canvas. Keeping the code deactivated here as reference.
  /* var camera = new Game.Camera(0, 0, canvas.width, canvas.height, room.width, room.height);*/
  /* camera.follow(player, canvas.width / 2, canvas.height / 2); */

  // Set the right viewport size for the camera
  var vWidth = Math.min(room.width, canvas.width);
  var vHeight = Math.min(room.height, canvas.height);

  // Setup the camera
  var camera = new Game.Camera(0, 0, vWidth, vHeight, room.width, room.height);
  camera.follow(player, vWidth / 2, vHeight / 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:

  // Using setInterval instead of requestAnimationFrame for better cross browser support,
  // but it's easy to change to a requestAnimationFrame polyfill.

  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>


[~ # ~] mise à jour [~ # ~]

Si la largeur et/ou la hauteur de la carte (pièce) est inférieure à la toile, le code précédent ne fonctionnera pas correctement. Pour résoudre ce problème, dans le script de jeu, effectuez la configuration de la caméra comme suit:

// Set the right viewport size for the camera
var vWidth = Math.min(room.width, canvas.width);
var vHeight = Math.min(room.height, canvas.height);

var camera = new Game.Camera(0, 0, vWidth, vHeight, room.width, room.height);
camera.follow(player, vWidth / 2, vHeight / 2);

Il vous suffit d'indiquer au constructeur de la caméra que la fenêtre sera la plus petite valeur entre la carte (pièce) ou le canevas. Et puisque nous voulons que le joueur soit centré et lié à cette fenêtre, le camera.follow la fonction doit également être mise à jour.


N'hésitez pas à signaler toute erreur ou à ajouter des suggestions.

80
Gustavo Carvalho

Le code dans la réponse acceptée est un peu trop. C'est aussi simple que cela:

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
}

Et la pince ressemble à:

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

Voici comment utiliser le canevas pour être une fenêtre d'affichage sur une autre image plus grande que le canevas

Une fenêtre n'est en réalité qu'une portion recadrée d'une image plus grande qui est affichée à l'utilisateur.

Dans ce cas, la fenêtre sera affichée à l'utilisateur sur un canevas (le canevas est la fenêtre).

Tout d'abord, codez une fonction de déplacement qui parcourt la fenêtre autour de l'image plus grande.

Cette fonction déplace le coin supérieur/gauche de la fenêtre de 5 pixels dans la direction spécifiée:

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

La fonction de déplacement appelle la fonction de dessin.

Dans draw (), la fonction drawImage recadrera une partie spécifiée d'une image plus grande.

drawImage affichera également cet "arrière-plan recadré" à l'utilisateur sur la toile.

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

Dans cet exemple,

L'arrière-plan est l'image d'arrière-plan complète (généralement non affichée, mais plutôt une source de recadrage)

cropLeft & cropTop définissent où sur l'image d'arrière-plan le recadrage commencera.

cropWidth & cropHeight définissent la taille d'un rectangle à recadrer à partir de l'image d'arrière-plan.

0,0 indique que la sous-image qui a été rognée de l'arrière-plan sera dessinée à 0,0 sur le canevas de la fenêtre.

viewWidth & viewHeight sont la largeur et la hauteur du canevas de la fenêtre

Voici donc un exemple de drawImage utilisant des nombres.

Supposons que notre fenêtre d'affichage (= notre canevas d'affichage) mesure 150 pixels de large et 100 pixels de haut.

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

Les 75 et 50 disent que le recadrage commencera à la position x = 75/y = 50 sur l'image d'arrière-plan.

Les 150 100 disent que le rectangle à recadrer aura 150 de large et 100 de haut.

Les 0,0,150,100 indiquent que l'image rectangle recadrée sera affichée en utilisant la taille complète du canevas de fenêtre.

C’est tout pour la mécanique de dessin d’une fenêtre… il suffit d’ajouter des raccourcis clavier!

Voici du code et un violon: 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>
5
markE

La façon dont vous vous y prenez en ce moment me semble correcte. Je changerais les limites "20" en une variable, donc vous pouvez facilement changer les limites d'un niveau ou du jeu entier si vous en avez besoin.

Vous pouvez résumer cette logique dans une méthode "Viewport" spécifique, qui gérerait simplement les calculs nécessaires pour déterminer où votre "Camera" doit être sur la carte, puis assurez-vous que les coordonnées X et Y de votre personnage correspondent au centre de ta caméra.

Vous pouvez également inverser cette méthode et déterminer l'emplacement de votre caméra en fonction de la position des caractères (par exemple: (position.x - (desired_camera_size.width / 2))) et tirez la caméra à partir de là.

Lorsque vous avez déterminé la position de votre caméra, vous pouvez commencer à vous soucier de dessiner la pièce elle-même comme première couche de votre toile.

1

Il s'agit simplement de définir la fenêtre d'affichage sur les coordonnées x et y de la cible, comme l'indique Colton , sur chaque image. Les transformations ne sont pas nécessaires mais peuvent être utilisées comme vous le souhaitez. La formule qui a fonctionné pour moi était:

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

Le serrage sur la carte est une deuxième étape facultative:

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;

Voici un exemple: https://jsfiddle.net/ggorlen/7yv7u572/

1
ggorlen

La solution de @ gustavo-carvalho est phénoménale, mais elle implique des calculs approfondis et des frais généraux cognitifs. L'approche de @ Colton est un pas dans la bonne direction; dommage qu'il n'ait pas été suffisamment élaboré dans sa réponse. J'ai pris son idée et j'ai couru avec pour créer ce CodePen . Il réalise exactement ce que @ user2337969 demande en utilisant context.translate. La beauté est que cela ne nécessite pas de compenser les coordonnées des cartes ou des joueurs, donc les dessiner est aussi facile que d'utiliser directement leurs x et y, ce qui est beaucoup plus simple.

Considérez la caméra 2D comme un rectangle qui parcourt une carte plus grande. Son coin supérieur gauche est à (x, y) coordonnées dans la carte, et sa taille est celle du canevas, c'est-à-dire canvas.width et canvas.height. Cela signifie que x peut aller de 0 à map.width - canvas.width et y de 0 à map.height - canvas.height (inclus). Ce sont min et max que nous alimentons dans la méthode clamp de @ Colton.

Pour que cela fonctionne cependant, j'ai dû retourner le signe sur x et y car avec context.translate , les valeurs positives déplacent le canevas vers la droite (faisant illusion comme si la caméra faisait un panoramique vers la gauche) et négatives - vers la gauche (comme si la caméra faisait un panoramique vers la droite).

0
Alex