├── logo.png ├── src ├── import.hx ├── elke │ ├── process │ │ ├── Command.hx │ │ ├── Process.hx │ │ └── Timeout.hx │ ├── entity │ │ ├── BaseEntity.hx │ │ ├── Entity2D.hx │ │ ├── Entities.hx │ │ └── Entity3D.hx │ ├── utils │ │ ├── ArrayTools.hx │ │ ├── GoldenRatioSampler.hx │ │ ├── EasedFloat.hx │ │ ├── MathTools.hx │ │ ├── AABB.hx │ │ ├── SpatialBuckets.hx │ │ ├── CollisionHandler.hx │ │ ├── Joystick.hx │ │ └── CellGraph.hx │ ├── pathfinding │ │ ├── PathPool.hx │ │ ├── PathNode.hx │ │ ├── Path.hx │ │ └── PathFinder.hx │ ├── graphics │ │ ├── SineDeformShader.hx │ │ ├── WobbleShader.hx │ │ ├── AsepriteResource.hx │ │ ├── Sprite.hx │ │ ├── TextureAtlas.hx │ │ ├── Plane3D.hx │ │ ├── SpriteGroupSprite.hx │ │ ├── MultiTileGroup.hx │ │ ├── SpriteGroup.hx │ │ ├── Animation.hx │ │ ├── Sprite3D.hx │ │ └── Transition.hx │ ├── buildutil │ │ ├── Config.hx │ │ ├── WebGenerator.hx │ │ └── AsepriteConverter.hx │ ├── gamestate │ │ ├── GameState.hx │ │ └── GameStateHandler.hx │ ├── Version.hx │ ├── math │ │ ├── IntersectionTests.hx │ │ └── ReliableChance.hx │ ├── things │ │ ├── TimerText.hx │ │ ├── TouchJoystick.hx │ │ └── Newgrounds.hx │ ├── sound │ │ └── Sounds.hx │ ├── input │ │ └── GamePadHandler.hx │ ├── T.hx │ ├── res │ │ └── TileSheetRes.hx │ └── Game.hx ├── Data.hx ├── entities │ └── ExampleEntity.hx ├── Main.hx ├── Const.hx ├── GameSaveData.hx └── gamestates │ └── PlayState.hx ├── res ├── props.json ├── img │ ├── boom.png │ ├── test.png │ ├── boom.aseprite │ ├── test.aseprite │ ├── test.tilesheet │ └── boom.tilesheet ├── fonts │ ├── small.png │ ├── daisyhud.png │ ├── debuff_0.png │ ├── gridgazer.png │ ├── marumonica.png │ ├── Static-Regular.ttf │ ├── m5x7_medium_12.png │ ├── static-regular.png │ ├── futilepro_medium_12.png │ ├── minecraftiaOutline.png │ ├── equipmentpro_medium_12.png │ ├── charset.txt │ ├── m5x7_medium_12.fnt │ ├── equipmentpro_medium_12.fnt │ ├── futilepro_medium_12.fnt │ ├── minecraftiaOutline.fnt │ ├── small.fnt │ └── debuff.fnt ├── sound │ └── click.wav └── data.cdb ├── .gitignore ├── generate_assets.hxml ├── common.hxml ├── .vscode ├── launch.json ├── tasks.json └── commandbar.json ├── templates ├── manifest.json ├── sw.js └── index.html ├── config.hxml ├── LICENSE └── README.md /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jefvel/game-base/HEAD/logo.png -------------------------------------------------------------------------------- /src/import.hx: -------------------------------------------------------------------------------- 1 | using elke.utils.ArrayTools; 2 | using elke.utils.MathTools; -------------------------------------------------------------------------------- /res/props.json: -------------------------------------------------------------------------------- 1 | { 2 | "fs.convert" : { 3 | "wav" : "ogg" 4 | } 5 | } -------------------------------------------------------------------------------- /res/img/boom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jefvel/game-base/HEAD/res/img/boom.png -------------------------------------------------------------------------------- /res/img/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jefvel/game-base/HEAD/res/img/test.png -------------------------------------------------------------------------------- /res/fonts/small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jefvel/game-base/HEAD/res/fonts/small.png -------------------------------------------------------------------------------- /res/sound/click.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jefvel/game-base/HEAD/res/sound/click.wav -------------------------------------------------------------------------------- /res/fonts/daisyhud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jefvel/game-base/HEAD/res/fonts/daisyhud.png -------------------------------------------------------------------------------- /res/fonts/debuff_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jefvel/game-base/HEAD/res/fonts/debuff_0.png -------------------------------------------------------------------------------- /res/fonts/gridgazer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jefvel/game-base/HEAD/res/fonts/gridgazer.png -------------------------------------------------------------------------------- /res/img/boom.aseprite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jefvel/game-base/HEAD/res/img/boom.aseprite -------------------------------------------------------------------------------- /res/img/test.aseprite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jefvel/game-base/HEAD/res/img/test.aseprite -------------------------------------------------------------------------------- /res/fonts/marumonica.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jefvel/game-base/HEAD/res/fonts/marumonica.png -------------------------------------------------------------------------------- /src/elke/process/Command.hx: -------------------------------------------------------------------------------- 1 | package elke.process; 2 | 3 | typedef Command = Void -> Void; 4 | 5 | -------------------------------------------------------------------------------- /res/fonts/Static-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jefvel/game-base/HEAD/res/fonts/Static-Regular.ttf -------------------------------------------------------------------------------- /res/fonts/m5x7_medium_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jefvel/game-base/HEAD/res/fonts/m5x7_medium_12.png -------------------------------------------------------------------------------- /res/fonts/static-regular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jefvel/game-base/HEAD/res/fonts/static-regular.png -------------------------------------------------------------------------------- /res/fonts/futilepro_medium_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jefvel/game-base/HEAD/res/fonts/futilepro_medium_12.png -------------------------------------------------------------------------------- /res/fonts/minecraftiaOutline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jefvel/game-base/HEAD/res/fonts/minecraftiaOutline.png -------------------------------------------------------------------------------- /src/Data.hx: -------------------------------------------------------------------------------- 1 | package; 2 | 3 | private typedef Init = haxe.macro.MacroType<[cdb.Module.build("../res/data.cdb")]>; 4 | -------------------------------------------------------------------------------- /res/fonts/equipmentpro_medium_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jefvel/game-base/HEAD/res/fonts/equipmentpro_medium_12.png -------------------------------------------------------------------------------- /src/elke/entity/BaseEntity.hx: -------------------------------------------------------------------------------- 1 | package elke.entity; 2 | 3 | interface BaseEntity { 4 | public var id:Int; 5 | public function update(dt:Float):Void; 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | game.js 3 | game.js.map 4 | game.hl 5 | *~ 6 | 7 | bin 8 | build 9 | redist 10 | publish.sh 11 | .tmp 12 | *.asd 13 | 14 | *.DS_Store 15 | .DS_Store 16 | 17 | *.wav.asd -------------------------------------------------------------------------------- /src/elke/utils/ArrayTools.hx: -------------------------------------------------------------------------------- 1 | package elke.utils; 2 | 3 | class ArrayTools { 4 | static public function randomElement(a:Array):T { 5 | if (a.length == 0) { 6 | return null; 7 | } 8 | return a[Std.int(Math.random() * a.length)]; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/elke/pathfinding/PathPool.hx: -------------------------------------------------------------------------------- 1 | package elke.pathfinding; 2 | 3 | import pool.Pool; 4 | 5 | @:build(pool.macro.PoolBuilder.build("elke.pathfinding", false)) 6 | class PathPool extends Pool 7 | { 8 | public function new() 9 | { 10 | 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /generate_assets.hxml: -------------------------------------------------------------------------------- 1 | 2 | -cp src 3 | 4 | # This will look for .aseprite files, and then export them, 5 | # either as single pngs if they contain one frame, or a png + tilesheet 6 | # when there are more than one frame. 7 | --macro elke.buildutil.AsepriteConverter.exportTileSheets() -------------------------------------------------------------------------------- /common.hxml: -------------------------------------------------------------------------------- 1 | --macro elke.buildutil.Config.initConfig() 2 | 3 | -cp src 4 | 5 | -lib heaps 6 | 7 | -lib deepnightLibs 8 | -lib ldtk-haxe-api 9 | -lib hscript 10 | -lib castle 11 | -lib stb_ogg_sound 12 | -lib newgrounds 13 | 14 | # -lib hide 15 | # -lib hxbit 16 | 17 | -main Main 18 | 19 | config.hxml 20 | -------------------------------------------------------------------------------- /res/data.cdb: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "name": "texts", 5 | "columns": [ 6 | { 7 | "typeStr": "0", 8 | "name": "id" 9 | }, 10 | { 11 | "typeStr": "1", 12 | "name": "text", 13 | "kind": "localizable" 14 | } 15 | ], 16 | "lines": [], 17 | "separators": [], 18 | "props": {} 19 | } 20 | ], 21 | "customTypes": [], 22 | "compress": false 23 | } -------------------------------------------------------------------------------- /src/elke/utils/GoldenRatioSampler.hx: -------------------------------------------------------------------------------- 1 | package elke.utils; 2 | 3 | /** 4 | * a sampler for getting unique samples uniformly between 0-1 5 | * gotten from https://blog.bruce-hill.com/6-useful-snippets#Golden-Ratio-Sampling 6 | */ 7 | class GoldenRatioSampler { 8 | var i = 0; 9 | final goldenRatio = (Math.sqrt(5) + 1) / 2.; 10 | public function new() { 11 | } 12 | public function nextSample() { 13 | i ++; 14 | return (i * goldenRatio) % 1; 15 | } 16 | } -------------------------------------------------------------------------------- /src/entities/ExampleEntity.hx: -------------------------------------------------------------------------------- 1 | package entities; 2 | 3 | import elke.entity.Entity2D; 4 | 5 | class ExampleEntity extends Entity2D { 6 | var sprite:elke.graphics.Sprite; 7 | 8 | public function new(?parent) { 9 | super(parent); 10 | 11 | sprite = hxd.Res.img.boom_tilesheet.toSprite2D(this); 12 | sprite.originX = 16; 13 | sprite.originY = 16; 14 | // sprite.scaleX = -1; 15 | sprite.animation.play("Explosion", true, false, Math.random()); 16 | } 17 | } -------------------------------------------------------------------------------- /res/fonts/charset.txt: -------------------------------------------------------------------------------- 1 | !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňʼnŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀƁƂƃƄƅƆƇƈƉƊƋƌƍƎƏƐƑƒƓƔƕƖƗƘƙƚƛƜƝƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀǁǂǃDŽDždžLJLjljNJNjnjǍǎǏǐǑǒǓǔǕǖǗǘǙǚǛǜǝǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰǴǵǶǷǸǹǺǻǼǽǾǿ -------------------------------------------------------------------------------- /src/elke/process/Process.hx: -------------------------------------------------------------------------------- 1 | package elke.process; 2 | 3 | class Process { 4 | public function update(dt:Float) { 5 | if (updateFn != null) { 6 | updateFn(dt); 7 | } 8 | } 9 | 10 | public function onStart() {} 11 | 12 | public function onFinish() {} 13 | 14 | public var updateFn:Float->Void; 15 | 16 | public function new(onUpdate:Float->Void = null) { 17 | this.updateFn = onUpdate; 18 | Game.instance.addProcess(this); 19 | } 20 | 21 | public function remove() { 22 | Game.instance.removeProcess(this); 23 | } 24 | } -------------------------------------------------------------------------------- /src/elke/process/Timeout.hx: -------------------------------------------------------------------------------- 1 | package elke.process; 2 | 3 | class Timeout extends Process { 4 | public var duration:Float = 0.; 5 | 6 | var elapsed:Float = 0.; 7 | 8 | var onRun:Void->Void; 9 | 10 | public function new(time:Float = 0., run:Void->Void) { 11 | duration = time; 12 | this.onRun = run; 13 | super(); 14 | } 15 | 16 | override function update(dt:Float) { 17 | elapsed += dt; 18 | if (elapsed >= duration) { 19 | onRun(); 20 | this.remove(); 21 | } 22 | } 23 | 24 | public function reset() { 25 | elapsed = 0.0; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Main.hx: -------------------------------------------------------------------------------- 1 | package; 2 | 3 | import gamestates.PlayState; 4 | import elke.Game; 5 | 6 | class Main { 7 | static var game:Game; 8 | 9 | static function main() { 10 | var colorString = haxe.macro.Compiler.getDefine("backgroundColor"); 11 | colorString = StringTools.replace(colorString, "#", "0x"); 12 | var color = Std.parseInt(colorString); 13 | game = new Game({ 14 | initialState: new PlayState(), 15 | onInit: () -> {}, 16 | tickRate: Const.TICK_RATE, 17 | pixelSize: Const.PIXEL_SIZE, 18 | backgroundColor: color, 19 | }); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/elke/graphics/SineDeformShader.hx: -------------------------------------------------------------------------------- 1 | package elke.graphics; 2 | 3 | class SineDeformShader extends hxsl.Shader { 4 | static var SRC = { 5 | @:import h3d.shader.Base2d; 6 | 7 | @param var texture : Sampler2D; 8 | @param var speed : Float; 9 | @param var frequency : Float; 10 | @param var amplitude : Float; 11 | 12 | function fragment() { 13 | calculatedUV.y += sin(calculatedUV.y * frequency + time * speed) * amplitude; // wave deform 14 | pixelColor = texture.get(calculatedUV); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/elke/buildutil/Config.hx: -------------------------------------------------------------------------------- 1 | package elke.buildutil; 2 | 3 | class Config { 4 | static function initConfig() { 5 | hxd.res.Config.ignoredExtensions["ase"] = true; 6 | hxd.res.Config.ignoredExtensions["blend"] = true; 7 | hxd.res.Config.ignoredExtensions["blend1"] = true; 8 | hxd.res.Config.ignoredExtensions["aseprite"] = true; 9 | hxd.res.Config.ignoredExtensions["wav.asd"] = true; 10 | 11 | // Files with the extension .tilesheet will be able to 12 | // be loaded using the TileSheetRes class. 13 | hxd.res.Config.extensions["tilesheet"] = "elke.res.TileSheetRes"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/elke/entity/Entity2D.hx: -------------------------------------------------------------------------------- 1 | package elke.entity; 2 | 3 | import elke.entity.BaseEntity; 4 | 5 | class Entity2D implements BaseEntity extends h2d.Object { 6 | public var id:Int; 7 | 8 | public function new(?parent) { 9 | id = Entities._NEXT_ID++; 10 | super(parent); 11 | } 12 | 13 | public function update(dt:Float) {} 14 | 15 | override function onAdd() { 16 | super.onAdd(); 17 | @:privateAccess 18 | Entities.getInstance().add(this); 19 | } 20 | 21 | override function onRemove() { 22 | super.onRemove(); 23 | @:privateAccess 24 | Entities.getInstance().remove(this); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/elke/entity/Entities.hx: -------------------------------------------------------------------------------- 1 | package elke.entity; 2 | 3 | class Entities { 4 | @:allow(elke.entity.Entity3D, elke.entity.Entity2D) 5 | static var _NEXT_ID = 0; 6 | static var instance:Entities; 7 | 8 | public static inline function getInstance() { 9 | return instance; 10 | } 11 | 12 | var entities:Array; 13 | 14 | public function new() { 15 | instance = this; 16 | entities = []; 17 | } 18 | 19 | function add(e:BaseEntity) { 20 | entities.push(e); 21 | } 22 | 23 | function remove(e:BaseEntity) { 24 | entities.remove(e); 25 | } 26 | 27 | public function update(dt:Float) { 28 | for (e in entities) { 29 | e.update(dt); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/elke/pathfinding/PathNode.hx: -------------------------------------------------------------------------------- 1 | package elke.pathfinding; 2 | 3 | import pool.Poolable; 4 | import elke.utils.CellGraph.GraphNode; 5 | 6 | class PathNode implements Poolable { 7 | public var parent: PathNode = null; 8 | public var val: GraphNode = null; 9 | public var G: Float = 0.; 10 | public var H: Float = 0.; 11 | public var F(get, null): Float; 12 | 13 | public var closed = false; 14 | public var index = 0; 15 | public static var nex = 0; 16 | public var pathLength = 0; 17 | 18 | public function new(p: PathNode, v: GraphNode, g: Float, h:Float) { 19 | parent = p; 20 | val = v; 21 | 22 | G = g; 23 | H = h; 24 | index = nex++; 25 | } 26 | 27 | function get_F() { 28 | return G + H; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Const.hx: -------------------------------------------------------------------------------- 1 | import hxd.impl.UInt16; 2 | 3 | class Const { 4 | // Pixel scaling for the 2d scene 5 | public static inline final PIXEL_SIZE = 2; 6 | 7 | // Pixels per unit, in 3d space 8 | public static inline final PIXEL_SIZE_WORLD = 32; 9 | public static inline final PPU = 1.0 / PIXEL_SIZE_WORLD; 10 | 11 | public static inline final TICK_RATE = 60; 12 | 13 | // change this to 14 | public static inline final SAVE_NAMESPACE = haxe.macro.Compiler.getDefine("saveNamespace"); 15 | // Newgrounds stuff 16 | public static inline final NEWGROUNDS_APP_ID = haxe.macro.Compiler.getDefine("newgroundsAppId"); 17 | public static inline final NEWGROUNDS_ENCRYPTION_KEY_RC4 = haxe.macro.Compiler.getDefine("newgroundsEncryptionKey"); 18 | } -------------------------------------------------------------------------------- /src/elke/graphics/WobbleShader.hx: -------------------------------------------------------------------------------- 1 | package elke.graphics; 2 | 3 | class WobbleShader extends hxsl.Shader { 4 | static var SRC = { 5 | @:import h3d.shader.Base2d; 6 | 7 | @param var texture : Sampler2D; 8 | @param var speed : Float; 9 | @param var frequency : Float; 10 | @param var amplitude : Float; 11 | @param var offset : Float; 12 | 13 | function fragment() { 14 | calculatedUV.x += sin(calculatedUV.y * frequency + time * speed + offset) * amplitude; // wave deform 15 | if (calculatedUV.x < 0 || calculatedUV.x > 1) discard; 16 | calculatedUV.y += cos(calculatedUV.y * frequency + time * speed + offset) * amplitude; // wave deform 17 | pixelColor = texture.get(calculatedUV); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/elke/gamestate/GameState.hx: -------------------------------------------------------------------------------- 1 | package elke.gamestate; 2 | 3 | class GameState { 4 | @:allow(elke.gamestate.GameStateHandler) 5 | var game:elke.Game; 6 | 7 | public var name:String; 8 | 9 | public function onEvent(e:hxd.Event):Void {} 10 | 11 | public function onPause() {} 12 | public function onUnpause() {} 13 | 14 | public function onEnter():Void {} 15 | 16 | public function onLeave():Void {} 17 | 18 | /** 19 | * tick runs at a fixed timestep 20 | * @param dt 21 | */ 22 | public function tick(dt:Float):Void {} 23 | 24 | /** 25 | * update runs every frame, at a variable dt 26 | * @param dt 27 | * @param timeUntilTick time until current tick ends 28 | */ 29 | public function update(dt:Float, timeUntilTick: Float):Void {} 30 | 31 | public function onRender(e:h3d.Engine):Void {} 32 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "2.0.0", 6 | "configurations": [ 7 | { 8 | "name": "Chrome WebGL", 9 | "type": "chrome", 10 | "request": "launch", 11 | "url": "${workspaceFolder}/build/web/index.html", 12 | "webRoot": "${workspaceFolder}/build/web", 13 | "preLaunchTask": "HeapsJS" 14 | }, 15 | { 16 | "name": "HashLink(SDL)", 17 | "request": "launch", 18 | "type": "hl", 19 | "cwd": "${workspaceRoot}", 20 | "preLaunchTask": "HeapsHL.SDL" 21 | }, 22 | { 23 | "name": "HashLink(DirectX)", 24 | "request": "launch", 25 | "type": "hl", 26 | "cwd": "${workspaceRoot}", 27 | "preLaunchTask": "HeapsHL.DX" 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /templates/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "::windowTitle::", 3 | "short_name": "::windowTitle::", 4 | "description": "::ogDescription::", 5 | "display": "fullscreen", 6 | "orientation": "landscape", 7 | "start_url": "index.html", 8 | "background_color": "::backgroundColor::", 9 | "icons": [ 10 | { 11 | "src": "icon-192x192.png", 12 | "sizes": "192x192", 13 | "type": "image/png" 14 | }, 15 | { 16 | "src": "icon-256x256.png", 17 | "sizes": "256x256", 18 | "type": "image/png" 19 | }, 20 | { 21 | "src": "icon-384x384.png", 22 | "sizes": "384x384", 23 | "type": "image/png" 24 | }, 25 | { 26 | "src": "icon-512x512.png", 27 | "sizes": "512x512", 28 | "type": "image/png" 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /src/elke/Version.hx: -------------------------------------------------------------------------------- 1 | package elke; 2 | 3 | macro function GetBuildID():haxe.macro.Expr.ExprOf { 4 | #if !display 5 | var process = new sys.io.Process('git', ['rev-parse', 'HEAD']); 6 | if (process.exitCode() != 0) { 7 | var message = process.stderr.readAll().toString(); 8 | var pos = haxe.macro.Context.currentPos(); 9 | haxe.macro.Context.error("Cannot execute `git rev-parse HEAD`. " + message, pos); 10 | } 11 | 12 | // read the output of the process 13 | var commitHash:String = process.stdout.readLine(); 14 | 15 | var buildTime = Date.now().toString().substr(0, 10); 16 | 17 | var str = '$buildTime - ${commitHash.substr(0, 8)}'; 18 | 19 | // Generates a string expression 20 | return macro $v{str}; 21 | #else 22 | // `#if display` is used for code completion. In this case returning an 23 | // empty string is good enough; We don't want to call git on every hint. 24 | var commitHash:String = ""; 25 | return macro $v{commitHash.substr(0, 8)}; 26 | #end 27 | } 28 | -------------------------------------------------------------------------------- /src/elke/math/IntersectionTests.hx: -------------------------------------------------------------------------------- 1 | package elke.math; 2 | 3 | /** 4 | * Ray circle intersection 5 | * @param x ray start x 6 | * @param y ray start y 7 | * @param toX ray end x 8 | * @param toY ray end y 9 | * @param cx circle x 10 | * @param cy circle y 11 | * @param r radius 12 | * @return Float, -1 if no hit, 0-1 if hit 13 | */ 14 | function rayCircleIntersection(x: Float, y: Float, toX: Float, toY: Float, cx: Float, cy: Float, r: Float): Float { 15 | var dx = x - toX; 16 | var dy = y - toY; 17 | 18 | var fx = toX - cx; 19 | var fy = toY - cy; 20 | 21 | var a = dx * dx + dy * dy; 22 | var b = 2 * (fx * dx + fy * dy); 23 | var c = (fx * fx + fy * fy) - r * r; 24 | 25 | var discriminant = b * b - 4 * a * c; 26 | if (discriminant < 0) { 27 | return -1.; 28 | } 29 | 30 | discriminant = Math.sqrt( discriminant ); 31 | 32 | var t2 = (-b + discriminant)/(2*a); 33 | t2 = 1 - t2; 34 | if (t2 >= 0 && t2 <= 1.0) { 35 | return t2; 36 | } 37 | 38 | return -1.; 39 | } 40 | -------------------------------------------------------------------------------- /src/elke/things/TimerText.hx: -------------------------------------------------------------------------------- 1 | package elke.things; 2 | 3 | import h2d.RenderContext; 4 | import h2d.Text; 5 | 6 | class TimerText extends Text { 7 | /** 8 | * @time in seconds 9 | */ 10 | public var time: Float = 0.; 11 | public function new(font, ?p) { 12 | super(font, p); 13 | dropShadow = { 14 | dx: 1, 15 | dy: 1, 16 | color: 0x111111, 17 | alpha: 0.4, 18 | }; 19 | } 20 | 21 | override function sync(ctx:RenderContext) { 22 | super.sync(ctx); 23 | var minutes = Math.floor(time / 60); 24 | var seconds = time - minutes * 60; 25 | var extraZero = minutes < 10 ? '0' : ''; 26 | var extraSecondZero = seconds < 10 ? '0' : ''; 27 | var hundredsSplit = '${seconds}'.split('.'); 28 | var hundreds = "000"; 29 | if (hundredsSplit.length > 1) { 30 | hundreds = '${hundredsSplit[1].substr(0, 3)}'; 31 | while(hundreds.length < 3){ 32 | hundreds = "0" + hundreds; 33 | } 34 | } 35 | 36 | text = '$extraZero$minutes:$extraSecondZero${Math.floor(seconds)}:$hundreds'; 37 | } 38 | } -------------------------------------------------------------------------------- /src/elke/math/ReliableChance.hx: -------------------------------------------------------------------------------- 1 | package elke.math; 2 | 3 | /** 4 | * A more reliable percentage chance 5 | * it feels more fair than just trying for Math.random, 6 | * it guarantees to succeed at least the amount of times for the percentage assigned. 7 | * example: percentage = 0.01, guarantees a success every 100 times 8 | */ 9 | class ReliableChance { 10 | public var percentage(default, set) = 0.; 11 | var tries = 0; 12 | var successEvery = 0; 13 | 14 | public function new(percentage = 0.) { 15 | this.percentage = percentage; 16 | } 17 | 18 | public function tryRoll() { 19 | if (percentage == 0 || successEvery == 0) { 20 | return false; 21 | } 22 | 23 | tries ++; 24 | 25 | if (tries >= successEvery) { 26 | tries = 0; 27 | return true; 28 | } 29 | 30 | return Math.random() < percentage; 31 | } 32 | 33 | function set_percentage(p: Float) { 34 | if (p == 0) { 35 | successEvery = 0; 36 | } else { 37 | successEvery = Std.int(1. / p); 38 | } 39 | 40 | return this.percentage = p; 41 | } 42 | } -------------------------------------------------------------------------------- /config.hxml: -------------------------------------------------------------------------------- 1 | ################## 2 | # Configuration 3 | ### 4 | 5 | ## Window 6 | -D windowTitle=Game 7 | -D windowSize=1280x720 8 | # Sets the default color of the scene + html background 9 | -D backgroundColor=#202030 10 | 11 | # Uncomment if you want a static sized window size 12 | #-D windowFixed 13 | 14 | ## Saves 15 | # Change this to a unique identifier for your game 16 | -D saveNamespace=game-base 17 | 18 | -D enableGamepads 19 | 20 | ## HTML/Js Specific values 21 | # Twitter card 22 | -D twitterSite=https://yourwebsite.com 23 | -D twitterCreator=@yourusername 24 | # OpenGraph 25 | -D ogUrl=https://yourwebsite.com 26 | -D ogDescription=Your game Description 27 | -D ogImage=https://link.to.your.img/photo.png 28 | 29 | ## Newgrounds stuff 30 | -D newgroundsAppId=fill in your newgrounds app ID here 31 | # RC4 Encryption key required 32 | -D newgroundsEncryptionKey=fill in your newgrounds encryption key here 33 | 34 | ## Etc 35 | # Uncomment and set if your Aseprite installation is not in the default path 36 | #-D asepritePath=path to your aseprite.exe/executable 37 | -------------------------------------------------------------------------------- /src/elke/utils/EasedFloat.hx: -------------------------------------------------------------------------------- 1 | package elke.utils; 2 | 3 | class EasedFloat { 4 | public var value(get, set): Float; 5 | 6 | public var easeTime = 0.3; 7 | public var easeFunction: (Float) -> Float = T.expoOut; 8 | 9 | var target = 0.; 10 | var from = 0.; 11 | 12 | var changeTime = 0.; 13 | 14 | var internalValue = 0.; 15 | 16 | public function new(initial: Float = 0., easeTime = 0.3) { 17 | this.easeTime = easeTime; 18 | setImmediate(initial); 19 | } 20 | 21 | public function setImmediate(value: Float) { 22 | target = from = value; 23 | changeTime = easeTime; 24 | } 25 | 26 | function set_value(v) { 27 | if (v == target) { 28 | return v; 29 | } 30 | 31 | from = target; 32 | target = v; 33 | 34 | changeTime = hxd.Timer.lastTimeStamp; 35 | 36 | return v; 37 | } 38 | 39 | function get_value() { 40 | var t = hxd.Timer.lastTimeStamp - changeTime; 41 | 42 | if (t > easeTime) { 43 | return target; 44 | } 45 | 46 | var r = Math.min(1, t / easeTime); 47 | var p = easeFunction != null ? easeFunction(r) : r; 48 | return from + p * (target - from); 49 | } 50 | } -------------------------------------------------------------------------------- /src/elke/pathfinding/Path.hx: -------------------------------------------------------------------------------- 1 | package elke.pathfinding; 2 | 3 | import haxe.ds.Vector; 4 | import pool.Poolable; 5 | import h2d.col.Point; 6 | 7 | 8 | class Path implements Poolable { 9 | final maxLength = 128; 10 | public var points : Vector; 11 | public var index: Int; 12 | public var length = 0; 13 | 14 | var _nextEmptyIndex = 0; 15 | 16 | @:allow(pool.Pool) 17 | @:allow(elke.pathfinding.PathPool) 18 | private function new() { 19 | this.index = 0; 20 | this.points = new Vector(maxLength); 21 | } 22 | 23 | public function push(x: Float, y: Float) { 24 | if (points[_nextEmptyIndex] == null) { 25 | points[_nextEmptyIndex] = new Point(); 26 | } 27 | 28 | points[_nextEmptyIndex].set(x, y); 29 | _nextEmptyIndex ++; 30 | } 31 | 32 | public function pop() { 33 | if (_nextEmptyIndex <= 0) { 34 | return null; 35 | } 36 | 37 | _nextEmptyIndex --; 38 | if (index >= _nextEmptyIndex) { 39 | index = _nextEmptyIndex - 1; 40 | } 41 | 42 | return points[_nextEmptyIndex]; 43 | } 44 | 45 | public function reset() { 46 | length = 0; 47 | index = 0; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Aksel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/GameSaveData.hx: -------------------------------------------------------------------------------- 1 | import hxd.Save; 2 | 3 | /** 4 | * you can fill this with whatever data needs saving, 5 | * then you can just use GameSaveData.getCurrent() to get an instance of save data 6 | * and save it using save() 7 | */ 8 | class GameSaveData { 9 | 10 | public function new() {} 11 | 12 | #if debug 13 | static inline final hash = false; 14 | static inline final saveName = '${Const.SAVE_NAMESPACE}_debug'; 15 | #else 16 | static inline final hash = true; 17 | static inline final saveName = Const.SAVE_NAMESPACE; 18 | #end 19 | 20 | public function save() { 21 | try { 22 | Save.save(current, saveName, hash); 23 | } catch (e) { 24 | trace(e); 25 | } 26 | } 27 | 28 | public static function load() { 29 | current = Save.load(null, saveName, hash); 30 | 31 | if (current == null) { 32 | current = new GameSaveData(); 33 | } 34 | } 35 | 36 | static var current: GameSaveData; 37 | public static function reset() { 38 | current = null; 39 | return getCurrent(); 40 | } 41 | 42 | public static function getCurrent() { 43 | if (current == null) { 44 | load(); 45 | } 46 | 47 | return current; 48 | } 49 | } -------------------------------------------------------------------------------- /src/elke/utils/MathTools.hx: -------------------------------------------------------------------------------- 1 | package elke.utils; 2 | 3 | class MathTools { 4 | static public function clamp(a:Float, min: Float, max: Float):Float { 5 | if (a < min) { 6 | return min; 7 | } 8 | if (a > max) { 9 | return max; 10 | } 11 | return a; 12 | } 13 | 14 | static public function angleBetween(radian: Float, toRadian: Float): Float { 15 | var diff = ( toRadian - radian + Math.PI ) % (Math.PI * 2) - Math.PI; 16 | diff = diff < -Math.PI ? diff + Math.PI * 2 : diff; 17 | diff *= 0.5; 18 | return diff; 19 | } 20 | 21 | public static function toFixed(number:Float, ?precision=2): Float { 22 | number *= Math.pow(10, precision); 23 | return Math.round(number) / Math.pow(10, precision); 24 | } 25 | 26 | static function formatMoneyString(s : String) { 27 | var r = ~/(\d)(?=(\d{3})+(?!\d))/g; 28 | return r.replace(s, "$1 "); 29 | } 30 | 31 | static public function toMoneyString(a: Int) : String { 32 | return formatMoneyString('$a'); 33 | } 34 | 35 | static public function toMoneyStringFloat(x: Float): String { 36 | var parts = '${x.toFixed()}'.split("."); 37 | parts[0] = formatMoneyString(parts[0]); 38 | if (parts.length == 1) parts.push('00'); 39 | return parts.join("."); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/elke/graphics/AsepriteResource.hx: -------------------------------------------------------------------------------- 1 | package elke.graphics; 2 | 3 | typedef AseFrameData = { 4 | x:Int, 5 | y:Int, 6 | w:Int, 7 | h:Int, 8 | duration:Int 9 | } 10 | 11 | typedef AseFrame = { 12 | frame : AseFrameData, 13 | spriteSourceSize : AseFrameData, 14 | sourceSize : AseSize, 15 | duration : Int 16 | } 17 | 18 | typedef AseSize = { 19 | w:Int, 20 | h:Int 21 | } 22 | 23 | typedef AseBound = { 24 | w: Int, 25 | h: Int, 26 | x: Int, 27 | y: Int, 28 | } 29 | 30 | typedef AseAnimation = { 31 | name:String, 32 | from:Int, 33 | to:Int, 34 | totalLength:Int, 35 | looping:Bool, 36 | linearSpeed:Bool, 37 | frameDuration:Int 38 | } 39 | 40 | typedef AseLayer = { 41 | name : String, 42 | opacity : Int, 43 | group : String, 44 | blendMode : String, 45 | } 46 | 47 | typedef AseSlice = { 48 | name: String, 49 | color: String, 50 | keys: Array, 51 | } 52 | 53 | typedef AseSliceKey = { 54 | frame: Int, 55 | bounds: AseBound, 56 | } 57 | 58 | typedef AseMeta = { 59 | frameTags : Array, 60 | size : AseSize, 61 | scale : Float, 62 | layers : Array, 63 | slices: Array, 64 | } 65 | 66 | typedef AseFile = { 67 | frames : Array, 68 | meta : AseMeta 69 | } 70 | -------------------------------------------------------------------------------- /src/elke/entity/Entity3D.hx: -------------------------------------------------------------------------------- 1 | package elke.entity; 2 | 3 | import h3d.scene.Object; 4 | import h3d.Vector; 5 | 6 | class Entity3D implements BaseEntity extends h3d.scene.Object { 7 | public var id:Int; 8 | 9 | public var maxSpeed = 0.0; 10 | 11 | public var vx = 0.0; 12 | public var vy = 0.0; 13 | public var vz = 0.0; 14 | 15 | public var friction = 0.98; 16 | public var gravitation = -0.06; 17 | 18 | public function new(?parent) { 19 | id = Entities._NEXT_ID++; 20 | super(parent); 21 | } 22 | 23 | override function onAdd() { 24 | super.onAdd(); 25 | @:privateAccess 26 | Entities.getInstance().add(this); 27 | } 28 | 29 | override function onRemove() { 30 | super.onRemove(); 31 | @:privateAccess 32 | Entities.getInstance().remove(this); 33 | } 34 | 35 | public function update(dt:Float) { 36 | // Clamp max speed 37 | var v = new Vector(vx, vy, vz); 38 | if (v.lengthSq() > this.maxSpeed * this.maxSpeed) { 39 | v.normalize(); 40 | v.scale3(this.maxSpeed); 41 | this.vx = v.x; 42 | this.vy = v.y; 43 | this.vz = v.z; 44 | } 45 | 46 | this.x += vx; 47 | this.y += vy; 48 | this.z += vz; 49 | 50 | this.vx *= friction; 51 | this.vy *= friction; 52 | this.vz *= friction; 53 | 54 | vz += gravitation; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /templates/sw.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2015, 2019 Google Inc. All Rights Reserved. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | // Incrementing OFFLINE_VERSION will kick off the install event and force 15 | // previously cached resources to be updated from the network. 16 | const OFFLINE_VERSION = 1; 17 | const CACHE_NAME = 'offline'; 18 | 19 | self.addEventListener('install', (event) => { 20 | event.waitUntil((async () => { 21 | const cache = await caches.open(CACHE_NAME); 22 | await cache.addAll([ 23 | 'index.html', 24 | 'game.js', 25 | ]); 26 | })()); 27 | }); 28 | 29 | self.addEventListener('fetch', (e) => { 30 | e.respondWith( 31 | caches.match(e.request).then((response) => response || fetch(e.request)), 32 | ); 33 | }); -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "haxe", 8 | "args": "active configuration", 9 | "group": { 10 | "kind": "build", 11 | "isDefault": true 12 | } 13 | }, 14 | { 15 | "label": "Generate Assets", 16 | "type": "hxml", 17 | "file": "generate_assets.hxml" 18 | }, 19 | { 20 | "label": "HeapsJS", 21 | "type": "hxml", 22 | "file": "build_js.hxml" 23 | }, 24 | { 25 | "label": "HeapsJS.Release", 26 | "type": "hxml", 27 | "file": "build_js_release.hxml" 28 | }, 29 | { 30 | "label": "HeapsHL.SDL", 31 | "type": "hxml", 32 | "file": "build_sdl.hxml", 33 | "presentation": { 34 | "reveal": "never", 35 | "panel": "dedicated", 36 | "clear": true 37 | }, 38 | "problemMatcher": [ 39 | "$haxe-absolute", 40 | "$haxe", 41 | "$haxe-error", 42 | "$haxe-trace" 43 | ], 44 | "group": { 45 | "kind": "build", 46 | "isDefault": true 47 | } 48 | }, 49 | { 50 | "label": "HeapsHL.DX", 51 | "type": "hxml", 52 | "file": "build_dx.hxml" 53 | }, 54 | ] 55 | } -------------------------------------------------------------------------------- /src/elke/gamestate/GameStateHandler.hx: -------------------------------------------------------------------------------- 1 | package elke.gamestate; 2 | 3 | import h3d.Engine; 4 | import elke.Game; 5 | 6 | class GameStateHandler { 7 | static var instance:GameStateHandler; 8 | 9 | public static inline function getInstance() { 10 | return instance; 11 | } 12 | 13 | var currentState:GameState; 14 | 15 | var game:Game; 16 | 17 | public function new(g:Game) { 18 | instance = this; 19 | game = g; 20 | } 21 | 22 | public function update(dt: Float, timeUntilTick: Float) { 23 | if (currentState != null) { 24 | currentState.update(dt, timeUntilTick); 25 | } 26 | } 27 | 28 | public function tick(dt:Float) { 29 | if (currentState != null) { 30 | currentState.tick(dt); 31 | } 32 | } 33 | 34 | public function onEvent(e:hxd.Event) { 35 | if (currentState != null) { 36 | currentState.onEvent(e); 37 | } 38 | } 39 | 40 | public function onPause() { 41 | if (currentState != null) { 42 | currentState.onPause(); 43 | } 44 | } 45 | 46 | public function onUnpause() { 47 | if (currentState != null) { 48 | currentState.onUnpause(); 49 | } 50 | } 51 | 52 | public function onRender(e: Engine) { 53 | if (currentState != null) { 54 | currentState.onRender(e); 55 | } 56 | } 57 | 58 | public function setState(s:GameState) { 59 | if (currentState != null) { 60 | currentState.onLeave(); 61 | } 62 | 63 | s.game = game; 64 | 65 | currentState = s; 66 | s.onEnter(); 67 | } 68 | } -------------------------------------------------------------------------------- /src/gamestates/PlayState.hx: -------------------------------------------------------------------------------- 1 | package gamestates; 2 | 3 | import entities.ExampleEntity; 4 | import h2d.Object; 5 | import elke.graphics.Transition; 6 | import h2d.Text; 7 | import hxd.Event; 8 | 9 | class PlayState extends elke.gamestate.GameState { 10 | var coolEntity:Array; 11 | var container:Object; 12 | 13 | public function new() {} 14 | 15 | var e:ExampleEntity; 16 | 17 | var text:Text; 18 | 19 | var uiContainer:Object; 20 | 21 | override function onEnter() { 22 | super.onEnter(); 23 | container = new Object(game.s2d); 24 | uiContainer = new Object(game.s2d); 25 | 26 | var fontSize = 128; 27 | var t = new Text(hxd.Res.fonts.futilepro_medium_12.toFont(), uiContainer); 28 | t.textColor = 0xFFFFFF; 29 | t.text = "Hello World"; 30 | t.textAlign = Center; 31 | game.s2d.filter.useScreenResolution = true; 32 | 33 | var o = new ExampleEntity(game.s2d); 34 | o.x = o.y = 100; 35 | 36 | text = t; 37 | } 38 | 39 | override function onEvent(e:Event) { 40 | if (e.kind == EPush) { 41 | Transition.to(() -> {}, 0.8, 0.8); 42 | } 43 | } 44 | 45 | var time = 0.0; 46 | 47 | override function tick(dt:Float) { 48 | time += dt; 49 | 50 | var s = text.getScene(); 51 | text.x = s.width >> 1; 52 | text.y = s.height * 0.5 + Math.sin(time) * 15 - text.textHeight; 53 | } 54 | 55 | override function onLeave() { 56 | super.onLeave(); 57 | container.remove(); 58 | uiContainer.remove(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![turbo logo](https://raw.githubusercontent.com/jefvel/game-base/master/logo.png) 2 | 3 | A very barebones [Heaps](https://heaps.io) game template, a bit inspired by [Deepnight's gamebase](https://github.com/deepnight/gameBase) (Use that one if you wanna make games, it's much more polished than this!) 4 | 5 | and [heeps](https://github.com/Yanrishatum/heeps) (based my 3D sprite implementation on the one that exists here). 6 | 7 | Some current features: 8 | 9 | * Possibility to set 2D pixel size for the screen. 10 | 11 | * Separate UI scene for high 12 | 13 | * Simple entity and gamestate system with fixed timestep updates. 14 | 15 | * A HTML template file that gets exported along with the js output. 16 | 17 | * The release JS build uses the Google closure js minifier. 18 | 19 | * Automatic aseprite file tilesheet generation. (Generates a png plus a .tilesheet file) 20 | 21 | * The .tilesheet file can be accessed using the resource system: 22 | 23 | ```haxe 24 | hxd.Res.img.testcharacter_tilesheet.toSprite2D(); // Creates a h2d.Bitmap type object with animation support 25 | hxd.Res.img.testcharacter_tilesheet.toSprite3D(); // Creates a 3D billboard type mesh for h3d. 26 | ``` 27 | 28 | *Comes with fonts created by [somepx](https://twitter.com/somepx)* 29 | 30 | ## Notes 31 | 32 | ### Generating MSDF fonts 33 | 34 | I use the npm package `msdf-bmfont`, install it globally, and then run 35 | 36 | `msdf-bmfont --font-size 48 -b 0 -r 4 --smart-size --pot --smart-size FONT-NAME.ttf` 37 | -------------------------------------------------------------------------------- /res/img/test.tilesheet: -------------------------------------------------------------------------------- 1 | { "frames": [ 2 | { 3 | "filename": "test 0.aseprite", 4 | "frame": { "x": 22, "y": 26, "w": 22, "h": 26 }, 5 | "rotated": false, 6 | "trimmed": true, 7 | "spriteSourceSize": { "x": 4, "y": 1, "w": 22, "h": 26 }, 8 | "sourceSize": { "w": 32, "h": 32 }, 9 | "duration": 200 10 | }, 11 | { 12 | "filename": "test 1.aseprite", 13 | "frame": { "x": 0, "y": 33, "w": 21, "h": 27 }, 14 | "rotated": false, 15 | "trimmed": true, 16 | "spriteSourceSize": { "x": 8, "y": 2, "w": 21, "h": 27 }, 17 | "sourceSize": { "w": 32, "h": 32 }, 18 | "duration": 200 19 | }, 20 | { 21 | "filename": "test 2.aseprite", 22 | "frame": { "x": 0, "y": 0, "w": 21, "h": 32 }, 23 | "rotated": false, 24 | "trimmed": true, 25 | "spriteSourceSize": { "x": 3, "y": 0, "w": 21, "h": 32 }, 26 | "sourceSize": { "w": 32, "h": 32 }, 27 | "duration": 200 28 | }, 29 | { 30 | "filename": "test 3.aseprite", 31 | "frame": { "x": 22, "y": 0, "w": 24, "h": 25 }, 32 | "rotated": false, 33 | "trimmed": true, 34 | "spriteSourceSize": { "x": 3, "y": 3, "w": 24, "h": 25 }, 35 | "sourceSize": { "w": 32, "h": 32 }, 36 | "duration": 100 37 | } 38 | ], 39 | "meta": { 40 | "app": "http://www.aseprite.org/", 41 | "version": "1.2.29", 42 | "image": "test.png", 43 | "format": "RGBA8888", 44 | "size": { "w": 46, "h": 60 }, 45 | "scale": "1", 46 | "frameTags": [ 47 | { "name": "Ok", "from": 0, "to": 2, "direction": "forward" }, 48 | { "name": "Smiley", "from": 3, "to": 3, "direction": "forward" } 49 | ], 50 | "slices": [ 51 | ] 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/elke/graphics/Sprite.hx: -------------------------------------------------------------------------------- 1 | package elke.graphics; 2 | 3 | @:access(h2d.Tile) 4 | class Sprite extends h2d.Bitmap { 5 | public var animation:Animation; 6 | 7 | var dirty = false; 8 | 9 | /** 10 | * Horizontal origin of sprite, in pixels. 11 | * 0 = left, 1 = right 12 | */ 13 | public var originX(default, set):Int = 0; 14 | 15 | /** 16 | * Vertical origin of sprite, in pixels. 17 | * 0 = top, 1 = bottom 18 | */ 19 | public var originY(default, set):Int = 0; 20 | 21 | var tiles:Array; 22 | 23 | public function new(anim, ?parent) { 24 | super(null, parent); 25 | animation = anim; 26 | } 27 | 28 | var lastTile:h2d.Tile; 29 | 30 | function refreshTile() { 31 | var frame = animation.getCurrentFrame(); 32 | var t = frame.tile; 33 | 34 | if (!dirty && t == lastTile) { 35 | return; 36 | } 37 | 38 | dirty = false; 39 | lastTile = t; 40 | 41 | this.tile = t; 42 | if (frame != null) { 43 | t.dx = frame.offsetX - originX; 44 | t.dy = frame.offsetY - originY; 45 | } 46 | } 47 | 48 | override function sync(ctx:h2d.RenderContext) { 49 | animation.update(ctx.elapsedTime); 50 | 51 | if (this.parent == null) { 52 | return; 53 | } 54 | 55 | refreshTile(); 56 | super.sync(ctx); 57 | } 58 | 59 | inline function set_originX(o:Int) { 60 | if (o != originX) 61 | dirty = true; 62 | return originX = o; 63 | } 64 | 65 | inline function set_originY(o:Int) { 66 | if (o != originY) 67 | dirty = true; 68 | return originY = o; 69 | } 70 | 71 | public inline function syncWith(parent:Sprite) { 72 | originX = parent.originX; 73 | originY = parent.originY; 74 | 75 | if (animation.currentFrame != parent.animation.currentFrame) { 76 | dirty = true; 77 | } 78 | 79 | animation.currentFrame = parent.animation.currentFrame; 80 | } 81 | } -------------------------------------------------------------------------------- /src/elke/graphics/TextureAtlas.hx: -------------------------------------------------------------------------------- 1 | package elke.graphics; 2 | 3 | import h2d.Drawable; 4 | import h2d.Object; 5 | import h2d.Bitmap; 6 | import h3d.mat.Texture; 7 | import h2d.RenderContext; 8 | import h2d.TileGroup; 9 | import h2d.Tile; 10 | 11 | class TextureAtlas { 12 | 13 | final maxWidth = 1024 << 1; 14 | final maxHeight = 1024 << 1; 15 | 16 | var atlas: Texture; 17 | public var atlasTile: Tile; 18 | 19 | var namedTiles: Map = new Map(); 20 | 21 | var tileList: Array = []; 22 | 23 | public function new() { 24 | resetTexture(); 25 | } 26 | 27 | function resetTexture() { 28 | if (atlas != null) { 29 | atlas.dispose(); 30 | atlas = null; 31 | } 32 | 33 | atlas = new Texture(maxWidth, maxHeight, [Target], RGBA); 34 | atlas.clear(0x000000, 0.0); 35 | atlasTile = Tile.fromTexture(atlas); 36 | } 37 | 38 | var cx = 0; 39 | var cy = 0; 40 | var rowHeight = 0; 41 | 42 | public function addObject(o: Object) { 43 | var b = o.getBounds(); 44 | if (cx + b.width > maxWidth) { 45 | cx = 0; 46 | cy += rowHeight; 47 | rowHeight = 0; 48 | } 49 | 50 | if (b.height > rowHeight) rowHeight = Std.int(b.height); 51 | 52 | o.x = cx; 53 | o.y = cy; 54 | o.drawTo(atlas); 55 | 56 | var res = atlasTile.sub(cx, cy, b.width, b.height); 57 | 58 | cx += Std.int(b.width); 59 | 60 | return res; 61 | } 62 | 63 | /** 64 | * skips to the next row in the packing 65 | */ 66 | public function nextRow() { 67 | cx = 0; 68 | cy += rowHeight; 69 | rowHeight = 0; 70 | } 71 | 72 | public function getNamedTile(name) { 73 | return namedTiles[name]; 74 | } 75 | 76 | public function addNamedTile(tile:Tile, name){ 77 | if (namedTiles.exists(name)) { 78 | return namedTiles[name]; 79 | } 80 | 81 | var t = addObject(new Bitmap(tile)); 82 | namedTiles[name] = t; 83 | 84 | return t; 85 | } 86 | } -------------------------------------------------------------------------------- /src/elke/sound/Sounds.hx: -------------------------------------------------------------------------------- 1 | package elke.sound; 2 | 3 | import hxd.snd.SoundGroup; 4 | import hxd.snd.Channel; 5 | import hxd.res.Sound; 6 | import hxd.snd.ChannelGroup; 7 | 8 | class Sounds { 9 | public var sfxChannel:ChannelGroup; 10 | public var musicChannel:ChannelGroup; 11 | 12 | public var sfxVolume(get, set):Float; 13 | public var musicVolume(get, set):Float; 14 | 15 | var currentMusic:Channel; 16 | 17 | function get_sfxVolume() 18 | return sfxChannel.volume; 19 | 20 | function set_sfxVolume(volume:Float) 21 | return sfxChannel.volume = volume; 22 | 23 | function get_musicVolume() 24 | return musicChannel.volume; 25 | 26 | function set_musicVolume(volume:Float) 27 | return musicChannel.volume = volume; 28 | 29 | public function new() { 30 | sfxChannel = new ChannelGroup("sfx"); 31 | musicChannel = new ChannelGroup("music"); 32 | } 33 | 34 | public function playSfx(snd:Sound, volume = 0.5, loop = false) { 35 | return snd.play(loop, volume, sfxChannel); 36 | } 37 | 38 | public function playMusic(music:Sound, volume = .5, loop = true) { 39 | currentMusic = music.play(loop, volume, musicChannel); 40 | return currentMusic; 41 | } 42 | 43 | public function stopMusic(fadeoutTime = 0.0) { 44 | if (currentMusic != null) { 45 | if (fadeoutTime > 0) { 46 | currentMusic.fadeTo(0, fadeoutTime, () -> currentMusic.stop()); 47 | } else { 48 | currentMusic.stop(); 49 | } 50 | currentMusic = null; 51 | } 52 | } 53 | 54 | /** 55 | * plays wobbly sound effect with random pitch 56 | * @param snd 57 | * @param volume = 0.3 58 | */ 59 | public function playWobble(snd:hxd.res.Sound, volume = 0.3, wobbleAmount = 0.1, loop = false) { 60 | var sound = snd.play(loop, volume, sfxChannel); 61 | sound.addEffect(new hxd.snd.effect.Pitch(1 - wobbleAmount + Math.random() * (wobbleAmount * 2))); 62 | return sound; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/elke/utils/AABB.hx: -------------------------------------------------------------------------------- 1 | package elke.utils; 2 | 3 | class AABB { 4 | public var x(default, set) = 0.; 5 | public var y(default, set) = 0.; 6 | public var width(default, set) = 0.; 7 | public var height(default, set) = 0.; 8 | 9 | var dirty = false; 10 | public var centerX = 0.; 11 | public var centerY = 0.; 12 | public var halfWidth = 0.; 13 | public var halfHeight = 0.; 14 | 15 | public function new(x = 0., y = 0., w = 1., h = 1.) { 16 | this.x = x; 17 | this.y = y; 18 | 19 | this.width = w; 20 | this.height = h; 21 | 22 | refreshCenter(); 23 | } 24 | 25 | function refreshCenter() { 26 | halfWidth = width * 0.5; 27 | halfHeight = height * 0.5; 28 | centerX = x + halfWidth; 29 | centerY = y + halfHeight; 30 | dirty = false; 31 | } 32 | 33 | public function testRect(x: Float, y: Float, w: Float, h: Float) { 34 | if (dirty) { 35 | refreshCenter(); 36 | } 37 | 38 | var hw2 = w * 0.5; 39 | var hh2 = h * 0.5; 40 | var cx2 = x + hw2; 41 | var cy2 = y + hh2; 42 | 43 | if (Math.abs(centerX - cx2) > halfWidth + hw2) return false; 44 | if (Math.abs(centerY - cy2) > halfHeight + hh2) return false; 45 | 46 | //if (x > this.x + this.width || x + width < this.x) return false; 47 | //if (y > this.y + this.height || y + height < this.y) return false; 48 | 49 | return true; 50 | } 51 | 52 | public function testCircle(x: Float, y: Float, r: Float) { 53 | return testRect(x - r, y - r, r * 2, r * 2); 54 | } 55 | 56 | public function toString() { 57 | return '${x}, ${y} - (${width}/${height})'; 58 | } 59 | 60 | function set_x(x) { 61 | dirty = true; 62 | return this.x = x; 63 | } 64 | 65 | function set_y(x) { 66 | dirty = true; 67 | return this.y = x; 68 | } 69 | function set_width(x) { 70 | dirty = true; 71 | return this.width = x; 72 | } 73 | function set_height(x) { 74 | dirty = true; 75 | return this.height = x; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/elke/graphics/Plane3D.hx: -------------------------------------------------------------------------------- 1 | package elke.graphics; 2 | 3 | import h3d.col.Bounds; 4 | 5 | class Plane3D extends h3d.prim.Primitive { 6 | var width:Float = 1.0; 7 | var height:Float = 1.0; 8 | 9 | public function new(width = 1.0, height = 1.0) { 10 | this.width = width; 11 | this.height = height; 12 | } 13 | 14 | override function triCount() { 15 | return 2; 16 | } 17 | 18 | override function vertexCount() { 19 | return 4; 20 | } 21 | 22 | override function alloc(engine:h3d.Engine) { 23 | var v = new hxd.FloatBuffer(); 24 | // Pos 25 | v.push(-width * 0.); 26 | v.push(0); 27 | v.push(-height * 0.); 28 | 29 | // Norm 30 | v.push(0); 31 | v.push(1); 32 | v.push(0); 33 | 34 | // UV 35 | v.push(0); 36 | v.push(1); 37 | 38 | // Pos 39 | v.push(-width * 0.); 40 | v.push(0); 41 | v.push(height * 1); 42 | // Norm 43 | v.push(0); 44 | v.push(1); 45 | v.push(0); 46 | // UV 47 | v.push(0); 48 | v.push(0); 49 | 50 | // Pos 51 | v.push(width * 1); 52 | v.push(0); 53 | v.push(-height * 0.); 54 | // Norm 55 | v.push(0); 56 | v.push(1); 57 | v.push(0); 58 | // UV 59 | v.push(1); 60 | v.push(1); 61 | 62 | // Pos 63 | v.push(width * 1); 64 | v.push(0); 65 | v.push(height * 1); 66 | // Norm 67 | v.push(0); 68 | v.push(1); 69 | v.push(0); 70 | // UV 71 | v.push(1); 72 | v.push(0); 73 | 74 | buffer = h3d.Buffer.ofFloats(v, 8, [Quads, RawFormat]); 75 | } 76 | 77 | override function getBounds():Bounds { 78 | var b = new h3d.col.Bounds(); 79 | b.addPos(-1, 0, -1); 80 | b.addPos(1, 0, 1); 81 | return b; 82 | } 83 | 84 | public static function get() { 85 | var engine = h3d.Engine.getCurrent(); 86 | var inst = @:privateAccess engine.resCache.get(Plane3D); 87 | if (inst == null) { 88 | inst = new Plane3D(); 89 | @:privateAccess engine.resCache.set(Plane3D, inst); 90 | } 91 | return inst; 92 | } 93 | } -------------------------------------------------------------------------------- /.vscode/commandbar.json: -------------------------------------------------------------------------------- 1 | { 2 | "skipTerminateQuickPick": true, 3 | "skipSwitchToOutput": false, 4 | "skipErrorMessage": true, 5 | "commands": [ 6 | { 7 | "text": "🍊 Build HL", 8 | "color": "orange", 9 | "commandType":"palette", 10 | "command": "workbench.action.tasks.runTask|HeapsHL.SDL", 11 | "alignment": "right", 12 | "skipTerminateQuickPick": false, 13 | "tooltip": "BUILD IT AND RUN IT", 14 | "priority": -10 15 | }, 16 | { 17 | "text": "🔨 Generate Assets", 18 | "color": "purple", 19 | "commandType":"palette", 20 | "command": "workbench.action.tasks.runTask|Generate Assets", 21 | "alignment": "right", 22 | "skipTerminateQuickPick": false, 23 | "priority": -9 24 | }, 25 | { 26 | "text": "Run HL", 27 | "color": "orange", 28 | "command": "haxe build_sdl.hxml && hl build/hl/hlboot.dat", 29 | "alignment": "right", 30 | "skipTerminateQuickPick": false, 31 | "priority": -11 32 | }, 33 | { 34 | "text": "☕ Build JS", 35 | "color": "yellow", 36 | "commandType":"palette", 37 | "command": "workbench.action.tasks.runTask|HeapsJS", 38 | "alignment": "right", 39 | "skipTerminateQuickPick": false, 40 | "priority": -20 41 | }, 42 | { 43 | "text": "Run JS", 44 | "color": "yellow", 45 | "command": "cd build/web && start index.html", 46 | "alignment": "right", 47 | "skipTerminateQuickPick": false, 48 | "priority": -21 49 | }, 50 | { 51 | "text": "🅰️ Build POT", 52 | "color": "white", 53 | "commandType":"palette", 54 | "command": "workbench.action.tasks.runTask|Lang", 55 | "alignment": "right", 56 | "skipTerminateQuickPick": false, 57 | "priority": -40 58 | }, 59 | { 60 | "text": "📦 Redist", 61 | "color": "lightgreen", 62 | "command": "haxelib run redistHelper build_dx_release.hxml build_sdl_release.hxml build_js_release.hxml -o redist/game.$ -p HeapsGame", 63 | "alignment": "right", 64 | "skipTerminateQuickPick": false, 65 | "priority": -50 66 | } 67 | ] 68 | } -------------------------------------------------------------------------------- /src/elke/input/GamePadHandler.hx: -------------------------------------------------------------------------------- 1 | package elke.input; 2 | 3 | import hxd.Pad; 4 | 5 | class GamePadHandler { 6 | public function new() { 7 | } 8 | 9 | public function init() { 10 | #if enableGamepads 11 | awaitGamepad(); 12 | #end 13 | } 14 | 15 | public var inFocus = true; 16 | 17 | public var pad: Pad = null; 18 | public var connected = false; 19 | function awaitGamepad() { 20 | Pad.wait(onGamepadConnect); 21 | } 22 | 23 | public function anyButtonPressed() { 24 | if (!isActive()) { 25 | return false; 26 | } 27 | for (v in pad.values) { 28 | if (Math.abs(v) > 0.4) { 29 | return true; 30 | } 31 | } 32 | return false; 33 | } 34 | 35 | public function vibrate(duration: Int, strength = 1.0) { 36 | if (!isActive()) { 37 | return false; 38 | } 39 | 40 | pad.rumble(strength, duration / 1000.); 41 | 42 | return true; 43 | } 44 | 45 | function onGamepadConnect(pad: hxd.Pad) { 46 | trace("Gamepad connected"); 47 | if (this.pad != null) { 48 | return; 49 | } 50 | 51 | this.connected = true; 52 | 53 | 54 | this.pad = pad; 55 | pad.axisDeadZone = 0.2; 56 | 57 | pad.onDisconnect = () -> { 58 | trace("disconnected"); 59 | this.pad = null; 60 | this.connected = false; 61 | } 62 | 63 | awaitGamepad(); 64 | } 65 | 66 | final conf = hxd.Pad.DEFAULT_CONFIG; 67 | public function isBtnDown(btn) { 68 | if (!isActive()) { 69 | return false; 70 | } 71 | 72 | return pad.isDown(btn); 73 | } 74 | 75 | public function getStickX() : Float { 76 | if (!isActive()) { 77 | return 0; 78 | } 79 | 80 | return pad.xAxis; 81 | } 82 | 83 | public function getStickY() : Float { 84 | if (!isActive()) { 85 | return 0; 86 | } 87 | 88 | return pad.yAxis; 89 | } 90 | 91 | inline function isActive() { 92 | return pad != null && inFocus; 93 | } 94 | 95 | final dz = 0.5; 96 | public function pressingLeft() { 97 | return isActive() && pad.xAxis < -dz; 98 | } 99 | public function pressingRight() { 100 | return isActive() && pad.xAxis > dz; 101 | } 102 | public function pressingUp() { 103 | return isActive() && pad.yAxis < -dz; 104 | } 105 | public function pressingDown() { 106 | return isActive() && pad.yAxis > dz; 107 | } 108 | } -------------------------------------------------------------------------------- /src/elke/graphics/SpriteGroupSprite.hx: -------------------------------------------------------------------------------- 1 | package elke.graphics; 2 | 3 | import elke.graphics.SpriteGroup.SpriteGroupTileSheet; 4 | import h2d.Tile; 5 | 6 | @:access(h2d.Tile) 7 | class SpriteGroupSprite { 8 | public var animation:Animation; 9 | 10 | var spriteGroupTileSheet: SpriteGroupTileSheet; 11 | var spriteGroup: SpriteGroup; 12 | 13 | public var x: Float = 0.; 14 | public var y: Float = 0.; 15 | public var scaleX = 1.0; 16 | public var scaleY = 1.0; 17 | public var flipX = false; 18 | public var rotation = 0.0; 19 | public var alpha = 1.0; 20 | 21 | public var offsetY = 0.; 22 | public var visible = true; 23 | 24 | var dirty = false; 25 | 26 | /** 27 | * Horizontal origin of sprite, in pixels. 28 | * 0 = left, 1 = right 29 | */ 30 | public var originX(default, set):Int = 0; 31 | 32 | /** 33 | * Vertical origin of sprite, in pixels. 34 | * 0 = top, 1 = bottom 35 | */ 36 | public var originY(default, set):Int = 0; 37 | 38 | var tiles:Array; 39 | 40 | public var tile: Tile; 41 | var lastTile:h2d.Tile; 42 | 43 | public function new(anim, spriteGroupTileSheet:SpriteGroupTileSheet, spriteGroup: SpriteGroup) { 44 | animation = anim; 45 | this.spriteGroupTileSheet = spriteGroupTileSheet; 46 | this.spriteGroup = spriteGroup; 47 | } 48 | 49 | function refreshTile() { 50 | var frame = animation.getCurrentFrame(); 51 | var t = spriteGroupTileSheet.tiles[animation.currentFrame]; 52 | 53 | if (!dirty && t == lastTile) { 54 | return; 55 | } 56 | 57 | dirty = false; 58 | lastTile = t; 59 | 60 | this.tile = t; 61 | if (frame != null) { 62 | t.dx = frame.offsetX - originX; 63 | t.dy = frame.offsetY - originY; 64 | } 65 | } 66 | 67 | public function draw(?s: SpriteGroup) { 68 | if (tile == null) { 69 | return; 70 | } 71 | 72 | if (s != null) { 73 | s.add(x, y, tile); 74 | } else { 75 | this.spriteGroup.addTransform(x, y, scaleX, scaleY, rotation, tile); 76 | } 77 | } 78 | 79 | public function update(dt: Float) { 80 | animation.update(dt); 81 | refreshTile(); 82 | } 83 | 84 | inline function set_originX(o:Int) { 85 | if (o != originX) 86 | dirty = true; 87 | return originX = o; 88 | } 89 | 90 | public function remove() { 91 | this.spriteGroup.removeSprite(this); 92 | } 93 | 94 | inline function set_originY(o:Int) { 95 | if (o != originY) 96 | dirty = true; 97 | return originY = o; 98 | } 99 | 100 | public inline function syncWith(parent:Sprite) { 101 | originX = parent.originX; 102 | originY = parent.originY; 103 | 104 | if (animation.currentFrame != parent.animation.currentFrame) { 105 | dirty = true; 106 | } 107 | 108 | animation.currentFrame = parent.animation.currentFrame; 109 | } 110 | } -------------------------------------------------------------------------------- /src/elke/utils/SpatialBuckets.hx: -------------------------------------------------------------------------------- 1 | package elke.utils; 2 | 3 | class SpatialBuckets { 4 | public var bucketSize(default, null) = 100; 5 | public var width = 2000.; 6 | public var height = 2000.; 7 | 8 | var columns = 0; 9 | 10 | var buckets = new Map>(); 11 | 12 | public function new(bucketSize = 100, width = 2000., height = 2000.) { 13 | this.bucketSize = bucketSize; 14 | resize(width, height); 15 | } 16 | 17 | function resize(width, height) { 18 | var oldColumns = this.columns; 19 | 20 | this.width = width; 21 | this.height = height; 22 | 23 | columns = Std.int(Math.ceil(bucketSize / width)); 24 | 25 | var oldBuckets = buckets; 26 | buckets = new Map>(); 27 | 28 | for (key => bucket in oldBuckets) { 29 | var row = Std.int(key / oldColumns); 30 | var col = key - row * columns; 31 | 32 | var newKey = row * columns + col; 33 | for (object in bucket) { 34 | addToBucket(newKey, object); 35 | } 36 | } 37 | } 38 | 39 | inline function addToBucket(key: Int, object: T) { 40 | if (!buckets.exists(key)) { 41 | buckets[key] = []; 42 | } 43 | 44 | var b = buckets[key]; 45 | if (!b.contains(object)) { 46 | b.push(object); 47 | } 48 | } 49 | 50 | public function add(object: T, x: Float, y: Float, w: Float, h: Float) { 51 | var xmin = Std.int(x / bucketSize); 52 | var xmax = Std.int((x + w) / bucketSize) + 1; 53 | var ymin = Std.int(y / bucketSize); 54 | var ymax = Std.int((y + h) / bucketSize) + 1; 55 | for (x in xmin...xmax) { 56 | for (y in ymin...ymax) { 57 | var key = x + y * columns; 58 | addToBucket(key, object); 59 | } 60 | } 61 | 62 | return null; 63 | } 64 | 65 | public function getPotentialCollisions(x:Float, y: Float, w: Float, h: Float) { 66 | var res: Array = null; 67 | 68 | var xmin = Std.int(x / bucketSize); 69 | var xmax = Std.int((x + w) / bucketSize) + 1; 70 | var ymin = Std.int(y / bucketSize); 71 | var ymax = Std.int((y + h) / bucketSize) + 1; 72 | 73 | for (x in xmin...xmax) { 74 | for (y in ymin...ymax) { 75 | var key = x + y * columns; 76 | var b = buckets[key]; 77 | if (b == null) { 78 | continue; 79 | } 80 | 81 | for (o in b) { 82 | if (res == null) res = []; 83 | if (!res.contains(o)) { 84 | res.push(o); 85 | } 86 | } 87 | } 88 | } 89 | 90 | return res; 91 | } 92 | 93 | public function getPotentialCollisionsCircle(x: Float, y: Float, r: Float) { 94 | return getPotentialCollisions(x - r, y - r, r * 2, r * 2); 95 | } 96 | 97 | public function addSphere(object: T, x: Float, y: Float, r: Float) { 98 | return add(object, x - r, y - r, r * 2, r * 2); 99 | } 100 | 101 | public function clear() { 102 | buckets.clear(); 103 | } 104 | } -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ::windowTitle:: 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 33 | 61 | 62 | ::additionalCss:: 63 | 64 | 85 | 86 | -------------------------------------------------------------------------------- /src/elke/things/TouchJoystick.hx: -------------------------------------------------------------------------------- 1 | package elke.things; 2 | 3 | import h2d.Object; 4 | import h2d.Graphics; 5 | 6 | class TouchJoystick extends Object { 7 | var bg: Graphics; 8 | var dot: Graphics; 9 | 10 | var r = 54.; 11 | var maxR = 72; 12 | #if js 13 | public var touchId = null; 14 | #else 15 | public var touchId = 0; 16 | #end 17 | public var active = false; 18 | 19 | public var mx = 0.; 20 | public var my = 0.; 21 | 22 | public var magnitude = 0.0; 23 | 24 | public function new(?p) { 25 | super(p); 26 | bg = new Graphics(this); 27 | bg.beginFill(0x111111, 0.4); 28 | bg.drawCircle(0, 0, r); 29 | dot = new Graphics(this); 30 | dot.beginFill(0x111111, 0.9); 31 | dot.drawCircle(0, 0, r * 0.3); 32 | visible = false; 33 | } 34 | 35 | var disabled = false; 36 | public function disable() { 37 | disabled = true; 38 | active = false; 39 | visible = false; 40 | mx = my = magnitude = 0; 41 | } 42 | 43 | public function enable() { 44 | disabled = false; 45 | visible = true; 46 | } 47 | 48 | public function start(x, y, touchID) { 49 | if (disabled) { 50 | return; 51 | } 52 | 53 | this.x = x; 54 | this.y = y; 55 | this.touchId = touchID; 56 | mx = my = 0; 57 | dot.x = dot.y = 0; 58 | visible = true; 59 | active = true; 60 | } 61 | 62 | public function movement(tx: Float, ty: Float) { 63 | if (disabled) { 64 | return; 65 | } 66 | 67 | var dx = tx - x; 68 | var dy = ty - y; 69 | var l = Math.sqrt(dx * dx + dy * dy); 70 | 71 | if (l > maxR) { 72 | var fx = dx / l; 73 | var fy = dy / l; 74 | fx *= (maxR - l); 75 | fy *= (maxR - l); 76 | x -= fx; 77 | y -= fy; 78 | 79 | dx = tx - x; 80 | dy = ty - y; 81 | l = Math.sqrt(dx * dx + dy * dy); 82 | } 83 | 84 | if (l > 0) { 85 | mx = dx / l; 86 | my = dy / l; 87 | 88 | if (l > r) { 89 | dx = mx * r; 90 | dy = my * r; 91 | } 92 | } else { 93 | mx = my = dx = dy = 0; 94 | } 95 | 96 | 97 | magnitude = l / r; 98 | 99 | mx *= l / r; 100 | my *= l / r; 101 | 102 | if (magnitude < 0.5) { 103 | mx *= (1 - 2 * (0.5 - magnitude)); 104 | my *= (1 - 2 * (0.5 - magnitude)); 105 | } 106 | 107 | if (magnitude < 0.2) { 108 | mx = my = 0; 109 | } 110 | 111 | dot.x = Math.round(dx); 112 | dot.y = Math.round(dy); 113 | } 114 | 115 | var dz = 0.4; 116 | public function goingLeft() { 117 | return active && mx < -dz; 118 | } 119 | public function goingRight() { 120 | return active && mx > dz; 121 | } 122 | public function goingUp() { 123 | return active && my < -dz; 124 | } 125 | public function goingDown() { 126 | return active && my > dz; 127 | } 128 | 129 | public function end() { 130 | visible = false; 131 | active = false; 132 | #if js 133 | touchId = null; 134 | #else 135 | touchId = 0; 136 | #end 137 | } 138 | } -------------------------------------------------------------------------------- /src/elke/buildutil/WebGenerator.hx: -------------------------------------------------------------------------------- 1 | package elke.buildutil; 2 | 3 | class WebGenerator { 4 | #if hscript 5 | static macro function generate() { 6 | var templates = []; 7 | function getRec(path) { 8 | for (f in sys.FileSystem.readDirectory(path)) { 9 | var file = path + "/" + f; 10 | if (sys.FileSystem.isDirectory(file)) { 11 | getRec(file); 12 | continue; 13 | } 14 | var tmpl = file.substr(10); 15 | templates.push({file: tmpl, data: sys.io.File.getContent(file)}); 16 | } 17 | } 18 | getRec("templates"); 19 | 20 | function val(name) { 21 | return haxe.macro.Context.definedValue(name); 22 | } 23 | 24 | final windowTitle = haxe.macro.Context.definedValue("windowTitle"); 25 | final name = "web"; 26 | 27 | var context = { 28 | windowTitle: val("windowTitle"), 29 | gameFile: "game.js", 30 | additionalCss: "", 31 | twitterSite: val("twitterSite"), 32 | twitterCreator: val("twitterCreator"), 33 | ogUrl: val("ogUrl"), 34 | ogDescription: val("ogDescription"), 35 | ogImage: val("ogImage"), 36 | backgroundColor: val("backgroundColor"), 37 | }; 38 | 39 | final fixedWindow = haxe.macro.Context.definedValue("windowFixed"); 40 | 41 | if (fixedWindow != null) { 42 | final sizes = haxe.macro.Context.definedValue("windowSize"); 43 | if (sizes == null) { 44 | throw "windowFixed defined without setting windowSize"; 45 | } 46 | 47 | var wh = sizes.split("x"); 48 | var width = wh[0]; 49 | var height = wh[1]; 50 | 51 | context.additionalCss += ' 52 | 56 | '; 57 | } 58 | 59 | var templateableFiles = [".hx", ".html", ".css", ".js", ".json", ".txt"]; 60 | var ignoredFiles = ["bullet.js"]; 61 | 62 | var interp = new hscript.Interp(); 63 | for (f in Reflect.fields(context)) 64 | interp.variables.set(f, Reflect.field(context, f)); 65 | for (t in templates) { 66 | var templateable = false; 67 | for (templateExtension in templateableFiles) { 68 | if (StringTools.endsWith(t.file, templateExtension)) { 69 | templateable = true; 70 | break; 71 | } 72 | } 73 | 74 | for (ignored in ignoredFiles) { 75 | if (t.file == ignored) { 76 | templateable = false; 77 | break; 78 | } 79 | } 80 | 81 | var data:String; 82 | if (templateable) { 83 | data = ~/::([^:]+)::/g.map(t.data, function(r) { 84 | var script = r.matched(1); 85 | var expr = new hscript.Parser().parseString(script); 86 | return "" + interp.execute(expr); 87 | }); 88 | } else { 89 | data = t.data; 90 | } 91 | 92 | var file = t.file.split("__name").join(name); 93 | var dir = file.split("/"); 94 | dir.pop(); 95 | try 96 | sys.FileSystem.createDirectory("build/" + name + "/" + dir.join("/")) 97 | catch (e:Dynamic) {}; 98 | sys.io.File.saveContent("build/" + name + "/" + file, data); 99 | } 100 | 101 | return null; 102 | } 103 | #end 104 | } -------------------------------------------------------------------------------- /src/elke/graphics/MultiTileGroup.hx: -------------------------------------------------------------------------------- 1 | package elke.graphics; 2 | 3 | import h2d.Bitmap; 4 | import h3d.mat.Texture; 5 | import h2d.RenderContext; 6 | import h2d.TileGroup; 7 | import h2d.Tile; 8 | 9 | class MultiTileGroup extends TileGroup { 10 | //var sheetMap: Map; 11 | //var sprites: Array = []; 12 | //static var debugAtlas: Bitmap; 13 | 14 | var atlas: Texture; 15 | var rootTile: Tile; 16 | 17 | public function new(?tiles: Array, ?p) { 18 | super(null, p); 19 | 20 | if (tiles != null) { 21 | compile(tiles); 22 | } 23 | } 24 | 25 | override function sync(ctx:RenderContext) { 26 | super.sync(ctx); 27 | clear(); 28 | /* 29 | for (s in sprites) { 30 | if (s.visible && s.tile != null) { 31 | curColor.a = s.alpha; 32 | addTransform( 33 | s.x, 34 | s.y, 35 | s.scaleX, 36 | s.scaleY, 37 | s.rotation, 38 | s.tile 39 | ); 40 | } 41 | } 42 | */ 43 | } 44 | 45 | override function onRemove() { 46 | super.onRemove(); 47 | if (atlas != null) { 48 | atlas.dispose(); 49 | } 50 | } 51 | 52 | public function addSprite(name: String) { 53 | /* 54 | var s = sheetMap[name]; 55 | if (s == null) { 56 | return null; 57 | } 58 | 59 | //var anim = new Animation(s.res); 60 | //var s = new SpriteGroupSprite(anim, s, this); 61 | //sprites.push(s); 62 | 63 | return s; 64 | */ 65 | } 66 | 67 | public function removeSprite(sprite: SpriteGroupSprite) { 68 | //sprites.remove(sprite); 69 | } 70 | 71 | public function clearGroup() { 72 | //sprites = []; 73 | } 74 | 75 | function compile(tiles: Array) { 76 | //debugAtlas = new Bitmap(null, Game.instance.s2d); 77 | /* 78 | sheetMap = new Map(); 79 | 80 | if (sheetMap != null) { 81 | sheetMap.clear(); 82 | } 83 | */ 84 | 85 | final maxWidth = 1024; 86 | final maxHeight = 1024; 87 | if (atlas != null) { 88 | atlas.dispose(); 89 | atlas = null; 90 | } 91 | 92 | atlas = new Texture(maxWidth, maxHeight, [Target], RGBA); 93 | atlas.clear(0x000000, 0.0); 94 | 95 | tiles.sort((a, b) -> Std.int(a.height - b.height)); 96 | 97 | var x = 0; 98 | var y = 0; 99 | var rowH = 0; 100 | 101 | var bm = new Bitmap(); 102 | rootTile = Tile.fromTexture(atlas); 103 | for (t in tiles) { 104 | bm.tile = t; 105 | if (x + t.width > maxWidth) { 106 | x = 0; 107 | y += rowH; 108 | rowH = 0; 109 | } 110 | if (rowH < t.height) { 111 | rowH = Std.int(t.height); 112 | } 113 | bm.x = x; 114 | bm.y = y; 115 | bm.drawTo(atlas); 116 | 117 | //sheetMap.set(t.name, new SpriteGroupTileSheet(x, y, sheet, rootTile)); 118 | 119 | x += Std.int(t.width); 120 | } 121 | 122 | //var resBm = new Bitmap(rootTile, this); 123 | //debugAtlas.tile = rootTile; 124 | } 125 | 126 | public var sortY = true; 127 | 128 | public function update(dt: Float) { 129 | /* 130 | if (sortY) { 131 | sprites.sort((a, b) -> Std.int((a.y + a.offsetY) - (b.y + b.offsetY))); 132 | } 133 | 134 | for (s in sprites) { 135 | s.update(dt); 136 | } 137 | */ 138 | } 139 | } -------------------------------------------------------------------------------- /src/elke/utils/CollisionHandler.hx: -------------------------------------------------------------------------------- 1 | package elke.utils; 2 | 3 | import h2d.col.Point; 4 | 5 | interface CollisionObject { 6 | var x: Float; 7 | var y: Float; 8 | var vx: Float; 9 | var vy: Float; 10 | var rotation: Float; 11 | var radius: Float; 12 | var mass: Float; 13 | var uncollidable: Bool; 14 | var filterGroup: Int; 15 | } 16 | 17 | /** 18 | * resolves collisions between an array of circles 19 | */ 20 | class CollisionHandler { 21 | public function new() { 22 | } 23 | 24 | public var useBuckets = true; 25 | public var bucketSize = 100; 26 | public var width = 2000.; 27 | public var height = 2000.; 28 | public function resolve(objects:Array) { 29 | var buckets = new Map>(); 30 | var columns = Std.int(Math.ceil(bucketSize / width)); 31 | var d = new Point(); 32 | 33 | inline function doResolve(m: CollisionObject, m2: CollisionObject) { 34 | d.set(m.x - m2.x, m.y - m2.y); 35 | var r = m.radius + m2.radius; 36 | var r2 = r * r; 37 | 38 | var d2 = d.lengthSq(); 39 | if (d2 < r2) { 40 | d.normalize(); 41 | var dist = Math.sqrt(d2); 42 | d.scale(r - dist); 43 | 44 | var totalMass = m.mass + m2.mass; 45 | if (totalMass > 0) { 46 | var move1 = m.mass / totalMass; 47 | var move2 = m2.mass / totalMass; 48 | 49 | if (m.mass == 0) { 50 | move1 = 1; 51 | move2 = 0; 52 | } 53 | 54 | if (m2.mass == 0) { 55 | move1 = 0; 56 | move2 = 1; 57 | } 58 | 59 | var vx1 = d.x * move2; 60 | var vy1 = d.y * move2; 61 | 62 | m.x += vx1; 63 | m.y += vy1; 64 | m.vx += vx1 * 0.5; 65 | m.vy += vy1 * 0.5; 66 | 67 | var vx2 = d.x * move1; 68 | var vy2 = d.y * move1; 69 | 70 | m2.x -= vx2; 71 | m2.y -= vy2; 72 | m2.vx -= vx2 * 0.5; 73 | m2.vy -= vy2 * 0.5; 74 | } 75 | } 76 | } 77 | 78 | if (useBuckets) { 79 | for (m in objects) { 80 | if (m.uncollidable) { 81 | continue; 82 | } 83 | 84 | var xmin = Std.int((m.x - m.radius) / bucketSize); 85 | var xmax = Std.int((m.x + m.radius) / bucketSize) + 1; 86 | var ymin = Std.int((m.y - m.radius) / bucketSize); 87 | var ymax = Std.int((m.y + m.radius) / bucketSize) + 1; 88 | for (x in xmin...xmax) { 89 | for (y in ymin...ymax) { 90 | var key = x + y * columns; 91 | if (!buckets.exists(key)) { 92 | buckets[key] = []; 93 | } 94 | 95 | var b = buckets[key]; 96 | for (m2 in b) { 97 | if (m2 == null) { 98 | continue; 99 | } 100 | if (m2.filterGroup != 0 && m2.filterGroup == m.filterGroup) { 101 | continue; 102 | } 103 | if (m == m2) 104 | continue; 105 | 106 | doResolve(m, m2); 107 | } 108 | 109 | b.push(m); 110 | } 111 | } 112 | } 113 | } else { 114 | for (m in objects) { 115 | if (m.uncollidable) { 116 | continue; 117 | } 118 | 119 | for (m2 in objects) { 120 | if (m2 == null) { 121 | break; 122 | } 123 | 124 | if (m2.uncollidable) { 125 | continue; 126 | } 127 | 128 | if (m2.filterGroup != 0 && m2.filterGroup == m.filterGroup) { 129 | continue; 130 | } 131 | 132 | if (m == m2) { 133 | continue; 134 | } 135 | 136 | doResolve(m, m2); 137 | } 138 | } 139 | } 140 | } 141 | } -------------------------------------------------------------------------------- /src/elke/T.hx: -------------------------------------------------------------------------------- 1 | 2 | package elke; 3 | 4 | typedef RGB = { 5 | var r:Float; 6 | var g:Float; 7 | var b:Float; 8 | } 9 | 10 | /** 11 | * Easing functions 12 | */ 13 | class T { 14 | static final PI2:Float = Math.PI / 2; 15 | 16 | static final EL:Float = 2 * Math.PI / .45; 17 | static final B1:Float = 1 / 2.75; 18 | static final B2:Float = 2 / 2.75; 19 | static final B3:Float = 1.5 / 2.75; 20 | static final B4:Float = 2.5 / 2.75; 21 | static final B5:Float = 2.25 / 2.75; 22 | static final B6:Float = 2.625 / 2.75; 23 | static final ELASTIC_AMPLITUDE:Float = 1; 24 | static final ELASTIC_PERIOD:Float = 0.4; 25 | 26 | public static inline function toRGB(int:Int):RGB { 27 | return { 28 | r: ((int >> 16) & 255) / 255, 29 | g: ((int >> 8) & 255) / 255, 30 | b: (int & 255) / 255, 31 | } 32 | } 33 | 34 | public static inline function smoothstep(from:Float, to:Float, x:Float) { 35 | x = clamp((x - from) / (to - from), 0., 1.); 36 | return x * x * (3. - 2. * x); 37 | } 38 | 39 | public static inline function smootherstep(from:Float, to:Float, x:Float) { 40 | x = clamp((x - from) / (to - from), 0., 1.); 41 | return x * x * x * (x * (x * 6. - 15.) + 10.); 42 | } 43 | 44 | public static inline function tickLerp(from:Float, to:Float, sharpness = 0.3, scale = 1.0) { 45 | var d = to - from; 46 | var s = sharpness; 47 | if (scale != 1.0) 48 | s = 1.0 - Math.pow(1.0 - sharpness, scale); 49 | 50 | return from + d * s; 51 | } 52 | 53 | public static inline function clamp(x:Float, lower, upper) { 54 | if (x < lower) 55 | return lower; 56 | if (x > upper) 57 | return upper; 58 | return x; 59 | } 60 | 61 | public static inline function quintIn(t:Float):Float 62 | { 63 | return t * t * t * t * t; 64 | } 65 | 66 | public static inline function sineIn(t:Float):Float 67 | { 68 | return -Math.cos(PI2 * t) + 1; 69 | } 70 | 71 | public static inline function expoIn(t:Float):Float 72 | { 73 | return Math.pow(2, 10 * (t - 1)); 74 | } 75 | 76 | public static inline function expoOut(t:Float):Float 77 | { 78 | return -Math.pow(2, -10 * t) + 1; 79 | } 80 | 81 | public static inline function quintOut(t:Float):Float { 82 | return (t = t - 1) * t * t * t * t + 1; 83 | } 84 | 85 | public static inline function smootherStepInOut(t:Float):Float { 86 | return t * t * t * (t * (t * 6 - 15) + 10); 87 | } 88 | 89 | public static function bounceIn(t:Float):Float { 90 | t = 1 - t; 91 | if (t < B1) 92 | return 1 - 7.5625 * t * t; 93 | if (t < B2) 94 | return 1 - (7.5625 * (t - B3) * (t - B3) + .75); 95 | if (t < B4) 96 | return 1 - (7.5625 * (t - B5) * (t - B5) + .9375); 97 | return 1 - (7.5625 * (t - B6) * (t - B6) + .984375); 98 | } 99 | 100 | public static function bounceOut(t:Float):Float { 101 | if (t < B1) 102 | return 7.5625 * t * t; 103 | if (t < B2) 104 | return 7.5625 * (t - B3) * (t - B3) + .75; 105 | if (t < B4) 106 | return 7.5625 * (t - B5) * (t - B5) + .9375; 107 | return 7.5625 * (t - B6) * (t - B6) + .984375; 108 | } 109 | 110 | public static inline function elasticIn(t:Float):Float { 111 | return -(ELASTIC_AMPLITUDE * Math.pow(2, 112 | 10 * (t -= 1)) * Math.sin((t - (ELASTIC_PERIOD / (2 * Math.PI) * Math.asin(1 / ELASTIC_AMPLITUDE))) * (2 * Math.PI) / ELASTIC_PERIOD)); 113 | } 114 | 115 | public static inline function elasticOut(t:Float):Float { 116 | return (ELASTIC_AMPLITUDE * Math.pow(2, 117 | -10 * t) * Math.sin((t - (ELASTIC_PERIOD / (2 * Math.PI) * Math.asin(1 / ELASTIC_AMPLITUDE))) * (2 * Math.PI) / ELASTIC_PERIOD) 118 | + 1); 119 | } 120 | } -------------------------------------------------------------------------------- /src/elke/utils/Joystick.hx: -------------------------------------------------------------------------------- 1 | package elke.utils; 2 | 3 | import h2d.col.Point; 4 | import elke.T; 5 | import h2d.Graphics; 6 | import elke.entity.Entity2D; 7 | 8 | class Joystick extends Entity2D { 9 | var bg: Graphics; 10 | var dot: Graphics; 11 | var r = 54.; 12 | var maxR = 72; 13 | #if js 14 | public var touchId = null; 15 | #else 16 | public var touchId = 0; 17 | #end 18 | public var active = false; 19 | 20 | public var mx = 0.; 21 | public var my = 0.; 22 | 23 | public var magnitude = 0.0; 24 | 25 | public function new(?p) { 26 | super(p); 27 | bg = new Graphics(this); 28 | bg.beginFill(0x111111, 0.4); 29 | bg.drawCircle(0, 0, r); 30 | dot = new Graphics(this); 31 | dot.beginFill(0x111111, 0.9); 32 | dot.drawCircle(0, 0, r * 0.3); 33 | visible = false; 34 | } 35 | 36 | public function handleEvent(e: hxd.Event) { 37 | #if js 38 | var s2d = getScene(); 39 | var g = s2d.globalToLocal(new Point(e.relX, e.relY)); 40 | g.x /= Game.instance.pixelSize; 41 | g.y /= Game.instance.pixelSize; 42 | 43 | if (e.kind == EPush) { 44 | if (g.x < s2d.width * 0.5) { 45 | if (e.touchId != null && !active) { 46 | start(g.x, g.y, e.touchId); 47 | return true; 48 | } 49 | } 50 | } 51 | 52 | if (e.kind == EMove) { 53 | if (e.touchId != null && e.touchId == touchId) { 54 | movement(g.x, g.y); 55 | return true; 56 | } 57 | } 58 | 59 | if (e.kind == ERelease || e.kind == EReleaseOutside) { 60 | if (e.touchId == touchId && active) { 61 | end(); 62 | return true; 63 | } 64 | } 65 | 66 | return false; 67 | #end 68 | } 69 | 70 | var disabled = false; 71 | public function disable() { 72 | disabled = true; 73 | active = false; 74 | visible = false; 75 | mx = my = magnitude = 0; 76 | } 77 | 78 | public function start(x, y, touchID) { 79 | if (disabled) { 80 | return; 81 | } 82 | 83 | this.x = x; 84 | this.y = y; 85 | this.touchId = touchID; 86 | mx = my = 0; 87 | dot.x = dot.y = 0; 88 | visible = true; 89 | active = true; 90 | } 91 | 92 | public function movement(tx: Float, ty: Float) { 93 | if (disabled) { 94 | return; 95 | } 96 | 97 | var dx = tx - x; 98 | var dy = ty - y; 99 | var l = Math.sqrt(dx * dx + dy * dy); 100 | 101 | if (l > maxR) { 102 | var fx = dx / l; 103 | var fy = dy / l; 104 | fx *= (maxR - l); 105 | fy *= (maxR - l); 106 | x -= fx; 107 | y -= fy; 108 | 109 | dx = tx - x; 110 | dy = ty - y; 111 | l = Math.sqrt(dx * dx + dy * dy); 112 | } 113 | 114 | if (l > 0) { 115 | mx = dx / l; 116 | my = dy / l; 117 | 118 | if (l > r) { 119 | dx = mx * r; 120 | dy = my * r; 121 | } 122 | } else { 123 | mx = my = dx = dy = 0; 124 | } 125 | 126 | 127 | magnitude = l / r; 128 | 129 | mx *= l / r; 130 | my *= l / r; 131 | 132 | if (magnitude < 0.5) { 133 | mx *= (1 - 2 * (0.5 - magnitude)); 134 | my *= (1 - 2 * (0.5 - magnitude)); 135 | } 136 | 137 | if (magnitude < 0.2) { 138 | mx = my = 0; 139 | } 140 | 141 | dot.x = Math.round(dx); 142 | dot.y = Math.round(dy); 143 | } 144 | 145 | var dz = 0.5; 146 | public function goingLeft() { 147 | return active && mx < -dz; 148 | } 149 | public function goingRight() { 150 | return active && mx > dz; 151 | } 152 | public function goingUp() { 153 | return active && my < -dz; 154 | } 155 | public function goingDown() { 156 | return active && my > dz; 157 | } 158 | 159 | public function end() { 160 | visible = false; 161 | active = false; 162 | #if js 163 | touchId = null; 164 | #else 165 | touchId = 0; 166 | #end 167 | } 168 | } -------------------------------------------------------------------------------- /src/elke/graphics/SpriteGroup.hx: -------------------------------------------------------------------------------- 1 | package elke.graphics; 2 | 3 | import h2d.RenderContext; 4 | import h2d.Tile; 5 | import h2d.Bitmap; 6 | import h3d.mat.Texture; 7 | import elke.res.TileSheetRes; 8 | import h2d.TileGroup; 9 | 10 | class SpriteGroupTileSheet { 11 | var x = 0; 12 | var y = 0; 13 | var name: String; 14 | public var tiles: Array; 15 | public var res: TileSheetRes; 16 | public function new(x, y, res:TileSheetRes, rootTile: Tile) { 17 | this.x = x; 18 | this.y = y; 19 | this.name = res.name; 20 | this.res = res; 21 | this.tiles = []; 22 | for (frame in res.frames) { 23 | var tile = frame.tile; 24 | var t = rootTile.sub(tile.ix + x, tile.iy + y, tile.width, tile.height); 25 | tiles.push(t); 26 | } 27 | } 28 | } 29 | 30 | class SpriteGroup extends TileGroup { 31 | var sheetMap: Map; 32 | var sprites: Array = []; 33 | //static var debugAtlas: Bitmap; 34 | 35 | var atlas: Texture; 36 | var rootTile: Tile; 37 | 38 | public function new(?tilesheets: Array, ?p) { 39 | super(null, p); 40 | 41 | if (tilesheets != null) { 42 | compile(tilesheets); 43 | } 44 | } 45 | 46 | override function sync(ctx:RenderContext) { 47 | super.sync(ctx); 48 | clear(); 49 | for (s in sprites) { 50 | if (s.visible && s.tile != null) { 51 | curColor.a = s.alpha; 52 | addTransform( 53 | s.x, 54 | s.y, 55 | s.scaleX, 56 | s.scaleY, 57 | s.rotation, 58 | s.tile 59 | ); 60 | } 61 | } 62 | } 63 | 64 | override function onRemove() { 65 | super.onRemove(); 66 | if (atlas != null) { 67 | atlas.dispose(); 68 | } 69 | } 70 | 71 | public function addSprite(name: String) { 72 | var s = sheetMap[name]; 73 | if (s == null) { 74 | return null; 75 | } 76 | 77 | var anim = new Animation(s.res); 78 | var s = new SpriteGroupSprite(anim, s, this); 79 | 80 | sprites.push(s); 81 | 82 | return s; 83 | } 84 | 85 | public function removeSprite(sprite: SpriteGroupSprite) { 86 | sprites.remove(sprite); 87 | } 88 | 89 | public function clearGroup() { 90 | sprites = []; 91 | } 92 | 93 | function compile(sheets: Array) { 94 | //debugAtlas = new Bitmap(null, Game.instance.s2d); 95 | sheetMap = new Map(); 96 | 97 | if (sheetMap != null) { 98 | sheetMap.clear(); 99 | } 100 | 101 | final maxWidth = 1024; 102 | final maxHeight = 1024; 103 | if (atlas != null) { 104 | atlas.dispose(); 105 | atlas = null; 106 | } 107 | 108 | atlas = new Texture(maxWidth, maxHeight, [Target], RGBA); 109 | atlas.clear(0x000000, 0.0); 110 | 111 | sheets.sort((a, b) -> Std.int(a.tile.height - b.tile.height)); 112 | 113 | var x = 0; 114 | var y = 0; 115 | var rowH = 0; 116 | 117 | var bm = new Bitmap(); 118 | rootTile = Tile.fromTexture(atlas); 119 | for (sheet in sheets) { 120 | var t = sheet.tile; 121 | bm.tile = t; 122 | if (x + t.width > maxWidth) { 123 | x = 0; 124 | y += rowH; 125 | rowH = 0; 126 | } 127 | if (rowH < t.height) { 128 | rowH = Std.int(t.height); 129 | } 130 | bm.x = x; 131 | bm.y = y; 132 | bm.drawTo(atlas); 133 | sheetMap.set(sheet.name, new SpriteGroupTileSheet(x, y, sheet, rootTile)); 134 | x += Std.int(t.width); 135 | } 136 | 137 | //var resBm = new Bitmap(rootTile, this); 138 | //debugAtlas.tile = rootTile; 139 | } 140 | 141 | public var sortY = true; 142 | 143 | public function update(dt: Float) { 144 | if (sortY) { 145 | sprites.sort((a, b) -> Std.int((a.y + a.offsetY) - (b.y + b.offsetY))); 146 | } 147 | 148 | for (s in sprites) { 149 | s.update(dt); 150 | } 151 | } 152 | } -------------------------------------------------------------------------------- /src/elke/graphics/Animation.hx: -------------------------------------------------------------------------------- 1 | package elke.graphics; 2 | 3 | import h2d.Tile; 4 | import elke.res.TileSheetRes; 5 | 6 | typedef AnimationEvent = { 7 | frame:Int, 8 | type:String, 9 | name:String, 10 | } 11 | 12 | class Animation { 13 | public var tileSheet:TileSheetRes; 14 | public var playing:Bool; 15 | public var looping:Bool; 16 | public var finished:Bool; 17 | 18 | public var currentFrame:Int = 0; 19 | public var currentAnimationName:AnimationId; 20 | 21 | var elapsedTime:Float; 22 | var totalElapsed:Float; 23 | 24 | public var events:Array; 25 | 26 | public var animationSpeed = 1.0; 27 | 28 | var onFinish : String -> Void; 29 | 30 | public function new(tileSheet) { 31 | this.tileSheet = tileSheet; 32 | events = []; 33 | } 34 | 35 | public function play(?animation:AnimationId, ?loop:Bool = true, ?force = false, ?percentage = 0.0, ?onFinish: String->Void) { 36 | if (!force) { 37 | if (playing && animation == currentAnimationName && !finished) { 38 | return; 39 | } 40 | } 41 | 42 | currentFrame = 0; 43 | finished = false; 44 | looping = loop; 45 | elapsedTime = 0.0; 46 | totalElapsed = 0.0; 47 | 48 | this.onFinish = onFinish; 49 | 50 | var anim = tileSheet.getAnimation(animation); 51 | if (animation != null && anim == null) { 52 | // throw "Could not find animation " + animation + " in sheet " + tileSheet.name; 53 | } else if (anim != null) { 54 | currentFrame = anim.from; 55 | } 56 | 57 | currentAnimationName = animation; 58 | playing = true; 59 | 60 | if (percentage > 0 && percentage < 1.0) { 61 | if (anim == null) { 62 | elapsedTime = tileSheet.totalLength / 1000.0 * percentage; 63 | } else { 64 | elapsedTime = anim.totalLength / 1000.0 * percentage; 65 | } 66 | var f = getCurrentFrame(); 67 | var s = 0; 68 | while (elapsedTime * 1000 >= f.duration) { 69 | elapsedTime -= f.duration / 1000.0; 70 | totalElapsed += f.duration / 1000.0; 71 | currentFrame++; 72 | f = getCurrentFrame(); 73 | s++; 74 | } 75 | } 76 | } 77 | 78 | /** 79 | * Returns the starting frame of a tag 80 | * @param tag 81 | */ 82 | public function getFrameByTagName(tag: String) { 83 | var t = tileSheet.getAnimation(tag); 84 | if (t == null) { 85 | return null; 86 | } 87 | 88 | return tileSheet.frames[t.from]; 89 | } 90 | 91 | public function getTilesByTagName(tag): Array { 92 | var t = tileSheet.getAnimation(tag); 93 | if (t == null) { 94 | return null; 95 | } 96 | 97 | var res = []; 98 | for (i in t.from...t.to + 1) { 99 | res.push(tileSheet.frames[i].tile); 100 | } 101 | 102 | return res; 103 | } 104 | 105 | public function getSlice(name: String) { 106 | var f = getCurrentFrame(); 107 | if (f.slices == null) { 108 | return null; 109 | } 110 | 111 | return f.slices[name]; 112 | 113 | /* 114 | var s = tileSheet.slices[name]; 115 | if (s == null) { 116 | return null; 117 | } 118 | 119 | for (k in s.keys) { 120 | if (k.frame == currentFrame) { 121 | return k.bounds; 122 | } 123 | } 124 | 125 | return null; 126 | */ 127 | } 128 | 129 | public function frameCount() { 130 | return tileSheet.frames.length; 131 | } 132 | 133 | public function getCurrentFrame() { 134 | return tileSheet.frames[currentFrame]; 135 | } 136 | 137 | public function getCurrentTile():h2d.Tile { 138 | return getCurrentFrame().tile; 139 | } 140 | 141 | public inline function getCurrentAnimation() { 142 | return this.tileSheet.getAnimation(currentAnimationName); 143 | } 144 | 145 | // Returns value between 0 - 1 of animation progress 146 | public function animationProgress():Float { 147 | var anim = tileSheet.getAnimation(currentAnimationName); 148 | return (totalElapsed * 1000) / anim.totalLength; 149 | } 150 | 151 | public function stop() { 152 | playing = false; 153 | var a = getCurrentAnimation(); 154 | if (a == null) { 155 | currentFrame = 0; 156 | } else { 157 | currentFrame = a.from; 158 | } 159 | } 160 | 161 | public function pause() { 162 | playing = false; 163 | } 164 | 165 | public function unpause() { 166 | playing = true; 167 | } 168 | 169 | public function update(dt:Float) { 170 | if (!playing) { 171 | return; 172 | } 173 | 174 | if (Game.instance.paused) { 175 | return; 176 | } 177 | 178 | var anim = tileSheet.getAnimation(currentAnimationName); 179 | 180 | var from = 0; 181 | var to = tileSheet.frames.length - 1; 182 | 183 | if (anim != null) { 184 | from = anim.from; 185 | to = anim.to; 186 | } 187 | 188 | var frame = tileSheet.frames[currentFrame]; 189 | 190 | elapsedTime += dt * animationSpeed; 191 | totalElapsed += dt * animationSpeed; 192 | 193 | if (elapsedTime * 1000 > frame.duration) { 194 | elapsedTime -= frame.duration / 1000.0; 195 | 196 | currentFrame++; 197 | 198 | if (looping) { 199 | if (currentFrame > to) { 200 | currentFrame = from; 201 | } 202 | } else { 203 | if (currentFrame > to) { 204 | currentFrame = to; 205 | finished = true; 206 | if (onFinish != null) { 207 | onFinish(currentAnimationName); 208 | onFinish = null; 209 | } 210 | } 211 | } 212 | } 213 | } 214 | } -------------------------------------------------------------------------------- /src/elke/graphics/Sprite3D.hx: -------------------------------------------------------------------------------- 1 | package elke.graphics; 2 | 3 | import h3d.mat.Pass; 4 | import h3d.Matrix; 5 | import h3d.scene.RenderContext; 6 | import h3d.mat.Material; 7 | import h3d.scene.Object; 8 | import h3d.scene.Mesh; 9 | 10 | class SpriteShader extends hxsl.Shader { 11 | static var SRC = { 12 | // Sprite size in world coords 13 | @param var spriteSize:Vec2; 14 | // Upper and lower uv coords 15 | @param var uvs:Vec4; 16 | // xy = origin, zw = tile offset 17 | @param var offset:Vec4; 18 | @param var tileSize:Vec2; 19 | @input var input:{ 20 | var position:Vec3; 21 | var normal:Vec3; 22 | var uv:Vec2; 23 | }; 24 | var relativePosition:Vec3; 25 | var transformedPosition:Vec3; 26 | var calculatedUV:Vec2; 27 | var pixelColor:Vec4; 28 | function __init__() { 29 | relativePosition.xz *= tileSize; 30 | relativePosition.xz += offset.zw; 31 | } 32 | function vertex() { 33 | var uv1 = uvs.xy; 34 | var uv2 = uvs.zw; 35 | var d = uv2 - uv1; 36 | calculatedUV = vec2(input.uv * d + uv1); 37 | } 38 | function fragment() { 39 | // pixelColor.rg = calculatedUV; 40 | } 41 | }; 42 | } 43 | 44 | @:access(h2d.Tile) 45 | class Sprite3D extends Mesh { 46 | public var animation:Animation; 47 | 48 | public var faceCamera:Bool; 49 | 50 | /** 51 | If true, will track camera on Z axis, otherwise only X and Y coordinates will be adjusted, and sprite will look forward at all times. (default: true) 52 | **/ 53 | public var faceZAxis:Bool; 54 | 55 | var plane:Plane3D; 56 | 57 | var dirty = false; 58 | 59 | public var flipX(default, set):Bool = false; 60 | public var flipY(default, set):Bool = false; 61 | 62 | /** 63 | * Horizontal origin of sprite, in pixels. 64 | * 0 = left, 1 = right 65 | */ 66 | public var originX(default, set):Int = 0; 67 | 68 | /** 69 | * Vertical origin of sprite, in pixels. 70 | * 0 = top, 1 = bottom 71 | */ 72 | public var originY(default, set):Int = 0; 73 | 74 | var ppu:Float = Const.PPU; 75 | 76 | public var removeAfterFinish = false; 77 | 78 | var onEvent:(String) -> Void; 79 | 80 | var mat:h3d.mat.Material; 81 | 82 | public function new(anim:Animation, ?parent:Object) { 83 | this.animation = anim; 84 | 85 | this.faceCamera = false; 86 | this.faceZAxis = true; 87 | this.plane = Plane3D.get(); 88 | 89 | mat = Material.create(anim.tileSheet.tile.getTexture()); 90 | mat.textureShader.killAlpha = true; 91 | mat.mainPass.addShader(new SpriteShader()); 92 | super(plane, mat, parent); 93 | } 94 | 95 | var lastTile:h2d.Tile; 96 | 97 | function refreshTile() { 98 | var t = animation.getCurrentTile(); 99 | 100 | if (!dirty && t == lastTile) { 101 | return; 102 | } 103 | 104 | dirty = false; 105 | lastTile = t; 106 | 107 | var u = !flipX ? t.u : t.u2; 108 | var u2 = !flipX ? t.u2 : t.u; 109 | var v = !flipY ? t.v : t.v2; 110 | var v2 = !flipY ? t.v2 : t.v; 111 | 112 | var ox = t.dx; 113 | var oy = t.dy; 114 | 115 | var tileSheet = animation.tileSheet; 116 | 117 | if (flipX) { 118 | ox = tileSheet.width - t.width - ox; 119 | ox -= tileSheet.width - originX; 120 | } else { 121 | ox -= originX; 122 | } 123 | 124 | if (!flipY) { 125 | oy = tileSheet.height - t.height - oy; 126 | oy -= tileSheet.height - originY; 127 | } else { 128 | oy -= originY; 129 | } 130 | 131 | var s = mat.mainPass.getShader(SpriteShader); 132 | s.uvs.set(u, v, u2, v2); 133 | s.offset.set((originX) * ppu, (tileSheet.height - originY) * ppu, // Origin X and Y 134 | ox * ppu, oy * ppu); 135 | 136 | s.spriteSize.set(tileSheet.width * ppu, tileSheet.height * ppu); 137 | s.tileSize.set(t.width * ppu, t.height * ppu); 138 | 139 | material.texture = t.getTexture(); 140 | } 141 | 142 | override private function syncRec(ctx:RenderContext) { 143 | if (animation != null) { 144 | animation.update(ctx.elapsedTime); 145 | } 146 | 147 | if (this.parent == null) { 148 | return; 149 | } 150 | 151 | refreshTile(); 152 | if (faceCamera) { 153 | var up = ctx.scene.camera.up; 154 | var vec = ctx.scene.camera.pos.sub(ctx.scene.camera.target); 155 | if (!faceZAxis) 156 | vec.z = 0; 157 | var oldX = qRot.x; 158 | var oldY = qRot.y; 159 | var oldZ = qRot.z; 160 | var oldW = qRot.w; 161 | qRot.initRotateMatrix(Matrix.lookAtX(vec, up)); 162 | if (oldX != qRot.x || oldY != qRot.y || oldZ != qRot.z || oldW != qRot.w) 163 | this.posChanged = true; 164 | } 165 | super.syncRec(ctx); 166 | } 167 | 168 | inline function set_flipX(f:Bool) { 169 | if (f != flipX) 170 | dirty = true; 171 | return flipX = f; 172 | } 173 | 174 | inline function set_flipY(f:Bool) { 175 | if (f != flipY) 176 | dirty = true; 177 | return flipY = f; 178 | } 179 | 180 | inline function set_originX(o:Int) { 181 | if (o != originX) 182 | dirty = true; 183 | return originX = o; 184 | } 185 | 186 | inline function set_originY(o:Int) { 187 | if (o != originY) 188 | dirty = true; 189 | return originY = o; 190 | } 191 | 192 | public inline function syncWith(parent:Sprite3D) { 193 | originX = parent.originX; 194 | originY = parent.originY; 195 | flipX = parent.flipX; 196 | 197 | if (animation.currentFrame != parent.animation.currentFrame) { 198 | dirty = true; 199 | } 200 | 201 | animation.currentFrame = parent.animation.currentFrame; 202 | } 203 | } -------------------------------------------------------------------------------- /src/elke/res/TileSheetRes.hx: -------------------------------------------------------------------------------- 1 | package elke.res; 2 | 3 | import h2d.Tile; 4 | import elke.graphics.Animation; 5 | import elke.graphics.AsepriteResource; 6 | import elke.graphics.Sprite; 7 | import elke.graphics.Sprite3D; 8 | 9 | typedef Frame = { 10 | offsetX:Int, 11 | offsetY:Int, 12 | tile : h2d.Tile, 13 | duration:Int, 14 | ?slices: Map, 15 | } 16 | 17 | typedef AnimationData = { 18 | name:String, 19 | from:Int, 20 | to:Int, 21 | totalLength:Int, 22 | looping:Bool, 23 | linearSpeed:Bool, 24 | frameDuration:Int 25 | } 26 | 27 | typedef TileSheetEvent = { 28 | frame : Int, 29 | type : String, 30 | name : String, 31 | } 32 | 33 | typedef Slice = { 34 | name: String, 35 | keys : Array<{ 36 | frame: Int, 37 | bounds: AseBound, 38 | }> 39 | } 40 | 41 | typedef TileSheetConfig = { 42 | events : Array, 43 | } 44 | 45 | typedef AnimationId = String; 46 | 47 | class TileSheetRes extends hxd.res.Resource { 48 | 49 | static var ENABLE_AUTO_WATCH = true; 50 | 51 | var loaded = false; 52 | public var frames : Array; 53 | public var animations:Map; 54 | 55 | public var slices: Map; 56 | 57 | public var width(default, null) : Int; 58 | public var height(default, null) : Int; 59 | 60 | public var totalLength : Int; 61 | 62 | /** 63 | * Full tile of the tilesheet 64 | */ 65 | public var tile(default, set): Tile; 66 | 67 | public var events : Array; 68 | 69 | function new(entry) { 70 | super(entry); 71 | if (entry != null) { 72 | loadData(); 73 | } 74 | } 75 | 76 | public inline function getAnimation(?animation:AnimationId) { 77 | if (animations[animation] == null) { 78 | return null; 79 | } 80 | 81 | return animations[animation]; 82 | } 83 | 84 | function watchCallb() { 85 | loadData(); 86 | } 87 | 88 | var data: AseFile; 89 | function loadData() : TileSheetRes { 90 | if (!loaded) { 91 | if(ENABLE_AUTO_WATCH) 92 | watch(watchCallb); 93 | } 94 | 95 | this.animations = new Map(); 96 | this.slices = new Map(); 97 | 98 | data = haxe.Json.parse(entry.getText()); 99 | var basePath = entry.path.substr(0, entry.path.length - ".tilesheet".length); 100 | 101 | if (hxd.res.Loader.currentInstance.exists(basePath + ".json")) { 102 | var config = hxd.res.Loader.currentInstance.load(basePath + ".json").toText(); 103 | if (config != null) { 104 | var events : TileSheetConfig = haxe.Json.parse(config); 105 | this.events = events.events; 106 | } 107 | } 108 | 109 | var tile = hxd.res.Loader.currentInstance.load(basePath + ".png").toTile(); 110 | this.tile = tile; 111 | 112 | if (data.meta.slices != null) { 113 | for (s in data.meta.slices) { 114 | slices[s.name] = { 115 | name: s.name, 116 | keys: s.keys, 117 | } 118 | } 119 | } 120 | 121 | 122 | if (data.meta.frameTags != null) { 123 | for (s in data.meta.frameTags) { 124 | animations[s.name] = s; 125 | 126 | var frameCount = s.to - s.from; 127 | s.totalLength = 0; 128 | 129 | s.looping = true; 130 | var l:Int = -1; 131 | 132 | s.linearSpeed = true; 133 | 134 | for (i in 0...frameCount + 1) { 135 | if (l == -1) { 136 | l = frames[i + s.from].duration; 137 | s.frameDuration = l; 138 | } else if (l != frames[i + s.from].duration) { 139 | s.linearSpeed = false; 140 | s.frameDuration = -1; 141 | } 142 | 143 | s.totalLength += frames[i + s.from].duration; 144 | } 145 | } 146 | } 147 | 148 | generateFrames(tile); 149 | 150 | loaded = true; 151 | 152 | return this; 153 | } 154 | 155 | function getSlicesForFrame(frame: Int) { 156 | var slices = new Map(); 157 | var empty = true; 158 | for (name => slice in this.slices) { 159 | for (s in slice.keys) { 160 | if (s.frame == frame) { 161 | slices[name] = s.bounds; 162 | empty = false; 163 | break; 164 | } 165 | } 166 | } 167 | 168 | if (empty) { 169 | return null; 170 | } 171 | 172 | return slices; 173 | } 174 | 175 | function generateFrames(tile: Tile) { 176 | width = data.frames[0].sourceSize.w; 177 | height = data.frames[0].sourceSize.h; 178 | 179 | frames = []; 180 | 181 | var frameIndex = 0; 182 | for (f in data.frames) { 183 | var dx = f.spriteSourceSize.x; 184 | var dy = f.spriteSourceSize.y; 185 | 186 | frames.push({ 187 | tile: tile.sub(f.frame.x, f.frame.y, f.frame.w, f.frame.h, dx, dy), 188 | duration: f.duration, 189 | offsetX: dx, 190 | offsetY: dy, 191 | slices: getSlicesForFrame(frameIndex), 192 | }); 193 | 194 | frameIndex ++; 195 | } 196 | 197 | totalLength = 0; 198 | var l = 0; 199 | 200 | for (f in frames) { 201 | l = f.duration; 202 | totalLength += f.duration; 203 | } 204 | 205 | return tile; 206 | } 207 | 208 | function set_tile(tile: Tile) { 209 | generateFrames(tile); 210 | return this.tile = tile; 211 | } 212 | 213 | public function toAnimation() : Animation { 214 | if (!loaded) { 215 | loadData(); 216 | } 217 | 218 | return new Animation(this); 219 | } 220 | 221 | public function toTileSheet(): TileSheetRes { 222 | if (!loaded) { 223 | loadData(); 224 | } 225 | 226 | return this; 227 | } 228 | 229 | public function toSprite3D(?parent) : Sprite3D { 230 | var anim = toAnimation(); 231 | return new Sprite3D(anim, parent); 232 | } 233 | 234 | public function toSprite2D(?parent) : Sprite { 235 | var anim = toAnimation(); 236 | return new Sprite(anim, parent); 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /src/elke/buildutil/AsepriteConverter.hx: -------------------------------------------------------------------------------- 1 | package elke.buildutil; 2 | 3 | import sys.io.File; 4 | import sys.FileSystem; 5 | import haxe.io.Path; 6 | 7 | typedef AseLayerInfo = { 8 | name : String, 9 | group : String, 10 | } 11 | 12 | typedef AseOptions = { 13 | ?layers : Array, 14 | ?hideLayers : Array, 15 | ?padding : Null, 16 | ?sheet : Null, 17 | ?trim : Null, 18 | } 19 | 20 | class AsepriteConverter { 21 | static final defaultAsePath = "C:/Program Files/Aseprite/Aseprite.exe"; 22 | static var asePath = defaultAsePath; 23 | static final ext = ".aseprite"; 24 | static final loaders = "⠇⠏⠹⠸⠼⠧"; 25 | function new() { 26 | } 27 | 28 | public static function doNothing() {} 29 | 30 | public static function exportTileSheets() { 31 | Sys.println("Generating assets..."); 32 | var startTime = Sys.time(); 33 | #if macro 34 | var def = haxe.macro.Context.definedValue("asepritePath"); 35 | #else 36 | var def : String = null; 37 | #end 38 | 39 | if (def != null) { 40 | asePath = def; 41 | } else { 42 | switch (Sys.systemName()) { 43 | case "Windows": asePath = defaultAsePath; 44 | case "Mac": asePath = "/Applications/Aseprite.app/Contents/MacOS/aseprite"; 45 | case "Linux": asePath = "aseprite"; 46 | default: asePath = defaultAsePath; 47 | } 48 | } 49 | 50 | recursiveLook("res/"); 51 | 52 | var duration = ("" + (Sys.time() - startTime)).substr(0, 4); 53 | 54 | Sys.println('\u001b[1A\u001b[2K\u001b[32mFinished\u001b[0m [${duration}s]'); 55 | } 56 | 57 | static function recursiveLook(directory) { 58 | if (sys.FileSystem.exists(directory)) { 59 | var files = sys.FileSystem.readDirectory(directory); 60 | for (file in files) { 61 | if (file == ".tmp") continue; 62 | var path = haxe.io.Path.join([directory, file]); 63 | if (!sys.FileSystem.isDirectory(path)) { 64 | if (StringTools.endsWith(path, ext)) { 65 | var absolutePath = FileSystem.absolutePath(path); 66 | var p = new Path(absolutePath); 67 | var input = p.toString(); 68 | 69 | var output = input.substr(0, input.length - ext.length); 70 | 71 | generateNormalAseFile(input, output); 72 | } 73 | } else { 74 | var directory = haxe.io.Path.addTrailingSlash(path); 75 | recursiveLook(directory); 76 | } 77 | } 78 | } 79 | } 80 | 81 | static function generateNormalAseFile(aseFilePath : String, destPath : String) { 82 | var bytes = new haxe.io.BytesInput(sys.io.File.getBytes(aseFilePath)); 83 | 84 | var size = bytes.readInt32(); 85 | var num = bytes.readUInt16() == 0xA5E0; 86 | var frames = bytes.readUInt16(); 87 | 88 | if (frames == 1) { 89 | convertAseFile(aseFilePath, destPath, { sheet: false }); 90 | } else { 91 | convertAseFile(aseFilePath, destPath); 92 | } 93 | } 94 | 95 | static var l = 0; 96 | 97 | static function convertAseFile(filePath : String, destPath : String, ?options : AseOptions = null) { 98 | var spacing = 1; 99 | 100 | var input = '-b $filePath'; 101 | var jsonOutput = '--data $destPath.tilesheet'; 102 | var pngOutput = '--sheet $destPath.png'; 103 | var format = '--format json-array'; 104 | var type = '--sheet-type packed'; 105 | var pack = '--sheet-pack'; 106 | var listTags = '--list-tags'; 107 | var padding = '--shape-padding $spacing'; 108 | var trim = '--trim'; 109 | var slices = '--list-slices'; 110 | 111 | var ignoreLayers = ''; 112 | var layers = ''; 113 | 114 | if (options != null) { 115 | if (options.layers != null) { 116 | for (layer in options.layers) { 117 | layers += ' --layer $layer'; 118 | } 119 | } 120 | if (options.hideLayers != null) { 121 | for (layer in options.hideLayers) { 122 | ignoreLayers += ' --ignore-layer $layer'; 123 | } 124 | } 125 | if (options.sheet != null) { 126 | if (!options.sheet) { 127 | jsonOutput = ''; 128 | pngOutput = ''; 129 | input += ' --save-as $destPath.png'; 130 | pack = ''; 131 | listTags = ''; 132 | trim = ''; 133 | format = ''; 134 | type = ''; 135 | } 136 | } 137 | } 138 | 139 | var cmd = '"$asePath" $jsonOutput $pngOutput $format $type $pack $padding $slices $listTags $trim $layers $ignoreLayers'; 140 | cmd += ' $input'; 141 | 142 | l = ++l % loaders.length; 143 | Sys.println('\u001b[1A\u001b[2K\u001b[35m${loaders.charAt(l)}\u001b[0m Generating $destPath'); 144 | Sys.command(cmd); 145 | } 146 | 147 | static function getAsepriteLayerData(aseFilePath : String) { 148 | var tmpFile = Sys.getCwd() + "tmp.tmp"; 149 | var cmd = '"$asePath" -b --list-layers --all-layers $aseFilePath --data $tmpFile'; 150 | Sys.command(cmd); 151 | var text = File.getContent(tmpFile).toString(); 152 | var c : elke.graphics.AsepriteResource.AseFile = haxe.Json.parse(text); 153 | FileSystem.deleteFile(tmpFile); 154 | return c.meta.layers; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/elke/graphics/Transition.hx: -------------------------------------------------------------------------------- 1 | package elke.graphics; 2 | 3 | import h3d.pass.ScreenFx; 4 | import h2d.filter.Filter; 5 | import h2d.Interactive; 6 | import h2d.RenderContext; 7 | import h2d.Bitmap; 8 | import h2d.Tile; 9 | import hxd.Math; 10 | 11 | class WipeShader extends h3d.shader.ScreenShader { 12 | static var SRC = { 13 | @param var texture : Sampler2D; 14 | /** 15 | * t goes from 0-1 (fading in) to 1-2 (fading out) 16 | */ 17 | @param var t:Float; 18 | @param var screenSize:Vec2; 19 | @param var wipeColor:Vec3; 20 | function fragment() { 21 | var c = vec4(wipeColor, 0.0); 22 | var a = 1.0 - abs(t - 1.0); 23 | c.a = a; 24 | output.color = c; 25 | } 26 | }; 27 | } 28 | 29 | class CircleWipeShader extends WipeShader { 30 | static var SRC = { 31 | function fragment() { 32 | var uv = (input.uv - 0.5) * 2.0; 33 | 34 | var d = length(vec2(1.0, (screenSize.y / screenSize.x))); 35 | var p = length(vec2(uv.x, uv.y * (screenSize.y / screenSize.x))); 36 | 37 | var sign = clamp((2 * floor(t)) - 1., -1, 1); 38 | var mult = (1.0 - floor(t)); 39 | 40 | var tMod = mod(t, 1); 41 | tMod -= 0.01; 42 | 43 | var p2 = tMod * d; 44 | p2 *= 1.02; 45 | 46 | var l = mult + sign * clamp((p - p2) * 129.9, 0, 1); 47 | 48 | var c = wipeColor; 49 | c *= l; 50 | 51 | output.color = vec4(c, l); 52 | } 53 | }; 54 | } 55 | 56 | class SideWipeShader extends WipeShader { 57 | static var SRC = { 58 | function fragment() { 59 | var uv = (input.uv - 0.5) * 2.0; 60 | 61 | var d = length(vec2(1.0, (screenSize.y / screenSize.x))); 62 | 63 | var sign = clamp((2 * floor(t)) - 1., -1, 1); 64 | var mult = (1.0 - floor(t)); 65 | 66 | var tMod = mod(t, 1); 67 | tMod -= 0.01; 68 | 69 | var p2 = tMod * d; 70 | p2 *= 1.02; 71 | 72 | var l = mult + sign * clamp(((input.uv.x + input.uv.y * 0.1) - tMod * 1.2) * 229.9, 0, 1); 73 | 74 | var c = wipeColor; 75 | c *= l; 76 | 77 | //output.color = texture.get(input.uv) * vec4(c, 1); 78 | output.color = vec4(c, l); 79 | } 80 | }; 81 | } 82 | 83 | class TransitionFilter extends Filter { 84 | /** 85 | * Progress goes from 0-1 (fading in) to 1-2 (fading out) 86 | */ 87 | public var progress:Float; 88 | public var wipeFilter:ScreenFx; 89 | public var shader: WipeShader; 90 | 91 | public function new() { 92 | super(); 93 | shader = new WipeShader(); 94 | wipeFilter = new ScreenFx(shader); 95 | } 96 | 97 | public function setWipeShader(s: WipeShader) { 98 | wipeFilter = new ScreenFx(s); 99 | } 100 | 101 | override function draw(ctx:RenderContext, t:h2d.Tile) { 102 | var out = ctx.textures.allocTileTarget("transitionOutput", t); 103 | ctx.engine.pushTarget(out); 104 | var s = wipeFilter.shader; 105 | s.screenSize.set(ctx.scene.width, ctx.scene.height); 106 | s.t = progress; 107 | s.texture = t.getTexture(); 108 | wipeFilter.render(); 109 | ctx.engine.popTarget(); 110 | return h2d.Tile.fromTexture(out); 111 | } 112 | } 113 | 114 | class Transition extends Interactive { 115 | var graphics:Bitmap; 116 | public var f:TransitionFilter; 117 | 118 | var alphaFade = false; 119 | 120 | public function new(?parent, color = 0x000000) { 121 | super(1, 1, parent); 122 | graphics = new Bitmap(Tile.fromColor(color), this); 123 | if (!alphaFade) { 124 | f = new TransitionFilter(); 125 | f.shader.wipeColor.set( 126 | (color >> 16 & 255) / 255, 127 | (color >> 8 & 255) / 255, 128 | (color >> 0 & 255) / 255 129 | ); 130 | //Game.instance.uiScene.filter = f; 131 | graphics.filter = f; 132 | } 133 | 134 | cursor = Default; 135 | } 136 | 137 | var scalingIn = false; 138 | var scalingOut = false; 139 | 140 | override function draw(ctx:RenderContext) { 141 | super.draw(ctx); 142 | if (auto) { 143 | parent.addChild(this); 144 | } 145 | } 146 | 147 | public var inTime = .5; 148 | public var outTime = .6; 149 | 150 | var t = 0.0; 151 | 152 | override function sync(ctx:RenderContext) { 153 | super.sync(ctx); 154 | 155 | var s = getScene(); 156 | if (s == null) { 157 | return; 158 | } 159 | 160 | width = s.width; 161 | height = s.height; 162 | 163 | var eased = 0.0; 164 | 165 | if (scalingIn) { 166 | t += ctx.elapsedTime / inTime; 167 | if (t >= 1) { 168 | scalingIn = false; 169 | var finish = onFinish; 170 | onFinish = null; 171 | if (finish != null) { 172 | Game.instance.dispatchCommand(finish); 173 | } 174 | if (auto) { 175 | t = 2.3; 176 | scalingOut = true; 177 | } 178 | } 179 | } else if (scalingOut) { 180 | t -= ctx.elapsedTime / outTime; 181 | if (t <= 0) { 182 | scalingOut = false; 183 | remove(); 184 | //Game.instance.uiScene.filter = null; 185 | if (onFinish != null) { 186 | onFinish(); 187 | } 188 | } 189 | } 190 | 191 | eased = elke.T.smootherStepInOut(Math.min(1, Math.max(0, t))); 192 | if (scalingOut) { 193 | if (alphaFade) { 194 | eased = (eased); 195 | } else { 196 | eased = 1 + (1 - eased); 197 | } 198 | } 199 | 200 | graphics.width = width; 201 | graphics.height = height; 202 | 203 | f.progress = eased; 204 | } 205 | 206 | var onFinish:Void->Void; 207 | var auto = true; 208 | 209 | public function show(?onFinish:Void->Void, auto = true) { 210 | scalingIn = true; 211 | this.auto = auto; 212 | this.onFinish = onFinish; 213 | } 214 | 215 | public function hide(?onFinish:Void->Void, reset = false) { 216 | this.onFinish = onFinish; 217 | scalingOut = true; 218 | if (reset) { 219 | t = 1.0; 220 | } 221 | } 222 | 223 | public static function to(onFinish:Void->Void, inTime = 0.5, outTime = 0.6, color = 0x000000) { 224 | var t = new Transition(Game.instance.s2d, color); 225 | t.inTime = inTime; 226 | t.outTime = outTime; 227 | t.show(onFinish); 228 | return t; 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /res/fonts/m5x7_medium_12.fnt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /res/fonts/equipmentpro_medium_12.fnt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /res/fonts/futilepro_medium_12.fnt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /src/elke/utils/CellGraph.hx: -------------------------------------------------------------------------------- 1 | package elke.utils; 2 | 3 | class Neighbor { 4 | public var node: GraphNode; 5 | public var cost: Float; 6 | } 7 | 8 | class GraphNode { 9 | public var x = 0.; 10 | public var y = 0.; 11 | public var width = 0.; 12 | public var height = 0.; 13 | public var neighbors: Array; 14 | 15 | public var centerX = 0.; 16 | public var centerY = 0.; 17 | public var halfWidth = 0.; 18 | public var halfHeight = 0.; 19 | 20 | public var cY = 0; 21 | public var cX = 0; 22 | public var cW = 1; 23 | public var cH = 1; 24 | 25 | public var id = 0; 26 | static var _next = 0; 27 | 28 | public function new(x = 0., y = 0., w = 0., h = 0.) { 29 | this.x = x; 30 | this.y = y; 31 | this.width = w; 32 | this.height = h; 33 | id = _next ++; 34 | 35 | halfWidth = width * 0.5; 36 | halfHeight = height * 0.5; 37 | centerX = x + halfWidth; 38 | centerY = y += halfHeight; 39 | 40 | neighbors = []; 41 | } 42 | 43 | public function getCenterX() { 44 | return x + width * 0.5; 45 | } 46 | public function getCenterY() { 47 | return y + height * 0.5; 48 | } 49 | 50 | public function hasNeighbor(cell: GraphNode) { 51 | for (c in neighbors) { 52 | if (c == cell) return true; 53 | } 54 | 55 | return false; 56 | } 57 | } 58 | 59 | class CellGraph { 60 | /* 61 | var intGrid: Map; 62 | var tileSize: Int; 63 | var width: Int; 64 | var height: Int; 65 | var filter : Int; 66 | */ 67 | 68 | var maxCSize = 1; 69 | 70 | var cellPxSize = 32; 71 | 72 | public var cells: Array = []; 73 | var bucket:SpatialBuckets; 74 | 75 | public function new(tileSize = 32) { 76 | /* 77 | this.intGrid = intGrid; 78 | this.width = width; 79 | this.height = height; 80 | this.tileSize = tileSize; 81 | this.filter = filter; 82 | */ 83 | 84 | bucket = new SpatialBuckets(maxCSize * tileSize, 64 * tileSize, 64 * tileSize); 85 | } 86 | 87 | var tempAABB = new AABB(); 88 | public function cellAtPos(x: Float, y: Float, radius = 4.) { 89 | var cs = bucket.getPotentialCollisionsCircle(x, y, radius); 90 | 91 | tempAABB.x = x - radius; 92 | tempAABB.y = y - radius; 93 | tempAABB.width = radius * 2; 94 | tempAABB.height = radius * 2; 95 | for (g in cs) { 96 | if (tempAABB.testRect(g.x, g.y, g.width, g.height)) { 97 | return g; 98 | } 99 | } 100 | 101 | return null; 102 | } 103 | 104 | public function getClosestCell(x: Float, y: Float, maxDistance = 100.) { 105 | var cs = bucket.getPotentialCollisionsCircle(x, y, maxDistance * 0.5); 106 | var minLenSq = Math.POSITIVE_INFINITY; 107 | var dx = 0.; 108 | var dy = 0.; 109 | var closest = null; 110 | for (g in cs) { 111 | dx = x - g.centerX; 112 | dy = y - g.centerY; 113 | var lSq = dx * dx + dy * dy; 114 | if (lSq < minLenSq) { 115 | closest = g; 116 | minLenSq = lSq; 117 | } 118 | } 119 | 120 | return closest; 121 | } 122 | 123 | public function tilemapToGraph(intGrid: Map, tileSize: Int, width: Int, height: Int, filter = 0) { 124 | inline function getCoordId(cx,cy) return cx+cy*width; 125 | inline function isCoordValid(cx,cy) { 126 | return cx>=0 && cx=0 && cy = []; 133 | var a = new AABB(); 134 | 135 | inline function addCell(cx, cy, cw, ch) { 136 | cells.push(new GraphNode(cx * tileSize, cy * tileSize, cw * tileSize, ch * tileSize)); 137 | } 138 | 139 | a.width = a.height = tileSize - 2; 140 | 141 | for (y in 0...height) { 142 | for (x in 0...width) { 143 | if (getInt(x, y) != filter) { 144 | continue; 145 | } 146 | 147 | a.x = tileSize * x + 1; 148 | a.y = tileSize * y + 1; 149 | 150 | var exists = false; 151 | for (c in cells) { 152 | if (a.testRect(c.x, c.y, c.width, c.height)) { 153 | exists = true; 154 | break; 155 | } 156 | } 157 | 158 | if (exists) { 159 | continue; 160 | } 161 | 162 | var availableH = 0; 163 | var availableV = 0; 164 | 165 | var broken = false; 166 | while(!broken && availableH < maxCSize) { 167 | if (!isCoordValid(x + availableH + 1, y) || getInt(x + availableH + 1, y) != filter) { 168 | broken = true; 169 | break; 170 | } 171 | 172 | availableH ++; 173 | } 174 | 175 | broken = false; 176 | while(!broken && availableV < maxCSize) { 177 | if (!isCoordValid(x, y + availableV) || getInt(x, y + availableV) != filter) { 178 | broken = true; 179 | break; 180 | } 181 | 182 | availableV ++; 183 | } 184 | 185 | 186 | var expandX = true;//availableV > availableH; 187 | // Expand square horizontally 188 | if (expandX) { 189 | availableH = 0; 190 | broken = false; 191 | while (!broken && availableH < maxCSize) { 192 | var nx = x + availableH + 1; 193 | for (ny in y...(y + availableV)) { 194 | 195 | a.x = nx * tileSize + 1; 196 | a.y = ny * tileSize + 1; 197 | for (c in cells) { 198 | if (a.testRect(c.x, c.y, c.width, c.height)) { 199 | broken = true; 200 | break; 201 | } 202 | } 203 | 204 | if (broken) break; 205 | 206 | if (!isCoordValid(nx, ny) || getInt(nx, ny) != filter) { 207 | broken = true; 208 | break; 209 | } 210 | } 211 | 212 | if (!broken) { 213 | availableH ++; 214 | } 215 | } 216 | 217 | //availableH += 1; 218 | 219 | } else { // Expand vertically 220 | availableV = 0; 221 | broken = false; 222 | while (!broken) { 223 | var ny = y + availableV + 1; 224 | for (nx in x...(x + availableH)) { 225 | a.x = nx * tileSize + 1; 226 | a.y = ny * tileSize + 1; 227 | for (c in cells) { 228 | if (a.testRect(c.x, c.y, c.width, c.height)) { 229 | broken = true; 230 | break; 231 | } 232 | } 233 | 234 | if (broken) break; 235 | 236 | if (!isCoordValid(nx, ny) || getInt(nx, ny) != filter) { 237 | broken = true; 238 | break; 239 | } 240 | } 241 | 242 | if (!broken) { 243 | availableV ++; 244 | } 245 | } 246 | 247 | availableV += 1; 248 | } 249 | 250 | if (availableV == 0) availableV = 1; 251 | if (availableH == 0) availableH = 1; 252 | 253 | addCell(x, y, availableH, availableV); 254 | } 255 | } 256 | 257 | this.cells = cells; 258 | 259 | connectCells(cells, tileSize); 260 | 261 | for (c in cells) { 262 | bucket.add(c, c.x, c.y, c.width, c.height); 263 | } 264 | 265 | return cells; 266 | } 267 | 268 | function connectCells(cells: Array, tileSize) { 269 | var a = new AABB(); 270 | 271 | for (c in cells) { 272 | a.x = c.x; 273 | a.y = c.y; 274 | a.width = c.width; 275 | a.height = c.height; 276 | 277 | for (c2 in cells) { 278 | if (c == c2) continue; 279 | if (c2.hasNeighbor(c)) { 280 | continue; 281 | } 282 | 283 | // Remove diagonal connections 284 | if (Math.abs(Math.abs(c.centerX - c2.centerX) - Math.abs(c.centerY - c2.centerY)) < tileSize) { 285 | continue; 286 | } 287 | 288 | if (a.testRect(c2.x, c2.y, c2.width, c2.height)) { 289 | c.neighbors.push(c2); 290 | c2.neighbors.push(c); 291 | } 292 | } 293 | } 294 | } 295 | } -------------------------------------------------------------------------------- /src/elke/things/Newgrounds.hx: -------------------------------------------------------------------------------- 1 | package elke.things; 2 | 3 | import hxd.Save; 4 | import haxe.Timer; 5 | 6 | #if js 7 | import io.newgrounds.NGLite; 8 | #end 9 | 10 | typedef HighScorePost = { 11 | name: String, 12 | scoreRaw: Int, 13 | score: String, 14 | } 15 | 16 | private class NewgroundsData { 17 | public var failedMedalUnlocks: Array = []; 18 | public var failedHighscorePosts: Array<{ boardID: Int, score: Int }> = []; 19 | 20 | public function new() { 21 | failedMedalUnlocks = []; 22 | failedHighscorePosts = []; 23 | } 24 | } 25 | 26 | /** 27 | * Simple class for managing newgrounds leaderboards and medals 28 | */ 29 | class Newgrounds { 30 | public static var instance(get, null) : Newgrounds; 31 | 32 | public var username: String = null; 33 | public var sessionId : String = null; 34 | public var signedIn = false; 35 | 36 | public var isLocal = false; 37 | 38 | final heartbeatTime = 3 * 60 * 1000; 39 | var heartbeatTimer: Timer = null; 40 | 41 | static final APP_ID = Const.NEWGROUNDS_APP_ID; 42 | static final ENCRYPTION_KEY_RC4 = Const.NEWGROUNDS_ENCRYPTION_KEY_RC4; 43 | 44 | static var _instance: Newgrounds = null; 45 | 46 | public var hasFailedCalls(get, null) = false; 47 | function get_hasFailedCalls() { 48 | var d = getFailedCalls(); 49 | return d.failedHighscorePosts.length > 0 || d.failedMedalUnlocks.length > 0; 50 | } 51 | 52 | function new() {} 53 | 54 | #if debug 55 | final debug = true; 56 | #else 57 | final debug = false; 58 | #end 59 | 60 | public static function initializeAndLogin(?onSuccess : Void -> Void, ?onFail: Void -> Void) { 61 | if (_instance != null) { 62 | if (onSuccess != null) { 63 | onSuccess(); 64 | } 65 | 66 | return _instance; 67 | } 68 | 69 | trace("Creating new newgrounds API"); 70 | try { 71 | _instance = new Newgrounds(); 72 | _instance.init(onSuccess, onFail); 73 | } catch (e) { 74 | trace(e); 75 | } 76 | 77 | return _instance; 78 | } 79 | 80 | public function signOut() { 81 | signedIn = false; 82 | #if js 83 | NGLite.core.calls.app.endSession(); 84 | #end 85 | } 86 | 87 | function init(?onSuccess, ?onFail) { 88 | #if js 89 | var onLogin = () -> { 90 | signedIn = true; 91 | sessionId = NGLite.getSessionId(); 92 | 93 | trace('Logged in as $username with session ID $sessionId'); 94 | 95 | // Start heartbeat 96 | if (heartbeatTimer != null) { 97 | heartbeatTimer.stop(); 98 | } 99 | 100 | checkFailedMedalsAndUnlocks(); 101 | 102 | loadMedalsAndScoreboards(() -> { 103 | if (onSuccess != null) { 104 | onSuccess(); 105 | } 106 | }); 107 | 108 | heartbeatTimer = Timer.delay(heartbeat, heartbeatTime); 109 | } 110 | 111 | var curSessionId = NGLite.getSessionId(); 112 | if (curSessionId == null || curSessionId == "") { 113 | if (onSuccess != null) { 114 | onSuccess(); 115 | } 116 | return; 117 | } 118 | 119 | NGLite.create(APP_ID, NGLite.getSessionId(), (e) -> { 120 | if (onFail != null) { 121 | onFail(); 122 | } 123 | }); 124 | 125 | NGLite.core.initEncryption(ENCRYPTION_KEY_RC4); 126 | 127 | NGLite.core.calls.app.checkSession() 128 | .addDataHandler(data -> { 129 | this.username = data.result.data.session.user.name; 130 | onLogin(); 131 | }) 132 | .addErrorHandler(e -> { 133 | if (onFail != null) onFail(); 134 | }) 135 | .send(); 136 | 137 | #else 138 | isLocal = true; 139 | if (onSuccess != null) { 140 | onSuccess(); 141 | } 142 | #end 143 | } 144 | 145 | final ngDataSave = '${Const.SAVE_NAMESPACE}_ng'; 146 | inline function getFailedCalls() { 147 | return Save.load(new NewgroundsData(), ngDataSave, !debug); 148 | } 149 | inline function saveFailedCalls(d: NewgroundsData) { 150 | Save.save(d, ngDataSave, !debug); 151 | } 152 | 153 | function checkFailedMedalsAndUnlocks() { 154 | #if js 155 | var data = getFailedCalls(); 156 | Save.delete(ngDataSave); 157 | 158 | if (data.failedHighscorePosts != null) { 159 | var highScores = data.failedHighscorePosts; 160 | for (d in highScores) { 161 | submitHighscore(d.boardID, d.score); 162 | } 163 | } 164 | 165 | if (data.failedMedalUnlocks != null) { 166 | for (m in data.failedMedalUnlocks) { 167 | unlockMedal(m); 168 | } 169 | } 170 | #end 171 | } 172 | 173 | public function getTop10Scoreboard(boardID: Int, onComplete: Array -> Void, user: String = null) { 174 | #if js 175 | if (!signedIn) { 176 | onComplete([]); 177 | return; 178 | } 179 | 180 | NGLite.core.calls.scoreBoard 181 | .getScores(boardID, 10, 0, ALL, false, null, user) 182 | .addDataHandler(data -> { 183 | var res = data.result.data.scores.map((s) -> ({ 184 | name: s.user.name, 185 | score: s.formattedValue, 186 | scoreRaw: s.value, 187 | })); 188 | onComplete(res); 189 | }) 190 | .addErrorHandler(error -> { 191 | onComplete([]); 192 | }) 193 | .send(); 194 | 195 | #else 196 | onComplete([]); 197 | #end 198 | } 199 | 200 | public function heartbeat() { 201 | #if js 202 | if (heartbeatTimer != null) { 203 | heartbeatTimer.stop(); 204 | } 205 | 206 | NGLite.core.calls.gateway.ping().addSuccessHandler(() -> { 207 | trace("Heartbeat success"); 208 | heartbeatTimer = Timer.delay(heartbeat, heartbeatTime); 209 | }).addErrorHandler((error) -> { 210 | trace("Heartbeat failure, session invalid"); 211 | signedIn = false; 212 | }).send(); 213 | #end 214 | } 215 | 216 | public var unlockedMedals: Map = new Map(); 217 | function loadMedalsAndScoreboards(?onComplete: Void -> Void) { 218 | #if js 219 | if (!signedIn) { 220 | if (onComplete != null) { 221 | onComplete(); 222 | } 223 | return; 224 | } 225 | 226 | var medalsPromise = new js.lib.Promise((resolve, reject) -> { 227 | NGLite.core.calls.medal.getList() 228 | .addDataHandler(h -> { 229 | for (m in h.result.data.medals) { 230 | unlockedMedals[m.id] = m.unlocked; 231 | } 232 | resolve(true); 233 | }) 234 | .addErrorHandler(e -> { 235 | reject(e); 236 | }) 237 | .send(); 238 | }); 239 | 240 | js.lib.Promise.all([ 241 | medalsPromise, 242 | //boardsPromise, 243 | ]).then(res -> { 244 | if (onComplete != null) { 245 | onComplete(); 246 | } 247 | }, err -> { 248 | if (onComplete != null) { 249 | onComplete(); 250 | } 251 | }); 252 | #end 253 | } 254 | 255 | public function unlockMedal(medalID: Int) { 256 | #if js 257 | if (!signedIn) { 258 | return; 259 | } 260 | 261 | if (unlockedMedals.exists(medalID) && unlockedMedals[medalID]) { 262 | return; 263 | } 264 | 265 | var posted = false; 266 | function failedPosting() { 267 | if (posted) { 268 | return; 269 | } 270 | 271 | posted = true; 272 | 273 | var d = getFailedCalls(); 274 | d.failedMedalUnlocks.push(medalID); 275 | saveFailedCalls(d); 276 | trace(d); 277 | } 278 | 279 | NGLite.core.calls.medal.unlock(medalID) 280 | .addSuccessHandler(() -> { 281 | unlockedMedals[medalID] = true; 282 | }) 283 | .addDataHandler(r -> { 284 | if (!r.result.success || !r.result.data.success) { 285 | failedPosting(); 286 | } 287 | }) 288 | .addErrorHandler(e -> { 289 | failedPosting(); 290 | }) 291 | .send(); 292 | #end 293 | } 294 | 295 | public function submitTimeHighscore(scoreboardID: Int, seconds: Float) { 296 | var timeMillisecs = Std.int(seconds * 1000); 297 | submitHighscore(scoreboardID, timeMillisecs); 298 | } 299 | 300 | public function submitHighscore(scoreboardID: Int, totalScore: Int) { 301 | #if js 302 | if (!signedIn) { 303 | return; 304 | } 305 | 306 | var posted = false; 307 | function failedPosting(){ 308 | if (posted) { 309 | return; 310 | } 311 | posted = true; 312 | 313 | var d = getFailedCalls(); 314 | d.failedHighscorePosts.push({ 315 | boardID: scoreboardID, 316 | score: totalScore, 317 | }); 318 | 319 | saveFailedCalls(d); 320 | } 321 | 322 | NGLite.core.calls.scoreBoard 323 | .postScore(scoreboardID, totalScore) 324 | .addErrorHandler(e -> { 325 | failedPosting(); 326 | }) 327 | .addDataHandler(r -> { 328 | if (!r.result.success || !r.result.data.success) { 329 | failedPosting(); 330 | } 331 | }) 332 | .send(); 333 | #end 334 | } 335 | 336 | public static function get_instance() { 337 | if (_instance == null) { 338 | initializeAndLogin(); 339 | } 340 | 341 | return _instance; 342 | } 343 | } -------------------------------------------------------------------------------- /res/img/boom.tilesheet: -------------------------------------------------------------------------------- 1 | { "frames": [ 2 | { 3 | "filename": "boom 0.aseprite", 4 | "frame": { "x": 0, "y": 39, "w": 14, "h": 20 }, 5 | "rotated": false, 6 | "trimmed": true, 7 | "spriteSourceSize": { "x": 13, "y": 5, "w": 14, "h": 20 }, 8 | "sourceSize": { "w": 32, "h": 32 }, 9 | "duration": 50 10 | }, 11 | { 12 | "filename": "boom 1.aseprite", 13 | "frame": { "x": 58, "y": 19, "w": 18, "h": 16 }, 14 | "rotated": false, 15 | "trimmed": true, 16 | "spriteSourceSize": { "x": 9, "y": 9, "w": 18, "h": 16 }, 17 | "sourceSize": { "w": 32, "h": 32 }, 18 | "duration": 50 19 | }, 20 | { 21 | "filename": "boom 2.aseprite", 22 | "frame": { "x": 32, "y": 53, "w": 17, "h": 12 }, 23 | "rotated": false, 24 | "trimmed": true, 25 | "spriteSourceSize": { "x": 9, "y": 13, "w": 17, "h": 12 }, 26 | "sourceSize": { "w": 32, "h": 32 }, 27 | "duration": 50 28 | }, 29 | { 30 | "filename": "boom 3.aseprite", 31 | "frame": { "x": 62, "y": 0, "w": 17, "h": 18 }, 32 | "rotated": false, 33 | "trimmed": true, 34 | "spriteSourceSize": { "x": 9, "y": 7, "w": 17, "h": 18 }, 35 | "sourceSize": { "w": 32, "h": 32 }, 36 | "duration": 50 37 | }, 38 | { 39 | "filename": "boom 4.aseprite", 40 | "frame": { "x": 17, "y": 19, "w": 17, "h": 17 }, 41 | "rotated": false, 42 | "trimmed": true, 43 | "spriteSourceSize": { "x": 9, "y": 7, "w": 17, "h": 17 }, 44 | "sourceSize": { "w": 32, "h": 32 }, 45 | "duration": 50 46 | }, 47 | { 48 | "filename": "boom 5.aseprite", 49 | "frame": { "x": 17, "y": 37, "w": 19, "h": 15 }, 50 | "rotated": false, 51 | "trimmed": true, 52 | "spriteSourceSize": { "x": 9, "y": 7, "w": 19, "h": 15 }, 53 | "sourceSize": { "w": 32, "h": 32 }, 54 | "duration": 50 55 | }, 56 | { 57 | "filename": "boom 6.aseprite", 58 | "frame": { "x": 37, "y": 37, "w": 19, "h": 15 }, 59 | "rotated": false, 60 | "trimmed": true, 61 | "spriteSourceSize": { "x": 9, "y": 7, "w": 19, "h": 15 }, 62 | "sourceSize": { "w": 32, "h": 32 }, 63 | "duration": 50 64 | }, 65 | { 66 | "filename": "boom 7.aseprite", 67 | "frame": { "x": 41, "y": 0, "w": 20, "h": 16 }, 68 | "rotated": false, 69 | "trimmed": true, 70 | "spriteSourceSize": { "x": 8, "y": 9, "w": 20, "h": 16 }, 71 | "sourceSize": { "w": 32, "h": 32 }, 72 | "duration": 50 73 | }, 74 | { 75 | "filename": "boom 8.aseprite", 76 | "frame": { "x": 0, "y": 0, "w": 20, "h": 18 }, 77 | "rotated": false, 78 | "trimmed": true, 79 | "spriteSourceSize": { "x": 8, "y": 8, "w": 20, "h": 18 }, 80 | "sourceSize": { "w": 32, "h": 32 }, 81 | "duration": 50 82 | }, 83 | { 84 | "filename": "boom 9.aseprite", 85 | "frame": { "x": 58, "y": 36, "w": 16, "h": 18 }, 86 | "rotated": false, 87 | "trimmed": true, 88 | "spriteSourceSize": { "x": 11, "y": 8, "w": 16, "h": 18 }, 89 | "sourceSize": { "w": 32, "h": 32 }, 90 | "duration": 50 91 | }, 92 | { 93 | "filename": "boom 10.aseprite", 94 | "frame": { "x": 21, "y": 0, "w": 19, "h": 18 }, 95 | "rotated": false, 96 | "trimmed": true, 97 | "spriteSourceSize": { "x": 8, "y": 8, "w": 19, "h": 18 }, 98 | "sourceSize": { "w": 32, "h": 32 }, 99 | "duration": 50 100 | }, 101 | { 102 | "filename": "boom 11.aseprite", 103 | "frame": { "x": 41, "y": 17, "w": 16, "h": 19 }, 104 | "rotated": false, 105 | "trimmed": true, 106 | "spriteSourceSize": { "x": 8, "y": 7, "w": 16, "h": 19 }, 107 | "sourceSize": { "w": 32, "h": 32 }, 108 | "duration": 50 109 | }, 110 | { 111 | "filename": "boom 12.aseprite", 112 | "frame": { "x": 0, "y": 19, "w": 16, "h": 19 }, 113 | "rotated": false, 114 | "trimmed": true, 115 | "spriteSourceSize": { "x": 8, "y": 7, "w": 16, "h": 19 }, 116 | "sourceSize": { "w": 32, "h": 32 }, 117 | "duration": 50 118 | }, 119 | { 120 | "filename": "boom 13.aseprite", 121 | "frame": { "x": 15, "y": 53, "w": 16, "h": 15 }, 122 | "rotated": false, 123 | "trimmed": true, 124 | "spriteSourceSize": { "x": 8, "y": 11, "w": 16, "h": 15 }, 125 | "sourceSize": { "w": 32, "h": 32 }, 126 | "duration": 50 127 | }, 128 | { 129 | "filename": "boom 14.aseprite", 130 | "frame": { "x": 50, "y": 55, "w": 16, "h": 12 }, 131 | "rotated": false, 132 | "trimmed": true, 133 | "spriteSourceSize": { "x": 8, "y": 11, "w": 16, "h": 12 }, 134 | "sourceSize": { "w": 32, "h": 32 }, 135 | "duration": 50 136 | }, 137 | { 138 | "filename": "boom 15.aseprite", 139 | "frame": { "x": 32, "y": 66, "w": 16, "h": 11 }, 140 | "rotated": false, 141 | "trimmed": true, 142 | "spriteSourceSize": { "x": 8, "y": 12, "w": 16, "h": 11 }, 143 | "sourceSize": { "w": 32, "h": 32 }, 144 | "duration": 50 145 | }, 146 | { 147 | "filename": "boom 16.aseprite", 148 | "frame": { "x": 67, "y": 55, "w": 10, "h": 8 }, 149 | "rotated": false, 150 | "trimmed": true, 151 | "spriteSourceSize": { "x": 11, "y": 12, "w": 10, "h": 8 }, 152 | "sourceSize": { "w": 32, "h": 32 }, 153 | "duration": 50 154 | }, 155 | { 156 | "filename": "boom 17.aseprite", 157 | "frame": { "x": 0, "y": 60, "w": 10, "h": 8 }, 158 | "rotated": false, 159 | "trimmed": true, 160 | "spriteSourceSize": { "x": 11, "y": 12, "w": 10, "h": 8 }, 161 | "sourceSize": { "w": 32, "h": 32 }, 162 | "duration": 50 163 | }, 164 | { 165 | "filename": "boom 18.aseprite", 166 | "frame": { "x": 35, "y": 19, "w": 6, "h": 8 }, 167 | "rotated": false, 168 | "trimmed": true, 169 | "spriteSourceSize": { "x": 14, "y": 11, "w": 6, "h": 8 }, 170 | "sourceSize": { "w": 32, "h": 32 }, 171 | "duration": 50 172 | }, 173 | { 174 | "filename": "boom 19.aseprite", 175 | "frame": { "x": 57, "y": 68, "w": 6, "h": 4 }, 176 | "rotated": false, 177 | "trimmed": true, 178 | "spriteSourceSize": { "x": 14, "y": 15, "w": 6, "h": 4 }, 179 | "sourceSize": { "w": 32, "h": 32 }, 180 | "duration": 50 181 | }, 182 | { 183 | "filename": "boom 20.aseprite", 184 | "frame": { "x": 77, "y": 19, "w": 3, "h": 3 }, 185 | "rotated": false, 186 | "trimmed": true, 187 | "spriteSourceSize": { "x": 15, "y": 15, "w": 3, "h": 3 }, 188 | "sourceSize": { "w": 32, "h": 32 }, 189 | "duration": 50 190 | }, 191 | { 192 | "filename": "boom 21.aseprite", 193 | "frame": { "x": 35, "y": 28, "w": 5, "h": 5 }, 194 | "rotated": false, 195 | "trimmed": true, 196 | "spriteSourceSize": { "x": 14, "y": 14, "w": 5, "h": 5 }, 197 | "sourceSize": { "w": 32, "h": 32 }, 198 | "duration": 50 199 | }, 200 | { 201 | "filename": "boom 22.aseprite", 202 | "frame": { "x": 75, "y": 36, "w": 5, "h": 5 }, 203 | "rotated": false, 204 | "trimmed": true, 205 | "spriteSourceSize": { "x": 14, "y": 14, "w": 5, "h": 5 }, 206 | "sourceSize": { "w": 32, "h": 32 }, 207 | "duration": 50 208 | }, 209 | { 210 | "filename": "boom 23.aseprite", 211 | "frame": { "x": 67, "y": 64, "w": 7, "h": 5 }, 212 | "rotated": false, 213 | "trimmed": true, 214 | "spriteSourceSize": { "x": 13, "y": 14, "w": 7, "h": 5 }, 215 | "sourceSize": { "w": 32, "h": 32 }, 216 | "duration": 50 217 | }, 218 | { 219 | "filename": "boom 24.aseprite", 220 | "frame": { "x": 49, "y": 68, "w": 7, "h": 5 }, 221 | "rotated": false, 222 | "trimmed": true, 223 | "spriteSourceSize": { "x": 13, "y": 14, "w": 7, "h": 5 }, 224 | "sourceSize": { "w": 32, "h": 32 }, 225 | "duration": 50 226 | }, 227 | { 228 | "filename": "boom 25.aseprite", 229 | "frame": { "x": 77, "y": 23, "w": 1, "h": 6 }, 230 | "rotated": false, 231 | "trimmed": true, 232 | "spriteSourceSize": { "x": 16, "y": 14, "w": 1, "h": 6 }, 233 | "sourceSize": { "w": 32, "h": 32 }, 234 | "duration": 50 235 | }, 236 | { 237 | "filename": "boom 26.aseprite", 238 | "frame": { "x": 58, "y": 17, "w": 1, "h": 1 }, 239 | "rotated": false, 240 | "trimmed": true, 241 | "spriteSourceSize": { "x": 16, "y": 19, "w": 1, "h": 1 }, 242 | "sourceSize": { "w": 32, "h": 32 }, 243 | "duration": 50 244 | }, 245 | { 246 | "filename": "boom 27.aseprite", 247 | "frame": { "x": 58, "y": 17, "w": 1, "h": 1 }, 248 | "rotated": false, 249 | "trimmed": true, 250 | "spriteSourceSize": { "x": 16, "y": 19, "w": 1, "h": 1 }, 251 | "sourceSize": { "w": 32, "h": 32 }, 252 | "duration": 50 253 | }, 254 | { 255 | "filename": "boom 28.aseprite", 256 | "frame": { "x": 58, "y": 17, "w": 1, "h": 1 }, 257 | "rotated": false, 258 | "trimmed": true, 259 | "spriteSourceSize": { "x": 16, "y": 19, "w": 1, "h": 1 }, 260 | "sourceSize": { "w": 32, "h": 32 }, 261 | "duration": 50 262 | }, 263 | { 264 | "filename": "boom 29.aseprite", 265 | "frame": { "x": 60, "y": 17, "w": 1, "h": 1 }, 266 | "rotated": false, 267 | "trimmed": true, 268 | "spriteSourceSize": { "x": 0, "y": 0, "w": 1, "h": 1 }, 269 | "sourceSize": { "w": 32, "h": 32 }, 270 | "duration": 50 271 | } 272 | ], 273 | "meta": { 274 | "app": "http://www.aseprite.org/", 275 | "version": "1.2.29", 276 | "image": "boom.png", 277 | "format": "RGBA8888", 278 | "size": { "w": 80, "h": 77 }, 279 | "scale": "1", 280 | "frameTags": [ 281 | { "name": "Explosion", "from": 0, "to": 29, "direction": "forward" } 282 | ], 283 | "slices": [ 284 | ] 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /src/elke/pathfinding/PathFinder.hx: -------------------------------------------------------------------------------- 1 | package elke.pathfinding; 2 | 3 | import h2d.Graphics; 4 | import elke.utils.CellGraph; 5 | import elke.pathfinding.PathPool; 6 | 7 | @:allow(elke.pathfinding.PathFinder) 8 | class PathFindingQuery { 9 | public var path: Array = null; 10 | public var fromX = 0.; 11 | public var fromY = 0.; 12 | public var toX = 0.; 13 | public var toY = 0.; 14 | public var complete = false; 15 | public var debounce(default, set) = 0.; 16 | 17 | public var immediate = false; 18 | 19 | var _dbnc = 0.; 20 | 21 | var pathNodes: Array = []; 22 | var open : Array = []; 23 | 24 | var startNode:GraphNode; 25 | var endNode: GraphNode; 26 | var inProgress = false; 27 | 28 | public function new(debounce = 0.) { 29 | this.debounce = debounce; 30 | reset(); 31 | } 32 | 33 | function set_debounce(d) { 34 | _dbnc = d; 35 | return this.debounce = d; 36 | } 37 | 38 | public function reset() { 39 | _dbnc = debounce; 40 | complete = false; 41 | inProgress = false; 42 | open = []; 43 | pathNodes = []; 44 | startNode = null; 45 | endNode = null; 46 | } 47 | } 48 | 49 | class PathFinder { 50 | var pathPool: PathPool; 51 | var graph: CellGraph; 52 | 53 | var pathfindingQueries: Array = []; 54 | 55 | var pathCache: Map>; 56 | var debugGraphics: Graphics; 57 | 58 | #if (target.threaded) 59 | var processingThread: sys.thread.Thread; 60 | var running = false; 61 | var accessMutex: sys.thread.Mutex; 62 | #end 63 | 64 | var threaded = false; 65 | 66 | public function new(graph: CellGraph, debugGraphics: Graphics) { 67 | pathPool = new PathPool(); 68 | this.graph = graph; 69 | this.debugGraphics = debugGraphics; 70 | 71 | pathCache = new Map(); 72 | #if (target.threaded) 73 | if (threaded) { 74 | accessMutex = new sys.thread.Mutex(); 75 | processingThread = sys.thread.Thread.create(threadProcess); 76 | running = true; 77 | } 78 | #end 79 | } 80 | 81 | function getCached(fromCell:GraphNode, toCell: GraphNode) { 82 | var p = pathCache[toCell.id]; 83 | if (p == null) { 84 | return null; 85 | } 86 | 87 | var i = 0; 88 | for (n in p) { 89 | if (n.id == fromCell.id) { 90 | return p.slice(i); 91 | } 92 | i++; 93 | } 94 | 95 | return null; 96 | } 97 | 98 | #if (target.threaded) 99 | var lastTime = 0.; 100 | function threadProcess() { 101 | while(running) { 102 | var newTime = haxe.Timer.stamp(); 103 | var dt = newTime - lastTime; 104 | lastTime = newTime; 105 | process(dt, 1.0); 106 | Sys.sleep(1 / 60); 107 | } 108 | } 109 | #end 110 | 111 | var accum = 0.; 112 | public function process(dt: Float, maxProcessingTime: Float) { 113 | accum -= dt; 114 | if (accum > 0) { 115 | return; 116 | } 117 | 118 | acquire(); 119 | 120 | var stamp = haxe.Timer.stamp(); 121 | 122 | var maxTime = maxProcessingTime;//1 / 166.;//Const.TICK_RATE * 0.5; 123 | final individualTime = maxTime * 0.5; 124 | 125 | var i = 0; 126 | while (true) { 127 | var q = pathfindingQueries[i]; 128 | if (q == null) { 129 | break; 130 | } 131 | 132 | q._dbnc -= dt; 133 | if (q._dbnc > 0) { 134 | i ++; 135 | continue; 136 | } 137 | 138 | 139 | var time = haxe.Timer.stamp(); 140 | q = processPathQuery(q, individualTime); 141 | time = haxe.Timer.stamp() - time; 142 | accum += time; 143 | 144 | if (q.complete) { 145 | pathfindingQueries.remove(q); 146 | } 147 | 148 | i++; 149 | 150 | if (haxe.Timer.stamp() - stamp >= maxTime) { 151 | break; 152 | } 153 | } 154 | 155 | release(); 156 | 157 | } 158 | 159 | inline function acquire() { 160 | 161 | #if (target.threaded) 162 | if (threaded) { 163 | accessMutex.acquire(); 164 | } 165 | #end 166 | } 167 | 168 | inline function release() { 169 | 170 | #if (target.threaded) 171 | if (threaded) { 172 | accessMutex.release(); 173 | } 174 | #end 175 | } 176 | 177 | public function findPathDelayed(x: Float, y: Float, toX: Float, toY: Float, ?existing: PathFindingQuery) : PathFindingQuery { 178 | acquire(); 179 | 180 | if (existing == null) { 181 | existing = new PathFindingQuery(); 182 | } else { 183 | pathfindingQueries.remove(existing); 184 | } 185 | 186 | existing.fromX = x; 187 | existing.fromY = y; 188 | existing.toX = toX; 189 | existing.toY = toY; 190 | existing.reset(); 191 | 192 | pathfindingQueries.push(existing); 193 | 194 | release(); 195 | 196 | return existing; 197 | } 198 | 199 | public function findPath(x: Float, y: Float, toX: Float, toY: Float, ?path: Array, quick = false) { 200 | var q = new PathFindingQuery(); 201 | q.fromX = x; 202 | q.fromY = y; 203 | q.toX = toX; 204 | q.toY = toY; 205 | q.immediate = true; 206 | return processPathQuery(q); 207 | } 208 | 209 | public function processPathQuery(q: PathFindingQuery, maxProcessingTime = 1.0): PathFindingQuery { 210 | var path = []; 211 | var quick = false; 212 | 213 | var stamp = haxe.Timer.stamp(); 214 | 215 | PathNode.nex = 0; 216 | 217 | var pathNodes: Array = q.pathNodes; 218 | var open : Array = q.open; 219 | 220 | if (q.startNode == null) { 221 | q.startNode = graph.getClosestCell(q.fromX, q.fromY); 222 | } 223 | if (q.endNode == null) { 224 | q.endNode = graph.getClosestCell(q.toX, q.toY); 225 | } 226 | 227 | var startNode = q.startNode; 228 | var endNode = q.endNode; 229 | 230 | if (!q.inProgress) { 231 | if (startNode == null || endNode == null) { 232 | q.complete = true; 233 | q.path = null; 234 | return q; 235 | } 236 | 237 | //path.push(startNode); 238 | if (startNode == endNode) { 239 | path.push(startNode); 240 | q.path = path; 241 | q.complete = true; 242 | return q; 243 | } 244 | 245 | var cached = getCached(startNode, endNode); 246 | if (cached != null) { 247 | q.path = cached; 248 | q.complete = true; 249 | return q; 250 | } 251 | } 252 | 253 | inline function H(n: GraphNode) { 254 | var dx = endNode.centerX - n.centerX; 255 | var dy = endNode.centerY - n.centerY; 256 | return Math.abs(dx) + Math.abs(dy); 257 | } 258 | 259 | inline function G(n: GraphNode, from: PathNode) { 260 | var cx = q.fromX; 261 | var cy = q.fromY; 262 | if (from != null) { 263 | cx = from.val.centerX; 264 | cy = from.val.centerY; 265 | } 266 | 267 | var dx = q.fromX - n.centerX; 268 | var dy = q.fromY - n.centerY; 269 | 270 | return (Math.abs(dx) + Math.abs(dy)) * 1. + from.G; 271 | //return (dx * dx + dy * dy); 272 | } 273 | 274 | inline function getPathNode(n: GraphNode) { 275 | var res = null; 276 | for (c in pathNodes) { 277 | if (c.val == n) { 278 | res = c; 279 | break; 280 | } 281 | } 282 | return res; 283 | } 284 | 285 | inline function getLowestCostNode() { 286 | return open.shift(); 287 | } 288 | 289 | inline function addToOpenList(n: PathNode, replace = false) { 290 | if (replace) open.remove(n); 291 | var inserted = false; 292 | for (i in 0...open.length) { 293 | if (n.F < open[i].F) { 294 | open.insert(i, n); 295 | inserted = true; 296 | break; 297 | } 298 | } 299 | 300 | if (!inserted) { 301 | open.push(n); 302 | } 303 | } 304 | 305 | var curPathNode: PathNode = null; 306 | if (!q.inProgress) { 307 | curPathNode = new PathNode(null, startNode, 0, H(startNode)); 308 | addToOpenList(curPathNode); 309 | pathNodes.push(curPathNode); 310 | } 311 | 312 | var dx = 0.; 313 | var dy = 0.; 314 | 315 | var tries = 0; 316 | 317 | var found = false; 318 | 319 | q.inProgress = true; 320 | 321 | var needsMoreProcessing = false; 322 | while(open.length > 0) { 323 | curPathNode = getLowestCostNode(); 324 | if (curPathNode == null) { 325 | break; 326 | } 327 | 328 | if (curPathNode.val == endNode) { 329 | found = true; 330 | break; 331 | } 332 | 333 | for (c in curPathNode.val.neighbors) { 334 | dx = endNode.centerX - c.centerX; 335 | dy = endNode.centerY - c.centerY; 336 | var o = getPathNode(c); 337 | if (o == null) { 338 | var newG = curPathNode.G; 339 | if (!quick) { 340 | newG = G(c, curPathNode); 341 | } 342 | 343 | o = new PathNode(curPathNode, c, newG, H(c)); 344 | o.pathLength = curPathNode.pathLength + 1; 345 | addToOpenList(o); 346 | 347 | pathNodes.push(o); 348 | } else { 349 | if (o.closed) continue; 350 | 351 | var pLen = curPathNode.pathLength; 352 | var cG = G(c, curPathNode); 353 | 354 | if (cG < o.G) { 355 | o.G = cG; 356 | o.parent = curPathNode; 357 | o.pathLength = pLen; 358 | addToOpenList(o, true); 359 | } 360 | } 361 | 362 | if (o.val == endNode) { 363 | found = true; 364 | break; 365 | } 366 | } 367 | 368 | curPathNode.closed = true; 369 | open.remove(curPathNode); 370 | 371 | if (tries > 10 && !q.immediate) { 372 | if (haxe.Timer.stamp() - stamp >= maxProcessingTime) { 373 | needsMoreProcessing = true; 374 | break; 375 | } 376 | } 377 | 378 | tries ++; 379 | } 380 | 381 | if (needsMoreProcessing) { 382 | return q; 383 | } else { 384 | q.complete = true; 385 | } 386 | 387 | if (curPathNode == null || curPathNode.val != endNode) { 388 | q.path = null; 389 | return q; 390 | } 391 | 392 | while (true) { 393 | if (curPathNode == null) break; 394 | path.push(curPathNode.val); 395 | curPathNode = curPathNode.parent; 396 | if (curPathNode == null) { 397 | break; 398 | } 399 | }; 400 | 401 | path.reverse(); 402 | 403 | pathCache[endNode.id] = path; 404 | 405 | q.path = path; 406 | 407 | /* 408 | debugGraphics.clear(); 409 | for (c in pathNodes) { 410 | if (c.closed) { 411 | debugGraphics.lineStyle(1, 0xff0000); 412 | } else { 413 | debugGraphics.lineStyle(1, 0x00FF00); 414 | } 415 | debugGraphics.drawRect(c.val.centerX - 2, c.val.centerY - 2, 4, 4); 416 | } 417 | */ 418 | 419 | return q; 420 | } 421 | } -------------------------------------------------------------------------------- /src/elke/Game.hx: -------------------------------------------------------------------------------- 1 | package elke; 2 | 3 | import hxd.Window; 4 | import elke.input.GamePadHandler; 5 | import elke.process.Command; 6 | import h2d.Text; 7 | import elke.process.Timeout; 8 | import h3d.impl.Benchmark; 9 | import h3d.Engine; 10 | import h2d.Scene; 11 | import elke.gamestate.GameState; 12 | import elke.sound.Sounds; 13 | import elke.gamestate.GameStateHandler; 14 | import elke.entity.Entities; 15 | 16 | typedef GameInitConf = { 17 | ?initialState:GameState, 18 | ?pixelSize:Int, 19 | ?tickRate:Int, 20 | ?onInit:Void->Void, 21 | ?backgroundColor:Int, 22 | } 23 | 24 | enum InputMethod { 25 | KeyboardAndMouse; 26 | Touch; 27 | Gamepad; 28 | } 29 | 30 | class Game extends hxd.App { 31 | public static var instance(default, null):Game; 32 | 33 | public var paused(default, set):Bool; 34 | 35 | /** 36 | * when touch controls are enabled, this is true 37 | * Whenever a non touch input happens, it will be disabled 38 | */ 39 | public var inputMethod:InputMethod = KeyboardAndMouse; 40 | 41 | public var usingTouch(get, null) = false; 42 | 43 | function get_usingTouch() { 44 | return inputMethod == Touch; 45 | } 46 | 47 | /** 48 | * the width of the screen in scaled pixels 49 | */ 50 | public var screenWidth:Int; 51 | 52 | /** 53 | * the height of the screen in scaled pixels 54 | */ 55 | public var screenHeight:Int; 56 | 57 | public var entities:Entities; 58 | public var states:GameStateHandler; 59 | 60 | public var sound:Sounds; 61 | 62 | public var time = 0.; 63 | 64 | /** 65 | * mouse x in scaled screen pixels 66 | */ 67 | public var mouseX:Int; 68 | 69 | /** 70 | * mouse y in scaled screen pixels 71 | */ 72 | public var mouseY:Int; 73 | 74 | public var gamepads:GamePadHandler = null; 75 | 76 | /** 77 | * size of window pixels. scale of window. 78 | */ 79 | public var pixelSize(default, set):Int; 80 | 81 | function set_pixelSize(size) { 82 | if (s2d != null) { 83 | if (size > 1) { 84 | if (s2d.filter == null) { 85 | s2d.filter = new h2d.filter.Nothing(); 86 | } 87 | } else { 88 | s2d.filter = null; 89 | } 90 | } 91 | 92 | pixelSize = size; 93 | onResize(); 94 | 95 | return pixelSize = size; 96 | } 97 | 98 | /** 99 | * updates per second 100 | */ 101 | public var tickRate(default, set) = 60; 102 | 103 | public var tickTime:Float = 1 / 60.; 104 | 105 | public var timeScale = 1.0; 106 | 107 | function set_tickRate(r:Int) { 108 | tickTime = 1. / r; 109 | return tickRate = r; 110 | } 111 | 112 | var initialState:GameState; 113 | var onInit:Void->Void; 114 | 115 | var conf:GameInitConf; 116 | 117 | public var benchmark:Benchmark; 118 | 119 | var drawCallsText:Text; 120 | 121 | public function new(?conf:GameInitConf) { 122 | super(); 123 | #if (hl && !debug) 124 | hl.UI.closeConsole(); 125 | #end 126 | 127 | this.conf = conf; 128 | } 129 | 130 | override function init() { 131 | super.init(); 132 | instance = this; 133 | 134 | initResources(); 135 | 136 | initEntities(); 137 | configRenderer(); 138 | 139 | hxd.Window.getInstance().addEventTarget(onEvent); 140 | 141 | sound = new Sounds(); 142 | 143 | states = new GameStateHandler(this); 144 | 145 | onResize(); 146 | 147 | runInitConf(); 148 | 149 | initiateGamepads(); 150 | 151 | /* 152 | benchmark = new Benchmark(uiScene); 153 | benchmark.measureCpu = true; 154 | benchmark.enable = true; 155 | */ 156 | 157 | #if debug 158 | drawCallsText = new Text(hxd.Res.fonts.minecraftiaOutline.toFont(), s2d); 159 | #end 160 | } 161 | 162 | function onEvent(e:hxd.Event) { 163 | #if js 164 | if (e.kind == EPush) { 165 | if (e.touchId != null) { 166 | inputMethod = Touch; 167 | } else { 168 | inputMethod = KeyboardAndMouse; 169 | } 170 | } 171 | if (e.kind == EKeyDown) { 172 | inputMethod = KeyboardAndMouse; 173 | } 174 | #else 175 | if (e.kind == EPush || e.kind == EKeyDown) { 176 | inputMethod = KeyboardAndMouse; 177 | } 178 | #end 179 | 180 | if (e.kind == EMove) { 181 | mouseX = Std.int(e.relX / pixelSize); 182 | mouseY = Std.int(e.relY / pixelSize); 183 | } 184 | 185 | if (e.kind == EFocusLost) { 186 | gamepads.inFocus = false; 187 | } 188 | if (e.kind == EFocus) { 189 | gamepads.inFocus = true; 190 | } 191 | 192 | states.onEvent(e); 193 | } 194 | 195 | function initiateGamepads() { 196 | gamepads = new GamePadHandler(); 197 | new Timeout(0.1, () -> { 198 | gamepads.init(); 199 | }); 200 | } 201 | 202 | public function vibrate(duration:Int) { 203 | if (inputMethod == Gamepad) { 204 | if (gamepads.vibrate(duration)) { 205 | return; 206 | } 207 | } 208 | 209 | #if js 210 | if (js.Browser.navigator.vibrate != null) { 211 | js.Browser.navigator.vibrate(duration); 212 | } 213 | #end 214 | } 215 | 216 | public function canBeAddedAsPWA() { 217 | #if js 218 | var d:Dynamic = js.Browser.window; 219 | return d.installPWAPrompt != null; 220 | #end 221 | 222 | return false; 223 | } 224 | 225 | public function promptAddAsPWA(?onAccept:Void->Void, ?onDecline:Void->Void) { 226 | #if js 227 | var d:Dynamic = js.Browser.window; 228 | var deferredInstall = null; 229 | if (d.installPWAPrompt != null) { 230 | deferredInstall = d.installPWAPrompt; 231 | } 232 | if (deferredInstall != null) { 233 | deferredInstall.prompt(); 234 | deferredInstall.userChoice.then((choiceResult) -> { 235 | if (choiceResult.outcome == 'accepted') { 236 | var d:Dynamic = js.Browser.window; 237 | d.installPWAPrompt = null; 238 | if (onAccept != null) { 239 | onAccept(); 240 | } 241 | } else { 242 | if (onDecline != null) { 243 | onDecline(); 244 | } 245 | } 246 | }); 247 | } else { 248 | if (onDecline != null) { 249 | onDecline(); 250 | } 251 | } 252 | #end 253 | 254 | if (onDecline != null) { 255 | onDecline(); 256 | } 257 | } 258 | 259 | function runInitConf() { 260 | if (conf == null) { 261 | return; 262 | } 263 | 264 | if (conf.onInit != null) { 265 | conf.onInit(); 266 | } 267 | 268 | if (conf.pixelSize != null) { 269 | pixelSize = conf.pixelSize; 270 | } 271 | 272 | if (conf.tickRate != null) { 273 | tickRate = conf.tickRate; 274 | } 275 | 276 | if (conf.backgroundColor != null) { 277 | engine.backgroundColor = conf.backgroundColor; 278 | } 279 | 280 | if (conf.initialState != null) { 281 | states.setState(conf.initialState); 282 | initialState = null; 283 | } 284 | 285 | conf = null; 286 | } 287 | 288 | function initEntities() { 289 | entities = new Entities(); 290 | } 291 | 292 | var processes:Array = []; 293 | 294 | public function addProcess(p) { 295 | processes.push(p); 296 | p.onStart(); 297 | } 298 | 299 | public function removeProcess(p) { 300 | if (processes.remove(p)) { 301 | p.onFinish(); 302 | } 303 | } 304 | 305 | public var uiScene:Scene; 306 | 307 | override function render(e:Engine) { 308 | s3d.render(e); 309 | s2d.render(e); 310 | states.onRender(e); 311 | uiScene.render(e); 312 | 313 | #if debug 314 | s2d.addChild(drawCallsText); 315 | drawCallsText.x = 2; 316 | drawCallsText.text = '${e.drawCalls}'; 317 | #end 318 | } 319 | 320 | function configRenderer() { 321 | // Image filtering set to nearest sharp pixel graphics. 322 | // If you don't want crisp pixel graphics you can just 323 | // remove this 324 | hxd.res.Image.DEFAULT_FILTER = Nearest; 325 | 326 | #if js 327 | // This causes the game to not be super small on high DPI mobile screens 328 | hxd.Window.getInstance().useScreenPixels = false; 329 | #end 330 | 331 | engine.autoResize = true; 332 | 333 | uiScene = new Scene(); 334 | } 335 | 336 | var freezeFrames = 0; 337 | 338 | public function freeze(frames) { 339 | freezeFrames = frames; 340 | } 341 | 342 | var commands:Array = []; 343 | 344 | public function dispatchCommand(c:Command) { 345 | commands.push(c); 346 | } 347 | 348 | var timeAccumulator = 0.0; 349 | 350 | override function update(dt:Float) { 351 | // benchmark.begin(); 352 | 353 | var maxTicksPerUpdate = 3; 354 | 355 | // Check if gamepad is pressed 356 | if (gamepads.anyButtonPressed()) { 357 | inputMethod = Gamepad; 358 | } 359 | 360 | timeAccumulator += dt; 361 | 362 | var timeUntilTick = Math.max(tickTime - timeAccumulator, 0); 363 | 364 | states.update(dt, timeUntilTick); 365 | 366 | while (timeAccumulator > tickTime * timeScale && maxTicksPerUpdate > 0) { 367 | 368 | if (commands.length > 0) { 369 | for (c in commands) 370 | c(); 371 | commands.splice(0, commands.length); 372 | } 373 | 374 | timeAccumulator -= tickTime * timeScale; 375 | if (freezeFrames > 0) { 376 | freezeFrames--; 377 | continue; 378 | } 379 | 380 | 381 | // States are still updated, to make sure pause menus and such work 382 | if (paused) { 383 | return; 384 | } 385 | 386 | time += tickTime; 387 | 388 | states.tick(tickTime * timeScale); 389 | 390 | for (p in processes) { 391 | p.update(tickTime * timeScale); 392 | } 393 | 394 | entities.update(tickTime * timeScale); 395 | 396 | maxTicksPerUpdate--; 397 | } 398 | // benchmark.end(); 399 | } 400 | 401 | override function onResize() { 402 | var s = hxd.Window.getInstance(); 403 | 404 | var w = Std.int(s.width / pixelSize); 405 | var h = Std.int(s.height / pixelSize); 406 | 407 | this.screenWidth = w; 408 | this.screenHeight = h; 409 | 410 | s2d.scaleMode = ScaleMode.Stretch(w, h); 411 | uiScene.scaleMode = ScaleMode.Resize; 412 | } 413 | 414 | static function initResources() { 415 | #if usepak 416 | hxd.Res.initPak("data"); 417 | #elseif (debug && hl) 418 | hxd.Res.initLocal(); 419 | hxd.res.Resource.LIVE_UPDATE = true; 420 | #else 421 | hxd.Res.initEmbed(); 422 | #end 423 | // Load CastleDB data. 424 | Data.load(hxd.Res.data.entry.getText()); 425 | } 426 | 427 | function set_paused(p) { 428 | if (p != this.paused) { 429 | if (p) { 430 | states.onPause(); 431 | } else { 432 | states.onUnpause(); 433 | } 434 | } 435 | 436 | return this.paused = p; 437 | } 438 | } 439 | -------------------------------------------------------------------------------- /res/fonts/minecraftiaOutline.fnt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /res/fonts/small.fnt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | -------------------------------------------------------------------------------- /res/fonts/debuff.fnt: -------------------------------------------------------------------------------- 1 | info face="Debuff" size=18 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=0 aa=1 padding=0,0,0,0 spacing=1,1 outline=1 2 | common lineHeight=18 base=14 scaleW=256 scaleH=256 pages=1 packed=0 alphaChnl=0 redChnl=4 greenChnl=4 blueChnl=4 3 | page id=0 file="debuff_0.png" 4 | chars count=114 5 | char id=32 x=99 y=51 width=5 height=3 xoffset=-2 yoffset=16 xadvance=11 page=0 chnl=15 6 | char id=33 x=154 y=0 width=6 height=14 xoffset=2 yoffset=2 xadvance=11 page=0 chnl=15 7 | char id=34 x=209 y=38 width=9 height=9 xoffset=0 yoffset=1 xadvance=11 page=0 chnl=15 8 | char id=35 x=216 y=14 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 9 | char id=36 x=48 y=0 width=11 height=14 xoffset=-1 yoffset=2 xadvance=11 page=0 chnl=15 10 | char id=37 x=84 y=40 width=9 height=11 xoffset=0 yoffset=4 xadvance=11 page=0 chnl=15 11 | char id=38 x=161 y=0 width=11 height=13 xoffset=-1 yoffset=2 xadvance=11 page=0 chnl=15 12 | char id=39 x=59 y=52 width=6 height=6 xoffset=2 yoffset=2 xadvance=11 page=0 chnl=15 13 | char id=40 x=144 y=0 width=9 height=14 xoffset=0 yoffset=2 xadvance=11 page=0 chnl=15 14 | char id=41 x=84 y=0 width=9 height=14 xoffset=0 yoffset=2 xadvance=11 page=0 chnl=15 15 | char id=42 x=39 y=53 width=9 height=6 xoffset=0 yoffset=6 xadvance=11 page=0 chnl=15 16 | char id=43 x=29 y=53 width=9 height=6 xoffset=0 yoffset=6 xadvance=11 page=0 chnl=15 17 | char id=44 x=249 y=38 width=6 height=8 xoffset=2 yoffset=10 xadvance=11 page=0 chnl=15 18 | char id=45 x=73 y=52 width=9 height=3 xoffset=0 yoffset=8 xadvance=11 page=0 chnl=15 19 | char id=46 x=177 y=50 width=4 height=3 xoffset=2 yoffset=12 xadvance=11 page=0 chnl=15 20 | char id=47 x=245 y=0 width=6 height=13 xoffset=2 yoffset=2 xadvance=11 page=0 chnl=15 21 | char id=48 x=36 y=29 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 22 | char id=49 x=60 y=28 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 23 | char id=50 x=72 y=28 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 24 | char id=51 x=108 y=28 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 25 | char id=52 x=132 y=27 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 26 | char id=53 x=156 y=27 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 27 | char id=54 x=168 y=26 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 28 | char id=55 x=204 y=26 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 29 | char id=56 x=240 y=26 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 30 | char id=57 x=0 y=43 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 31 | char id=58 x=0 y=55 width=6 height=8 xoffset=2 yoffset=6 xadvance=11 page=0 chnl=15 32 | char id=59 x=202 y=38 width=6 height=10 xoffset=2 yoffset=8 xadvance=11 page=0 chnl=15 33 | char id=60 x=219 y=38 width=9 height=9 xoffset=0 yoffset=5 xadvance=11 page=0 chnl=15 34 | char id=61 x=239 y=38 width=9 height=8 xoffset=0 yoffset=6 xadvance=11 page=0 chnl=15 35 | char id=62 x=229 y=38 width=9 height=9 xoffset=0 yoffset=5 xadvance=11 page=0 chnl=15 36 | char id=63 x=94 y=0 width=9 height=14 xoffset=0 yoffset=2 xadvance=11 page=0 chnl=15 37 | char id=64 x=24 y=41 width=11 height=11 xoffset=-1 yoffset=5 xadvance=11 page=0 chnl=15 38 | char id=65 x=120 y=15 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 39 | char id=66 x=108 y=15 width=11 height=12 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 40 | char id=67 x=132 y=15 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 41 | char id=68 x=144 y=15 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 42 | char id=69 x=156 y=15 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 43 | char id=70 x=168 y=14 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 44 | char id=71 x=48 y=15 width=11 height=12 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 45 | char id=72 x=180 y=14 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 46 | char id=73 x=192 y=14 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 47 | char id=74 x=204 y=14 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 48 | char id=75 x=197 y=0 width=11 height=13 xoffset=-1 yoffset=2 xadvance=11 page=0 chnl=15 49 | char id=76 x=228 y=14 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 50 | char id=77 x=240 y=14 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 51 | char id=78 x=0 y=31 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 52 | char id=79 x=12 y=29 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 53 | char id=80 x=24 y=29 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 54 | char id=81 x=72 y=15 width=11 height=12 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 55 | char id=82 x=48 y=28 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 56 | char id=83 x=84 y=15 width=11 height=12 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 57 | char id=84 x=96 y=15 width=11 height=12 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 58 | char id=85 x=84 y=28 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 59 | char id=86 x=96 y=28 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 60 | char id=87 x=209 y=0 width=11 height=13 xoffset=-1 yoffset=2 xadvance=11 page=0 chnl=15 61 | char id=88 x=120 y=27 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 62 | char id=89 x=60 y=15 width=11 height=12 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 63 | char id=90 x=12 y=41 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 64 | char id=91 x=134 y=0 width=9 height=14 xoffset=0 yoffset=2 xadvance=11 page=0 chnl=15 65 | char id=92 x=36 y=15 width=6 height=13 xoffset=2 yoffset=2 xadvance=11 page=0 chnl=15 66 | char id=93 x=104 y=0 width=9 height=14 xoffset=0 yoffset=2 xadvance=11 page=0 chnl=15 67 | char id=94 x=7 y=55 width=9 height=7 xoffset=0 yoffset=1 xadvance=11 page=0 chnl=15 68 | char id=95 x=83 y=52 width=9 height=3 xoffset=0 yoffset=13 xadvance=11 page=0 chnl=15 69 | char id=96 x=66 y=52 width=6 height=6 xoffset=2 yoffset=1 xadvance=11 page=0 chnl=15 70 | char id=97 x=142 y=39 width=11 height=10 xoffset=-1 yoffset=5 xadvance=11 page=0 chnl=15 71 | char id=98 x=36 y=0 width=11 height=14 xoffset=-1 yoffset=1 xadvance=11 page=0 chnl=15 72 | char id=99 x=178 y=38 width=11 height=10 xoffset=-1 yoffset=5 xadvance=11 page=0 chnl=15 73 | char id=100 x=24 y=0 width=11 height=14 xoffset=-1 yoffset=1 xadvance=11 page=0 chnl=15 74 | char id=101 x=94 y=40 width=11 height=10 xoffset=-1 yoffset=5 xadvance=11 page=0 chnl=15 75 | char id=102 x=12 y=0 width=11 height=14 xoffset=-1 yoffset=2 xadvance=11 page=0 chnl=15 76 | char id=103 x=72 y=0 width=11 height=14 xoffset=-1 yoffset=5 xadvance=11 page=0 chnl=15 77 | char id=104 x=60 y=0 width=11 height=14 xoffset=-1 yoffset=1 xadvance=11 page=0 chnl=15 78 | char id=105 x=173 y=0 width=11 height=13 xoffset=-1 yoffset=2 xadvance=11 page=0 chnl=15 79 | char id=106 x=0 y=0 width=11 height=16 xoffset=-1 yoffset=2 xadvance=11 page=0 chnl=15 80 | char id=107 x=185 y=0 width=11 height=13 xoffset=-1 yoffset=2 xadvance=11 page=0 chnl=15 81 | char id=108 x=221 y=0 width=11 height=13 xoffset=-1 yoffset=2 xadvance=11 page=0 chnl=15 82 | char id=109 x=130 y=39 width=11 height=10 xoffset=-1 yoffset=5 xadvance=11 page=0 chnl=15 83 | char id=110 x=144 y=27 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 84 | char id=111 x=154 y=39 width=11 height=10 xoffset=-1 yoffset=5 xadvance=11 page=0 chnl=15 85 | char id=112 x=233 y=0 width=11 height=13 xoffset=-1 yoffset=5 xadvance=11 page=0 chnl=15 86 | char id=113 x=0 y=17 width=11 height=13 xoffset=-1 yoffset=5 xadvance=11 page=0 chnl=15 87 | char id=114 x=180 y=26 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 88 | char id=115 x=192 y=26 width=11 height=11 xoffset=-1 yoffset=5 xadvance=11 page=0 chnl=15 89 | char id=116 x=12 y=15 width=11 height=13 xoffset=-1 yoffset=2 xadvance=11 page=0 chnl=15 90 | char id=117 x=118 y=40 width=11 height=10 xoffset=-1 yoffset=5 xadvance=11 page=0 chnl=15 91 | char id=118 x=166 y=39 width=11 height=10 xoffset=-1 yoffset=5 xadvance=11 page=0 chnl=15 92 | char id=119 x=228 y=26 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 93 | char id=120 x=190 y=38 width=11 height=10 xoffset=-1 yoffset=5 xadvance=11 page=0 chnl=15 94 | char id=121 x=24 y=15 width=11 height=13 xoffset=-1 yoffset=5 xadvance=11 page=0 chnl=15 95 | char id=122 x=106 y=40 width=11 height=10 xoffset=-1 yoffset=5 xadvance=11 page=0 chnl=15 96 | char id=123 x=114 y=0 width=9 height=14 xoffset=0 yoffset=2 xadvance=11 page=0 chnl=15 97 | char id=124 x=43 y=15 width=4 height=13 xoffset=2 yoffset=2 xadvance=11 page=0 chnl=15 98 | char id=125 x=124 y=0 width=9 height=14 xoffset=0 yoffset=2 xadvance=11 page=0 chnl=15 99 | char id=126 x=17 y=53 width=11 height=6 xoffset=-1 yoffset=6 xadvance=11 page=0 chnl=15 100 | char id=160 x=93 y=52 width=5 height=3 xoffset=-2 yoffset=16 xadvance=11 page=0 chnl=15 101 | char id=162 x=36 y=41 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 102 | char id=163 x=48 y=40 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 103 | char id=169 x=60 y=40 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 104 | char id=174 x=216 y=26 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 105 | char id=176 x=49 y=52 width=9 height=6 xoffset=0 yoffset=2 xadvance=11 page=0 chnl=15 106 | char id=8192 x=105 y=51 width=5 height=3 xoffset=-2 yoffset=16 xadvance=11 page=0 chnl=15 107 | char id=8193 x=111 y=51 width=5 height=3 xoffset=-2 yoffset=16 xadvance=11 page=0 chnl=15 108 | char id=8194 x=117 y=51 width=5 height=3 xoffset=-2 yoffset=16 xadvance=11 page=0 chnl=15 109 | char id=8195 x=123 y=51 width=5 height=3 xoffset=-2 yoffset=16 xadvance=11 page=0 chnl=15 110 | char id=8196 x=129 y=51 width=5 height=3 xoffset=-2 yoffset=16 xadvance=11 page=0 chnl=15 111 | char id=8197 x=135 y=50 width=5 height=3 xoffset=-2 yoffset=16 xadvance=11 page=0 chnl=15 112 | char id=8198 x=141 y=50 width=5 height=3 xoffset=-2 yoffset=16 xadvance=11 page=0 chnl=15 113 | char id=8199 x=147 y=50 width=5 height=3 xoffset=-2 yoffset=16 xadvance=11 page=0 chnl=15 114 | char id=8200 x=153 y=50 width=5 height=3 xoffset=-2 yoffset=16 xadvance=11 page=0 chnl=15 115 | char id=8201 x=159 y=50 width=5 height=3 xoffset=-2 yoffset=16 xadvance=11 page=0 chnl=15 116 | char id=8202 x=165 y=50 width=5 height=3 xoffset=-2 yoffset=16 xadvance=11 page=0 chnl=15 117 | char id=8364 x=72 y=40 width=11 height=11 xoffset=-1 yoffset=4 xadvance=11 page=0 chnl=15 118 | char id=12288 x=171 y=50 width=5 height=3 xoffset=-2 yoffset=16 xadvance=11 page=0 chnl=15 119 | --------------------------------------------------------------------------------