w3schools name keywords description definicion content charset bootstrap javascript html5 canvas html5-canvas viewport

javascript - name - Cámara/viewport HTML5 Canvas: ¿cómo hacerlo actalmente?



viewport definicion (5)

El código en la respuesta aceptada es un poco demasiado. Es así de simple:

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 }

Y la abrazadera se ve así:

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

Estoy seguro de que esto ya fue solucionado 1000 veces antes: obtuve un lienzo del tamaño de 960 * 560 y una habitación del tamaño de 5000 * 3000 de los cuales siempre se deben sacar solo 960 * 560, dependiendo de dónde se encuentre el jugador. El jugador debe estar siempre en el medio, pero cuando se encuentre cerca de las fronteras, se debe calcular la mejor vista). El jugador puede moverse completamente gratis con WASD o las teclas de flecha. Y todos los objetos deberían moverse ellos mismos, en lugar de mover todo lo demás, excepto el jugador para crear la ilusión de que el jugador se mueve.

Ahora encontré esas dos preguntas:

HTML5: la creación de una ventana gráfica para obras de lienzo , pero solo para este tipo de juego, no puedo reproducir el código para el mío.

Cambiar la vista "centro" de un lienzo html5 parece ser más prometedor y también mejor, pero solo lo entiendo para dibujar todos los demás objetos correctamente en relación con el reproductor y no cómo desplazar el visor de lienzo en relación con el reproductor, lo que quiero para lograr primero, por supuesto.

Mi código (simplificado, la lógica del juego es por separado):

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 forma en que trato de hacerlo funcionar me parece totalmente incorrecta y ni siquiera sé cómo lo estoy intentando ... ¿Alguna idea? ¿Qué piensas del context.transform-thing?

Espero que entiendas mi descripción y que alguien tenga una idea. Saludos cordiales


Esta es una simple cuestión de establecer la ventana gráfica en las coordenadas xey del objetivo, como afirma Colton , en cada cuadro. Las transformaciones no son necesarias pero se pueden usar como se desee. La fórmula que funcionó para mí fue:

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

La sujeción al mapa es un segundo paso opcional:

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;

Aquí hay un ejemplo: https://jsfiddle.net/ggorlen/7yv7u572/


La forma en que lo estás haciendo en este momento parece correcto para mí. Sin embargo, cambiaría los límites de "20" a una variable, por lo que puedes cambiar fácilmente los límites de un nivel o todo el juego si alguna vez lo requieres.

Podría resumir esta lógica en un método específico de "Viewport", que simplemente manejaría los cálculos necesarios para determinar dónde debe estar su "Camera" en el mapa, y luego asegúrese de que las coordenadas X e Y de su personaje coincidan con el centro de tu cámara.

También puede voltear ese método y determinar la ubicación de su cámara en función de la posición de los caracteres (por ejemplo: (position.x - (desired_camera_size.width / 2)) ) y dibujar la cámara desde allí hacia afuera.

Cuando tenga la posición de la cámara resuelta, puede comenzar a preocuparse por dibujar la habitación como la primera capa de su lienzo.


DEMO EN VIVO en jsfiddle.net

Esta demostración ilustra el uso de la vista en un escenario de juego real. Use las teclas de las flechas para mover el reproductor por la habitación. La sala grande se genera sobre la marcha usando rectángulos y el resultado se guarda en una imagen.

Tenga en cuenta que el jugador siempre está en el medio, excepto cuando está cerca de las fronteras (como lo desee).

Ahora trataré de explicar las partes principales del código, al menos las partes que son más difíciles de entender simplemente mirándolo.

Usar drawImage para dibujar imágenes grandes según la posición de la ventana

Una variante del método drawImage tiene ocho nuevos parámetros. Podemos utilizar este método para cortar partes de una imagen original y dibujarlas en el lienzo.

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

La primera imagen de parámetro, al igual que con las otras variantes, es una referencia a un objeto de imagen o una referencia a un elemento de lienzo diferente. Para los otros ocho parámetros, es mejor mirar la imagen a continuación. Los primeros cuatro parámetros definen la ubicación y el tamaño del sector en la imagen de origen. Los últimos cuatro parámetros definen la posición y el tamaño en el lienzo de destino.

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

Cómo funciona en la demostración:

Tenemos una imagen grande que representa la habitación y queremos mostrar en el lienzo solo la parte dentro de la ventana gráfica. La posición de recorte (sx, sy) es la misma posición de la cámara (xView, yView) y las dimensiones de recorte son las mismas que en la ventana sWidth=canvas.width (canvas), por lo que sWidth=canvas.width y sHeight=canvas.height .

