在上一篇《Chrome自带恐龙小游戏的源码研究(六)》中研究了恐龙的跳跃过程,这一篇研究恐龙与障碍物之间的碰撞检测。

碰撞盒子

  游戏中采用的是矩形(非旋转矩形)碰撞。这类碰撞优点是计算比较简单,缺点是对不规则物体的检测不够精确。如果不做更为精细的处理,结果会像下图:

如图所示,两个盒子虽然有重叠部分,但实际情况是恐龙和仙人掌之间并未发生碰撞。为了解决这个问题,需要建立多个碰撞盒子:

不过这样还是有问题,观察图片,恐龙和仙人掌都有四个碰撞盒子,如果每次Game Loop里都对这些盒子进行碰撞检测,那么结果是每次需要进行4X4=16次计算,如果物体或者盒子很多,就会导致运算量大大增加,造成严重的性能问题。为改进这一点,只需要先检测两个大盒子之间是否碰撞,如果没有,则略去里面小盒子的碰撞检测。反之则对里面的小盒子做碰撞检测。游戏中使用CollisionBox构造函数创建碰撞盒子:

/**
* 碰撞盒子
* @param x {number} 盒子x坐标
* @param y {number} 盒子y坐标
* @param w {number} 盒子宽度
* @param h {number} 盒子高度
*/
function CollisionBox(x, y, w, h) {
this.x = x;
this.y = y;
this.width = w;
this.height = h;
}

使用boxCompare方法检测两个盒子是否发生碰撞:

 /**
* 碰撞检测
* @param tRexBox {Object} 霸王龙的碰撞盒子
* @param obstacleBox {Object} 障碍物的碰撞盒子
*/
function boxCompare(tRexBox, obstacleBox) {
var tRexBoxX = tRexBox.x,
tRexBoxY = tRexBox.y,
obstacleBoxX = obstacleBox.x,
obstacleBoxY = obstacleBox.y; return tRexBoxX < obstacleBoxX + obstacleBox.width && tRexBoxX + tRexBox.width > obstacleBoxX && tRexBoxY < obstacleBoxY + obstacleBox.height && tRexBox.height + tRexBoxY > obstacleBoxY;
}

建立碰撞盒子

  接下来要为恐龙和障碍物建立碰撞盒子。游戏中为恐龙建立了6个碰撞盒子,分布在头、躯干和脚,同时它还有闪避状态:

        Trex.collisionBoxes = {
DUCKING:[
new CollisionBox(1,18,55,25)
],
RUNNING: [
new CollisionBox(22, 0, 17, 16),
new CollisionBox(1, 18, 30, 9),
new CollisionBox(10, 35, 14, 8),
new CollisionBox(1, 24, 29, 5),
new CollisionBox(5, 30, 21, 4),
new CollisionBox(9, 34, 15, 4)
]
};

障碍物的碰撞盒子定义在Obstacle.types中:

 Obstacle.types = [{
type: 'CACTUS_SMALL',
width: 17,
height: 35,
yPos: 105,
multipleSpeed: 4,
minGap: 120,
minSpeed: 0,
collisionBoxes: [new CollisionBox(0, 7, 5, 27), new CollisionBox(4, 0, 6, 34), new CollisionBox(10, 4, 7, 14)]
},
{
type: 'CACTUS_LARGE',
width: 25,
height: 50,
yPos: 90,
multipleSpeed: 7,
minGap: 120,
minSpeed: 0,
collisionBoxes: [new CollisionBox(0, 12, 7, 38), new CollisionBox(8, 0, 7, 49), new CollisionBox(13, 10, 10, 38)]
},
{
type: 'PTERODACTYL',
width: 46,
height: 40,
yPos: [100, 75, 50],
// Variable height mobile.
multipleSpeed: 999,
minSpeed: 8.5,
minGap: 150,
collisionBoxes: [new CollisionBox(15, 15, 16, 5), new CollisionBox(18, 21, 24, 6), new CollisionBox(2, 14, 4, 3), new CollisionBox(6, 10, 4, 7), new CollisionBox(10, 8, 6, 9)],
numFrames: 2,
frameRate: 1000 / 6,
speedOffset: .8
}];

不过这只是定义了障碍物数量为1的情况,复数的障碍物需要在创建时修正碰撞盒子:

 if (this.size > 1) {//只针对仙人掌
this.collisionBoxes[1].width = this.width - this.collisionBoxes[0].width - this.collisionBoxes[2].width;
this.collisionBoxes[2].x = this.width - this.collisionBoxes[2].width;
}

