├── images ├── 1.png ├── 2.png ├── 3.png └── 4.png ├── LICENSE ├── README.md ├── src └── tink.js └── bin ├── tink.js └── tink.js.map /images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kittykatattack/tink/HEAD/images/1.png -------------------------------------------------------------------------------- /images/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kittykatattack/tink/HEAD/images/2.png -------------------------------------------------------------------------------- /images/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kittykatattack/tink/HEAD/images/3.png -------------------------------------------------------------------------------- /images/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kittykatattack/tink/HEAD/images/4.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Tink - Interactivity utilities for Pixi (v3.0.11) 2 | ======================================= 3 | 4 | Tink is an easy-to-use set of interactivity tools for the Pixi 5 | rendering engine. You can use Tink to easily create: 6 | 7 | - Drag and drop objects. 8 | - Click-able, touch-able buttons with customizable actions. 9 | - A universal pointer object that works for both touch and and the 10 | mouse. 11 | - Interactive sprites that behave like buttons. 12 | 13 | (Important! This library targets Pixi v3.0.11, which is the most stable version of Pixi, and is the only version I can recommend using. This library will eventually be upgraded for Pixi v4 when the v4 branch matures.) 14 | 15 | [Setting up](#settingup)
16 | [A universal pointer](#auniversalpointer)
17 | [Pointer interaction with sprites](#pointerinteractionwithsprites)
18 | [Drag and drop sprites](#draganddropsprites)
19 | [Buttons](#buttons)
20 | [Making buttons](#makingbuttons)
21 | [Making an interactive sprite](#makinganinteractivesprite)
22 | [Keyboard control](#keyboardcontrol)
23 | [Setting Tink's optional scale](#scale)
24 | 25 | Let's find out how to use Tink. 26 | 27 | 28 | Setting up 29 | ---------- 30 | 31 | First, link to the `tink.js` script in your HTML file. 32 | ```js 33 | 34 | ``` 35 | Then create a new instance of Tink at the beginning of your JavaScript program. 36 | Supply it with a reference to your running PIXI instance and the `renderer.view` object (the HTML5 canvas). 37 | ```js 38 | let t = new Tink(PIXI, renderer.view); 39 | ``` 40 | The variable `t` now represents your running Tink instance. Generally you should 41 | create a new Tink instance after all the resources have loaded. 42 | 43 | Next call Tink's `update` method inside your game loop to update all of Tink's 44 | interactive objects each frame. Here's a simple game loop that will do 45 | the trick: 46 | ```js 47 | function gameLoop(){ 48 | 49 | //Start the loop 50 | requestAnimationFrame(gameLoop); 51 | 52 | //Update Tink 53 | t.update(); 54 | 55 | //Optionally, you probably also want to render your root Pixi 56 | //container, the `stage` object, in this loop: 57 | //renderer.render(stage); 58 | } 59 | ``` 60 | This is what you need to do to get started with the examples ahead. 61 | 62 | 63 | A universal pointer 64 | ------------------- 65 | 66 | Tink lets you make a pointer object that automatically figures 67 | out whether the user is interacting with a mouse or with touch. 68 | Use Tink's `makePointer` method to create a pointer. 69 | ```js 70 | pointer = t.makePointer(); 71 | ``` 72 | Usually one pointer will be enough for most games or applications, 73 | but you can make as many as you need. (Does your game or application 74 | require complex multi-touch interaction with gestures? Then consider 75 | using an excellent HTML5 library called [hammer.js](http://hammerjs.github.io/getting-started/). 76 | 77 | The `pointer` object has three user-definable methods that you can 78 | program: `press`, `release`, and `tap`. `press` is triggered when the 79 | left mouse button is pressed down, or the user presses his or her finger 80 | to the device screen. `release` is triggered when the mouse button is 81 | released, or the user lifts his or her from the screen. `tap` is triggered 82 | if the left mouse button is clicked, or the user taps the screen. 83 | 84 | Here's an example of how you can define these methods on the `pointer`: 85 | ```js 86 | pointer.press = () => console.log("The pointer was pressed"); 87 | pointer.release = () => console.log("The pointer was released"); 88 | pointer.tap = () => console.log("The pointer was tapped"); 89 | ``` 90 | Also use the `tap` method to capture mouse clicks. 91 | 92 | The `pointer` also has `x` and `y` properties that tell you its position 93 | on the canvas (Pixi's `renderer.view`.) 94 | ```js 95 | pointer.x 96 | pointer.y 97 | ``` 98 | It also has three Boolean properties that tell you pointer's current 99 | state: `isUp`, `isDown` and `tapped`. 100 | ```js 101 | pointer.isUp 102 | pointer.isDown 103 | pointer.tapped 104 | ``` 105 | 106 | If you need to hide the pointer for some reason, use the Boolean `visible` 107 | property. 108 | ```js 109 | //Hide the pointer 110 | pointer.visible = false; 111 | 112 | //Make the pointer visible 113 | pointer.visible = true; 114 | ``` 115 | 116 | 117 | Pointer interaction with sprites 118 | -------------------------------- 119 | 120 | The `pointer` has a `hitTestSprite` method that you can use to find out 121 | if the pointer is touching a sprite. 122 | ```js 123 | pointer.hitTestSprite(anySprite); 124 | ``` 125 | If the pointer is within the rectangular area of a sprite, 126 | `hitTestSprite` will return `true`. 127 | 128 | `hitTestSprite` will also work with circular sprites. Just add a property 129 | to a sprite called `circular` and set it to `true`. 130 | ```js 131 | anyCircularSprite.circular = true; 132 | ``` 133 | This flags `hitTestSprite` to use a circular collision detection algorithm 134 | instead of the default rectangular one. If you want to display a hand icon 135 | while the pointer is over sprite the you can set the pointer's 136 | `cursor` property to `"pointer"`. Setting it `"auto"` when the pointer 137 | leaves the sprite's area will display the default arrow icon. Here's some 138 | sample code you could use inside your game loop to enable this 139 | feature. 140 | ```js 141 | if (pointer.hitTestSprite(anySprite)) { 142 | 143 | //Display a hand icon while the pointer is over the sprite 144 | pointer.cursor = "pointer"; 145 | } 146 | else { 147 | 148 | //Display the default arrow icon when the 149 | //pointer moves outside the sprite's area 150 | pointer.cursor = "auto"; 151 | } 152 | ``` 153 | `pointer.cursor` just references the HTML5 canvas's element's 154 | `style.cursor` property to achieve this, using two standard values 155 | from the HTML5 spec: `"pointer"` and `"auto"`. You can assign any cursor style 156 | value that you like. (A web search for "HTML style.cursor" will turn up a complete 157 | list of possible values.) You can also set this manually if you want 158 | to through Pixi's `renderer.view` object. Here's how: 159 | ```js 160 | renderer.view.style.cursor = "cursorStyle"; 161 | ``` 162 | These cursor styles will only be visible on a mouse-based interface; 163 | on a touch interface they're ignored. 164 | 165 | 166 | Drag and drop sprites 167 | --------------------- 168 | 169 | You can add drag-and-drop functionality to a sprite with Tink's 170 | `makeDraggable` method. Just supply it with a single sprite, or a list of 171 | sprites, that you want to make draggable. 172 | ```js 173 | t.makeDraggable(cat, tiger, hedgehog); 174 | ``` 175 | You can then use the mouse or touch to drag the sprites around the 176 | canvas. 177 | 178 | ![Drag and drop](images/1.png) 179 | 180 | When you select a draggable sprite, its stacking order changes so that it 181 | appears above the other sprites. The mouse's arrow icon also changes 182 | to a hand when its over a draggable sprite. 183 | 184 | Draggable sprites have a Boolean property called `draggable` that is 185 | set to `true`. To disable dragging, set `draggable` to `false`. 186 | ```js 187 | anySprite.draggable = false; 188 | ``` 189 | Setting it back to `true` will enable dragging again. 190 | 191 | To completely remove a sprite (or list of sprites) from the drag-and-drop 192 | system, use the `makeUndraggable` method, like this: 193 | ```js 194 | t.makeUndraggable(cat, tiger, hedgehog); 195 | ``` 196 | Drag-and-drop is a fundamental interactive feature that can be used as 197 | the basis for making puzzles games, matching games, or sophisticated 198 | user interfaces. 199 | 200 | 201 | Buttons 202 | ------- 203 | Buttons are an important UI component that you'll definitely want to use 204 | in your games and applications. Tink has a useful button method that 205 | lets you quickly create them. Before I show you how to make buttons, 206 | lets first find out what buttons actually are, can how you can use them. 207 | 208 | ### What are buttons? 209 | 210 | You can think of buttons as "clickable/touchable sprites". The most important 211 | thing you need to know about buttons is that they have **states** and 212 | **actions**. 213 | *States* define what the button looks like, and actions define what it does. 214 | Most buttons have three states: 215 | 216 | - **Up**: When the pointer is not touching the button. 217 | - **Over**: When the pointer is over the button. 218 | - **Down**: When the pointer is pressing down on the button. 219 | 220 | ![Button state](images/2.png) 221 | 222 | Touch-based interfaces need only two states: up and down. 223 | 224 | With the button object that you'll learn to make in the next section, 225 | you be able access these states through the button's `state` property, like this: 226 | ```js 227 | playButton.state 228 | ``` 229 | The `state` property could have the string value `"up"`, `"over"`, 230 | or `"down"`, which you could use in your game logic. 231 | 232 | Buttons also have *actions*: 233 | 234 | - **Press**: When the pointer presses the button. 235 | - **Release**: When the pointer is released from the button. 236 | - **Over**: When the pointer moves into the button's area. 237 | - **Out**: When the pointer moves out of the button's area. 238 | - **Tap**: When the button has been tapped (or clicked.) 239 | 240 | You can define these actions as user-definable methods, like this: 241 | ```js 242 | playButton.press = () => console.log("pressed"); 243 | playButton.release = () => console.log("released"); 244 | playButton.over = () => console.log("over"); 245 | playButton.out = () => console.log("out"); 246 | playButton.tap = () => console.log("tapped"); 247 | ``` 248 | In the button object that we'll make ahead, you'll able to access the button's 249 | "pressed" and "released" actions in a string property, like this: 250 | ```js 251 | playButton.action 252 | ``` 253 | Got it? Good! So how do we actually make buttons? 254 | 255 | 256 | ### Making buttons 257 | 258 | First, start with three images that define the three button states. 259 | You might call them "up.png", "over.png", and "down.png". Then add those three 260 | images to a tileset, or as frames in a texture atlas. 261 | 262 | ![Texture atlas](images/3.png) 263 | 264 | Although having three image states is standard, sometimes buttons have only two image states. 265 | This is particularly true of touch-only buttons, which don't have an "over" state. The 266 | button object that we're going to make ahead will use three images if they're available, 267 | but if it only has two, Tink will assign these to the "up" and "down" states. 268 | 269 | Next, publish the texture atlas and load it into your program: 270 | ```js 271 | PIXI.loader 272 | .add("images/button.json") 273 | .load(setup); 274 | ``` 275 | Then in the `setup` function where you initialize your sprites, create an array that 276 | references each of the three button frames in this order: up, over and down. 277 | ```js 278 | function setup() { 279 | 280 | //Create an alias for the texture atlas frame ids 281 | let id = PIXI.loader.resources["images/button.json"].textures; 282 | 283 | let buttonFrames = [ 284 | id["up.png"], 285 | id["over.png"], 286 | id["down.png"] 287 | ]; 288 | } 289 | ``` 290 | These don't have to be frame ids: you can use an array of any Pixi 291 | textures, like single image textures if you want to. 292 | 293 | Finally, use Tink's `button` method to create the button. Supply the 294 | `buttonFrames` array as the first argument. 295 | ```js 296 | let playButton = t.button(buttonFrames, 32, 96); 297 | ``` 298 | The second and third optional arguments are the button's `x` and `y` position. 299 | And don't forget to add the button to the `stage` (Pixi's root 300 | container object)! 301 | ```js 302 | stage.addChild(playButton); 303 | ``` 304 | You now have a useful button object that you can use in any game or 305 | application. 306 | 307 | ![Button object](images/4.png) 308 | 309 | At its heart, a button is just an ordinary Pixi `MovieClip` with extra properties 310 | and methods, so you can treat it like any other `MovieClip`object. 311 | 312 | 313 | Making an interactive sprite 314 | ---------------------------- 315 | 316 | Tink has another useful method called `makeInteractive` that lets you add button 317 | properties and methods to any ordinary sprite. 318 | ```js 319 | t.makeInteractive(anySprite); 320 | ``` 321 | This lets you turn any sprite into a button-like object. You can now 322 | assign `press` or `release` methods to the sprite, and access its 323 | `state` and `action` properties, like this: 324 | ```js 325 | anySprite.press = () => { 326 | //Do something when the pointer presses the sprite 327 | }; 328 | 329 | anySprite.release = () => { 330 | //Do something when the pointer is released after pressing the sprite 331 | }; 332 | ``` 333 | If you want any sprite to behave like a button, use `makeInteractive`! 334 | 335 | 336 | Keyboard control 337 | ---------------- 338 | 339 | `keyboard` is a method that listens for and captures keyboard events. It's really 340 | just a convenient wrapper function for HTML `keyup` and `keydown` events 341 | so that you can keep your application code clutter-free and easier to write and read. 342 | Here's how to use the `keyboard` method. Create a new keyboard object like this: 343 | ```js 344 | let keyObject = t.keyboard(asciiKeyCodeNumber); 345 | ``` 346 | Its one argument is the ASCII key code number of the keyboard key 347 | that you want to listen for. [Here's a list of ASCII key codes you can 348 | use](http://www.asciitable.com). 349 | Then assign `press` and `release` methods to the keyboard object like this: 350 | ```js 351 | keyObject.press = () => { 352 | //key object pressed 353 | }; 354 | keyObject.release = () => { 355 | //key object released 356 | }; 357 | ``` 358 | Keyboard objects also have `isDown` and `isUp` Boolean properties that you can use to 359 | check the state of each key. 360 | 361 | Tink has another convenience method called `arrowControl` that lets you 362 | quickly create a 4 direction controller for sprites using the 363 | keyboard arrow keys. It's useful for quickly prototyping game ideas. 364 | Supply the `arrowControl` method with the sprite you want to control 365 | and the pixels per frame that you want it to move: 366 | ```js 367 | t.arrowControl(anySprite, 5); 368 | ``` 369 | Then just update your sprite's velocity inside your game loop, like 370 | this: 371 | ```js 372 | function gameLoop() { 373 | requestAnimationFrame(gameLoop); 374 | 375 | anySprite.x += vx; 376 | anySprite.y += vy 377 | } 378 | ``` 379 | You'll then be able to move the sprite in all four directions using 380 | the arrow keys. 381 | (For the `arrowControl` method to work, your sprite needs to have `vx` and `vy` properties 382 | that refer to the sprite's velocity.) 383 | 384 | 385 | Setting Tink's optional scale 386 | ----------------------------- 387 | 388 | You now know everything you need to know about using Tink, so go ahead and start using it! 389 | But, if you want to re-size or re-scale Pixi's renderer inside the 390 | browser window, there's one more thing you need to know. 391 | 392 | You can use an optional helper function called `scaleToWindow` that 393 | will automatically scale your Pixi renderer to the maximum size of 394 | the browser window. Visit `scaleToWindow`'s [source code repository](https://github.com/kittykatattack/scaleToWindow) to 395 | find out how to use it. Tink's constructor has an optional second argument: `scale`. The 396 | default `scale` value is 1, but if you've re-scaled the canvas using 397 | the `scaleToWindow` function, supply `scaleToWindow`'s return value. 398 | Here's an example of what this might look like: 399 | 400 | First, run `scaleToWindow` and capture the returned `scale` value at the beginning of your program. 401 | ```js 402 | let scale = scaleToWindow(renderer.view); 403 | ``` 404 | Next, create a new instance of Tink and supply the `scale` value as the second argument in the constructor. 405 | ```js 406 | let t = new Tink(PIXI, renderer.view, scale); 407 | ``` 408 | This will ensure that the coordinates that Tink uses will match the canvas's scaled pixel coordinates. 409 | 410 | Finally, make sure to update the Tink scale if you also opted for rescaling the canvas element every time the size of the browser window has changed: 411 | ```js 412 | window.addEventListener("resize", function(event){ 413 | let scale = scaleToWindow(anyCanvasElement); 414 | t.scale(scale) 415 | }); 416 | ``` 417 | -------------------------------------------------------------------------------- /src/tink.js: -------------------------------------------------------------------------------- 1 | class Tink { 2 | constructor(PIXI, element, scale = 1) { 3 | 4 | //Add element and scale properties 5 | this.element = element; 6 | this._scale = scale; 7 | 8 | //An array to store all the draggable sprites 9 | this.draggableSprites = []; 10 | 11 | //An array to store all the pointer objects 12 | //(there will usually just be one) 13 | this.pointers = []; 14 | 15 | //An array to store all the buttons and button-like 16 | //interactive sprites 17 | this.buttons = []; 18 | 19 | //A local PIXI reference 20 | this.PIXI = PIXI; 21 | 22 | //Aliases for Pixi objects 23 | this.TextureCache = this.PIXI.utils.TextureCache; 24 | 25 | //Note: change MovieClip to AnimatedSprite for Pixi v4 26 | this.AnimatedSprite = this.PIXI.extras.MovieClip; 27 | this.Texture = this.PIXI.Texture; 28 | } 29 | 30 | get scale() { 31 | return this._scale; 32 | } 33 | 34 | set scale(value) { 35 | this._scale = value; 36 | 37 | //Update scale values for all pointers 38 | this.pointers.forEach(pointer => pointer.scale = value); 39 | } 40 | 41 | //`makeDraggable` lets you make a drag-and-drop sprite by pushing it 42 | //into the `draggableSprites` array 43 | makeDraggable(...sprites) { 44 | 45 | //If the first argument isn't an array of sprites... 46 | if (!(sprites[0] instanceof Array)) { 47 | sprites.forEach(sprite => { 48 | this.draggableSprites.push(sprite); 49 | 50 | //If the sprite's `draggable` property hasn't already been defined by 51 | //another library, like Hexi, define it 52 | if (sprite.draggable === undefined) { 53 | sprite.draggable = true; 54 | sprite._localDraggableAllocation = true; 55 | } 56 | }); 57 | } 58 | 59 | //If the first argument is an array of sprites... 60 | else { 61 | let spritesArray = sprites[0]; 62 | if (spritesArray.length > 0) { 63 | for (let i = spritesArray.length - 1; i >= 0; i--) { 64 | let sprite = spritesArray[i]; 65 | this.draggableSprites.push(sprite); 66 | 67 | //If the sprite's `draggable` property hasn't already been defined by 68 | //another library, like Hexi, define it 69 | if (sprite.draggable === undefined) { 70 | sprite.draggable = true; 71 | sprite._localDraggableAllocation = true; 72 | } 73 | } 74 | } 75 | } 76 | } 77 | 78 | //`makeUndraggable` removes the sprite from the `draggableSprites` 79 | //array 80 | makeUndraggable(...sprites) { 81 | 82 | //If the first argument isn't an array of sprites... 83 | if (!(sprites[0] instanceof Array)) { 84 | sprites.forEach(sprite => { 85 | this.draggableSprites.splice(this.draggableSprites.indexOf(sprite), 1); 86 | if (sprite._localDraggableAllocation === true) sprite.draggable = false; 87 | }); 88 | } 89 | 90 | //If the first argument is an array of sprites 91 | else { 92 | let spritesArray = sprites[0]; 93 | if (spritesArray.length > 0) { 94 | for (let i = spritesArray.length - 1; i >= 0; i--) { 95 | let sprite = spritesArray[i]; 96 | this.draggableSprites.splice(this.draggableSprites.indexOf(sprite), 1); 97 | if (sprite._localDraggableAllocation === true) sprite.draggable = false; 98 | } 99 | } 100 | } 101 | } 102 | 103 | makePointer(element = this.element, scale = this.scale) { 104 | 105 | //Get a reference to Tink's global `draggableSprites` array 106 | let draggableSprites = this.draggableSprites; 107 | 108 | //Get a reference to Tink's `addGlobalPositionProperties` method 109 | let addGlobalPositionProperties = this.addGlobalPositionProperties; 110 | 111 | //The pointer object will be returned by this function 112 | let pointer = { 113 | element: element, 114 | _scale: scale, 115 | 116 | //Private x and y properties 117 | _x: 0, 118 | _y: 0, 119 | 120 | //Width and height 121 | width: 1, 122 | height: 1, 123 | 124 | //The public x and y properties are divided by the scale. If the 125 | //HTML element that the pointer is sensitive to (like the canvas) 126 | //is scaled up or down, you can change the `scale` value to 127 | //correct the pointer's position values 128 | get x() { 129 | return this._x / this.scale; 130 | }, 131 | get y() { 132 | return this._y / this.scale; 133 | }, 134 | 135 | //Add `centerX` and `centerY` getters so that we 136 | //can use the pointer's coordinates with easing 137 | //and collision functions 138 | get centerX() { 139 | return this.x; 140 | }, 141 | get centerY() { 142 | return this.y; 143 | }, 144 | 145 | //`position` returns an object with x and y properties that 146 | //contain the pointer's position 147 | get position() { 148 | return { 149 | x: this.x, 150 | y: this.y 151 | }; 152 | }, 153 | 154 | get scale() { 155 | return this._scale; 156 | }, 157 | set scale(value) { 158 | this._scale = value; 159 | }, 160 | 161 | //Add a `cursor` getter/setter to change the pointer's cursor 162 | //style. Values can be "pointer" (for a hand icon) or "auto" for 163 | //an ordinary arrow icon. 164 | get cursor() { 165 | return this.element.style.cursor; 166 | }, 167 | set cursor(value) { 168 | this.element.style.cursor = value; 169 | }, 170 | 171 | //Booleans to track the pointer state 172 | isDown: false, 173 | isUp: true, 174 | tapped: false, 175 | 176 | //Properties to help measure the time between up and down states 177 | downTime: 0, 178 | elapsedTime: 0, 179 | 180 | //Optional `press`,`release` and `tap` methods 181 | press: undefined, 182 | release: undefined, 183 | tap: undefined, 184 | 185 | //A `dragSprite` property to help with drag and drop 186 | dragSprite: null, 187 | 188 | //The drag offsets to help drag sprites 189 | dragOffsetX: 0, 190 | dragOffsetY: 0, 191 | 192 | //A property to check whether or not the pointer 193 | //is visible 194 | _visible: true, 195 | get visible() { 196 | return this._visible; 197 | }, 198 | set visible(value) { 199 | if (value === true) { 200 | this.cursor = "auto"; 201 | } else { 202 | this.cursor = "none"; 203 | } 204 | this._visible = value; 205 | }, 206 | 207 | //The pointer's mouse `moveHandler` 208 | moveHandler(event) { 209 | 210 | //Get the element that's firing the event 211 | let element = event.target; 212 | 213 | //Find the pointer’s x and y position (for mouse). 214 | //Subtract the element's top and left offset from the browser window 215 | this._x = (event.pageX - element.offsetLeft); 216 | this._y = (event.pageY - element.offsetTop); 217 | 218 | //Prevent the event's default behavior 219 | event.preventDefault(); 220 | }, 221 | 222 | //The pointer's `touchmoveHandler` 223 | touchmoveHandler(event) { 224 | let element = event.target; 225 | 226 | //Find the touch point's x and y position 227 | this._x = (event.targetTouches[0].pageX - element.offsetLeft); 228 | this._y = (event.targetTouches[0].pageY - element.offsetTop); 229 | event.preventDefault(); 230 | }, 231 | 232 | //The pointer's `downHandler` 233 | downHandler(event) { 234 | 235 | //Set the down states 236 | this.isDown = true; 237 | this.isUp = false; 238 | this.tapped = false; 239 | 240 | //Capture the current time 241 | this.downTime = Date.now(); 242 | 243 | //Call the `press` method if it's been assigned 244 | if (this.press) this.press(); 245 | event.preventDefault(); 246 | }, 247 | 248 | //The pointer's `touchstartHandler` 249 | touchstartHandler(event) { 250 | let element = event.target; 251 | 252 | //Find the touch point's x and y position 253 | this._x = event.targetTouches[0].pageX - element.offsetLeft; 254 | this._y = event.targetTouches[0].pageY - element.offsetTop; 255 | 256 | //Set the down states 257 | this.isDown = true; 258 | this.isUp = false; 259 | this.tapped = false; 260 | 261 | //Capture the current time 262 | this.downTime = Date.now(); 263 | 264 | //Call the `press` method if it's been assigned 265 | if (this.press) this.press(); 266 | event.preventDefault(); 267 | }, 268 | 269 | //The pointer's `upHandler` 270 | upHandler(event) { 271 | 272 | //Figure out how much time the pointer has been down 273 | this.elapsedTime = Math.abs(this.downTime - Date.now()); 274 | 275 | //If it's less than 200 milliseconds, it must be a tap or click 276 | if (this.elapsedTime <= 200 && this.tapped === false) { 277 | this.tapped = true; 278 | 279 | //Call the `tap` method if it's been assigned 280 | if (this.tap) this.tap(); 281 | } 282 | this.isUp = true; 283 | this.isDown = false; 284 | 285 | //Call the `release` method if it's been assigned 286 | if (this.release) this.release(); 287 | 288 | //`event.preventDefault();` needs to be disabled to prevent range sliders 289 | //from getting trapped in Firefox (and possibly Safari) 290 | //event.preventDefault(); 291 | }, 292 | 293 | //The pointer's `touchendHandler` 294 | touchendHandler(event) { 295 | 296 | //Figure out how much time the pointer has been down 297 | this.elapsedTime = Math.abs(this.downTime - Date.now()); 298 | 299 | //If it's less than 200 milliseconds, it must be a tap or click 300 | if (this.elapsedTime <= 200 && this.tapped === false) { 301 | this.tapped = true; 302 | 303 | //Call the `tap` method if it's been assigned 304 | if (this.tap) this.tap(); 305 | } 306 | this.isUp = true; 307 | this.isDown = false; 308 | 309 | //Call the `release` method if it's been assigned 310 | if (this.release) this.release(); 311 | 312 | //event.preventDefault(); 313 | }, 314 | 315 | //`hitTestSprite` figures out if the pointer is touching a sprite 316 | hitTestSprite(sprite) { 317 | 318 | //Add global `gx` and `gy` properties to the sprite if they 319 | //don't already exist 320 | addGlobalPositionProperties(sprite); 321 | 322 | //The `hit` variable will become `true` if the pointer is 323 | //touching the sprite and remain `false` if it isn't 324 | let hit = false; 325 | 326 | //Find out the sprite's offset from its anchor point 327 | let xAnchorOffset, yAnchorOffset; 328 | if (sprite.anchor !== undefined) { 329 | xAnchorOffset = sprite.width * sprite.anchor.x; 330 | yAnchorOffset = sprite.height * sprite.anchor.y; 331 | } else { 332 | xAnchorOffset = 0; 333 | yAnchorOffset = 0; 334 | } 335 | 336 | //Is the sprite rectangular? 337 | if (!sprite.circular) { 338 | 339 | //Get the position of the sprite's edges using global 340 | //coordinates 341 | let left = sprite.gx - xAnchorOffset, 342 | right = sprite.gx + sprite.width - xAnchorOffset, 343 | top = sprite.gy - yAnchorOffset, 344 | bottom = sprite.gy + sprite.height - yAnchorOffset; 345 | 346 | //Find out if the pointer is intersecting the rectangle. 347 | //`hit` will become `true` if the pointer is inside the 348 | //sprite's area 349 | hit = this.x > left && this.x < right && this.y > top && this.y < bottom; 350 | } 351 | 352 | //Is the sprite circular? 353 | else { 354 | //Find the distance between the pointer and the 355 | //center of the circle 356 | let vx = this.x - (sprite.gx + (sprite.width / 2) - xAnchorOffset), 357 | vy = this.y - (sprite.gy + (sprite.width / 2) - yAnchorOffset), 358 | distance = Math.sqrt(vx * vx + vy * vy); 359 | 360 | //The pointer is intersecting the circle if the 361 | //distance is less than the circle's radius 362 | hit = distance < sprite.width / 2; 363 | } 364 | //Check the value of `hit` 365 | return hit; 366 | } 367 | }; 368 | 369 | //Bind the events to the handlers 370 | //Mouse events 371 | element.addEventListener( 372 | "mousemove", pointer.moveHandler.bind(pointer), false 373 | ); 374 | element.addEventListener( 375 | "mousedown", pointer.downHandler.bind(pointer), false 376 | ); 377 | 378 | //Add the `mouseup` event to the `window` to 379 | //catch a mouse button release outside of the canvas area 380 | window.addEventListener( 381 | "mouseup", pointer.upHandler.bind(pointer), false 382 | ); 383 | 384 | //Touch events 385 | element.addEventListener( 386 | "touchmove", pointer.touchmoveHandler.bind(pointer), false 387 | ); 388 | element.addEventListener( 389 | "touchstart", pointer.touchstartHandler.bind(pointer), false 390 | ); 391 | 392 | //Add the `touchend` event to the `window` object to 393 | //catch a mouse button release outside of the canvas area 394 | window.addEventListener( 395 | "touchend", pointer.touchendHandler.bind(pointer), false 396 | ); 397 | 398 | //Disable the default pan and zoom actions on the `canvas` 399 | element.style.touchAction = "none"; 400 | 401 | //Add the pointer to Tink's global `pointers` array 402 | this.pointers.push(pointer); 403 | 404 | //Return the pointer 405 | return pointer; 406 | } 407 | 408 | //Many of Tink's objects, like pointers, use collision 409 | //detection using the sprites' global x and y positions. To make 410 | //this easier, new `gx` and `gy` properties are added to sprites 411 | //that reference Pixi sprites' `getGlobalPosition()` values. 412 | addGlobalPositionProperties(sprite) { 413 | if (sprite.gx === undefined) { 414 | Object.defineProperty( 415 | sprite, 416 | "gx", { 417 | get() { 418 | return sprite.getGlobalPosition().x; 419 | } 420 | } 421 | ); 422 | } 423 | 424 | if (sprite.gy === undefined) { 425 | Object.defineProperty( 426 | sprite, 427 | "gy", { 428 | get() { 429 | return sprite.getGlobalPosition().y; 430 | } 431 | } 432 | ); 433 | } 434 | } 435 | 436 | //A method that implments drag-and-drop functionality 437 | //for each pointer 438 | updateDragAndDrop(draggableSprites) { 439 | 440 | //Create a pointer if one doesn't already exist 441 | if (this.pointers.length === 0) { 442 | this.makePointer(this.element, this.scale); 443 | } 444 | 445 | //Loop through all the pointers in Tink's global `pointers` array 446 | //(there will usually just be one, but you never know) 447 | this.pointers.forEach(pointer => { 448 | 449 | //Check whether the pointer is pressed down 450 | if (pointer.isDown) { 451 | 452 | //You need to capture the co-ordinates at which the pointer was 453 | //pressed down and find out if it's touching a sprite 454 | 455 | //Only run pointer.code if the pointer isn't already dragging 456 | //sprite 457 | if (pointer.dragSprite === null) { 458 | 459 | //Loop through the `draggableSprites` in reverse to start searching at the bottom of the stack 460 | for (let i = draggableSprites.length - 1; i > -1; i--) { 461 | 462 | //Get a reference to the current sprite 463 | let sprite = draggableSprites[i]; 464 | 465 | //Check for a collision with the pointer using `hitTestSprite` 466 | if (pointer.hitTestSprite(sprite) && sprite.draggable) { 467 | 468 | //Calculate the difference between the pointer's 469 | //position and the sprite's position 470 | pointer.dragOffsetX = pointer.x - sprite.gx; 471 | pointer.dragOffsetY = pointer.y - sprite.gy; 472 | 473 | //Set the sprite as the pointer's `dragSprite` property 474 | pointer.dragSprite = sprite; 475 | 476 | //The next two lines re-order the `sprites` array so that the 477 | //selected sprite is displayed above all the others. 478 | //First, splice the sprite out of its current position in 479 | //its parent's `children` array 480 | let children = sprite.parent.children; 481 | children.splice(children.indexOf(sprite), 1); 482 | 483 | //Next, push the `dragSprite` to the end of its `children` array so that it's 484 | //displayed last, above all the other sprites 485 | children.push(sprite); 486 | 487 | //Reorganize the `draggableSpites` array in the same way 488 | draggableSprites.splice(draggableSprites.indexOf(sprite), 1); 489 | draggableSprites.push(sprite); 490 | 491 | //Break the loop, because we only need to drag the topmost sprite 492 | break; 493 | } 494 | } 495 | } 496 | 497 | //If the pointer is down and it has a `dragSprite`, make the sprite follow the pointer's 498 | //position, with the calculated offset 499 | else { 500 | pointer.dragSprite.x = pointer.x - pointer.dragOffsetX; 501 | pointer.dragSprite.y = pointer.y - pointer.dragOffsetY; 502 | } 503 | } 504 | 505 | //If the pointer is up, drop the `dragSprite` by setting it to `null` 506 | if (pointer.isUp) { 507 | pointer.dragSprite = null; 508 | } 509 | 510 | //Change the mouse arrow pointer to a hand if it's over a 511 | //draggable sprite 512 | draggableSprites.some(sprite => { 513 | if (pointer.hitTestSprite(sprite) && sprite.draggable) { 514 | if (pointer.visible) pointer.cursor = "pointer"; 515 | return true; 516 | } else { 517 | if (pointer.visible) pointer.cursor = "auto"; 518 | return false; 519 | } 520 | }); 521 | }); 522 | } 523 | 524 | makeInteractive(o) { 525 | 526 | //The `press`,`release`, `over`, `out` and `tap` methods. They're `undefined` 527 | //for now, but they can be defined in the game program 528 | o.press = o.press || undefined; 529 | o.release = o.release || undefined; 530 | o.over = o.over || undefined; 531 | o.out = o.out || undefined; 532 | o.tap = o.tap || undefined; 533 | 534 | //The `state` property tells you the button's 535 | //current state. Set its initial state to "up" 536 | o.state = "up"; 537 | 538 | //The `action` property tells you whether its being pressed or 539 | //released 540 | o.action = ""; 541 | 542 | //The `pressed` and `hoverOver` Booleans are mainly for internal 543 | //use in this code to help figure out the correct state. 544 | //`pressed` is a Boolean that helps track whether or not 545 | //the sprite has been pressed down 546 | o.pressed = false; 547 | 548 | //`hoverOver` is a Boolean which checks whether the pointer 549 | //has hovered over the sprite 550 | o.hoverOver = false; 551 | 552 | //tinkType is a string that will be set to "button" if the 553 | //user creates an object using the `button` function 554 | o.tinkType = ""; 555 | 556 | //Set `enabled` to true to allow for interactivity 557 | //Set `enabled` to false to disable interactivity 558 | o.enabled = true; 559 | 560 | //Add the sprite to the global `buttons` array so that it can 561 | //be updated each frame in the `updateButtons method 562 | this.buttons.push(o); 563 | } 564 | 565 | //The `updateButtons` method will be called each frame 566 | //inside the game loop. It updates all the button-like sprites 567 | updateButtons() { 568 | 569 | //Create a pointer if one doesn't already exist 570 | if (this.pointers.length === 0) { 571 | this.makePointer(this.element, this.scale); 572 | } 573 | 574 | //Loop through all of Tink's pointers (there will usually 575 | //just be one) 576 | this.pointers.forEach(pointer => { 577 | 578 | pointer.shouldBeHand = false; 579 | 580 | 581 | 582 | //Loop through all the button-like sprites that were created 583 | //using the `makeInteractive` method 584 | this.buttons.forEach(o => { 585 | 586 | //Only do this if the interactive object is enabled 587 | if (o.enabled) { 588 | 589 | //Figure out if the pointer is touching the sprite 590 | let hit = pointer.hitTestSprite(o); 591 | 592 | //1. Figure out the current state 593 | if (pointer.isUp) { 594 | 595 | //Up state 596 | o.state = "up"; 597 | 598 | //Show the first image state frame, if this is a `Button` sprite 599 | if (o.tinkType === "button") o.gotoAndStop(0); 600 | } 601 | 602 | //If the pointer is touching the sprite, figure out 603 | //if the over or down state should be displayed 604 | if (hit) { 605 | 606 | //Over state 607 | o.state = "over"; 608 | 609 | //Show the second image state frame if this sprite has 610 | //3 frames and it's a `Button` sprite 611 | if (o.totalFrames && o.totalFrames === 3 && o.tinkType === "button") { 612 | o.gotoAndStop(1); 613 | } 614 | 615 | //Down state 616 | if (pointer.isDown) { 617 | o.state = "down"; 618 | 619 | //Show the third frame if this sprite is a `Button` sprite and it 620 | //has only three frames, or show the second frame if it 621 | //only has two frames 622 | if (o.tinkType === "button") { 623 | if (o.totalFrames === 3) { 624 | o.gotoAndStop(2); 625 | } else { 626 | o.gotoAndStop(1); 627 | } 628 | } 629 | } 630 | 631 | 632 | 633 | //Flag this pointer to be changed to a hand 634 | pointer.shouldBeHand = true; 635 | //if (pointer.visible) pointer.cursor = "pointer"; 636 | // } else { 637 | // //Turn the pointer to an ordinary arrow icon if the 638 | // //pointer isn't touching a sprite 639 | // if (pointer.visible) pointer.cursor = "auto"; 640 | 641 | //Change the pointer icon to a hand 642 | if (pointer.visible) pointer.cursor = "pointer"; 643 | } else { 644 | //Turn the pointer to an ordinary arrow icon if the 645 | //pointer isn't touching a sprite 646 | if (pointer.visible) pointer.cursor = "auto"; 647 | 648 | } 649 | 650 | //Perform the correct interactive action 651 | 652 | //a. Run the `press` method if the sprite state is "down" and 653 | //the sprite hasn't already been pressed 654 | if (o.state === "down") { 655 | if (!o.pressed) { 656 | if (o.press) o.press(); 657 | o.pressed = true; 658 | o.action = "pressed"; 659 | } 660 | } 661 | 662 | //b. Run the `release` method if the sprite state is "over" and 663 | //the sprite has been pressed 664 | if (o.state === "over") { 665 | if (o.pressed) { 666 | if (o.release) o.release(); 667 | o.pressed = false; 668 | o.action = "released"; 669 | //If the pointer was tapped and the user assigned a `tap` 670 | //method, call the `tap` method 671 | if (pointer.tapped && o.tap) o.tap(); 672 | } 673 | 674 | //Run the `over` method if it has been assigned 675 | if (!o.hoverOver) { 676 | if (o.over) o.over(); 677 | o.hoverOver = true; 678 | } 679 | } 680 | 681 | //c. Check whether the pointer has been released outside 682 | //the sprite's area. If the button state is "up" and it's 683 | //already been pressed, then run the `release` method. 684 | if (o.state === "up") { 685 | if (o.pressed) { 686 | if (o.release) o.release(); 687 | o.pressed = false; 688 | o.action = "released"; 689 | } 690 | 691 | //Run the `out` method if it has been assigned 692 | if (o.hoverOver) { 693 | if (o.out) o.out(); 694 | o.hoverOver = false; 695 | } 696 | } 697 | } 698 | }); 699 | 700 | if (pointer.shouldBeHand) { 701 | pointer.cursor = "pointer"; 702 | } else { 703 | pointer.cursor = "auto"; 704 | } 705 | 706 | 707 | }); 708 | } 709 | 710 | //A function that creates a sprite with 3 frames that 711 | //represent the button states: up, over and down 712 | button(source, x = 0, y = 0) { 713 | 714 | //The sprite object that will be returned 715 | let o; 716 | 717 | //Is it an array of frame ids or textures? 718 | if (typeof source[0] === "string") { 719 | 720 | //They're strings, but are they pre-existing texture or 721 | //paths to image files? 722 | //Check to see if the first element matches a texture in the 723 | //cache 724 | if (this.TextureCache[source[0]]) { 725 | 726 | //It does, so it's an array of frame ids 727 | o = this.AnimatedSprite.fromFrames(source); 728 | } else { 729 | 730 | //It's not already in the cache, so let's load it 731 | o = this.AnimatedSprite.fromImages(source); 732 | } 733 | } 734 | 735 | //If the `source` isn't an array of strings, check whether 736 | //it's an array of textures 737 | else if (source[0] instanceof this.Texture) { 738 | 739 | //Yes, it's an array of textures. 740 | //Use them to make a AnimatedSprite o 741 | o = new this.AnimatedSprite(source); 742 | } 743 | 744 | //Add interactive properties to the button 745 | this.makeInteractive(o); 746 | 747 | //Set the `tinkType` to "button" 748 | o.tinkType = "button"; 749 | 750 | //Position the button 751 | o.x = x; 752 | o.y = y; 753 | 754 | //Return the new button sprite 755 | return o; 756 | } 757 | 758 | //Run the `udpate` function in your game loop 759 | //to update all of Tink's interactive objects 760 | update() { 761 | 762 | //Update the drag and drop system 763 | if (this.draggableSprites.length !== 0) this.updateDragAndDrop(this.draggableSprites); 764 | 765 | //Update the buttons and button-like interactive sprites 766 | if (this.buttons.length !== 0) this.updateButtons(); 767 | } 768 | 769 | /* 770 | `keyboard` is a method that listens for and captures keyboard events. It's really 771 | just a convenient wrapper function for HTML `keyup` and `keydown` events so that you can keep your application code clutter-free and easier to write and read. 772 | 773 | Here's how to use the `keyboard` method. Create a new keyboard object like this: 774 | ```js 775 | let keyObject = keyboard(asciiKeyCodeNumber); 776 | ``` 777 | It's one argument is the ASCII key code number of the keyboard key 778 | that you want to listen for. [Here's a list of ASCII key codes you can 779 | use](http://www.asciitable.com). 780 | Then assign `press` and `release` methods to the keyboard object like this: 781 | ```js 782 | keyObject.press = () => { 783 | //key object pressed 784 | }; 785 | keyObject.release = () => { 786 | //key object released 787 | }; 788 | ``` 789 | Keyboard objects also have `isDown` and `isUp` Boolean properties that you can use to check the state of each key. 790 | */ 791 | keyboard(keyCode) { 792 | let key = {}; 793 | key.code = keyCode; 794 | key.isDown = false; 795 | key.isUp = true; 796 | key.press = undefined; 797 | key.release = undefined; 798 | 799 | //The `downHandler` 800 | key.downHandler = event => { 801 | if (event.keyCode === key.code) { 802 | if (key.isUp && key.press) key.press(); 803 | key.isDown = true; 804 | key.isUp = false; 805 | } 806 | event.preventDefault(); 807 | }; 808 | 809 | //The `upHandler` 810 | key.upHandler = event => { 811 | if (event.keyCode === key.code) { 812 | if (key.isDown && key.release) key.release(); 813 | key.isDown = false; 814 | key.isUp = true; 815 | } 816 | event.preventDefault(); 817 | }; 818 | 819 | //Attach event listeners 820 | window.addEventListener( 821 | "keydown", key.downHandler.bind(key), false 822 | ); 823 | window.addEventListener( 824 | "keyup", key.upHandler.bind(key), false 825 | ); 826 | 827 | //Return the key object 828 | return key; 829 | } 830 | 831 | //`arrowControl` is a convenience method for updating a sprite's velocity 832 | //for 4-way movement using the arrow directional keys. Supply it 833 | //with the sprite you want to control and the speed per frame, in 834 | //pixels, that you want to update the sprite's velocity 835 | arrowControl(sprite, speed) { 836 | 837 | if (speed === undefined) { 838 | throw new Error("Please supply the arrowControl method with the speed at which you want the sprite to move"); 839 | } 840 | 841 | let upArrow = this.keyboard(38), 842 | rightArrow = this.keyboard(39), 843 | downArrow = this.keyboard(40), 844 | leftArrow = this.keyboard(37); 845 | 846 | //Assign key `press` methods 847 | leftArrow.press = () => { 848 | //Change the sprite's velocity when the key is pressed 849 | sprite.vx = -speed; 850 | sprite.vy = 0; 851 | }; 852 | leftArrow.release = () => { 853 | //If the left arrow has been released, and the right arrow isn't down, 854 | //and the sprite isn't moving vertically: 855 | //Stop the sprite 856 | if (!rightArrow.isDown && sprite.vy === 0) { 857 | sprite.vx = 0; 858 | } 859 | }; 860 | upArrow.press = () => { 861 | sprite.vy = -speed; 862 | sprite.vx = 0; 863 | }; 864 | upArrow.release = () => { 865 | if (!downArrow.isDown && sprite.vx === 0) { 866 | sprite.vy = 0; 867 | } 868 | }; 869 | rightArrow.press = () => { 870 | sprite.vx = speed; 871 | sprite.vy = 0; 872 | }; 873 | rightArrow.release = () => { 874 | if (!leftArrow.isDown && sprite.vy === 0) { 875 | sprite.vx = 0; 876 | } 877 | }; 878 | downArrow.press = () => { 879 | sprite.vy = speed; 880 | sprite.vx = 0; 881 | }; 882 | downArrow.release = () => { 883 | if (!upArrow.isDown && sprite.vx === 0) { 884 | sprite.vy = 0; 885 | } 886 | }; 887 | } 888 | } -------------------------------------------------------------------------------- /bin/tink.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 4 | 5 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 6 | 7 | var Tink = (function () { 8 | function Tink(PIXI, element) { 9 | var scale = arguments.length <= 2 || arguments[2] === undefined ? 1 : arguments[2]; 10 | 11 | _classCallCheck(this, Tink); 12 | 13 | //Add element and scale properties 14 | this.element = element; 15 | this._scale = scale; 16 | 17 | //An array to store all the draggable sprites 18 | this.draggableSprites = []; 19 | 20 | //An array to store all the pointer objects 21 | //(there will usually just be one) 22 | this.pointers = []; 23 | 24 | //An array to store all the buttons and button-like 25 | //interactive sprites 26 | this.buttons = []; 27 | 28 | //A local PIXI reference 29 | this.PIXI = PIXI; 30 | 31 | //Aliases for Pixi objects 32 | this.TextureCache = this.PIXI.utils.TextureCache; 33 | 34 | //Note: change MovieClip to AnimatedSprite for Pixi v4 35 | this.AnimatedSprite = this.PIXI.extras.MovieClip; 36 | this.Texture = this.PIXI.Texture; 37 | } 38 | 39 | _createClass(Tink, [{ 40 | key: "makeDraggable", 41 | 42 | //`makeDraggable` lets you make a drag-and-drop sprite by pushing it 43 | //into the `draggableSprites` array 44 | value: function makeDraggable() { 45 | var _this = this; 46 | 47 | for (var _len = arguments.length, sprites = Array(_len), _key = 0; _key < _len; _key++) { 48 | sprites[_key] = arguments[_key]; 49 | } 50 | 51 | //If the first argument isn't an array of sprites... 52 | if (!(sprites[0] instanceof Array)) { 53 | sprites.forEach(function (sprite) { 54 | _this.draggableSprites.push(sprite); 55 | 56 | //If the sprite's `draggable` property hasn't already been defined by 57 | //another library, like Hexi, define it 58 | if (sprite.draggable === undefined) { 59 | sprite.draggable = true; 60 | sprite._localDraggableAllocation = true; 61 | } 62 | }); 63 | } 64 | 65 | //If the first argument is an array of sprites... 66 | else { 67 | var spritesArray = sprites[0]; 68 | if (spritesArray.length > 0) { 69 | for (var i = spritesArray.length - 1; i >= 0; i--) { 70 | var sprite = spritesArray[i]; 71 | this.draggableSprites.push(sprite); 72 | 73 | //If the sprite's `draggable` property hasn't already been defined by 74 | //another library, like Hexi, define it 75 | if (sprite.draggable === undefined) { 76 | sprite.draggable = true; 77 | sprite._localDraggableAllocation = true; 78 | } 79 | } 80 | } 81 | } 82 | } 83 | 84 | //`makeUndraggable` removes the sprite from the `draggableSprites` 85 | //array 86 | 87 | }, { 88 | key: "makeUndraggable", 89 | value: function makeUndraggable() { 90 | var _this2 = this; 91 | 92 | for (var _len2 = arguments.length, sprites = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 93 | sprites[_key2] = arguments[_key2]; 94 | } 95 | 96 | //If the first argument isn't an array of sprites... 97 | if (!(sprites[0] instanceof Array)) { 98 | sprites.forEach(function (sprite) { 99 | _this2.draggableSprites.splice(_this2.draggableSprites.indexOf(sprite), 1); 100 | if (sprite._localDraggableAllocation === true) sprite.draggable = false; 101 | }); 102 | } 103 | 104 | //If the first argument is an array of sprites 105 | else { 106 | var spritesArray = sprites[0]; 107 | if (spritesArray.length > 0) { 108 | for (var i = spritesArray.length - 1; i >= 0; i--) { 109 | var sprite = spritesArray[i]; 110 | this.draggableSprites.splice(this.draggableSprites.indexOf(sprite), 1); 111 | if (sprite._localDraggableAllocation === true) sprite.draggable = false; 112 | } 113 | } 114 | } 115 | } 116 | }, { 117 | key: "makePointer", 118 | value: function makePointer() { 119 | var element = arguments.length <= 0 || arguments[0] === undefined ? this.element : arguments[0]; 120 | var scale = arguments.length <= 1 || arguments[1] === undefined ? this.scale : arguments[1]; 121 | 122 | //Get a reference to Tink's global `draggableSprites` array 123 | var draggableSprites = this.draggableSprites; 124 | 125 | //Get a reference to Tink's `addGlobalPositionProperties` method 126 | var addGlobalPositionProperties = this.addGlobalPositionProperties; 127 | 128 | //The pointer object will be returned by this function 129 | var pointer = { 130 | element: element, 131 | _scale: scale, 132 | 133 | //Private x and y properties 134 | _x: 0, 135 | _y: 0, 136 | 137 | //Width and height 138 | width: 1, 139 | height: 1, 140 | 141 | //The public x and y properties are divided by the scale. If the 142 | //HTML element that the pointer is sensitive to (like the canvas) 143 | //is scaled up or down, you can change the `scale` value to 144 | //correct the pointer's position values 145 | get x() { 146 | return this._x / this.scale; 147 | }, 148 | get y() { 149 | return this._y / this.scale; 150 | }, 151 | 152 | //Add `centerX` and `centerY` getters so that we 153 | //can use the pointer's coordinates with easing 154 | //and collision functions 155 | get centerX() { 156 | return this.x; 157 | }, 158 | get centerY() { 159 | return this.y; 160 | }, 161 | 162 | //`position` returns an object with x and y properties that 163 | //contain the pointer's position 164 | get position() { 165 | return { 166 | x: this.x, 167 | y: this.y 168 | }; 169 | }, 170 | 171 | get scale() { 172 | return this._scale; 173 | }, 174 | set scale(value) { 175 | this._scale = value; 176 | }, 177 | 178 | //Add a `cursor` getter/setter to change the pointer's cursor 179 | //style. Values can be "pointer" (for a hand icon) or "auto" for 180 | //an ordinary arrow icon. 181 | get cursor() { 182 | return this.element.style.cursor; 183 | }, 184 | set cursor(value) { 185 | this.element.style.cursor = value; 186 | }, 187 | 188 | //Booleans to track the pointer state 189 | isDown: false, 190 | isUp: true, 191 | tapped: false, 192 | 193 | //Properties to help measure the time between up and down states 194 | downTime: 0, 195 | elapsedTime: 0, 196 | 197 | //Optional `press`,`release` and `tap` methods 198 | press: undefined, 199 | release: undefined, 200 | tap: undefined, 201 | 202 | //A `dragSprite` property to help with drag and drop 203 | dragSprite: null, 204 | 205 | //The drag offsets to help drag sprites 206 | dragOffsetX: 0, 207 | dragOffsetY: 0, 208 | 209 | //A property to check whether or not the pointer 210 | //is visible 211 | _visible: true, 212 | get visible() { 213 | return this._visible; 214 | }, 215 | set visible(value) { 216 | if (value === true) { 217 | this.cursor = "auto"; 218 | } else { 219 | this.cursor = "none"; 220 | } 221 | this._visible = value; 222 | }, 223 | 224 | //The pointer's mouse `moveHandler` 225 | moveHandler: function moveHandler(event) { 226 | 227 | //Get the element that's firing the event 228 | var element = event.target; 229 | 230 | //Find the pointer’s x and y position (for mouse). 231 | //Subtract the element's top and left offset from the browser window 232 | this._x = event.pageX - element.offsetLeft; 233 | this._y = event.pageY - element.offsetTop; 234 | 235 | //Prevent the event's default behavior 236 | event.preventDefault(); 237 | }, 238 | 239 | //The pointer's `touchmoveHandler` 240 | touchmoveHandler: function touchmoveHandler(event) { 241 | var element = event.target; 242 | 243 | //Find the touch point's x and y position 244 | this._x = event.targetTouches[0].pageX - element.offsetLeft; 245 | this._y = event.targetTouches[0].pageY - element.offsetTop; 246 | event.preventDefault(); 247 | }, 248 | 249 | //The pointer's `downHandler` 250 | downHandler: function downHandler(event) { 251 | 252 | //Set the down states 253 | this.isDown = true; 254 | this.isUp = false; 255 | this.tapped = false; 256 | 257 | //Capture the current time 258 | this.downTime = Date.now(); 259 | 260 | //Call the `press` method if it's been assigned 261 | if (this.press) this.press(); 262 | event.preventDefault(); 263 | }, 264 | 265 | //The pointer's `touchstartHandler` 266 | touchstartHandler: function touchstartHandler(event) { 267 | var element = event.target; 268 | 269 | //Find the touch point's x and y position 270 | this._x = event.targetTouches[0].pageX - element.offsetLeft; 271 | this._y = event.targetTouches[0].pageY - element.offsetTop; 272 | 273 | //Set the down states 274 | this.isDown = true; 275 | this.isUp = false; 276 | this.tapped = false; 277 | 278 | //Capture the current time 279 | this.downTime = Date.now(); 280 | 281 | //Call the `press` method if it's been assigned 282 | if (this.press) this.press(); 283 | event.preventDefault(); 284 | }, 285 | 286 | //The pointer's `upHandler` 287 | upHandler: function upHandler(event) { 288 | 289 | //Figure out how much time the pointer has been down 290 | this.elapsedTime = Math.abs(this.downTime - Date.now()); 291 | 292 | //If it's less than 200 milliseconds, it must be a tap or click 293 | if (this.elapsedTime <= 200 && this.tapped === false) { 294 | this.tapped = true; 295 | 296 | //Call the `tap` method if it's been assigned 297 | if (this.tap) this.tap(); 298 | } 299 | this.isUp = true; 300 | this.isDown = false; 301 | 302 | //Call the `release` method if it's been assigned 303 | if (this.release) this.release(); 304 | 305 | //`event.preventDefault();` needs to be disabled to prevent range sliders 306 | //from getting trapped in Firefox (and possibly Safari) 307 | //event.preventDefault(); 308 | }, 309 | 310 | //The pointer's `touchendHandler` 311 | touchendHandler: function touchendHandler(event) { 312 | 313 | //Figure out how much time the pointer has been down 314 | this.elapsedTime = Math.abs(this.downTime - Date.now()); 315 | 316 | //If it's less than 200 milliseconds, it must be a tap or click 317 | if (this.elapsedTime <= 200 && this.tapped === false) { 318 | this.tapped = true; 319 | 320 | //Call the `tap` method if it's been assigned 321 | if (this.tap) this.tap(); 322 | } 323 | this.isUp = true; 324 | this.isDown = false; 325 | 326 | //Call the `release` method if it's been assigned 327 | if (this.release) this.release(); 328 | 329 | //event.preventDefault(); 330 | }, 331 | 332 | //`hitTestSprite` figures out if the pointer is touching a sprite 333 | hitTestSprite: function hitTestSprite(sprite) { 334 | 335 | //Add global `gx` and `gy` properties to the sprite if they 336 | //don't already exist 337 | addGlobalPositionProperties(sprite); 338 | 339 | //The `hit` variable will become `true` if the pointer is 340 | //touching the sprite and remain `false` if it isn't 341 | var hit = false; 342 | 343 | //Find out the sprite's offset from its anchor point 344 | var xAnchorOffset = undefined, 345 | yAnchorOffset = undefined; 346 | if (sprite.anchor !== undefined) { 347 | xAnchorOffset = sprite.width * sprite.anchor.x; 348 | yAnchorOffset = sprite.height * sprite.anchor.y; 349 | } else { 350 | xAnchorOffset = 0; 351 | yAnchorOffset = 0; 352 | } 353 | 354 | //Is the sprite rectangular? 355 | if (!sprite.circular) { 356 | 357 | //Get the position of the sprite's edges using global 358 | //coordinates 359 | var left = sprite.gx - xAnchorOffset, 360 | right = sprite.gx + sprite.width - xAnchorOffset, 361 | top = sprite.gy - yAnchorOffset, 362 | bottom = sprite.gy + sprite.height - yAnchorOffset; 363 | 364 | //Find out if the pointer is intersecting the rectangle. 365 | //`hit` will become `true` if the pointer is inside the 366 | //sprite's area 367 | hit = this.x > left && this.x < right && this.y > top && this.y < bottom; 368 | } 369 | 370 | //Is the sprite circular? 371 | else { 372 | //Find the distance between the pointer and the 373 | //center of the circle 374 | var vx = this.x - (sprite.gx + sprite.width / 2 - xAnchorOffset), 375 | vy = this.y - (sprite.gy + sprite.width / 2 - yAnchorOffset), 376 | distance = Math.sqrt(vx * vx + vy * vy); 377 | 378 | //The pointer is intersecting the circle if the 379 | //distance is less than the circle's radius 380 | hit = distance < sprite.width / 2; 381 | } 382 | //Check the value of `hit` 383 | return hit; 384 | } 385 | }; 386 | 387 | //Bind the events to the handlers 388 | //Mouse events 389 | element.addEventListener("mousemove", pointer.moveHandler.bind(pointer), false); 390 | element.addEventListener("mousedown", pointer.downHandler.bind(pointer), false); 391 | 392 | //Add the `mouseup` event to the `window` to 393 | //catch a mouse button release outside of the canvas area 394 | window.addEventListener("mouseup", pointer.upHandler.bind(pointer), false); 395 | 396 | //Touch events 397 | element.addEventListener("touchmove", pointer.touchmoveHandler.bind(pointer), false); 398 | element.addEventListener("touchstart", pointer.touchstartHandler.bind(pointer), false); 399 | 400 | //Add the `touchend` event to the `window` object to 401 | //catch a mouse button release outside of the canvas area 402 | window.addEventListener("touchend", pointer.touchendHandler.bind(pointer), false); 403 | 404 | //Disable the default pan and zoom actions on the `canvas` 405 | element.style.touchAction = "none"; 406 | 407 | //Add the pointer to Tink's global `pointers` array 408 | this.pointers.push(pointer); 409 | 410 | //Return the pointer 411 | return pointer; 412 | } 413 | 414 | //Many of Tink's objects, like pointers, use collision 415 | //detection using the sprites' global x and y positions. To make 416 | //this easier, new `gx` and `gy` properties are added to sprites 417 | //that reference Pixi sprites' `getGlobalPosition()` values. 418 | 419 | }, { 420 | key: "addGlobalPositionProperties", 421 | value: function addGlobalPositionProperties(sprite) { 422 | if (sprite.gx === undefined) { 423 | Object.defineProperty(sprite, "gx", { 424 | get: function get() { 425 | return sprite.getGlobalPosition().x; 426 | } 427 | }); 428 | } 429 | 430 | if (sprite.gy === undefined) { 431 | Object.defineProperty(sprite, "gy", { 432 | get: function get() { 433 | return sprite.getGlobalPosition().y; 434 | } 435 | }); 436 | } 437 | } 438 | 439 | //A method that implments drag-and-drop functionality 440 | //for each pointer 441 | 442 | }, { 443 | key: "updateDragAndDrop", 444 | value: function updateDragAndDrop(draggableSprites) { 445 | 446 | //Create a pointer if one doesn't already exist 447 | if (this.pointers.length === 0) { 448 | this.makePointer(this.element, this.scale); 449 | } 450 | 451 | //Loop through all the pointers in Tink's global `pointers` array 452 | //(there will usually just be one, but you never know) 453 | this.pointers.forEach(function (pointer) { 454 | 455 | //Check whether the pointer is pressed down 456 | if (pointer.isDown) { 457 | 458 | //You need to capture the co-ordinates at which the pointer was 459 | //pressed down and find out if it's touching a sprite 460 | 461 | //Only run pointer.code if the pointer isn't already dragging 462 | //sprite 463 | if (pointer.dragSprite === null) { 464 | 465 | //Loop through the `draggableSprites` in reverse to start searching at the bottom of the stack 466 | for (var i = draggableSprites.length - 1; i > -1; i--) { 467 | 468 | //Get a reference to the current sprite 469 | var sprite = draggableSprites[i]; 470 | 471 | //Check for a collision with the pointer using `hitTestSprite` 472 | if (pointer.hitTestSprite(sprite) && sprite.draggable) { 473 | 474 | //Calculate the difference between the pointer's 475 | //position and the sprite's position 476 | pointer.dragOffsetX = pointer.x - sprite.gx; 477 | pointer.dragOffsetY = pointer.y - sprite.gy; 478 | 479 | //Set the sprite as the pointer's `dragSprite` property 480 | pointer.dragSprite = sprite; 481 | 482 | //The next two lines re-order the `sprites` array so that the 483 | //selected sprite is displayed above all the others. 484 | //First, splice the sprite out of its current position in 485 | //its parent's `children` array 486 | var children = sprite.parent.children; 487 | children.splice(children.indexOf(sprite), 1); 488 | 489 | //Next, push the `dragSprite` to the end of its `children` array so that it's 490 | //displayed last, above all the other sprites 491 | children.push(sprite); 492 | 493 | //Reorganize the `draggableSpites` array in the same way 494 | draggableSprites.splice(draggableSprites.indexOf(sprite), 1); 495 | draggableSprites.push(sprite); 496 | 497 | //Break the loop, because we only need to drag the topmost sprite 498 | break; 499 | } 500 | } 501 | } 502 | 503 | //If the pointer is down and it has a `dragSprite`, make the sprite follow the pointer's 504 | //position, with the calculated offset 505 | else { 506 | pointer.dragSprite.x = pointer.x - pointer.dragOffsetX; 507 | pointer.dragSprite.y = pointer.y - pointer.dragOffsetY; 508 | } 509 | } 510 | 511 | //If the pointer is up, drop the `dragSprite` by setting it to `null` 512 | if (pointer.isUp) { 513 | pointer.dragSprite = null; 514 | } 515 | 516 | //Change the mouse arrow pointer to a hand if it's over a 517 | //draggable sprite 518 | draggableSprites.some(function (sprite) { 519 | if (pointer.hitTestSprite(sprite) && sprite.draggable) { 520 | if (pointer.visible) pointer.cursor = "pointer"; 521 | return true; 522 | } else { 523 | if (pointer.visible) pointer.cursor = "auto"; 524 | return false; 525 | } 526 | }); 527 | }); 528 | } 529 | }, { 530 | key: "makeInteractive", 531 | value: function makeInteractive(o) { 532 | 533 | //The `press`,`release`, `over`, `out` and `tap` methods. They're `undefined` 534 | //for now, but they can be defined in the game program 535 | o.press = o.press || undefined; 536 | o.release = o.release || undefined; 537 | o.over = o.over || undefined; 538 | o.out = o.out || undefined; 539 | o.tap = o.tap || undefined; 540 | 541 | //The `state` property tells you the button's 542 | //current state. Set its initial state to "up" 543 | o.state = "up"; 544 | 545 | //The `action` property tells you whether its being pressed or 546 | //released 547 | o.action = ""; 548 | 549 | //The `pressed` and `hoverOver` Booleans are mainly for internal 550 | //use in this code to help figure out the correct state. 551 | //`pressed` is a Boolean that helps track whether or not 552 | //the sprite has been pressed down 553 | o.pressed = false; 554 | 555 | //`hoverOver` is a Boolean which checks whether the pointer 556 | //has hovered over the sprite 557 | o.hoverOver = false; 558 | 559 | //tinkType is a string that will be set to "button" if the 560 | //user creates an object using the `button` function 561 | o.tinkType = ""; 562 | 563 | //Set `enabled` to true to allow for interactivity 564 | //Set `enabled` to false to disable interactivity 565 | o.enabled = true; 566 | 567 | //Add the sprite to the global `buttons` array so that it can 568 | //be updated each frame in the `updateButtons method 569 | this.buttons.push(o); 570 | } 571 | 572 | //The `updateButtons` method will be called each frame 573 | //inside the game loop. It updates all the button-like sprites 574 | 575 | }, { 576 | key: "updateButtons", 577 | value: function updateButtons() { 578 | var _this3 = this; 579 | 580 | //Create a pointer if one doesn't already exist 581 | if (this.pointers.length === 0) { 582 | this.makePointer(this.element, this.scale); 583 | } 584 | 585 | //Loop through all of Tink's pointers (there will usually 586 | //just be one) 587 | this.pointers.forEach(function (pointer) { 588 | 589 | pointer.shouldBeHand = false; 590 | 591 | //Loop through all the button-like sprites that were created 592 | //using the `makeInteractive` method 593 | _this3.buttons.forEach(function (o) { 594 | 595 | //Only do this if the interactive object is enabled 596 | if (o.enabled) { 597 | 598 | //Figure out if the pointer is touching the sprite 599 | var hit = pointer.hitTestSprite(o); 600 | 601 | //1. Figure out the current state 602 | if (pointer.isUp) { 603 | 604 | //Up state 605 | o.state = "up"; 606 | 607 | //Show the first image state frame, if this is a `Button` sprite 608 | if (o.tinkType === "button") o.gotoAndStop(0); 609 | } 610 | 611 | //If the pointer is touching the sprite, figure out 612 | //if the over or down state should be displayed 613 | if (hit) { 614 | 615 | //Over state 616 | o.state = "over"; 617 | 618 | //Show the second image state frame if this sprite has 619 | //3 frames and it's a `Button` sprite 620 | if (o.totalFrames && o.totalFrames === 3 && o.tinkType === "button") { 621 | o.gotoAndStop(1); 622 | } 623 | 624 | //Down state 625 | if (pointer.isDown) { 626 | o.state = "down"; 627 | 628 | //Show the third frame if this sprite is a `Button` sprite and it 629 | //has only three frames, or show the second frame if it 630 | //only has two frames 631 | if (o.tinkType === "button") { 632 | if (o.totalFrames === 3) { 633 | o.gotoAndStop(2); 634 | } else { 635 | o.gotoAndStop(1); 636 | } 637 | } 638 | } 639 | 640 | //Flag this pointer to be changed to a hand 641 | pointer.shouldBeHand = true; 642 | //if (pointer.visible) pointer.cursor = "pointer"; 643 | // } else { 644 | // //Turn the pointer to an ordinary arrow icon if the 645 | // //pointer isn't touching a sprite 646 | // if (pointer.visible) pointer.cursor = "auto"; 647 | 648 | //Change the pointer icon to a hand 649 | if (pointer.visible) pointer.cursor = "pointer"; 650 | } else { 651 | //Turn the pointer to an ordinary arrow icon if the 652 | //pointer isn't touching a sprite 653 | if (pointer.visible) pointer.cursor = "auto"; 654 | } 655 | 656 | //Perform the correct interactive action 657 | 658 | //a. Run the `press` method if the sprite state is "down" and 659 | //the sprite hasn't already been pressed 660 | if (o.state === "down") { 661 | if (!o.pressed) { 662 | if (o.press) o.press(); 663 | o.pressed = true; 664 | o.action = "pressed"; 665 | } 666 | } 667 | 668 | //b. Run the `release` method if the sprite state is "over" and 669 | //the sprite has been pressed 670 | if (o.state === "over") { 671 | if (o.pressed) { 672 | if (o.release) o.release(); 673 | o.pressed = false; 674 | o.action = "released"; 675 | //If the pointer was tapped and the user assigned a `tap` 676 | //method, call the `tap` method 677 | if (pointer.tapped && o.tap) o.tap(); 678 | } 679 | 680 | //Run the `over` method if it has been assigned 681 | if (!o.hoverOver) { 682 | if (o.over) o.over(); 683 | o.hoverOver = true; 684 | } 685 | } 686 | 687 | //c. Check whether the pointer has been released outside 688 | //the sprite's area. If the button state is "up" and it's 689 | //already been pressed, then run the `release` method. 690 | if (o.state === "up") { 691 | if (o.pressed) { 692 | if (o.release) o.release(); 693 | o.pressed = false; 694 | o.action = "released"; 695 | } 696 | 697 | //Run the `out` method if it has been assigned 698 | if (o.hoverOver) { 699 | if (o.out) o.out(); 700 | o.hoverOver = false; 701 | } 702 | } 703 | } 704 | }); 705 | 706 | if (pointer.shouldBeHand) { 707 | pointer.cursor = "pointer"; 708 | } else { 709 | pointer.cursor = "auto"; 710 | } 711 | }); 712 | } 713 | 714 | //A function that creates a sprite with 3 frames that 715 | //represent the button states: up, over and down 716 | 717 | }, { 718 | key: "button", 719 | value: function button(source) { 720 | var x = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1]; 721 | var y = arguments.length <= 2 || arguments[2] === undefined ? 0 : arguments[2]; 722 | 723 | //The sprite object that will be returned 724 | var o = undefined; 725 | 726 | //Is it an array of frame ids or textures? 727 | if (typeof source[0] === "string") { 728 | 729 | //They're strings, but are they pre-existing texture or 730 | //paths to image files? 731 | //Check to see if the first element matches a texture in the 732 | //cache 733 | if (this.TextureCache[source[0]]) { 734 | 735 | //It does, so it's an array of frame ids 736 | o = this.AnimatedSprite.fromFrames(source); 737 | } else { 738 | 739 | //It's not already in the cache, so let's load it 740 | o = this.AnimatedSprite.fromImages(source); 741 | } 742 | } 743 | 744 | //If the `source` isn't an array of strings, check whether 745 | //it's an array of textures 746 | else if (source[0] instanceof this.Texture) { 747 | 748 | //Yes, it's an array of textures. 749 | //Use them to make a AnimatedSprite o 750 | o = new this.AnimatedSprite(source); 751 | } 752 | 753 | //Add interactive properties to the button 754 | this.makeInteractive(o); 755 | 756 | //Set the `tinkType` to "button" 757 | o.tinkType = "button"; 758 | 759 | //Position the button 760 | o.x = x; 761 | o.y = y; 762 | 763 | //Return the new button sprite 764 | return o; 765 | } 766 | 767 | //Run the `udpate` function in your game loop 768 | //to update all of Tink's interactive objects 769 | 770 | }, { 771 | key: "update", 772 | value: function update() { 773 | 774 | //Update the drag and drop system 775 | if (this.draggableSprites.length !== 0) this.updateDragAndDrop(this.draggableSprites); 776 | 777 | //Update the buttons and button-like interactive sprites 778 | if (this.buttons.length !== 0) this.updateButtons(); 779 | } 780 | 781 | /* 782 | `keyboard` is a method that listens for and captures keyboard events. It's really 783 | just a convenient wrapper function for HTML `keyup` and `keydown` events so that you can keep your application code clutter-free and easier to write and read. 784 | Here's how to use the `keyboard` method. Create a new keyboard object like this: 785 | ```js 786 | let keyObject = keyboard(asciiKeyCodeNumber); 787 | ``` 788 | It's one argument is the ASCII key code number of the keyboard key 789 | that you want to listen for. [Here's a list of ASCII key codes you can 790 | use](http://www.asciitable.com). 791 | Then assign `press` and `release` methods to the keyboard object like this: 792 | ```js 793 | keyObject.press = () => { 794 | //key object pressed 795 | }; 796 | keyObject.release = () => { 797 | //key object released 798 | }; 799 | ``` 800 | Keyboard objects also have `isDown` and `isUp` Boolean properties that you can use to check the state of each key. 801 | */ 802 | 803 | }, { 804 | key: "keyboard", 805 | value: function keyboard(keyCode) { 806 | var key = {}; 807 | key.code = keyCode; 808 | key.isDown = false; 809 | key.isUp = true; 810 | key.press = undefined; 811 | key.release = undefined; 812 | 813 | //The `downHandler` 814 | key.downHandler = function (event) { 815 | if (event.keyCode === key.code) { 816 | if (key.isUp && key.press) key.press(); 817 | key.isDown = true; 818 | key.isUp = false; 819 | } 820 | event.preventDefault(); 821 | }; 822 | 823 | //The `upHandler` 824 | key.upHandler = function (event) { 825 | if (event.keyCode === key.code) { 826 | if (key.isDown && key.release) key.release(); 827 | key.isDown = false; 828 | key.isUp = true; 829 | } 830 | event.preventDefault(); 831 | }; 832 | 833 | //Attach event listeners 834 | window.addEventListener("keydown", key.downHandler.bind(key), false); 835 | window.addEventListener("keyup", key.upHandler.bind(key), false); 836 | 837 | //Return the key object 838 | return key; 839 | } 840 | 841 | //`arrowControl` is a convenience method for updating a sprite's velocity 842 | //for 4-way movement using the arrow directional keys. Supply it 843 | //with the sprite you want to control and the speed per frame, in 844 | //pixels, that you want to update the sprite's velocity 845 | 846 | }, { 847 | key: "arrowControl", 848 | value: function arrowControl(sprite, speed) { 849 | 850 | if (speed === undefined) { 851 | throw new Error("Please supply the arrowControl method with the speed at which you want the sprite to move"); 852 | } 853 | 854 | var upArrow = this.keyboard(38), 855 | rightArrow = this.keyboard(39), 856 | downArrow = this.keyboard(40), 857 | leftArrow = this.keyboard(37); 858 | 859 | //Assign key `press` methods 860 | leftArrow.press = function () { 861 | //Change the sprite's velocity when the key is pressed 862 | sprite.vx = -speed; 863 | sprite.vy = 0; 864 | }; 865 | leftArrow.release = function () { 866 | //If the left arrow has been released, and the right arrow isn't down, 867 | //and the sprite isn't moving vertically: 868 | //Stop the sprite 869 | if (!rightArrow.isDown && sprite.vy === 0) { 870 | sprite.vx = 0; 871 | } 872 | }; 873 | upArrow.press = function () { 874 | sprite.vy = -speed; 875 | sprite.vx = 0; 876 | }; 877 | upArrow.release = function () { 878 | if (!downArrow.isDown && sprite.vx === 0) { 879 | sprite.vy = 0; 880 | } 881 | }; 882 | rightArrow.press = function () { 883 | sprite.vx = speed; 884 | sprite.vy = 0; 885 | }; 886 | rightArrow.release = function () { 887 | if (!leftArrow.isDown && sprite.vy === 0) { 888 | sprite.vx = 0; 889 | } 890 | }; 891 | downArrow.press = function () { 892 | sprite.vy = speed; 893 | sprite.vx = 0; 894 | }; 895 | downArrow.release = function () { 896 | if (!upArrow.isDown && sprite.vx === 0) { 897 | sprite.vy = 0; 898 | } 899 | }; 900 | } 901 | }, { 902 | key: "scale", 903 | get: function get() { 904 | return this._scale; 905 | }, 906 | set: function set(value) { 907 | this._scale = value; 908 | 909 | //Update scale values for all pointers 910 | this.pointers.forEach(function (pointer) { 911 | return pointer.scale = value; 912 | }); 913 | } 914 | }]); 915 | 916 | return Tink; 917 | })(); 918 | //# sourceMappingURL=tink.js.map -------------------------------------------------------------------------------- /bin/tink.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/tink.js"],"names":[],"mappings":"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","file":"tink.js","sourcesContent":["class Tink {\n constructor(PIXI, element, scale = 1) {\n\n //Add element and scale properties\n this.element = element;\n this._scale = scale;\n\n //An array to store all the draggable sprites\n this.draggableSprites = [];\n\n //An array to store all the pointer objects\n //(there will usually just be one)\n this.pointers = [];\n\n //An array to store all the buttons and button-like\n //interactive sprites\n this.buttons = [];\n\n //A local PIXI reference\n this.PIXI = PIXI;\n\n //Aliases for Pixi objects\n this.TextureCache = this.PIXI.utils.TextureCache;\n\n //Note: change MovieClip to AnimatedSprite for Pixi v4\n this.AnimatedSprite = this.PIXI.extras.MovieClip;\n this.Texture = this.PIXI.Texture;\n }\n\n get scale() {\n return this._scale;\n }\n\n set scale(value) {\n this._scale = value;\n\n //Update scale values for all pointers\n this.pointers.forEach(pointer => pointer.scale = value);\n }\n\n //`makeDraggable` lets you make a drag-and-drop sprite by pushing it\n //into the `draggableSprites` array\n makeDraggable(...sprites) {\n\n //If the first argument isn't an array of sprites...\n if (!(sprites[0] instanceof Array)) {\n sprites.forEach(sprite => {\n this.draggableSprites.push(sprite);\n\n //If the sprite's `draggable` property hasn't already been defined by\n //another library, like Hexi, define it\n if (sprite.draggable === undefined) {\n sprite.draggable = true;\n sprite._localDraggableAllocation = true;\n }\n });\n }\n\n //If the first argument is an array of sprites...\n else {\n let spritesArray = sprites[0];\n if (spritesArray.length > 0) {\n for (let i = spritesArray.length - 1; i >= 0; i--) {\n let sprite = spritesArray[i];\n this.draggableSprites.push(sprite);\n\n //If the sprite's `draggable` property hasn't already been defined by\n //another library, like Hexi, define it\n if (sprite.draggable === undefined) {\n sprite.draggable = true;\n sprite._localDraggableAllocation = true;\n }\n }\n }\n }\n }\n\n //`makeUndraggable` removes the sprite from the `draggableSprites`\n //array\n makeUndraggable(...sprites) {\n\n //If the first argument isn't an array of sprites...\n if (!(sprites[0] instanceof Array)) {\n sprites.forEach(sprite => {\n this.draggableSprites.splice(this.draggableSprites.indexOf(sprite), 1);\n if (sprite._localDraggableAllocation === true) sprite.draggable = false;\n });\n }\n\n //If the first argument is an array of sprites\n else {\n let spritesArray = sprites[0];\n if (spritesArray.length > 0) {\n for (let i = spritesArray.length - 1; i >= 0; i--) {\n let sprite = spritesArray[i];\n this.draggableSprites.splice(this.draggableSprites.indexOf(sprite), 1);\n if (sprite._localDraggableAllocation === true) sprite.draggable = false;\n }\n }\n }\n }\n\n makePointer(element = this.element, scale = this.scale) {\n\n //Get a reference to Tink's global `draggableSprites` array\n let draggableSprites = this.draggableSprites;\n\n //Get a reference to Tink's `addGlobalPositionProperties` method\n let addGlobalPositionProperties = this.addGlobalPositionProperties;\n\n //The pointer object will be returned by this function\n let pointer = {\n element: element,\n _scale: scale,\n\n //Private x and y properties\n _x: 0,\n _y: 0,\n\n //Width and height\n width: 1,\n height: 1,\n\n //The public x and y properties are divided by the scale. If the\n //HTML element that the pointer is sensitive to (like the canvas)\n //is scaled up or down, you can change the `scale` value to\n //correct the pointer's position values\n get x() {\n return this._x / this.scale;\n },\n get y() {\n return this._y / this.scale;\n },\n\n //Add `centerX` and `centerY` getters so that we\n //can use the pointer's coordinates with easing\n //and collision functions\n get centerX() {\n return this.x;\n },\n get centerY() {\n return this.y;\n },\n\n //`position` returns an object with x and y properties that\n //contain the pointer's position\n get position() {\n return {\n x: this.x,\n y: this.y\n };\n },\n\n get scale() {\n return this._scale;\n },\n set scale(value) {\n this._scale = value;\n },\n\n //Add a `cursor` getter/setter to change the pointer's cursor\n //style. Values can be \"pointer\" (for a hand icon) or \"auto\" for \n //an ordinary arrow icon.\n get cursor() {\n return this.element.style.cursor;\n },\n set cursor(value) {\n this.element.style.cursor = value;\n },\n\n //Booleans to track the pointer state\n isDown: false,\n isUp: true,\n tapped: false,\n\n //Properties to help measure the time between up and down states\n downTime: 0,\n elapsedTime: 0,\n\n //Optional `press`,`release` and `tap` methods\n press: undefined,\n release: undefined,\n tap: undefined,\n\n //A `dragSprite` property to help with drag and drop\n dragSprite: null,\n\n //The drag offsets to help drag sprites\n dragOffsetX: 0,\n dragOffsetY: 0,\n\n //A property to check whether or not the pointer\n //is visible\n _visible: true,\n get visible() {\n return this._visible;\n },\n set visible(value) {\n if (value === true) {\n this.cursor = \"auto\";\n } else {\n this.cursor = \"none\";\n }\n this._visible = value;\n },\n\n //The pointer's mouse `moveHandler`\n moveHandler(event) {\n\n //Get the element that's firing the event\n let element = event.target;\n\n //Find the pointer’s x and y position (for mouse).\n //Subtract the element's top and left offset from the browser window\n this._x = (event.pageX - element.offsetLeft);\n this._y = (event.pageY - element.offsetTop);\n\n //Prevent the event's default behavior \n event.preventDefault();\n },\n\n //The pointer's `touchmoveHandler`\n touchmoveHandler(event) {\n let element = event.target;\n\n //Find the touch point's x and y position\n this._x = (event.targetTouches[0].pageX - element.offsetLeft);\n this._y = (event.targetTouches[0].pageY - element.offsetTop);\n event.preventDefault();\n },\n\n //The pointer's `downHandler`\n downHandler(event) {\n\n //Set the down states\n this.isDown = true;\n this.isUp = false;\n this.tapped = false;\n\n //Capture the current time\n this.downTime = Date.now();\n\n //Call the `press` method if it's been assigned\n if (this.press) this.press();\n event.preventDefault();\n },\n\n //The pointer's `touchstartHandler`\n touchstartHandler(event) {\n let element = event.target;\n\n //Find the touch point's x and y position\n this._x = event.targetTouches[0].pageX - element.offsetLeft;\n this._y = event.targetTouches[0].pageY - element.offsetTop;\n\n //Set the down states\n this.isDown = true;\n this.isUp = false;\n this.tapped = false;\n\n //Capture the current time\n this.downTime = Date.now();\n\n //Call the `press` method if it's been assigned\n if (this.press) this.press();\n event.preventDefault();\n },\n\n //The pointer's `upHandler`\n upHandler(event) {\n\n //Figure out how much time the pointer has been down\n this.elapsedTime = Math.abs(this.downTime - Date.now());\n\n //If it's less than 200 milliseconds, it must be a tap or click\n if (this.elapsedTime <= 200 && this.tapped === false) {\n this.tapped = true;\n\n //Call the `tap` method if it's been assigned\n if (this.tap) this.tap();\n }\n this.isUp = true;\n this.isDown = false;\n\n //Call the `release` method if it's been assigned\n if (this.release) this.release();\n\n //`event.preventDefault();` needs to be disabled to prevent range sliders\n //from getting trapped in Firefox (and possibly Safari)\n //event.preventDefault();\n },\n\n //The pointer's `touchendHandler`\n touchendHandler(event) {\n\n //Figure out how much time the pointer has been down\n this.elapsedTime = Math.abs(this.downTime - Date.now());\n\n //If it's less than 200 milliseconds, it must be a tap or click\n if (this.elapsedTime <= 200 && this.tapped === false) {\n this.tapped = true;\n\n //Call the `tap` method if it's been assigned\n if (this.tap) this.tap();\n }\n this.isUp = true;\n this.isDown = false;\n\n //Call the `release` method if it's been assigned\n if (this.release) this.release();\n \n //event.preventDefault();\n },\n\n //`hitTestSprite` figures out if the pointer is touching a sprite\n hitTestSprite(sprite) {\n\n //Add global `gx` and `gy` properties to the sprite if they\n //don't already exist\n addGlobalPositionProperties(sprite);\n\n //The `hit` variable will become `true` if the pointer is\n //touching the sprite and remain `false` if it isn't\n let hit = false;\n\n //Find out the sprite's offset from its anchor point\n let xAnchorOffset, yAnchorOffset;\n if (sprite.anchor !== undefined) {\n xAnchorOffset = sprite.width * sprite.anchor.x;\n yAnchorOffset = sprite.height * sprite.anchor.y;\n } else {\n xAnchorOffset = 0;\n yAnchorOffset = 0;\n }\n\n //Is the sprite rectangular?\n if (!sprite.circular) {\n\n //Get the position of the sprite's edges using global\n //coordinates\n let left = sprite.gx - xAnchorOffset,\n right = sprite.gx + sprite.width - xAnchorOffset,\n top = sprite.gy - yAnchorOffset,\n bottom = sprite.gy + sprite.height - yAnchorOffset;\n\n //Find out if the pointer is intersecting the rectangle.\n //`hit` will become `true` if the pointer is inside the\n //sprite's area\n hit = this.x > left && this.x < right && this.y > top && this.y < bottom;\n }\n\n //Is the sprite circular?\n else {\n //Find the distance between the pointer and the\n //center of the circle\n let vx = this.x - (sprite.gx + (sprite.width / 2) - xAnchorOffset),\n vy = this.y - (sprite.gy + (sprite.width / 2) - yAnchorOffset),\n distance = Math.sqrt(vx * vx + vy * vy);\n\n //The pointer is intersecting the circle if the\n //distance is less than the circle's radius\n hit = distance < sprite.width / 2;\n }\n //Check the value of `hit`\n return hit;\n }\n };\n\n //Bind the events to the handlers\n //Mouse events\n element.addEventListener(\n \"mousemove\", pointer.moveHandler.bind(pointer), false\n );\n element.addEventListener(\n \"mousedown\", pointer.downHandler.bind(pointer), false\n );\n\n //Add the `mouseup` event to the `window` to\n //catch a mouse button release outside of the canvas area\n window.addEventListener(\n \"mouseup\", pointer.upHandler.bind(pointer), false\n );\n\n //Touch events\n element.addEventListener(\n \"touchmove\", pointer.touchmoveHandler.bind(pointer), false\n );\n element.addEventListener(\n \"touchstart\", pointer.touchstartHandler.bind(pointer), false\n );\n\n //Add the `touchend` event to the `window` object to\n //catch a mouse button release outside of the canvas area\n window.addEventListener(\n \"touchend\", pointer.touchendHandler.bind(pointer), false\n );\n\n //Disable the default pan and zoom actions on the `canvas`\n element.style.touchAction = \"none\";\n\n //Add the pointer to Tink's global `pointers` array\n this.pointers.push(pointer);\n\n //Return the pointer\n return pointer;\n }\n\n //Many of Tink's objects, like pointers, use collision\n //detection using the sprites' global x and y positions. To make\n //this easier, new `gx` and `gy` properties are added to sprites\n //that reference Pixi sprites' `getGlobalPosition()` values.\n addGlobalPositionProperties(sprite) {\n if (sprite.gx === undefined) {\n Object.defineProperty(\n sprite,\n \"gx\", {\n get() {\n return sprite.getGlobalPosition().x;\n }\n }\n );\n }\n\n if (sprite.gy === undefined) {\n Object.defineProperty(\n sprite,\n \"gy\", {\n get() {\n return sprite.getGlobalPosition().y;\n }\n }\n );\n }\n }\n\n //A method that implments drag-and-drop functionality \n //for each pointer\n updateDragAndDrop(draggableSprites) {\n\n //Create a pointer if one doesn't already exist\n if (this.pointers.length === 0) {\n this.makePointer(this.element, this.scale);\n }\n\n //Loop through all the pointers in Tink's global `pointers` array\n //(there will usually just be one, but you never know)\n this.pointers.forEach(pointer => {\n\n //Check whether the pointer is pressed down\n if (pointer.isDown) {\n\n //You need to capture the co-ordinates at which the pointer was\n //pressed down and find out if it's touching a sprite\n\n //Only run pointer.code if the pointer isn't already dragging\n //sprite\n if (pointer.dragSprite === null) {\n\n //Loop through the `draggableSprites` in reverse to start searching at the bottom of the stack\n for (let i = draggableSprites.length - 1; i > -1; i--) {\n\n //Get a reference to the current sprite\n let sprite = draggableSprites[i];\n\n //Check for a collision with the pointer using `hitTestSprite`\n if (pointer.hitTestSprite(sprite) && sprite.draggable) {\n\n //Calculate the difference between the pointer's\n //position and the sprite's position\n pointer.dragOffsetX = pointer.x - sprite.gx;\n pointer.dragOffsetY = pointer.y - sprite.gy;\n\n //Set the sprite as the pointer's `dragSprite` property\n pointer.dragSprite = sprite;\n\n //The next two lines re-order the `sprites` array so that the\n //selected sprite is displayed above all the others.\n //First, splice the sprite out of its current position in\n //its parent's `children` array\n let children = sprite.parent.children;\n children.splice(children.indexOf(sprite), 1);\n\n //Next, push the `dragSprite` to the end of its `children` array so that it's\n //displayed last, above all the other sprites\n children.push(sprite);\n\n //Reorganize the `draggableSpites` array in the same way\n draggableSprites.splice(draggableSprites.indexOf(sprite), 1);\n draggableSprites.push(sprite);\n\n //Break the loop, because we only need to drag the topmost sprite\n break;\n }\n }\n }\n\n //If the pointer is down and it has a `dragSprite`, make the sprite follow the pointer's\n //position, with the calculated offset\n else {\n pointer.dragSprite.x = pointer.x - pointer.dragOffsetX;\n pointer.dragSprite.y = pointer.y - pointer.dragOffsetY;\n }\n }\n\n //If the pointer is up, drop the `dragSprite` by setting it to `null`\n if (pointer.isUp) {\n pointer.dragSprite = null;\n }\n\n //Change the mouse arrow pointer to a hand if it's over a\n //draggable sprite\n draggableSprites.some(sprite => {\n if (pointer.hitTestSprite(sprite) && sprite.draggable) {\n if (pointer.visible) pointer.cursor = \"pointer\";\n return true;\n } else {\n if (pointer.visible) pointer.cursor = \"auto\";\n return false;\n }\n });\n });\n }\n\n makeInteractive(o) {\n\n //The `press`,`release`, `over`, `out` and `tap` methods. They're `undefined`\n //for now, but they can be defined in the game program\n o.press = o.press || undefined;\n o.release = o.release || undefined;\n o.over = o.over || undefined;\n o.out = o.out || undefined;\n o.tap = o.tap || undefined;\n\n //The `state` property tells you the button's\n //current state. Set its initial state to \"up\"\n o.state = \"up\";\n\n //The `action` property tells you whether its being pressed or\n //released\n o.action = \"\";\n\n //The `pressed` and `hoverOver` Booleans are mainly for internal\n //use in this code to help figure out the correct state.\n //`pressed` is a Boolean that helps track whether or not\n //the sprite has been pressed down\n o.pressed = false;\n\n //`hoverOver` is a Boolean which checks whether the pointer\n //has hovered over the sprite\n o.hoverOver = false;\n\n //tinkType is a string that will be set to \"button\" if the \n //user creates an object using the `button` function\n o.tinkType = \"\";\n\n //Set `enabled` to true to allow for interactivity\n //Set `enabled` to false to disable interactivity\n o.enabled = true;\n\n //Add the sprite to the global `buttons` array so that it can\n //be updated each frame in the `updateButtons method\n this.buttons.push(o);\n }\n\n //The `updateButtons` method will be called each frame \n //inside the game loop. It updates all the button-like sprites\n updateButtons() {\n\n //Create a pointer if one doesn't already exist\n if (this.pointers.length === 0) {\n this.makePointer(this.element, this.scale);\n }\n\n //Loop through all of Tink's pointers (there will usually\n //just be one)\n this.pointers.forEach(pointer => {\n\n pointer.shouldBeHand = false;\n\n\n\n //Loop through all the button-like sprites that were created\n //using the `makeInteractive` method\n this.buttons.forEach(o => {\n\n //Only do this if the interactive object is enabled\n if (o.enabled) {\n\n //Figure out if the pointer is touching the sprite\n let hit = pointer.hitTestSprite(o);\n\n //1. Figure out the current state\n if (pointer.isUp) {\n\n //Up state\n o.state = \"up\";\n\n //Show the first image state frame, if this is a `Button` sprite\n if (o.tinkType === \"button\") o.gotoAndStop(0);\n }\n\n //If the pointer is touching the sprite, figure out\n //if the over or down state should be displayed\n if (hit) {\n\n //Over state\n o.state = \"over\";\n\n //Show the second image state frame if this sprite has\n //3 frames and it's a `Button` sprite\n if (o.totalFrames && o.totalFrames === 3 && o.tinkType === \"button\") {\n o.gotoAndStop(1);\n }\n\n //Down state\n if (pointer.isDown) {\n o.state = \"down\";\n\n //Show the third frame if this sprite is a `Button` sprite and it\n //has only three frames, or show the second frame if it\n //only has two frames\n if (o.tinkType === \"button\") {\n if (o.totalFrames === 3) {\n o.gotoAndStop(2);\n } else {\n o.gotoAndStop(1);\n }\n }\n }\n\n\n\n //Flag this pointer to be changed to a hand\n pointer.shouldBeHand = true;\n //if (pointer.visible) pointer.cursor = \"pointer\";\n // } else {\n // //Turn the pointer to an ordinary arrow icon if the\n // //pointer isn't touching a sprite\n // if (pointer.visible) pointer.cursor = \"auto\";\n\n //Change the pointer icon to a hand\n if (pointer.visible) pointer.cursor = \"pointer\";\n } else {\n //Turn the pointer to an ordinary arrow icon if the\n //pointer isn't touching a sprite\n if (pointer.visible) pointer.cursor = \"auto\";\n\n }\n\n //Perform the correct interactive action\n\n //a. Run the `press` method if the sprite state is \"down\" and\n //the sprite hasn't already been pressed\n if (o.state === \"down\") {\n if (!o.pressed) {\n if (o.press) o.press();\n o.pressed = true;\n o.action = \"pressed\";\n }\n }\n\n //b. Run the `release` method if the sprite state is \"over\" and\n //the sprite has been pressed\n if (o.state === \"over\") {\n if (o.pressed) {\n if (o.release) o.release();\n o.pressed = false;\n o.action = \"released\";\n //If the pointer was tapped and the user assigned a `tap`\n //method, call the `tap` method\n if (pointer.tapped && o.tap) o.tap();\n }\n\n //Run the `over` method if it has been assigned\n if (!o.hoverOver) {\n if (o.over) o.over();\n o.hoverOver = true;\n }\n }\n\n //c. Check whether the pointer has been released outside\n //the sprite's area. If the button state is \"up\" and it's\n //already been pressed, then run the `release` method.\n if (o.state === \"up\") {\n if (o.pressed) {\n if (o.release) o.release();\n o.pressed = false;\n o.action = \"released\";\n }\n\n //Run the `out` method if it has been assigned\n if (o.hoverOver) {\n if (o.out) o.out();\n o.hoverOver = false;\n }\n }\n }\n });\n\n if (pointer.shouldBeHand) {\n pointer.cursor = \"pointer\";\n } else {\n pointer.cursor = \"auto\";\n }\n\n\n });\n }\n\n //A function that creates a sprite with 3 frames that\n //represent the button states: up, over and down\n button(source, x = 0, y = 0) {\n\n //The sprite object that will be returned\n let o;\n\n //Is it an array of frame ids or textures?\n if (typeof source[0] === \"string\") {\n\n //They're strings, but are they pre-existing texture or\n //paths to image files?\n //Check to see if the first element matches a texture in the\n //cache\n if (this.TextureCache[source[0]]) {\n\n //It does, so it's an array of frame ids\n o = this.AnimatedSprite.fromFrames(source);\n } else {\n\n //It's not already in the cache, so let's load it\n o = this.AnimatedSprite.fromImages(source);\n }\n }\n\n //If the `source` isn't an array of strings, check whether\n //it's an array of textures\n else if (source[0] instanceof this.Texture) {\n\n //Yes, it's an array of textures. \n //Use them to make a AnimatedSprite o \n o = new this.AnimatedSprite(source);\n }\n\n //Add interactive properties to the button\n this.makeInteractive(o);\n\n //Set the `tinkType` to \"button\"\n o.tinkType = \"button\";\n\n //Position the button\n o.x = x;\n o.y = y;\n\n //Return the new button sprite\n return o;\n }\n\n //Run the `udpate` function in your game loop\n //to update all of Tink's interactive objects\n update() {\n\n //Update the drag and drop system\n if (this.draggableSprites.length !== 0) this.updateDragAndDrop(this.draggableSprites);\n\n //Update the buttons and button-like interactive sprites\n if (this.buttons.length !== 0) this.updateButtons();\n }\n\n /*\n `keyboard` is a method that listens for and captures keyboard events. It's really\n just a convenient wrapper function for HTML `keyup` and `keydown` events so that you can keep your application code clutter-free and easier to write and read.\n\n Here's how to use the `keyboard` method. Create a new keyboard object like this:\n ```js\n let keyObject = keyboard(asciiKeyCodeNumber);\n ```\n It's one argument is the ASCII key code number of the keyboard key\n that you want to listen for. [Here's a list of ASCII key codes you can\n use](http://www.asciitable.com).\n Then assign `press` and `release` methods to the keyboard object like this:\n ```js\n keyObject.press = () => {\n //key object pressed\n };\n keyObject.release = () => {\n //key object released\n };\n ```\n Keyboard objects also have `isDown` and `isUp` Boolean properties that you can use to check the state of each key. \n */\n keyboard(keyCode) {\n let key = {};\n key.code = keyCode;\n key.isDown = false;\n key.isUp = true;\n key.press = undefined;\n key.release = undefined;\n\n //The `downHandler`\n key.downHandler = event => {\n if (event.keyCode === key.code) {\n if (key.isUp && key.press) key.press();\n key.isDown = true;\n key.isUp = false;\n }\n event.preventDefault();\n };\n\n //The `upHandler`\n key.upHandler = event => {\n if (event.keyCode === key.code) {\n if (key.isDown && key.release) key.release();\n key.isDown = false;\n key.isUp = true;\n }\n event.preventDefault();\n };\n\n //Attach event listeners\n window.addEventListener(\n \"keydown\", key.downHandler.bind(key), false\n );\n window.addEventListener(\n \"keyup\", key.upHandler.bind(key), false\n );\n\n //Return the key object\n return key;\n }\n\n //`arrowControl` is a convenience method for updating a sprite's velocity\n //for 4-way movement using the arrow directional keys. Supply it\n //with the sprite you want to control and the speed per frame, in\n //pixels, that you want to update the sprite's velocity\n arrowControl(sprite, speed) {\n\n if (speed === undefined) {\n throw new Error(\"Please supply the arrowControl method with the speed at which you want the sprite to move\");\n }\n\n let upArrow = this.keyboard(38),\n rightArrow = this.keyboard(39),\n downArrow = this.keyboard(40),\n leftArrow = this.keyboard(37);\n\n //Assign key `press` methods\n leftArrow.press = () => {\n //Change the sprite's velocity when the key is pressed\n sprite.vx = -speed;\n sprite.vy = 0;\n };\n leftArrow.release = () => {\n //If the left arrow has been released, and the right arrow isn't down,\n //and the sprite isn't moving vertically: \n //Stop the sprite\n if (!rightArrow.isDown && sprite.vy === 0) {\n sprite.vx = 0;\n }\n };\n upArrow.press = () => {\n sprite.vy = -speed;\n sprite.vx = 0;\n };\n upArrow.release = () => {\n if (!downArrow.isDown && sprite.vx === 0) {\n sprite.vy = 0;\n }\n };\n rightArrow.press = () => {\n sprite.vx = speed;\n sprite.vy = 0;\n };\n rightArrow.release = () => {\n if (!leftArrow.isDown && sprite.vy === 0) {\n sprite.vx = 0;\n }\n };\n downArrow.press = () => {\n sprite.vy = speed;\n sprite.vx = 0;\n };\n downArrow.release = () => {\n if (!upArrow.isDown && sprite.vx === 0) {\n sprite.vy = 0;\n }\n };\n }\n}"]} --------------------------------------------------------------------------------