在庞大的游戏世界中,玩家不能一览地图全貌,而是只能看到其中一部分,并一步步探索,这时就要用到一种技术来显示局部的地图,游戏术语称为摄像机(Camera)。下面两张图中的白色矩形框表示了Camera的作用,玩家控制的角色总是在该矩形内。

可以想像成一个200X100宽高的相框固定在坐标(0,0)处,然后移动下面的蓝纸,蓝纸的不同位置就会显示在相框中。

同时,也只需绘制出现在相框中的地图即可,这样可以提升一部分程序的性能。为实现Camera功能,需要添加一些辅助方法。

改造一下原来的AABB函数:

class AABB {
/**
* 碰撞盒子
* @param x {number} 盒子x坐标
* @param y {number} 盒子y坐标
* @param w {number} 盒子宽度
* @param h {number} 盒子高度
*/
constructor(x,y,w,h) {
this.pos = new Vector(x,y);
this.size = new Vector(w,h);
this.center = new Vector(this.pos.x + w / 2,this.pos.y + h / 2);
this.halfSize = new Vector(this.size.x / 2,this.size.y / 2); this.init();
} set(x, y, /*optional*/w, /*optional*/h) {
this.pos = new Vector(x, y);
this.size = new Vector(w || this.width, h || this.height); this.init();
} init() {
this.left = this.pos.x;
this.top = this.pos.y;
this.width = this.size.x;
this.height = this.size.y;
this.right = this.left + this.width;
this.bottom = this.top + this.height;
} within(r) {
return r.left <= this.left &&
r.right >= this.right &&
r.top <= this.top &&
r.bottom >= this.bottom;
}
}

同时新增Camera构造函数:

let AXIS = {};
Object.defineProperties(AXIS,{
'NONE':{
value:"none"
},
'HORIZONTAL':{
value:"horizontal"
},
'VERTICAL':{
value:"vertical"
},
'BOTH':{
value:"both"
}
}); class Camera {
/**
* 摄像机构造函数
* @param level {map} 地图
* @param x {Number} camera的x坐标
* @param y {Number} camera的y坐标
* @param canvasWidth {Number} camera视口宽度
* @param canvasHeight {Number} camera视口高度
* @param maxX {Number} camera的最大x坐标
* @param maxY {Number} camera的最大y坐标
*/
constructor(level,x,y,canvasWidth,canvasHeight,maxX,maxY) {
//摄像机左上角的x,y坐标
this.x = x;
this.y = y;
//摄像机的大小
this.w = canvasWidth;
this.h = canvasHeight; //摄像机开始移动的临界点
//跟踪对象到摄像机边界的距离
this.xDeadZone = 0; //距离水平边界的距离
this.yDeadZone = 0; //距离垂直边界的距离 //摄像机能够移动的最大范围
this.maxX = maxX || level.cols - this.w;
this.maxY = maxY || level.rows - this.h;
//摄像机移动的方向
this.axis = AXIS.BOTH;
//镜头跟随的对象
this.followed = null; //表示camera视口
this.viewportRect = new AABB(this.x,this.y,this.w,this.h); //表示整个地图范围
this.worldRect = new AABB(0,0,level.cols,level.rows);
} follow(gameObject,xDeadZone,yDeadZone) {
this.followed = gameObject;
this.xDeadZone = xDeadZone;
this.yDeadZone = yDeadZone;
} update() {
//仅在有跟随对象时更新摄像机位置
if(this.followed !== null) {
if(this.axis === AXIS.HORIZONTAL || this.axis === AXIS.BOTH) {
//根据跟随对象位置更新摄像机的x坐标
if(this.followed.pos.x - this.x + this.xDeadZone > this.w) {
this.x = this.followed.pos.x - (this.w - this.xDeadZone);
} else if(this.followed.pos.x - this.xDeadZone < this.x) {
this.x = this.followed.pos.x - this.xDeadZone;
}
} if(this.axis === AXIS.VERTICAL || this.axis === AXIS.BOTH) {
//根据跟随对象位置更新摄像机的y坐标
if(this.followed.pos.y - this.y + this.yDeadZone > this.h) {
this.y = this.followed.pos.y - (this.h - this.yDeadZone);
} else if(this.followed.pos.y - this.yDeadZone < this.y) {
this.y = this.followed.pos.y - this.yDeadZone;
}
}
} //重新设置camera视口的x坐标和y坐标
this.viewportRect.set(this.x,this.y); //保证camera不会超出地图范围
if(!this.viewportRect.within(this.worldRect)) {
if(this.viewportRect.left < this.worldRect.left) this.x = this.worldRect.left;
if(this.viewportRect.top < this.worldRect.top) this.y = this.worldRect.top;
if(this.viewportRect.right > this.worldRect.right) this.x = this.worldRect.right - this.w;
if(this.viewportRect.bottom > this.worldRect.bottom) this.y = this.worldRect.bottom - this.h;
}
}
}