下图分别为单数和复数的盒子。

最后执行碰撞检测:

 function checkForCollision(obstacle, tRex) {
//创建最外层的大盒子
var tRexBox = new CollisionBox(tRex.xPos + 1, tRex.yPos + 1, tRex.config.WIDTH - 2, tRex.config.HEIGHT - 2);
var obstacleBox = new CollisionBox(obstacle.xPos + 1, obstacle.yPos + 1, obstacle.typeConfig.width * obstacle.size - 2, obstacle.typeConfig.height - 2); }
if (boxCompare(tRexBox, obstacleBox)) {
var collisionBoxes = obstacle.collisionBoxes;
var tRexCollisionBoxes = tRex.ducking ? Trex.collisionBoxes.DUCKING: Trex.collisionBoxes.RUNNING; for (var t = 0; t < tRexCollisionBoxes.length; t++) {
for (var i = 0; i < collisionBoxes.length; i++) {
//修正盒子
var adjTrexBox = createAdjustedCollisionBox(tRexCollisionBoxes[t], tRexBox);
var adjObstacleBox = createAdjustedCollisionBox(collisionBoxes[i], obstacleBox);
var crashed = boxCompare(adjTrexBox, adjObstacleBox); if (crashed) {
return [adjTrexBox, adjObstacleBox];
}
}
}
}
return false;
}
//修正盒子,将相对坐标转为画布坐标
function createAdjustedCollisionBox(box, adjustment) {
return new CollisionBox(box.x + adjustment.x, box.y + adjustment.y, box.width, box.height);
}

以下是最终运行效果,打开控制台就能看到碰撞输出:

 

// this.bumpThreshold ? this.dimensions.WIDTH : 0;
},
draw:function() {
this.ctx.drawImage(imgSprite,
this.sourceXPos[0], this.spritePos.y,
this.dimensions.WIDTH, this.dimensions.HEIGHT,
this.xPos[0],this.yPos,
this.dimensions.WIDTH,this.dimensions.HEIGHT);

this.ctx.drawImage(imgSprite,
this.sourceXPos[1], this.spritePos.y,
this.dimensions.WIDTH, this.dimensions.HEIGHT,
this.xPos[1],this.yPos,
this.dimensions.WIDTH,this.dimensions.HEIGHT);

},
updateXPos:function(pos,increment) {
var line1 = pos,
line2 = pos === 0 ? 1 : 0;

this.xPos[line1] -= increment;
this.xPos[line2] = this.xPos[line1] + this.dimensions.WIDTH;

if(this.xPos[line1] = 0; i--) {
Cloud.clouds[i].update(speed);
}
var lastCloud = Cloud.clouds[numClouds - 1];
if(numClouds lastCloud.cloudGap &&
Cloud.config.CLOUD_FREQUENCY > Math.random()) {
this.addCloud();
}

Cloud.clouds = Cloud.clouds.filter(function(obj){
return !obj.remove;
});
} else {
this.addCloud();
}
},
update:function(speed) {
if(!this.remove) {
//向左移动
this.xPos -= Math.ceil(speed);
this.draw();

if(!this.isVisible()) {
this.remove = true;
}
}

},
//判断云朵是否移出屏幕外
isVisible:function() {
return this.xPos + Cloud.config.WIDTH > 0;
},
addCloud:function () {
var cloud = new Cloud(this.canvas,spriteDefinition.CLOUD,DEFAULT_WIDTH);
Cloud.clouds.push(cloud);
}
};
//endregion

//region NightMode
NightMode.config = {
FADE_SPEED: 0.035, //淡入淡出速度
HEIGHT: 40, //月亮高度
MOON_SPEED: 0.25, //月亮移动速度
NUM_STARS: 2, //星星数量
STAR_SIZE: 9, //星星宽度
STAR_SPEED: 0.3,//星星速度
STAR_MAX_Y: 70, //星星在画布上出现的位置
WIDTH: 20 //半个月度宽度
};
//月亮在不同时期有不同的位置
NightMode.phases = [140,120,100,60,40,20,0];

NightMode.invertTimer = 0;
NightMode.inverted = false;
NightMode.invertTrigger = false;
NightMode.INVERT_FADE_DURATION = 5000;

function NightMode(canvas,spritePos,containerWidth) {
this.spritePos = spritePos;
this.canvas = canvas;
this.ctx = canvas.getContext("2d");
this.containerWidth = containerWidth;
this.xPos = containerWidth - 50; //月亮的x坐标
this.yPos = 30; //月亮的y坐标
this.currentPhase = 0;
this.opacity = 0;
this.stars = []; //用于存储星星
this.drawStars = false; //是否绘制星星
this.placeStars(); //放置星星
}

