HTML5 Canvas相机/视口 - 如何做到这一点? [英] HTML5 Canvas camera/viewport - how to actally do it?

查看:187
本文介绍了HTML5 Canvas相机/视口 - 如何做到这一点?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我相信这是1000次之前的解决:我有一个大小为960 * 560的画布和一个大小为5000 * 3000的房间,其中总是只有960 * 560应该绘制,取决于在哪里玩家是。玩家应该总是在中间,但当接近边界时,则应计算最佳视图)。玩家可以完全免费使用WASD或箭头键。所有对象应该移动自己 - 而不是我移动的一切,但玩家创造的幻觉的玩家移动。



我现在发现这两个quesitons:





在演示中如何工作



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



我们需要注意裁剪尺寸,因为 drawImage 在裁片位置或裁剪尺寸基于位置无效。这就是为什么我们需要 if 部分。

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

//偏移指向裁剪图像
sx = xView;
sy = yView;

//裁剪后的图像的尺寸
sWidth = context.canvas.width;
sHeight = context.canvas.height;

//如果裁剪的图像小于canvas,我们需要更改源维度
if(image.width - sx< sWidth){
sWidth = image.width - sx;
}
if(image.height - sy< sHeight){
sHeight = image.height - sy;
}

//在画布上定位以绘制裁剪后的图像
dx = 0;
dy = 0;
//匹配目标与源以不缩放图像
dWidth = sWidth;
dHeight = sHeight;

//绘制裁剪后的图像
context.drawImage(image,sx,sy,sWidth,sHeight,dx,dy,dWidth,dHeight);






绘制与视口相关的游戏物件



当写游戏时,这是一个很好的练习,在游戏中为每个对象分别进行逻辑和渲染。所以在demo中我们有 update draw 函数。 更新方法更改对象状态,如游戏世界上的位置,应用物理,动画状态等。 draw 方法实际上渲染对象,并考虑到视口适当地渲染它,对象需要知道渲染上下文和视口属性。



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



转换很简单:



在世界(房间)中的对象位置:(x,y)

视口位置:(xView,yView)



渲染位置 xView,y-yView)



这适用于所有类型的坐标,即使是负的坐标。






游戏相机



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



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



视口我们设置每个轴的deadZone与画布的中心会合。看看代码中的follow函数:


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






世界的限制



由于每个对象,包括相机,都有自己的更新功能,它容易检查游戏世界的边界。只需记住在更新函数的最后将阻止运动的代码。






演示



查看完整代码并自行尝试。比用词语解释更好。也许在阅读代码后,这很多信息将被澄清。



LIVE DEMO



完整代码:

 <!DOCTYPE HTML> 
< html>
< body>
< canvas id =gameCanvaswidth = 400 height = 400 />
< script>
//我们的游戏类,方法和对象的包装
window.Game = {};

//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& $ amp;
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);
}

//将class矩形添加到我们的Game对象
Game.Rectangle = Rectangle;
})();

//class的封装相机(避免全局对象)
(function(){

//可能轴移动相机
var AXIS = {
NONE:none,
HORIZONTAL:horizo​​ntal,
VERTICAL:vertical,
BOTH:both
};

//相机构造函数
function相机(xView,yView,canvasWidth,canvasHeight,worldWidth,worldHeight)
{
//相机位置(左上坐标)
this.xView = xView || 0;
this.yView = yView || 0;

//相机开始移动之前从物体到边界的距离
this.xDeadZone = 0; //最小到水平边框的距离
this.yDeadZone = 0; //最小到垂直边框的距离

//视口尺寸
this.wView = canvasWidth;
this.hView = canvasHeight;

//允许摄像机在垂直和水平轴上移动
this.axis = AXIS.BOTH;

//应该遵循的对象
this.followed = null;

//表示视口的矩形
this.viewportRect = new Game.Rectangle(this.xView,this.yView,this.wView,this.hView);

//代表世界边界的矩形(房间边界)
this.worldRect = new Game.Rectangle(0,0,worldWidth,worldHeight);

}

// gameObject需要具有x和y属性(作为世界(或房间)位置)
Camera.prototype.follow =函数(gameObject,xDeadZone,yDeadZone)
{
this.followed = gameObject;
this.xDeadZone = xDeadZone;
this.yDeadZone = yDeadZone;
}

Camera.prototype.update = function()
{
//跟随播放器(或其他所需的对象)
if .followed!= null)
{
if(this.axis == AXIS.HORIZONTAL || this.axis == AXIS.BOTH)
{
//移动相机水平轴基于跟随的对象位置
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)
{
//垂直轴移动摄像机基于跟随的对象位置
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;
}

}

//更新viewportRect
this.viewportRect.set(this.xView,this.yView);

//不让相机离开世界的边界
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;
}

}

