Chrome自带恐龙小游戏的源码研究(三)
在上一篇《Chrome自带恐龙小游戏的源码研究(二)》中实现了云朵的绘制和移动,这一篇主要研究如何让游戏实现昼夜交替。
昼夜交替的效果主要是通过样式来完成,但改变样式的时机则由脚本控制。 首先对游戏容器使用transition创建一个贝塞尔渐变:
1 .game-body{ 2 transition:filter 1.5s cubic-bezier(0.65, 0.05, 0.36, 1), 3 background-color 1.5s cubic-bezier(0.65, 0.05, 0.36, 1); 4 5 /*告诉浏览器即将对某元素执行什么动画,这样浏览器可以提前进行准备来优化动画效果,使动画更为流畅*/ 6 will-change: filter,background-color; 7 }
渐变作用于两个属性:filter及background-color。
1 .inverted{ 2 filter: invert(100%); /*反色*/ 3 background-color: #000; /*改变背景为黑色(入夜效果)*/ 4 }
当符合条件时,游戏容器将添加inverted样式,以达到昼夜交替效果。
接下来看一下代码实现:
1 NightMode.config = { 2 FADE_SPEED: 0.035, //淡入淡出速度 3 HEIGHT: 40, //月亮高度 4 MOON_SPEED: 0.25, //月亮移动速度 5 NUM_STARS: 2, //星星数量 6 STAR_SIZE: 9, //星星宽度 7 STAR_SPEED: 0.3,//星星速度 8 STAR_MAX_Y: 70, //星星在画布上出现的位置 9 WIDTH: 20 //半个月度宽度 10 }; 11 //月亮在不同时期有不同的位置 12 NightMode.phases = [140,120,100,60,40,20,0]; 13 14 //时间记录 15 NightMode.invertTimer = 0; 16 //是否可以进行昼夜交替 17 NightMode.inverted = false; 18 //用于控制样式切换 19 NightMode.invertTrigger = false; 20 //黑夜持续时间 21 NightMode.INVERT_FADE_DURATION = 5000;
1 function NightMode(canvas,spritePos,containerWidth) { 2 this.spritePos = spritePos; 3 this.canvas = canvas; 4 this.ctx = canvas.getContext("2d"); 5 this.containerWidth = containerWidth; 6 this.xPos = containerWidth - 50; //月亮的x坐标 7 this.yPos = 30; //月亮的y坐标 8 this.currentPhase = 0; 9 this.opacity = 0; 10 this.stars = []; //用于存储星星 11 this.drawStars = false; //是否绘制星星 12 this.placeStars(); //放置星星 13 } 14 15 NightMode.prototype = { 16 update:function(activated) { 17 //若夜晚模式处于激活状态且opacity为0时 18 //对月亮周期进行更新 19 if(activated && this.opacity == 0) { 20 this.currentPhase++; 21 if(this.currentPhase >= NightMode.phases.length) { 22 this.currentPhase = 0; 23 } 24 } 25 26 //淡入 27 if(activated && (this.opacity < 1 || this.opacity == 0)) { 28 this.opacity += NightMode.config.FADE_SPEED; 29 } else if(this.opacity > 0) {//淡出 30 this.opacity -= NightMode.config.FADE_SPEED; 31 } 32 33 //当opacity大于0时移动月亮位置 34 if(this.opacity > 0) { 35 this.xPos = this.updateXPos(this.xPos, NightMode.config.MOON_SPEED); 36 37 //移动星星 38 if(this.drawStars) { 39 for (var i = 0; i < NightMode.config.NUM_STARS; i++) { 40 this.stars[i].x = this.updateXPos(this.stars[i].x,NightMode.config.STAR_SPEED); 41 } 42 } 43 this.draw(); 44 } else { 45 this.opacity = 0; 46 this.placeStars(); 47 } 48 this.drawStars = true; 49 }, 50 updateXPos: function(currentPos, speed) { 51 if (currentPos < -NightMode.config.WIDTH) { 52 currentPos = this.containerWidth; 53 } else { 54 currentPos -= speed; 55 } 56 return currentPos; 57 }, 58 draw:function() { 59 //周期为3时画满月 60 var moonSourceWidth = this.currentPhase == 3 ? NightMode.config.WIDTH * 2 : 61 NightMode.config.WIDTH; 62 var moonSourceHeight = NightMode.config.HEIGHT; 63 //从雪碧图上获取月亮正确的形状 64 var moonSourceX = this.spritePos.x + NightMode.phases[this.currentPhase]; 65 var moonOutputWidth = moonSourceWidth; 66 var starSize = NightMode.config.STAR_SIZE; 67 var starSourceX = spriteDefinition.STAR.x; 68 69 this.ctx.save(); 70 //画布透明度也随之变化 71 this.ctx.globalAlpha = this.opacity; 72 73 if (this.drawStars) { 74 for (var i = 0; i < NightMode.config.NUM_STARS; i++) { 75 this.ctx.drawImage(imgSprite, 76 starSourceX, this.stars[i].sourceY, 77 starSize, starSize, 78 Math.round(this.stars[i].x), this.stars[i].y, 79 NightMode.config.STAR_SIZE, NightMode.config.STAR_SIZE); 80 } 81 } 82 83 this.ctx.drawImage(imgSprite, 84 moonSourceX, this.spritePos.y, 85 moonSourceWidth, moonSourceHeight, 86 Math.round(this.xPos), this.yPos, 87 moonOutputWidth, NightMode.config.HEIGHT); 88 89 this.ctx.globalAlpha = 1; 90 this.ctx.restore(); 91 }, 92 placeStars:function() { 93 //将画布分为若干组 94 var segmentSize = Math.round(this.containerWidth /NightMode.config.NUM_STARS); 95 for (var i = 0; i < NightMode.config.NUM_STARS; i++) { 96 this.stars[i] = {}; 97 //每组星星位置随机 98 this.stars[i].x = getRandomNum(segmentSize * i, segmentSize * (i + 1)); 99 this.stars[i].y = getRandomNum(0, NightMode.config.STAR_MAX_Y); 100 this.stars[i].sourceY = spriteDefinition.STAR.y + NightMode.config.STAR_SIZE * i; 101 } 102 }, 103 invert:function(deltaTime) { 104 this.update(NightMode.inverted); 105 106 //黑夜持续时间5秒 107 if(NightMode.invertTimer > NightMode.INVERT_FADE_DURATION) { 108 NightMode.invertTimer = 0; 109 NightMode.invertTrigger = false; 110 NightMode.inverted = document.body.classList.toggle('inverted',NightMode.invertTrigger); 111 } else if(NightMode.invertTimer) { 112 NightMode.invertTimer += deltaTime; 113 } else { 114 //每500帧触发黑夜,这里只是为了模拟效果,完整游戏中是每700米触发一次黑夜 115 NightMode.invertTrigger = !(gameFrame % 500); 116 if(NightMode.invertTrigger && NightMode.invertTimer === 0) { 117 NightMode.invertTimer += deltaTime; 118 NightMode.inverted = document.body.classList.toggle('inverted',NightMode.invertTrigger); 119 } 120 } 121 }, 122 reset: function() { 123 this.currentPhase = 0; 124 this.opacity = 0; 125 this.update(false); 126 } 127 };
最后添加测试代码:
1 window.onload = function () { 2 var h = new HorizonLine(c,spriteDefinition.HORIZON); 3 var cloud = new Cloud(c,spriteDefinition.CLOUD,DEFAULT_WIDTH); 4 var night = new NightMode(c,spriteDefinition.MOON,DEFAULT_WIDTH); 5 var startTime = 0; 6 var deltaTime; 7 (function draw(time) { 8 gameFrame++; 9 ctx.clearRect(0,0,600,150); 10 time = time || 0; 11 deltaTime = time - startTime; 12 h.update(deltaTime,3); 13 cloud.updateClouds(0.2); 14 night.invert(deltaTime); 15 startTime = time; 16 window.requestAnimationFrame(draw,c); 17 })(); 18 };
下面是运行效果(每500帧切换一次):