├── .gitignore ├── .template └── index.html ├── .vscode ├── launch.json └── tasks.json ├── README.md ├── build.hxml ├── docs ├── app.js ├── app.min.js ├── index.html └── s.png └── src ├── Controls.hx ├── DrawTools.hx ├── Game.hx ├── Main.hx ├── MyScene.hx ├── Save.hx ├── Scene.hx ├── Spr.hx ├── Util.hx ├── Vec.hx └── import.hx /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | .sketches -------------------------------------------------------------------------------- /.template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 30 | 31 | 32 |
33 | 34 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Haxe Interpreter", 6 | "type": "haxe-eval", 7 | "request": "launch" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "haxe", 6 | "args": "active configuration", 7 | "group": { 8 | "kind": "build", 9 | "isDefault": true 10 | } 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 01010111's Tiny Haxe Engine 2 | 3 | This is a tiny haxe engine for making tiny HTML5 games! It's loosely inspired by Pico 8, and aims to provide similar functionality! 4 | 5 | ## Cheat Sheet 6 | 7 | ### Scenes 8 | 9 | To start a new project, simply start coding in `MyScene`! A Scene must have two things to run properly, an `update()` function and a `draw()` function. These are called by the main game loop 60 times per second. If you want to load a new scene, use `Game.s = new MyNewScene()`. 10 | 11 | ### Drawing 12 | 13 | There are several built in functions for drawing: 14 | 15 | ```haxe 16 | // Clear the canvas 17 | clr(); 18 | 19 | // Draw a black stroked circle at x:32, y:64, with a radius of 8 pixels, and a line width of 4 20 | circ('black', 32, 64, 8, 4); 21 | // Draw a red filled circle at x:12, y:56, with a radius of 12 pixels 22 | fcirc('#FF004D', 12, 56, 12); 23 | 24 | // Draw a green stroked rectangle at x:8, y:16, with a width of 32, a height of 12, and a line width of 2 pixels 25 | rect('green', 8, 16, 32, 12, 2); 26 | // Draw a yellow filled rectangle at the same position of the same size 27 | frect('#FF0', 8, 16, 32, 12); 28 | 29 | // Draw a purple line from x:0, y:0 to x:100, y:100 with a default line width of 1 pixel 30 | line('purple', 0, 0, 100, 100); 31 | ``` 32 | 33 | ### Sprites 34 | 35 | To use sprites, prepare a spritesheet with the graphics to use in your game. It should be a grid of frames. Before using it, you must load it. 36 | 37 | ```haxe 38 | // Load an image to be used in game with an integer ID 39 | Spr.l('my-sprites.png', 0); 40 | 41 | // Draw a portion of the sprite (with an ID of 0) to screen at x:8, y:12, the portion is found at x:16, y:32 on the sprite and has a width of 24 and a height of 64 42 | spr(0, 8, 12, 16, 32, 24, 64); 43 | 44 | // Instead of referencing all those numbers you can store an atlas frame with a given ID (in this case we'll use 9), and the same arguments for offset and size 45 | Spr.a(9, 0, 16, 32, 24, 64); 46 | atl(9, 8, 12); 47 | ``` 48 | 49 | ### Controls 50 | 51 | You can use keyboard and mouse (or tap) to control your game! 52 | 53 | ```haxe 54 | // `Controls.p()` to see if a button is pressed, `Controls.jp()` to see if a button was just pressed. Just pass through the keycode of the key you want to check! 55 | console.log(Controls.p(32)); // will log whether the spacebar was pressed 56 | 57 | // It also tracks mouse clicks. You can refernce them like this: 58 | Controls.p(-1); // Left mouse button 59 | Controls.p(-2); // Middle mouse button 60 | Controls.p(-3); // Right mouse button 61 | 62 | // If you need the mouse position reference `Controls.M` 63 | console.log(Controls.M.x, Controls.M.y); // logs mouse x and y position 64 | ``` 65 | 66 | [keycode.info](https://keycode.info/) is my favorite place to quickly determine the keycode of a specific key! 67 | 68 | ### Building 69 | 70 | First, make sure haxe (at least version 4) is installed. You also need to install `uglifyjs`: 71 | ```haxelib install uglifyjs``` 72 | 73 | Run `haxe build.hxml` from the the project's root folder to generate several files: 74 | - `bin/app.js` - this is the unminified version of your game, you can ignore it! 75 | - `bin/app.min.js` - the minified tiny version of your game 76 | - `bin/index.html` copied from `.template/index.html` (if you want to edit it, edit the one in the `.template` folder!) 77 | 78 | Make sure to add your assets to the `bin` folder as well! 79 | 80 | Then you need to spin up a server and open `index.html` in your browser! 81 | 82 | ### Misc 83 | 84 | To resize your game, change the `Game.init()` call in `.template/index.html` - by default it sets the game to 128x128px. 85 | 86 | To resize the screen, you can mess with the CSS in `.template/index.html`. 87 | 88 | ## Notes 89 | 90 | Use these substitutes to save some space! 91 | 92 | - `Array.remove` - use `Array.splice` instead 93 | - `Map` - use an Int Map instead: `Map` 94 | 95 | ## Roadmap 96 | 97 | - [ ] Audio 98 | - [ ] Responsiveness 99 | - [x] Sprite features 100 | - [x] FlipX/Y 101 | - [x] Rotation? 102 | - [x] Vibration 103 | - [x] Saving/loading 104 | - [x] Use `requestAnimationFrame()` -------------------------------------------------------------------------------- /build.hxml: -------------------------------------------------------------------------------- 1 | # --- Features - Comment out to disable a feature --- 2 | 3 | -D use_mouse 4 | -D use_keyboard 5 | -D use_touch 6 | -D use_primatives 7 | -D use_sprites 8 | -D use_transform 9 | 10 | # --- Options --- 11 | # -D show_framerate 12 | # -D framework_only 13 | -dce full 14 | 15 | # --- Build --- 16 | 17 | Game 18 | -lib uglifyjs 19 | -cp src 20 | -D analyzer-optimize 21 | --js bin/app.js 22 | --cmd cp .template/index.html bin/ 23 | --cmd cp bin/* docs/ -------------------------------------------------------------------------------- /docs/app.js: -------------------------------------------------------------------------------- 1 | // Generated by Haxe 4.0.5 2 | (function ($hx_exports, $global) { "use strict"; 3 | var Controls = function() { }; 4 | Controls.init = function() { 5 | window.document.onkeydown = function(e) { 6 | Controls.k.h[e.keyCode] = true; 7 | return; 8 | }; 9 | window.document.onkeyup = function(e1) { 10 | Controls.k.h[e1.keyCode] = false; 11 | return; 12 | }; 13 | Game.ctx.canvas.onpointerdown = function(e2) { 14 | Controls.k.h[-1 - e2.button] = true; 15 | return; 16 | }; 17 | Game.ctx.canvas.onpointerup = function(e3) { 18 | Controls.k.h[-1 - e3.button] = false; 19 | return; 20 | }; 21 | Game.ctx.canvas.onpointermove = function(e4) { 22 | return Controls.M = { x : Math.floor(e4.offsetX / Game.zx), y : Math.floor(e4.offsetY / Game.zy)}; 23 | }; 24 | Game.ctx.canvas.ontouchstart = function(e5) { 25 | Controls.k.h[-1] = true; 26 | return; 27 | }; 28 | Game.ctx.canvas.ontouchend = function(e6) { 29 | Controls.k.h[-1] = false; 30 | return; 31 | }; 32 | Game.ctx.canvas.ontouchmove = function(e7) { 33 | return Controls.M = { x : Math.floor(e7.touches.item(0).clientX / Game.zx), y : Math.floor(e7.touches.item(0).clientY / Game.zy)}; 34 | }; 35 | }; 36 | Controls.p = function(n) { 37 | if(Controls.k.h.hasOwnProperty(n)) { 38 | return Controls.k.h[n]; 39 | } else { 40 | return false; 41 | } 42 | }; 43 | Controls.jp = function(n) { 44 | if(!Controls.k.h.hasOwnProperty(n)) { 45 | return false; 46 | } 47 | if(!Controls.l.h.hasOwnProperty(n)) { 48 | Controls.l.h[n] = Controls.k.h[n]; 49 | return Controls.k.h[n]; 50 | } 51 | var o = Controls.l.h[n] != Controls.k.h[n] && Controls.k.h[n]; 52 | Controls.l.h[n] = Controls.k.h[n]; 53 | return o; 54 | }; 55 | var DrawTools = function() { }; 56 | DrawTools.clr = function(col) { 57 | if(col != null) { 58 | DrawTools.frect(col,0,0,Game.w,Game.h); 59 | } else { 60 | Game.ctx.clearRect(0,0,Game.w,Game.h); 61 | } 62 | }; 63 | DrawTools.circ = function(col,x,y,r,lw) { 64 | DrawTools.ls(col,lw); 65 | Game.ctx.arc(x,y,r,0,2 * Math.PI); 66 | Game.ctx.stroke(); 67 | }; 68 | DrawTools.frect = function(col,x,y,w,h) { 69 | Game.ctx.fillStyle = col; 70 | Game.ctx.fillRect(x,y,w,h); 71 | }; 72 | DrawTools.ls = function(col,lw) { 73 | Game.ctx.lineWidth = lw != null ? lw : 1; 74 | Game.ctx.strokeStyle = col; 75 | Game.ctx.beginPath(); 76 | return Game.ctx; 77 | }; 78 | DrawTools.spr = function(x,y,sprite,options) { 79 | options = { a : options != null && options.a != null ? options.a : 1, sx : options != null && options.sx != null ? options.sx : 1, sy : options != null && options.sy != null ? options.sy : 1, r : options != null && options.r != null ? options.r : 1}; 80 | var i_hat = Vec.get(options.sx,0); 81 | var j_hat = Vec.get(0,options.sy); 82 | var offset = Vec.get(sprite.w / 2,sprite.h / 2); 83 | i_hat.set_a(i_hat.get_a() + options.r); 84 | j_hat.set_a(j_hat.get_a() + options.r); 85 | Game.ctx.save(); 86 | Game.ctx.transform(i_hat.x,j_hat.x,i_hat.y,j_hat.y,x,y); 87 | Game.ctx.transform(1,0,0,1,-offset.x,-offset.y); 88 | Game.ctx.globalAlpha = options.a; 89 | Game.ctx.drawImage(Spr.m.h[sprite.id],sprite.ox,sprite.oy,sprite.w,sprite.h,0,0,sprite.w,sprite.h); 90 | Game.ctx.restore(); 91 | i_hat.put(); 92 | j_hat.put(); 93 | offset.put(); 94 | }; 95 | DrawTools.atl = function(id,x,y,options) { 96 | DrawTools.spr(x,y,Spr.atl.h[id],options); 97 | }; 98 | var Game = $hx_exports["Game"] = function() { }; 99 | Game.init = function(p,_w,_h) { 100 | window.document.oncontextmenu = function(e) { 101 | return e.preventDefault(); 102 | }; 103 | var c = window.document.createElement("canvas"); 104 | var el = window.document.getElementById(p); 105 | el.appendChild(c); 106 | Game.w = c.width = _w; 107 | Game.h = c.height = _h; 108 | Game.ctx = c.getContext("2d",null); 109 | Controls.init(); 110 | window.onresize = function(e1) { 111 | Game.zx = el.offsetWidth / _w; 112 | return Game.zy = el.offsetHeight / _h; 113 | }; 114 | window.onresize(); 115 | window.requestAnimationFrame(Game.loop); 116 | Main.main(); 117 | }; 118 | Game.loop = function(e) { 119 | Game.t = e; 120 | Game.s.update(); 121 | Game.s.draw(); 122 | window.requestAnimationFrame(Game.loop); 123 | }; 124 | var Main = function() { }; 125 | Main.main = function() { 126 | Game.s = new MyScene(); 127 | }; 128 | var MyScene = function() { 129 | this.p = []; 130 | Spr.l("s.png",0); 131 | Spr.a(0,0,0,0,8,8); 132 | Spr.a(1,0,8,0,8,8); 133 | Spr.a(2,0,16,0,8,8); 134 | Spr.a(3,0,24,0,8,8); 135 | }; 136 | MyScene.prototype = { 137 | update: function() { 138 | var _g = 0; 139 | var _g1 = this.p; 140 | while(_g < _g1.length) { 141 | var o = _g1[_g]; 142 | ++_g; 143 | o.x += o.dx; 144 | o.y += o.dy; 145 | if(o.x + 10 > Game.w && o.dx > 0 || o.x < 0 && o.dx < 0) { 146 | o.dx *= -1; 147 | } 148 | if(o.y + 10 > Game.h && o.dy > 0 || o.y < 0 && o.dy < 0) { 149 | o.dy *= -1; 150 | } 151 | } 152 | if(Controls.jp(-1)) { 153 | this.p.push({ id : this.p.length % 4, x : Controls.M.x, y : Controls.M.y, dx : Math.random() - 0.5, dy : Math.random() - 0.5, s : 1, r : 0, a : Math.random()}); 154 | } 155 | if(Controls.p(-3)) { 156 | var _g2 = 0; 157 | var _g3 = this.p; 158 | while(_g2 < _g3.length) { 159 | var o1 = _g3[_g2]; 160 | ++_g2; 161 | if(Math.abs(o1.x - Controls.M.x) < 8 && Math.abs(o1.y - Controls.M.y) < 8) { 162 | this.p.splice(this.p.indexOf(o1),1); 163 | } 164 | } 165 | } 166 | if(Controls.p(88)) { 167 | var _g21 = 0; 168 | var _g31 = this.p; 169 | while(_g21 < _g31.length) { 170 | var o2 = _g31[_g21]; 171 | ++_g21; 172 | o2.dx *= 0.9; 173 | o2.dy *= 0.9; 174 | } 175 | } 176 | if(Controls.jp(67)) { 177 | var _g22 = 0; 178 | var _g32 = this.p; 179 | while(_g22 < _g32.length) { 180 | var o3 = _g32[_g22]; 181 | ++_g22; 182 | o3.dx = Math.random() - 0.5; 183 | o3.dy = Math.random() - 0.5; 184 | } 185 | } 186 | if(Controls.jp(83)) { 187 | Save.s("test",{ p : this.p}); 188 | } 189 | if(Controls.jp(76)) { 190 | var l = Save.l("test"); 191 | this.p = l == null ? [] : l.p; 192 | } 193 | } 194 | ,draw: function() { 195 | DrawTools.clr("black"); 196 | if(Controls.p(-3)) { 197 | DrawTools.circ("red",Controls.M.x,Controls.M.y,8,4); 198 | } 199 | var _g = 0; 200 | var _g1 = this.p; 201 | while(_g < _g1.length) { 202 | var o = _g1[_g]; 203 | ++_g; 204 | o.s += Math.sin(Game.t / 1000) / 10; 205 | o.r += 0.1; 206 | DrawTools.atl(o.id,Math.round(o.x),Math.round(o.y),{ a : o.a, sx : o.s, sy : o.s, r : o.r}); 207 | } 208 | } 209 | }; 210 | var Save = function() { }; 211 | Save.s = function(s,d) { 212 | if(js_Browser.getLocalStorage() == null) { 213 | return false; 214 | } 215 | js_Browser.getLocalStorage().setItem(s,JSON.stringify(d)); 216 | return true; 217 | }; 218 | Save.l = function(s) { 219 | if(js_Browser.getLocalStorage() == null) { 220 | return null; 221 | } 222 | return JSON.parse(js_Browser.getLocalStorage().getItem(s)); 223 | }; 224 | var Spr = function() { }; 225 | Spr.l = function(src,id) { 226 | var d = window.document.createElement("div"); 227 | d.style.display = "none"; 228 | window.document.body.appendChild(d); 229 | var s = window.document.createElement("img"); 230 | s.src = src; 231 | d.appendChild(s); 232 | Spr.m.h[id] = s; 233 | }; 234 | Spr.a = function(id,sid,ox,oy,w,h) { 235 | Spr.atl.h[id] = { id : sid, ox : ox, oy : oy, w : w, h : h}; 236 | }; 237 | var Vec = function() { 238 | }; 239 | Vec.get = function(x,y) { 240 | if(Vec.p.length > 0) { 241 | return Vec.p.shift().set(x,y); 242 | } 243 | return new Vec().set(x,y); 244 | }; 245 | Vec.prototype = { 246 | get_l: function() { 247 | return Math.sqrt(this.x * this.x + this.y * this.y); 248 | } 249 | ,get_a: function() { 250 | return Math.atan2(this.y,this.x); 251 | } 252 | ,set_a: function(n) { 253 | this.set(this.get_l() * Math.cos(n),this.get_l() * Math.sin(n)); 254 | return n; 255 | } 256 | ,set: function(x,y) { 257 | this.x = x; 258 | this.y = y; 259 | return this; 260 | } 261 | ,put: function() { 262 | Vec.p.push(this); 263 | } 264 | }; 265 | var haxe_ds_IntMap = function() { 266 | this.h = { }; 267 | }; 268 | var js_Browser = function() { }; 269 | js_Browser.getLocalStorage = function() { 270 | try { 271 | var s = window.localStorage; 272 | s.getItem(""); 273 | if(s.length == 0) { 274 | var key = "_hx_" + Math.random(); 275 | s.setItem(key,key); 276 | s.removeItem(key); 277 | } 278 | return s; 279 | } catch( e ) { 280 | return null; 281 | } 282 | }; 283 | Controls.M = { x : 0, y : 0}; 284 | Controls.k = new haxe_ds_IntMap(); 285 | Controls.l = new haxe_ds_IntMap(); 286 | Spr.m = new haxe_ds_IntMap(); 287 | Spr.atl = new haxe_ds_IntMap(); 288 | Vec.p = []; 289 | })(typeof exports != "undefined" ? exports : typeof window != "undefined" ? window : typeof self != "undefined" ? self : this, {}); 290 | -------------------------------------------------------------------------------- /docs/app.min.js: -------------------------------------------------------------------------------- 1 | !function(t){"use strict";function d(){}d.init=function(){window.document.onkeydown=function(t){d.k.h[t.keyCode]=!0},window.document.onkeyup=function(t){d.k.h[t.keyCode]=!1},p.ctx.canvas.onpointerdown=function(t){d.k.h[-1-t.button]=!0},p.ctx.canvas.onpointerup=function(t){d.k.h[-1-t.button]=!1},p.ctx.canvas.onpointermove=function(t){return d.M={x:Math.floor(t.offsetX/p.zx),y:Math.floor(t.offsetY/p.zy)}},p.ctx.canvas.ontouchstart=function(t){d.k.h[-1]=!0},p.ctx.canvas.ontouchend=function(t){d.k.h[-1]=!1},p.ctx.canvas.ontouchmove=function(t){return d.M={x:Math.floor(t.touches.item(0).clientX/p.zx),y:Math.floor(t.touches.item(0).clientY/p.zy)}}},d.p=function(t){return!!d.k.h.hasOwnProperty(t)&&d.k.h[t]},d.jp=function(t){if(!d.k.h.hasOwnProperty(t))return!1;if(!d.l.h.hasOwnProperty(t))return d.l.h[t]=d.k.h[t],d.k.h[t];var n=d.l.h[t]!=d.k.h[t]&&d.k.h[t];return d.l.h[t]=d.k.h[t],n};function r(){}r.clr=function(t){null!=t?r.frect(t,0,0,p.w,p.h):p.ctx.clearRect(0,0,p.w,p.h)},r.circ=function(t,n,e,o,i){r.ls(t,i),p.ctx.arc(n,e,o,0,2*Math.PI),p.ctx.stroke()},r.frect=function(t,n,e,o,i){p.ctx.fillStyle=t,p.ctx.fillRect(n,e,o,i)},r.ls=function(t,n){return p.ctx.lineWidth=null!=n?n:1,p.ctx.strokeStyle=t,p.ctx.beginPath(),p.ctx},r.spr=function(t,n,e,o){o={a:null!=o&&null!=o.a?o.a:1,sx:null!=o&&null!=o.sx?o.sx:1,sy:null!=o&&null!=o.sy?o.sy:1,r:null!=o&&null!=o.r?o.r:1};var i=u.get(o.sx,0),r=u.get(0,o.sy),a=u.get(e.w/2,e.h/2);i.set_a(i.get_a()+o.r),r.set_a(r.get_a()+o.r),p.ctx.save(),p.ctx.transform(i.x,r.x,i.y,r.y,t,n),p.ctx.transform(1,0,0,1,-a.x,-a.y),p.ctx.globalAlpha=o.a,p.ctx.drawImage(c.m.h[e.id],e.ox,e.oy,e.w,e.h,0,0,e.w,e.h),p.ctx.restore(),i.put(),r.put(),a.put()},r.atl=function(t,n,e,o){r.spr(n,e,c.atl.h[t],o)};var p=t.Game=function(){};p.init=function(t,n,e){window.document.oncontextmenu=function(t){return t.preventDefault()};var o=window.document.createElement("canvas"),i=window.document.getElementById(t);i.appendChild(o),p.w=o.width=n,p.h=o.height=e,p.ctx=o.getContext("2d",null),d.init(),window.onresize=function(t){return p.zx=i.offsetWidth/n,p.zy=i.offsetHeight/e},window.onresize(),window.requestAnimationFrame(p.loop),a.main()},p.loop=function(t){p.t=t,p.s.update(),p.s.draw(),window.requestAnimationFrame(p.loop)};var a=function(){};a.main=function(){p.s=new n};var n=function(){this.p=[],c.l("s.png",0),c.a(0,0,0,0,8,8),c.a(1,0,8,0,8,8),c.a(2,0,16,0,8,8),c.a(3,0,24,0,8,8)};n.prototype={update:function(){for(var t,n=0,e=this.p;np.w&&0p.h&&0 2 | 3 | 4 | 5 | 6 | 30 | 31 | 32 |
33 | 34 | -------------------------------------------------------------------------------- /docs/s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/01010111/tiny-haxe-engine/5353ae23085f42fc5e053ee83b46a65231c868fc/docs/s.png -------------------------------------------------------------------------------- /src/Controls.hx: -------------------------------------------------------------------------------- 1 | class Controls { 2 | 3 | // Mouse position 4 | public static var M = { x: 0, y: 0 } 5 | 6 | static var k:Map = []; 7 | static var l:Map = []; 8 | 9 | /** 10 | * Starts listeners for controls 11 | **/ 12 | public static function init() { 13 | #if use_keyboard 14 | document.onkeydown = (e) -> k.set(e.keyCode, true); 15 | document.onkeyup = (e) -> k.set(e.keyCode, false); 16 | #end 17 | #if use_mouse 18 | CTX.canvas.onpointerdown = (e) -> k.set(-1 - e.button, true); 19 | CTX.canvas.onpointerup = (e) -> k.set(-1 - e.button, false); 20 | CTX.canvas.onpointermove = (e) -> M = {x: (e.offsetX / Game.zx).floor(),y: (e.offsetY / Game.zy).floor()} 21 | #end 22 | #if use_touch 23 | CTX.canvas.ontouchstart = (e) -> k.set(-1, true); 24 | CTX.canvas.ontouchend = (e) -> k.set(-1, false); 25 | CTX.canvas.ontouchmove = (e) -> M = {x: (e.touches.item(0).clientX / Game.zx).floor(),y: (e.touches.item(0).clientY / Game.zy).floor()} 26 | #end 27 | } 28 | 29 | /** 30 | * Check the state of a button 31 | **/ 32 | public static function p(n:Int):Bool return k.exists(n) && k[n]; 33 | 34 | /** 35 | * Check to see if a button was just pressed 36 | **/ 37 | public static function jp(n:Int):Bool { 38 | if (!k.exists(n)) return false; 39 | if (!l.exists(n)) { 40 | l.set(n, k[n]); 41 | return k[n]; 42 | } 43 | var o = l[n] != k[n] && k[n]; 44 | l.set(n, k[n]); 45 | return o; 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /src/DrawTools.hx: -------------------------------------------------------------------------------- 1 | class DrawTools { 2 | 3 | /** 4 | * Clear the canvas 5 | **/ 6 | public static function clr(?col:String) { 7 | #if use_primatives 8 | col != null ? frect(col, 0, 0, Game.w, Game.h) : CTX.clearRect(0, 0, Game.w, Game.h); 9 | #else 10 | if (col == null) CTX.clearRect(0, 0, Game.w, Game.h); 11 | else { 12 | CTX.fillStyle = col; 13 | CTX.fillRect(0, 0, Game.w, Game.h); 14 | } 15 | #end 16 | } 17 | 18 | #if use_primatives 19 | /** 20 | * Draw a line with a given color, between points x1,y1 and x2,y2 with a given line weight 21 | **/ 22 | public static function line(col:String, x1:Float, y1:Float, x2:Float, y2:Float, ?lw:Float) { 23 | ls(col, lw); 24 | CTX.moveTo(x1, y1); 25 | CTX.lineTo(x2, y2); 26 | CTX.stroke(); 27 | } 28 | 29 | /** 30 | * Draw a filled circle with a given color, at coordinates x,y with a given radius 31 | **/ 32 | public static function fcirc(col:String, x:Float, y:Float, r:Float) { 33 | CTX.fillStyle = col; 34 | CTX.beginPath(); 35 | CTX.arc(x,y,r,0,2*PI); 36 | CTX.fill(); 37 | } 38 | 39 | /** 40 | * Draw a stroked circle with a given color, at coordinates x,y with a given radius and line weight 41 | **/ 42 | public static function circ(col:String, x:Float, y:Float, r:Float, ?lw:Float) { 43 | ls(col, lw); 44 | CTX.arc(x,y,r,0,2*PI); 45 | CTX.stroke(); 46 | } 47 | 48 | /** 49 | * Draw a filled rectangle with a given color, at coordinates x,y with a given width and height 50 | **/ 51 | public static function frect(col:String, x:Float, y:Float, w:Float, h:Float) { 52 | CTX.fillStyle = col; 53 | CTX.fillRect(x,y,w,h); 54 | } 55 | 56 | /** 57 | * Draw a stroked rectangle with a given color, at coordinates x,y with a given width, height, and line weight 58 | **/ 59 | public static function rect(col:String, x:Float, y:Float, w:Float, h:Float, ?lw:Float) { 60 | ls(col, lw); 61 | CTX.strokeRect(x,y,w,h); 62 | } 63 | 64 | static function ls(col:String, ?lw:Float) { 65 | CTX.lineWidth = lw != null ? lw : 1; 66 | CTX.strokeStyle = col; 67 | CTX.beginPath(); 68 | return CTX; 69 | } 70 | 71 | #end 72 | 73 | #if use_sprites 74 | 75 | #if use_transform 76 | /** 77 | * Draw a sprite at coordinates x,y and a given id, offset, size, and options 78 | * `ATTENTION`: make sure to use `Spr.l()` to load an image first! 79 | **/ 80 | public static function spr(x:Float, y:Float, sprite:{id:Int, ox:Float, oy:Float, w:Float, h:Float}, ?options:{?a:Float,?sx:Float,?sy:Float,?r:Float}) { 81 | options = { 82 | a: options != null && options.a != null ? options.a : 1, 83 | sx: options != null && options.sx != null ? options.sx : 1, 84 | sy: options != null && options.sy != null ? options.sy : 1, 85 | r: options != null && options.r != null ? options.r : 1 86 | } 87 | var i_hat = Vec.get(options.sx, 0); 88 | var j_hat = Vec.get(0, options.sy); 89 | var offset = Vec.get(sprite.w / 2, sprite.h / 2); 90 | i_hat.a += options.r; 91 | j_hat.a += options.r; 92 | CTX.save(); 93 | CTX.transform(i_hat.x, j_hat.x, i_hat.y, j_hat.y, x, y); 94 | CTX.transform(1,0,0,1,-offset.x,-offset.y); 95 | CTX.globalAlpha = options.a; 96 | CTX.drawImage(Spr.m[sprite.id], sprite.ox, sprite.oy, sprite.w, sprite.h, 0, 0, sprite.w, sprite.h); 97 | CTX.restore(); 98 | i_hat.put(); 99 | j_hat.put(); 100 | offset.put(); 101 | } 102 | 103 | /** 104 | * Draw an atlas frame with a given ID at coordinates x,y with options 105 | **/ 106 | public static function atl(id:Int, x:Float, y:Float, ?options:{?a:Float,?sx:Float,?sy:Float,?r:Float}) { 107 | spr(x,y,Spr.atl[id],options); 108 | } 109 | 110 | #else 111 | 112 | /** 113 | * Draw a sprite with a given ID, at coordinates x,y and a given offset and size 114 | * `ATTENTION`: make sure to use `Spr.l()` to load an image first! 115 | **/ 116 | public static function spr(id:Int, x:Float, y:Float, ox:Float, oy:Float, w:Float, h:Float) { 117 | CTX.drawImage(Spr.m[id], ox, oy, w, h, x, y, w, h); 118 | } 119 | 120 | /** 121 | * Draw an atlas frame with a given ID at coordinates x,y 122 | **/ 123 | public static function atl(id:Int, x:Float, y:Float) { 124 | var a = Spr.atl[id]; 125 | CTX.drawImage(Spr.m[a.id], a.ox, a.oy, a.w, a.h, x, y, a.w, a.h); 126 | } 127 | 128 | #end 129 | 130 | #end 131 | 132 | } -------------------------------------------------------------------------------- /src/Game.hx: -------------------------------------------------------------------------------- 1 | import js.html.DivElement; 2 | import js.html.CanvasRenderingContext2D; 3 | 4 | @:expose('Game') 5 | class Game { 6 | 7 | // context for drawing - reference `CTX` globally 8 | public static var ctx:CanvasRenderingContext2D; 9 | 10 | // width and height 11 | public static var w:Int; 12 | public static var h:Int; 13 | 14 | // zoom scalars for calculating mouse position 15 | public static var zx:Float; 16 | public static var zy:Float; 17 | 18 | // current scene 19 | public static var s:Scene; 20 | 21 | // elapsed time 22 | public static var t:Float; 23 | 24 | // Framerate vars 25 | #if show_framerate 26 | static var last_time:Float = 0; 27 | static var fr_el:DivElement; 28 | #end 29 | 30 | /** 31 | * Starts the engine. Pass parent element's id, and desired width and height 32 | **/ 33 | static function init(p:String,_w:Int,_h:Int) { 34 | // Create container for framerate 35 | #if show_framerate 36 | fr_el = document.createDivElement(); 37 | fr_el.classList.add('fr'); 38 | document.body.appendChild(fr_el); 39 | #end 40 | 41 | // Prevent right clicks so we can use them in a game 42 | document.oncontextmenu = (e) -> e.preventDefault(); 43 | 44 | // Create a canvas element and append it to the desired parent element 45 | var c = document.createCanvasElement(); 46 | var el = document.getElementById(p); 47 | el.appendChild(c); 48 | 49 | // Set width and height 50 | w = c.width = _w; 51 | h = c.height = _h; 52 | 53 | // Set the main canvas context 54 | ctx = c.getContext2d(); 55 | 56 | // Initialize controls 57 | Controls.init(); 58 | 59 | // Reset zoom variables on resize 60 | window.onresize = (e) -> { 61 | zx = el.offsetWidth/_w; 62 | zy = el.offsetHeight/_h; 63 | } 64 | window.onresize(); 65 | 66 | // Start game loop 67 | window.requestAnimationFrame(loop); 68 | 69 | // Start game 70 | Main.main(); 71 | } 72 | 73 | // Our game loop 74 | static function loop(e:Float) { 75 | t = e; 76 | s.update(); 77 | s.draw(); 78 | #if show_framerate 79 | var fr = '${(10000/(e - last_time)).round()/10}'; 80 | if (fr.length < 4) fr += '.0'; 81 | fr_el.innerText = '$fr fps'; 82 | last_time = e; 83 | #end 84 | window.requestAnimationFrame(loop); 85 | } 86 | 87 | } -------------------------------------------------------------------------------- /src/Main.hx: -------------------------------------------------------------------------------- 1 | class Main { 2 | 3 | public static function main() { 4 | #if framework_only Game.s = cast {update:()->{},draw:()->{}} #else 5 | // This is where your scene is initially loaded! 6 | Game.s = new MyScene(); 7 | #end 8 | } 9 | 10 | } -------------------------------------------------------------------------------- /src/MyScene.hx: -------------------------------------------------------------------------------- 1 | class MyScene implements Scene { 2 | 3 | public function new() { 4 | 5 | } 6 | 7 | public function update() { 8 | 9 | } 10 | 11 | public function draw() { 12 | 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /src/Save.hx: -------------------------------------------------------------------------------- 1 | import js.Browser; 2 | 3 | class Save { 4 | 5 | /** 6 | * Save an object to local storage with a given string, returns false if saving is impossible 7 | **/ 8 | public static function s(s:String, d:Dynamic):Bool { 9 | if (Browser.getLocalStorage() == null) return false; 10 | Browser.getLocalStorage().setItem(s,d.stringify()); 11 | return true; 12 | } 13 | 14 | /** 15 | * Load an object to local storage, returns null if nothing is found 16 | **/ 17 | public static function l(s:String):Dynamic { 18 | if (Browser.getLocalStorage() == null) return null; 19 | return Browser.getLocalStorage().getItem(s).parse(); 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /src/Scene.hx: -------------------------------------------------------------------------------- 1 | interface Scene { 2 | public function update():Void; 3 | public function draw():Void; 4 | } -------------------------------------------------------------------------------- /src/Spr.hx: -------------------------------------------------------------------------------- 1 | import js.html.ImageElement; 2 | 3 | class Spr { 4 | 5 | // map of available sprites 6 | public static var m:Map = []; 7 | 8 | // map of atlases 9 | public static var atl:Map = []; 10 | 11 | /** 12 | * Load a sprite from a given source. The sprite's ID will be the filename without an extention: 13 | * ie: a src of `assets/my_img.png` will produce this ID: `my_img` 14 | **/ 15 | public static function l(src:String, id:Int) { 16 | var d = document.createDivElement(); 17 | d.style.display = 'none'; 18 | document.body.appendChild(d); 19 | var s = document.createImageElement(); 20 | s.src = src; 21 | d.appendChild(s); 22 | m.set(id, s); 23 | } 24 | 25 | /** 26 | * Register an atlas frame with a given ID, and referencing the source image ID, and describing the frame's offset and size 27 | **/ 28 | public static function a(id:Int, sid:Int, ox:Float, oy:Float, w:Float, h:Float) atl.set(id, { id: sid, ox: ox, oy: oy, w: w, h: h }); 29 | 30 | } -------------------------------------------------------------------------------- /src/Util.hx: -------------------------------------------------------------------------------- 1 | class Util { 2 | 3 | public static function bz(n:Int) window.navigator.vibrate(n); 4 | 5 | } -------------------------------------------------------------------------------- /src/Vec.hx: -------------------------------------------------------------------------------- 1 | class Vec { 2 | static var p:Array = []; 3 | public static function get(x,y) { 4 | if (p.length > 0) return p.shift().set(x,y); 5 | return new Vec().set(x,y); 6 | } 7 | public var x:Float; 8 | public var y:Float; 9 | public var l(get,set):Float; 10 | public var a(get,set):Float; 11 | function get_l():Float { 12 | return (x*x + y*y).sqrt(); 13 | } 14 | function set_l(n:Float):Float { 15 | normalize(); 16 | set(x*n,y*n); 17 | return n; 18 | } 19 | function get_a():Float return Math.atan2(y,x); 20 | function set_a(n:Float):Float { 21 | set(l*n.cos(), l*n.sin()); 22 | return n; 23 | } 24 | function new(){} 25 | public function set(x,y) { 26 | this.x = x; 27 | this.y = y; 28 | return this; 29 | } 30 | public function put() p.push(this); 31 | public function normalize() set(x/l,y/l); 32 | } -------------------------------------------------------------------------------- /src/import.hx: -------------------------------------------------------------------------------- 1 | import js.Browser.document as document; 2 | import js.Browser.window as window; 3 | import js.Browser.console as console; 4 | import Game.ctx as CTX; 5 | import Math.PI as PI; 6 | import DrawTools.*; 7 | 8 | using Math; 9 | using haxe.Json; --------------------------------------------------------------------------------