NightMode.prototype = {
update:function(activated) {
//若夜晚模式处于激活状态且opacity为0时
//对月亮周期进行更新
if(activated && this.opacity == 0) {
this.currentPhase++;
if(this.currentPhase >= NightMode.phases.length) {
this.currentPhase = 0;
}
}

//淡入
if(activated && (this.opacity 0) {//淡出
this.opacity -= NightMode.config.FADE_SPEED;
}

//当opacity大于0时移动月亮位置
if(this.opacity > 0) {
this.xPos = this.updateXPos(this.xPos, NightMode.config.MOON_SPEED);

//移动星星
if(this.drawStars) {
for (var i = 0; i NightMode.INVERT_FADE_DURATION) {
NightMode.invertTimer = 0;
NightMode.invertTrigger = false;
NightMode.inverted = document.body.classList.toggle('inverted',NightMode.invertTrigger);
} else if(NightMode.invertTimer) {
NightMode.invertTimer += deltaTime;
} else {
//每500帧触发黑夜,这里只是为了模拟效果,完整游戏中是每700米触发一次黑夜
NightMode.invertTrigger = !(gameFrame % 500);
if(NightMode.invertTrigger && NightMode.invertTimer === 0) {
NightMode.invertTimer += deltaTime;
NightMode.inverted = document.body.classList.toggle('inverted',NightMode.invertTrigger);
}
}
},
reset: function() {
this.currentPhase = 0;
this.opacity = 0;
this.update(false);
}
};
//endregion

//region Obstacle
Obstacle.MAX_GAP_COEFFICIENT = 1.5; //障碍物最大间距系数
Obstacle.MAX_OBSTACLE_LENGTH = 3; //障碍物的最大数量
Obstacle.obstacles = []; //存储障碍物的数组
Obstacle.obstacleHistory = []; //记录障碍物数组中障碍物的类型
Obstacle.MAX_OBSTACLE_DUPLICATION = 2; //障碍物的最大重复数量

//障碍物类型的相关配置
Obstacle.types = [
{
type: 'CACTUS_SMALL', //小仙人掌
width: 17,
height: 35,
yPos: 105,
multipleSpeed: 4,
minGap: 120, //最小间距
minSpeed: 0, //最低速度
collisionBoxes: [
new CollisionBox(0, 7, 5, 27),
new CollisionBox(4, 0, 6, 34),
new CollisionBox(10, 4, 7, 14)
]
},
{
type: 'CACTUS_LARGE', //大仙人掌
width: 25,
height: 50,
yPos: 90,
multipleSpeed: 7,
minGap: 120,
minSpeed: 0,
collisionBoxes: [
new CollisionBox(0, 12, 7, 38),
new CollisionBox(8, 0, 7, 49),
new CollisionBox(13, 10, 10, 38)
]
},
{
type: 'PTERODACTYL', //翼龙
width: 46,
height: 40,
yPos: [ 100, 75, 50 ], //有高、中、低三种高度
multipleSpeed: 999,
minSpeed: 8.5, //最小速度
minGap: 150,
numFrames: 2, //有两个动画帧
frameRate: 1000/6, //动画帧的切换速率
speedOffset: .8, //速度修正
collisionBoxes: [
new CollisionBox(15, 15, 16, 5),
new CollisionBox(18, 21, 24, 6),
new CollisionBox(2, 14, 4, 3),
new CollisionBox(6, 10, 4, 7),
new CollisionBox(10, 8, 6, 9)
]
}
];

/**
* 绘制障碍物构造函数
* @param canvas
* @param type 障碍物的类型
* @param spriteImgPos 雪碧图坐标
* @param dimensions 屏幕尺寸
* @param gapCoefficient 障碍物间隙
* @param speed 障碍物移动速度
* @param opt_xOffset 障碍物水平偏移量
* @constructor
*/
function Obstacle(canvas,type,spriteImgPos,dimensions,gapCoefficient,speed,opt_xOffset) {
this.ctx = canvas.getContext('2d');
this.spritePos = spriteImgPos;
//障碍物类型(仙人掌、翼龙)
this.typeConfig = type;
this.gapCoefficient = gapCoefficient;
//每个障碍物的数量(1~3)
this.size = getRandomNum(1,Obstacle.MAX_OBSTACLE_LENGTH);
this.dimensions = dimensions;
//表示该障碍物是否可以被移除
this.remove = false;
//水平坐标
this.xPos = dimensions.WIDTH + (opt_xOffset || 0);
this.yPos = 0;
this.width = 0;
this.collisionBoxes = [];
this.gap = 0;
this.speedOffset = 0; //速度修正

//障碍物的动画帧
this.currentFrame = 0;
//动画帧切换的计时器
this.timer = 0;

this.init(speed);
}

Obstacle.prototype = {
init:function(speed) {
this.cloneCollisionBoxes();
//如果随机障碍物是翼龙,则只出现一只
//翼龙的multipleSpeed是999,远大于speed
if (this.size > 1 && this.typeConfig.multipleSpeed > speed) {
this.size = 1;
}
//障碍物的总宽度等于单个障碍物的宽度乘以个数
this.width = this.typeConfig.width * this.size;

//若障碍物的纵坐标是一个数组
//则随机选取一个
if (Array.isArray(this.typeConfig.yPos)) {
var yPosConfig = this.typeConfig.yPos;
this.yPos = yPosConfig[getRandomNum(0, yPosConfig.length - 1)];
} else {
this.yPos = this.typeConfig.yPos;
}

this.draw();

//修正盒子
if (this.size > 1) {
this.collisionBoxes[1].width = this.width - this.collisionBoxes[0].width -
this.collisionBoxes[2].width;

this.collisionBoxes[2].x = this.width - this.collisionBoxes[2].width;
}

//对翼龙的速度进行修正,让它看起来有的飞得快一些,有些飞得慢一些
if (this.typeConfig.speedOffset) {
this.speedOffset = Math.random() > 0.5 ? this.typeConfig.speedOffset :
-this.typeConfig.speedOffset;
}

//障碍物之间的间隙,与游戏速度有关
this.gap = this.getGap(this.gapCoefficient, speed);
},
cloneCollisionBoxes: function() {
var collisionBoxes = this.typeConfig.collisionBoxes;
for (var i = collisionBoxes.length - 1; i >= 0; i--) {
this.collisionBoxes[i] = new CollisionBox(collisionBoxes[i].x,
collisionBoxes[i].y, collisionBoxes[i].width,
collisionBoxes[i].height);
}
},
//障碍物之间的间隔,gapCoefficient为间隔系数
getGap: function(gapCoefficient, speed) {
var minGap = Math.round(this.width * speed +
this.typeConfig.minGap * gapCoefficient);
var maxGap = Math.round(minGap * Obstacle.MAX_GAP_COEFFICIENT);
return getRandomNum(minGap, maxGap);
},
//判断障碍物是否移出屏幕外
isVisible: function() {
return this.xPos + this.width > 0;
},
draw:function() {
//障碍物宽高
var sourceWidth = this.typeConfig.width;
var sourceHeight = this.typeConfig.height;

//根据障碍物数量计算障碍物在雪碧图上的x坐标
//this.size的取值范围是1~3
var sourceX = (sourceWidth * this.size) * (0.5 * (this.size - 1)) +
this.spritePos.x;

// 如果当前动画帧大于0,说明障碍物类型是翼龙
// 更新翼龙的雪碧图x坐标使其匹配第二帧动画
if (this.currentFrame > 0) {
sourceX += sourceWidth * this.currentFrame;
}
this.ctx.drawImage(imgSprite,
sourceX, this.spritePos.y,
sourceWidth * this.size, sourceHeight,
this.xPos, this.yPos,
sourceWidth * this.size, sourceHeight);
},
//单个障碍物的移动
update:function(deltaTime, speed) {
//如果障碍物还没有移出屏幕外
if (!this.remove) {
//如果有速度修正则修正速度
if (this.typeConfig.speedOffset) {
speed += this.speedOffset;
}
//更新x坐标
this.xPos -= Math.floor((speed * FPS / 1000) * deltaTime);

// Update frame
if (this.typeConfig.numFrames) {
this.timer += deltaTime;
if (this.timer >= this.typeConfig.frameRate) {
//在两个动画帧之间来回切换以达到动画效果
this.currentFrame =
this.currentFrame == this.typeConfig.numFrames - 1 ?
0 : this.currentFrame + 1;
this.timer = 0;
}
}
this.draw();

if (!this.isVisible()) {
this.remove = true;
}
}
},
//管理多个障碍物移动
updateObstacles: function(deltaTime, currentSpeed) {
//保存一个障碍物列表的副本
var updatedObstacles = Obstacle.obstacles.slice(0);

for (var i = 0; i 0) {
//获取障碍物列表中的最后一个障碍物
var lastObstacle = Obstacle.obstacles[Obstacle.obstacles.length - 1];

//若满足条件则添加障碍物
if (lastObstacle &&
lastObstacle.isVisible() &&
(lastObstacle.xPos + lastObstacle.width + lastObstacle.gap) 1) {
Obstacle.obstacleHistory.splice(Obstacle.MAX_OBSTACLE_DUPLICATION);
}
},
//检查障碍物是否超过允许的最大重复数
duplicateObstacleCheck:function(nextObstacleType) {
var duplicateCount = 0;
//与history数组中的障碍物类型比较,最大只允许重得两次
for(var i = 0; i = Obstacle.MAX_OBSTACLE_DUPLICATION;
}
};
//endregion

//region Trex
var keycode = {
JUMP: {'38': 1, '32': 1}, // Up, spacebar
DUCK: {'40': 1} //Down
};

document.addEventListener('keydown',onKeyDown);
document.addEventListener('keyup',onKeyUp);

function onKeyDown(e) {
if(keycode.JUMP[e.keyCode]) {
e.preventDefault();
if(!trex.jumping && !trex.ducking) {
trex.startJump(6);
}
}
if(keycode.DUCK[e.keyCode]) {
e.preventDefault();
if(trex.jumping) {
trex.setSpeedDrop();
} else if(!trex.jumping && !trex.ducking) {
trex.setDuck(true);
}
}
}

function onKeyUp(e) {
if(keycode.JUMP[e.keyCode]) {
trex.endJump();
}
if(keycode.DUCK[e.keyCode]) {
trex.speedDrop = false;
trex.setDuck(false);
}
}
//todo
Trex.config = {
BLINK_TIMING:3000, //眨眼间隔
WIDTH: 44, //站立时宽度
WIDTH_DUCK: 59, //闪避时宽度
HEIGHT: 47, //站立时高度
BOTTOM_PAD: 10,
GRAVITY: 0.6, //重力
INIITAL_JUMP_VELOCITY: -10,//初始起跳速度
DROP_VELOCITY: -5, //下落速度
SPEED_DROP_COEFFICIENT:3, //加速下降系数
MIN_JUMP_HEIGHT: 30, //最小起跳高度
MAX_JUMP_HEIGHT: 30 //最大起跳高度
};
//状态
Trex.status = {
CRASHED: 'CRASHED', //与障碍物发生碰撞
DUCKING: 'DUCKING', //闪避
JUMPING: 'JUMPING', //跳跃
RUNNING: 'RUNNING', //跑动
WAITING: 'WAITING' //待机
};

//建立多个碰撞盒子
Trex.collisionBoxes = {
DUCKING:[
new CollisionBox(1,18,55,25)
],
RUNNING: [
new CollisionBox(22, 0, 17, 16),
new CollisionBox(1, 18, 30, 9),
new CollisionBox(10, 35, 14, 8),
new CollisionBox(1, 24, 29, 5),
new CollisionBox(5, 30, 21, 4),
new CollisionBox(9, 34, 15, 4)
]
};

//元数据(metadata),记录各个状态的动画帧和帧率
Trex.animFrames = {
WAITING: {//待机状态
frames: [44, 0],//动画帧x坐标在44和0之间切换,由于在雪碧图中的y坐标是0所以不用记录
msPerFrame: 1000 / 3 //一秒3帧
},
RUNNING: {
frames: [88, 132],
msPerFrame: 1000 / 12
},
CRASHED: {
frames: [220],
msPerFrame: 1000 / 60
},
JUMPING: {
frames: [0],
msPerFrame: 1000 / 60
},
DUCKING: {
frames: [262, 321],
msPerFrame: 1000 / 8
}
};

function Trex(canvas,spritePos){
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.spritePos = spritePos; //在雪碧图中的位置
this.xPos = 0; //在画布中的x坐标
this.yPos = 0; //在画布中的y坐标
this.groundYPos = 0; //初始化地面的高度
this.currentFrame = 0; //初始化动画帧
this.currentAnimFrames = []; //记录当前状态的动画帧
this.blinkDelay = 0; //眨眼延迟(随机)
this.animStartTime = 0; //动画开始的时间
this.timer = 0; //计时器
this.msPerFrame = 1000 / FPS; //默认帧率
this.config = Trex.config; //拷贝一个配置的副本方便以后使用
this.jumpVelocity = 0; //跳跃的初始速度

this.status = Trex.status.WAITING; //初始化默认状态为待机状态

//为各种状态建立标识
this.jumping = false; //角色是否处于跳跃中
this.ducking = false; //角色是否处于闪避中
this.reachedMinHeight = false; //是否到达最小跳跃高度
this.speedDrop = false; //是否加速降落
this.jumpCount = 0; //跳跃次数

this.init();
}

Trex.prototype = {
init:function() {
this.groundYPos = DEFAULT_HEIGHT - this.config.HEIGHT - this.config.BOTTOM_PAD;
this.yPos = this.groundYPos;

//计算出最小起跳高度
this.minJumpHeight = this.groundYPos - this.config.MIN_JUMP_HEIGHT;
this.draw(0,0);
this.update(0,Trex.status.RUNNING);
},
setBlinkDelay:function () {
//设置随机眨眼间隔时间
this.blinkDelay = Math.ceil(Math.random() * Trex.config.BLINK_TIMING);
},
setDuck:function (isDucking) {
if (isDucking && this.status !== Trex.status.DUCKING) {
this.update(0, Trex.status.DUCKING);
this.ducking = true;
} else if (this.status === Trex.status.DUCKING) {
this.update(0, Trex.status.RUNNING);
this.ducking = false;
}
},
update:function (deltaTime,opt_status) {
this.timer += deltaTime;

if(opt_status) {
this.status = opt_status;
this.currentFrame = 0;
//得到对应状态的帧率 e.g. WAITING 1000ms / 3fps = 333ms/fps
this.msPerFrame = Trex.animFrames[opt_status].msPerFrame;
//对应状态的动画帧 e.g. WAITING [44,0]
this.currentAnimFrames = Trex.animFrames[opt_status].frames;

if(opt_status === Trex.status.WAITING) {
//开始计时
this.animStartTime = getTimeStamp();
//设置延时
this.setBlinkDelay();
}
}

//待机状态
if(this.status === Trex.status.WAITING) {
//执行眨眼动作
this.blink(getTimeStamp());
} else {
this.draw(this.currentAnimFrames[this.currentFrame], 0);
}

//计时器超过一帧的运行时间,切换到下一帧
if (this.timer >= this.msPerFrame) {
this.currentFrame = this.currentFrame === this.currentAnimFrames.length - 1 ?
0 : this.currentFrame + 1;
this.timer = 0;
}

if (this.speedDrop && this.yPos === this.groundYPos) {
this.speedDrop = false;
this.setDuck(true);
}
},
//开始跳跃
startJump:function (speed) {
if(!this.jumping) {
//切换到jump状态
this.update(0,Trex.status.JUMPING);
//设置跳跃速度
this.jumpVelocity = this.config.INIITAL_JUMP_VELOCITY - (speed / 10);
this.jumping = true;
this.reachedMinHeight = false;
this.speedDrop = false;
}
},
updateJump:function (deltaTime, speed) {
//帧切换速率
var msPerFrame = Trex.animFrames[this.status].msPerFrame;
//经过的帧数
var framesElapsed = deltaTime / msPerFrame;
//更新y轴坐标
if(this.speedDrop) {
this.yPos += Math.round(this.jumpVelocity *
this.config.SPEED_DROP_COEFFICIENT * framesElapsed);
} else {
this.yPos += Math.round(this.jumpVelocity * framesElapsed);
}
//由于速度受重力影响,需要对速度进行修正
this.jumpVelocity += this.config.GRAVITY * framesElapsed;

//达到最小跳跃高度
if (this.yPos this.groundYPos) {
this.reset();
this.jumpCount++;
}
this.update(deltaTime);
},
endJump: function() {
if (this.reachedMinHeight && this.jumpVelocity = this.blinkDelay) {
this.draw(this.currentAnimFrames[this.currentFrame],0);

if (this.currentFrame === 1) {//0闭眼 1开眼
//设置新的眨眼间隔时间
this.setBlinkDelay();
this.animStartTime = time;
}
}
},
draw:function (x,y) {
var sourceX = x;
var sourceY = y;
var sourceWidth = this.ducking && this.status != Trex.status.CRASHED ?
this.config.WIDTH_DUCK : this.config.WIDTH;
var sourceHeight = this.config.HEIGHT;
sourceX += this.spritePos.x;
sourceY += this.spritePos.y;

this.ctx.drawImage(imgSprite,
sourceX, sourceY,
sourceWidth, sourceHeight,
this.xPos, this.yPos,
this.ducking ? this.config.WIDTH_DUCK : this.config.WIDTH, this.config.HEIGHT);
}
};
//endregion

//region CheckCollision

/**
* 碰撞盒子
* @param x {number} x坐标
* @param y {number} y坐标
* @param w {number} 宽度
* @param h {number} 高度
*/
function CollisionBox(x,y,w,h) {
this.x = x;
this.y = y;
this.width = w;
this.height = h;
}

/**
* 碰撞检测
* @param tRexBox {Object} 霸王龙的碰撞盒子
* @param obstacleBox {Object} 障碍物的碰撞盒子
*/
function boxCompare(tRexBox, obstacleBox) {
var tRexBoxX = tRexBox.x,
tRexBoxY = tRexBox.y,
obstacleBoxX = obstacleBox.x,
obstacleBoxY = obstacleBox.y;

return tRexBoxX obstacleBoxX &&
tRexBoxY obstacleBoxY;
}

function createAdjustedCollisionBox(box, adjustment) {
return new CollisionBox(
box.x + adjustment.x,
box.y + adjustment.y,
box.width,
box.height);
}

function drawCollisionBoxes(canvasCtx, tRexBox, obstacleBox) {
canvasCtx.save();
canvasCtx.lineWidth = 0.5;
canvasCtx.strokeStyle = '#f00';
canvasCtx.strokeRect(tRexBox.x+0.5, tRexBox.y+0.5, tRexBox.width, tRexBox.height);

canvasCtx.strokeStyle = '#0f0';
canvasCtx.strokeRect(obstacleBox.x+0.5, obstacleBox.y+0.5,
obstacleBox.width, obstacleBox.height);
canvasCtx.restore();
}

function checkForCollision(obstacle, tRex, opt_canvasCtx) {
var tRexBox = new CollisionBox(
tRex.xPos + 1,
tRex.yPos + 1,
tRex.config.WIDTH - 2,
tRex.config.HEIGHT - 2);

var obstacleBox = new CollisionBox(
obstacle.xPos + 1,
obstacle.yPos + 1,
obstacle.typeConfig.width * obstacle.size - 2,
obstacle.typeConfig.height - 2);

if (opt_canvasCtx) {
drawCollisionBoxes(opt_canvasCtx, tRexBox, obstacleBox);
}
if (boxCompare(tRexBox, obstacleBox)) {
var collisionBoxes = obstacle.collisionBoxes;
var tRexCollisionBoxes = tRex.ducking ?
Trex.collisionBoxes.DUCKING : Trex.collisionBoxes.RUNNING;

for (var t = 0; t

后记

  通过建立碰撞盒子进行碰撞检测在应用上非常广泛,著名的街机游戏《街霸》和《拳皇》就是采用了这种方式:

可以看到游戏中对人物建立了多个碰撞盒子,红色代表攻击区域,蓝色代表可以被攻击的区域,绿色区域之间不能重叠,用来推挤对手。

Chrome自带恐龙小游戏的源码研究(七)的更多相关文章

  1. Chrome自带恐龙小游戏的源码研究(完)

    在上一篇<Chrome自带恐龙小游戏的源码研究(七)>中研究了恐龙与障碍物的碰撞检测,这一篇主要研究组成游戏的其它要素. 游戏分数记录 如图所示,分数及最高分记录显示在游戏界面的右上角,每 ...

  2. Chrome自带恐龙小游戏的源码研究(一)

    目录 Chrome自带恐龙小游戏的源码研究(一)——绘制地面 Chrome自带恐龙小游戏的源码研究(二)——绘制云朵 Chrome自带恐龙小游戏的源码研究(三)——昼夜交替 Chrome自带恐龙小游戏 ...

  3. Chrome自带恐龙小游戏的源码研究(六)

    在上一篇<Chrome自带恐龙小游戏的源码研究(五)>中实现了眨眼睛的恐龙,这一篇主要研究恐龙的跳跃. 恐龙的跳跃 游戏通过敲击键盘的Spacebar或者Up来实现恐龙的跳跃.先用一张图来 ...

  4. Chrome自带恐龙小游戏的源码研究(五)

    在上一篇<Chrome自带恐龙小游戏的源码研究(四)>中实现了障碍物的绘制及移动,从这一篇开始主要研究恐龙的绘制及一系列键盘动作的实现. 会眨眼睛的恐龙 在游戏开始前的待机界面,如果仔细观 ...

  5. Chrome自带恐龙小游戏的源码研究(四)

    在上一篇<Chrome自带恐龙小游戏的源码研究(三)>中实现了让游戏昼夜交替,这一篇主要研究如何绘制障碍物. 障碍物有两种:仙人掌和翼龙.仙人掌有大小两种类型,可以同时并列多个:翼龙按高. ...

  6. Chrome自带恐龙小游戏的源码研究(三)

    在上一篇<Chrome自带恐龙小游戏的源码研究(二)>中实现了云朵的绘制和移动,这一篇主要研究如何让游戏实现昼夜交替. 昼夜交替的效果主要是通过样式来完成,但改变样式的时机则由脚本控制. ...

  7. Chrome自带恐龙小游戏的源码研究(二)

    在上一篇<Chrome自带恐龙小游戏的源码研究(一)>中实现了地面的绘制和运动,这一篇主要研究云朵的绘制. 云朵的绘制通过Cloud构造函数完成.Cloud实现代码如下: Cloud.co ...

  8. WinFom中经典小游戏(含源码)

    最近整理了若干经典的小游戏,无聊时可以打发时间.程序本身不大,练手非常不错,主要是GDI编程,主界面地址如下图所示 源码下载方式 1,关注微信公众号:小特工作室(也可直接扫描签名处二维码) 2,发送: ...

  9. github下载下来的C#控制台小游戏[含源码]

    早就听说了github是世界最大的源码库,但自己却不是很懂,今天去研究了下,注册了一个帐号,然后在上面搜索了一下C# game,然后发现有许多的游戏. 随意地选择了一个,感觉比较简单,于是就下载了下来 ...

随机推荐

  1. LOJ#2132. 「NOI2015」荷马史诗

    $n \leq 100000$个数字,放进$k$叉树里,一个点只能放一个数,使所有数字乘以各自深度这个值之和最小的同时,最大深度的数字最小. 哈夫曼.这是我刚学OI那段时间看到的,感觉就是个很无聊的贪 ...

  2. ckeditor自己用的配置文件config.js

    原文发布时间为:2011-01-17 -- 来源于本人的百度文章 [由搬家工具导入] CKEDITOR.editorConfig = function(config) {    // Define c ...

  3. [Json] 1 - 数据格式(转)

    JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.JSON采用完全独立于语言的文本格式,这些特性使JSON成为理想的数据交换语言.易于人阅读和编写,同时也易 ...

  4. PyCharm 安装配置使用

    PyCharm 安装 下载地址 专业版:https://download.jetbrains.com/python/pycharm-professional-2017.2.3.exe 社区版:http ...

  5. Codeforces Gym100814 B.Unlucky Teacher (ACM International Collegiate Programming Contest, Egyptian Collegiate Programming Contest (2015) Arab Academy for Science and Technology)

    今日份的训练题解,今天写出来的题没有昨天多,可能是因为有些事吧... 这个题就是老师改卷子,忘带标准答案了,但是他改了一部分卷子,并且确定自己改的卷子没出错,他想从改过的卷子里把标准答案推出来. 因为 ...

  6. [UR #3] 核聚变反应强度

    次大公约数就是gcd再除以其最小质因子(如果有的话).可以发现要求的sgcd 的前身gcd都是a1的约数,所以把a1质因数分解直接做就行了. #include<bits/stdc++.h> ...

  7. Storyboards Tutorial 04

    设计好后运行发现没有任何变化,是空白的.这是因为你的tableview相关的delegate方法还在.所以首先要屏蔽或者删除在PlayerDetailsViewController.m 如下的操作 # ...

  8. Java父类与子类中静态代码块 实例代码块 静态变量 实例变量 构造函数执行顺序

    实例化子类时,父类与子类中的静态代码块.实例代码块.静态变量.实例变量.构造函数的执行顺序是怎样的? 代码执行的优先级为: firest:静态部分 second:实例化过程 详细顺序为: 1.父类静态 ...

  9. 在dedecms后台发表文章显示外部连接栏目

    问题描述:客户的网站,有个顶级栏目,下面包含了几个子栏目,这个顶级栏目不想发布什么内容,点击后进入他的某个子栏目就可以了,这时候把这个顶级栏目设置为“外部连接”就可以了 但是设置顶级栏目为外部连接后, ...

  10. Object-C 类

    Classes 类 像其它的面向对象的语言一样,Object-C也提供创建对象的蓝本.即类. 首先我们在类中定义一些能够反复使用的属性和方法. 然后,我们实例化类,即对象,之后就能够使用属性和訪问. ...