在游戏开始时初始化Camera:

camera = new Camera(levels,0,0,c.width / MAPCONFIG.TILESIZE,c.height / MAPCONFIG.TILESIZE);
camera.follow(player,c.width / 2 / MAPCONFIG.TILESIZE,c.height / 2 / MAPCONFIG.TILESIZE);

渲染地图时只绘制Camera部分:

_drawLayer(layerIndex) {
let tileSize = MAPCONFIG.TILESIZE,
startCol = camera.x >> 0, //起始列
endCol = Math.floor(startCol + camera.w) + 1, //结束列
startRow = camera.y >> 0, //开始行
endRow = Math.floor(startRow + camera.h) + 1, //结束行
offsetX = -camera.x + startCol,
offsetY = -camera.y + startRow; for (let r = startRow; r < endRow; r++) {
for (let c = startCol; c < endCol; c++) {
let tile = this.getTile(layerIndex, c, r),
x = (c - startCol + offsetX) * tileSize, //瓦片的x坐标
y = (r - startRow + offsetY) * tileSize; //瓦片的y坐标 if (tile !== -1) {
this.ctx.drawImage(
this.spriteSheet,
tile * tileSize % this.dimensions.w, //瓦片精灵图上的x坐标
Math.floor(tile * tileSize / this.dimensions.w) * tileSize, //瓦片精灵图上的y坐标
tileSize,
tileSize,
Math.round(x),
Math.round(y),
tileSize,
tileSize
);
}
}
}
}

以下是演示效果:

input{
position: relative;
z-index: 2;
}
#debugTools ul{
transition: all .3s;
opacity: 1;
left: 70px;
top: 0;
position: absolute;
}
#debugTools ul li{
float: left;
margin-right: 15px;
}
#debugTools.active ul {
left: 0;
opacity: 0;
}
-->

 
  • 网格
  • 精灵框
  • 碰撞框
  • 坐标

// {
let loadBatch = {
count: 0,
total: assetList.length,
cb: callback
},next;

(function loadAsset(src) {
if(gCachedAssets[src] === undefined) {
let assetType = getAssetTypeFromExtension(src);

if(assetType === 0) {
let img = new Image();
img.onload = () => {
onLoadedCallback(img,loadBatch);
next = assetList.shift();

if(next) {
loadAsset(next);
}
};
img.src = src;
gCachedAssets[src] = img;
} else if(assetType === 1) {
let script = document.createElement('script');

script.addEventListener('load', () => {
onLoadedCallback(script, loadBatch);
next = assetList.shift();

if(next) {
loadAsset(next);
}
});

script.src = src;
gCachedAssets[src] = script;

document.getElementsByTagName('head')[0].appendChild(script);
}
} else {
onLoadedCallback(gCachedAssets[src], loadBatch);
}

})(assetList.shift());

},
onLoadedCallback = (asset, batch) => {
batch.count++;
if (batch.count === batch.total) {
batch.cb(asset);
}
},
getAssetTypeFromExtension = (assetName) => {
if(assetName.indexOf('.jpg') !== -1 || assetName.indexOf('.jpeg') !== -1 || assetName.indexOf('.png') !== -1) {
return 0;
}

if(assetName.indexOf('.js') !== -1 || assetName.indexOf('.json') !== -1) {
return 1;
}

return -1;
};