//将class添加到我们的游戏对象
Game.Camera = Camera;

})();

//class的wrapper Player
(function(){
function Player(x,y){
//(x,y)= center的对象
//注意:
//它代表玩家在世界(房间)上的位置,而不是画布位置
this.x = x;
this.y = y;

//以像素每秒的速度移动
this.speed = 200;

//渲染属性
this.width = 50;
this.height = 50;
}

Player.prototype.update = function(step,worldWidth,worldHeight){
//参数步长是框架(以秒计)

//检查控制并相应地移动播放器
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;

//不让玩家离开世界的边界
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){
//绘制一个简单的矩形形状作为我们的玩家模型
context.save();
context.fillStyle =black;
//绘制前,我们需要将玩家世界的位置转换为画布位置
context.fillRect((this.x-this.width / 2) - xView,(this.y-this.height / 2 ) - yView,this.width,this.height);
context.restore();
}

//将classPlayer添加到我们的Game对象
Game.Player = Player;

})();

//class的包装器映射
(function(){
function Map(width,height){
// map dimensions
this .width = width;
this.height = height;

//地图纹理
this.image = null;
}

//生成大型地图的示例
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 ,j = 0; j ctx.rect(x,y,40,40);
}
color =(color ==red?blue:red);
ctx.fillStyle = color;
ctx.fill();
ctx.closePath();
}
ctx.restore();

//将生成的映射存储为此图像纹理
this.image = new Image();
this.image.src = ctx.canvas.toDataURL(image / png);

//清除上下文
ctx = null;
}

//绘制调整为相机的地图
Map.prototype.draw = function(context,xView,yView){
//最简单的方法:draw整个地图只改变画布中的目标坐标
// canvas将自己选择图像(至少在硬件加速环境中没有性能差距 - >)
//context.drawImage(this。 image,0,0,this.image.width,this.image.height,-xView,-yView,this.image.width,this.image.height);

// didatic方法:

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

//偏移指向裁剪图像
sx = xView;
sy = yView;

//裁剪后的图像的尺寸
sWidth = context.canvas.width;
sHeight = context.canvas.height;

//如果裁剪的图像小于canvas,我们需要更改源维度
if(this.image.width - sx< sWidth){
sWidth = this。 image.width - sx;
}
if(this.image.height - sy< sHeight){
sHeight = this.image.height - sy;
}

//在画布上定位以绘制裁剪后的图像
dx = 0;
dy = 0;
//匹配目标与源以不缩放图像
dWidth = sWidth;
dHeight = sHeight;

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

//添加class映射到我们的Game对象
Game.Map = Map;

})();

//游戏脚本
(function(){
//准备我们的游戏画布
var canvas = document.getElementById(gameCanvas);
var context = canvas.getContext(2d);

//游戏设置:
var FPS = 30;
var INTERVAL = 1000 / FPS; //毫秒
var STEP = INTERVAL / 1000 // seconds

//设置一个表示房间的对象
var room = {
width:5000,
height :3000,
map:new Game.Map(5000,3000)
};

//为房间生成一个大图像纹理
room.map。 generate();

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

//设置魔法相机!
var camera = new Game.Camera(0,0,canvas.width,canvas.height,room.width,room.height);
camera.follow(player,canvas.width / 2,canvas .height / 2);

//游戏更新函数
var update = function(){
player.update(STEP,room.width,room.height);
camera.update();
}

//游戏绘制函数
var draw = function(){
//清除整个画布
context.clearRect(0,0 ,canvas.width,canvas.height);

//重绘所有对象
room.map.draw(context,camera.xView,camera.yView);
player.draw(context,camera.xView,camera.yView);
}

//游戏循环
var gameLoop = function(){
update();
draw();
}

//< - 配置播放/暂停功能:

//出于兼容性原因,我将使用setInterval而不是requestAnimationFrame,
//但是很容易改变。

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 = {
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暂停游戏
Game.togglePause();
break;
}
},false);

