Javascript HTML5画布相机/视口-如何实际操作?
我确信这是1000次之前的解决方案:我得到了一张960*560大小的画布和一个5000*3000大小的房间,根据玩家的位置,其中只有960*560应该被绘制。玩家应该总是在中间,但是当接近边界时,那么最好的视图应该被计算出来。玩家可以使用WASD或箭头键完全自由移动。所有物体都应该自己移动,而不是我移动除玩家以外的所有物体,以产生玩家移动的错觉 现在我找到了这两个问题: 工作,但只有这种类型的游戏,我不能复制我的代码 看起来更有希望,也更出色,但我只了解如何相对于播放器正确绘制所有其他对象,而不了解如何相对于播放器滚动画布视口,当然这是我首先要实现的 我的代码(简化-游戏逻辑是分开的):Javascript HTML5画布相机/视口-如何实际操作?,javascript,html,canvas,html5-canvas,viewport,Javascript,Html,Canvas,Html5 Canvas,Viewport,我确信这是1000次之前的解决方案:我得到了一张960*560大小的画布和一个5000*3000大小的房间,根据玩家的位置,其中只有960*560应该被绘制。玩家应该总是在中间,但是当接近边界时,那么最好的视图应该被计算出来。玩家可以使用WASD或箭头键完全自由移动。所有物体都应该自己移动,而不是我移动除玩家以外的所有物体,以产生玩家移动的错觉 现在我找到了这两个问题: 工作,但只有这种类型的游戏,我不能复制我的代码 看起来更有希望,也更出色,但我只了解如何相对于播放器正确绘制所有其他对象,而不
var canvas=document.getElementById(“游戏”);
canvas.tabIndex=0;
canvas.focus();
var cc=canvas.getContext(“2d”);
//定义用于在画布内滚动的视口
/*视口x位置*/视图×视图=0;
/*视口y位置*/view\u yview=0;
/*视口宽度*/view\u wview=960;
/*视口高度*/view\u hview=560;
/*扇区宽度*/房间宽度=5000;
/*扇区高度*/房间高度=3000;
canvas.width=view\u wview;
canvas.height=view\u hview;
函数绘图()
{
清除();
画框(画);
//世界的终点和视口
如果(player.x<20)player.x=20;
如果(player.y<20)player.y=20;
如果(player.x>房间宽度-20)player.x=房间宽度-20;
如果(player.y>房间高度-20)player.y=房间高度-20;
如果(player.x>view\u wview/2)?
如果(player.y>视图/2)?
}
我试图让它工作的方式感觉完全错误,我甚至不知道我是如何尝试的。。。有什么想法吗?你怎么看待上下文转换这件事
我希望你能理解我的描述,并且有人有想法。对我来说,你现在的做法似乎是正确的。不过,我会将“20”边界更改为一个变量,这样,如果您需要,您可以轻松更改一个关卡或整个游戏的边界 您可以将此逻辑抽象为特定的“视口”方法,该方法只需处理确定“摄影机”在地图上的位置所需的计算,然后确保角色的X和Y坐标与摄影机的中心匹配 您还可以翻转该方法,根据字符位置(例如:
(position.x-(所需的\u camera\u size.width/2))
)确定相机的位置,然后从那里开始绘制相机
当你弄清楚相机的位置后,你就可以开始担心把房间本身画成画布的第一层了 以下是如何使用画布作为另一个大于画布的图像上的视口 视口实际上只是向用户显示的较大图像的裁剪部分 在这种情况下,视口将在画布上显示给用户(画布就是视口) 首先,编写一个移动函数,围绕较大的图像平移视口 此函数将视口的左上角按指定方向移动5px:
function move(direction){
switch (direction){
case "left":
left-=5;
break;
case "up":
top-=5;
break;
case "right":
left+=5;
break;
case "down":
top+=5
break;
}
draw(top,left);
}
move函数调用draw函数
在draw()中,drawImage
函数将裁剪较大图像的指定部分
drawImage
还将在画布上向用户显示“裁剪的背景”
context.clearRect(0,0,game.width,game.height);
context.drawImage(background,cropLeft,cropTop,cropWidth,cropHeight,
0,0,viewWidth,viewHeight);
在这个例子中
背景是完整的背景图像(通常不显示,而是裁剪的来源)
cropLeft&cropTop定义在背景图像上开始裁剪的位置
cropWidth&cropHeight定义从背景图像裁剪出的矩形的大小
0,0表示从背景裁剪的子图像将在视口画布上以0,0绘制
viewWidth&viewHeight是视口画布的宽度和高度
下面是一个使用数字的drawImage示例
假设我们的视口(=显示画布)宽150像素,高100像素
context.drawImage(background,75,50,150,100,0,0,150,100);
75和50表示裁剪将从背景图像上的位置x=75/y=50开始
150100表示要裁剪的矩形将是150宽100高
0,0150100表示裁剪的矩形图像将使用视口画布的全尺寸显示
这就是绘制视口的机制…只需添加关键点控件
下面是代码和小提琴:
正文{背景色:象牙;}
画布{边框:1px纯红;}
$(函数(){
var canvas=document.getElementById(“canvas”);
var ctx=canvas.getContext(“2d”);
var game=document.getElementById(“游戏”);
var gameCtx=game.getContext(“2d”);
左向量=20;
var-top=20;
var background=新图像();
background.onload=function(){
canvas.width=background.width/2;
canvas.height=background.height/2;
gameCtx.fillStyle=“红色”;
gameCtx.strokeStyle=“蓝色”;
gameCtx.lineWidth=3;
ctx.fillStyle=“红色”;
ctx.strokeStyle=“蓝色”;
ctx.线宽=3;
移动(顶部,左侧);
}
background.src=”https://dl.dropboxusercontent.com/u/139992952/stackoverflow/game.jpg";
功能移动(方向){
开关(方向){
案例“左”:
左-=5;
打破
案例“up”:
top-=5;
打破
案例“权利”:
左+=5;
打破
案例“向下”:
顶部+=5
打破
}
绘制(顶部,左侧);
}
乐趣
context.drawImage(background,75,50,150,100,0,0,150,100);
<!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>
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);
<!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>
// 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);
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;
}
function update() {
// Assign the viewport to follow a target for this frame
var viewportX = canvas.width / 2 - target.x;
var viewportY = canvas.height / 2 - target.y;
// Draw each entity, including the target, relative to the viewport
ctx.fillRect(
entity.x + viewportX,
entity.y + viewportY,
entity.size,
entity.size
);
}
function update() {
// Assign the viewport to follow a target for this frame
var viewportX = canvas.width / 2 - target.x;
var viewportY = canvas.height / 2 - target.y;
// Keep viewport in map bounds
viewportX = clamp(viewportX, canvas.width - map.width, 0);
viewportY = clamp(viewportY, canvas.height - map.height, 0);
// Draw each entity, including the target, relative to the viewport
ctx.fillRect(
entity.x + viewportX,
entity.y + viewportY,
entity.size,
entity.size
);
}
// Restrict n to a range between lo and hi
function clamp(n, lo, hi) {
return n < lo ? lo : n > hi ? hi : n;
}
//|StackOverflow Says:
//|Links to pastebin.com must be accompanied by code. Please |//
//|indent all code by 4 spaces using the code toolbar button |//
//|or the CTRL+K keyboard shortcut. For more editing help, |//
//|click the [?] toolbar icon. |//
//| |//
//|StackOverflow Also Says (when I include the code here) |//
//|You are over you 30,000 character limit for posts. |//
function(){ console.log("[FixingStackOverflowComplaint]"); }