(function() {
var canvas = document.createElement('canvas'),
a = document.getElementById('a');
canvas.id = 'c1';
canvas.width = 640;
canvas.height = 506;

a.appendChild(canvas);

var c = document.getElementById('c1'),
ctx = c.getContext('2d'),
lastTime = 0,
elapsed,
paused = false,
raqId,
playerSpriteSheet = new Image(),
levelAssets = new Image(),
canvasBG = new Image(),
now;

let camera;

loadAssets(['https://files.cnblogs.com/files/undefined000/game.min-v2.js?v=10'],function() {
playerSpriteSheet.src = imageData;
levelAssets.src = levelSpriteSheet;
canvasBG.src = background;

camera = new Camera(levels.stage1,0,0,c.width / MAPCONFIG.TILESIZE,c.height / MAPCONFIG.TILESIZE);

let level = new MapManager(levels.stage1,ctx,{
image:levelAssets,
w:416,
h:96
},camera);

let player = new Player(new Vector(5,2),ctx,level,playerSpriteSheet,camera);
camera.follow(player,c.width / 2 / MAPCONFIG.TILESIZE,c.height / 2 / MAPCONFIG.TILESIZE);

function loop() {
draw();
}
function stop() {
cancelAnimationFrame(raqId)
}

function draw() {
ctx.clearRect(0,0,c.width,c.height);

now = +new Date;

if(lastTime !== 0) {
elapsed = Math.min(now - lastTime,16);
} else elapsed = 16;

level.render();
player.update(elapsed);
camera.update();

lastTime = now;

raqId = requestAnimationFrame(draw);
}
loop();

window.addEventListener('keyup',(e) => {
if(e.keyCode === 80) {
paused = !paused;
if(paused) {
stop();
let txt = 'Pause';
ctx.font = '50px Source Han Serif';
ctx.fillStyle = '#f00';
ctx.fillText(txt, (c.width - ctx.measureText(txt).width) / 2, c.height / 2);
} else {
loop();
}
}
});
});

})();
// ]]>

更新日志

  2017/04/09  更新角色跳跃

  2017/04/21  更新角色冲刺

  2017/05/01  更新角色状态机

  2017/05/16  更新角色攻击动画

  2017/05/22  更新角色移动攻击动画

  2017/05/24  更新角色跳跃攻击动画

  2017/06/04  更新地图绘制

  2017/06/22  更新摄像机、长距离冲刺

HTML5 2D平台游戏开发#7Camera的更多相关文章

  1. HTML5 2D平台游戏开发#4状态机

    在实现了<HTML5 2D平台游戏开发——角色动作篇之冲刺>之后,我发现随着角色动作的增加,代码中的逻辑判断越来越多,铺天盖地的if() else()语句实在让我捉襟见肘: 这还仅仅是角色 ...

  2. HTML5 2D平台游戏开发#6地图绘制

    此前已经完成了一部分角色的动作,现在还缺少可以交互的地图让游戏看起来能玩.不过在开始之前应当考虑清楚使用什么类型的地图,就2D平台游戏来说,一般有两种类型的地图,Tile-based和Art-base ...

  3. HTML5 2D平台游戏开发#11斜坡物理

    在游戏中会经常遇到斜坡地形,比如众所周知的魂斗罗,角色可以在坡上移动和跳跃: 斜坡在2D游戏中很常见,处理起来也较为棘手.最初我打算用分离轴定律来实现,在建立了一个物理模型之后: 发现上坡时没什么问题 ...

  4. HTML5 2D平台游戏开发#8指令技

    一般在动作游戏中,玩家可以通过对输入设备输入一系列的指令让角色完成某个或多个特定的动作.以格斗游戏<拳皇>为例,键入↓↘→↘↓↙← + A or C可以触发IORI的必杀技八稚女: 通过一 ...

  5. HTML5 2D平台游戏开发#5攻击

    目前为止,角色除了基本的移动外还什么都不能做,于是我打算先实现角色的攻击动画.角色的普通攻击一共可以分为三个阶段: 一段斩 二段斩 三段斩 移动攻击 跳跃攻击 触发方式为角色站立时按下J(攻击)键,角 ...

  6. HTML5 2D平台游戏开发#10Wall Jump

    这个术语不知道怎么翻译比较贴切,但并不妨碍对字面意思的理解,大概就是飞檐走壁.比如: 这是游戏<忍者龙剑传>中的场景,玩家可以通过操纵角色在墙面上移动并跳跃. 首先需要实现角色抓墙这一动作 ...

  7. HTML5 2D平台游戏开发#9蓄力技

    在很多动作游戏中,玩家操控的角色可以施放出比普通攻击更强力的蓄力技,一般操作为按住攻击键一段时间然后松开,具体效果像下面这张图: 要实现这个操作首先要记录下按键被按住的时间,初始是0: this.sa ...

  8. HTML5 2D平台游戏开发#2跳跃与二段跳

    在上一篇<Canvas制作时间与行为可控的sprite动画>中已经实现了角色的左右移动,本篇继续实现角色的一系列动作之一:跳跃.先来看看最终效果: 要实现跳跃,必须模拟垂直方向的速度和重力 ...

  9. HTML5 2D平台游戏开发#1

    在Web领域通常会用到一组sprite来展示动画,这类动画从开始到结束往往不会有用户参与,即用户很少会用控制器(例如鼠标.键盘.手柄.操作杆等输入设备)进行操作.但在游戏领域,sprite动画与控制器 ...

