|
| 1 | +<!DOCTYPE HTML> |
| 2 | +<title>Blue Robot Demo</title> |
| 3 | +<style> |
| 4 | + html { overflow: hidden; min-height: 200px; min-width: 380px; } |
| 5 | + body { height: 200px; position: relative; margin: 8px; } |
| 6 | + .buttons { position: absolute; bottom: 0px; left: 0px; margin: 4px; } |
| 7 | +</style> |
| 8 | +<canvas width="380" height="200"></canvas> |
| 9 | +<script> |
| 10 | + var Landscape = function (context, width, height) { |
| 11 | + this.offset = 0; |
| 12 | + this.width = width; |
| 13 | + this.advance = function (dx) { |
| 14 | + this.offset += dx; |
| 15 | + }; |
| 16 | + this.horizon = height * 0.7; |
| 17 | + // This creates the sky gradient (from a darker blue to white at the bottom) |
| 18 | + this.sky = context.createLinearGradient(0, 0, 0, this.horizon); |
| 19 | + this.sky.addColorStop(0.0, 'rgb(55,121,179)'); |
| 20 | + this.sky.addColorStop(0.7, 'rgb(121,194,245)'); |
| 21 | + this.sky.addColorStop(1.0, 'rgb(164,200,214)'); |
| 22 | + // this creates the grass gradient (from a darker green to a lighter green) |
| 23 | + this.earth = context.createLinearGradient(0, this.horizon, 0, height); |
| 24 | + this.earth.addColorStop(0.0, 'rgb(81,140,20)'); |
| 25 | + this.earth.addColorStop(1.0, 'rgb(123,177,57)'); |
| 26 | + this.paintBackground = function (context, width, height) { |
| 27 | + // first, paint the sky and grass rectangles |
| 28 | + context.fillStyle = this.sky; |
| 29 | + context.fillRect(0, 0, width, this.horizon); |
| 30 | + context.fillStyle = this.earth; |
| 31 | + context.fillRect(0, this.horizon, width, height-this.horizon); |
| 32 | + // then, draw the cloudy banner |
| 33 | + // we make it cloudy by having the draw text off the top of the |
| 34 | + // canvas, and just having the blurred shadow shown on the canvas |
| 35 | + context.save(); |
| 36 | + context.translate(width-((this.offset+(this.width*3.2)) % (this.width*4.0))+0, 0); |
| 37 | + context.shadowColor = 'white'; |
| 38 | + context.shadowOffsetY = 30+this.horizon/3; // offset down on canvas |
| 39 | + context.shadowBlur = '5'; |
| 40 | + context.fillStyle = 'white'; |
| 41 | + context.textAlign = 'left'; |
| 42 | + context.textBaseline = 'top'; |
| 43 | + context.font = '20px sans-serif'; |
| 44 | + context.fillText('WHATWG ROCKS', 10, -30); // text up above canvas |
| 45 | + context.restore(); |
| 46 | + // then, draw the background tree |
| 47 | + context.save(); |
| 48 | + context.translate(width-((this.offset+(this.width*0.2)) % (this.width*1.5))+30, 0); |
| 49 | + context.beginPath(); |
| 50 | + context.fillStyle = 'rgb(143,89,2)'; |
| 51 | + context.lineStyle = 'rgb(10,10,10)'; |
| 52 | + context.lineWidth = 2; |
| 53 | + context.rect(0, this.horizon+5, 10, -50); // trunk |
| 54 | + context.fill(); |
| 55 | + context.stroke(); |
| 56 | + context.beginPath(); |
| 57 | + context.fillStyle = 'rgb(78,154,6)'; |
| 58 | + context.arc(5, this.horizon-60, 30, 0, Math.PI*2); // leaves |
| 59 | + context.fill(); |
| 60 | + context.stroke(); |
| 61 | + context.restore(); |
| 62 | + }; |
| 63 | + this.paintForeground = function (context, width, height) { |
| 64 | + // draw the box that goes in front |
| 65 | + context.save(); |
| 66 | + context.translate(width-((this.offset+(this.width*0.7)) % (this.width*1.1))+0, 0); |
| 67 | + context.beginPath(); |
| 68 | + context.rect(0, this.horizon - 5, 25, 25); |
| 69 | + context.fillStyle = 'rgb(220,154,94)'; |
| 70 | + context.lineStyle = 'rgb(10,10,10)'; |
| 71 | + context.lineWidth = 2; |
| 72 | + context.fill(); |
| 73 | + context.stroke(); |
| 74 | + context.restore(); |
| 75 | + }; |
| 76 | + }; |
| 77 | +</script> |
| 78 | +<script> |
| 79 | + var BlueRobot = function () { |
| 80 | + this.sprites = new Image(); |
| 81 | + this.sprites.src = 'blue-robot.png'; // this sprite sheet has 8 cells |
| 82 | + this.targetMode = 'idle'; |
| 83 | + this.walk = function () { |
| 84 | + this.targetMode = 'walk'; |
| 85 | + }; |
| 86 | + this.stop = function () { |
| 87 | + this.targetMode = 'idle'; |
| 88 | + }; |
| 89 | + this.frameIndex = { |
| 90 | + 'idle': [0], // first cell is the idle frame |
| 91 | + 'walk': [1,2,3,4,5,6], // the walking animation is cells 1-6 |
| 92 | + 'stop': [7], // last cell is the stopping animation |
| 93 | + }; |
| 94 | + this.mode = 'idle'; |
| 95 | + this.frame = 0; // index into frameIndex |
| 96 | + this.tick = function () { |
| 97 | + // this advances the frame and the robot |
| 98 | + // the return value is how many pixels the robot has moved |
| 99 | + this.frame += 1; |
| 100 | + if (this.frame >= this.frameIndex[this.mode].length) { |
| 101 | + // we've reached the end of this animation cycle |
| 102 | + this.frame = 0; |
| 103 | + if (this.mode != this.targetMode) { |
| 104 | + // switch to next cycle |
| 105 | + if (this.mode == 'walk') { |
| 106 | + // we need to stop walking before we decide what to do next |
| 107 | + this.mode = 'stop'; |
| 108 | + } else if (this.mode == 'stop') { |
| 109 | + if (this.targetMode == 'walk') |
| 110 | + this.mode = 'walk'; |
| 111 | + else |
| 112 | + this.mode = 'idle'; |
| 113 | + } else if (this.mode == 'idle') { |
| 114 | + if (this.targetMode == 'walk') |
| 115 | + this.mode = 'walk'; |
| 116 | + } |
| 117 | + } |
| 118 | + } |
| 119 | + if (this.mode == 'walk') |
| 120 | + return 8; |
| 121 | + return 0; |
| 122 | + }, |
| 123 | + this.paint = function (context, x, y) { |
| 124 | + if (!this.sprites.complete) return; |
| 125 | + // draw the right frame out of the sprite sheet onto the canvas |
| 126 | + // we assume each frame is as high as the sprite sheet |
| 127 | + // the x,y coordinates give the position of the bottom center of the sprite |
| 128 | + context.drawImage(this.sprites, |
| 129 | + this.frameIndex[this.mode][this.frame] * this.sprites.height, 0, this.sprites.height, this.sprites.height, |
| 130 | + x-this.sprites.height/2, y-this.sprites.height, this.sprites.height, this.sprites.height); |
| 131 | + }; |
| 132 | + }; |
| 133 | +</script> |
| 134 | +<script> |
| 135 | + var animating = false; |
| 136 | + var canvas = document.getElementsByTagName('canvas')[0]; |
| 137 | + var context = canvas.getContext('2d'); |
| 138 | + var landscape = new Landscape(context, canvas.width, canvas.height); |
| 139 | + var blueRobot = new BlueRobot(); |
| 140 | + // paint when the browser wants us to, using requestAnimationFrame() |
| 141 | + function paint() { |
| 142 | + context.clearRect(0, 0, canvas.width, canvas.height); |
| 143 | + landscape.paintBackground(context, canvas.width, canvas.height); |
| 144 | + blueRobot.paint(context, canvas.width/2, landscape.horizon*1.1); |
| 145 | + landscape.paintForeground(context, canvas.width, canvas.height); |
| 146 | + if (animating) |
| 147 | + requestAnimationFrame(paint); |
| 148 | + } |
| 149 | + var interval = null; |
| 150 | + var cancelingTimeout = null; |
| 151 | + function startAnim() { |
| 152 | + if (cancelingTimeout) { |
| 153 | + clearTimeout(cancelingTimeout); |
| 154 | + cancelingTimeout = null; |
| 155 | + } |
| 156 | + if (!animating) { |
| 157 | + animating = true; |
| 158 | + paint(); |
| 159 | + // but tick every 150ms, so that we don't slow down when we don't paint |
| 160 | + interval = setInterval(function () { |
| 161 | + var dx = blueRobot.tick(); |
| 162 | + landscape.advance(dx); |
| 163 | + }, 100); |
| 164 | + } |
| 165 | + } |
| 166 | + function stopAnim() { |
| 167 | + if (cancelingTimeout) return; |
| 168 | + cancelingTimeout = setTimeout(function () { |
| 169 | + cancelingTimeout = null; |
| 170 | + if (animating) { |
| 171 | + clearInterval(interval); |
| 172 | + animating = false; |
| 173 | + } |
| 174 | + }, 1000); |
| 175 | + } |
| 176 | + paint(); |
| 177 | + blueRobot.sprites.onload = paint; |
| 178 | +</script> |
| 179 | +<p class="buttons"> |
| 180 | + <input type=button value="Walk" onclick="blueRobot.walk(); startAnim();"> |
| 181 | + <input type=button value="Stop" onclick="blueRobot.stop(); stopAnim();"> |
| 182 | +<footer> |
| 183 | + <small> Blue Robot Player Sprite by <a href="https://johncolburn.deviantart.com/">JohnColburn</a>. |
| 184 | + Licensed under the terms of the Creative Commons Attribution Share-Alike 3.0 Unported license.</small> |
| 185 | + <small> This work is itself licensed under a <a rel="license" href="https://creativecommons.org/licenses/by-sa/3.0/">Creative |
| 186 | + Commons Attribution-ShareAlike 3.0 Unported License</a>.</small> |
| 187 | +</footer> |
0 commit comments