码迷,mamicode.com
首页 > 编程语言 > 详细

【转】265行JavaScript代码的第一人称3D H5游戏Demo

时间:2016-01-02 18:35:25      阅读:313      评论:0      收藏:0      [点我收藏+]

标签:

译文:http://blog.jobbole.com/70956/

原文:http://www.playfuljs.com/a-first-person-engine-in-265-lines/

这是一篇关于利用 Canvas 实现3D 游戏场景绘制的文章,看完感觉很受启发,所以自己准备总结一下。我们先看下最终效果:

技术分享

很酷是不是。原作者只使用了265行代码就实现了这么炫酷的效果。代码写的也很清晰明了,并且原文也对原理也进行了解释。本篇就直接将原作者的代码全部贴上来大家观赏观赏。


 

  1 <!doctype html>
  2 <html>
  3   <head>
  4     <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
  5     <title>Raycaster Demo - PlayfulJS</title>
  6   </head>
  7   <body style=‘background: #000; margin: 0; padding: 0; width: 100%; height: 100%;‘>
  8     <canvas id=‘display‘ width=‘1‘ height=‘1‘ style=‘width: 100%; height: 100%;‘ />
  9 
 10     <script>
 11 
 12       var CIRCLE = Math.PI * 2;
 13       var MOBILE = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent)
 14 
 15       function Controls() {
 16         this.codes  = { 37: left, 39: right, 38: forward, 40: backward };
 17         this.states = { left: false, right: false, forward: false, backward: false };
 18         document.addEventListener(keydown, this.onKey.bind(this, true), false);
 19         document.addEventListener(keyup, this.onKey.bind(this, false), false);
 20         document.addEventListener(touchstart, this.onTouch.bind(this), false);
 21         document.addEventListener(touchmove, this.onTouch.bind(this), false);
 22         document.addEventListener(touchend, this.onTouchEnd.bind(this), false);
 23       }
 24 
 25       Controls.prototype.onTouch = function(e) {
 26         var t = e.touches[0];
 27         this.onTouchEnd(e);
 28         if (t.pageY < window.innerHeight * 0.5) this.onKey(true, { keyCode: 38 });
 29         else if (t.pageX < window.innerWidth * 0.5) this.onKey(true, { keyCode: 37 });
 30         else if (t.pageY > window.innerWidth * 0.5) this.onKey(true, { keyCode: 39 });
 31       };
 32 
 33       Controls.prototype.onTouchEnd = function(e) {
 34         this.states = { left: false, right: false, forward: false, backward: false };
 35         e.preventDefault();
 36         e.stopPropagation();
 37       };
 38 
 39       Controls.prototype.onKey = function(val, e) {
 40         var state = this.codes[e.keyCode];
 41         if (typeof state === undefined) return;
 42         this.states[state] = val;
 43         e.preventDefault && e.preventDefault();
 44         e.stopPropagation && e.stopPropagation();
 45         console.log(e.keyCode);
 46       };
 47 
 48       function Bitmap(src, width, height) {
 49         this.image = new Image();
 50         this.image.src = src;
 51         this.width = width;
 52         this.height = height;
 53       }
 54       
 55       function Player(x, y, direction) {
 56         this.x = x;
 57         this.y = y;
 58         this.direction = direction;
 59         this.weapon = new Bitmap(assets/knife_hand.png, 319, 320);
 60         this.paces = 0;
 61       }
 62 
 63       Player.prototype.rotate = function(angle) {
 64         this.direction = (this.direction + angle + CIRCLE) % (CIRCLE);
 65       };
 66 
 67       Player.prototype.walk = function(distance, map) {
 68         var dx = Math.cos(this.direction) * distance;
 69         var dy = Math.sin(this.direction) * distance;
 70         if (map.get(this.x + dx, this.y) <= 0) this.x += dx;
 71         if (map.get(this.x, this.y + dy) <= 0) this.y += dy;
 72         this.paces += distance;
 73       };
 74 
 75       Player.prototype.update = function(controls, map, seconds) {
 76         if (controls.left) this.rotate(-Math.PI * seconds);
 77         if (controls.right) this.rotate(Math.PI * seconds);
 78         if (controls.forward) this.walk(3 * seconds, map);
 79         if (controls.backward) this.walk(-3 * seconds, map);
 80       };
 81 
 82       function Map(size) {
 83         this.size = size;
 84         this.wallGrid = new Uint8Array(size * size);
 85         this.skybox = new Bitmap(assets/deathvalley_panorama.jpg, 2000, 750);
 86         this.wallTexture = new Bitmap(assets/wall_texture.jpg, 1024, 1024);
 87         this.light = 0;
 88       }
 89 
 90       Map.prototype.get = function(x, y) {
 91         x = Math.floor(x);
 92         y = Math.floor(y);
 93         if (x < 0 || x > this.size - 1 || y < 0 || y > this.size - 1) return -1;
 94         return this.wallGrid[y * this.size + x];
 95       };
 96 
 97       Map.prototype.randomize = function() {
 98         for (var i = 0; i < this.size * this.size; i++) {
 99           this.wallGrid[i] = Math.random() < 0.3 ? 1 : 0;
100         }
101       };
102 
103       Map.prototype.cast = function(point, angle, range) {
104         var self = this;
105         var sin = Math.sin(angle);
106         var cos = Math.cos(angle);
107         var noWall = { length2: Infinity };
108 
109         return ray({ x: point.x, y: point.y, height: 0, distance: 0 });
110 
111         function ray(origin) {
112           var stepX = step(sin, cos, origin.x, origin.y);
113           var stepY = step(cos, sin, origin.y, origin.x, true);
114           var nextStep = stepX.length2 < stepY.length2
115             ? inspect(stepX, 1, 0, origin.distance, stepX.y)
116             : inspect(stepY, 0, 1, origin.distance, stepY.x);
117 
118           if (nextStep.distance > range) return [origin];
119           return [origin].concat(ray(nextStep));
120         }
121 
122         function step(rise, run, x, y, inverted) {
123           if (run === 0) return noWall;
124           var dx = run > 0 ? Math.floor(x + 1) - x : Math.ceil(x - 1) - x;
125           var dy = dx * (rise / run);
126           return {
127             x: inverted ? y + dy : x + dx,
128             y: inverted ? x + dx : y + dy,
129             length2: dx * dx + dy * dy
130           };
131         }
132 
133         function inspect(step, shiftX, shiftY, distance, offset) {
134           var dx = cos < 0 ? shiftX : 0;
135           var dy = sin < 0 ? shiftY : 0;
136           step.height = self.get(step.x - dx, step.y - dy);
137           step.distance = distance + Math.sqrt(step.length2);
138           if (shiftX) step.shading = cos < 0 ? 2 : 0;
139           else step.shading = sin < 0 ? 2 : 1;
140           step.offset = offset - Math.floor(offset);
141           return step;
142         }
143       };
144 
145       Map.prototype.update = function(seconds) {
146         if (this.light > 0) this.light = Math.max(this.light - 10 * seconds, 0);
147         else if (Math.random() * 5 < seconds) this.light = 2;
148       };
149 
150       function Camera(canvas, resolution, focalLength) {
151         this.ctx = canvas.getContext(2d);
152         this.width = canvas.width = window.innerWidth * 0.5;
153         this.height = canvas.height = window.innerHeight * 0.5;
154         this.resolution = resolution;
155         this.spacing = this.width / resolution;
156         this.focalLength = focalLength || 0.8;
157         this.range = MOBILE ? 8 : 14;
158         this.lightRange = 5;
159         this.scale = (this.width + this.height) / 1200;
160       }
161 
162       Camera.prototype.render = function(player, map) {
163         this.drawSky(player.direction, map.skybox, map.light);
164         this.drawColumns(player, map);
165         this.drawWeapon(player.weapon, player.paces);
166       };
167 
168       Camera.prototype.drawSky = function(direction, sky, ambient) {
169         var width = sky.width * (this.height / sky.height) * 2;
170         var left = (direction / CIRCLE) * -width;
171 
172         this.ctx.save();
173         this.ctx.drawImage(sky.image, left, 0, width, this.height);
174         if (left < width - this.width) {
175           this.ctx.drawImage(sky.image, left + width, 0, width, this.height);
176         }
177         if (ambient > 0) {
178           this.ctx.fillStyle = #ffffff;
179           this.ctx.globalAlpha = ambient * 0.1;
180           this.ctx.fillRect(0, this.height * 0.5, this.width, this.height * 0.5);
181         }
182         this.ctx.restore();
183       };
184 
185       Camera.prototype.drawColumns = function(player, map) {
186         this.ctx.save();
187         for (var column = 0; column < this.resolution; column++) {
188           var x = column / this.resolution - 0.5;
189           var angle = Math.atan2(x, this.focalLength);
190           var ray = map.cast(player, player.direction + angle, this.range);
191           this.drawColumn(column, ray, angle, map);
192         }
193         this.ctx.restore();
194       };
195 
196       Camera.prototype.drawWeapon = function(weapon, paces) {
197         var bobX = Math.cos(paces * 2) * this.scale * 6;
198         var bobY = Math.sin(paces * 4) * this.scale * 6;
199         var left = this.width * 0.66 + bobX;
200         var top = this.height * 0.6 + bobY;
201         this.ctx.drawImage(weapon.image, left, top, weapon.width * this.scale, weapon.height * this.scale);
202       };
203 
204       Camera.prototype.drawColumn = function(column, ray, angle, map) {
205         var ctx = this.ctx;
206         var texture = map.wallTexture;
207         var left = Math.floor(column * this.spacing);
208         var width = Math.ceil(this.spacing);
209         var hit = -1;
210 
211         while (++hit < ray.length && ray[hit].height <= 0);
212 
213         for (var s = ray.length - 1; s >= 0; s--) {
214           var step = ray[s];
215           var rainDrops = Math.pow(Math.random(), 3) * s;
216           var rain = (rainDrops > 0) && this.project(0.1, angle, step.distance);
217 
218           if (s === hit) {
219             var textureX = Math.floor(texture.width * step.offset);
220             var wall = this.project(step.height, angle, step.distance);
221 
222             ctx.globalAlpha = 1;
223             ctx.drawImage(texture.image, textureX, 0, 1, texture.height, left, wall.top, width, wall.height);
224             
225             ctx.fillStyle = #000000;
226             ctx.globalAlpha = Math.max((step.distance + step.shading) / this.lightRange - map.light, 0);
227             ctx.fillRect(left, wall.top, width, wall.height);
228           }
229           
230           ctx.fillStyle = #ffffff;
231           ctx.globalAlpha = 0.15;
232           while (--rainDrops > 0) ctx.fillRect(left, Math.random() * rain.top, 1, rain.height);
233         }
234       };
235 
236       Camera.prototype.project = function(height, angle, distance) {
237         var z = distance * Math.cos(angle);
238         var wallHeight = this.height * height / z;
239         var bottom = this.height / 2 * (1 + 1 / z);
240         return {
241           top: bottom - wallHeight,
242           height: wallHeight
243         }; 
244       };
245 
246       function GameLoop() {
247         this.frame = this.frame.bind(this);
248         this.lastTime = 0;
249         this.callback = function() {};
250       }
251 
252       GameLoop.prototype.start = function(callback) {
253         this.callback = callback;
254         requestAnimationFrame(this.frame);
255       };
256 
257       GameLoop.prototype.frame = function(time) {
258         var seconds = (time - this.lastTime) / 1000;
259         this.lastTime = time;
260         if (seconds < 0.2) this.callback(seconds);
261         requestAnimationFrame(this.frame);
262       };
263 
264       var display = document.getElementById(display);
265       var player = new Player(15.3, -1.2, Math.PI * 0.3);
266       var map = new Map(32);
267       var controls = new Controls();
268       var camera = new Camera(display, MOBILE ? 160 : 320, 0.8);
269       var loop = new GameLoop();
270 
271       map.randomize();
272       
273       loop.start(function frame(seconds) {
274         map.update(seconds);
275         player.update(controls.states, map, seconds);
276         camera.render(player, map);
277       });
278 
279     </script>
280     <script>
281       (function(i,s,o,g,r,a,m){i[GoogleAnalyticsObject]=r;i[r]=i[r]||function(){
282       (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
283       m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
284       })(window,document,script,//www.google-analytics.com/analytics.js,ga);
285 
286       ga(create, UA-50885475-1, playfuljs.com);
287       ga(send, pageview);
288     </script>
289   </body>
290 </html>

 

【转】265行JavaScript代码的第一人称3D H5游戏Demo

标签:

原文地址:http://www.cnblogs.com/muyun/p/5094992.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!