随机推荐

  1. HDU 5923 Prediction(2016 CCPC东北地区大学生程序设计竞赛 Problem B,并查集)

    题目链接  2016 CCPC东北地区大学生程序设计竞赛 B题 题意  给定一个无向图和一棵树,树上的每个结点对应无向图中的一条边,现在给出$q$个询问, 每次选定树中的一个点集,然后真正被选上的是这 ...

  2. luogu P2776 [SDOI2007]小组队列

    题目背景 嘛,这道非常简单的给大家提供信心的省选题洛谷居然没有! 这么简单的题怎么可以没有! 给大家提升士气是义不容辞的责任! 所以我就来补一下啦.. 值得一提的是,标程是我自己做的.. 很渣,因为数 ...

  3. k8s学习

    k8s简介 在学习k8s之前,相信大家和我一样,肯定都学习和使用过docker容器,并且对容器技术有了一个基本的认识.引用张磊老师的总结:其实一个"容器",实际上是一个由Linux ...

  4. 所有iOS设备的屏幕分辨率

     iPhone设备 物理分辨率是硬件所支持的,逻辑分辨率是软件可以达到的. 代数 设备 操作系统 逻辑分辨率(point) 物理分辨率(pixel) 屏幕尺寸(对角线长度) 缩放因子   iPhone ...

  5. Features (OCMock 2)

    This page describes the features present in OCMock 2.x, using the traditional syntax. All these feat ...

  6. WCF IIS上部署服务

    一.选择应用程序池:.Net Framework 4.0集成模式 二.IIS Access is denied:程序所在文件夹给予Everyone权限 三.HTTP 错误 500.21 - Inter ...

  7. httpd.conf详细解释

    httpd.conf详解  http://www.php100.com/html/webkaifa/apache/2009/0418/1192.html

  8. 【iOS开发-55】图片轮播案例:scrollView的分页、滚动栏、利用代理控制定时器和Page Control以及多线程问题

    案例: (1)用storyboard布局,这里用了三样东西. --UIScrollView就是我们准备存放滚动图片的容器. --Page Control就是控制页数的那几个小点点.能够设置有多少个点. ...

  9. Android服务之bindService源代码分析

    上一篇分析startService时没有画出调用ActivityManagerService之前的时序图,这里画出bindService的时序图.它们的调用流程是一致的. 先看ContextWrapp ...

  10. 13 Basic Cat Command Examples in Linux

    FROM: http://www.tecmint.com/13-basic-cat-command-examples-in-linux/ The cat (short for “concatenate ...