Debemos tener cuidado con las dimensiones de los cultivos porque drawImage no dibuja nada en el lienzo si la posición de recorte o las dimensiones de recorte en función de la posición no son válidas. Es por eso que necesitamos las secciones if abajo.

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

Dibujar objetos de juego relacionados con la ventana gráfica

Al escribir un juego, es una buena práctica separar la lógica y la representación de cada objeto en el juego. Entonces en la demo tenemos funciones de update y draw . El método de update cambia el estado del objeto como la posición en el "mundo del juego", aplica la física, el estado de la animación, etc. El método de draw realmente renderiza el objeto y para representarlo correctamente teniendo en cuenta la ventana gráfica, el objeto necesita conocer el contexto del renderizado y la ventana gráfica. propiedades.

Tenga en cuenta que los objetos del juego se actualizan teniendo en cuenta la posición del mundo del juego. Eso significa que la posición (x, y) del objeto es la posición en el mundo. A pesar de eso, dado que la ventana gráfica está cambiando, los objetos deben representarse correctamente y la posición del renderizado será diferente de la posición del mundo.

La conversión es simple:

posición del objeto en el mundo (habitación): (x, y)
posición de la vista: (xView, yView)

posición de render : (x-xView, y-yView)

Esto funciona para todo tipo de coordenadas, incluso las negativas.

Cámara del juego

Nuestros objetos del juego tienen un método de actualización separado. En la implementación de demostración, la cámara se trata como un objeto de juego y también tiene un método de actualización separado.

El objeto de la cámara tiene la posición superior izquierda de la ventana (xView, yView) , un objeto a seguir, un rectángulo que representa la ventana gráfica, un rectángulo que representa el límite del mundo del juego y la distancia mínima de cada borde mover (xDeadZone, yDeadZone). También definimos los grados de libertad de la cámara (eje). Para un juego de estilo de vista superior como un RPG, la cámara puede moverse tanto en el eje x (horizontal) como en el eje y (vertical).

Para mantener al jugador en el medio de la ventana gráfica, configuramos la zona muerta de cada eje para que converja con el centro del lienzo. Mira la siguiente función en el código:

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

Los límites del mundo

Como cada objeto, incluida la cámara, tiene su propia función de actualización, es fácil verificar el límite del mundo del juego. Solo recuerde poner el código que bloquea el movimiento al final de la función de actualización.

Demostración

Vea el código completo y pruébelo usted mismo. Mucho mejor que explicar con palabras. Tal vez después de leer el código, esta gran cantidad de información será aclarada.

DEMO EN VIVO

Código completo:

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


No dude en informar cualquier error o agregar sugerencias.


A continuación, se indica cómo usar el lienzo para que sea una ventana gráfica en otra imagen más grande que el lienzo

Una ventana gráfica es en realidad solo una parte recortada de una imagen más grande que se muestra al usuario.

En este caso, la ventana gráfica se mostrará al usuario en un lienzo (el lienzo es la ventana gráfica).

Primero, codifique una función de movimiento que pase la ventana gráfica alrededor de la imagen más grande.

Esta función mueve la esquina superior / izquierda de la ventana gráfica en 5px en la dirección especificada:

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 función de movimiento llama a la función dibujar.

En draw (), la función drawImage recortará una parte especificada de una imagen más grande.

drawImage también mostrará ese "fondo recortado" para el usuario en el lienzo.

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

En este ejemplo,

El fondo es la imagen de fondo completa (generalmente no se muestra, pero es más bien una fuente de recorte)

cropLeft & cropTop define en qué parte de la imagen de fondo comenzará el recorte.

cropWidth & cropHeight define qué tan grande se recortará un rectángulo de la imagen de fondo.

0,0 decir que la imagen secundaria que se ha recortado del fondo se dibujará en 0,0 en el lienzo de la ventana gráfica.

viewWidth y viewHeight son el ancho y alto del lienzo de la ventana gráfica

Así que aquí hay un ejemplo de drawImage usando números.

Digamos que nuestra ventana gráfica (= nuestro lienzo de pantalla) tiene 150 píxeles de ancho y 100 píxeles de alto.

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

Los 75 y 50 dicen que el recorte comenzará en la posición x = 75 / y = 50 en la imagen de fondo.

Los 150.100 dicen que el rectángulo que se cortará tendrá 150 de ancho y 100 de alto.

Los 0,0,150,100 dicen que la imagen rectangular recortada se mostrará con el tamaño completo del lienzo de la ventana gráfica.

¡Eso es todo por la mecánica de dibujar una ventana gráfica ... simplemente agregue controles clave!

Aquí hay un código y un violín: 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//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>