代码部分都加了详细注释,我就不再详细多说了,主要就是几个主要的函数
Obstacle障碍物
NightMode昼夜更替
Cloud云朵
HorizonLine地面
Runner运动的配置
GameOverPanel游戏结束界面
DistanceMeter分数以及高分记录
CollisionBox碰撞检测
Trex恐龙
<script type="text/javascript">
var div = document.querySelector('div');
var spriteDefinition = {
// 地面在精灵图中的位置
HORIZON: { x: 2, y: 54 },
// 云朵在精灵图中的位置
CLOUD: { x: 86, y: 2 },
STAR: { x: 645, y: 2 },//星星
MOON: { x: 484, y: 2 },//月亮
CACTUS_LARGE: { x: 332, y: 2 }, //大仙人掌
CACTUS_SMALL: { x: 228, y: 2 },//小仙人掌
PTERODACTYL: { x: 134, y: 2 }//翼龙
},
gameFrame = 600;
FPS = 60,
DEFAULT_WIDTH = 600,
imgSprite = document.getElementById('sprite');
// 障碍物,仙人掌和翼龙
Obstacle.obstacles = [];//存储障碍物的数组
Obstacle.obstacleHistory = [];//记录障碍物数组中障碍物的类型
// 障碍物不能太密集也不能太稀疏,两个障碍物之间也应该有一个间距以供d跳后的落脚
// 因此设置一个障碍物最大间距系数
Obstacle.MAX_GAP_COEFFICIENT = 1.5;
// 每组障碍物的最大数量
Obstacle.MAX_OBSTACLE_LENGTH = 3;
// 相邻的障碍物类型的最大重复数
Obstacle.MAX_OBSTACLE_DUPLICATION = 2;
Obstacle.types = [
{
type: 'CACTUS_SMALL',//小仙人掌
width: 17,//宽
height: 35,//高
yPos: 105,//在画布上的y坐标
multipleSpeed: 4,
minGap: 120, //最小间距
minSpeed: 0 //最低速度
},
{
type: 'CACTUS_LARGE',//大仙人掌
width: 25,
height: 50,
yPos: 90,
multipleSpeed: 7,
minGap: 120,
minSpeed: 0
},
{
type: 'PTERODACTYL',//翼龙
width: 46,
height: 40,
yPos: [100, 75, 50],//有高,中,低三个高度
multipleSpeed: 999,
minSpeed: 8.5,
minGap: 150,
numFrames: 2,//有两个动画帧
frameRate: 1000 / 6,//动画帧切换速率,这里为一秒6帧
speedOffset: 0.8 //速度修正
}
];
// 绘制障碍物构造函数
Obstacle.dimensions={
WIDTH:600
};
// dimensions 屏幕尺寸 gapCoefficient 障碍物间隙 opt_xOffset 障碍物水平偏移量
function Obstacle(canvas,type, spriteImgPos, gapCoefficient, speed, opt_xOffset) {
// console.log(opt_xOffset);
this.canvas = canvas;
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 =Obstacle.dimensions;
// 表示该障碍物是否可以被移除
this.remove = false;
// 水平坐标
this.xPos = this.dimensions.WIDTH + (opt_xOffset || 0);
this.yPos = 0;
this.width = 0;
this.gap = 0;
this.speedOffset = 0;//速度修正
// 障碍物的动画帧
this.currentFrame = 0;
// 动画帧切换的计时器
this.timer = 0;
this.init(speed);
}
Obstacle.prototype = {
init: function (speed) {
// console.log(this.typeConfig);
// 如果随机障碍物是翼龙,则只出现一只
// 翼龙的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.typeConfig.speedOffset) {
this.speedOffset = Math.random() > 0.5 ? this.typeConfig.speedOffset : -this.typeConfig.speedOffset;
}
// 障碍物之间的间隙,与游戏速度有关
this.gap = this.getGap(this.gapCoefficient, speed);
},
// 障碍物之间的间隔,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;
// console.log(sourceX);
// 如果当前动画帧大于0,说明障碍物类型是翼龙
// 更新翼龙的精灵图x坐标使其匹配第二帧动画
if (this.currentFrame > 0) {
sourceX += sourceWidth * this.currentFrame;
};
// console.log(this.xPos, this.yPos);
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 =this.xPos- Math.floor((speed * FPS / 1000) * deltaTime);
// console.log(this.xPos);
// 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 updateObstacles = Obstacle.obstacles.slice(0);
for (var i = 0; i < Obstacle.obstacles.length; i++) {
var obstacle = Obstacle.obstacles[i];
obstacle.update(deltaTime, currentSpeed);
// 移除被标记为删除的障碍物
if (obstacle.remove) {
updateObstacles.shift();
}
}
Obstacle.obstacles = updateObstacles;
if (Obstacle.obstacles.length > 0) {
// 获取障碍物列表中的最后一个障碍物
var lastObstacle = Obstacle.obstacles[Obstacle.obstacles.length - 1];
// 若满足条件则添加障碍物
if (lastObstacle &&
lastObstacle.isVisible() &&
(lastObstacle.xPos + lastObstacle.width + lastObstacle.gap) <
this.dimensions.WIDTH) {
this.addNewObstacle(currentSpeed);
}
} else {
// 若障碍物列表中没有障碍物则立即添加
this.addNewObstacle(currentSpeed);
}
},
// 随机添加障碍
addNewObstacle: function (currentSpeed) {
// 随机选取一种类型的障碍
var obstacleTypeIndex = getRandomNum(0, Obstacle.types.length - 1);
var obstacleType = Obstacle.types[obstacleTypeIndex];
// 检查随机取到的障碍物类型是否与前两个重复
// 或者检查其速度是否合法,这样可以保证游戏在低速时不出现翼龙
// 如果检查不通过则重新再选一次直到通过为止
if (this.duplicateObstracleCheck(obstacleType.type) || currentSpeed < obstacleType.minSpeed) {
this.addNewObstacle(currentSpeed);
} else {
// 检查通过后,获取该值在精灵图中的坐标
var obstacleSpritePos = this.spritePos[obstacleType.type];
// 生成新的障碍物并存入数组
Obstacle.obstacles.push(new Obstacle(this.canvas, obstacleType, obstacleSpritePos, this.dimensions, this.gapCoefficient, currentSpeed, obstacleType.width));
// 同时将障碍物类型存入histo数组
// 该方法向数组开头添加一个或者更多元素,并返回长度
Obstacle.obstacleHistory.unshift(obstacleType.type);
}
// 若history数组的长度大于1,则清空最前面的两个
if (Obstacle.obstacleHistory.length > 1) {
Obstacle.obstacleHistory.splice(Obstacle.MAX_OBSTACLE_DUPLICATION);
}
},
// 检查障碍物是否超过允许的最大重复数
duplicateObstracleCheck: function (nextObstacleType) {
var duplicateCount = 0;
// 与history数组中的障碍物类型比较,最大只允许重得两次
for (var i = 0; i < Obstacle.obstacleHistory.length; i++) {
duplicateCount = Obstacle.obstacleHistory[i] === nextObstacleType ? duplicateCount + 1 : 0;
}
return duplicateCount >= Obstacle.MAX_OBSTACLE_DUPLICATION;
}
};
// 昼夜更替效果
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.INVER_FADE_DURATION = 5000;
// 黑夜构造函数
function NightMode(canvas, spritePos, containerWidth) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.spritePos = spritePos;
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 < 1 || this.opacity == 0)) {
this.opacity += NightMode.config.FADE_SPEED;
} else if (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.config.NUM_STARS; i++) {
this.stars[i].x = this.updateXPos(this.stars[i].x, NightMode.config.STAR_SPEED);
};
}
this.draw();
} else {
this.opacity = 0;
this.placeStars();
}
this.drawStars = true;
},
updateXPos: function (currentPos, speed) {
if (currentPos < -NightMode.config.WIDTH) {
currentPos = this.containerWidth;
} else {
currentPos -= speed;
}
return currentPos;
},
draw: function () {
// 周期为3时画满月
var moonSourceWidth = this.currentPhase == 3 ? NightMode.config.WIDTH * 2 : NightMode.config.WIDTH;
var moonSourceHeight = NightMode.config.HEIGHT;
// 从精灵图上获取月亮正确的形状
var moonSourceX = this.spritePos.x + NightMode.phases[this.currentPhase];
// console.log(moonSourceX);
var moonOutputWidth = moonSourceWidth;
var starSize = NightMode.config.STAR_SIZE;
var starSourceX = spriteDefinition.STAR.x;
this.ctx.save();
// 画布透明度随之变化
this.ctx.globalAlpha = this.opacity;
if (this.drawStars) {
for (var i = 0; i < NightMode.config.NUM_STARS; i++) {
this.ctx.drawImage(imgSprite,
starSourceX, this.stars[i].sourceY,
starSize, starSize,
Math.round(this.stars[i].x), this.stars[i].y,
NightMode.config.STAR_SIZE, NightMode.config.STAR_SIZE);
}
}
this.ctx.drawImage(imgSprite,
moonSourceX, this.spritePos.y,
moonSourceWidth, moonSourceHeight,
Math.round(this.xPos), this.yPos,
moonOutputWidth, NightMode.config.HEIGHT);
this.ctx.globalAlpha = 1;
this.ctx.restore();
},
placeStars: function () {
// 将画布分为若干组
var segmentSize = Math.round(this.containerWidth / NightMode.config.NUM_STARS);
for (var i = 0; i < NightMode.config.NUM_STARS; i++) {
this.stars[i] = {};
// 每组星星位置随机
this.stars[i].x = getRandomNum(segmentSize * i, segmentSize * (i + 1));
this.stars[i].y = getRandomNum(0, NightMode.config.STAR_MAX_Y);
this.stars[i].sourceY = spriteDefinition.STAR.y + NightMode.config.STAR_SIZE * i;
}
},
invert: function (deltaTime) {
this.update(NightMode.inverted);
// 黑夜持续时间为5秒
if (NightMode.invertTimer > NightMode.INVER_FADE_DURATION) {
NightMode.invertTimer = 0;
NightMode.invertTrigger = false;
// 切换类
NightMode.inverted = div.classList.toggle('inverted', NightMode.invertTrigger)
} else if (NightMode.invertTimer) {
NightMode.invertTimer += deltaTime;
} else {
// 每700米触发黑夜,
NightMode.invertTrigger = !(gameFrame % 500);
if (NightMode.invertTrigger && NightMode.invertTimer === 0) {
NightMode.invertTimer += deltaTime;
NightMode.inverted = div.classList.toggle('inverted', NightMode.invertTrigger);
}
}
},
reset: function () {
this.currentPhase = 0;
this.opacity = 0;
this.update(false);
}
};
// 云朵构造函数
Cloud.config = {
HEIGHT: 14,//云朵sprite的高度
MAX_CLOUD_GAP: 400,//两朵云之间最大的间隙
MAX_SKY_LEVEL: 30,//云朵 的最大高度
MIN_CLOUD_GAP: 100,//两朵云之间的最小间隙
MIN_SKY_LEVEL: 71,//云朵的最小高度
WIDTH: 46,//云朵sprite的宽度
MAX_CLOUDS: 6,//最多云朵数量
CLOUD_FREQUENCY: 0.5//云朵出现频率
};
// 储存云朵
Cloud.clouds = [];
function getRandomNum(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// canvas用于绘制的画布
// spritePos在雪碧图中的坐标
// containerWidth容器宽度
function Cloud(canvas, spritePos, containerWidth) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.spritePos = spritePos;
this.xPos = containerWidth;//云朵初始x坐标在屏幕外
this.yPos = 0;//云朵初始高度
this.remove = false;//是否移除
// console.log(this.spritePos.x);
// 云朵之间的间隙400-100
this.cloudGap = getRandomNum(Cloud.config.MAX_CLOUD_GAP, Cloud.config.MIN_CLOUD_GAP);
this.init();
}
// 主要逻辑代码在Cloud的原型链中
Cloud.prototype = {
init: function () {
// 设置云朵的高度为随机30-71
this.yPos = getRandomNum(Cloud.config.MAX_SKY_LEVEL, Cloud.config.MIN_SKY_LEVEL);
this.draw();
},
draw: function () {
// 不使用save和restore下一次绘制会在第一次绘制的基础上继续绘制,添加过后第二次绘制会回归初始化
this.ctx.save();
var sourceWidth = Cloud.config.WIDTH,
sourceHeight = Cloud.config.HEIGHT;
this.ctx.drawImage(imgSprite,
this.spritePos.x, this.spritePos.y,
sourceWidth, sourceHeight,
this.xPos, this.yPos,
sourceWidth, sourceHeight);
this.ctx.restore();
},
// 添加云朵并控制其移动
updateClouds: function (speed) {
var numClouds = Cloud.clouds.length;
if (numClouds) {
for (var i = numClouds - 1; i >= 0; i--) {
Cloud.clouds[i].update(speed);
};
var lastCloud = Cloud.clouds[numClouds - 1];
// 若当前存在的云朵数量小于最大云朵数量
// 并且云朵位置大于间隙时
// 随机添加云朵
// 云朵数量小于设定最大值,屏幕宽度-初始坐标大于最后一个云朵
if (numClouds < Cloud.config.MAX_CLOUDS &&
(DEFAULT_WIDTH - lastCloud.xPos) > 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);
}
};
// canvas地面绘制在画布上
// spritpos 地面在精灵图上的坐标
function HorizonLine(canvas, spritePos) {
this.spritePos = spritePos;
this.canvas = canvas;
this.ctx = canvas.getContext("2d");
this.sourceDimensions = {};
this.dimensions = HorizonLine.dimensions;
// 在精灵图坐标为2处和602处分别为不同的地形
this.sourceXPos = [this.spritePos.x, this.spritePos.x + this.dimensions.WIDTH];
this.xPos = [];//地面在画布中的x坐标
this.yPos = 0;//地面在画布中的y坐标
this.bumpThreshold = 0.5; //随机地形系数
this.setSourceDimesions();
this.draw();
}
HorizonLine.dimensions = {
WIDTH: 600, //宽600
HEIGHT: 12, //高12像素
YPOS: 127 //在画布中的位置
};
// HorizonLine原型中的方法
HorizonLine.prototype = {
setSourceDimesions: function () {
for (var dimension in HorizonLine.dimensions) {
this.sourceDimensions[dimension] = HorizonLine.dimensions[dimension];
this.dimensions[dimension] = HorizonLine.dimensions[dimension];
}
//地面在画布上的位置
this.xPos = [0, HorizonLine.dimensions.WIDTH];//0,600
this.yPos = HorizonLine.dimensions.YPOS;
},
//随机地形
getRandomType: function () {
return Math.random() > this.bumpThreshold ? this.dimensions.WIDTH : 0;
},
draw: function () {
//要绘制的图像,图像将要被绘制的区域的左上角的坐标,图像所要绘制区域的大小,所要绘制的图像区域的左上角坐标,图像区域所要绘制的画布大小
this.ctx.drawImage(imgSprite,
this.sourceXPos[0], this.spritePos.y,
this.sourceDimensions.WIDTH, this.sourceDimensions.HEIGHT,
this.xPos[0], this.yPos,
this.dimensions.WIDTH, this.dimensions.HEIGHT);
this.ctx.drawImage(imgSprite,
this.sourceXPos[1], this.spritePos.y,
this.sourceDimensions.WIDTH, this.sourceDimensions.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;
// 若第一段地面完全移出canvas外
if (this.xPos[line1] <= -this.dimensions.WIDTH) {
// 则将其移动至canvas外右侧
this.xPos[line1] += this.dimensions.WIDTH * 2;
// 同时将第二段地面移动至canvas内
this.xPos[line2] = this.xPos[line1] - this.dimensions.WIDTH;
// 随机选择地形
this.sourceXPos[line1] = this.getRandomType() + this.spritePos.x;
}
},
update: function (deltaTime, speed) {
//FPS每秒传真率(刷新率)
var increment = Math.floor(speed * (FPS / 1000) * deltaTime);
if (this.xPos[0] <= 0) {
// 交换地面一和地面二
this.updateXPos(0, increment);
} else {
this.updateXPos(1, increment);
}
this.draw();
},
reset: function () {
this.xPos[0] = 0;
this.xPos[1] = HorizonLine.dimensions.WIDTH;
}
};
//不写onload方法就显示不出图片
window.onload = function () {
var canvas = document.createElement('canvas'),
a = document.getElementById('runner-container'),
ctx = canvas.getContext('2d');
canvas.id = 'c';
canvas.width = 600;
canvas.height = 150;
a.appendChild(canvas);
// console.log(spriteDefinition.CLOUD.x);
var speed = 3;
var h = new HorizonLine(canvas, spriteDefinition.HORIZON);
var cloud = new Cloud(canvas, spriteDefinition.CLOUD, DEFAULT_WIDTH);
var night = new NightMode(canvas, spriteDefinition.MOON, DEFAULT_WIDTH);
var obstacle = new Obstacle(canvas,Obstacle.types[0] ,spriteDefinition, 0.6, 1);
// console.log(obstacle);
var startTime = 0;
var deltaTime;
(function draw(time) {
gameFrame++;
if (speed < 13.5) {
speed += 0.01;
}
// 清空矩形区域内的绘图
ctx.clearRect(0, 0, 600, 150);
time = time || 0;
deltaTime = time - startTime;
h.update(deltaTime, speed);
cloud.updateClouds(0.2);
night.invert(deltaTime);
obstacle.updateObstacles(deltaTime, speed);
// console.log(speed);
startTime = time;
window.requestAnimationFrame(draw, canvas);
})();
};
</script>
参考了博客园逐影的文章,讲的非常详细。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)