├── .gitignore ├── Procfile ├── README.md ├── index.js ├── package-lock.json ├── package.json └── public ├── index.html ├── libs └── p5.min.js └── sketch.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node index.js 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # javascript-ray-casting 2 | 3 | **javascript-ray-casting** is a [Ray Casting] engine built using [p5.js] to render all the graphics. It attempts to simulate a pseudo 3D environment from a 2D map 4 | 5 | This technique was very popular in game development in the 90s, when computer could not simulate a fully 3D environment in real time. Ray casting can go very fast, because only a calculation has to be done for every vertical line of the screen. Some of the most well known games that used this technique, are of course [Wolfenstein 3D] and [DOOM]. 6 | 7 | * [Try the app now] 8 | 9 | For a more in depth explanation of the calculations involved, I recommend the following awesome tutorials: 10 | 11 | * [Lode's Computer Graphics Tutorial: Raycasting] by Lode Vandevenne 12 | * [Ray-Casting Tutorial For Game Development And Other Purposes] by F. Permadi 13 | 14 |

15 | d3-mini-game gameplay screenshot 16 |

17 | 18 |

19 | d3-mini-game gameplay screenshot 20 |

21 | 22 |

23 | d3-mini-game gameplay screenshot 24 |

25 | 26 | 29 | [Ray Casting]:https://en.wikipedia.org/wiki/Ray_casting 30 | [p5.js]:https://p5js.org/ 31 | [Wolfenstein 3D]:https://en.wikipedia.org/wiki/Wolfenstein_3D 32 | [DOOM]:https://en.wikipedia.org/wiki/Doom_(series) 33 | [Try the app now]:https://ray-casting-aruvham.herokuapp.com/ 34 | [Lode's Computer Graphics Tutorial: Raycasting]:http://lodev.org/cgtutor/raycasting.html 35 | [Ray-Casting Tutorial For Game Development And Other Purposes]:http://permadi.com/1996/05/ray-casting-tutorial-table-of-contents/ 36 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var app = express(); 3 | 4 | app.use(express.static(__dirname + '/public/')); 5 | app.set('port', (process.env.PORT || 3000)); 6 | app.listen(app.get('port'), function() { 7 | console.log('Node app is running on port', app.get('port')); 8 | }); 9 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "javascript-ray-casting", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "dependencies": { 6 | "accepts": { 7 | "version": "1.3.3", 8 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", 9 | "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=" 10 | }, 11 | "array-flatten": { 12 | "version": "1.1.1", 13 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 14 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 15 | }, 16 | "content-disposition": { 17 | "version": "0.5.2", 18 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", 19 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" 20 | }, 21 | "content-type": { 22 | "version": "1.0.2", 23 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz", 24 | "integrity": "sha1-t9ETrueo3Se9IRM8TcJSnfFyHu0=" 25 | }, 26 | "cookie": { 27 | "version": "0.3.1", 28 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 29 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 30 | }, 31 | "cookie-signature": { 32 | "version": "1.0.6", 33 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 34 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 35 | }, 36 | "debug": { 37 | "version": "2.6.7", 38 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz", 39 | "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=" 40 | }, 41 | "depd": { 42 | "version": "1.1.0", 43 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", 44 | "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM=" 45 | }, 46 | "destroy": { 47 | "version": "1.0.4", 48 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 49 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 50 | }, 51 | "ee-first": { 52 | "version": "1.1.1", 53 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 54 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 55 | }, 56 | "encodeurl": { 57 | "version": "1.0.1", 58 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", 59 | "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" 60 | }, 61 | "escape-html": { 62 | "version": "1.0.3", 63 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 64 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 65 | }, 66 | "etag": { 67 | "version": "1.8.0", 68 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.0.tgz", 69 | "integrity": "sha1-b2Ma7zNtbEY2K1F2QETOIWvjwFE=" 70 | }, 71 | "express": { 72 | "version": "4.15.3", 73 | "resolved": "https://registry.npmjs.org/express/-/express-4.15.3.tgz", 74 | "integrity": "sha1-urZdDwOqgMNYQIly/HAPkWlEtmI=" 75 | }, 76 | "finalhandler": { 77 | "version": "1.0.3", 78 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.3.tgz", 79 | "integrity": "sha1-70fneVDpmXgOhgIqVg4yF+DQzIk=" 80 | }, 81 | "forwarded": { 82 | "version": "0.1.0", 83 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz", 84 | "integrity": "sha1-Ge+YdMSuHCl7zweP3mOgm2aoQ2M=" 85 | }, 86 | "fresh": { 87 | "version": "0.5.0", 88 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.0.tgz", 89 | "integrity": "sha1-9HTKXmqSRtb9jglTz6m5yAWvp44=" 90 | }, 91 | "http-errors": { 92 | "version": "1.6.1", 93 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.1.tgz", 94 | "integrity": "sha1-X4uO2YrKVFZWv1cplzh/kEpyIlc=" 95 | }, 96 | "inherits": { 97 | "version": "2.0.3", 98 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 99 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 100 | }, 101 | "ipaddr.js": { 102 | "version": "1.3.0", 103 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.3.0.tgz", 104 | "integrity": "sha1-HgOlL9rYOou7KyXL9JmLTP/NPew=" 105 | }, 106 | "media-typer": { 107 | "version": "0.3.0", 108 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 109 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 110 | }, 111 | "merge-descriptors": { 112 | "version": "1.0.1", 113 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 114 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 115 | }, 116 | "methods": { 117 | "version": "1.1.2", 118 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 119 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 120 | }, 121 | "mime": { 122 | "version": "1.3.4", 123 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", 124 | "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=" 125 | }, 126 | "mime-db": { 127 | "version": "1.27.0", 128 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", 129 | "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=" 130 | }, 131 | "mime-types": { 132 | "version": "2.1.15", 133 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", 134 | "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=" 135 | }, 136 | "ms": { 137 | "version": "2.0.0", 138 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 139 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 140 | }, 141 | "negotiator": { 142 | "version": "0.6.1", 143 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 144 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 145 | }, 146 | "on-finished": { 147 | "version": "2.3.0", 148 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 149 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=" 150 | }, 151 | "parseurl": { 152 | "version": "1.3.1", 153 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz", 154 | "integrity": "sha1-yKuMkiO6NIiKpkopeyiFO+wY2lY=" 155 | }, 156 | "path-to-regexp": { 157 | "version": "0.1.7", 158 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 159 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 160 | }, 161 | "proxy-addr": { 162 | "version": "1.1.4", 163 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.4.tgz", 164 | "integrity": "sha1-J+VF9pYKRKYn2bREZ+NcG2tM4vM=" 165 | }, 166 | "qs": { 167 | "version": "6.4.0", 168 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", 169 | "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" 170 | }, 171 | "range-parser": { 172 | "version": "1.2.0", 173 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 174 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" 175 | }, 176 | "send": { 177 | "version": "0.15.3", 178 | "resolved": "https://registry.npmjs.org/send/-/send-0.15.3.tgz", 179 | "integrity": "sha1-UBP5+ZAj31DRvZiSwZ4979HVMwk=" 180 | }, 181 | "serve-static": { 182 | "version": "1.12.3", 183 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.3.tgz", 184 | "integrity": "sha1-n0uhni8wMMVH+K+ZEHg47DjVseI=" 185 | }, 186 | "setprototypeof": { 187 | "version": "1.0.3", 188 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", 189 | "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" 190 | }, 191 | "statuses": { 192 | "version": "1.3.1", 193 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", 194 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 195 | }, 196 | "type-is": { 197 | "version": "1.6.15", 198 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", 199 | "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=" 200 | }, 201 | "unpipe": { 202 | "version": "1.0.0", 203 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 204 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 205 | }, 206 | "utils-merge": { 207 | "version": "1.0.0", 208 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", 209 | "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" 210 | }, 211 | "vary": { 212 | "version": "1.1.1", 213 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.1.tgz", 214 | "integrity": "sha1-Z1Neu2lMHVIldFeYRmUyP1h+jTc=" 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "javascript-ray-casting", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/aruvham/javascript-ray-casting.git" 8 | }, 9 | "author": "Arturo Ruvalcaba", 10 | "license": "ISC", 11 | "dependencies": { 12 | "express": "^4.15.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ray Casting 6 | 7 | 8 | 9 | 10 | 20 | 21 | -------------------------------------------------------------------------------- /public/sketch.js: -------------------------------------------------------------------------------- 1 | // Ray Casting 2 | // aruvham 3 | 4 | var worldMap = [[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], 5 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 6 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 7 | [1,0,0,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,0,0,1], 8 | [1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1], 9 | [1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1], 10 | [1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1], 11 | [1,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,1], 12 | [1,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,1], 13 | [1,1,1,1,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,1,1,1,1], 14 | [1,0,0,0,0,0,0,0,1,1,1,0,0,1,1,1,1,1,1,0,0,1,1,1,0,0,0,0,0,0,0,1], 15 | [1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1], 16 | [1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1], 17 | [1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1], 18 | [1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1], 19 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 20 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 21 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 22 | [1,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,1], 23 | [1,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,1], 24 | [1,0,0,1,1,1,0,0,0,1,1,1,0,1,1,1,1,1,1,0,1,1,1,0,0,0,1,1,1,0,0,1], 25 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 26 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 27 | [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]]; 28 | 29 | var mapWidth = worldMap[0].length, // in blocks 30 | mapHeight = worldMap.length; // in blocks 31 | 32 | var screenWidth = 320, // px 33 | screenHeight = 240;// px 34 | 35 | var FOV = 60, // degrees 36 | resolution = 2, // px 37 | numberOfRays = screenWidth / resolution; 38 | 39 | function setup() { 40 | createCanvas(640, 240); 41 | minimap = new Minimap(0, 0, 10); 42 | player = new Player(150, 170); 43 | } 44 | 45 | function draw() { 46 | noStroke(); 47 | background(255); 48 | fill(255); 49 | rect(320,0,320,240); 50 | fill(0); 51 | rect(320,0,320,120); 52 | fill(200); 53 | rect(320,120,320,120); 54 | 55 | minimap.draw(); 56 | castRays(); 57 | player.update(); 58 | player.draw(); 59 | } 60 | 61 | function castRays() { 62 | var angleStep = FOV / numberOfRays; // 60 / 80 = 0.75 63 | var radianStep = (angleStep / 180) * PI // 0.013 64 | var initialAngle = player.dir - (0.5 * FOV / 180) * PI; 65 | stroke(0); 66 | 67 | for(var i = 0; i < numberOfRays; i++) { 68 | 69 | // make sure angle is between 0 and 2PI rads 70 | var newAngle = initialAngle + (radianStep * i); 71 | if(newAngle < 0) newAngle = (2 * PI) + newAngle; 72 | var distance = castSingleRay(newAngle); 73 | stroke("#FFF1A7"); 74 | line(player.x, player.y, player.x + distance[0] * cos(newAngle), player.y + distance[0] * sin(newAngle)); 75 | 76 | // render 77 | var z = (distance[0]/10); 78 | var z = z * cos(player.dir - newAngle); 79 | var wallHeight = 240/z; 80 | if(wallHeight > 240) wallHeight = 240; 81 | 82 | fill("#01A1A1"); 83 | stroke("#01A1A1"); 84 | if(distance[1] == 0 || distance[1] == 1) { 85 | fill("#016666"); 86 | stroke("#016666"); 87 | } 88 | rect(320 + i * resolution, 120-(0.5 * wallHeight), resolution, wallHeight); 89 | } 90 | } 91 | 92 | function castSingleRay(angle) { 93 | // Moving right/left? up/down? Determined by 94 | // which quadrant the angle is in 95 | var right = angle > (3 * PI / 2) || (angle >= 0 && angle < PI / 2); 96 | var up = angle > PI && angle < 2 * PI; 97 | 98 | // HORIZONTAL WALL COLLISIONS 99 | var hCol = horizontalCollision(up, angle); 100 | var vCol = verticalCollision(right, angle); 101 | return (hCol[0] < vCol[0]) ? hCol : vCol; 102 | } 103 | 104 | function horizontalCollision(up, angle) { 105 | var x = floor(player.gridX); 106 | var y = up ? floor(player.gridY) : floor(player.gridY) + 1; 107 | var aY = y * minimap.scale; 108 | var aX = player.x - (player.y - aY) / tan(angle); 109 | var dX = minimap.scale / tan(angle); 110 | var dY = minimap.scale; 111 | var distance = [1000, 0]; // arbitrary large number 112 | var i = 0; 113 | 114 | // moving up 115 | if(up) { 116 | // while inside the map 117 | while((aY - i * dY) >= minimap.y) { 118 | // cordinate positions 119 | var posX = aX - i * dX; 120 | var posY = aY - i * dY; 121 | // grid positions 122 | var gX = floor(posX / minimap.scale); 123 | var gY = (posY/ minimap.scale) - 1; 124 | 125 | if((gX < mapWidth) && (gY >= 0) && worldMap[gY][gX] != 0) { 126 | distance = [dist(player.x, player.y, posX, posY), 0]; 127 | break; 128 | } 129 | i++; 130 | } 131 | // moving down 132 | } else { 133 | while((aY + i * dY) <= minimap.y + mapHeight * minimap.scale) { 134 | var posX = aX + i * dX; 135 | var posY = aY + i * dY; 136 | var gX = floor(posX / minimap.scale); 137 | var gY = (posY)/ minimap.scale; 138 | 139 | if((gX < mapWidth) && (gY >= 0) && worldMap[gY][gX] != 0) { 140 | distance = [dist(player.x, player.y, posX, posY), 1]; 141 | break; 142 | } 143 | i++; 144 | } 145 | } 146 | return distance; 147 | } 148 | 149 | function verticalCollision(right, angle) { 150 | var x = right ? floor(player.gridX) + 1: floor(player.gridX); 151 | var y = floor(player.gridY); 152 | var aX = x * minimap.scale; 153 | var aY = player.y + (aX - player.x) * tan(angle); 154 | var dY = minimap.scale * tan(angle); 155 | var dX = minimap.scale; 156 | 157 | var distance = [1000, 0]; // arbitrary large number 158 | var i = 0; 159 | fill(255, 0, 255); 160 | 161 | // moving right 162 | if(right) { 163 | // while inside the map 164 | while((aX + i * dX) <= minimap.x + mapWidth * minimap.scale) { 165 | // cordinate positions 166 | var posX = aX + i * dX; 167 | var posY = aY + i * dY; 168 | // grid positions 169 | var gX = posX / minimap.scale; 170 | var gY = floor(posY/ minimap.scale); 171 | 172 | if((gX < mapWidth) && (gY >= 0) && (gY < mapHeight) && worldMap[gY][gX] != 0) { 173 | 174 | distance = [dist(player.x, player.y, posX, posY), 2]; 175 | break; 176 | } 177 | i++; 178 | } 179 | } else { 180 | // while inside the map 181 | while((aX - i * dX) >= minimap.x) { 182 | // cordinate positions 183 | var posX = aX - i * dX; 184 | var posY = aY - i * dY; 185 | // grid positions 186 | var gX = (posX / minimap.scale) - 1; 187 | var gY = floor(posY/ minimap.scale); 188 | 189 | if((gX >= 0) && (gY >= 0) && (gY < mapHeight) && worldMap[gY][gX] != 0) { 190 | distance = [dist(player.x, player.y, posX, posY), 3]; 191 | break; 192 | } 193 | i++; 194 | } 195 | } 196 | return distance; 197 | } 198 | 199 | function Player(x, y) { 200 | this.x = x + minimap.x; 201 | this.y = y + minimap.y; 202 | this.gridX = this.x / minimap.scale; 203 | this.gridY = this.y / minimap.scale; 204 | this.dir = 0; // angle 205 | this.rot = 0; 206 | this.speed = 0; 207 | this.moveSpeed = 1; 208 | this.rotSpeed = 2.5 * PI/180; 209 | this.radius = 4; 210 | 211 | this.draw = function() { 212 | var squareSize = (2 * this.radius) * (2 * this.radius); 213 | squareSize /= 2; 214 | squareSize = sqrt(squareSize); 215 | fill(255, 0, 0); 216 | stroke(255, 0, 0); 217 | rectMode(CENTER); 218 | rect(this.x, this.y, squareSize, squareSize); 219 | rectMode(CORNER); 220 | } 221 | 222 | this.update = function() { 223 | // so player angle is always between 0 and 2PI rads 224 | this.dir += (this.rot > 0) ? this.rot * this.rotSpeed : 2 * PI + this.rot * this.rotSpeed; 225 | this.dir %= 2 * PI; 226 | newX = this.x + this.speed * this.moveSpeed * cos(this.dir); 227 | newY = this.y + this.speed * this.moveSpeed * sin(this.dir); 228 | 229 | if(!this.collision(newX, newY)) { 230 | this.x = newX; 231 | this.y = newY; 232 | } 233 | 234 | this.gridX = this.x / minimap.scale; 235 | this.gridY = this.y / minimap.scale; 236 | } 237 | 238 | this.collision = function(newX, newY) { 239 | if(newX - this.radius < minimap.x || 240 | newX + this.radius > minimap.x + mapWidth * minimap.scale || 241 | newY - this.radius < minimap.y || 242 | newY + this.radius > minimap.y + mapHeight * minimap.scale) { 243 | return true; 244 | } 245 | return worldMap[floor((newY - minimap.y) / minimap.scale)][floor((newX - minimap.x) / minimap.scale)] != 0; 246 | } 247 | } 248 | 249 | function Minimap(x, y, scale) { 250 | this.x = x; 251 | this.y = y; 252 | this.scale = scale; // in pixels 253 | 254 | this.draw = function() { 255 | fill(255); 256 | noStroke(); 257 | rect(this.x, this.y, mapWidth * this.scale, mapHeight * this.scale); 258 | 259 | fill(200); 260 | for(y = 0; y < mapHeight; y++) { 261 | for(x = 0; x < mapWidth; x++) { 262 | if(worldMap[y][x] != 0) { 263 | rect(this.x + x * this.scale, this.y + y * this.scale, this.scale, this.scale); 264 | } 265 | } 266 | } 267 | } 268 | 269 | this.drawGrid = function() { 270 | stroke(0); 271 | for(x = 0; x <= mapWidth; x++) { 272 | line(this.x + x * this.scale, this.y, this.x + x * this.scale, this.y + mapHeight * this.scale); 273 | } 274 | for(y = 0; y <= mapHeight; y++) { 275 | line(this.x, this.y + y * this.scale, this.x + mapWidth * this.scale, this.y + y * this.scale); 276 | } 277 | } 278 | } 279 | 280 | function keyPressed() { 281 | if(keyCode == UP_ARROW) player.speed = 1; 282 | if(keyCode == DOWN_ARROW) player.speed = -1; 283 | if(keyCode == LEFT_ARROW) player.rot = -1; 284 | if(keyCode == RIGHT_ARROW) player.rot = 1; 285 | } 286 | 287 | function keyReleased() { 288 | if(keyCode == UP_ARROW || keyCode == DOWN_ARROW) player.speed = 0; 289 | if(keyCode == LEFT_ARROW || keyCode == RIGHT_ARROW) player.rot = 0; 290 | } 291 | 292 | window.addEventListener("keydown", function(e) { 293 | if([32, 37, 38, 39, 40].indexOf(e.keyCode) > -1) { 294 | e.preventDefault(); 295 | } 296 | }, false); 297 | --------------------------------------------------------------------------------