├── .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 |