├── Sources └── mokha │ ├── IDestroyable.hx │ ├── IDrawable.hx │ ├── tools │ └── ArrayTools.hx │ ├── utils │ ├── tilemaps │ │ ├── Tile.hx │ │ ├── Tilemapper.hx │ │ └── Tilemap.hx │ ├── gestures │ │ └── Swiper.hx │ ├── transformation │ │ └── Transformer.hx │ ├── collision │ │ ├── Collider.hx │ │ └── QuadTree.hx │ ├── animation │ │ ├── AnimationFrame.hx │ │ ├── Animation.hx │ │ └── Animator.hx │ └── tween │ │ └── Tweener.hx │ ├── ui │ ├── BitmapText.hx │ ├── TextInput.hx │ ├── BitmapFont.hx │ ├── Text.hx │ └── TextArea.hx │ ├── Painter.hx │ ├── State.hx │ ├── Mokha.hx │ ├── managers │ ├── output │ │ └── AudioOutputManager.hx │ └── input │ │ ├── KeyboardInputManager.hx │ │ └── MouseInputManager.hx │ ├── Entity.hx │ ├── Sprite.hx │ ├── Object.hx │ ├── Engine.hx │ ├── collections │ ├── Pool.hx │ └── Group.hx │ ├── Camera.hx │ ├── math │ ├── Random.hx │ └── noise │ │ └── PerlinNoise.hx │ ├── shapes │ └── Rectangle.hx │ └── Game.hx ├── haxelib.json ├── README.md └── LICENSE.txt /Sources/mokha/IDestroyable.hx: -------------------------------------------------------------------------------- 1 | package mokha; 2 | 3 | interface IDestroyable { 4 | function destroy() : Void; 5 | } 6 | -------------------------------------------------------------------------------- /Sources/mokha/IDrawable.hx: -------------------------------------------------------------------------------- 1 | package mokha; 2 | 3 | interface IDrawable { 4 | var body : mokha.shapes.Rectangle; 5 | var transformer : mokha.utils.transformation.Transformer; 6 | function draw(g : kha.graphics2.Graphics) : Void; 7 | } 8 | -------------------------------------------------------------------------------- /haxelib.json: -------------------------------------------------------------------------------- 1 | { 2 | "classPath": "Sources", 3 | "contributors": [ "dstrekelj" ], 4 | "dependencies": { }, 5 | "description": "2D game development framework for Kha", 6 | "name": "mokha", 7 | "license": "MIT", 8 | "releasenote": "Initial release", 9 | "tags": ["game", "kha"], 10 | "url" : "https://github.com/dstrekelj/mokha/", 11 | "version": "1.0.0" 12 | } 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mokha 2 | 3 | Mokha is a 2D game development framework for Kha. 4 | 5 | **This project is under development. The API will change without notice.** 6 | 7 | **Stable versions will be published under the `master` branch when the time comes.** 8 | 9 | ## Instructions 10 | 11 | You can use Mokha by adding it as a submodule to your Kha project: 12 | 13 | ``` 14 | cd ./Libraries 15 | git submodule add https://github.com/dstrekelj/mokha 16 | ``` 17 | -------------------------------------------------------------------------------- /Sources/mokha/tools/ArrayTools.hx: -------------------------------------------------------------------------------- 1 | package mokha.tools; 2 | 3 | /** 4 | A collection of static extensions for arrays. 5 | **/ 6 | class ArrayTools { 7 | /** 8 | Checkes whether an array contains a specific element. 9 | @param array Array 10 | @param element Element 11 | @return `true` if element is in array 12 | **/ 13 | public static function has(array : Array, element : T) : Bool { 14 | return array.indexOf(element) >= 0; 15 | } 16 | } -------------------------------------------------------------------------------- /Sources/mokha/utils/tilemaps/Tile.hx: -------------------------------------------------------------------------------- 1 | package mokha.utils.tilemaps; 2 | 3 | /** 4 | A tile is an element of a tilemap. 5 | **/ 6 | class Tile extends mokha.Object { 7 | public var x : Float; 8 | public var y : Float; 9 | public var width : Float; 10 | public var height : Float; 11 | public var index : Int; 12 | 13 | public function new(x : Float, y : Float, width : Float, height : Float, index : Int) : Void { 14 | super(); 15 | this.x = x; 16 | this.y = y; 17 | this.width = width; 18 | this.height = height; 19 | this.index = index; 20 | } 21 | } -------------------------------------------------------------------------------- /Sources/mokha/ui/BitmapText.hx: -------------------------------------------------------------------------------- 1 | package mokha.ui; 2 | 3 | import mokha.ui.BitmapFont; 4 | import mokha.Entity; 5 | 6 | import kha.graphics2.Graphics; 7 | import kha.Color; 8 | 9 | class BitmapText extends Entity { 10 | public var bitmapFont : BitmapFont; 11 | public var value : String; 12 | 13 | public function new(value : String, bitmapFont : BitmapFont) : Void { 14 | super(0, 0, 0, 0); 15 | 16 | this.value = value; 17 | this.bitmapFont = bitmapFont; 18 | } 19 | 20 | override public function draw(g : Graphics) : Void { 21 | super.draw(g); 22 | 23 | g.color = Color.White; 24 | for (i in 0...value.length) { 25 | bitmapFont.drawChar(g, i * bitmapFont.charWidth, 0, value.charAt(i)); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /Sources/mokha/utils/tilemaps/Tilemapper.hx: -------------------------------------------------------------------------------- 1 | package mokha.utils.tilemaps; 2 | 3 | import mokha.utils.tilemaps.Tile; 4 | import mokha.utils.tilemaps.Tilemap; 5 | 6 | import kha.Image; 7 | 8 | /** 9 | Utility class for creating tilemaps. 10 | **/ 11 | class Tilemapper { 12 | public static function fromMatrix(tileWidth : Int, tileHeight : Int, tileSheet : Image, tileData : Array>) : Tilemap { 13 | var tilemap = new Tilemap(tileWidth, tileHeight, tileSheet); 14 | 15 | var height = tileData.length; 16 | var width = tileData[0].length; 17 | 18 | for (y in 0...height) { 19 | for (x in 0...width) { 20 | tilemap.add(new Tile(x * tileWidth, y * tileHeight, tileWidth, tileHeight, tileData[y][x])); 21 | } 22 | } 23 | 24 | return tilemap; 25 | } 26 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Domagoj Štrekelj 2 | 3 | This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. 4 | 5 | Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 6 | 7 | 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 8 | 9 | 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 10 | 11 | 3. This notice may not be removed or altered from any source distribution. 12 | -------------------------------------------------------------------------------- /Sources/mokha/Painter.hx: -------------------------------------------------------------------------------- 1 | package mokha; 2 | 3 | import mokha.shapes.Rectangle; 4 | import mokha.Object; 5 | 6 | import kha.graphics2.Graphics; 7 | 8 | /** 9 | A collection of static extensions for "painting" to the buffer. 10 | **/ 11 | class Painter { 12 | /** 13 | Paints an instance of the `Rectangle` class. 14 | @param g Graphics 15 | @param r Rectangle instance 16 | @param fill If `true` rectangle is filled 17 | **/ 18 | public static function paintRectangle(g : Graphics, r : Rectangle, fill : Bool = false) : Void { 19 | if (fill) g.fillRect(r.x, r.y, r.width, r.height) 20 | else g.drawRect(r.x, r.y, r.width, r.height); 21 | } 22 | 23 | /** 24 | Paints an instance of the `Object` class, taking into account 25 | the transformations applied to it. 26 | @param g Graphics 27 | @param o Object instance 28 | **/ 29 | public static function paintObject(g : Graphics, o : Object) : Void { 30 | g.pushTransformation(o.transformer.transformation); 31 | o.draw(g); 32 | g.popTransformation(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/mokha/utils/tilemaps/Tilemap.hx: -------------------------------------------------------------------------------- 1 | package mokha.utils.tilemaps; 2 | 3 | import mokha.groups.Group; 4 | import mokha.utils.tilemaps.Tile; 5 | 6 | import kha.graphics2.Graphics; 7 | import kha.Image; 8 | 9 | /** 10 | A tilemap is a collection of tiles. 11 | **/ 12 | class Tilemap extends Group { 13 | var tileWidth : Int; 14 | var tileHeight : Int; 15 | var tileSheet : Image; 16 | var tileColumns : Int; 17 | var tileRows : Int; 18 | 19 | public function new(tileWidth : Int, tileHeight : Int, tileSheet : Image) : Void { 20 | super(); 21 | this.tileWidth = tileWidth; 22 | this.tileHeight = tileHeight; 23 | this.tileSheet = tileSheet; 24 | 25 | this.tileColumns = Std.int(tileSheet.width / tileWidth); 26 | this.tileRows = Std.int(tileSheet.height / tileHeight); 27 | } 28 | 29 | override public function draw(g : Graphics) : Void { 30 | for (m in this.members) { 31 | var sy = Std.int(m.index / tileColumns); 32 | var sx = Std.int(m.index - sy * tileColumns); 33 | g.drawSubImage(tileSheet, m.x, m.y, sx * m.width, sy * m.height, m.width, m.height); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /Sources/mokha/utils/gestures/Swiper.hx: -------------------------------------------------------------------------------- 1 | package mokha.utils.gestures; 2 | 3 | import mokha.managers.input.MouseInputManager; 4 | 5 | import kha.math.FastVector2; 6 | 7 | class Swiper { 8 | public var swipe(get, null) : FastVector2; 9 | 10 | var mouse : MouseInputManager; 11 | var first : FastVector2; 12 | var last : FastVector2; 13 | 14 | public function new(mouse : MouseInputManager) : Void { 15 | this.mouse = mouse; 16 | 17 | swipe = new FastVector2(0, 0); 18 | first = new FastVector2(0, 0); 19 | last = new FastVector2(0, 0); 20 | } 21 | 22 | public function update() : Void { 23 | if (mouse.justPressed) { 24 | set(first, mouse.x, mouse.y); 25 | set(swipe, 0, 0); 26 | } 27 | 28 | if (mouse.isPressed) { 29 | set(last, mouse.x, mouse.y); 30 | calc(); 31 | } 32 | 33 | if (mouse.justReleased) { 34 | set(last, mouse.x, mouse.y); 35 | calc(); 36 | } 37 | } 38 | 39 | public function destroy() : Void { 40 | mouse = null; 41 | swipe = null; 42 | first = null; 43 | last = null; 44 | } 45 | 46 | @:noCompletion inline function get_swipe() return swipe; 47 | 48 | inline function set(v : FastVector2, x : Float, y : Float) : Void { 49 | v.x = x; 50 | v.y = y; 51 | } 52 | 53 | inline function calc() : Void { 54 | swipe = last.sub(first); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/mokha/State.hx: -------------------------------------------------------------------------------- 1 | package mokha; 2 | 3 | import kha.graphics2.Graphics; 4 | 5 | /** 6 | States are containers of game logic. For example, a menu state 7 | would describe a game's menu screen, while a play state would 8 | describe a game's gameplay screen. 9 | 10 | A state handles the objects assigned to it, and also defines a 11 | camera which 'observes' the objects. The camera's transformation 12 | matrix is applied to the objects before they're drawn, so that 13 | the objects are presented from the viewpoint of the camera. 14 | **/ 15 | class State { 16 | /** 17 | Creates new state. 18 | **/ 19 | function new() : Void {} 20 | 21 | /** 22 | Override this. Called when states are switched, after the 23 | previous state has been destroyed. 24 | **/ 25 | public function onCreate() : Void {} 26 | 27 | /** 28 | Override this. Called when states are switched, before the 29 | next state is created. 30 | **/ 31 | public function onDestroy() : Void {} 32 | 33 | /** 34 | Override this. Called when state is updated. 35 | **/ 36 | public function update() : Void {} 37 | 38 | /** 39 | Override this. Called when state is drawn. 40 | **/ 41 | public function draw(g : Graphics) : Void {} 42 | } 43 | -------------------------------------------------------------------------------- /Sources/mokha/utils/transformation/Transformer.hx: -------------------------------------------------------------------------------- 1 | package mokha.utils.transformation; 2 | 3 | import mokha.shapes.Rectangle; 4 | 5 | import kha.math.FastMatrix3; 6 | 7 | /** 8 | The transformer handles the transformation matrix of an object. 9 | The matrix is defined by the object's position and angle of 10 | rotation. 11 | **/ 12 | class Transformer { 13 | /** 14 | Angle of rotation applied during the draw call. 15 | **/ 16 | public var angle : Float; 17 | 18 | /** 19 | A reference object body, for translation. 20 | **/ 21 | public var body : Rectangle; 22 | 23 | /** 24 | Transformation matrix. 25 | **/ 26 | public var transformation(get, null) : FastMatrix3; 27 | 28 | /** 29 | Creates new transformer. 30 | **/ 31 | public function new() : Void { 32 | body = new Rectangle(0, 0, 0, 0); 33 | transformation = FastMatrix3.identity(); 34 | angle = 0; 35 | } 36 | 37 | /** 38 | Destroys transformer. 39 | **/ 40 | public function destroy() : Void { 41 | body = null; 42 | transformation = null; 43 | } 44 | 45 | @:noCompletion inline function get_transformation() : FastMatrix3 { 46 | return FastMatrix3.translation(body.x, body.y).multmat(FastMatrix3.rotation(angle)); 47 | } 48 | } -------------------------------------------------------------------------------- /Sources/mokha/ui/TextInput.hx: -------------------------------------------------------------------------------- 1 | package mokha.ui; 2 | 3 | import mokha.managers.input.KeyboardInputManager; 4 | import mokha.ui.Text; 5 | 6 | import kha.Font; 7 | 8 | /** 9 | Simple text input class which has its value changed through 10 | keyboard input. 11 | **/ 12 | class TextInput extends Text { 13 | /** 14 | Regular expression rule for keyboard characters allowed to 15 | be used as input. 16 | **/ 17 | public var rule : EReg; 18 | 19 | /** 20 | Creates new text input. 21 | @param font Font family 22 | @param size Font size 23 | **/ 24 | public function new(font : Font, size : Int) : Void { 25 | super("", font, size); 26 | 27 | rule = ~/.{1}/i; 28 | } 29 | 30 | /** 31 | Handles keyboard input. 32 | @param keys Keyboard input manager 33 | **/ 34 | public function handleInput(keys : KeyboardInputManager) : Void { 35 | if (keys.justPressedSpecialKey == "char") { 36 | if (keys.justPressedKey != null && rule.match(keys.justPressedKey)) { 37 | value += keys.justPressedKey; 38 | } 39 | } 40 | 41 | if (keys.justPressedSpecialKey == "backspace") { 42 | if (value.length > 0) { 43 | value = value.substr(0, value.length - 1); 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/mokha/Mokha.hx: -------------------------------------------------------------------------------- 1 | package mokha; 2 | 3 | /** 4 | Global variables. 5 | **/ 6 | class Mokha { 7 | /** 8 | Draw window width. 9 | **/ 10 | @:allow(mokha.Engine) 11 | public static var windowWidth(default, null) : Int; 12 | 13 | /** 14 | Draw window height. 15 | **/ 16 | @:allow(mokha.Engine) 17 | public static var windowHeight(default, null) : Int; 18 | 19 | /** 20 | Rendering resolution width. 21 | **/ 22 | @:allow(mokha.Game) 23 | public static var renderWidth(default, null) : Int; 24 | 25 | /** 26 | Rendering resolution height. 27 | **/ 28 | @:allow(mokha.Game) 29 | public static var renderHeight(default, null) : Int; 30 | 31 | /** 32 | Total time elapsed. 33 | **/ 34 | @:allow(mokha.Engine) 35 | public static var elapsed(default, null) : Float; 36 | 37 | /** 38 | Delta time between frames. 39 | **/ 40 | @:allow(mokha.Engine) 41 | public static var delta(default, null) : Float; 42 | 43 | /** 44 | Current game instance. 45 | **/ 46 | @:allow(mokha.Game) 47 | static var game(default, null) : Game; 48 | 49 | /** 50 | Switches between states. 51 | @param state State 52 | **/ 53 | public static inline function switchState(state : Class) : Void { 54 | game.switchState(state); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/mokha/managers/output/AudioOutputManager.hx: -------------------------------------------------------------------------------- 1 | package mokha.managers.output; 2 | 3 | import kha.audio1.AudioChannel; 4 | import kha.audio1.Audio; 5 | import kha.Sound; 6 | 7 | /** 8 | Helper class for managing audio output. 9 | **/ 10 | class AudioOutputManager { 11 | /** 12 | An instance of the audio output manager. 13 | **/ 14 | static var instance : AudioOutputManager = null; 15 | 16 | /** 17 | Total number of sounds managed. Used for sound IDs. 18 | **/ 19 | static var soundCount : Int = 0; 20 | 21 | /** 22 | A map of all sounds, or rather their audio channels 23 | mapped to their sound IDs. 24 | **/ 25 | public var sounds(get, null) : Map; 26 | 27 | /** 28 | Creates new audio output manager. 29 | **/ 30 | function new() : Void { 31 | sounds = new Map(); 32 | } 33 | 34 | /** 35 | Retrieves instance of audio output manager. 36 | @return Audio output manager instance 37 | **/ 38 | public static function get() : AudioOutputManager { 39 | if (instance == null) instance = new AudioOutputManager(); 40 | return instance; 41 | } 42 | 43 | /** 44 | Updates audio output manager state. 45 | **/ 46 | public function update() : Void { 47 | 48 | } 49 | 50 | /** 51 | Plays sound. 52 | @param sound Sound asset 53 | @param loop If `true`, sound restarts when playback is over 54 | @param stream If `true`, sound is streamed from memory 55 | @return Sound ID as integer 56 | **/ 57 | public function play(sound : Sound, loop : Bool = false, stream : Bool = false) : Int { 58 | soundCount += 1; 59 | sounds.set(soundCount, Audio.play(sound, loop)); 60 | return soundCount; 61 | } 62 | 63 | @:noCompletion inline function get_sounds() return sounds; 64 | } -------------------------------------------------------------------------------- /Sources/mokha/Entity.hx: -------------------------------------------------------------------------------- 1 | package mokha; 2 | 3 | import mokha.shapes.Rectangle; 4 | import mokha.utils.collision.Collider; 5 | 6 | /** 7 | An entity is an object with a "physical" body which describes 8 | position and size properties. It also contains a collider to make 9 | it collideable. 10 | **/ 11 | class Entity extends Object { 12 | /** 13 | A rectangle which describes the (x, y) position and (width, 14 | height) size of the entity. 15 | **/ 16 | public var body : Rectangle; 17 | 18 | /** 19 | Collider object to handle collision with. 20 | **/ 21 | public var collider : Collider; 22 | 23 | /** 24 | Creates new collideable entity. 25 | @param x Entity horizontal position 26 | @param y Entity vertical position 27 | @param width Entity width 28 | @param height Entity height 29 | **/ 30 | public function new(x : Float, y : Float, width : Float, height : Float) : Void { 31 | super(); 32 | 33 | body = new Rectangle(x, y, width, height); 34 | collider = new Collider(x, y, width, height); 35 | 36 | collider.body = body; 37 | transformer.body = body; 38 | } 39 | 40 | /** 41 | Nulls references. 42 | **/ 43 | override public function destroy() : Void { 44 | super.destroy(); 45 | 46 | collider.destroy(); 47 | 48 | body = null; 49 | collider = null; 50 | } 51 | 52 | /** 53 | Kills entity, also making it not collideable. 54 | **/ 55 | override public function kill() : Void { 56 | super.kill(); 57 | 58 | collider.isCollideable = false; 59 | } 60 | 61 | /** 62 | Revives entity, also making it collideable. 63 | **/ 64 | override public function revive() : Void { 65 | super.revive(); 66 | 67 | collider.isCollideable = true; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Sources/mokha/utils/collision/Collider.hx: -------------------------------------------------------------------------------- 1 | package mokha.utils.collision; 2 | 3 | import mokha.shapes.Rectangle; 4 | 5 | /** 6 | A collider is a tool for detecting collision. 7 | **/ 8 | class Collider { 9 | /** 10 | Reference to body. 11 | **/ 12 | public var body : Rectangle; 13 | 14 | /** 15 | Rectangular hitbox or bounding box. 16 | **/ 17 | public var hitbox(get, null) : Rectangle; 18 | 19 | public var offsetX : Float; 20 | public var offsetY : Float; 21 | 22 | /** 23 | If `true`, overlap and collision checks can be performed. 24 | **/ 25 | public var isCollideable : Bool; 26 | 27 | /** 28 | Creates new collideable collider with rectangular hitbox. 29 | @param x Hitbox X position 30 | @param y Hitbox Y position 31 | @param width Hitbox width 32 | @param height Hitbox height 33 | @return Collider 34 | **/ 35 | public function new(x : Float, y : Float, width : Float, height : Float) : Void { 36 | body = new Rectangle(0, 0, 0, 0); 37 | hitbox = new Rectangle(x, y, width, height); 38 | offsetX = 0; 39 | offsetY = 0; 40 | isCollideable = true; 41 | } 42 | 43 | /** 44 | Cleans up references. 45 | **/ 46 | public function destroy() : Void { 47 | body = null; 48 | hitbox = null; 49 | } 50 | 51 | /** 52 | Detects overlap between two entities. 53 | @param e1 Subject entity 54 | @param e2 Object entity 55 | @return `true` if both entities are collidable and their hitboxes overlap 56 | **/ 57 | public function detectOverlap(collider : Collider) : Bool { 58 | return isCollideable && collider.isCollideable ? hitbox.overlapsRectangle(collider.hitbox) : false; 59 | } 60 | 61 | @:noCompletion inline function get_hitbox() : Rectangle { 62 | hitbox.setPosition(body.x + offsetX, body.y + offsetY); 63 | return hitbox; 64 | } 65 | } -------------------------------------------------------------------------------- /Sources/mokha/Sprite.hx: -------------------------------------------------------------------------------- 1 | package mokha; 2 | 3 | import mokha.utils.animation.Animator; 4 | 5 | import kha.graphics2.Graphics; 6 | import kha.Color; 7 | import kha.Image; 8 | 9 | /** 10 | A sprite is an entity with a visual component. It has an image 11 | graphic and an animator. 12 | **/ 13 | class Sprite extends Entity { 14 | /** 15 | Sprite graphic. 16 | **/ 17 | public var graphic : Image; 18 | 19 | /** 20 | Sprite animator. 21 | **/ 22 | public var animator : Animator; 23 | 24 | /** 25 | Creates new sprite. 26 | @param x Horizontal position 27 | @param y Vertical position 28 | @param width Sprite width 29 | @param height Sprite height 30 | @param graphic Image graphic 31 | **/ 32 | public function new(x : Float, y : Float, width : Float, height : Float, graphic : Image) : Void { 33 | super(x, y, width, height); 34 | 35 | this.graphic = graphic; 36 | 37 | animator = new Animator(width, height, graphic.width, graphic.height); 38 | } 39 | 40 | /** 41 | Updates sprite and its animator. 42 | **/ 43 | override public function update() : Void { 44 | super.update(); 45 | 46 | animator.update(); 47 | } 48 | 49 | /** 50 | Draws sprite using the graphic. Color is set to white before the 51 | image is drawn because of default multiplicative color blending. 52 | @param g G2 API access to framebuffer 53 | **/ 54 | override public function draw(g : Graphics) : Void { 55 | super.draw(g); 56 | 57 | g.color = Color.White; 58 | animator.draw(g, this); 59 | } 60 | 61 | /** 62 | Nulls references. 63 | **/ 64 | override public function destroy() : Void { 65 | super.destroy(); 66 | 67 | animator.destroy(); 68 | 69 | animator = null; 70 | graphic = null; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Sources/mokha/Object.hx: -------------------------------------------------------------------------------- 1 | package mokha; 2 | 3 | import mokha.utils.transformation.Transformer; 4 | 5 | import kha.graphics2.Graphics; 6 | 7 | /** 8 | An object contains the bare minimum of methods and properties 9 | required to partake in the Mokha game life cycle. 10 | **/ 11 | class Object { 12 | /** 13 | If `true`, object is updated. Default is `true`. 14 | **/ 15 | public var isActive : Bool; 16 | 17 | /** 18 | If `true`, object is drawn. Default is `true`. 19 | **/ 20 | public var isVisible : Bool; 21 | 22 | /** 23 | Read-only. If `true`, object is active and visible. 24 | **/ 25 | public var isAlive(get, null) : Bool; 26 | 27 | /** 28 | Handles transformations made to this object. 29 | **/ 30 | public var transformer : Transformer; 31 | 32 | /** 33 | Creates a new active and visible object. 34 | **/ 35 | public function new() : Void { 36 | isActive = true; 37 | isVisible = true; 38 | 39 | transformer = new Transformer(); 40 | } 41 | 42 | /** 43 | Override this. Intended for collision logic. 44 | **/ 45 | public function update() : Void {} 46 | 47 | /** 48 | Override this. Intended for drawing to framebuffer. 49 | @param g G2 API access to framebuffer 50 | **/ 51 | public function draw(g : Graphics) : Void {} 52 | 53 | /** 54 | Override this. Intended for nulling references to objects. 55 | **/ 56 | public function destroy() : Void { 57 | transformer.destroy(); 58 | 59 | transformer = null; 60 | } 61 | 62 | /** 63 | Makes the object inactive and invisible. 64 | **/ 65 | public function kill() : Void { 66 | isActive = false; 67 | isVisible = false; 68 | } 69 | 70 | /** 71 | Makes the object active and visible. 72 | **/ 73 | public function revive() : Void { 74 | isActive = true; 75 | isVisible = true; 76 | } 77 | 78 | @:noCompletion inline function get_isAlive() return isActive && isVisible; 79 | } 80 | -------------------------------------------------------------------------------- /Sources/mokha/utils/animation/AnimationFrame.hx: -------------------------------------------------------------------------------- 1 | package mokha.utils.animation; 2 | 3 | /** 4 | An animation frame is a rectangular shape that is positioned 5 | somewhere on the sprite sheet. It's a mask that only shows 6 | the part of the image on the sprite sheet that overlaps with 7 | the rectangular animation frame shape. 8 | **/ 9 | class AnimationFrame { 10 | /** 11 | X position on sprite sheet. 12 | **/ 13 | public var x(get, null) : Float; 14 | 15 | /** 16 | Y position on sprite sheet. 17 | **/ 18 | public var y(get, null) : Float; 19 | 20 | /** 21 | Width. 22 | **/ 23 | public var width(get, null) : Float; 24 | 25 | /** 26 | Height. 27 | **/ 28 | public var height(get, null) : Float; 29 | 30 | /** 31 | Row index in sprite sheet. 32 | **/ 33 | public var row(get, null) : Int; 34 | 35 | /** 36 | Column index in sprite sheet. 37 | **/ 38 | public var column(get, null) : Int; 39 | 40 | /** 41 | Creates new animation frame. 42 | **/ 43 | public function new(width : Float, height : Float) : Void { 44 | this.width = width; 45 | this.height = height; 46 | 47 | this.x = 0; 48 | this.y = 0; 49 | this.row = 0; 50 | this.column = 0; 51 | } 52 | 53 | /** 54 | Updates animation frame position on sprite sheet. 55 | **/ 56 | public function update(frameIndex : Int, totalRows : Int, totalColumns : Int) : Void { 57 | this.row = Std.int(frameIndex / totalColumns); 58 | this.column = Std.int(frameIndex - this.row * totalColumns); 59 | this.y = this.row * this.height; 60 | this.x = this.column * this.width; 61 | } 62 | 63 | @:noCompletion inline function get_x() return this.x; 64 | 65 | @:noCompletion inline function get_y() return this.y; 66 | 67 | @:noCompletion inline function get_width() return this.width; 68 | 69 | @:noCompletion inline function get_height() return this.height; 70 | 71 | @:noCompletion inline function get_row() return this.row; 72 | 73 | @:noCompletion inline function get_column() return this.column; 74 | } -------------------------------------------------------------------------------- /Sources/mokha/utils/animation/Animation.hx: -------------------------------------------------------------------------------- 1 | package mokha.utils.animation; 2 | 3 | /** 4 | An animation consists of frame indices, a frame rate, and 5 | behavioural flags. 6 | **/ 7 | class Animation { 8 | /** 9 | Animation frame indices. 10 | **/ 11 | public var frameIndices : Array; 12 | 13 | /** 14 | Animation frame rate. 15 | **/ 16 | public var frameRate : Float; 17 | 18 | /** 19 | If `true`, the animation is currently active. 20 | **/ 21 | public var isActive : Bool; 22 | 23 | /** 24 | If `true`, the animation repeats when done. 25 | **/ 26 | public var isRepeating : Bool; 27 | 28 | /** 29 | Current frame index number. 30 | **/ 31 | public var frameIndex : Int; 32 | 33 | /** 34 | Position in frame indices array. 35 | **/ 36 | var index : Int; 37 | 38 | /** 39 | Creates new animation. 40 | @param frameIndices Array of animation frame indices 41 | @param frameRate Animation frame rate 42 | @param isRepeating If `true`, the animation repeats when done. 43 | **/ 44 | public function new(frameIndices : Array, frameRate : Float, isRepeating : Bool) : Void { 45 | this.frameIndices = frameIndices; 46 | this.frameRate = frameRate; 47 | this.isRepeating = isRepeating; 48 | 49 | this.isActive = true; 50 | this.index = 0; 51 | this.frameIndex = this.frameIndices[this.index]; 52 | } 53 | 54 | /** 55 | Updates animation. 56 | **/ 57 | public function update() : Void { 58 | if (!this.isActive) return; 59 | 60 | this.frameIndex = this.frameIndices[this.index]; 61 | this.index += 1; 62 | 63 | if (this.index >= this.frameIndices.length) { 64 | this.index = 0; 65 | this.isActive = this.isRepeating; 66 | } 67 | } 68 | 69 | /** 70 | Resets animation to the first frame and makes it active again. 71 | **/ 72 | public function reset() : Void { 73 | this.index = 0; 74 | this.frameIndex = this.frameIndices[this.index]; 75 | this.isActive = true; 76 | } 77 | } -------------------------------------------------------------------------------- /Sources/mokha/ui/BitmapFont.hx: -------------------------------------------------------------------------------- 1 | package mokha.ui; 2 | 3 | import kha.graphics2.Graphics; 4 | import kha.Font; 5 | import kha.Image; 6 | 7 | /** 8 | Monospace bitmap font. 9 | **/ 10 | class BitmapFont { 11 | public var charWidth(default, null) : Float; 12 | public var charHeight(default, null) : Float; 13 | 14 | var bitmap : Image; 15 | var chars : String; 16 | var charMap : Map; 17 | var columns : Int; 18 | var rows : Int; 19 | 20 | public function new(bitmap : Image, charWidth : Float, charHeight : Float, chars : String) : Void { 21 | this.bitmap = bitmap; 22 | this.charWidth = charWidth; 23 | this.charHeight = charHeight; 24 | this.chars = chars; 25 | 26 | columns = Std.int(bitmap.width / charWidth); 27 | rows = Std.int(bitmap.height / charHeight); 28 | 29 | charMap = new Map(); 30 | for (i in 0...chars.length) { 31 | charMap.set(chars.charAt(i), i); 32 | } 33 | } 34 | 35 | /** 36 | Font interface. Returns height of font when drawn. 37 | **/ 38 | public function height(fontSize : Int) : Float { 39 | return charHeight; 40 | } 41 | 42 | /** 43 | Font interface. Returns width of string when drawn. 44 | **/ 45 | public function width(fontSize : Int, str : String) : Float { 46 | return charWidth * str.length; 47 | } 48 | 49 | /** 50 | Font interface. Returns font baseline position. 51 | **/ 52 | public function baseline(fontSize : Int) : Float { 53 | return 0; 54 | } 55 | 56 | public function drawChar(g : Graphics, x : Float, y : Float, char : String) : Void { 57 | if (charMap.exists(char)) { 58 | var charIndex = charMap.get(char); 59 | g.drawSubImage(bitmap, x, y, getColumn(charIndex) * charWidth, getRow(charIndex) * charHeight, charWidth, charHeight); 60 | } 61 | } 62 | 63 | inline function getIndex(column : Int, row : Int) : Int { 64 | return row * columns + column; 65 | } 66 | 67 | inline function getRow(index : Int) : Int { 68 | return Std.int(index / columns); 69 | } 70 | 71 | inline function getColumn(index : Int) : Int { 72 | return index - getRow(index) * columns; 73 | } 74 | } -------------------------------------------------------------------------------- /Sources/mokha/Engine.hx: -------------------------------------------------------------------------------- 1 | package mokha; 2 | 3 | import mokha.Mokha; 4 | 5 | import kha.Assets; 6 | import kha.Framebuffer; 7 | import kha.Scheduler; 8 | import kha.System; 9 | 10 | /** 11 | The engine initializes the current game, sets up the window and 12 | framerate. 13 | **/ 14 | class Engine { 15 | /** 16 | Current game object. 17 | **/ 18 | static var game : Game; 19 | 20 | /** 21 | Initialises the mokha "engine". 22 | @param config Engine configuration structure 23 | **/ 24 | public static function init(config : EngineConfig) : Void { 25 | if (config.title == null) config.title = "Mokha"; 26 | if (config.width == null) config.width = 640; 27 | if (config.height == null) config.height = 480; 28 | if (config.frameRate == null) config.frameRate = 1 / 60; 29 | 30 | var systemOptions : SystemOptions = { 31 | title: config.title, 32 | width: config.width, 33 | height: config.height, 34 | samplesPerPixel: config.antiAliasing 35 | }; 36 | 37 | System.init(systemOptions, function () { 38 | Assets.loadEverything(function () { 39 | Mokha.elapsed = 0; 40 | Mokha.delta = 0; 41 | Mokha.windowWidth = systemOptions.width; 42 | Mokha.windowHeight = systemOptions.height; 43 | 44 | game = Type.createInstance(config.game, []); 45 | 46 | System.notifyOnRender(render); 47 | Scheduler.addTimeTask(update, 0, config.frameRate); 48 | }); 49 | }); 50 | } 51 | 52 | /** 53 | Renders game to framebuffer. 54 | @param framebuffer Framebuffer 55 | **/ 56 | static function render(framebuffer : Framebuffer) : Void { 57 | game.draw(framebuffer); 58 | } 59 | 60 | /** 61 | Updates game and engine parameters. 62 | **/ 63 | static function update() : Void { 64 | Mokha.delta = Scheduler.time() - Mokha.elapsed; 65 | Mokha.elapsed = Scheduler.time(); 66 | 67 | game.update(); 68 | } 69 | } 70 | 71 | /** 72 | Engine configuration options. 73 | @param game Game class 74 | @param ?frameRate Game frame rate, defaults to 1 / 60 75 | @param ?height Window height 76 | @param ?antiAliasing Number of samples per pixel, defaults to 1 (no anti-aliasing) 77 | @param ?title Window title 78 | @param ?width Window width 79 | **/ 80 | typedef EngineConfig = { 81 | game : Class, 82 | ?frameRate : Float, 83 | ?height : Int, 84 | ?antiAliasing : Int, 85 | ?title : String, 86 | ?width : Int 87 | } 88 | -------------------------------------------------------------------------------- /Sources/mokha/collections/Pool.hx: -------------------------------------------------------------------------------- 1 | package mokha.collections; 2 | 3 | import mokha.IDestroyable; 4 | 5 | /** 6 | An object pool handles the instantiation and reuse of objects with 7 | short lifespans. The idea is to create a pool of a certain 8 | capacity, fill it with objects, and then take objects from the 9 | pool when necessary. 10 | **/ 11 | class Pool { 12 | /** 13 | The number of available pool objects. 14 | **/ 15 | public var available(get, null) : Int; 16 | 17 | /** 18 | The array of objects forming the pool. 19 | **/ 20 | var pool : Array; 21 | 22 | /** 23 | Creates a new pool of a certain capacity. 24 | @param capacity Maximum number of pooled objects 25 | **/ 26 | public function new() : Void { 27 | available = 0; 28 | 29 | pool = new Array(); 30 | } 31 | 32 | /** 33 | Adds object to pool. An object won't be added to the pool if 34 | it is null, the pool is at capacity, if it already exists in 35 | the pool, or if it's already in use. 36 | @param object Object 37 | @return `true` if object is successfully added to pool 38 | **/ 39 | public function add(object : T) : Bool { 40 | if (object == null) return false; 41 | 42 | var i = pool.indexOf(object); 43 | if (i != -1 || i < available) return false; 44 | 45 | pool[available++] = object; 46 | 47 | return true 48 | } 49 | 50 | /** 51 | Empties the pool. 52 | @return An array of objects that were in the pool before emptying it 53 | **/ 54 | public function empty() : Array { 55 | available = 0; 56 | var old = pool; 57 | pool = []; 58 | return old; 59 | } 60 | 61 | /** 62 | Fills pool with a set number of objects, creating instances 63 | according to passed arguments. If the number of objects is 64 | greater than the capacity, the pool will be filled to 65 | capacity. 66 | @param count Number of objects to fill the pool with 67 | @param args Array of arguments to pass to the constructor 68 | **/ 69 | public function fill(count : Int, ?args : Array = []) : Void { 70 | while (count-- > 0) 71 | pool[available++] = Type.createInstance(Class, args); 72 | } 73 | 74 | /** 75 | Gets an object from the pool if available, or creates a new 76 | instance with the provided arguments. 77 | @param args Array of arguments to pass to the constructor 78 | @return Object 79 | **/ 80 | public function get(?args : Array = []) : T { 81 | if (available == 0) return Type.createInstance(Class, args); 82 | return pool[--available]; 83 | } 84 | 85 | @:noCompletion inline function get_available() return available; 86 | } 87 | -------------------------------------------------------------------------------- /Sources/mokha/Camera.hx: -------------------------------------------------------------------------------- 1 | package mokha; 2 | 3 | import mokha.shapes.Rectangle; 4 | import mokha.Entity; 5 | import mokha.Object; 6 | 7 | import kha.graphics2.Graphics; 8 | import kha.math.FastMatrix3; 9 | 10 | class Camera extends Object { 11 | public var deadzone : Rectangle; 12 | public var viewport : Rectangle; 13 | public var transformation : FastMatrix3; 14 | public var target : Entity; 15 | public var followStyle : CameraFollowStyle; 16 | 17 | var x : Float; 18 | var y : Float; 19 | 20 | public function new() : Void { 21 | super(); 22 | 23 | x = 0; 24 | y = 0; 25 | 26 | deadzone = new Rectangle(0, 0, Mokha.renderWidth, Mokha.renderHeight); 27 | viewport = new Rectangle(0, 0, Mokha.renderWidth, Mokha.renderHeight); 28 | transformation = FastMatrix3.identity(); 29 | target = null; 30 | followStyle = null; 31 | } 32 | 33 | override public function update() : Void { 34 | super.update(); 35 | 36 | follow(); 37 | } 38 | 39 | override public function draw(g : Graphics) : Void { 40 | super.draw(g); 41 | 42 | g.transformation = transformation; 43 | } 44 | 45 | override public function destroy() : Void { 46 | super.destroy(); 47 | 48 | viewport = null; 49 | transformation = null; 50 | } 51 | 52 | function follow() : Void { 53 | if (target != null || followStyle != null) { 54 | switch (followStyle) { 55 | case CameraFollowStyle.LockOn: 56 | transformation = FastMatrix3.translation(-target.body.x + viewport.width / 2, -target.body.y + viewport.height / 2); 57 | case CameraFollowStyle.RoomByRoom: 58 | if (target.body.centroidX + x > viewport.width) { 59 | transformation = transformation.multmat(FastMatrix3.translation(-viewport.width, 0)); 60 | x += -viewport.width; 61 | } 62 | 63 | if (target.body.centroidX + x < viewport.x) { 64 | transformation = transformation.multmat(FastMatrix3.translation(viewport.width, 0)); 65 | x += viewport.width; 66 | } 67 | 68 | if (target.body.centroidY + y> viewport.height) { 69 | transformation = transformation.multmat(FastMatrix3.translation(0, -viewport.height)); 70 | y += -viewport.height; 71 | } 72 | 73 | if (target.body.centroidY + y < viewport.y) { 74 | transformation = transformation.multmat(FastMatrix3.translation(0, viewport.height)); 75 | y += viewport.height; 76 | } 77 | case _: 78 | } 79 | } 80 | } 81 | } 82 | 83 | enum CameraFollowStyle { 84 | LockOn; 85 | RoomByRoom; 86 | } 87 | -------------------------------------------------------------------------------- /Sources/mokha/ui/Text.hx: -------------------------------------------------------------------------------- 1 | package mokha.ui; 2 | 3 | import mokha.Entity; 4 | 5 | import kha.graphics2.Graphics; 6 | import kha.Color; 7 | import kha.Font; 8 | 9 | /** 10 | A rudimentary text label UI element. 11 | **/ 12 | class Text extends Entity { 13 | /** 14 | Text font family. 15 | **/ 16 | public var font(default, set) : Null; 17 | 18 | /** 19 | Text background color. 20 | **/ 21 | public var backgroundColor : Color; 22 | 23 | /** 24 | Text foreground color. 25 | **/ 26 | public var foregroundColor : Color; 27 | 28 | /** 29 | Text font size. 30 | **/ 31 | public var size(default, set) : Null; 32 | 33 | /** 34 | Text value. 35 | **/ 36 | public var value(default, set) : Null; 37 | 38 | /** 39 | Creates new text object. 40 | @param value Text string 41 | @param font Text font family 42 | @param size Text font size 43 | **/ 44 | public function new(value : String, font : Font, size : Int) : Void { 45 | super(0, 0, 0, 0); 46 | 47 | this.font = font; 48 | this.size = size; 49 | this.value = value; 50 | 51 | backgroundColor = Color.fromValue(0x00000000); 52 | foregroundColor = Color.White; 53 | } 54 | 55 | /** 56 | Draws text. 57 | **/ 58 | override public function draw(g : Graphics) : Void { 59 | super.draw(g); 60 | 61 | g.color = backgroundColor; 62 | g.fillRect(0, 0, body.width, body.height); 63 | g.color = foregroundColor; 64 | g.font = font; 65 | g.fontSize = size; 66 | g.drawString(value, 0, 0); 67 | g.color = Color.White; 68 | } 69 | 70 | /** 71 | Updates text height. 72 | **/ 73 | 74 | function updateHeight() : Void { 75 | if (font == null || size == null) return; 76 | body.height = font.height(size); 77 | } 78 | 79 | /** 80 | Updates text width. 81 | **/ 82 | function updateWidth() : Void { 83 | if (font == null || size == null || value == null) return; 84 | body.width = font.width(size, value); 85 | } 86 | 87 | /** 88 | Sets font, updating text dimensions in the process. 89 | @param font Font 90 | @return Font 91 | **/ 92 | @:noCompletion inline function set_font(font : Font) { 93 | this.font = font; 94 | updateHeight(); 95 | updateWidth(); 96 | return font; 97 | } 98 | 99 | /** 100 | Sets size, updating text dimensions in the process. 101 | @param size Size 102 | @return Size 103 | **/ 104 | @:noCompletion inline function set_size(size : Int) { 105 | this.size = size; 106 | updateHeight(); 107 | updateWidth(); 108 | return size; 109 | } 110 | 111 | /** 112 | Sets value, updating text dimensions in the process. 113 | @param value Value 114 | @return Value 115 | **/ 116 | @:noCompletion inline function set_value(value : String) { 117 | this.value = value; 118 | updateWidth(); 119 | return value; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /Sources/mokha/utils/animation/Animator.hx: -------------------------------------------------------------------------------- 1 | package mokha.utils.animation; 2 | 3 | import mokha.utils.animation.Animation; 4 | import mokha.utils.animation.AnimationFrame; 5 | import mokha.Mokha; 6 | import mokha.Sprite; 7 | 8 | import kha.graphics2.Graphics; 9 | 10 | /** 11 | An animator is a tool for animating sprite sheets. 12 | **/ 13 | class Animator { 14 | /** 15 | Frame rectangle. 16 | **/ 17 | var frame : AnimationFrame; 18 | 19 | /** 20 | Time accumulator. 21 | **/ 22 | var timeAccumulator : Float; 23 | 24 | /** 25 | Animation map. 26 | **/ 27 | var animations : Map; 28 | 29 | /** 30 | Current animation. 31 | **/ 32 | var animation : Animation; 33 | 34 | /** 35 | Number of frames horizontally along the spritesheet. 36 | **/ 37 | var frameColumns : Int; 38 | 39 | /** 40 | Number of frames vertically along the spritesheet. 41 | **/ 42 | var frameRows : Int; 43 | 44 | /** 45 | Creates new animator. 46 | @param frameWidth Animation frame width 47 | @param frameHeight Animation frame height 48 | @param sheetWidth Sprite sheet width 49 | @param sheetHeight Sprite sheet height 50 | **/ 51 | public function new(frameWidth : Float, frameHeight : Float, sheetWidth : Float, sheetHeight : Float) : Void { 52 | this.frame = new AnimationFrame(frameWidth, frameHeight); 53 | 54 | this.frameColumns = Std.int(sheetWidth / frameWidth); 55 | this.frameRows = Std.int(sheetHeight / frameHeight); 56 | this.timeAccumulator = 0; 57 | this.animations = new Map(); 58 | this.animation = null; 59 | } 60 | 61 | /** 62 | Updates animator. 63 | **/ 64 | public function update() : Void { 65 | if (this.animation == null) return; 66 | 67 | this.timeAccumulator += Mokha.delta; 68 | 69 | if (this.timeAccumulator < this.animation.frameRate) return; 70 | 71 | this.animation.update(); 72 | this.frame.update(this.animation.frameIndex, frameRows, frameColumns); 73 | this.timeAccumulator = 0; 74 | } 75 | 76 | /** 77 | Draws current animation frame. 78 | **/ 79 | public function draw(g : Graphics, sprite : Sprite) : Void { 80 | g.drawSubImage(sprite.graphic, 0, 0, this.frame.x, this.frame.y, this.frame.width, this.frame.height); 81 | } 82 | 83 | /** 84 | Destroys animator. 85 | **/ 86 | public function destroy() : Void { 87 | this.animation = null; 88 | this.animations = null; 89 | this.frame = null; 90 | } 91 | 92 | /** 93 | Adds new animation. 94 | @param label Animation label 95 | @param frames Array of animation frame indices 96 | @param frameRate Animation frame rate 97 | @param repeat If `true`, animation restarts when done 98 | **/ 99 | public function add(label : String, frames : Array, frameRate : Float, repeat : Bool) : Void { 100 | this.animations.set(label, new Animation(frames, frameRate, repeat)); 101 | } 102 | 103 | /** 104 | Uses animation. 105 | @param label Animation label 106 | **/ 107 | public function use(label : String) : Void { 108 | this.animation = this.animations.get(label); 109 | this.animation.reset(); 110 | this.frame.update(this.animation.frameIndex, frameRows, frameColumns); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Sources/mokha/math/Random.hx: -------------------------------------------------------------------------------- 1 | package mokha.math; 2 | 3 | import kha.math.Random; 4 | 5 | /** 6 | This class is a wrapper around Kha's `Random` class that provides 7 | additional functionality, such as returning random boolean or 8 | signed values. Best used as a single, reused instance. 9 | **/ 10 | class Random { 11 | /** 12 | Internal reference to Kha's pseudorandom number generator. 13 | **/ 14 | var random : kha.math.Random; 15 | 16 | /** 17 | Creates new pseudorandom number generator. 18 | @param seed Number generation seed, defaults to 1 19 | **/ 20 | public function new(?seed : Int = 1) : Void { 21 | this.random = new kha.math.Random(seed); 22 | } 23 | 24 | /** 25 | Returns a pseudorandom boolean state. Probability is expected 26 | to be in range [0, 1]. 27 | @param probability Probability of `true`, defaults to 0.5 28 | **/ 29 | public inline function bool(probability : Float = 0.5) : Bool { 30 | return probability > floatIn(0, 1); 31 | } 32 | 33 | /** 34 | Returns a pseudorandom floating point value. 35 | **/ 36 | public inline function float() : Float { 37 | return random.GetFloat(); 38 | } 39 | 40 | /** 41 | Returns a pseudorandom floating point value in [min, max] 42 | range. 43 | @param min Smallest possible number, included 44 | @param max Largest possible number, included 45 | **/ 46 | public inline function floatIn(min : Float, max : Float) : Float { 47 | return random.GetFloatIn(min, max); 48 | } 49 | 50 | /** 51 | Returns a pseudorandom floating point value in [0, max] range. 52 | @param max Largest possible number, included 53 | **/ 54 | public inline function floatUpTo(max : Float) : Float { 55 | return floatIn(0, max); 56 | } 57 | 58 | /** 59 | Returns a pseudorandom integer. 60 | **/ 61 | public inline function int() : Int { 62 | return random.Get(); 63 | } 64 | 65 | /** 66 | Returns a pseudorandom integer in range [min, max]. 67 | @param min Smallest possible number, included 68 | @param max Largest possible number, included 69 | **/ 70 | public inline function intIn(min : Int, max : Int) : Int { 71 | return random.GetIn(min, max); 72 | } 73 | 74 | /** 75 | Returns a pseudorandom integer in range [0, max]. 76 | @param max Largest possible number, included 77 | **/ 78 | public inline function intUpTo(max : Int) : Int { 79 | return random.GetUpTo(max); 80 | } 81 | 82 | /** 83 | Returns a pseudorandom sign (-1 or 1). Probability is expected 84 | to be in range [0, 1]. 85 | @param probability Probability of sign (-1), defaults to 0.5 86 | **/ 87 | public inline function sign(probability : Float = 0.5) : Int { 88 | return bool(probability) ? -1 : 1; 89 | } 90 | 91 | /** 92 | For an array of weights (probabilities), returns the index of 93 | the element whose weight was picked. For optimal results, the 94 | sum of all weights should equal 1. 95 | @param weights An array of weights in range [0, 1] 96 | **/ 97 | public function weightedPick(weights : Array) : Int { 98 | var weightSum = 0.0; 99 | for (weight in weights) weightSum += weight; 100 | 101 | var roll = floatUpTo(weightSum); 102 | for (i in 0...weights.length) { 103 | if (weights[i] > roll) return i; 104 | roll -= weights[i]; 105 | } 106 | 107 | return 0; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Sources/mokha/collections/Group.hx: -------------------------------------------------------------------------------- 1 | package mokha.collections; 2 | 3 | import mokha.Object; 4 | 5 | import kha.graphics2.Graphics; 6 | 7 | /** 8 | A group is a collection of objects. 9 | **/ 10 | class Group extends Object { 11 | /** 12 | Called after an object in the group is updated. 13 | **/ 14 | public var onUpdate : T->Void; 15 | 16 | /** 17 | Called after an object in the group is drawn. 18 | **/ 19 | public var onDraw : T->Void; 20 | 21 | /** 22 | Called after an object in the group is destroyed. 23 | **/ 24 | public var onDestroy : Void->Void; 25 | 26 | /** 27 | Called after an object is added to the group. 28 | **/ 29 | public var onAdd : T->Void; 30 | 31 | /** 32 | Called after an object is removed from the group. 33 | **/ 34 | public var onRemove : T->Void; 35 | 36 | /** 37 | Group size. 38 | **/ 39 | public var size(get, null) : Int; 40 | 41 | /** 42 | Group members array. 43 | **/ 44 | var members : Array; 45 | 46 | /** 47 | Creates new group. 48 | **/ 49 | public function new() : Void { 50 | super(); 51 | this.members = new Array(); 52 | } 53 | 54 | /** 55 | Updates all members (if both group and group member are active). 56 | **/ 57 | override public function update() : Void { 58 | if (this.isActive) { 59 | for (m in this.members) { 60 | if (m.isActive) { 61 | m.update(); 62 | if (onUpdate != null) onUpdate(m); 63 | } 64 | } 65 | } 66 | } 67 | 68 | /** 69 | Draws all members (if both group and group member are visible). 70 | @param g G2 API access to framebuffer 71 | **/ 72 | override public function draw(g : Graphics) : Void { 73 | if (this.isVisible) { 74 | for (m in this.members) { 75 | if (m.isVisible) { 76 | m.draw(g); 77 | if (onDraw != null) onDraw(m); 78 | } 79 | } 80 | } 81 | } 82 | 83 | /** 84 | Nulls references. 85 | **/ 86 | override public function destroy() : Void { 87 | super.destroy(); 88 | this.each(function (m : T) { 89 | m.destroy(); 90 | if (onDestroy != null) onDestroy(); 91 | }); 92 | this.members = null; 93 | } 94 | 95 | /** 96 | Adds member to group. 97 | @param m Member to add 98 | @return Added group member 99 | **/ 100 | public function add(m : T) : T { 101 | this.members.push(m); 102 | if (onAdd != null) onAdd(m); 103 | return m; 104 | } 105 | 106 | /** 107 | Removes member from group. 108 | @param m Member to remove 109 | @return Removed group member 110 | **/ 111 | public function remove(m : T) : T { 112 | var index = this.members.indexOf(m); 113 | if (index < 0) return null; 114 | this.members.splice(index, 1); 115 | if (onRemove != null) onRemove(m); 116 | return m; 117 | } 118 | 119 | /** 120 | Iterates over every member of group and executes function on it. 121 | @param f Callback function 122 | **/ 123 | public function each(f : T->Void) : Void { 124 | for (m in this.members) f(m); 125 | } 126 | 127 | /** 128 | Exposes the iterator of the members array. 129 | @return Iterator over group members 130 | **/ 131 | public function iterator() : Iterator { 132 | return this.members.iterator(); 133 | } 134 | 135 | @:noCompletion inline function get_size() return members.length; 136 | } 137 | -------------------------------------------------------------------------------- /Sources/mokha/math/noise/PerlinNoise.hx: -------------------------------------------------------------------------------- 1 | package mokha.math.noise; 2 | 3 | /* 4 | Improved Perlin noise, by Ken Perlin. 5 | Source: http://mrl.nyu.edu/~perlin/noise/ 6 | */ 7 | class PerlinNoise { 8 | public static function noise(x : Float, y : Float, z : Float) : Float { 9 | var X = Std.int(Math.floor(x)) & 255; 10 | var Y = Std.int(Math.floor(y)) & 255; 11 | var Z = Std.int(Math.floor(z)) & 255; 12 | 13 | x -= Math.floor(x); 14 | y -= Math.floor(y); 15 | z -= Math.floor(z); 16 | 17 | var u : Float = fade(x); 18 | var v : Float = fade(y); 19 | var w : Float = fade(z); 20 | 21 | var A : Int = p[X] + Y; 22 | var AA : Int = p[A] + Z; 23 | var AB : Int = p[A + 1] + Z; 24 | 25 | var B : Int = p[X + 1] + Y; 26 | var BA : Int = p[B] + Z; 27 | var BB : Int = p[B + 1] + Z; 28 | 29 | return lerp(w, 30 | lerp(v, 31 | lerp(u, 32 | grad(p[AA ], x , y , z ), 33 | grad(p[BA ], x - 1, y , z ) 34 | ), 35 | lerp(u, 36 | grad(p[AB ], x , y - 1, z ), 37 | grad(p[BB ], x - 1, y - 1, z ) 38 | ) 39 | ), 40 | lerp(v, 41 | lerp(u, 42 | grad(p[AA + 1], x , y , z - 1), 43 | grad(p[BA + 1], x - 1, y , z - 1) 44 | ), 45 | lerp(u, 46 | grad(p[AB + 1], x , y - 1, z - 1), 47 | grad(p[BB + 1], x - 1, y - 1, z - 1) 48 | ) 49 | ) 50 | ); 51 | } 52 | 53 | static inline function fade(t : Float) : Float { 54 | return t * t * t * (t * (t * 6 - 15) + 10); 55 | } 56 | 57 | static inline function lerp(t : Float, a : Float, b : Float) : Float { 58 | return a + t * (b - a); 59 | } 60 | 61 | static function grad(hash : Int, x : Float, y : Float, z : Float) : Float { 62 | var h : Int = hash & 15; 63 | var u : Float = (h < 8) ? x : y; 64 | var v : Float = (h < 4) ? y : (((h == 12) || (h == 14)) ? x : z); 65 | return (((h & 1) == 0) ? u : -u) + (((h & 2) == 0) ? v : -v); 66 | } 67 | 68 | static var permutation : Array = [ 151, 160, 137, 91, 90, 69 | 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 70 | 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 71 | 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 72 | 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 73 | 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 74 | 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 75 | 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 76 | 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 77 | 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 78 | 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 79 | 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 80 | 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 81 | 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 82 | 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 83 | 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 84 | 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 85 | 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 86 | 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180 ]; 87 | 88 | static var p : Array = [for (i in 0...512) permutation[Std.int(i % permutation.length)]]; 89 | } -------------------------------------------------------------------------------- /Sources/mokha/shapes/Rectangle.hx: -------------------------------------------------------------------------------- 1 | package mokha.shapes; 2 | 3 | /** 4 | Rectangle shape with collision logic. 5 | **/ 6 | class Rectangle { 7 | /** 8 | Rectangle x position. 9 | **/ 10 | public var x : Float; 11 | 12 | /** 13 | Rectangle y position. 14 | **/ 15 | public var y : Float; 16 | 17 | /** 18 | Rectangle width. 19 | **/ 20 | public var width : Float; 21 | 22 | /** 23 | Rectangle height. 24 | **/ 25 | public var height : Float; 26 | 27 | /** 28 | Centroid X position. 29 | **/ 30 | public var centroidX(get, null) : Float; 31 | 32 | /** 33 | Centroid Y position. 34 | **/ 35 | public var centroidY(get, null) : Float; 36 | 37 | /** 38 | Creates new rectangle. 39 | @param x Horizontal position (top left corner) 40 | @param y Vertical position (top left corner) 41 | @param width Rectangle width 42 | @param height Rectangle height 43 | **/ 44 | public function new(x : Float, y : Float, width : Float, height : Float) : Void { 45 | this.x = x; 46 | this.y = y; 47 | this.width = width; 48 | this.height = height; 49 | } 50 | 51 | /** 52 | Shorthand for position setting. 53 | **/ 54 | public function setPosition(x : Float, y : Float) : Void { 55 | this.x = x; 56 | this.y = y; 57 | } 58 | 59 | /** 60 | Shorthand for size setting. 61 | **/ 62 | public function setSize(width : Float, height : Float) : Void { 63 | this.width = width; 64 | this.height = height; 65 | } 66 | 67 | /** 68 | Shorthand for centroid setting. 69 | **/ 70 | public function setCentroid(x : Float, y : Float) : Void { 71 | this.x = x - width / 2; 72 | this.y = y - height / 2; 73 | } 74 | 75 | /** 76 | Checks if rectangle area contains point. 77 | @param x Point horizontal position 78 | @param y Point vertical position 79 | @return `true` if point is within rectangle 80 | **/ 81 | public function containsPoint(x : Float, y : Float) : Bool { 82 | if (this.x > x) return false; 83 | if (this.y > y) return false; 84 | if ((this.x + width) < x) return false; 85 | if ((this.y + width) < y) return false; 86 | return true; 87 | } 88 | 89 | /** 90 | Checks if rectangle area contains another rectangle. 91 | @param r Object rectangle 92 | @return `true` if object rectangle is completely contained within subject 93 | **/ 94 | public function containsRectangle(r : Rectangle) : Bool { 95 | if ((x + width) < (r.x + r.width)) return false; 96 | if (x > r.x) return false; 97 | if ((y + height) < (r.y + r.height)) return false; 98 | if (y > r.y) return false; 99 | return true; 100 | } 101 | 102 | /** 103 | Checks if rectangle area overlaps another rectangle. 104 | @param r Object rectangle 105 | @return `true` if object rectangle is completely or partially overlapping subject 106 | **/ 107 | public function overlapsRectangle(r : Rectangle) : Bool { 108 | if (x > (r.x + r.width)) return false; 109 | if (y > (r.y + r.height)) return false; 110 | if ((x + width) < r.x) return false; 111 | if ((y + width) < r.y) return false; 112 | return true; 113 | } 114 | 115 | /** 116 | Gets X position of centroid. 117 | @return X position of centroid 118 | **/ 119 | inline function get_centroidX() : Float { 120 | return x + width / 2; 121 | } 122 | 123 | /** 124 | Gets Y position of centroid. 125 | @return Y position of centroid 126 | **/ 127 | inline function get_centroidY() : Float { 128 | return y + height / 2; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Sources/mokha/Game.hx: -------------------------------------------------------------------------------- 1 | package mokha; 2 | 3 | import mokha.Mokha; 4 | 5 | import kha.graphics2.ImageScaleQuality; 6 | import kha.Color; 7 | import kha.Framebuffer; 8 | import kha.Image; 9 | import kha.Scaler; 10 | import kha.System; 11 | 12 | /** 13 | The game class prepares a backbuffer to which states draw. The 14 | backbuffer is defined by the game's rendering resolution (width 15 | and height). It also handles states and state changes. 16 | **/ 17 | class Game { 18 | /** 19 | Backbuffer to which current state draws. 20 | **/ 21 | var backbuffer : Image; 22 | 23 | /** 24 | Reference to current state. 25 | **/ 26 | var state : State; 27 | 28 | /** 29 | Image scale quality, in case the backbuffer is scaled to 30 | framebuffer size. 31 | **/ 32 | var imageScaleQuality : ImageScaleQuality; 33 | 34 | /** 35 | Game configuration structure. 36 | **/ 37 | var config : GameConfig; 38 | 39 | /** 40 | Creates a new game. 41 | @param config Game configuration structure 42 | **/ 43 | public function new(config : GameConfig) : Void { 44 | if (config.smoothScaling == null) config.smoothScaling = true; 45 | if (config.width == null) config.width = Mokha.windowWidth; 46 | if (config.height == null) config.height = Mokha.windowHeight; 47 | if (config.backbuffer == null) config.backbuffer = { clear : true, color: Color.Black }; 48 | if (config.framebuffer == null) config.framebuffer = { clear : true, color: Color.Black }; 49 | 50 | this.config = config; 51 | 52 | backbuffer = Image.createRenderTarget(config.width, config.height); 53 | 54 | imageScaleQuality = config.smoothScaling ? ImageScaleQuality.High : ImageScaleQuality.Low; 55 | 56 | Mokha.game = this; 57 | Mokha.renderWidth = config.width; 58 | Mokha.renderHeight = config.height; 59 | 60 | switchState(config.state); 61 | } 62 | 63 | /** 64 | Override this. Updates current game state. 65 | **/ 66 | public function update() : Void { 67 | if (state != null) state.update(); 68 | } 69 | 70 | /** 71 | Override this. Draws current game state. Backbuffer is 72 | scaled to fit the framebuffer, which has its dimensions 73 | defined by the window. 74 | @param f Framebuffer 75 | **/ 76 | public function draw(f : Framebuffer) : Void { 77 | var bg = backbuffer.g2; 78 | 79 | bg.begin(config.backbuffer.clear, config.backbuffer.color); 80 | if (state != null) state.draw(bg); 81 | bg.end(); 82 | 83 | var fg = f.g2; 84 | 85 | fg.begin(config.framebuffer.clear, config.framebuffer.color); 86 | fg.imageScaleQuality = imageScaleQuality; 87 | Scaler.scale(backbuffer, f, System.screenRotation); 88 | fg.end(); 89 | } 90 | 91 | /** 92 | Switches current state to a different one. Current state is 93 | destroyed before the next state is created. 94 | @param state State class 95 | **/ 96 | public function switchState(state : Class) : Void { 97 | if (this.state != null) this.state.onDestroy(); 98 | this.state = Type.createInstance(state, []); 99 | this.state.onCreate(); 100 | } 101 | } 102 | 103 | /** 104 | Game configuration options. 105 | @param state Initial game state 106 | @param ?width Render width 107 | @param ?height Render height 108 | @param ?backbuffer Backbuffer configuration 109 | @param ?framebuffer Framebuffer configuration 110 | @param ?smoothScaling If `true`, backbuffer scaling will be anti-aliased 111 | **/ 112 | typedef GameConfig = { 113 | state : Class, 114 | ?width : Int, 115 | ?height : Int, 116 | ?backbuffer : BufferConfig, 117 | ?framebuffer : BufferConfig, 118 | ?smoothScaling : Bool 119 | } 120 | 121 | /** 122 | Buffer configuration options. 123 | @param clear If `true`, buffer is cleared 124 | @param color Buffer clear color 125 | **/ 126 | typedef BufferConfig = { 127 | clear : Bool, 128 | color : Color 129 | } 130 | -------------------------------------------------------------------------------- /Sources/mokha/utils/tween/Tweener.hx: -------------------------------------------------------------------------------- 1 | package mokha.utils.tween; 2 | 3 | import mokha.Mokha; 4 | 5 | /** 6 | A basic implementation of a tweening engine. Tweens are 7 | cleared as soon as they're finished - they are not 8 | reusable. 9 | **/ 10 | class Tweener { 11 | /** 12 | The total number of tweens handled by the tweener. 13 | **/ 14 | var tweenCount : Int; 15 | 16 | /** 17 | A map of all active tweens. 18 | **/ 19 | var tweens : Map; 20 | 21 | /** 22 | A reference to the currently updated tween, kept 23 | separate for the sake of performance. 24 | **/ 25 | var tween : Tween; 26 | 27 | /** 28 | Total running time of tweener. 29 | **/ 30 | var runningTime : Float; 31 | 32 | /** 33 | Creates new tweener. 34 | **/ 35 | public function new() : Void { 36 | tweenCount = 0; 37 | tweens = new Map(); 38 | tween = null; 39 | runningTime = 0; 40 | } 41 | 42 | /** 43 | Updates running time and tweens. Ensures tweens 44 | which are not active any more get destroyed and 45 | removed from the map. 46 | **/ 47 | public function update() : Void { 48 | runningTime += Mokha.delta; 49 | 50 | for(key in tweens.keys()) { 51 | tween = tweens.get(key); 52 | if (tween.endTime >= runningTime) { 53 | tween.update((runningTime - tween.startTime) / tween.duration); 54 | } else { 55 | tween.destroy(); 56 | tweens.remove(key); 57 | } 58 | } 59 | } 60 | 61 | /** 62 | Starts a tween, adding it to the tweener's map of 63 | tweens. Tweens are mapped to an integer key unique 64 | to the current tweener. 65 | @param from Value to tween from 66 | @param to Value to tween to 67 | @param duration Tween duration 68 | @param callback Function to execute with every tween 69 | **/ 70 | public function start(from : Float, to : Float, duration : Float, callback : Float->Void) : Void { 71 | tweens.set(tweenCount, new Tween(runningTime, from, to, duration, callback)); 72 | tweenCount += 1; 73 | } 74 | 75 | /** 76 | Destroys tweener, nulling references. 77 | **/ 78 | public function destroy() : Void { 79 | tweens = null; 80 | tween = null; 81 | } 82 | } 83 | 84 | /** 85 | A basic tween object. A tween specifies a `from` and 86 | `to` value which is interpolated according to a 87 | tweening function. A tween also has a start time, 88 | duration, and end time, all of which are used to 89 | determine whether a tween should still be handled by the 90 | tweener (tweening engine). 91 | **/ 92 | private class Tween { 93 | /** 94 | Time when tween was created. 95 | **/ 96 | public var startTime(get, null) : Float; 97 | 98 | /** 99 | Time when tween will end. 100 | **/ 101 | public var endTime(get, null) : Float; 102 | 103 | /** 104 | Time it takes for tween to finish. 105 | **/ 106 | public var duration(get, null) : Float; 107 | 108 | /** 109 | Value from which to interpolate. 110 | **/ 111 | var from : Float; 112 | 113 | /** 114 | Value to which to interpolate. 115 | **/ 116 | var to : Float; 117 | 118 | /** 119 | Callback function which is called with the interpolated 120 | value. 121 | **/ 122 | var callback : Float->Void; 123 | 124 | /** 125 | Creates a new tween. 126 | @param startTime Time at which tween was created 127 | @param from Value from which to interpolate 128 | @param to Value to which to interpolate 129 | @param duration Time until tween completion 130 | @param callback Function which is called with the interpolated value 131 | **/ 132 | public function new(startTime : Float, from : Float, to : Float, duration : Float, callback : Float->Void) { 133 | this.startTime = startTime; 134 | this.duration = duration; 135 | 136 | this.from = from; 137 | this.to = to; 138 | this.callback = callback; 139 | 140 | endTime = startTime + duration; 141 | } 142 | 143 | /** 144 | Updates tween according to a tweening function 145 | (currently, linear interpolation). 146 | @param t Ratio of time passed and tween end time. 147 | **/ 148 | public function update(t : Float) : Void { 149 | callback((1 - t) * from + t * to); 150 | } 151 | 152 | /** 153 | Destroys tween, nulling references. 154 | **/ 155 | public function destroy() : Void { 156 | callback = null; 157 | } 158 | 159 | @:noCompletion inline function get_startTime() return startTime; 160 | 161 | @:noCompletion inline function get_endTime() return endTime; 162 | 163 | @:noCompletion inline function get_duration() return duration; 164 | } 165 | -------------------------------------------------------------------------------- /Sources/mokha/ui/TextArea.hx: -------------------------------------------------------------------------------- 1 | package mokha.ui; 2 | 3 | import mokha.Entity; 4 | 5 | import kha.graphics2.Graphics; 6 | import kha.Color; 7 | import kha.Font; 8 | 9 | /** 10 | A text area defines a rectangular area in which text is wrapped 11 | to fit. Text which exceeds the area is not drawn. 12 | **/ 13 | class TextArea extends Entity { 14 | /** 15 | Font family. 16 | **/ 17 | public var font(default, set) : Font; 18 | 19 | /** 20 | Font size. 21 | **/ 22 | public var size(default, set) : Int; 23 | 24 | /** 25 | Text area value. 26 | **/ 27 | public var value(default, set) : String; 28 | 29 | /** 30 | Text area background color. 31 | **/ 32 | public var backgroundColor : Color; 33 | 34 | /** 35 | Text area foreground color. 36 | **/ 37 | public var foregroundColor : Color; 38 | 39 | /** 40 | Lines of text that fit within the text area. 41 | **/ 42 | var lines : Array; 43 | 44 | /** 45 | Creates new text area. 46 | @param width Text area width 47 | @param height Text area height 48 | @param font Text area font family 49 | @param size Text area font size 50 | **/ 51 | public function new(width : Int, height : Int, font : Font, size : Int) : Void { 52 | super(0, 0, width, height); 53 | 54 | this.font = font; 55 | this.size = size; 56 | 57 | lines = new Array(); 58 | 59 | value = ""; 60 | backgroundColor = Color.fromValue(0x00000000); 61 | foregroundColor = Color.White; 62 | } 63 | 64 | /** 65 | Draws text area and text that fits within it. 66 | **/ 67 | override public function draw(g : Graphics) : Void { 68 | super.draw(g); 69 | 70 | g.color = backgroundColor; 71 | g.fillRect(0, 0, body.width, body.height); 72 | g.color = foregroundColor; 73 | g.font = font; 74 | g.fontSize = size; 75 | for (i in 0...lines.length) { 76 | if (lines[i] != "") { 77 | g.drawString(lines[i], 0, i * font.height(size)); 78 | } 79 | } 80 | g.color = Color.White; 81 | } 82 | 83 | /** 84 | Prepares visible lines of text area value, with regard to 85 | text area dimensions, font family, and font size. 86 | **/ 87 | function prepareLines() : Void { 88 | if (this.value == null || this.value == "") return; 89 | 90 | lines = new Array(); 91 | 92 | var lineWidth : Float = 0; 93 | var lineHeight = font.height(size); 94 | var line = new StringBuf(); 95 | 96 | var wordWidth : Float = 0; 97 | var linesHeight : Float = 0; 98 | 99 | // Every sentence should be its own line, if possible 100 | for (sentence in ~/\n|\r/g.split(value)) { 101 | if (linesHeight + lineHeight > body.height) break; 102 | // Sentences should be broken into new lines if too long 103 | for (word in ~/\s/g.split(sentence)) { 104 | wordWidth = 0; 105 | word += " "; 106 | 107 | for (char in word.split("")) { 108 | wordWidth += font.width(size, char); 109 | } 110 | 111 | if (lineWidth + wordWidth <= body.width) { 112 | line.add(word); 113 | lineWidth += wordWidth; 114 | } else { 115 | if (linesHeight + lineHeight > body.height - lineHeight) break; 116 | lines.push(line.toString()); 117 | line = new StringBuf(); 118 | line.add(word); 119 | lineWidth = wordWidth; 120 | linesHeight += lineHeight; 121 | } 122 | } 123 | lines.push(line.toString()); 124 | line = new StringBuf(); 125 | lineWidth = 0; 126 | linesHeight += lineHeight; 127 | } 128 | } 129 | 130 | /** 131 | Internal. Sets new font family and prepares new lines. 132 | @param font Font family 133 | @return Font family 134 | **/ 135 | @:noCompletion inline function set_font(font : Font) : Font { 136 | this.font = font; 137 | prepareLines(); 138 | return font; 139 | } 140 | 141 | /** 142 | Internal. Sets new font size and prepares new lines. 143 | @param size Font size 144 | @return Font size 145 | **/ 146 | @:noCompletion inline function set_size(size : Int) : Int { 147 | this.size = size; 148 | prepareLines(); 149 | return size; 150 | } 151 | 152 | /** 153 | Internal. Sets new text area value and prepares new lines. 154 | @param value Text area value 155 | @return Text area value 156 | **/ 157 | @:noCompletion inline function set_value(value : String) : String { 158 | this.value = value; 159 | prepareLines(); 160 | return value; 161 | } 162 | } -------------------------------------------------------------------------------- /Sources/mokha/utils/collision/QuadTree.hx: -------------------------------------------------------------------------------- 1 | package mokha.utils.collision; 2 | 3 | import mokha.shapes.Rectangle; 4 | import mokha.Entity; 5 | 6 | import haxe.ds.Vector; 7 | 8 | /** 9 | QuadTrees are a space partitioning data structure useful for 10 | collisions in 2D space. The screen is split into four regions, 11 | each of which can in itself be another QuadTree. Objects are 12 | added to the QuadTree and passed grouped in these regions. 13 | Afterwards, collision checks can be only on those objects in 14 | a specific region, instead of all of them. 15 | **/ 16 | class QuadTree extends Rectangle { 17 | /** 18 | Maximum number of QuadTree subdivisions. 19 | **/ 20 | var maxDepth : Int; 21 | 22 | /** 23 | Maximum number of entities in a region before it is split. 24 | **/ 25 | var maxEntities : Int; 26 | 27 | /** 28 | Region depth level. 29 | **/ 30 | var depth : Int; 31 | 32 | /** 33 | Region entities. 34 | **/ 35 | var entities : Array; 36 | 37 | /** 38 | Region nodes (subdivisions). 39 | **/ 40 | var nodes : Vector; 41 | 42 | /** 43 | Creates a QuadTree. 44 | @param x Region X position 45 | @param y Region Y position 46 | @param width Region width 47 | @param height Region height 48 | @param maxDepth Maximum depth level 49 | @param maxEntities Maximum entity count 50 | @param depth Current depth level 51 | **/ 52 | function new(x : Float, y : Float, width : Float, height : Float, maxDepth : Int, maxEntities : Int, depth : Int) : Void { 53 | super(x, y, width, height); 54 | this.depth = depth; 55 | this.maxDepth = maxDepth; 56 | this.maxEntities = maxEntities; 57 | clear(); 58 | } 59 | 60 | /** 61 | Gets an existing QuadTree root or creates a new one. 62 | @param x Region X position 63 | @param y Region Y position 64 | @param width Region width 65 | @param height Region height 66 | @param maxDepth Maximum depth level 67 | @param maxEntities Maximum entity count 68 | @return QuadTree instance, the root of all other generated trees 69 | **/ 70 | public static function create(x : Float, y : Float, width : Float, height : Float, maxDepth : Int, maxEntities : Int) : QuadTree { 71 | return new QuadTree(x, y, width, height, maxDepth, maxEntities, 0); 72 | } 73 | 74 | /** 75 | Clears QuadTree by removing all entities and nodes. 76 | **/ 77 | public function clear() : Void { 78 | this.entities = new Array(); 79 | 80 | if (nodes != null) { 81 | for (node in nodes) node.clear(); 82 | this.nodes = null; 83 | } 84 | } 85 | 86 | /** 87 | Inserts item into QuadTree. 88 | @param entity Entity item 89 | **/ 90 | public function insert(entity : Entity) : Void { 91 | if (this.entities.length >= this.maxEntities && this.depth < this.maxDepth) { 92 | if (this.nodes == null) split(); 93 | forward(entity); 94 | } 95 | 96 | this.entities.push(entity); 97 | } 98 | 99 | /** 100 | Fetches the environment surrounding an entity in the QuadTree. 101 | @param entity Entity item 102 | @return Array of entities surrounding the observed entity 103 | **/ 104 | public function fetch(entity : Entity) : Array { 105 | var environment = new Array(); 106 | 107 | if (this.nodes != null) { 108 | for (node in this.nodes) { 109 | if (entity.collider.hitbox.overlapsRectangle(node)) { 110 | environment = environment.concat(node.fetch(entity)); 111 | } 112 | } 113 | } else { 114 | environment = environment.concat(this.entities); 115 | } 116 | 117 | return environment; 118 | } 119 | 120 | /** 121 | Destroys QuadTree. 122 | **/ 123 | public function destroy() : Void { 124 | this.entities = null; 125 | for (node in this.nodes) node.destroy(); 126 | this.nodes = null; 127 | } 128 | 129 | #if mokha_debug 130 | public function draw(g : kha.graphics2.Graphics) : Void { 131 | g.color = kha.Color.Cyan; 132 | g.drawRect(this.x, this.y, this.width, this.height); 133 | if (this.nodes != null) { 134 | for (node in this.nodes) node.draw(g); 135 | } 136 | g.color = kha.Color.White; 137 | } 138 | #end 139 | 140 | /** 141 | Splits QuadTree region into four smaller QuadTrees. 142 | **/ 143 | function split() : Void { 144 | this.nodes = new Vector(4); 145 | 146 | var nodeWidth = this.width / 2; 147 | var nodeHeight = this.height / 2; 148 | 149 | this.nodes[0] = new QuadTree(x + nodeWidth, y , nodeWidth, nodeHeight, maxDepth, maxEntities, depth + 1); 150 | this.nodes[1] = new QuadTree(x + nodeWidth, y + nodeHeight, nodeWidth, nodeHeight, maxDepth, maxEntities, depth + 1); 151 | this.nodes[2] = new QuadTree(x , y + nodeHeight, nodeWidth, nodeHeight, maxDepth, maxEntities, depth + 1); 152 | this.nodes[3] = new QuadTree(x , y , nodeWidth, nodeHeight, maxDepth, maxEntities, depth + 1); 153 | 154 | for (entity in this.entities) forward(entity); 155 | } 156 | 157 | /** 158 | Forwards item into appropriate node. 159 | @param entity Entity item 160 | **/ 161 | function forward(entity : Entity) : Void { 162 | for (node in this.nodes) { 163 | if (entity.collider.hitbox.overlapsRectangle(node)) { 164 | node.insert(entity); 165 | } 166 | } 167 | } 168 | } -------------------------------------------------------------------------------- /Sources/mokha/managers/input/KeyboardInputManager.hx: -------------------------------------------------------------------------------- 1 | package mokha.managers.input; 2 | 3 | import kha.input.Keyboard; 4 | import kha.Key; 5 | 6 | /** 7 | Manages keyboard input. 8 | **/ 9 | class KeyboardInputManager { 10 | /** 11 | Reference to existing keyboard input manager instance. 12 | **/ 13 | static var instance : KeyboardInputManager; 14 | 15 | /** 16 | A map of keys that are or were pressed during this frame. 17 | If value is `true`, key remains pressed. 18 | **/ 19 | public var keysPressed(get, null) : Map; 20 | 21 | /** 22 | A map of special keys that are or were pressed during this 23 | frame. If value is `true`, key remains pressed. 24 | **/ 25 | public var specialKeysPressed(get, null) : Map; 26 | 27 | /** 28 | `true` if any key is pressed or remains pressed this frame. 29 | **/ 30 | public var isPressed(get, null) : Bool; 31 | 32 | /** 33 | `true` if any key was pressed for this frame. 34 | **/ 35 | public var justPressed(get, null) : Bool; 36 | 37 | /** 38 | Last key pressed in this frame. Equal to empty string 39 | if no key was pressed. 40 | **/ 41 | public var justPressedKey(get, null) : String; 42 | 43 | /** 44 | Last special key pressed in this frame. Equal to empty 45 | string if no special key was pressed. 46 | **/ 47 | public var justPressedSpecialKey(get, null) : String; 48 | 49 | /** 50 | `true` if any key was released this frame. 51 | **/ 52 | public var justReleased(get, null) : Bool; 53 | 54 | /** 55 | Number of keys pressed. 56 | **/ 57 | public var keysPressedCount(get, null) : Int; 58 | 59 | /** 60 | Number of special keys pressed. 61 | **/ 62 | public var specialKeysPressedCount(get, null) : Int; 63 | 64 | /** 65 | Last key released in this frame. Equal to empty string if no 66 | key was released. 67 | **/ 68 | public var justReleasedKey(get, null) : String; 69 | 70 | /** 71 | Last special key released in this frame. Equal to empty 72 | string if no special key was released. 73 | **/ 74 | public var justReleasedSpecialKey(get, null) : String; 75 | 76 | /** 77 | Creates new keyboard input manager. 78 | **/ 79 | function new() : Void { 80 | Keyboard.get().notify(onDown, onUp); 81 | keysPressed = new Map(); 82 | specialKeysPressed = new Map(); 83 | keysPressedCount = 0; 84 | specialKeysPressedCount = 0; 85 | } 86 | 87 | /** 88 | Gets or creates keyboard input manager instance. 89 | **/ 90 | public static function get() : KeyboardInputManager { 91 | if (instance == null) instance = new KeyboardInputManager(); 92 | return instance; 93 | } 94 | 95 | /** 96 | Updates keyboard input manager state. 97 | **/ 98 | public function update() : Void { 99 | justPressed = false; 100 | justPressedKey = ""; 101 | justPressedSpecialKey = ""; 102 | 103 | justReleased = false; 104 | justReleasedKey = ""; 105 | justReleasedSpecialKey = ""; 106 | 107 | for (key in keysPressed.keys()) { 108 | if (!keysPressed.get(key)) { 109 | keysPressed.remove(key); 110 | } 111 | } 112 | 113 | for (key in specialKeysPressed.keys()) { 114 | if (!specialKeysPressed.get(key)) { 115 | specialKeysPressed.remove(key); 116 | } 117 | } 118 | 119 | if (keysPressedCount == 0 && specialKeysPressedCount == 0) { 120 | isPressed = false; 121 | } 122 | } 123 | 124 | /** 125 | Performs a check to see if a key is pressed this frame. 126 | Keys are stored as lowercase strings. 127 | @param key Keyboard key 128 | @return `true` if key is pressed 129 | **/ 130 | public function isPressedKey(key : String) : Bool { 131 | return isPressed && keysPressed.exists(key) && keysPressed.get(key); 132 | } 133 | 134 | /** 135 | Performs a check to see if a special key is pressed this 136 | frame. Keys are stored as lowercase strings. 137 | @param key Special keyboard key 138 | @return `true` if key is pressed 139 | **/ 140 | public function isPressedSpecialKey(key : String) : Bool { 141 | return isPressed && specialKeysPressed.exists(key) && specialKeysPressed.get(key); 142 | } 143 | 144 | /** 145 | Handles the event of a keyboard key being held down. 146 | @param key Keyboard key 147 | @param char Character (if applicable) 148 | **/ 149 | function onDown(key : Key, char : String) : Void { 150 | justPressedSpecialKey = key.getName().toLowerCase(); 151 | specialKeysPressed.set(justPressedSpecialKey, true); 152 | specialKeysPressedCount += 1; 153 | 154 | if (key.match(Key.CHAR)) { 155 | justPressedKey = char.toLowerCase(); 156 | keysPressed.set(justPressedKey, true); 157 | keysPressedCount += 1; 158 | } 159 | 160 | isPressed = true; 161 | justPressed = true; 162 | } 163 | 164 | /** 165 | Handles the event of a keyboard key being released. 166 | @param key Keyboard key 167 | @param char Character (if applicable) 168 | **/ 169 | function onUp(key : Key, char : String) : Void { 170 | justReleasedSpecialKey = key.getName().toLowerCase(); 171 | specialKeysPressed.set(justReleasedSpecialKey, false); 172 | specialKeysPressedCount -= 1; 173 | 174 | if (key.match(Key.CHAR)) { 175 | justReleasedKey = char.toLowerCase(); 176 | keysPressed.set(justReleasedKey, false); 177 | keysPressedCount -= 1; 178 | } 179 | 180 | justReleased = true; 181 | } 182 | 183 | @:noCompletion inline function get_keysPressed() return keysPressed; 184 | 185 | @:noCompletion inline function get_specialKeysPressed() return specialKeysPressed; 186 | 187 | @:noCompletion inline function get_isPressed() return isPressed; 188 | 189 | @:noCompletion inline function get_justPressed() return justPressed; 190 | 191 | @:noCompletion inline function get_justPressedKey() return justPressedKey; 192 | 193 | @:noCompletion inline function get_justPressedSpecialKey() return justPressedSpecialKey; 194 | 195 | @:noCompletion inline function get_justReleased() return justReleased; 196 | 197 | @:noCompletion inline function get_justReleasedKey() return justReleasedKey; 198 | 199 | @:noCompletion inline function get_justReleasedSpecialKey() return justReleasedSpecialKey; 200 | 201 | @:noCompletion inline function get_keysPressedCount() return keysPressedCount; 202 | 203 | @:noCompletion inline function get_specialKeysPressedCount() return specialKeysPressedCount; 204 | } 205 | -------------------------------------------------------------------------------- /Sources/mokha/managers/input/MouseInputManager.hx: -------------------------------------------------------------------------------- 1 | package mokha.managers.input; 2 | 3 | import mokha.Mokha; 4 | 5 | import kha.input.Mouse; 6 | 7 | /** 8 | Helper class for managing mouse / touch input. 9 | **/ 10 | class MouseInputManager { 11 | /** 12 | Mouse input manager instance. 13 | **/ 14 | static var instance : MouseInputManager; 15 | 16 | /** 17 | Pointer X position. 18 | **/ 19 | public var x(get, null) : Int; 20 | 21 | /** 22 | Pointer Y position. 23 | **/ 24 | public var y(get, null) : Int; 25 | 26 | /** 27 | Mouse wheel movement delta. 28 | **/ 29 | public var dw(get, null) : Int; 30 | 31 | /** 32 | Pointer horizontal movement delta. 33 | **/ 34 | public var dx(get, null) : Int; 35 | 36 | /** 37 | Pointer vertical movement delta. 38 | **/ 39 | public var dy(get, null) : Int; 40 | 41 | /** 42 | A map of buttons pressed during a frame. 43 | **/ 44 | public var buttonsPressed(get, null) : Map; 45 | 46 | /** 47 | `true` if any button is pressed or remains pressed this frame. 48 | **/ 49 | public var isPressed(get, null) : Bool; 50 | 51 | /** 52 | `true` if any button was pressed last frame. 53 | **/ 54 | public var justPressed(get, null) : Bool; 55 | 56 | /** 57 | `true` if any button was released last frame. 58 | **/ 59 | public var justReleased(get, null) : Bool; 60 | 61 | /** 62 | Number of buttons pressed. 63 | **/ 64 | public var buttonsPressedCount(get, null) : Int; 65 | 66 | /** 67 | Horizontal scale factor. 68 | **/ 69 | var scaleX : Float; 70 | 71 | /** 72 | Vertical scale factor. 73 | **/ 74 | var scaleY : Float; 75 | 76 | /** 77 | Creates new mouse input manager. 78 | **/ 79 | function new() : Void { 80 | Mouse.get().notify(onMouseDown, onMouseUp, onMouseMove, onMouseWheel); 81 | buttonsPressed = new Map(); 82 | buttonsPressedCount = 0; 83 | scaleX = Mokha.renderWidth / Mokha.windowWidth; 84 | scaleY = Mokha.renderHeight / Mokha.windowHeight; 85 | } 86 | 87 | /** 88 | Gets existing mouse input manager if possible, otherwise 89 | creates a new instance. 90 | @return Mouse input manager instance 91 | **/ 92 | public static function get() : MouseInputManager { 93 | if (instance == null) instance = new MouseInputManager(); 94 | return instance; 95 | } 96 | 97 | /** 98 | Updates tracked mouse input states. 99 | **/ 100 | public function update() : Void { 101 | dy = 0; 102 | dx = 0; 103 | dw = 0; 104 | justPressed = false; 105 | justReleased = false; 106 | 107 | for (button in buttonsPressed.keys()) 108 | if(!buttonsPressed.get(button)) 109 | buttonsPressed.remove(button); 110 | 111 | if (buttonsPressedCount == 0) isPressed = false; 112 | } 113 | 114 | /** 115 | Performs a check to see if a button is currently pressed. 116 | Buttons are referenced by their integer IDs. 117 | @param b Button ID 118 | @return `true` if button is pressed 119 | **/ 120 | public function isPressedButton(b : Int) : Bool { 121 | return isPressed && buttonsPressed.exists(b) && buttonsPressed.get(b); 122 | } 123 | 124 | /** 125 | Performs a check to see if a button was just pressed in the 126 | last frame. Buttons are referenced by their integer IDs. 127 | @param b Button ID 128 | @return `true` if button was just pressed 129 | **/ 130 | public function justPressedButton(b : Int) : Bool { 131 | return justPressed && buttonsPressed.exists(b) && buttonsPressed.get(b); 132 | } 133 | 134 | /** 135 | Performs a check to see if a button was just released in the 136 | last frame. Buttons are referenced by their Integer IDs. 137 | @param b Button ID 138 | @return `true` if button was just released 139 | **/ 140 | public function justReleasedButton(b : Int) : Bool { 141 | return justReleased && buttonsPressed.exists(b) && !buttonsPressed.get(b); 142 | } 143 | 144 | /** 145 | Handles the event of a mouse button being held down. 146 | @param b Mouse button ID 147 | @param x Mouse pointer X position 148 | @param y Mouse pointer Y position 149 | **/ 150 | function onMouseDown(b : Int, x : Int, y : Int) : Void { 151 | buttonsPressed.set(b, true); 152 | buttonsPressedCount += 1; 153 | setX(x); 154 | setY(y); 155 | isPressed = true; 156 | justPressed = true; 157 | } 158 | 159 | /** 160 | Handles the event of a mouse button being released. 161 | @param b Mouse button ID 162 | @param x Mouse pointer X position 163 | @param y Mouse pointer Y position 164 | **/ 165 | function onMouseUp(b : Int, x : Int, y : Int) : Void { 166 | buttonsPressed.set(b, false); 167 | buttonsPressedCount -= 1; 168 | setX(x); 169 | setY(y); 170 | justReleased = true; 171 | } 172 | 173 | /** 174 | Handles the event of the mouse pointer being moved. 175 | @param x Mouse pointer X position 176 | @param y Mouse pointer Y position 177 | @param dx Mouse pointer horizontal movement delta 178 | @param dy Mouse pointer vertical movement delta 179 | **/ 180 | function onMouseMove(x : Int, y : Int, dx : Int, dy : Int) : Void { 181 | setX(x); 182 | setY(y); 183 | this.dx = dx; 184 | this.dy = dy; 185 | } 186 | 187 | /** 188 | Handles the event of the mouse wheel being used. 189 | @param dw Mouse wheel movement delta 190 | **/ 191 | function onMouseWheel(dw : Int) : Void { 192 | this.dw = dw; 193 | } 194 | 195 | /** 196 | Sets mouse X position according to scale factor. 197 | @param x Mouse X position 198 | **/ 199 | inline function setX(x : Int) : Void { 200 | this.x = (scaleX == 1) ? x : Std.int(x * scaleX); 201 | } 202 | 203 | /** 204 | Sets mouse Y position according to scale factor. 205 | @param y Mouse Y position 206 | **/ 207 | inline function setY(y : Int) : Void { 208 | this.y = (scaleY == 1) ? y : Std.int(y * scaleY); 209 | } 210 | 211 | @:noCompletion inline function get_x() return x; 212 | 213 | @:noCompletion inline function get_y() return y; 214 | 215 | @:noCompletion inline function get_dw() return dw; 216 | 217 | @:noCompletion inline function get_dx() return dx; 218 | 219 | @:noCompletion inline function get_dy() return dy; 220 | 221 | @:noCompletion inline function get_buttonsPressed() return buttonsPressed; 222 | 223 | @:noCompletion inline function get_isPressed() return isPressed; 224 | 225 | @:noCompletion inline function get_justPressed() return justPressed; 226 | 227 | @:noCompletion inline function get_justReleased() return justReleased; 228 | 229 | @:noCompletion inline function get_buttonsPressedCount() return buttonsPressedCount; 230 | } 231 | --------------------------------------------------------------------------------