├── .gitignore ├── LICENSE.md ├── README.md ├── emthree ├── effects.lua ├── emthree.lua └── internal │ ├── async.lua │ └── utils.lua ├── examples ├── basic │ ├── assets │ │ ├── brickBlue06.png │ │ ├── brickGreen05.png │ │ ├── brickGrey.png │ │ ├── brickRed04.png │ │ ├── brickSpecial01.png │ │ ├── brickSpecial02.png │ │ ├── brickSpecial03.png │ │ ├── brickSpecial06.png │ │ ├── brickSpecial07.png │ │ ├── brickSpecial08.png │ │ ├── brickSpecial09.png │ │ ├── brickSpecial10.png │ │ ├── brickWhite05.png │ │ └── brickYellow03.png │ ├── basic.atlas │ ├── basic.collection │ ├── basic.script │ ├── brick.go │ └── brick.script ├── debug │ ├── debug.font │ └── debug.go ├── examples.collection ├── examples.gui ├── examples.gui_script └── ocean-commotion │ ├── assets │ ├── effects │ │ ├── bubble.png │ │ ├── white_dot.png │ │ └── white_dot2.png │ ├── explode.particlefx │ ├── fish │ │ ├── eyes-blue.png │ │ ├── eyes-green.png │ │ ├── eyes-orange.png │ │ ├── eyes-purple.png │ │ ├── eyes-red.png │ │ ├── eyes-starfish.png │ │ ├── eyes-yellow.png │ │ ├── fish-blue-h.png │ │ ├── fish-blue-v.png │ │ ├── fish-blue-w.png │ │ ├── fish-blue.png │ │ ├── fish-bones.png │ │ ├── fish-green-h.png │ │ ├── fish-green-v.png │ │ ├── fish-green-w.png │ │ ├── fish-green.png │ │ ├── fish-orange-h.png │ │ ├── fish-orange-v.png │ │ ├── fish-orange-w.png │ │ ├── fish-orange.png │ │ ├── fish-purple-h.png │ │ ├── fish-purple-v.png │ │ ├── fish-purple-w.png │ │ ├── fish-purple.png │ │ ├── fish-red-h.png │ │ ├── fish-red-v.png │ │ ├── fish-red-w.png │ │ ├── fish-red.png │ │ ├── fish-starfish.png │ │ ├── fish-yellow-h.png │ │ ├── fish-yellow-v.png │ │ ├── fish-yellow-w.png │ │ └── fish-yellow.png │ ├── font │ │ ├── Pacifico.ttf │ │ └── text.font │ ├── sprites.atlas │ └── ui │ │ ├── bg.png │ │ ├── button-YES.png │ │ ├── button1.png │ │ └── button_NO.png │ ├── board.script │ ├── color.lua │ ├── fish.go │ ├── fish.script │ ├── ocean-commotion.collection │ └── type.lua ├── game.project ├── images ├── block.png ├── board.png ├── match.png └── slot.png └── input └── game.input_binding /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .externalToolBuilders 3 | .DS_Store 4 | .lock-wscript 5 | build 6 | *.pyc 7 | .project 8 | .cproject 9 | builtins 10 | .internal 11 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Björn Ritzl 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 | # Emthree 2 | Emthree is a match three engine for Defold. 3 | 4 | 5 | # Setup 6 | ## Add project dependencies 7 | You can use Emthree in your own project by adding this project as a [Defold library dependency](http://www.defold.com/manuals/libraries/). Open your game.project file and in the dependencies field under project add: 8 | 9 | https://github.com/britzl/emthree/archive/master.zip 10 | 11 | Or point to the ZIP file of a [specific release](https://github.com/britzl/emthree/releases). 12 | 13 | 14 | # Examples 15 | Two examples, one basic and one a bit more advanced are available to try here: 16 | 17 | * https://britzl.github.io/Emthree/index.html 18 | 19 | 20 | # Usage 21 | 22 | ## Terminology 23 | This documentation uses a number of different concepts to describe the functionality of Emthree. Please refer to the list below to better understand how Emthree works. 24 | 25 | ### Board 26 | A grid of slots where the game is played. 27 | 28 | ![](images/board.png) 29 | 30 | ### Slot 31 | A position on the board. A slot can hold a block. 32 | 33 | ![](images/slot.png) 34 | 35 | ### Block 36 | A block represents an element in a slot on the board. It could be fish, candy, cakes or something else, fitting the theme of the game. Blocks will fall if there are free slots below them. Blocks have a color and an optional type. 37 | 38 | ![](images/block.png) 39 | 40 | ### Blocker 41 | A blocker represents an element in a slot on the board that is static. It will not fall and it will stop blocks from falling any further. Blockers have an optional type. 42 | 43 | ### Spawner 44 | A spawner represents an element in a slot on the board that creates/spawns blocks. It will not fall and it will stop blocks from falling any further. Spawners have an optional type. 45 | 46 | ### Match 47 | Three or more blocks of the same color is considered a match. 48 | 49 | ![](images/match.png) 50 | 51 | ### Stable Board 52 | A board is considered stable when the board doesn't contain any matches and all slots are filled with blocks. 53 | 54 | 55 | ## EmthreeAPI - The basics 56 | 57 | ### emthree.create_board(width, height, block_size) 58 | Create an Emthree board of the specified dimensions 59 | 60 | **PARAMETERS** 61 | * ```width``` (number) - Board width in blocks 62 | * ```height``` (number) - Board height in blocks 63 | * ```block_size``` (number) - Size of a block 64 | * ```config``` (table) - Optional table with board configuration values 65 | 66 | The `config` table can contain the following values: 67 | * ```remove_duration``` - Time in seconds to wait while removing blocks 68 | * ```swap_duration``` Time in seconds for the swap animation 69 | * ```slide_duration``` - Time in seconds for the slide animation 70 | * ```collapse_duration``` - Time in seconds for the collapse animation 71 | * ```collapse_direction``` - Direction to move blocks in when collapsing the board after removing blocks. Can be one of `emthree.COLLAPSE_UP`, `emthree.COLLAPSE_DOWN`, `emthree.COLLAPSE_LEFT` or `emthree.COLLAPSE_RIGHT` 72 | * ```slide_easing``` - Easing function to apply when sliding blocks. Any of the `go.EASING_*` constants. 73 | * ```collapse_easing``` - Easing function to apply when collapsing blocks. Any of the `go.EASING_*` constants. 74 | 75 | **RETURN** 76 | * ```board``` (table) - A representation of the board, complete with a 2D array of blocks 77 | 78 | 79 | 80 | ### emthree.stabilize(board, callback) 81 | Stabilize the board. This will search through the board for any matches, remove these and the spawn new blocks. This process will be repeated until there are no matches to remove or new blocks to spawn. When the board is stable it will invoke the function specified by `emthree.on_stabilized`. 82 | 83 | **PARAMETERS** 84 | * ```board``` (table) - The board to stabilize 85 | * ```callback``` (function) - Optional function to call when the board is stable 86 | 87 | 88 | ### emthree.on_input(board, action) 89 | Pass user input to the board to detect interaction with the blocks. Only touch/click events with a state of `pressed` or `released` must be sent. When a block is selected through a click Emthree will send an `emthree.SELECT` message to the game object representing the block. When a block is deselected an `emthree.RESET` message is sent. Emthree will release input focus while processing input and acquire it again when the board is stable. 90 | 91 | **PARAMETERS** 92 | * ```board``` (table) - The board to apply the input to 93 | * ```action``` (table) - The action table received from the engine lifecycle function on_input. Only pass on touch/click events where `pressed` or `released` is true. 94 | 95 | 96 | ## EmthreeAPI - Slot access 97 | 98 | ### emthreee.iterate_blocks(board, filter_fn) 99 | Return an iterator function that can be used to iterate all blocks on the board. The blocks can optionally be filtered by the iterator to only return blocks that match certain criteria 100 | 101 | **PARAMETERS** 102 | * ```board``` (table) - The board to iterate 103 | * ```filter_fn``` (function) - A function that can be used to filter blocks. The function will receive the board, x and y position as arguments and must return true or false depending on if the block at that position should be returned or not. 104 | 105 | **RETURN** 106 | * ```fn``` (function) - The iterator function. The function will return a block. 107 | 108 | 109 | ### emthree.get_blocks(board, color) 110 | Get all blocks of a certain color 111 | 112 | **PARAMETERS** 113 | * ```board``` (table) - The board to get blocks from 114 | * ```color``` (any) - The color to search for 115 | 116 | **RETURN** 117 | * ```blocks``` (table) - A list of all blocks matching the specified color 118 | 119 | 120 | ### emthree.get_block(board, x, y) 121 | Get the block at a specific position 122 | 123 | **PARAMETERS** 124 | * ```board``` (table) - The board to get the block from 125 | * ```x``` (number) - Horizontal position 126 | * ```y``` (number) - Vertical position 127 | 128 | **RETURN** 129 | * ```block``` (table) - The block or nil if position is outside board 130 | 131 | 132 | ### emthree.on_board(board, x, y) 133 | Check if a position is on the board 134 | 135 | **PARAMETERS** 136 | * ```board``` (table) - The board to check position on 137 | * ```x``` (number) - The horizontal slot position 138 | * ```y``` (number) - The vertical slot position 139 | 140 | **RETURN** 141 | * ```on_board``` (boolean) - True if the position is on the board 142 | 143 | 144 | ### emthree.is_block(board, x, y) 145 | Check if the content of a slot is a block 146 | 147 | **PARAMETERS** 148 | * ```board``` (table) - The board to check 149 | * ```x``` (number) - Horizontal position 150 | * ```y``` (number) - Vertical position 151 | 152 | **RETURN** 153 | * ```result``` (boolean) - True if there is a block in the slot 154 | 155 | 156 | ### emthree.is_blocker(board, x, y) 157 | Check if the content of a slot is a blocker 158 | 159 | **PARAMETERS** 160 | * ```board``` (table) - The board to check 161 | * ```x``` (number) - Horizontal position 162 | * ```y``` (number) - Vertical position 163 | 164 | **RETURN** 165 | * ```result``` (boolean) - True if there is a blocker in the slot 166 | 167 | 168 | ### emthree.is_spawner(board, x, y) 169 | Check if the content of a slot is a spawner 170 | 171 | **PARAMETERS** 172 | * ```board``` (table) - The board to check 173 | * ```x``` (number) - Horizontal position 174 | * ```y``` (number) - Vertical position 175 | 176 | **RETURN** 177 | * ```result``` (boolean) - True if there is a spawner in the slot 178 | 179 | 180 | ### emthree.is_empty(board, x, y) 181 | Check if a slot is empty 182 | 183 | **PARAMETERS** 184 | * ```board``` (table) - The board to check 185 | * ```x``` (number) - Horizontal position 186 | * ```y``` (number) - Vertical position 187 | 188 | **RETURN** 189 | * ```result``` (boolean) - True if the slot is empty 190 | 191 | 192 | ## EmthreeAPI - Remove, change and create blocks 193 | 194 | ### emthree.remove_block(board, block) 195 | Remove a block from the board. This will invoke the function specified by `emthree.on_block_removed()`. This will also post an `emthree.REMOVE` message to the game object representing the block. 196 | 197 | **PARAMETERS** 198 | * ```board``` (table) - The board to remove block from 199 | * ```block``` (table) - The block to remove 200 | 201 | 202 | ### emthree.remove_blocks(board, blocks) 203 | Remove a list of blocks. Will repeatedly call `emthree.remove_block()`. 204 | 205 | **PARAMETERS** 206 | * ```board``` (table) - The board to remove blocks from 207 | * ```blocks``` (table) - The blocks to remove 208 | 209 | 210 | ### emthree.change_block(block, type, color) 211 | Change a block on the board from one type and color to another. This will post an `emthree.CHANGE` message to the game object representing the block. 212 | 213 | **PARAMETERS** 214 | * ```block``` (table) - The block to change 215 | * ```type``` (any) - The type to change to 216 | * ```color``` (any) - The color to change to 217 | 218 | 219 | ### emthree.create_block(board, x, y, type, color) 220 | Create a new block on the board. This will call the function passed to `emthree.on_create_block()`. 221 | 222 | **PARAMETERS** 223 | * ```board``` (table) - The board to create block on 224 | * ```x``` (number) - The horizontal slot position of the block 225 | * ```y``` (number) - The vertical slot position of the block 226 | * ```type``` (any) - The type of the block 227 | * ```color``` (any) - The color of the block 228 | 229 | **RETURN** 230 | * ```block``` (table) - The created block 231 | 232 | 233 | ### emthree.create_blocker(board, x, y, type) 234 | Create a new blocker on the board. This will call the function passed to `emthree.on_create_blocker()` 235 | 236 | **PARAMETERS** 237 | * ```board``` (table) - The board to create blocker on 238 | * ```x``` (number) - The horizontal slot position of the blocker 239 | * ```y``` (number) - The vertical slot position of the blocker 240 | * ```type``` (any) - The type of the blocker 241 | 242 | **RETURN** 243 | * ```blocker``` (table) - The created blocker 244 | 245 | 246 | ### emthree.create_spawner(board, x, y, type) 247 | Create a new spawner on the board. This will call the function passed to `emthree.on_create_spawner()` 248 | 249 | **PARAMETERS** 250 | * ```board``` (table) - The board to create spawner on 251 | * ```x``` (number) - The horizontal slot position of the spawner 252 | * ```y``` (number) - The vertical slot position of the spawner 253 | * ```type``` (any) - The type of the spawner 254 | 255 | **RETURN** 256 | * ```blocker``` (table) - The created spawner 257 | 258 | 259 | ### emthree.fill_board(board) 260 | Fill the board with blocks. This will call the function passed to `emthree.on_create_block()`. 261 | 262 | **PARAMETERS** 263 | * ```board``` (table) - The board to fill with blocks 264 | 265 | 266 | ### emthree.shuffle(board, callback) 267 | Shuffle the blocks on the board. 268 | 269 | **PARAMETERS** 270 | * ```board``` (table) - The board to shuffle blocks on 271 | 272 | 273 | ## EmthreeAPI - Callbacks 274 | 275 | ### emthree.on_create_block(board, fn) 276 | Set a function to be called whenever a block is to be created on the board. The function is expected to spawn a game object and return the game object id. 277 | 278 | **PARAMETERS** 279 | * ```board``` (table) - The board that will notify when a new block needs to be created 280 | * ```fn``` (function) - The function to call when a block should be created 281 | 282 | The function must accept and return the following: 283 | 284 | **PARAMETERS** 285 | * ```board``` (table) - The board where the block should be created 286 | * ```position``` (vector3) - Position of the block to create 287 | * ```type``` (any) - The block type, can be nil 288 | * ```color``` (any) - The color of the block 289 | 290 | **RETURN** 291 | * ```id``` (hash) - Id of the created game object 292 | * ```type``` (any) - Type of the created block 293 | * ```color``` (any) - Color of the created block 294 | 295 | 296 | ### emthree.on_create_blocker(board, fn) 297 | Set a function to be called whenever a blocker is to be created on the board. The function is expected to spawn a game object and return the game object id. 298 | 299 | **PARAMETERS** 300 | * ```board``` (table) - The board that will notify when a new blocker needs to be created 301 | * ```fn``` (function) - The function to call when a blocker should be created 302 | 303 | The function must accept and return the following: 304 | 305 | **PARAMETERS** 306 | * ```board``` (table) - The board where the blocker should be created 307 | * ```position``` (vector3) - Position of the blocker to create 308 | * ```type``` (any) - The blocker type, can be nil 309 | 310 | **RETURN** 311 | * ```id``` (hash) - Id of the created game object 312 | * ```type``` (any) - Type of the created blocker 313 | 314 | 315 | ### emthree.on_create_spawner(board, fn) 316 | Set a function to be called whenever a spawner is to be created on the board. The function is expected to spawn a game object and return the game object id. 317 | 318 | **PARAMETERS** 319 | * ```board``` (table) - The board that will notify when a new spawner needs to be created 320 | * ```fn``` (function) - The function to call when a spawner should be created 321 | 322 | The function must accept and return the following: 323 | 324 | **PARAMETERS** 325 | * ```board``` (table) - The board where the spawner should be created 326 | * ```position``` (vector3) - Position of the spawner to create 327 | * ```type``` (any) - The spawner type, can be nil 328 | 329 | **RETURN** 330 | * ```id``` (hash) - Id of the created game object 331 | * ```type``` (any) - Type of the created spawner 332 | 333 | 334 | ### emthree.on_match(board, fn) 335 | Set a function to be called whenever a match on the board is detected. Use this callback to remove the blocks involved in the match and optionally also create new blocks based on the match. The default implementation will remove the blocks and do nothing else. 336 | 337 | **PARAMETERS** 338 | * ```board``` (table) - The board that will notify when a match is detected 339 | * ```fn``` (function) - The function to call when a match is detected. 340 | 341 | The function must accept the following arguments: 342 | 343 | **PARAMETERS** 344 | * ```board``` (table) - The board where the match was detected 345 | * ```block``` (table) - A block that is part of the match 346 | * ```horisontal_neighbors``` (table) - A list of horizontal neighboring blocks that are part of the match 347 | * ```vertical_neighbors``` (table) - A list of vertical neighboring blocks that are part of the match 348 | 349 | 350 | ### emthree.on_block_removed(board, fn) 351 | Set a function to be called whenever a block is removed. Use this callback to trigger effects on special blocks. 352 | 353 | **PARAMETERS** 354 | * ```board``` (table) - The board that will notify when a block is removed 355 | * ```fn``` (function) - The function to call when a block is removed. The function will receive `board` and `block` as arguments. 356 | 357 | 358 | ### emthree.on_stabilized(board, fn) 359 | Set a function to be called when the board is stable. 360 | 361 | **PARAMETERS** 362 | * ```board``` (table) - The board that will notify when stable 363 | * ```fn``` (function) - The function to call when the board is stable. The function will receive `board` as arguments. 364 | 365 | 366 | ### emthree.on_swap(board, fn) 367 | Set a function to be called whenever a two blocks have swapped places based on user input. Use this to trigger effects when certain types of blocks were swapped. 368 | 369 | **PARAMETERS** 370 | * ```board``` (table) - The board where swaps will be notified 371 | * ```fn``` (function) - The function to call when a swap is made. The function is expected to take `board`, `slot1` and `slot2` as arguments and return true if swap resulted in any changes on the board. 372 | 373 | 374 | ### emthree.on_no_possible_switches(board, fn) 375 | Set a function to be called whenever the board has no possible switches available to create a match. You can use `emthree.shuffle()` to attempt to rearragne the blocks. 376 | 377 | **PARAMETERS** 378 | * ```board``` (table) - The board where no possible switches availab le will be notified 379 | * ```fn``` (function) - The function to call when no possible switches are detected. The function is expected to take `board` as arguments. 380 | 381 | 382 | ## EmthreeAPI - Utility functions 383 | 384 | ### emthree.screen_to_slot(board, x, y) 385 | Get the slot at a screen position (pixels). 386 | 387 | **PARAMETERS** 388 | * ```board``` (table) - The board to get the slot from 389 | * ```x``` (number) - Horizontal screen position (pixels) 390 | * ```y``` (number) - Vertical screen position (pixels) 391 | 392 | **RETURN** 393 | * ```x``` (number) - Horizontal slot position 394 | * ```y``` (number) - Vertical slot position 395 | 396 | 397 | ### emthree.slot_to_screen(board, x, y) 398 | Get the screen position (pixels) of a slot. 399 | 400 | **PARAMETERS** 401 | * ```board``` (table) - The board to get the slot from 402 | * ```x``` (number) - Horizontal slot position 403 | * ```y``` (number) - Vertical slot position 404 | 405 | **RETURN** 406 | * ```x``` (number) - Horizontal screen position (pixels) 407 | * ```y``` (number) - Vertical screen position (pixels) 408 | 409 | 410 | ## EmthreeAPI - Messages 411 | 412 | ### emthree.REMOVE 413 | Message sent to the game object representing a block on the board when it is to be removed. 414 | 415 | 416 | ### emthree.CHANGE 417 | Message sent to the game object representing a block on the board when it is to change color and/or type. 418 | 419 | **PARAMETERS** 420 | * ```color``` (any) - The color to change to 421 | * ```type``` (any) - The type to change to 422 | 423 | 424 | ### emthree.SELECT 425 | Message sent to the game object representing a block on the board when it is selected. 426 | 427 | 428 | ### emthree.RESET 429 | Message sent to the game object representing a block on the board when it is deselected. 430 | -------------------------------------------------------------------------------- /emthree/effects.lua: -------------------------------------------------------------------------------- 1 | local emthree = require "emthree.emthree" 2 | 3 | local M = {} 4 | 5 | 6 | 7 | function M.horisontal_lineblast(board, block, width) 8 | assert(board, "You must provide a board") 9 | assert(block, "You must provide a block") 10 | width = width or 1 11 | for w=0,width-1 do 12 | local y = block.y - math.floor(width / 2) + w 13 | if emthree.on_board(board, block.x, y) then 14 | emthree.remove_block(board, board.slots[block.x][y]) 15 | for x=block.x, 0, -1 do 16 | if emthree.is_block(board, x, y) then emthree.remove_block(board, board.slots[x][y]) end 17 | end 18 | for x=block.x, board.width -1 do 19 | if emthree.is_block(board, x, y) then emthree.remove_block(board, board.slots[x][y]) end 20 | end 21 | end 22 | end 23 | end 24 | 25 | 26 | function M.vertical_lineblast(board, block, width) 27 | assert(board, "You must provide a board") 28 | assert(block, "You must provide a block") 29 | width = width or 1 30 | for w=0,width-1 do 31 | local x = block.x - math.floor(width / 2) + w 32 | if emthree.on_board(board, x, block.y) then 33 | emthree.remove_block(board, board.slots[x][block.y]) 34 | for y=block.y, 0, -1 do 35 | if emthree.is_block(board, x, y) then emthree.remove_block(board, board.slots[x][y]) end 36 | end 37 | for y=block.y, board.height -1 do 38 | if emthree.is_block(board, x, y) then emthree.remove_block(board, board.slots[x][y]) end 39 | end 40 | end 41 | end 42 | end 43 | 44 | 45 | function M.bomb(board, block, radius) 46 | assert(board, "You must provide a board") 47 | assert(block, "You must provide a block") 48 | assert(radius, "You must provide a radius") 49 | for r=1,radius do 50 | for x=block.x-r,block.x+r do 51 | for y=block.y-r,block.y+r do 52 | if emthree.is_block(board, x, y) then 53 | emthree.remove_block(board, board.slots[x][y]) 54 | end 55 | end 56 | end 57 | end 58 | end 59 | 60 | 61 | function M.remove_color(board, color) 62 | assert(board, "You must provide a board") 63 | assert(color, "You must provide a color") 64 | local blocks = emthree.get_blocks(board, color) 65 | while #blocks > 0 do 66 | local b = table.remove(blocks, math.random(1, #blocks)) 67 | emthree.remove_block(board, b) 68 | end 69 | end 70 | 71 | 72 | function M.remove_all(board) 73 | assert(board, "You must provide a board") 74 | local blocks = emthree.get_blocks(board) 75 | while #blocks > 0 do 76 | local b = table.remove(blocks, math.random(1, #blocks)) 77 | if emthree.is_block(board, b.x, b.y) then 78 | emthree.remove_block(board, b) 79 | end 80 | end 81 | end 82 | 83 | return M -------------------------------------------------------------------------------- /emthree/emthree.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local async = require "emthree.internal.async" 4 | local utils = require "emthree.internal.utils" 5 | 6 | M.REMOVE = hash("emthree_remove") 7 | M.CHANGE = hash("emthree_change") 8 | M.SELECT = hash("emthree_select") 9 | M.RESET = hash("emthree_reset") 10 | 11 | 12 | M.COLLAPSE_DOWN = hash("emthree_slide_down") 13 | M.COLLAPSE_UP = hash("emthree_slide_up") 14 | M.COLLAPSE_LEFT = hash("emthree_slide_left") 15 | M.COLLAPSE_RIGHT = hash("emthree_slide_right") 16 | 17 | local DIRECTIONS = { 18 | [M.COLLAPSE_DOWN] = vmath.vector3(0, -1, 0), 19 | [M.COLLAPSE_UP] = vmath.vector3(0, 1, 0), 20 | [M.COLLAPSE_LEFT] = vmath.vector3(-1, 0, 0), 21 | [M.COLLAPSE_RIGHT] = vmath.vector3(1, 0, 0), 22 | } 23 | 24 | local ORTHOGONAL = { 25 | [M.COLLAPSE_DOWN] = vmath.vector3(1, 0, 0), 26 | [M.COLLAPSE_UP] = vmath.vector3(1, 0, 0), 27 | [M.COLLAPSE_LEFT] = vmath.vector3(0, 1, 0), 28 | [M.COLLAPSE_RIGHT] = vmath.vector3(0, 1, 0), 29 | } 30 | 31 | 32 | local function spawner_filter(board, x, y) 33 | local block = board.slots[x][y] 34 | return block and block.spawner 35 | end 36 | 37 | 38 | local function create_color_filter(color) 39 | return function(board, x, y) 40 | local block = board.slots[x][y] 41 | return (not color and block) or (color and block and block.color == color) 42 | end 43 | end 44 | 45 | 46 | local function delay(seconds, fn, a) 47 | timer.delay(seconds, false, function() 48 | fn(a) 49 | end) 50 | end 51 | 52 | -- 53 | -- Returns a list of neighbor slots of the same color as 54 | -- the one on x, y. Horizontally. 55 | -- 56 | local function horisontal_neighbors(board, x, y) 57 | assert(board, "You must provide a board") 58 | local neighbors = {} 59 | if not board.slots[x][y] or not board.slots[x][y].color then 60 | return neighbors 61 | end 62 | 63 | local color = board.slots[x][y].color 64 | -- 65 | -- Search from slot left to the edge 66 | -- 67 | for i = x - 1, 0, -1 do 68 | local block = board.slots[i][y] 69 | if block and block.color == color then 70 | table.insert(neighbors, block) 71 | else 72 | -- 73 | -- Break the search as soon as we hit something of a different color 74 | -- 75 | break 76 | end 77 | end 78 | 79 | -- 80 | -- Search from slot right to the edge 81 | -- 82 | for i = x + 1, board.width - 1 do 83 | local block = board.slots[i][y] 84 | if block and block.color == color then 85 | table.insert(neighbors, block) 86 | else 87 | -- 88 | -- Break the search as soon as we hit something of a different color 89 | -- 90 | break 91 | end 92 | end 93 | return neighbors 94 | end 95 | 96 | -- 97 | -- Returns a list of neighbor slots of the same color as 98 | -- the one on x, y. Vertically. 99 | -- 100 | local function vertical_neighbors(board, x, y) 101 | assert(board, "You must provide a board") 102 | local neighbors = {} 103 | if not board.slots[x][y] or not board.slots[x][y].color then 104 | return neighbors 105 | end 106 | 107 | local color = board.slots[x][y].color 108 | 109 | -- 110 | -- Search from slot down to the edge 111 | -- 112 | for i = y - 1, 0, -1 do 113 | local slot = board.slots[x][i] 114 | if slot and slot.color == color then 115 | table.insert(neighbors, slot) 116 | else 117 | -- 118 | -- Break the search as soon as we hit something of a different type 119 | -- 120 | break 121 | end 122 | end 123 | 124 | -- 125 | -- Search from slot up to the edge 126 | -- 127 | for i = y + 1, board.height - 1 do 128 | local slot = board.slots[x][i] 129 | if slot and slot.color == color then 130 | table.insert(neighbors, slot) 131 | else 132 | -- 133 | -- Break the search as soon as we hit something of a different color 134 | -- 135 | break 136 | end 137 | end 138 | return neighbors 139 | end 140 | 141 | 142 | -- 143 | -- Scans the board for any matching neighbors (row or column) 144 | -- and count them. 145 | -- 146 | local function find_matching_neighbors(board) 147 | assert(board, "You must provide a board") 148 | for block in M.iterate_blocks(board) do 149 | -- 150 | -- Count the same type line of neighbors horisontally and 151 | -- vertically. Note that any number of subsequent neighbors 152 | -- are counted, so if a blue block has 3 blue block immediately 153 | -- to the right and 3 to the left if has 6 horisontal neighbors. 154 | -- 155 | local hn = horisontal_neighbors(board, block.x, block.y) 156 | local vn = vertical_neighbors(board, block.x, block.y) 157 | block.horisontal_neighbors = hn 158 | block.vertical_neighbors = vn 159 | end 160 | end 161 | 162 | 163 | -- 164 | -- Remove blocks that are part of matches, then call callback 165 | -- 166 | local function remove_matching_neighbors(board, callback) 167 | assert(board, "You must provide a board") 168 | assert(callback, "You must provide a callback") 169 | local duration = 0 170 | 171 | local did_remove = false 172 | -- handle t, l and cross shaped block formations in a first pass 173 | for block in M.iterate_blocks(board) do 174 | if #block.horisontal_neighbors >= 2 and #block.vertical_neighbors >= 2 then 175 | did_remove = true 176 | board.on_match(board, block, block.horisontal_neighbors, block.vertical_neighbors) 177 | end 178 | end 179 | 180 | -- handle horisontal and vertical 181 | for block in M.iterate_blocks(board) do 182 | if #block.horisontal_neighbors >= 2 or #block.vertical_neighbors >= 2 then 183 | did_remove = true 184 | board.on_match(board, block, block.horisontal_neighbors, block.vertical_neighbors) 185 | end 186 | end 187 | 188 | if did_remove then 189 | delay(duration, callback) 190 | else 191 | callback() 192 | end 193 | end 194 | 195 | -- 196 | -- Apply shift logic to all slots on the board, then 197 | -- call the callback. 198 | -- 199 | local function collapse(board, callback) 200 | assert(board, "You must provide a board") 201 | assert(callback, "You must provide a callback") 202 | local duration = board.config.collapse_duration 203 | 204 | local direction = DIRECTIONS[board.config.collapse_direction] 205 | 206 | local collapsed = false 207 | local blocks = board.slots 208 | local empty_x = nil 209 | local empty_y = nil 210 | local block_size = board.block_size 211 | 212 | local function check_collapse(x, y) 213 | local block = blocks[x][y] 214 | if block and block.blocker then 215 | empty_x = nil 216 | empty_y = nil 217 | elseif block and block.spawner then 218 | -- do nothing 219 | elseif block then 220 | -- collapse block if there's an empty block below 221 | if empty_x and empty_y then 222 | -- 223 | -- Move to empty slot 224 | -- 225 | blocks[empty_x][empty_y] = block 226 | blocks[x][y] = nil 227 | block.x = empty_x 228 | block.y = empty_y 229 | 230 | -- 231 | -- Calc new position and animate 232 | --- 233 | local to = vmath.vector3((block_size / 2) + (block_size * empty_x), (block_size / 2) + (block_size * empty_y), 0) 234 | go.animate(block.id, "position", go.PLAYBACK_ONCE_FORWARD, to, board.config.collapse_easing, duration) 235 | collapsed = true 236 | empty_x = empty_x - direction.x 237 | empty_y = empty_y - direction.y 238 | end 239 | -- first empty slot 240 | elseif not empty_x then 241 | empty_x = x 242 | empty_y = y 243 | end 244 | end 245 | 246 | if board.config.collapse_direction == M.COLLAPSE_DOWN then 247 | for x = 0,board.width - 1 do 248 | empty_x = nil 249 | empty_y = nil 250 | for y = 0,board.height - 1 do 251 | check_collapse(x, y) 252 | end 253 | end 254 | elseif board.config.collapse_direction == M.COLLAPSE_UP then 255 | for x = 0,board.width - 1 do 256 | empty_x = nil 257 | empty_y = nil 258 | for y = board.height-1,0,-1 do 259 | check_collapse(x, y) 260 | end 261 | end 262 | elseif board.config.collapse_direction == M.COLLAPSE_LEFT then 263 | for y = 0,board.height - 1 do 264 | empty_x = nil 265 | empty_y = nil 266 | for x = 0, board.width-1 do 267 | check_collapse(x, y) 268 | end 269 | end 270 | elseif board.config.collapse_direction == M.COLLAPSE_RIGHT then 271 | for y = 0,board.height - 1 do 272 | empty_x = nil 273 | empty_y = nil 274 | for x = board.width-1,0,-1 do 275 | check_collapse(x, y) 276 | end 277 | end 278 | end 279 | 280 | if collapsed then 281 | delay(duration, callback, collapsed) 282 | else 283 | callback(collapsed) 284 | end 285 | end 286 | 287 | -- 288 | -- Apply slide logic to all slots on the board, then 289 | -- call the callback. 290 | -- 291 | local function slide(board, callback) 292 | assert(board, "You must provide a board") 293 | assert(callback, "You must provide a callback") 294 | local duration = board.config.slide_duration 295 | 296 | local direction = DIRECTIONS[board.config.collapse_direction] 297 | local orthogonal = ORTHOGONAL[board.config.collapse_direction] 298 | 299 | local did_slide = false 300 | local blocks = board.slots 301 | local block_size = board.block_size 302 | 303 | local empty_right = 0 304 | local empty_left = 0 305 | 306 | local function check_slide(x, y) 307 | local block = blocks[x][y] 308 | if block and block.blocker then 309 | empty_right = 0 310 | empty_left = 0 311 | elseif block and not block.spawner and not block.blocker then 312 | -- slide block if there's empty slots left and/or right 313 | if empty_right >= 1 or empty_left >= 1 then 314 | -- left or right? 315 | if empty_right >= 1 and empty_left >= 1 then 316 | if (x % 2 == 0) then 317 | empty_right = 0 318 | else 319 | empty_left = 0 320 | end 321 | end 322 | 323 | -- where to slide? 324 | local tox = x 325 | local toy = y + direction.y 326 | if empty_right > 0 then 327 | tox = tox + orthogonal.x 328 | empty_right = 0 329 | else 330 | tox = tox - orthogonal.x 331 | empty_left = 0 332 | end 333 | blocks[tox][toy] = block 334 | blocks[x][y] = nil 335 | block.x = tox 336 | block.y = toy 337 | 338 | -- slide diagonal 339 | local to = vmath.vector3((block_size / 2) + (block_size * tox), (block_size / 2) + (block_size * toy), 0) 340 | go.animate(block.id, "position", go.PLAYBACK_ONCE_FORWARD, to, board.config.slide_easing, duration) 341 | did_slide = true 342 | else 343 | local rx = x + orthogonal.x 344 | local ry = y + orthogonal.y 345 | if M.is_empty(board, rx, ry) then 346 | empty_right = empty_right + 1 347 | elseif M.is_blocker(board, rx, ry) then 348 | empty_right = 0 349 | end 350 | local lx = x - orthogonal.x 351 | local ly = y - orthogonal.y 352 | if M.is_empty(board, lx, ly) then 353 | empty_left = empty_left + 1 354 | elseif M.is_blocker(board, lx, ly) then 355 | empty_left = 0 356 | end 357 | end 358 | end 359 | end 360 | 361 | if board.config.collapse_direction == M.COLLAPSE_DOWN then 362 | for x = 0,board.width - 1 do 363 | empty_right = 0 364 | empty_left = 0 365 | for y = 0,board.height - 1 do 366 | check_slide(x, y) 367 | if did_slide then 368 | break 369 | end 370 | end 371 | end 372 | elseif board.config.collapse_direction == M.COLLAPSE_UP then 373 | for x = 0,board.width - 1 do 374 | empty_right = 0 375 | empty_left = 0 376 | for y = board.height-1,0,-1 do 377 | check_slide(x, y) 378 | if did_slide then 379 | break 380 | end 381 | end 382 | end 383 | elseif board.config.collapse_direction == M.COLLAPSE_LEFT then 384 | for y = 0,board.height - 1 do 385 | empty_right = 0 386 | empty_left = 0 387 | for x = 0, board.width-1 do 388 | check_slide(x, y) 389 | if did_slide then 390 | break 391 | end 392 | end 393 | end 394 | elseif board.config.collapse_direction == M.COLLAPSE_RIGHT then 395 | for y = 0,board.height - 1 do 396 | empty_right = 0 397 | empty_left = 0 398 | for x = board.width-1,0,-1 do 399 | check_slide(x, y) 400 | if did_slide then 401 | break 402 | end 403 | end 404 | end 405 | end 406 | 407 | if did_slide then 408 | delay(duration, callback, did_slide) 409 | else 410 | callback(did_slide) 411 | end 412 | end 413 | 414 | 415 | -- 416 | -- Construct a new random board. It's a 2D table with origo in the 417 | -- bottom left corner: 418 | -- 419 | -- ... 420 | -- (0,2) (1,2) (2,2) 421 | -- (0,1) (1,1) (2,1) 422 | -- (0,0) (1,0) (2,0) ... 423 | -- 424 | -- Each slot stores the id of the game object that sits there, the x and y 425 | -- position and the type, for easy searching. Storing the x and y position is 426 | -- redundant but useful if we use the slots out of context, which we do at times. 427 | -- @param width 428 | -- @param height 429 | -- @param block_size Size of the blocks in pixels 430 | -- @param config Additional (and optional) board configuration values 431 | -- @return The created bord. Pass it when calling the other functions 432 | function M.create_board(width, height, block_size, config) 433 | assert(width, "You must provide a board width") 434 | assert(height, "You must provide a board height") 435 | assert(block_size, "You must provide a block size") 436 | config = config or {} 437 | config.swap_duration = config.swap_duration or 0.2 438 | config.remove_duration = config.remove_duration or 0 439 | config.slide_duration = config.slide_duration or 0.2 440 | config.collapse_duration = config.collapse_duration or 0.2 441 | config.collapse_direction = config.collapse_direction or M.COLLAPSE_DOWN 442 | config.collapse_easing = config.collapse_easing or go.EASING_OUTBOUNCE 443 | config.slide_easing = config.slide_easing or go.EASING_LINEAR 444 | 445 | local board = { 446 | width = width, 447 | height = height, 448 | block_size = block_size, 449 | slots = {}, 450 | config = config, 451 | } 452 | for x = 0, width - 1 do 453 | board.slots[x] = {} 454 | end 455 | 456 | M.on_match(board, function(board, block, horisontal_neighbors, vertical_neighbors) 457 | if #horisontal_neighbors >= 2 then 458 | M.remove_block(board, block) 459 | M.remove_blocks(board, horisontal_neighbors) 460 | end 461 | if #vertical_neighbors >= 2 then 462 | M.remove_block(board, block) 463 | M.remove_blocks(board, vertical_neighbors) 464 | end 465 | end) 466 | 467 | M.on_block_removed(board, function(board, block) 468 | -- do nothing 469 | end) 470 | 471 | M.on_stabilized(board, function(board) 472 | -- do nothing, board is stable 473 | end) 474 | 475 | M.on_swap(board, function(board, slot1, slot2) 476 | return false 477 | end) 478 | 479 | M.on_create_block(board, function(board, x, y, type, color) 480 | error("You must call emthree.on_create_block() and provide a function to create blocks on the board") 481 | end) 482 | 483 | M.on_create_blocker(board, function(board, x, y, type) 484 | return nil, type 485 | end) 486 | 487 | M.on_create_spawner(board, function(board, x, y, type) 488 | return nil, type 489 | end) 490 | 491 | M.on_no_possible_switches(board, function(board) 492 | -- do nothing 493 | end) 494 | return board 495 | end 496 | 497 | 498 | --- Fill the board with blocks 499 | -- @param board The board to fill 500 | function M.fill_board(board) 501 | for x = 0, board.width - 1 do 502 | for y = 0, board.height - 1 do 503 | if not board.slots[x][y] then 504 | M.create_block(board, x, y) 505 | end 506 | end 507 | end 508 | end 509 | 510 | 511 | local function swap(board, slot1, slot2, callback) 512 | local duration = board.config.swap_duration 513 | 514 | local pos1 = go.get_position(slot1.id) 515 | local pos2 = go.get_position(slot2.id) 516 | go.animate(slot1.id, "position", go.PLAYBACK_ONCE_FORWARD, pos2, go.EASING_INOUTSINE, duration) 517 | go.animate(slot2.id, "position", go.PLAYBACK_ONCE_FORWARD, pos1, go.EASING_INOUTSINE, duration) 518 | -- 519 | -- Switch the board structure data content of the two slots 520 | -- In Lua we can write a, b = b, a to swap two values 521 | -- 522 | local block1 = board.slots[slot1.x][slot1.y] 523 | local block2 = board.slots[slot2.x][slot2.y] 524 | board.slots[block1.x][block1.y], board.slots[block2.x][block2.y] = board.slots[block2.x][block2.y], board.slots[block1.x][block1.y] 525 | block1.x, block2.x = block2.x, block1.x 526 | block1.y, block2.y = block2.y, block1.y 527 | 528 | delay(duration, callback) 529 | end 530 | 531 | 532 | --- 533 | -- Swap the contents of two board slots 534 | -- @param board 535 | -- @param slot1 536 | -- @param slot2 537 | local function swap_slots(board, slot1, slot2) 538 | assert(board, "You must provide a board") 539 | assert(slot1, "You must provide a first slot") 540 | assert(slot2, "You must provide a second slot") 541 | assert(coroutine.running()) 542 | async(function(done) swap(board, slot1, slot2, done) end) 543 | if not board.on_swap(board, slot1, slot2) then 544 | local hn1 = horisontal_neighbors(board, slot1.x, slot1.y) 545 | local vn1 = vertical_neighbors(board, slot1.x, slot1.y) 546 | local hn2 = horisontal_neighbors(board, slot2.x, slot2.y) 547 | local vn2 = vertical_neighbors(board, slot2.x, slot2.y) 548 | -- not a valid swap, did not generate a match - swap back again 549 | if #hn1 < 2 and #hn2 < 2 and #vn1 < 2 and #vn2 < 2 then 550 | async(function(done) swap(board, slot1, slot2, done) end) 551 | end 552 | end 553 | M.stabilize(board) 554 | end 555 | 556 | 557 | -- 558 | -- Return an iterator function for use in generic for loops 559 | -- to iterate all blocks on the board 560 | -- @param board 561 | -- @param filter_fn Optional filter function (args: board, x, y) 562 | -- @return Function iterator 563 | function M.iterate_blocks(board, filter_fn) 564 | assert(board, "You must provide a board") 565 | local x = 0 566 | local y = -1 567 | return function() 568 | repeat 569 | y = y + 1 570 | if x == board.width - 1 and y == board.height then 571 | return nil 572 | end 573 | 574 | if y == board.height then 575 | y = 0 576 | x = x + 1 577 | end 578 | local block = board.slots[x][y] 579 | until (filter_fn and filter_fn(board, x, y) and block) or (not filter_fn and block) 580 | return board.slots[x][y] 581 | end 582 | end 583 | 584 | 585 | --- Check if a slot is empty 586 | -- @param board 587 | -- @param x 588 | -- @param y 589 | -- @return true if the slot is empty 590 | function M.is_empty(board, x, y) 591 | assert(board, "You must provide a board") 592 | assert(x and y, "You must provide a position") 593 | if M.on_board(board, x, y) then 594 | return board.slots[x][y] == nil 595 | end 596 | return false 597 | end 598 | 599 | 600 | --- Check if the content of a slot is a block 601 | -- @param board 602 | -- @param x 603 | -- @param y 604 | -- @return true if there is a block in the slot 605 | function M.is_block(board, x, y) 606 | assert(board, "You must provide a board") 607 | assert(x and y, "You must provide a position") 608 | if M.on_board(board, x, y) then 609 | return board.slots[x][y] and board.slots[x][y].block 610 | end 611 | return false 612 | end 613 | 614 | 615 | --- Check if the content of a slot is a blocker 616 | -- @param board 617 | -- @param x 618 | -- @param y 619 | -- @return true if there is a blocker in the slot 620 | function M.is_blocker(board, x, y) 621 | assert(board, "You must provide a board") 622 | assert(x and y, "You must provide a position") 623 | if M.on_board(board, x, y) then 624 | return board.slots[x][y] and board.slots[x][y].blocker 625 | end 626 | return false 627 | end 628 | 629 | 630 | 631 | --- Check if the content of a slot is a spawner 632 | -- @param board 633 | -- @param x 634 | -- @param y 635 | -- @return true if there is a spawner in the slot 636 | function M.is_spawner(board, x, y) 637 | assert(board, "You must provide a board") 638 | assert(x and y, "You must provide a position") 639 | if M.on_board(board, x, y) then 640 | return board.slots[x][y] and board.slots[x][y].spawner 641 | end 642 | return false 643 | end 644 | 645 | 646 | --- Get a block on the board 647 | -- @param board 648 | -- @param x 649 | -- @param y 650 | -- @return The block at the given position or nil if out of bounds 651 | function M.get_block(board, x, y) 652 | assert(board, "You must provide a board") 653 | assert(x and y, "You must provide a position") 654 | if M.on_board(board, x, y) then 655 | return board.slots[x][y] 656 | end 657 | return nil 658 | end 659 | 660 | 661 | --- 662 | -- Get all blocks of a specific color 663 | -- @param board 664 | -- @param color Color to search for or nil to get all blocks 665 | -- @return All blocks of the specified color 666 | function M.get_blocks(board, color) 667 | assert(board, "You must provide a board") 668 | local blocks = {} 669 | for block in M.iterate_blocks(board, create_color_filter(color)) do 670 | table.insert(blocks, block) 671 | end 672 | return blocks 673 | end 674 | 675 | -- 676 | -- Remove a single block from the board 677 | -- @param board 678 | -- @param block The block to remove 679 | -- @param no_trigger True if the on_block_removed function 680 | -- should NOT be called (defaults to true) 681 | function M.remove_block(board, block, no_trigger) 682 | assert(board, "You must provide a board") 683 | 684 | if not block then 685 | return 686 | end 687 | 688 | if not board.slots[block.x][block.y] then 689 | -- the block has already been removed 690 | -- this can happen when we remove a match and a line blast or 691 | -- other special block effect takes place at the same time 692 | return 693 | end 694 | 695 | msg.post(block.id, M.REMOVE) 696 | block.removed = true 697 | -- 698 | -- Empty slots are set to nil so we can find them 699 | -- 700 | board.slots[block.x][block.y] = nil 701 | 702 | if not no_trigger then 703 | board.on_block_removed(board, block) 704 | end 705 | end 706 | 707 | 708 | -- 709 | -- Remove a list of blocks from the board 710 | -- @param board 711 | -- @param blocks 712 | function M.remove_blocks(board, blocks) 713 | assert(board, "You must provide a board") 714 | assert(blocks, "You must provide a list of blocks") 715 | for _,block in pairs(blocks) do 716 | M.remove_block(board, block) 717 | end 718 | end 719 | 720 | 721 | -- 722 | -- Change type and color of an existing block 723 | -- Use this when converting blocks into other types due 724 | -- to a match of some kind 725 | -- Will clear list of neighbors 726 | function M.change_block(block, type, color) 727 | assert(block, "You must provide a block") 728 | assert(type or color, "You must provide type and/or color") 729 | block.color = color or block.color 730 | block.type = type or block.type 731 | block.vertical_neighbors = {} 732 | block.horisontal_neighbors = {} 733 | msg.post(block.id, M.CHANGE, { color = block.color, type = block.type, position = go.get_position(block.id) }) 734 | end 735 | 736 | 737 | --- Create a new block on the board. This will call the function that was provided 738 | -- by on_create_block() 739 | -- @param board 740 | -- @param x 741 | -- @param y 742 | -- @param type 743 | -- @param color 744 | -- @return The created block 745 | function M.create_block(board, x, y, type, color) 746 | assert(board, "You must provide a board") 747 | assert(x and y, "You must provide a position") 748 | assert(not board.slots[x][y], "The position is not empty") 749 | 750 | local sx, sy = M.slot_to_screen(board, x, y) 751 | local id, color, type = board.on_create_block(board, vmath.vector3(sx, sy, 0), type, color) 752 | board.slots[x][y] = { id = id, x = x, y = y, color = color, type = type, block = true } 753 | return board.slots[x][y] 754 | end 755 | 756 | 757 | --- Create a new blocker on the board. This will call the function that was provided 758 | -- by on_create_blocker() 759 | -- @param board 760 | -- @param x 761 | -- @param y 762 | -- @param type 763 | -- @return The created blocker 764 | function M.create_blocker(board, x, y, type) 765 | assert(board, "You must provide a board") 766 | assert(x and y, "You must provide a position") 767 | assert(not board.slots[x][y], "The position is not empty") 768 | 769 | local sx, sy = M.slot_to_screen(board, x, y) 770 | local id, type = board.on_create_blocker(board, vmath.vector3(sx, sy, 0), type) 771 | board.slots[x][y] = { id = id, x = x, y = y, type = type, blocker = true } 772 | return board.slots[x][y] 773 | end 774 | 775 | 776 | --- Create a new spawner on the board. This will call the function that was provided 777 | -- by on_create_spawner() 778 | -- @param board 779 | -- @param x 780 | -- @param y 781 | -- @param type 782 | -- @return The created spawner 783 | function M.create_spawner(board, x, y, type) 784 | assert(board, "You must provide a board") 785 | assert(x and y, "You must provide a position") 786 | assert(not board.slots[x][y], "The position is not empty") 787 | 788 | local sx, sy = M.slot_to_screen(board, x, y) 789 | local id, type = board.on_create_spawner(board, vmath.vector3(sx, sy, 0), type) 790 | board.slots[x][y] = { id = id, x = x, y = y, type = type, spawner = true } 791 | return board.slots[x][y] 792 | end 793 | 794 | 795 | -- 796 | -- Find and return any empty slots. 797 | -- 798 | local function find_empty_slots(board) 799 | assert(board, "You must provide a board") 800 | local slots = {} 801 | for x = 0, board.width - 1 do 802 | for y = 0, board.height - 1 do 803 | if not board.slots[x][y] then 804 | -- 805 | -- The slot is nil/empty so we store this position in the 806 | -- list of slots that we will return 807 | -- 808 | table.insert(slots, { x = x, y = y }) 809 | end 810 | end 811 | end 812 | return slots 813 | end 814 | 815 | 816 | 817 | -- 818 | -- Find and return any slots containing spawners 819 | -- 820 | local function find_spawners(board) 821 | assert(board, "You must provide a board") 822 | local spawners = {} 823 | for block in M.iterate_blocks(board, spawner_filter) do 824 | table.insert(spawners, { x = block.x, y = block.y }) 825 | end 826 | return spawners 827 | end 828 | 829 | 830 | 831 | local function trigger_spawners(board, callback) 832 | assert(board, "You must provide a board") 833 | local duration = board.config.collapse_duration 834 | 835 | local triggered = false 836 | for _,spawner in pairs(find_spawners(board)) do 837 | local direction = DIRECTIONS[board.config.collapse_direction] 838 | local x = spawner.x + direction.x 839 | local y = spawner.y + direction.y 840 | 841 | local distance = 0 842 | while M.on_board(board, x, y) and not board.slots[x][y] do 843 | distance = distance + 1 844 | x = x + direction.x 845 | y = y + direction.y 846 | end 847 | 848 | if distance > 0 then 849 | triggered = true 850 | local fromslot_x = spawner.x 851 | local fromslot_y = spawner.y 852 | local from_x, from_y = M.slot_to_screen(board, spawner.x, spawner.y) 853 | local from = vmath.vector3(from_x, from_y, 0) 854 | 855 | for i=1,distance do 856 | local toslot_x = spawner.x + direction.x * i 857 | local toslot_y = spawner.y + direction.y * i 858 | local block = M.create_block(board, toslot_x, toslot_y) 859 | 860 | local to_x, to_y = M.slot_to_screen(board, toslot_x, toslot_y) 861 | local to = vmath.vector3(to_x, to_y, 0) 862 | go.set_position(from, block.id) 863 | go.animate(block.id, "position", go.PLAYBACK_ONCE_FORWARD, to, board.config.collapse_easing, duration) 864 | end 865 | end 866 | end 867 | 868 | if triggered then 869 | delay(duration, callback, triggered) 870 | else 871 | callback(triggered) 872 | end 873 | end 874 | 875 | -- simple block swap without animations or callbacks 876 | local function quick_swap(board, block1, block2) 877 | board.slots[block1.x][block1.y], board.slots[block2.x][block2.y] = board.slots[block2.x][block2.y], board.slots[block1.x][block1.y] 878 | block1.x, block2.x = block2.x, block1.x 879 | block1.y, block2.y = block2.y, block1.y 880 | end 881 | 882 | -- swap two blocks amd check if there is a match 883 | local function swap_and_check_match(board, block1, block2) 884 | if not block1 or not block2 then return false end 885 | if block1.blocker or block2.blocker then return false end 886 | if block1.spawner or block2.spawner then return false end 887 | quick_swap(board, block1, block2) 888 | local hn1 = horisontal_neighbors(board, block1.x, block1.y) 889 | local vn1 = vertical_neighbors(board, block1.x, block1.y) 890 | local hn2 = horisontal_neighbors(board, block2.x, block2.y) 891 | local vn2 = vertical_neighbors(board, block2.x, block2.y) 892 | quick_swap(board, block1, block2) 893 | return #hn1 >= 2 or #hn2 >= 2 or #vn1 >= 2 or #vn2 >= 2 894 | end 895 | 896 | -- check if the board has any possible switches which will 897 | -- result in a match of three or more blocks in a row 898 | -- this does not take into account special blocks 899 | local function has_possible_switches(board) 900 | assert(board, "You must provide a board") 901 | for x = 1, board.width - 2 do 902 | for y = 1, board.height - 2 do 903 | local slot = board.slots[x][y] 904 | local up = board.slots[x][y + 1] 905 | local down = board.slots[x][y - 1] 906 | local left = board.slots[x - 1][y] 907 | local right = board.slots[x + 1][y] 908 | 909 | if swap_and_check_match(board, slot, up) then return true end 910 | if swap_and_check_match(board, slot, down) then return true end 911 | if swap_and_check_match(board, slot, left) then return true end 912 | if swap_and_check_match(board, slot, right) then return true end 913 | end 914 | end 915 | return false 916 | end 917 | 918 | 919 | --- Stabilize the board 920 | -- This will find and remove matching blocks and spawn new blocks in their 921 | -- place. This process will be repeated until the board is filled and no 922 | -- more matches exists. The board is at that point considered stable 923 | -- @param board The board to stabilize 924 | -- @param callback Optional callback to invoke when board is stable 925 | function M.stabilize(board, callback) 926 | assert(board, "You must provide a board") 927 | local fn = function() 928 | while true do 929 | async(function(done) 930 | find_matching_neighbors(board) 931 | remove_matching_neighbors(board, done) 932 | end) 933 | local did_collapse = async(function(done) collapse(board, done) end) 934 | local did_spawn = async(function(done) trigger_spawners(board, done) end) 935 | local did_slide = async(function(done) slide(board, done) end) 936 | 937 | if not did_spawn and not did_collapse and not did_slide then 938 | board.on_stabilized(board) 939 | if not has_possible_switches(board) then 940 | board.on_no_possible_switches(board) 941 | end 942 | if callback then callback() end 943 | break 944 | end 945 | end 946 | end 947 | 948 | if not coroutine.running() then 949 | utils.corun(fn) 950 | else 951 | fn() 952 | end 953 | end 954 | 955 | 956 | function M.shuffle(board, callback) 957 | assert(board, "You must provide a board") 958 | assert(coroutine.running()) 959 | 960 | -- get list of all blocks that can be shuffled 961 | local blocks = {} 962 | for x = 0, board.width - 1 do 963 | for y = 0, board.height - 1 do 964 | local slot = board.slots[x][y] 965 | if slot and not slot.blocker and not slot.spawner then 966 | blocks[#blocks + 1] = slot 967 | end 968 | end 969 | end 970 | -- make sure there is an even number of blovks 971 | if #blocks % 2 == 1 then 972 | blocks[#blocks] = nil 973 | end 974 | -- shuffle blocks 975 | for i = #blocks, 2, -1 do 976 | local j = math.random(i) 977 | blocks[i], blocks[j] = blocks[j], blocks[i] 978 | end 979 | -- swap places on blocks 980 | while #blocks > 0 do 981 | local block1 = table.remove(blocks) 982 | local block2 = table.remove(blocks) 983 | async(function(done) swap(board, block1, block2, done) end) 984 | end 985 | M.stabilize(board, callback) 986 | end 987 | 988 | --- Check if a position is on the board or not 989 | -- @param board 990 | -- @param x 991 | -- @param y 992 | -- @return true of the position is on the board 993 | function M.on_board(board, x, y) 994 | return x >= 0 and x < board.width and y >= 0 and y < board.height 995 | end 996 | 997 | --- Handle user input 998 | -- @param board The board to apply the input on 999 | -- @param action The user action table (must be a pressed or released action) 1000 | -- @return true if the action was handled 1001 | function M.on_input(board, action) 1002 | assert(board, "You must provide a board") 1003 | assert(action.pressed or action.released, "You must provide either a pressed or released action") 1004 | local x, y = M.screen_to_slot(board, action.x, action.y) 1005 | local block = M.get_block(board, x, y) 1006 | if block and (block.blocker or block.spawner) then 1007 | return 1008 | end 1009 | 1010 | if action.pressed then 1011 | if not board.mark_1 then 1012 | board.mark_1 = block 1013 | else 1014 | board.mark_2 = block 1015 | end 1016 | return block ~= nil 1017 | else 1018 | 1019 | if board.mark_1 and board.mark_1 == board.mark_2 then 1020 | -- second click, released on the first block again -> deselect it 1021 | msg.post(board.mark_1.id, M.RESET) 1022 | board.mark_1 = nil 1023 | board.mark_2 = nil 1024 | return true 1025 | 1026 | elseif board.mark_1 and board.mark_1 == block then 1027 | -- first click, released on the first block -> select it 1028 | msg.post(board.mark_1.id, M.SELECT) 1029 | return true 1030 | 1031 | elseif board.mark_2 and board.mark_2 == block then 1032 | -- second click, released on the second block -> swap them 1033 | utils.corun(function() 1034 | msg.post(".", "release_input_focus") 1035 | local dx = math.abs(board.mark_1.x - board.mark_2.x) 1036 | local dy = math.abs(board.mark_1.y - board.mark_2.y) 1037 | if (dx == 1 and dy == 0) or (dy == 1 and dx == 0) then 1038 | swap_slots(board, board.mark_1, board.mark_2) 1039 | end 1040 | if not board.mark_1.removed then msg.post(board.mark_1.id, M.RESET) end 1041 | board.mark_1 = nil 1042 | board.mark_2 = nil 1043 | msg.post(".", "acquire_input_focus") 1044 | end) 1045 | return true 1046 | 1047 | elseif board.mark_1 and not board.mark_2 then 1048 | -- one block selected, released on some other block -> swipe and swap 1049 | utils.corun(function() 1050 | msg.post(".", "release_input_focus") 1051 | local dx = utils.clamp(board.mark_1.x - x, -1, 1) 1052 | local dy = utils.clamp(board.mark_1.y - y, -1, 1) 1053 | block = M.get_block(board, board.mark_1.x - dx, board.mark_1.y - dy) 1054 | if (dx == 0 or dy == 0) and block and not block.blocker then 1055 | swap_slots(board, board.mark_1, block) 1056 | end 1057 | if not board.mark_1.removed then msg.post(board.mark_1.id, M.RESET) end 1058 | board.mark_1 = nil 1059 | msg.post(".", "acquire_input_focus") 1060 | end) 1061 | return true 1062 | 1063 | else 1064 | if board.mark_1 then 1065 | msg.post(board.mark_1.id, M.RESET) 1066 | board.mark_1 = nil 1067 | end 1068 | if board.mark_2 then 1069 | msg.post(board.mark_2.id, M.RESET) 1070 | board.mark_2 = nil 1071 | end 1072 | end 1073 | end 1074 | end 1075 | 1076 | -- Convert a screen position to slot on the board 1077 | -- @param board 1078 | -- @param x 1079 | -- @param y 1080 | -- @return slot_x 1081 | -- @return slot_y 1082 | function M.screen_to_slot(board, x, y) 1083 | assert(board, "You must provide a board") 1084 | assert(x and y, "You must provide a position") 1085 | local pos = go.get_world_position() 1086 | local scale = go.get_world_scale() 1087 | local x = math.floor((x - pos.x) / (board.block_size * scale.x)) 1088 | local y = math.floor((y - pos.y) / (board.block_size * scale.y)) 1089 | return x, y 1090 | end 1091 | 1092 | 1093 | -- Convert a board position to a screen position 1094 | -- @param board 1095 | -- @param x 1096 | -- @param y 1097 | -- @return screen_x 1098 | -- @return screen_y 1099 | function M.slot_to_screen(board, x, y) 1100 | assert(board, "You must provide a board") 1101 | assert(x and y, "You must provide a position") 1102 | local x = (board.block_size / 2) + (board.block_size * x) 1103 | local y = (board.block_size / 2) + (board.block_size * y) 1104 | return x, y 1105 | end 1106 | 1107 | 1108 | function M.color_frequency(board) 1109 | local f = {} 1110 | for block in M.iterate_blocks(board) do 1111 | if block.color then 1112 | f[block.color] = f[block.color] or 0 1113 | f[block.color] = f[block.color] + 1 1114 | end 1115 | end 1116 | local l = {} 1117 | for color,count in pairs(f) do 1118 | table.insert(l, { color = color, count = count }) 1119 | end 1120 | table.sort(l, function(a, b) 1121 | return a.count > b.count 1122 | end) 1123 | return l 1124 | end 1125 | 1126 | 1127 | function M.dump(board) 1128 | local s = "\n" 1129 | for y = board.height - 1,0,-1 do 1130 | s = s .. y .. ":" 1131 | for x = 0, board.width - 1 do 1132 | local block = board.slots[x][y] 1133 | if block then 1134 | if block.spawner then 1135 | s = s .. "U" 1136 | elseif block.blocker then 1137 | s = s .. "X" 1138 | else 1139 | s = s .. hash_to_hex(block.color):sub(1,1) 1140 | end 1141 | else 1142 | s = s .. " " 1143 | end 1144 | end 1145 | s = s .. "\n" 1146 | end 1147 | return s 1148 | end 1149 | 1150 | 1151 | --- Set a function to be called when a match is detected on the board 1152 | function M.on_match(board, fn) 1153 | assert(board, "You must provide a board") 1154 | assert(fn, "You must provide a function") 1155 | board.on_match = fn 1156 | end 1157 | 1158 | 1159 | --- Set a function to be called when a block is removed 1160 | function M.on_block_removed(board, fn) 1161 | assert(board, "You must provide a board") 1162 | assert(fn, "You must provide a function") 1163 | board.on_block_removed = fn 1164 | end 1165 | 1166 | 1167 | --- Set a function to be called when a board is stabilized 1168 | function M.on_stabilized(board, fn) 1169 | assert(board, "You must provide a board") 1170 | assert(fn, "You must provide a function") 1171 | board.on_stabilized = fn 1172 | end 1173 | 1174 | 1175 | --- Set a function to be called when two blocks have been swapped by the user 1176 | function M.on_swap(board, fn) 1177 | assert(board, "You must provide a board") 1178 | assert(fn, "You must provide a function") 1179 | board.on_swap = fn 1180 | end 1181 | 1182 | 1183 | -- The function to call when a block should be created 1184 | function M.on_create_block(board, fn) 1185 | assert(board, "You must provide a board") 1186 | assert(fn, "You must provide a function") 1187 | board.on_create_block = fn 1188 | end 1189 | 1190 | 1191 | -- The function to call when a blocker should be created 1192 | function M.on_create_blocker(board, fn) 1193 | assert(board, "You must provide a board") 1194 | assert(fn, "You must provide a function") 1195 | board.on_create_blocker = fn 1196 | end 1197 | 1198 | 1199 | -- The function to call when a spawner should be created 1200 | function M.on_create_spawner(board, fn) 1201 | assert(board, "You must provide a board") 1202 | assert(fn, "You must provide a function") 1203 | board.on_create_spawner = fn 1204 | end 1205 | 1206 | 1207 | function M.on_no_possible_switches(board, fn) 1208 | assert(board, "You must provide a board") 1209 | assert(fn, "You must provide a function") 1210 | board.on_no_possible_switches = fn 1211 | end 1212 | 1213 | return M 1214 | -------------------------------------------------------------------------------- /emthree/internal/async.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local unpack = _G.unpack or table.unpack 4 | 5 | 6 | function M.async(fn, ...) 7 | assert(fn) 8 | local co = coroutine.running() 9 | assert(co) 10 | local results = nil 11 | local state = "RUNNING" 12 | fn(function(...) 13 | results = { ... } 14 | if state == "YIELDED" then 15 | local ok, err = coroutine.resume(co) 16 | if not ok then print(err) end 17 | else 18 | state = "DONE" 19 | end 20 | end, ...) 21 | if state == "RUNNING" then 22 | state = "YIELDED" 23 | coroutine.yield() 24 | state = "DONE" -- not really needed 25 | end 26 | return unpack(results) 27 | end 28 | 29 | 30 | function M.http_request(url, method, headers, post_data, options) 31 | return M.async(function(done) 32 | http.request(url, method, function(self, id, response) 33 | done(response) 34 | end, headers, post_data, options) 35 | end) 36 | end 37 | 38 | 39 | setmetatable(M, { 40 | __call = function(t, ...) 41 | return M.async(...) 42 | end 43 | }) 44 | 45 | return M 46 | -------------------------------------------------------------------------------- /emthree/internal/utils.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | function M.clamp(value, min, max) 4 | if value > max then return max end 5 | if value < min then return min end 6 | return value 7 | end 8 | 9 | 10 | function M.corun(fn, ...) 11 | local co = coroutine.create(fn) 12 | local ok, err = coroutine.resume(co, ...) 13 | if not ok then 14 | print(err) 15 | end 16 | end 17 | 18 | 19 | return M -------------------------------------------------------------------------------- /examples/basic/assets/brickBlue06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/basic/assets/brickBlue06.png -------------------------------------------------------------------------------- /examples/basic/assets/brickGreen05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/basic/assets/brickGreen05.png -------------------------------------------------------------------------------- /examples/basic/assets/brickGrey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/basic/assets/brickGrey.png -------------------------------------------------------------------------------- /examples/basic/assets/brickRed04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/basic/assets/brickRed04.png -------------------------------------------------------------------------------- /examples/basic/assets/brickSpecial01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/basic/assets/brickSpecial01.png -------------------------------------------------------------------------------- /examples/basic/assets/brickSpecial02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/basic/assets/brickSpecial02.png -------------------------------------------------------------------------------- /examples/basic/assets/brickSpecial03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/basic/assets/brickSpecial03.png -------------------------------------------------------------------------------- /examples/basic/assets/brickSpecial06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/basic/assets/brickSpecial06.png -------------------------------------------------------------------------------- /examples/basic/assets/brickSpecial07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/basic/assets/brickSpecial07.png -------------------------------------------------------------------------------- /examples/basic/assets/brickSpecial08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/basic/assets/brickSpecial08.png -------------------------------------------------------------------------------- /examples/basic/assets/brickSpecial09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/basic/assets/brickSpecial09.png -------------------------------------------------------------------------------- /examples/basic/assets/brickSpecial10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/basic/assets/brickSpecial10.png -------------------------------------------------------------------------------- /examples/basic/assets/brickWhite05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/basic/assets/brickWhite05.png -------------------------------------------------------------------------------- /examples/basic/assets/brickYellow03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/basic/assets/brickYellow03.png -------------------------------------------------------------------------------- /examples/basic/basic.atlas: -------------------------------------------------------------------------------- 1 | images { 2 | image: "/examples/basic/assets/brickBlue06.png" 3 | } 4 | images { 5 | image: "/examples/basic/assets/brickGreen05.png" 6 | } 7 | images { 8 | image: "/examples/basic/assets/brickRed04.png" 9 | } 10 | images { 11 | image: "/examples/basic/assets/brickSpecial01.png" 12 | } 13 | images { 14 | image: "/examples/basic/assets/brickSpecial02.png" 15 | } 16 | images { 17 | image: "/examples/basic/assets/brickSpecial03.png" 18 | } 19 | images { 20 | image: "/examples/basic/assets/brickSpecial06.png" 21 | } 22 | images { 23 | image: "/examples/basic/assets/brickSpecial07.png" 24 | } 25 | images { 26 | image: "/examples/basic/assets/brickSpecial08.png" 27 | } 28 | images { 29 | image: "/examples/basic/assets/brickSpecial09.png" 30 | } 31 | images { 32 | image: "/examples/basic/assets/brickSpecial10.png" 33 | } 34 | images { 35 | image: "/examples/basic/assets/brickWhite05.png" 36 | } 37 | images { 38 | image: "/examples/basic/assets/brickYellow03.png" 39 | } 40 | images { 41 | image: "/examples/basic/assets/brickGrey.png" 42 | } 43 | margin: 0 44 | extrude_borders: 2 45 | inner_padding: 0 46 | -------------------------------------------------------------------------------- /examples/basic/basic.collection: -------------------------------------------------------------------------------- 1 | name: "basic" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "go" 5 | data: "components {\n" 6 | " id: \"basic\"\n" 7 | " component: \"/examples/basic/basic.script\"\n" 8 | " position {\n" 9 | " x: 0.0\n" 10 | " y: 0.0\n" 11 | " z: 0.0\n" 12 | " }\n" 13 | " rotation {\n" 14 | " x: 0.0\n" 15 | " y: 0.0\n" 16 | " z: 0.0\n" 17 | " w: 1.0\n" 18 | " }\n" 19 | "}\n" 20 | "embedded_components {\n" 21 | " id: \"factory\"\n" 22 | " type: \"factory\"\n" 23 | " data: \"prototype: \\\"/examples/basic/brick.go\\\"\\n" 24 | "load_dynamically: false\\n" 25 | "\"\n" 26 | " position {\n" 27 | " x: 0.0\n" 28 | " y: 0.0\n" 29 | " z: 0.0\n" 30 | " }\n" 31 | " rotation {\n" 32 | " x: 0.0\n" 33 | " y: 0.0\n" 34 | " z: 0.0\n" 35 | " w: 1.0\n" 36 | " }\n" 37 | "}\n" 38 | "embedded_components {\n" 39 | " id: \"debugfactory\"\n" 40 | " type: \"factory\"\n" 41 | " data: \"prototype: \\\"/examples/debug/debug.go\\\"\\n" 42 | "load_dynamically: false\\n" 43 | "\"\n" 44 | " position {\n" 45 | " x: 0.0\n" 46 | " y: 0.0\n" 47 | " z: 0.0\n" 48 | " }\n" 49 | " rotation {\n" 50 | " x: 0.0\n" 51 | " y: 0.0\n" 52 | " z: 0.0\n" 53 | " w: 1.0\n" 54 | " }\n" 55 | "}\n" 56 | "" 57 | position { 58 | x: 80.0 59 | y: 205.0 60 | z: 0.0 61 | } 62 | rotation { 63 | x: 0.0 64 | y: 0.0 65 | z: 0.0 66 | w: 1.0 67 | } 68 | scale3 { 69 | x: 1.0 70 | y: 1.0 71 | z: 1.0 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /examples/basic/basic.script: -------------------------------------------------------------------------------- 1 | local emthree = require "emthree.emthree" 2 | local effects = require "emthree.effects" 3 | 4 | local blocksize = 80 -- Distance between block centers 5 | local boardwidth = 6 -- Number of columns 6 | local boardheight = 10 -- Number of rows 7 | 8 | local colors = { 9 | hash("brickBlue06"), 10 | hash("brickGreen05"), 11 | hash("brickRed04"), 12 | hash("brickWhite05"), 13 | hash("brickYellow03"), 14 | } 15 | 16 | --[[local color_lookup = { 17 | [hash("brickBlue06")] = "blue", 18 | [hash("brickGreen05")] = "green", 19 | [hash("brickRed04")] = "red", 20 | [hash("brickWhite05")] = "white", 21 | [hash("brickYellow03")] = "yellow", 22 | }--]] 23 | 24 | local function create_block(board, position, type, color) 25 | if not type and not color then 26 | color = color or colors[math.random(#colors)] 27 | end 28 | local id = factory.create("#factory", position, null, { color = color, type = type }) 29 | msg.post(id, "set_parent", { parent_id = go.get_id(), keep_world_transform = 0 }) 30 | return id, color, type 31 | end 32 | 33 | local function create_spawner(board, position, type) 34 | position.z = 1 35 | local id = factory.create("#factory", position, null, { type = type, color = hash("brickGrey") }) 36 | msg.post(id, "set_parent", { parent_id = go.get_id(), keep_world_transform = 0 }) 37 | return id, type 38 | end 39 | 40 | local function create_blocker(board, position, type) 41 | position.z = 1 42 | local id = factory.create("#factory", position, null, { type = type, color = hash("brickSpecial06") }) 43 | msg.post(id, "set_parent", { parent_id = go.get_id(), keep_world_transform = 0 }) 44 | return id, type 45 | end 46 | 47 | function init(self) 48 | self.board = emthree.create_board(boardwidth, boardheight, blocksize, { direction = emthree.COLLAPSE_DOWN }) 49 | emthree.on_create_block(self.board, create_block) 50 | emthree.on_create_spawner(self.board, create_spawner) 51 | emthree.on_create_blocker(self.board, create_blocker) 52 | emthree.on_no_possible_switches(self.board, function() emthree.shuffle(self.board) end) 53 | 54 | for x=0,boardwidth-1 do 55 | emthree.create_spawner(self.board, x, boardheight - 1, hash("SPAWNER")) 56 | end 57 | emthree.fill_board(self.board) 58 | 59 | --[[self.debug = {} 60 | for x=0,boardwidth-1 do 61 | self.debug[x] = {} 62 | for y=0,boardheight-1 do 63 | local pos = go.get_position() + vmath.vector3((blocksize / 2) + (blocksize * x), (blocksize / 2) + (blocksize * y), 1) 64 | self.debug[x][y] = msg.url(nil, factory.create("#debugfactory", pos), "label") 65 | end 66 | end--]] 67 | 68 | emthree.stabilize(self.board, function() 69 | msg.post(".", "acquire_input_focus") 70 | end) 71 | end 72 | 73 | 74 | --[[function update(self, dt) 75 | for x=0,boardwidth-1 do 76 | for y=0,boardheight-1 do 77 | local block = self.board.slots[x][y] 78 | if not block then 79 | label.set_text(self.debug[x][y], "EMPTY") 80 | elseif block.blocker then 81 | label.set_text(self.debug[x][y], "BLOCKER") 82 | elseif block.spawner then 83 | label.set_text(self.debug[x][y], "SPAWNER") 84 | else 85 | label.set_text(self.debug[x][y], color_lookup[block.color] or "?") 86 | end 87 | end 88 | end 89 | end--]] 90 | 91 | function on_input(self, action_id, action) 92 | if action_id == hash("touch") and (action.pressed or action.released) then 93 | return emthree.on_input(self.board, action) 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /examples/basic/brick.go: -------------------------------------------------------------------------------- 1 | components { 2 | id: "brick" 3 | component: "/examples/basic/brick.script" 4 | position { 5 | x: 0.0 6 | y: 0.0 7 | z: 0.0 8 | } 9 | rotation { 10 | x: 0.0 11 | y: 0.0 12 | z: 0.0 13 | w: 1.0 14 | } 15 | } 16 | embedded_components { 17 | id: "sprite" 18 | type: "sprite" 19 | data: "tile_set: \"/examples/basic/basic.atlas\"\n" 20 | "default_animation: \"brickBlue06\"\n" 21 | "material: \"/builtins/materials/sprite.material\"\n" 22 | "blend_mode: BLEND_MODE_ALPHA\n" 23 | "" 24 | position { 25 | x: 0.0 26 | y: 0.0 27 | z: 0.0 28 | } 29 | rotation { 30 | x: 0.0 31 | y: 0.0 32 | z: 0.0 33 | w: 1.0 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/basic/brick.script: -------------------------------------------------------------------------------- 1 | go.property("color", hash("yellow")) 2 | go.property("type", hash("plain")) 3 | 4 | local emthree = require "emthree.emthree" 5 | 6 | function init(self) 7 | msg.post("#sprite", "play_animation", { id = self.color }) 8 | end 9 | 10 | function on_message(self, message_id, message, sender) 11 | if message_id == emthree.REMOVE then 12 | go.delete() 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /examples/debug/debug.font: -------------------------------------------------------------------------------- 1 | font: "/builtins/fonts/vera_mo_bd.ttf" 2 | material: "/builtins/fonts/font.material" 3 | size: 15 4 | antialias: 1 5 | alpha: 1.0 6 | outline_alpha: 1.0 7 | outline_width: 1.0 8 | shadow_alpha: 0.0 9 | shadow_blur: 0 10 | shadow_x: 0.0 11 | shadow_y: 0.0 12 | extra_characters: "" 13 | output_format: TYPE_BITMAP 14 | all_chars: false 15 | cache_width: 0 16 | cache_height: 0 17 | render_mode: MODE_MULTI_LAYER 18 | -------------------------------------------------------------------------------- /examples/debug/debug.go: -------------------------------------------------------------------------------- 1 | embedded_components { 2 | id: "label" 3 | type: "label" 4 | data: "size {\n" 5 | " x: 128.0\n" 6 | " y: 32.0\n" 7 | " z: 0.0\n" 8 | " w: 0.0\n" 9 | "}\n" 10 | "scale {\n" 11 | " x: 1.0\n" 12 | " y: 1.0\n" 13 | " z: 1.0\n" 14 | " w: 0.0\n" 15 | "}\n" 16 | "color {\n" 17 | " x: 1.0\n" 18 | " y: 1.0\n" 19 | " z: 1.0\n" 20 | " w: 1.0\n" 21 | "}\n" 22 | "outline {\n" 23 | " x: 0.0\n" 24 | " y: 0.0\n" 25 | " z: 0.0\n" 26 | " w: 1.0\n" 27 | "}\n" 28 | "shadow {\n" 29 | " x: 0.0\n" 30 | " y: 0.0\n" 31 | " z: 0.0\n" 32 | " w: 1.0\n" 33 | "}\n" 34 | "leading: 1.0\n" 35 | "tracking: 0.0\n" 36 | "pivot: PIVOT_CENTER\n" 37 | "blend_mode: BLEND_MODE_ALPHA\n" 38 | "line_break: false\n" 39 | "text: \"Label\"\n" 40 | "font: \"/examples/debug/debug.font\"\n" 41 | "material: \"/builtins/fonts/label.material\"\n" 42 | "" 43 | position { 44 | x: 0.0 45 | y: 0.0 46 | z: 0.0 47 | } 48 | rotation { 49 | x: 0.0 50 | y: 0.0 51 | z: 0.0 52 | w: 1.0 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /examples/examples.collection: -------------------------------------------------------------------------------- 1 | name: "examples" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "go" 5 | data: "components {\n" 6 | " id: \"examples\"\n" 7 | " component: \"/examples/examples.gui\"\n" 8 | " position {\n" 9 | " x: 0.0\n" 10 | " y: 0.0\n" 11 | " z: 0.0\n" 12 | " }\n" 13 | " rotation {\n" 14 | " x: 0.0\n" 15 | " y: 0.0\n" 16 | " z: 0.0\n" 17 | " w: 1.0\n" 18 | " }\n" 19 | "}\n" 20 | "embedded_components {\n" 21 | " id: \"basicproxy\"\n" 22 | " type: \"collectionproxy\"\n" 23 | " data: \"collection: \\\"/examples/basic/basic.collection\\\"\\n" 24 | "exclude: false\\n" 25 | "\"\n" 26 | " position {\n" 27 | " x: 0.0\n" 28 | " y: 0.0\n" 29 | " z: 0.0\n" 30 | " }\n" 31 | " rotation {\n" 32 | " x: 0.0\n" 33 | " y: 0.0\n" 34 | " z: 0.0\n" 35 | " w: 1.0\n" 36 | " }\n" 37 | "}\n" 38 | "embedded_components {\n" 39 | " id: \"oceanproxy\"\n" 40 | " type: \"collectionproxy\"\n" 41 | " data: \"collection: \\\"/examples/ocean-commotion/ocean-commotion.collection\\\"\\n" 42 | "exclude: false\\n" 43 | "\"\n" 44 | " position {\n" 45 | " x: 0.0\n" 46 | " y: 0.0\n" 47 | " z: 0.0\n" 48 | " }\n" 49 | " rotation {\n" 50 | " x: 0.0\n" 51 | " y: 0.0\n" 52 | " z: 0.0\n" 53 | " w: 1.0\n" 54 | " }\n" 55 | "}\n" 56 | "" 57 | position { 58 | x: 0.0 59 | y: 0.0 60 | z: 0.0 61 | } 62 | rotation { 63 | x: 0.0 64 | y: 0.0 65 | z: 0.0 66 | w: 1.0 67 | } 68 | scale3 { 69 | x: 1.0 70 | y: 1.0 71 | z: 1.0 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /examples/examples.gui: -------------------------------------------------------------------------------- 1 | script: "/examples/examples.gui_script" 2 | fonts { 3 | name: "system_font" 4 | font: "/builtins/fonts/debug/always_on_top.font" 5 | } 6 | background_color { 7 | x: 0.0 8 | y: 0.0 9 | z: 0.0 10 | w: 0.0 11 | } 12 | nodes { 13 | position { 14 | x: 320.0 15 | y: 600.0 16 | z: 0.0 17 | w: 1.0 18 | } 19 | rotation { 20 | x: 0.0 21 | y: 0.0 22 | z: 0.0 23 | w: 1.0 24 | } 25 | scale { 26 | x: 1.0 27 | y: 1.0 28 | z: 1.0 29 | w: 1.0 30 | } 31 | size { 32 | x: 200.0 33 | y: 50.0 34 | z: 0.0 35 | w: 1.0 36 | } 37 | color { 38 | x: 0.2 39 | y: 0.2 40 | z: 0.2 41 | w: 1.0 42 | } 43 | type: TYPE_BOX 44 | blend_mode: BLEND_MODE_ALPHA 45 | texture: "" 46 | id: "basic_button" 47 | xanchor: XANCHOR_NONE 48 | yanchor: YANCHOR_NONE 49 | pivot: PIVOT_CENTER 50 | adjust_mode: ADJUST_MODE_FIT 51 | layer: "" 52 | inherit_alpha: true 53 | slice9 { 54 | x: 0.0 55 | y: 0.0 56 | z: 0.0 57 | w: 0.0 58 | } 59 | clipping_mode: CLIPPING_MODE_NONE 60 | clipping_visible: true 61 | clipping_inverted: false 62 | alpha: 1.0 63 | template_node_child: false 64 | size_mode: SIZE_MODE_MANUAL 65 | } 66 | nodes { 67 | position { 68 | x: 0.0 69 | y: 0.0 70 | z: 0.0 71 | w: 1.0 72 | } 73 | rotation { 74 | x: 0.0 75 | y: 0.0 76 | z: 0.0 77 | w: 1.0 78 | } 79 | scale { 80 | x: 1.0 81 | y: 1.0 82 | z: 1.0 83 | w: 1.0 84 | } 85 | size { 86 | x: 200.0 87 | y: 100.0 88 | z: 0.0 89 | w: 1.0 90 | } 91 | color { 92 | x: 1.0 93 | y: 1.0 94 | z: 1.0 95 | w: 1.0 96 | } 97 | type: TYPE_TEXT 98 | blend_mode: BLEND_MODE_ALPHA 99 | text: "BASIC" 100 | font: "system_font" 101 | id: "basic_text" 102 | xanchor: XANCHOR_NONE 103 | yanchor: YANCHOR_NONE 104 | pivot: PIVOT_CENTER 105 | outline { 106 | x: 1.0 107 | y: 1.0 108 | z: 1.0 109 | w: 1.0 110 | } 111 | shadow { 112 | x: 1.0 113 | y: 1.0 114 | z: 1.0 115 | w: 1.0 116 | } 117 | adjust_mode: ADJUST_MODE_FIT 118 | line_break: false 119 | parent: "basic_button" 120 | layer: "" 121 | inherit_alpha: true 122 | alpha: 1.0 123 | outline_alpha: 1.0 124 | shadow_alpha: 1.0 125 | template_node_child: false 126 | text_leading: 1.0 127 | text_tracking: 0.0 128 | } 129 | nodes { 130 | position { 131 | x: 320.0 132 | y: 524.0 133 | z: 0.0 134 | w: 1.0 135 | } 136 | rotation { 137 | x: 0.0 138 | y: 0.0 139 | z: 0.0 140 | w: 1.0 141 | } 142 | scale { 143 | x: 1.0 144 | y: 1.0 145 | z: 1.0 146 | w: 1.0 147 | } 148 | size { 149 | x: 200.0 150 | y: 50.0 151 | z: 0.0 152 | w: 1.0 153 | } 154 | color { 155 | x: 0.2 156 | y: 0.2 157 | z: 0.2 158 | w: 1.0 159 | } 160 | type: TYPE_BOX 161 | blend_mode: BLEND_MODE_ALPHA 162 | texture: "" 163 | id: "ocean_button" 164 | xanchor: XANCHOR_NONE 165 | yanchor: YANCHOR_NONE 166 | pivot: PIVOT_CENTER 167 | adjust_mode: ADJUST_MODE_FIT 168 | layer: "" 169 | inherit_alpha: true 170 | slice9 { 171 | x: 0.0 172 | y: 0.0 173 | z: 0.0 174 | w: 0.0 175 | } 176 | clipping_mode: CLIPPING_MODE_NONE 177 | clipping_visible: true 178 | clipping_inverted: false 179 | alpha: 1.0 180 | template_node_child: false 181 | size_mode: SIZE_MODE_MANUAL 182 | } 183 | nodes { 184 | position { 185 | x: 0.0 186 | y: 0.0 187 | z: 0.0 188 | w: 1.0 189 | } 190 | rotation { 191 | x: 0.0 192 | y: 0.0 193 | z: 0.0 194 | w: 1.0 195 | } 196 | scale { 197 | x: 1.0 198 | y: 1.0 199 | z: 1.0 200 | w: 1.0 201 | } 202 | size { 203 | x: 200.0 204 | y: 100.0 205 | z: 0.0 206 | w: 1.0 207 | } 208 | color { 209 | x: 1.0 210 | y: 1.0 211 | z: 1.0 212 | w: 1.0 213 | } 214 | type: TYPE_TEXT 215 | blend_mode: BLEND_MODE_ALPHA 216 | text: "OCEAN COMMOTION" 217 | font: "system_font" 218 | id: "ocean_text" 219 | xanchor: XANCHOR_NONE 220 | yanchor: YANCHOR_NONE 221 | pivot: PIVOT_CENTER 222 | outline { 223 | x: 1.0 224 | y: 1.0 225 | z: 1.0 226 | w: 1.0 227 | } 228 | shadow { 229 | x: 1.0 230 | y: 1.0 231 | z: 1.0 232 | w: 1.0 233 | } 234 | adjust_mode: ADJUST_MODE_FIT 235 | line_break: false 236 | parent: "ocean_button" 237 | layer: "" 238 | inherit_alpha: true 239 | alpha: 1.0 240 | outline_alpha: 1.0 241 | shadow_alpha: 1.0 242 | template_node_child: false 243 | text_leading: 1.0 244 | text_tracking: 0.0 245 | } 246 | material: "/builtins/materials/gui.material" 247 | adjust_reference: ADJUST_REFERENCE_PARENT 248 | max_nodes: 512 249 | -------------------------------------------------------------------------------- /examples/examples.gui_script: -------------------------------------------------------------------------------- 1 | function init(self) 2 | msg.post(".", "acquire_input_focus") 3 | end 4 | 5 | function on_message(self, message_id, message, sender) 6 | if message_id == hash("proxy_loaded") then 7 | msg.post(sender, "enable") 8 | msg.post("#examples", "disable") 9 | end 10 | end 11 | 12 | function on_input(self, action_id, action) 13 | if action_id == hash("touch") and action.released then 14 | if gui.pick_node(gui.get_node("basic_button"), action.x, action.y) then 15 | msg.post("#basicproxy", "async_load") 16 | elseif gui.pick_node(gui.get_node("ocean_button"), action.x, action.y) then 17 | msg.post("#oceanproxy", "async_load") 18 | end 19 | end 20 | end 21 | 22 | function on_reload(self) 23 | -- Add input-handling code here 24 | -- Remove this function if not needed 25 | end 26 | -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/effects/bubble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/effects/bubble.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/effects/white_dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/effects/white_dot.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/effects/white_dot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/effects/white_dot2.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/explode.particlefx: -------------------------------------------------------------------------------- 1 | emitters { 2 | id: "emitter" 3 | mode: PLAY_MODE_ONCE 4 | duration: 1.5 5 | space: EMISSION_SPACE_EMITTER 6 | position { 7 | x: 0.0 8 | y: 0.0 9 | z: 0.0 10 | } 11 | rotation { 12 | x: 0.0 13 | y: 0.0 14 | z: 0.0 15 | w: 1.0 16 | } 17 | tile_source: "/examples/ocean-commotion/assets/sprites.atlas" 18 | animation: "white_dot" 19 | material: "/builtins/materials/particlefx.material" 20 | blend_mode: BLEND_MODE_ADD 21 | particle_orientation: PARTICLE_ORIENTATION_DEFAULT 22 | inherit_velocity: 0.0 23 | max_particle_count: 12 24 | type: EMITTER_TYPE_BOX 25 | start_delay: 0.0 26 | properties { 27 | key: EMITTER_KEY_SPAWN_RATE 28 | points { 29 | x: 0.0 30 | y: 30.0 31 | t_x: 1.0 32 | t_y: 0.0 33 | } 34 | spread: 20.0 35 | } 36 | properties { 37 | key: EMITTER_KEY_SIZE_X 38 | points { 39 | x: 0.0 40 | y: 180.0 41 | t_x: 1.0 42 | t_y: 0.0 43 | } 44 | spread: 80.0 45 | } 46 | properties { 47 | key: EMITTER_KEY_SIZE_Y 48 | points { 49 | x: 0.0 50 | y: 200.0 51 | t_x: 1.0 52 | t_y: 0.0 53 | } 54 | spread: 50.0 55 | } 56 | properties { 57 | key: EMITTER_KEY_SIZE_Z 58 | points { 59 | x: 0.0 60 | y: 0.0 61 | t_x: 1.0 62 | t_y: 0.0 63 | } 64 | spread: 0.0 65 | } 66 | properties { 67 | key: EMITTER_KEY_PARTICLE_LIFE_TIME 68 | points { 69 | x: 0.0 70 | y: 1.0 71 | t_x: 1.0 72 | t_y: 0.0 73 | } 74 | spread: 0.5 75 | } 76 | properties { 77 | key: EMITTER_KEY_PARTICLE_SPEED 78 | points { 79 | x: 0.0 80 | y: 150.0 81 | t_x: 1.0 82 | t_y: 0.0 83 | } 84 | spread: 50.0 85 | } 86 | properties { 87 | key: EMITTER_KEY_PARTICLE_SIZE 88 | points { 89 | x: 0.0 90 | y: 15.0 91 | t_x: 1.0 92 | t_y: 0.0 93 | } 94 | spread: 5.0 95 | } 96 | properties { 97 | key: EMITTER_KEY_PARTICLE_RED 98 | points { 99 | x: 0.0 100 | y: 1.0 101 | t_x: 1.0 102 | t_y: 0.0 103 | } 104 | spread: 0.0 105 | } 106 | properties { 107 | key: EMITTER_KEY_PARTICLE_GREEN 108 | points { 109 | x: 0.0 110 | y: 0.9 111 | t_x: 1.0 112 | t_y: 0.0 113 | } 114 | spread: 0.1 115 | } 116 | properties { 117 | key: EMITTER_KEY_PARTICLE_BLUE 118 | points { 119 | x: 0.0 120 | y: 0.9 121 | t_x: 1.0 122 | t_y: 0.0 123 | } 124 | spread: 0.1 125 | } 126 | properties { 127 | key: EMITTER_KEY_PARTICLE_ALPHA 128 | points { 129 | x: 0.0 130 | y: 1.0 131 | t_x: 1.0 132 | t_y: 0.0 133 | } 134 | spread: 0.0 135 | } 136 | properties { 137 | key: EMITTER_KEY_PARTICLE_ROTATION 138 | points { 139 | x: 0.0 140 | y: 0.0 141 | t_x: 1.0 142 | t_y: 0.0 143 | } 144 | spread: 0.0 145 | } 146 | particle_properties { 147 | key: PARTICLE_KEY_SCALE 148 | points { 149 | x: 0.0 150 | y: 1.0 151 | t_x: 1.0 152 | t_y: 0.0 153 | } 154 | points { 155 | x: 0.13333334 156 | y: 0.31931743 157 | t_x: 1.0 158 | t_y: 0.0 159 | } 160 | points { 161 | x: 0.44786325 162 | y: 0.7078589 163 | t_x: 0.5429583 164 | t_y: 0.83975965 165 | } 166 | points { 167 | x: 0.7606838 168 | y: 0.24482013 169 | t_x: 0.73185706 170 | t_y: -0.6814582 171 | } 172 | points { 173 | x: 1.0 174 | y: 0.20370677 175 | t_x: 1.0 176 | t_y: 0.0 177 | } 178 | } 179 | particle_properties { 180 | key: PARTICLE_KEY_RED 181 | points { 182 | x: 0.0 183 | y: 1.0 184 | t_x: 1.0 185 | t_y: 0.0 186 | } 187 | } 188 | particle_properties { 189 | key: PARTICLE_KEY_GREEN 190 | points { 191 | x: 0.0 192 | y: 1.0 193 | t_x: 1.0 194 | t_y: 0.0 195 | } 196 | } 197 | particle_properties { 198 | key: PARTICLE_KEY_BLUE 199 | points { 200 | x: 0.0 201 | y: 1.0 202 | t_x: 1.0 203 | t_y: 0.0 204 | } 205 | } 206 | particle_properties { 207 | key: PARTICLE_KEY_ALPHA 208 | points { 209 | x: 0.0 210 | y: 0.0 211 | t_x: 0.07194582 212 | t_y: 0.99740857 213 | } 214 | points { 215 | x: 0.11320755 216 | y: 0.99277455 217 | t_x: 0.99418455 218 | t_y: 0.10768964 219 | } 220 | points { 221 | x: 0.62393165 222 | y: 0.25672176 223 | t_x: 0.99173677 224 | t_y: -0.12828955 225 | } 226 | points { 227 | x: 1.0 228 | y: 0.0072254334 229 | t_x: 0.4737472 230 | t_y: -0.8806609 231 | } 232 | } 233 | particle_properties { 234 | key: PARTICLE_KEY_ROTATION 235 | points { 236 | x: 0.0 237 | y: 0.0 238 | t_x: 1.0 239 | t_y: 0.0 240 | } 241 | } 242 | modifiers { 243 | type: MODIFIER_TYPE_ACCELERATION 244 | use_direction: 0 245 | position { 246 | x: 0.0 247 | y: 0.0 248 | z: 0.0 249 | } 250 | rotation { 251 | x: 0.0 252 | y: 0.0 253 | z: 0.0 254 | w: 1.0 255 | } 256 | properties { 257 | key: MODIFIER_KEY_MAGNITUDE 258 | points { 259 | x: 0.0 260 | y: 100.0 261 | t_x: 1.0 262 | t_y: 0.0 263 | } 264 | spread: 40.0 265 | } 266 | } 267 | size_mode: SIZE_MODE_MANUAL 268 | start_delay_spread: 0.0 269 | duration_spread: 0.0 270 | stretch_with_velocity: false 271 | start_offset: 0.0 272 | } 273 | emitters { 274 | id: "emitter1" 275 | mode: PLAY_MODE_ONCE 276 | duration: 0.3 277 | space: EMISSION_SPACE_EMITTER 278 | position { 279 | x: 0.0 280 | y: 0.0 281 | z: 0.0 282 | } 283 | rotation { 284 | x: 0.0 285 | y: 0.0 286 | z: 0.0 287 | w: 1.0 288 | } 289 | tile_source: "/examples/ocean-commotion/assets/sprites.atlas" 290 | animation: "white_dot2" 291 | material: "/builtins/materials/particlefx.material" 292 | blend_mode: BLEND_MODE_ALPHA 293 | particle_orientation: PARTICLE_ORIENTATION_DEFAULT 294 | inherit_velocity: 0.0 295 | max_particle_count: 32 296 | type: EMITTER_TYPE_CIRCLE 297 | start_delay: 0.0 298 | properties { 299 | key: EMITTER_KEY_SPAWN_RATE 300 | points { 301 | x: 0.0 302 | y: 80.0 303 | t_x: 1.0 304 | t_y: 0.0 305 | } 306 | spread: 20.0 307 | } 308 | properties { 309 | key: EMITTER_KEY_SIZE_X 310 | points { 311 | x: 0.0 312 | y: 100.0 313 | t_x: 1.0 314 | t_y: 0.0 315 | } 316 | spread: 0.0 317 | } 318 | properties { 319 | key: EMITTER_KEY_SIZE_Y 320 | points { 321 | x: 0.0 322 | y: 12.0 323 | t_x: 1.0 324 | t_y: 0.0 325 | } 326 | spread: 0.0 327 | } 328 | properties { 329 | key: EMITTER_KEY_SIZE_Z 330 | points { 331 | x: 0.0 332 | y: 0.0 333 | t_x: 1.0 334 | t_y: 0.0 335 | } 336 | spread: 0.0 337 | } 338 | properties { 339 | key: EMITTER_KEY_PARTICLE_LIFE_TIME 340 | points { 341 | x: 0.0 342 | y: 0.2 343 | t_x: 1.0 344 | t_y: 0.0 345 | } 346 | spread: 0.1 347 | } 348 | properties { 349 | key: EMITTER_KEY_PARTICLE_SPEED 350 | points { 351 | x: 0.0 352 | y: 100.0 353 | t_x: 1.0 354 | t_y: 0.0 355 | } 356 | spread: 50.0 357 | } 358 | properties { 359 | key: EMITTER_KEY_PARTICLE_SIZE 360 | points { 361 | x: 0.0 362 | y: 15.0 363 | t_x: 1.0 364 | t_y: 0.0 365 | } 366 | spread: 0.0 367 | } 368 | properties { 369 | key: EMITTER_KEY_PARTICLE_RED 370 | points { 371 | x: 0.0 372 | y: 1.0 373 | t_x: 1.0 374 | t_y: 0.0 375 | } 376 | spread: 0.0 377 | } 378 | properties { 379 | key: EMITTER_KEY_PARTICLE_GREEN 380 | points { 381 | x: 0.0 382 | y: 1.0 383 | t_x: 1.0 384 | t_y: 0.0 385 | } 386 | spread: 0.0 387 | } 388 | properties { 389 | key: EMITTER_KEY_PARTICLE_BLUE 390 | points { 391 | x: 0.0 392 | y: 1.0 393 | t_x: 1.0 394 | t_y: 0.0 395 | } 396 | spread: 0.0 397 | } 398 | properties { 399 | key: EMITTER_KEY_PARTICLE_ALPHA 400 | points { 401 | x: 0.0 402 | y: 1.0 403 | t_x: 1.0 404 | t_y: 0.0 405 | } 406 | spread: 0.0 407 | } 408 | properties { 409 | key: EMITTER_KEY_PARTICLE_ROTATION 410 | points { 411 | x: 0.0 412 | y: 0.0 413 | t_x: 1.0 414 | t_y: 0.0 415 | } 416 | spread: 0.0 417 | } 418 | particle_properties { 419 | key: PARTICLE_KEY_SCALE 420 | points { 421 | x: 0.0 422 | y: 1.0 423 | t_x: 1.0 424 | t_y: 0.0 425 | } 426 | points { 427 | x: 0.12307692 428 | y: 0.7719094 429 | t_x: 1.0 430 | t_y: 0.0 431 | } 432 | points { 433 | x: 0.4 434 | y: 0.67215174 435 | t_x: 0.5429583 436 | t_y: 0.83975965 437 | } 438 | points { 439 | x: 0.64786327 440 | y: 0.57113016 441 | t_x: 0.73185706 442 | t_y: -0.6814582 443 | } 444 | points { 445 | x: 1.0 446 | y: 0.395 447 | t_x: 1.0 448 | t_y: 0.0 449 | } 450 | } 451 | particle_properties { 452 | key: PARTICLE_KEY_RED 453 | points { 454 | x: 0.0 455 | y: 1.0 456 | t_x: 1.0 457 | t_y: 0.0 458 | } 459 | } 460 | particle_properties { 461 | key: PARTICLE_KEY_GREEN 462 | points { 463 | x: 0.0 464 | y: 1.0 465 | t_x: 1.0 466 | t_y: 0.0 467 | } 468 | } 469 | particle_properties { 470 | key: PARTICLE_KEY_BLUE 471 | points { 472 | x: 0.0 473 | y: 1.0 474 | t_x: 1.0 475 | t_y: 0.0 476 | } 477 | } 478 | particle_properties { 479 | key: PARTICLE_KEY_ALPHA 480 | points { 481 | x: 0.0 482 | y: 0.0 483 | t_x: 0.07194582 484 | t_y: 0.99740857 485 | } 486 | points { 487 | x: 0.10636994 488 | y: 0.8873579 489 | t_x: 0.99418455 490 | t_y: 0.10768964 491 | } 492 | points { 493 | x: 0.44800672 494 | y: 0.308156 495 | t_x: 0.5694311 496 | t_y: -0.82203907 497 | } 498 | points { 499 | x: 1.0 500 | y: 0.0072254334 501 | t_x: 0.4737472 502 | t_y: -0.8806609 503 | } 504 | } 505 | particle_properties { 506 | key: PARTICLE_KEY_ROTATION 507 | points { 508 | x: 0.0 509 | y: 0.0 510 | t_x: 1.0 511 | t_y: 0.0 512 | } 513 | } 514 | size_mode: SIZE_MODE_MANUAL 515 | start_delay_spread: 0.0 516 | duration_spread: 0.0 517 | stretch_with_velocity: false 518 | start_offset: 0.0 519 | } 520 | emitters { 521 | id: "emitter2" 522 | mode: PLAY_MODE_ONCE 523 | duration: 3.0 524 | space: EMISSION_SPACE_WORLD 525 | position { 526 | x: 0.0 527 | y: 0.0 528 | z: 0.0 529 | } 530 | rotation { 531 | x: 0.0 532 | y: 0.0 533 | z: 0.0 534 | w: 1.0 535 | } 536 | tile_source: "/examples/ocean-commotion/assets/sprites.atlas" 537 | animation: "bubble" 538 | material: "/builtins/materials/particlefx.material" 539 | blend_mode: BLEND_MODE_ADD 540 | particle_orientation: PARTICLE_ORIENTATION_DEFAULT 541 | inherit_velocity: 0.0 542 | max_particle_count: 2 543 | type: EMITTER_TYPE_2DCONE 544 | start_delay: 0.0 545 | properties { 546 | key: EMITTER_KEY_SPAWN_RATE 547 | points { 548 | x: 0.0 549 | y: 2.0 550 | t_x: 1.0 551 | t_y: 0.0 552 | } 553 | spread: 0.0 554 | } 555 | properties { 556 | key: EMITTER_KEY_SIZE_X 557 | points { 558 | x: 0.0 559 | y: 51.29495 560 | t_x: 1.0 561 | t_y: 0.0 562 | } 563 | spread: 4.0 564 | } 565 | properties { 566 | key: EMITTER_KEY_SIZE_Y 567 | points { 568 | x: 0.0 569 | y: 60.17744 570 | t_x: 1.0 571 | t_y: 0.0 572 | } 573 | spread: 0.0 574 | } 575 | properties { 576 | key: EMITTER_KEY_SIZE_Z 577 | points { 578 | x: 0.0 579 | y: 0.0 580 | t_x: 1.0 581 | t_y: 0.0 582 | } 583 | spread: 0.0 584 | } 585 | properties { 586 | key: EMITTER_KEY_PARTICLE_LIFE_TIME 587 | points { 588 | x: 0.0 589 | y: 3.0 590 | t_x: 1.0 591 | t_y: 0.0 592 | } 593 | spread: 1.0 594 | } 595 | properties { 596 | key: EMITTER_KEY_PARTICLE_SPEED 597 | points { 598 | x: 0.0 599 | y: 80.0 600 | t_x: 1.0 601 | t_y: 0.0 602 | } 603 | spread: 20.0 604 | } 605 | properties { 606 | key: EMITTER_KEY_PARTICLE_SIZE 607 | points { 608 | x: 0.0 609 | y: 25.0 610 | t_x: 1.0 611 | t_y: 0.0 612 | } 613 | spread: 10.0 614 | } 615 | properties { 616 | key: EMITTER_KEY_PARTICLE_RED 617 | points { 618 | x: 0.0 619 | y: 1.0 620 | t_x: 1.0 621 | t_y: 0.0 622 | } 623 | spread: 0.0 624 | } 625 | properties { 626 | key: EMITTER_KEY_PARTICLE_GREEN 627 | points { 628 | x: 0.0 629 | y: 1.0 630 | t_x: 1.0 631 | t_y: 0.0 632 | } 633 | spread: 0.0 634 | } 635 | properties { 636 | key: EMITTER_KEY_PARTICLE_BLUE 637 | points { 638 | x: 0.0 639 | y: 1.0 640 | t_x: 1.0 641 | t_y: 0.0 642 | } 643 | spread: 0.0 644 | } 645 | properties { 646 | key: EMITTER_KEY_PARTICLE_ALPHA 647 | points { 648 | x: 0.0 649 | y: 1.0 650 | t_x: 1.0 651 | t_y: 0.0 652 | } 653 | spread: 0.0 654 | } 655 | properties { 656 | key: EMITTER_KEY_PARTICLE_ROTATION 657 | points { 658 | x: 0.0 659 | y: 0.0 660 | t_x: 1.0 661 | t_y: 0.0 662 | } 663 | spread: 0.0 664 | } 665 | particle_properties { 666 | key: PARTICLE_KEY_SCALE 667 | points { 668 | x: 0.0 669 | y: 1.0 670 | t_x: 1.0 671 | t_y: 0.0 672 | } 673 | } 674 | particle_properties { 675 | key: PARTICLE_KEY_RED 676 | points { 677 | x: 0.0 678 | y: 1.0 679 | t_x: 1.0 680 | t_y: 0.0 681 | } 682 | } 683 | particle_properties { 684 | key: PARTICLE_KEY_GREEN 685 | points { 686 | x: 0.0 687 | y: 1.0 688 | t_x: 1.0 689 | t_y: 0.0 690 | } 691 | } 692 | particle_properties { 693 | key: PARTICLE_KEY_BLUE 694 | points { 695 | x: 0.0 696 | y: 1.0 697 | t_x: 1.0 698 | t_y: 0.0 699 | } 700 | } 701 | particle_properties { 702 | key: PARTICLE_KEY_ALPHA 703 | points { 704 | x: 0.0 705 | y: 0.0 706 | t_x: 0.07194582 707 | t_y: 0.99740857 708 | } 709 | points { 710 | x: 0.11320755 711 | y: 0.99277455 712 | t_x: 0.99418455 713 | t_y: 0.10768964 714 | } 715 | points { 716 | x: 0.7112546 717 | y: 0.555656 718 | t_x: 0.5694311 719 | t_y: -0.82203907 720 | } 721 | points { 722 | x: 1.0 723 | y: 0.0072254334 724 | t_x: 0.4737472 725 | t_y: -0.8806609 726 | } 727 | } 728 | particle_properties { 729 | key: PARTICLE_KEY_ROTATION 730 | points { 731 | x: 0.0 732 | y: 0.0 733 | t_x: 1.0 734 | t_y: 0.0 735 | } 736 | } 737 | size_mode: SIZE_MODE_MANUAL 738 | start_delay_spread: 0.0 739 | duration_spread: 0.0 740 | stretch_with_velocity: false 741 | start_offset: 0.0 742 | } 743 | -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/eyes-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/eyes-blue.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/eyes-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/eyes-green.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/eyes-orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/eyes-orange.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/eyes-purple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/eyes-purple.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/eyes-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/eyes-red.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/eyes-starfish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/eyes-starfish.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/eyes-yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/eyes-yellow.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/fish-blue-h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/fish-blue-h.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/fish-blue-v.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/fish-blue-v.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/fish-blue-w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/fish-blue-w.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/fish-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/fish-blue.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/fish-bones.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/fish-bones.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/fish-green-h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/fish-green-h.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/fish-green-v.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/fish-green-v.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/fish-green-w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/fish-green-w.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/fish-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/fish-green.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/fish-orange-h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/fish-orange-h.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/fish-orange-v.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/fish-orange-v.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/fish-orange-w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/fish-orange-w.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/fish-orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/fish-orange.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/fish-purple-h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/fish-purple-h.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/fish-purple-v.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/fish-purple-v.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/fish-purple-w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/fish-purple-w.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/fish-purple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/fish-purple.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/fish-red-h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/fish-red-h.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/fish-red-v.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/fish-red-v.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/fish-red-w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/fish-red-w.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/fish-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/fish-red.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/fish-starfish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/fish-starfish.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/fish-yellow-h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/fish-yellow-h.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/fish-yellow-v.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/fish-yellow-v.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/fish-yellow-w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/fish-yellow-w.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/fish/fish-yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/fish/fish-yellow.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/font/Pacifico.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/font/Pacifico.ttf -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/font/text.font: -------------------------------------------------------------------------------- 1 | font: "/examples/ocean-commotion/assets/font/Pacifico.ttf" 2 | material: "/builtins/fonts/font.material" 3 | size: 96 4 | antialias: 1 5 | alpha: 1.0 6 | outline_alpha: 0.0 7 | outline_width: 0.0 8 | shadow_alpha: 0.0 9 | shadow_blur: 0 10 | shadow_x: 0.0 11 | shadow_y: 0.0 12 | extra_characters: "" 13 | output_format: TYPE_BITMAP 14 | all_chars: false 15 | cache_width: 0 16 | cache_height: 0 17 | -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/sprites.atlas: -------------------------------------------------------------------------------- 1 | images { 2 | image: "/examples/ocean-commotion/assets/fish/eyes-blue.png" 3 | } 4 | images { 5 | image: "/examples/ocean-commotion/assets/fish/eyes-green.png" 6 | } 7 | images { 8 | image: "/examples/ocean-commotion/assets/fish/eyes-orange.png" 9 | } 10 | images { 11 | image: "/examples/ocean-commotion/assets/fish/eyes-purple.png" 12 | } 13 | images { 14 | image: "/examples/ocean-commotion/assets/fish/eyes-red.png" 15 | } 16 | images { 17 | image: "/examples/ocean-commotion/assets/fish/eyes-starfish.png" 18 | } 19 | images { 20 | image: "/examples/ocean-commotion/assets/fish/eyes-yellow.png" 21 | } 22 | images { 23 | image: "/examples/ocean-commotion/assets/fish/fish-blue-h.png" 24 | } 25 | images { 26 | image: "/examples/ocean-commotion/assets/fish/fish-blue-v.png" 27 | } 28 | images { 29 | image: "/examples/ocean-commotion/assets/fish/fish-blue-w.png" 30 | } 31 | images { 32 | image: "/examples/ocean-commotion/assets/fish/fish-blue.png" 33 | } 34 | images { 35 | image: "/examples/ocean-commotion/assets/fish/fish-green-h.png" 36 | } 37 | images { 38 | image: "/examples/ocean-commotion/assets/fish/fish-green-v.png" 39 | } 40 | images { 41 | image: "/examples/ocean-commotion/assets/fish/fish-green-w.png" 42 | } 43 | images { 44 | image: "/examples/ocean-commotion/assets/fish/fish-green.png" 45 | } 46 | images { 47 | image: "/examples/ocean-commotion/assets/fish/fish-orange-h.png" 48 | } 49 | images { 50 | image: "/examples/ocean-commotion/assets/fish/fish-orange-v.png" 51 | } 52 | images { 53 | image: "/examples/ocean-commotion/assets/fish/fish-orange-w.png" 54 | } 55 | images { 56 | image: "/examples/ocean-commotion/assets/fish/fish-orange.png" 57 | } 58 | images { 59 | image: "/examples/ocean-commotion/assets/fish/fish-purple-h.png" 60 | } 61 | images { 62 | image: "/examples/ocean-commotion/assets/fish/fish-purple-v.png" 63 | } 64 | images { 65 | image: "/examples/ocean-commotion/assets/fish/fish-purple-w.png" 66 | } 67 | images { 68 | image: "/examples/ocean-commotion/assets/fish/fish-purple.png" 69 | } 70 | images { 71 | image: "/examples/ocean-commotion/assets/fish/fish-red-h.png" 72 | } 73 | images { 74 | image: "/examples/ocean-commotion/assets/fish/fish-red-v.png" 75 | } 76 | images { 77 | image: "/examples/ocean-commotion/assets/fish/fish-red-w.png" 78 | } 79 | images { 80 | image: "/examples/ocean-commotion/assets/fish/fish-red.png" 81 | } 82 | images { 83 | image: "/examples/ocean-commotion/assets/fish/fish-starfish.png" 84 | } 85 | images { 86 | image: "/examples/ocean-commotion/assets/fish/fish-yellow-h.png" 87 | } 88 | images { 89 | image: "/examples/ocean-commotion/assets/fish/fish-yellow-v.png" 90 | } 91 | images { 92 | image: "/examples/ocean-commotion/assets/fish/fish-yellow-w.png" 93 | } 94 | images { 95 | image: "/examples/ocean-commotion/assets/fish/fish-yellow.png" 96 | } 97 | images { 98 | image: "/examples/ocean-commotion/assets/effects/bubble.png" 99 | } 100 | images { 101 | image: "/examples/ocean-commotion/assets/effects/white_dot.png" 102 | } 103 | images { 104 | image: "/examples/ocean-commotion/assets/effects/white_dot2.png" 105 | } 106 | images { 107 | image: "/examples/ocean-commotion/assets/fish/fish-bones.png" 108 | } 109 | margin: 0 110 | extrude_borders: 2 111 | inner_padding: 0 112 | -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/ui/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/ui/bg.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/ui/button-YES.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/ui/button-YES.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/ui/button1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/ui/button1.png -------------------------------------------------------------------------------- /examples/ocean-commotion/assets/ui/button_NO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/examples/ocean-commotion/assets/ui/button_NO.png -------------------------------------------------------------------------------- /examples/ocean-commotion/board.script: -------------------------------------------------------------------------------- 1 | local emthree = require "emthree.emthree" 2 | local effects = require "emthree.effects" 3 | local block_color = require "examples.ocean-commotion.color" 4 | local block_type = require "examples.ocean-commotion.type" 5 | 6 | local blocksize = 96 -- Distance between block centers 7 | local boardwidth = 6 -- Number of columns 8 | local boardheight = 9 -- Number of rows 9 | 10 | -- 11 | -- The list of fishes that are used. These names must match animations/images 12 | -- in the atlas used for fishes. 13 | -- 14 | -- 15 | local colors = { 16 | block_color.YELLOW, 17 | block_color.BLUE, 18 | block_color.ORANGE, 19 | block_color.PURPLE, 20 | block_color.GREEN, 21 | block_color.RED 22 | } 23 | 24 | local function is_striped(block) 25 | return block.type == block_type.STRIPED_V or block.type == block_type.STRIPED_H 26 | end 27 | 28 | local function is_striped_vertical(block) 29 | return block.type == block_type.STRIPED_V 30 | end 31 | 32 | local function is_striped_horizontal(block) 33 | return block.type == block_type.STRIPED_H 34 | end 35 | 36 | local function is_wrapped(block) 37 | return block.type == block_type.WRAPPED 38 | end 39 | 40 | local function is_bomb(block) 41 | return block.type == block_type.BOMB 42 | end 43 | 44 | local function create_block(board, position, type, color) 45 | if not type and not color then 46 | color = color or colors[math.random(#colors)] 47 | type = type or block_type.PLAIN 48 | end 49 | local id = factory.create("#fish_factory", position, null, { color = color, type = type }) 50 | msg.post(id, "set_parent", { parent_id = go.get_id(), keep_world_transform = 0 }) 51 | return id, color, type 52 | end 53 | 54 | local function create_blocker(board, position, type) 55 | local id = factory.create("#fish_factory", position, null, { color = nil, type = type }) 56 | msg.post(id, "set_parent", { parent_id = go.get_id(), keep_world_transform = 0 }) 57 | return id, type 58 | end 59 | 60 | 61 | local function on_match(board, block, horisontal_neighbors, vertical_neighbors) 62 | -- handle t, l and cross shaped formations 63 | if #horisontal_neighbors >= 2 and #vertical_neighbors >= 2 then 64 | emthree.remove_blocks(board, horisontal_neighbors) 65 | emthree.remove_blocks(board, vertical_neighbors) 66 | emthree.change_block(block, block_type.WRAPPED, block.color) 67 | elseif #horisontal_neighbors >= 4 then 68 | emthree.remove_blocks(board, horisontal_neighbors) 69 | emthree.change_block(block, block_type.BOMB, nil) 70 | elseif #horisontal_neighbors == 3 then 71 | emthree.remove_blocks(board, horisontal_neighbors) 72 | emthree.change_block(block, block_type.STRIPED_V, block.color) 73 | elseif #horisontal_neighbors == 2 then 74 | emthree.remove_block(board, block) 75 | emthree.remove_blocks(board, horisontal_neighbors) 76 | elseif #vertical_neighbors >= 4 then 77 | emthree.remove_blocks(board, vertical_neighbors) 78 | emthree.change_block(block, block_type.BOMB, nil) 79 | elseif #vertical_neighbors == 3 then 80 | emthree.remove_blocks(board, vertical_neighbors) 81 | emthree.change_block(block, block_type.STRIPED_H, block.color) 82 | elseif #vertical_neighbors == 2 then 83 | emthree.remove_block(board, block) 84 | emthree.remove_blocks(board, vertical_neighbors) 85 | end 86 | end 87 | 88 | 89 | local function on_block_removed(board, block) 90 | if is_striped_horizontal(block) then 91 | effects.horisontal_lineblast(board, block) 92 | elseif is_striped_vertical(block) then 93 | effects.vertical_lineblast(board, block) 94 | elseif is_wrapped(block) then 95 | effects.bomb(board, block, 1) 96 | elseif is_bomb(block) then 97 | effects.remove_color(board, block.color) 98 | end 99 | end 100 | 101 | -- 102 | -- Handle swapping of special blocks and trigger desired effect 103 | -- 104 | local function on_swap(board, block1, block2) 105 | if is_bomb(block1) and is_bomb(block2) then 106 | effects.remove_all(board) 107 | return true 108 | elseif (is_striped_vertical(block1) and is_striped_vertical(block2)) or (is_striped_horizontal(block1) and is_striped_horizontal(block2)) then 109 | emthree.change_block(block1, block_type.STRIPED_H, block1.color) 110 | emthree.change_block(block2, block_type.STRIPED_V, block2.color) 111 | emthree.remove_block(board, block1) 112 | emthree.remove_block(board, block2) 113 | return true 114 | elseif is_striped(block1) and is_striped(block2) then 115 | emthree.remove_block(board, block1) 116 | emthree.remove_block(board, block2) 117 | return true 118 | elseif is_wrapped(block1) and is_wrapped(block2) then 119 | emthree.remove_block(board, block2, true) 120 | effects.bomb(board, block1, 2) 121 | local wrapped = emthree.create_block(board, block2.x, block2.y, block_type.WRAPPED, block2.color) 122 | emthree.stabilize(board) 123 | emthree.remove_block(board, wrapped) 124 | return true 125 | elseif (is_striped(block1) or is_striped(block2)) and (is_wrapped(block1) or is_wrapped(block2)) then 126 | effects.horisontal_lineblast(board, block1, 3) 127 | effects.vertical_lineblast(board, block2, 3) 128 | return true 129 | elseif (is_bomb(block1) or is_bomb(block2)) and (is_striped(block1) or is_striped(block2)) then 130 | print("bomb+striped - add code to convert to striped and trigger them") 131 | return true 132 | elseif (is_bomb(block1) or is_bomb(block2)) and (is_wrapped(block1) or is_wrapped(block2)) then 133 | print("bomb+wrapped - add code") 134 | return true 135 | elseif is_bomb(block1) or is_bomb(block2) then 136 | effects.remove_color(board, is_bomb(block1) and block2.color or block1.color) 137 | emthree.remove_block(board, is_bomb(block1) and block1 or block2, true) 138 | return true 139 | end 140 | return false 141 | end 142 | 143 | local function change_block(type, color) 144 | return function(board, action) 145 | local x, y = emthree.screen_to_slot(board, action.x, action.y) 146 | local block = emthree.get_block(board, x, y) 147 | if block then 148 | emthree.change_block(block, type or block.type, color or block.color) 149 | end 150 | end 151 | end 152 | 153 | local function delete_block(board, action) 154 | local x, y = emthree.screen_to_slot(board, action.x, action.y) 155 | local block = emthree.get_block(board, x, y) 156 | if block then 157 | emthree.remove_block(board, block, true) 158 | end 159 | end 160 | 161 | -- create a board with a specific setup 162 | local function debug_board(board) 163 | -- 1 = YELLOW, 164 | -- 2 = BLUE, 165 | -- 3 = ORANGE, 166 | -- 4 = PURPLE, 167 | -- 5 = GREEN, 168 | -- 6 = RED 169 | local BOARD = 170 | "545562" .. 171 | "145211" .. 172 | "V34353" .. 173 | "343226" .. 174 | "VH3165" .. 175 | "1B2114" .. 176 | "524226" .. 177 | "2446H5" 178 | 179 | for y=0,boardheight-2 do 180 | for x=0,boardwidth-1 do 181 | local i = 1 + ((boardheight - 2 - y) * boardwidth) + x 182 | local slot = BOARD:sub(i, i) 183 | if slot == "1" then 184 | emthree.create_block(board, x, y, block_type.PLAIN, colors[1]) 185 | elseif slot == "2" then 186 | emthree.create_block(board, x, y, block_type.PLAIN, colors[2]) 187 | elseif slot == "3" then 188 | emthree.create_block(board, x, y, block_type.PLAIN, colors[3]) 189 | elseif slot == "4" then 190 | emthree.create_block(board, x, y, block_type.PLAIN, colors[4]) 191 | elseif slot == "5" then 192 | emthree.create_block(board, x, y, block_type.PLAIN, colors[5]) 193 | elseif slot == "6" then 194 | emthree.create_block(board, x, y, block_type.PLAIN, colors[6]) 195 | elseif slot == "B" then 196 | emthree.create_block(board, x, y, block_type.BOMB, colors[1]) 197 | elseif slot == "H" then 198 | emthree.create_block(board, x, y, block_type.STRIPED_H, colors[1]) 199 | elseif slot == "V" then 200 | emthree.create_block(board, x, y, block_type.STRIPED_V, colors[1]) 201 | elseif slot == "W" then 202 | emthree.create_block(board, x, y, block_type.WRAPPED, colors[1]) 203 | end 204 | end 205 | end 206 | 207 | end 208 | 209 | 210 | -- 211 | -- INIT the board 212 | -- 213 | function init(self) 214 | self.board = emthree.create_board(boardwidth, boardheight, blocksize) 215 | emthree.on_create_block(self.board, create_block) 216 | emthree.on_create_blocker(self.board, create_blocker) 217 | emthree.on_match(self.board, on_match) 218 | emthree.on_block_removed(self.board, on_block_removed) 219 | emthree.on_swap(self.board, on_swap) 220 | emthree.on_no_possible_switches(self.board, function() emthree.shuffle(self.board) end) 221 | 222 | -- start the board 223 | for x=0,boardwidth-1 do 224 | emthree.create_spawner(self.board, x, boardheight - 1, "SPAWNER") 225 | end 226 | --debug_board(self.board) 227 | emthree.fill_board(self.board) 228 | emthree.stabilize(self.board, function() 229 | msg.post(".", "acquire_input_focus") 230 | end) 231 | 232 | self.on_touch = emthree.on_input 233 | end 234 | 235 | -- 236 | -- INPUT HANDLING 237 | -- 238 | function on_input(self, action_id, action) 239 | if action_id == hash("touch") and (action.pressed or action.released) then 240 | return self.on_touch(self.board, action) 241 | elseif action_id == hash("wrapped") and action.released then 242 | self.on_touch = change_block(block_type.WRAPPED, nil) 243 | elseif action_id == hash("bomb") and action.released then 244 | self.on_touch = change_block(block_type.BOMB, nil) 245 | elseif action_id == hash("horizontal") and action.released then 246 | self.on_touch = change_block(block_type.STRIPED_H, nil) 247 | elseif action_id == hash("vertical") and action.released then 248 | self.on_touch = change_block(block_type.STRIPED_V, nil) 249 | elseif action_id == hash("color1") and action.released then 250 | self.on_touch = change_block(block_type.PLAIN, colors[1]) 251 | elseif action_id == hash("color2") and action.released then 252 | self.on_touch = change_block(block_type.PLAIN, colors[2]) 253 | elseif action_id == hash("color3") and action.released then 254 | self.on_touch = change_block(block_type.PLAIN, colors[3]) 255 | elseif action_id == hash("color4") and action.released then 256 | self.on_touch = change_block(block_type.PLAIN, colors[4]) 257 | elseif action_id == hash("color5") and action.released then 258 | self.on_touch = change_block(block_type.PLAIN, colors[5]) 259 | elseif action_id == hash("color6") and action.released then 260 | self.on_touch = change_block(block_type.PLAIN, colors[6]) 261 | elseif action_id == hash("delete") and action.released then 262 | self.on_touch = delete_block 263 | elseif action_id == hash("stabilize") and action.released then 264 | emthree.stabilize(self.board) 265 | elseif action_id == hash("cancel") and action.released then 266 | self.on_touch = emthree.on_input 267 | elseif action_id == hash("dump") and action.released then 268 | print(emthree.dump(self.board)) 269 | end 270 | end 271 | 272 | function on_reload(self) 273 | end 274 | -------------------------------------------------------------------------------- /examples/ocean-commotion/color.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | M.YELLOW = hash("yellow") 4 | M.BLUE = hash("blue") 5 | M.ORANGE = hash("orange") 6 | M.PURPLE = hash("purple") 7 | M.GREEN = hash("green") 8 | M.RED = hash("red") 9 | 10 | return M -------------------------------------------------------------------------------- /examples/ocean-commotion/fish.go: -------------------------------------------------------------------------------- 1 | components { 2 | id: "explosion" 3 | component: "/examples/ocean-commotion/assets/explode.particlefx" 4 | position { 5 | x: 0.0 6 | y: 0.0 7 | z: 0.0 8 | } 9 | rotation { 10 | x: 0.0 11 | y: 0.0 12 | z: 0.0 13 | w: 1.0 14 | } 15 | } 16 | components { 17 | id: "script" 18 | component: "/examples/ocean-commotion/fish.script" 19 | position { 20 | x: 0.0 21 | y: 0.0 22 | z: 0.0 23 | } 24 | rotation { 25 | x: 0.0 26 | y: 0.0 27 | z: 0.0 28 | w: 1.0 29 | } 30 | } 31 | embedded_components { 32 | id: "sprite" 33 | type: "sprite" 34 | data: "tile_set: \"/examples/ocean-commotion/assets/sprites.atlas\"\n" 35 | "default_animation: \"fish-blue\"\n" 36 | "material: \"/builtins/materials/sprite.material\"\n" 37 | "blend_mode: BLEND_MODE_ALPHA\n" 38 | "" 39 | position { 40 | x: 0.0 41 | y: 0.0 42 | z: 0.0 43 | } 44 | rotation { 45 | x: 0.0 46 | y: 0.0 47 | z: 0.0 48 | w: 1.0 49 | } 50 | } 51 | embedded_components { 52 | id: "sprite-eyes" 53 | type: "sprite" 54 | data: "tile_set: \"/examples/ocean-commotion/assets/sprites.atlas\"\n" 55 | "default_animation: \"eyes-blue\"\n" 56 | "material: \"/builtins/materials/sprite.material\"\n" 57 | "blend_mode: BLEND_MODE_ALPHA\n" 58 | "" 59 | position { 60 | x: 0.0 61 | y: 0.0 62 | z: 0.1 63 | } 64 | rotation { 65 | x: 0.0 66 | y: 0.0 67 | z: 0.0 68 | w: 1.0 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /examples/ocean-commotion/fish.script: -------------------------------------------------------------------------------- 1 | go.property("color", hash("yellow")) 2 | go.property("type", hash("plain")) 3 | go.property("blink", 0) 4 | 5 | local emthree = require "emthree.emthree" 6 | local block_color = require "examples.ocean-commotion.color" 7 | local block_type = require "examples.ocean-commotion.type" 8 | 9 | 10 | local normal_scale = 0.45 -- The normal render size 11 | local zoomed_scale = 0.6 -- When interacted with, zoom to this 12 | 13 | local colors = { 14 | [block_color.YELLOW] = "yellow", 15 | [block_color.BLUE] = "blue", 16 | [block_color.ORANGE] = "orange", 17 | [block_color.PURPLE] = "purple", 18 | [block_color.GREEN] = "green", 19 | [block_color.RED] = "red" 20 | } 21 | 22 | local function change_sprite(id) 23 | sprite.play_flipbook("#sprite", id) 24 | end 25 | 26 | local function change_eyes(id) 27 | sprite.play_flipbook("#sprite-eyes", id) 28 | end 29 | 30 | local function blink(self) 31 | local t 32 | if self.blinking then 33 | t = math.random() / 5 + 0.1 34 | else 35 | t = math.random(4) + 3 36 | end 37 | go.animate("#", "blink", go.PLAYBACK_ONCE_FORWARD, 1, go.EASING_LINEAR, t, 0, function(self) 38 | if not self.blinking then 39 | msg.post("#sprite-eyes", "disable") 40 | else 41 | msg.post("#sprite-eyes", "enable") 42 | end 43 | self.blinking = not self.blinking 44 | blink(self) 45 | end) 46 | end 47 | 48 | local function update_sprites(self) 49 | particlefx.stop("#explosion") 50 | msg.post("#sprite", "enable") 51 | msg.post("#sprite-eyes", "enable") 52 | local c = colors[self.color] 53 | local e = colors[self.color] 54 | if self.type == block_type.BLOCKER then 55 | msg.post("#sprite-eyes", "disable") 56 | e = "eyes-" .. e 57 | c = "fish-bones" 58 | elseif self.type == block_type.STRIPED_H then 59 | e = "eyes-" .. e 60 | c = "fish-" .. c .. "-h" 61 | elseif self.type == block_type.STRIPED_V then 62 | e = "eyes-" .. e 63 | c = "fish-" .. c .. "-v" 64 | elseif self.type == block_type.WRAPPED then 65 | msg.post("#sprite-eyes", "disable") 66 | go.cancel_animations("#", "blink") 67 | e = "eyes-" .. e 68 | c = "fish-" .. c .. "-w" 69 | elseif self.type == block_type.BOMB then 70 | e = "eyes-starfish" 71 | c = "fish-starfish" 72 | else 73 | e = "eyes-" .. c 74 | c = "fish-" .. c 75 | end 76 | change_sprite(hash(c)) 77 | change_eyes(hash(e)) 78 | end 79 | 80 | 81 | function init(self) 82 | -- store original rotation 83 | -- 84 | self.rot = go.get_rotation() 85 | 86 | -- Render scaled down since atlas graphics is too large. 87 | -- An alternative is to scale down the source material but working with 88 | -- higher res images will show its benefits on retina screens. 89 | -- 90 | go.set_scale(normal_scale) 91 | msg.post("#", "sway") 92 | update_sprites(self) 93 | 94 | self.blinking = false 95 | blink(self) 96 | end 97 | 98 | 99 | function on_message(self, message_id, message, sender) 100 | if message_id == emthree.CHANGE then 101 | self.color = message.color or block_color.YELLOW 102 | self.type = message.type or block_type.PLAIN 103 | go.set_position(message.position) 104 | update_sprites(self) 105 | elseif message_id == hash("sway") then 106 | local rot = go.get(".", "euler.z") 107 | go.set(".", "euler.z", rot - 1) 108 | local t = (math.random() * 2) + 2 109 | go.cancel_animations(".", "euler.z") 110 | go.animate(".", "euler.z", go.PLAYBACK_LOOP_PINGPONG, rot + 1, go.EASING_INOUTQUAD, t) 111 | elseif message_id == emthree.SELECT then 112 | -- 113 | -- Play a simple effect used to signal that we are 114 | -- interacting with the fish. 115 | -- 116 | go.animate(".", "scale", go.PLAYBACK_ONCE_FORWARD, zoomed_scale, go.EASING_INOUTSINE, 0.1) 117 | local rot = go.get(".", "euler.z") 118 | local r = math.random() / 50 119 | go.cancel_animations(".", "euler.z") 120 | go.animate(".", "euler.z", go.PLAYBACK_LOOP_PINGPONG, rot - 4, go.EASING_INOUTSINE, 0.1 + r) 121 | elseif message_id == emthree.RESET then 122 | -- 123 | -- Reset the fish, meaning that we stop animation and 124 | -- go back to normal scaling. 125 | -- 126 | go.cancel_animations(".", "scale") 127 | go.animate(".", "scale", go.PLAYBACK_ONCE_FORWARD, normal_scale, go.EASING_INOUTSINE, 0.1) 128 | go.cancel_animations(".", "euler.z") 129 | go.set_rotation(self.rot) 130 | msg.post("#", "sway") 131 | elseif message_id == emthree.REMOVE then 132 | -- 133 | -- This fish is being removed from the board. 134 | -- Go out with a bang! 135 | -- 136 | particlefx.play("#explosion") 137 | -- 138 | -- Remove 139 | -- 140 | go.delete() 141 | end 142 | end 143 | -------------------------------------------------------------------------------- /examples/ocean-commotion/ocean-commotion.collection: -------------------------------------------------------------------------------- 1 | name: "oceancommotion" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "board" 5 | data: "components {\n" 6 | " id: \"script\"\n" 7 | " component: \"/examples/ocean-commotion/board.script\"\n" 8 | " position {\n" 9 | " x: 0.0\n" 10 | " y: 0.0\n" 11 | " z: 0.0\n" 12 | " }\n" 13 | " rotation {\n" 14 | " x: 0.0\n" 15 | " y: 0.0\n" 16 | " z: 0.0\n" 17 | " w: 1.0\n" 18 | " }\n" 19 | "}\n" 20 | "embedded_components {\n" 21 | " id: \"fish_factory\"\n" 22 | " type: \"factory\"\n" 23 | " data: \"prototype: \\\"/examples/ocean-commotion/fish.go\\\"\\n" 24 | "load_dynamically: false\\n" 25 | "\"\n" 26 | " position {\n" 27 | " x: 0.0\n" 28 | " y: 0.0\n" 29 | " z: 0.0\n" 30 | " }\n" 31 | " rotation {\n" 32 | " x: 0.0\n" 33 | " y: 0.0\n" 34 | " z: 0.0\n" 35 | " w: 1.0\n" 36 | " }\n" 37 | "}\n" 38 | "" 39 | position { 40 | x: 35.0 41 | y: 110.0 42 | z: 0.0 43 | } 44 | rotation { 45 | x: 0.0 46 | y: 0.0 47 | z: 0.0 48 | w: 1.0 49 | } 50 | scale3 { 51 | x: 1.0 52 | y: 1.0 53 | z: 1.0 54 | } 55 | } 56 | embedded_instances { 57 | id: "logo" 58 | data: "embedded_components {\n" 59 | " id: \"label\"\n" 60 | " type: \"label\"\n" 61 | " data: \"size {\\n" 62 | " x: 128.0\\n" 63 | " y: 32.0\\n" 64 | " z: 0.0\\n" 65 | " w: 0.0\\n" 66 | "}\\n" 67 | "scale {\\n" 68 | " x: 0.5\\n" 69 | " y: 0.5\\n" 70 | " z: 1.0\\n" 71 | " w: 0.0\\n" 72 | "}\\n" 73 | "color {\\n" 74 | " x: 1.0\\n" 75 | " y: 1.0\\n" 76 | " z: 1.0\\n" 77 | " w: 1.0\\n" 78 | "}\\n" 79 | "outline {\\n" 80 | " x: 0.0\\n" 81 | " y: 0.0\\n" 82 | " z: 0.0\\n" 83 | " w: 1.0\\n" 84 | "}\\n" 85 | "shadow {\\n" 86 | " x: 0.0\\n" 87 | " y: 0.0\\n" 88 | " z: 0.0\\n" 89 | " w: 1.0\\n" 90 | "}\\n" 91 | "leading: 1.0\\n" 92 | "tracking: 0.0\\n" 93 | "pivot: PIVOT_CENTER\\n" 94 | "blend_mode: BLEND_MODE_ALPHA\\n" 95 | "line_break: false\\n" 96 | "text: \\\"Ocean Commotion\\\"\\n" 97 | "font: \\\"/examples/ocean-commotion/assets/font/text.font\\\"\\n" 98 | "material: \\\"/builtins/fonts/label.material\\\"\\n" 99 | "\"\n" 100 | " position {\n" 101 | " x: 0.0\n" 102 | " y: 0.0\n" 103 | " z: 0.0\n" 104 | " }\n" 105 | " rotation {\n" 106 | " x: 0.0\n" 107 | " y: 0.0\n" 108 | " z: 0.0\n" 109 | " w: 1.0\n" 110 | " }\n" 111 | "}\n" 112 | "" 113 | position { 114 | x: 320.0 115 | y: 1067.0 116 | z: 1.0 117 | } 118 | rotation { 119 | x: 0.0 120 | y: 0.0 121 | z: 0.0 122 | w: 1.0 123 | } 124 | scale3 { 125 | x: 1.0 126 | y: 1.0 127 | z: 1.0 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /examples/ocean-commotion/type.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | M.PLAIN = hash("plain") 4 | M.STRIPED_H = hash("striped-h") 5 | M.STRIPED_V = hash("striped-v") 6 | M.WRAPPED = hash("wrapped") 7 | M.BOMB = hash("bomb") 8 | 9 | M.BLOCKER = hash("blocker") 10 | 11 | return M -------------------------------------------------------------------------------- /game.project: -------------------------------------------------------------------------------- 1 | [project] 2 | title = Emthree 3 | version = 0.1 4 | dependencies = 5 | 6 | [bootstrap] 7 | main_collection = /examples/examples.collectionc 8 | 9 | [input] 10 | game_binding = /input/game.input_bindingc 11 | 12 | [display] 13 | width = 640 14 | height = 1136 15 | high_dpi = 1 16 | 17 | [physics] 18 | scale = 0.02 19 | 20 | [script] 21 | shared_state = 1 22 | 23 | [particle_fx] 24 | max_count = 256 25 | max_particle_count = 4096 26 | 27 | [sprite] 28 | max_count = 256 29 | 30 | [library] 31 | include_dirs = emthree 32 | 33 | -------------------------------------------------------------------------------- /images/block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/images/block.png -------------------------------------------------------------------------------- /images/board.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/images/board.png -------------------------------------------------------------------------------- /images/match.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/images/match.png -------------------------------------------------------------------------------- /images/slot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/emthree/c379486a7902d841c3f2d975d6d31d46238a11b3/images/slot.png -------------------------------------------------------------------------------- /input/game.input_binding: -------------------------------------------------------------------------------- 1 | key_trigger { 2 | input: KEY_B 3 | action: "bomb" 4 | } 5 | key_trigger { 6 | input: KEY_W 7 | action: "wrapped" 8 | } 9 | key_trigger { 10 | input: KEY_H 11 | action: "horizontal" 12 | } 13 | key_trigger { 14 | input: KEY_V 15 | action: "vertical" 16 | } 17 | key_trigger { 18 | input: KEY_1 19 | action: "color1" 20 | } 21 | key_trigger { 22 | input: KEY_2 23 | action: "color2" 24 | } 25 | key_trigger { 26 | input: KEY_3 27 | action: "color3" 28 | } 29 | key_trigger { 30 | input: KEY_4 31 | action: "color4" 32 | } 33 | key_trigger { 34 | input: KEY_5 35 | action: "color5" 36 | } 37 | key_trigger { 38 | input: KEY_ESC 39 | action: "cancel" 40 | } 41 | key_trigger { 42 | input: KEY_SPACE 43 | action: "stabilize" 44 | } 45 | key_trigger { 46 | input: KEY_BACKSPACE 47 | action: "delete" 48 | } 49 | key_trigger { 50 | input: KEY_D 51 | action: "dump" 52 | } 53 | key_trigger { 54 | input: KEY_6 55 | action: "color6" 56 | } 57 | mouse_trigger { 58 | input: MOUSE_BUTTON_1 59 | action: "touch" 60 | } 61 | --------------------------------------------------------------------------------