// - >

//当页面加载时启动游戏
window.onload = function(){
Game.play();
}

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



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


I'm sure this was solven 1000 times before: I got a canvas in the size of 960*560 and a room in the size of 5000*3000 of which always only 960*560 should be drawn, depending on where the player is. The player should be always in the middle, but when near to borders - then the best view should be calculated). The player can move entirely free with WASD or the arrow keys. And all objects should move themselves - instead of that i move everything else but the player to create the illusion that the player moves.

I now found those two 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.

My code (simplified - the game logic is seperately):

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

The way i am trying to get it working feels totally wrong and i don't even know how i am trying it... Any ideas? What do you think about the context.transform-thing?

I hope you understand my description and that someone has an idea. Kind regards

解决方案

LIVE DEMO at jsfiddle.net

This demo illustrates the viewport usage in a real game scenario. Use arrows keys to move the player over the room. The large room is generated on the fly using rectangles and the result is saved into an image.

Notice that the player is always in the middle except when near to borders (as you desire).


Now I'll try to explain the main portions of the code, at least the parts that are more difficult to understand just looking at it.


Using drawImage to draw large images according to viewport position

A variant of the drawImage method has eight new parameters. We can use this method to slice parts of a source image and draw them to the canvas.

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

The first parameter image, just as with the other variants, is either a reference to an image object or a reference to a different canvas element. For the other eight parameters it's best to look at the image below. The first four parameters define the location and size of the slice on the source image. The last four parameters define the position and size on the destination canvas.

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

How it works in demo:

We have a large image that represents the room and we want to show on canvas only the part within the viewport. The crop position (sx, sy) is the same position of the camera (xView, yView) and the crop dimensions are the same as the viewport(canvas) so sWidth=canvas.width and sHeight=canvas.height.

We need to take care about the crop dimensions because drawImage draws nothing on canvas if the crop position or crop dimensions based on position are invalid. That's why we need the if sections bellow.

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


Drawing game objects related to viewport

When writing a game it's a good practice separate the logic and the rendering for each object in game. So in demo we have update and draw functions. The update method changes object status like position on the "game world", apply physics, animation state, etc. The draw method actually render the object and to render it properly considering the viewport, the object need to know the render context and the viewport properties.

Notice that game objects are updated considering the game world's position. That means the (x,y) position of the object is the position in world. Despite of that, since the viewport is changing, objects need to be rendered properly and the render position will be different than world's position.

The conversion is simple:

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

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

This works for all kind of coordinates, even the negative ones.


Game Camera

Our game objects have a separated update method. In Demo implementation, the camera is treated as a game object and also have a separated update method.

The camera object holds the left top position of viewport (xView, yView), an object to be followed, a rectangle representing the viewport, a rectangle that represents the game world's boundary and the minimal distance of each border that player could be before camera starts move (xDeadZone, yDeadZone). Also we defined the camera's degrees of freedom (axis). For an top view style game like an RPG the camera is allowed to move in both x(horizontal) and y(vertical) axis.

To keep player in the middle of the viewport we set the deadZone of each axis to converge with the center of canvas. Look at the follow function in the code:

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


World's limits

Since each object, including camera, have its own update function, its easy to check the game world's boundary. Only remember to put the code that block the movement at the final of the update function.


Demonstration

See the full code and try it yourself. Much better than explain with words. Maybe after reading the code, this lot of information will be clarified.

LIVE DEMO

Full code:

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


Feel free to report any errors or to add suggestions.

这篇关于HTML5 Canvas相机/视口 - 如何做到这一点?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