{"id":15722563,"url":"https://github.com/rocamocha/defold-annie","last_synced_at":"2026-04-28T08:34:11.416Z","repository":{"id":60019538,"uuid":"539907716","full_name":"rocamocha/defold-annie","owner":"rocamocha","description":"A utility module for defold aimed at streamlining workflow for 2D sprites with non-uniform animation groups.","archived":false,"fork":false,"pushed_at":"2022-09-23T22:36:50.000Z","size":71,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-06T05:18:03.930Z","etag":null,"topics":["defold","defold-library","defold-module"],"latest_commit_sha":null,"homepage":"","language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rocamocha.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-09-22T09:44:40.000Z","updated_at":"2023-09-05T14:52:54.000Z","dependencies_parsed_at":"2022-09-25T15:30:16.927Z","dependency_job_id":null,"html_url":"https://github.com/rocamocha/defold-annie","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rocamocha%2Fdefold-annie","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rocamocha%2Fdefold-annie/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rocamocha%2Fdefold-annie/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rocamocha%2Fdefold-annie/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rocamocha","download_url":"https://codeload.github.com/rocamocha/defold-annie/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246395572,"owners_count":20770240,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["defold","defold-library","defold-module"],"created_at":"2024-10-03T22:08:24.572Z","updated_at":"2026-04-28T08:34:06.362Z","avatar_url":"https://github.com/rocamocha.png","language":"Lua","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Say hello to Annie 👋\nAnnie is a utility module written for the Defold game engine that provides some basic functions to make working with atlases that contain animation groups that consist of 2D sprites with differing uniform dimensions easier to align.\n\nThe main utilities of Annie are:\n- basic animation switch for lightweight integration into responsive logic in your `update()` (Annie doesn't send the `'play_animation'` message if already playing the animation)\n- ability to bypass the animation switch, forcing a replay of the current animation\n- automatic alignment of the parent gameobject on playing an animation\n- ability to lock and unlock animations\n- ability to retain the cursor position between different animations\n\nCurrently Annie's main purpose is to assist with aligning animation groups to each other.\n\n# Say what now? 🤔\nThis description is a bit of a mouthful...\n\u003eanimation groups that consist of 2D sprites with differing uniform dimensions\n\nIf you're having trouble understanding what this means, here's a practical description of the problem Annie tries to solve.\n\nImagine you have a tall sprite:\n![tall](example/images/tall1.png)\n\nAnd a wide sprite:\n![wide](example/images/wide1.png)\n\nFor these sprites, you want to keep the circle aligned to the origin of your main gameobject.\nTo achieve this, you would need to:\n- move the wide sprite to the left at runtime\n- move the tall sprite up at runtime\n\nSince a sprite component's position can't be altered by scripting at runtime, you need to child the sprite component to a gameobject and manipulate that gameoject's position instead.\n\nFor a larger number of animations that don't have the same size, it becomes quickly apparent that if you don't come up with a manageable data structure to source these position offsets from, things might get messy!\n\nAnnie was originally written to solve this exact problem, and it solved the problem so well that I started added more features that take care of common techniques that I found myself using that took advantage of being integrated with Annie's auto-alignment utility!\n\n# Usage\n\n## 1. Installation\n\nTo get started with Annie, assign her install function in the `init()` of a script component.\n\n```lua\nlocal annie = require 'annie.annie'\n\nfunction init(self)\n    self.annie = annie.install([animation_set], [data])\nend\n```\n\nThe Annie instance is returned as a table that has some important values:\n```lua\nannie = {\n    objects = {},\n    sprites = {},\n    animation_set = animation_set \n    current_animation = ''\n    last_animation = ''\n    locked = false\n    linked_timers = {}\n    cursor = 0\n    -- API functions not listed here, but this is where they live\n}\n```\n\nBoth parameters of the install function are optional. Excluding them from your call will disable Annie's auto-alignment utility.\n\nThe `animation_set` parameter is used internally as a key for a value lookup that corresponds to your hardcoded animation data.\nAnnie looks for a value that matches your `animation_set` from a table within Annie's module with the key `'data'`.\nYou can override this internal lookup and provide the Annie instance being installed with it's own *unique* data by using the `data` parameter of the install function.\n\nIn either case, your animation data for Annie should be a table that follows this structure:\n```lua\n{\n    arbitrary_readable_name = { -- this is what Annie looks for with the animation_set parameter\n        animation = { -- this key needs to match the animation_id in the atlas\n            0, --\u003e in pixels, the X value that Annie will set the position of linked objects to (offset)\n            0, --\u003e in pixels, the Y value that Annie will set the position of linked objects to (offset)\n            keep_cursor = {} --\u003e optional field containing strings that match animation_ids in the atlas\n        }\n    },\n\n    another_animation_set = {\n        my_animation = {\n            0,\n            0        \n        },\n        another_animation = {\n            0,\n            0,\n            keep_cursor = {'my_animation'}\n        }\n    },\n\n    -- etc.\n}\n```\nIf an animation doesn't need an offset, you don't need to include it at all. Annie will automatically add an entry for the missing data!\n\n*Important:* This 'automatic data creation' does of course have a small overhead. If you *really* need to remove it, Annie will not break as long as *ALL* animations that are played through Annie are provided in the data.\n\nFor large projects where atlases contain numerous animation groups, or a larger number of distinct objects need to have Annie installed, the following procedure to manage your animation data is recommended:\n\u003eSerialize your animation sets into separate files and either\n\u003e1. write a deserializer that consolidates them into a single table to feed into Annie at runtime\n\u003e2. or require the relevant file in the script installing Annie, then pass the data into the `data` parameter\n\n## 2. Implementing Annie\n\nAnnie tries not to assume what specific skills of hers that you need, and her features are compartmentalized where possible. To make customization of Annie's usage possible, many of her utilities are exposed and it is up to you to implement them. However, there are some higher-level assumptions made to make instance management easy and intuitive.\n\n### 2a. Linking gameobjects and sprites to Annie\n***\nAn installed Annie instance needs to know which gameobjects she should worry about.\nUsually, it makes sense for your main script to be attached to a 'primary' gameobject for which the main positional updates are handled through.\nA 'display' gameobject that is a child to this 'primary' gameobject can then be used to handle the offsets for a sprite with non-uniform animation dimensions.\nThe 'display' gameobject should be linked to Annie.\n\nAn example of this hierarchy:\n\n![hierarchy](docs/typical_setup.png)\n\n*Very important:*\n\u003eCurrently, Annie understands each linked gameobject as only having a single sprite component. Please keep this limitation in mind.\n\nTo link a single gameobject-sprite pair to Annie:\n```lua\nself.annie.link('display', '#my_sprite')\n```\nThe first parameter is a `urlstring` passed to `msg.url()` internally.\nThe second parameter is concatenated to the first parameter, then passed to `msg.url()` internally.\nAnnie stores the returns of these calls in order to know which gameobjects her internal function calls should affect.\nIf no second parameter is provided, `'#sprite'` is used by default.\n\nThere is also an alternative link function that condenses linking multiple gameobject-sprite pairs into a single call.\n\nIf all of your sprites would be represented by the `urlstring` fragment `'#sprite'`:\n```lua\nself.annie.mlink('a','b','c') -- accepts variable number of parameters\n```\nThis will link gameobjects `a`, `b`, and `c` to Annie, assuming they each have a sprite component named `'sprite'`.\n\nTo specify specific sprite names for each gameobject, provide a table:\n```lua\nself.annie.mlink({\n    a = '#some_sprite',\n    b = '#another_sprite',\n    c = '#sprite_again'\n})\n```\n\n### 2b. Customizing the `annie.play()` function\n***\n*Important note:*\n\u003eAnnie cannot track external changes to relevant factors of her functionality by herself. Annie is meant to be an easy and intuitive replacement for common calls (and their surrounding logic) such as:\n\u003e- `msg.post(url, 'play_animation', { id = hash(animation) })`\n\u003e- `sprite.play_flipbook(url, hash(animation))`\n\u003e\n\u003eUsing these calls without informing Annie could introduce hard to find bugs if you treat Annie's utilities as a black box, so try not to use them when you've already got Annie in your script! If you still need to use them, make sure to keep your Annie instance updated.\n\nTo make Annie's replacement of the above calls intuitive and simple, Annie has a predefined dummy function `annie.play()` that is meant to be overwritten to include Annie's utilities on a per-instance basis. The default function uses only Annie's `annie.play_anim()` utility:\n```lua\nfunction annie.play(animation, mode, ...)\n    annie.play_anim(animation, mode, ...)\nend\n```\nA common utility that you may need to add to the function is Annie's ability to flip one or both of the offsets from your animation data.\n\nFor example, if you had an object where you've offset the animation on the X axis and the object can face either left or right. When the object changes the direction it faces, you might need to flip the offsets to keep it aligned to the origin of the parent gameobject by the same factors when mirrored.\n\nTo add this functionality, simply overwrite the `annie.play()` function and add it in after Annie's installation:\n```lua\nlocal annie = require 'annie.annie'\n\nfunction init(self)\n    self.annie = annie.install([animation_set], [data])\n    \n    function self.annie.play(animation, mode, ...)\n        self.annie.play_anim(animation, mode, ...)\n        self.annie.flip_offset([flip_x], [flip_y])\n    end\nend\n```\nSee the API reference for a usage explanation of `.flip_offset()` and more of Annie's utilities.\n\n*Advanced:*\nYou can ignore this dummy function and access the utilities with your own external function wrappers if that makes more sense for your use case (inclusion of utilities based on game state, for example). However, if you do this `annie.lock()` and `annie.unlock()` cannot be used for their secondary function of playing an animation while locking/unlocking the instance since they call `annie.play()` for this, and you will also need to overwrite `annie.animation_done()` if you want to keep this functionality.\n\n## 3. Locking and unlocking the Annie instance\n***\nIt is possible to 'lock' the instance, preventing the internal state from being changed and disabling the ability to play animations altogether:\n```lua\nself.annie.lock()\nself.annie.unlock()\n```\nUseful if you want to integrate the state of the instance into your game logic.\n\nBoth functions will also attempt to call `annie.play()`, useful for keeping your code compact!\n\n## 4. Features around `animation_done`\n***\nTo make use of built-in utilities that involve the `animation_done` message, add `annie.animation_done()` to your script's `on_message()`:\n```lua\nfunction on_message(self, message_id, message, sender)\n    if message_id == hash('animation_done') then\n        self.annie.animation_done()\n    end\nend\n```\n\nSometimes, you may want to link timers to your animation to cancel them when the animation is finished. Annie provides a place to store timer handles and a method to cancel them through the instance, which could make it easier to manage and keep track of depending on your use case:\n```lua\nlocal my_timer = timer.delay(1, false, function() print('I\\'m a linked timer!') end)\nself.annie.add_linked_timer(my_timer)\n```\n\nBy default, `annie.animation_done()` also unlocks the instance.\nEither behaviour can be removed or replaced by overwriting the function.\n\n# API\nThe API is used by calling functions from the installed Annie instance.\n***\n## `annie.link(urlstring, sprite_name)`\nLinks a gameobject-sprite pair to the instance.\n### PARAMETERS\n- `urlstring` - (string) passed to `msg.url()` internally. [See Defold API](https://defold.com/ref/stable/msg/?q=msg.url#msg.url:urlstring).\n- `sprite_name` - `optional` (string) the [fragment] part of `urlstring` that corresponds to the gameobject's sprite. Uses `'#sprite` if none are provided.\n### RETURN\n- `object` - (url) the return of the `msg.url()` call provided with the gameobject urlstring\n- `sprite` - (url) the return of the `msg.url()` call provided with the sprite component urlstring\n***\n## `annie.mlink(...)`\nLinks any number of gameobject-sprite pairs to the instance, using sprite name `'sprite'`.\n### PARAMETERS\n- `...` - urlstrings passed to `msg.url()` internally. [See Defold API](https://defold.com/ref/stable/msg/?q=msg.url#msg.url:urlstring).\n### RETURN\n- (table) containing an array of only the gameobject-sprite components linked by this specific call represented as 2 tables of equal length starting with index 1:\n```lua\n{\n    objects = {\n        1 = url: [socket:/path]\n    },\n    sprites = {\n        1 = url: [socket:/path#fragment]\n    }\n}\n```\n***\n## `annie.mlink(t)`\nLinks gameobject-sprite pairs to the instance using table keys as the urlstring and values as the sprite name. Calls `annie.link()` internally to accomplish this.\n### PARAMETERS\n- `t` - (table) with keys as the [path] of the urlstring, values as the [fragment]: `{path = fragment}`\n### RETURN\n- (table) containing an array of only the gameobject-sprite components linked by this specific call represented as 2 tables of equal length starting with index 1:\n```lua\n{\n    objects = {\n        1 = url: [socket:/path]\n    },\n    sprites = {\n        1 = url: [socket:/path#fragment]\n    }\n}\n```\n***\n## `annie.play_anim(animation, mode, ...)`\nPlays an animation.\nAttempts to index provided animation data for offsets to use.\nVariable utility by passing `mode`.\n### PARAMETERS\n- `animation` - (string) the animation_id to use internally with `msg.post()`. [See Defold API](https://defold.com/ref/stable/msg/?q=msg.post#msg.post:receiver-message_id-[message])\n- `mode` - `optional` (hash):\n    - `hash('keep_cursor')` - keeps the cursor position if the last animation matches any of the parameters passed with `...` or from the provided animation data if there are none passed\n    - `hash('force_replay')` - forces the animation message to be sent even if the current animation matches the animation you are trying to play\n- `...` - `optional` additional parameters used by logic determined by `mode`\n***\n## `annie.offset(x, y)`\nSets the position of each linked gameobject with `go.set_position()` [See Defold API](https://defold.com/ref/stable/go/?q=go.set_position#go.set_position:position-[id])\n### PARAMETERS\n- `x` - (number) the X position to set\n- `y` - (number) the Y position to set\n***\n## `annie.offset(t)`\nSets the position of each linked gameobject with `go.set_position()` [See Defold API](https://defold.com/ref/stable/go/?q=go.set_position#go.set_position:position-[id])\n### PARAMETERS\n- `t` - (table) the X and Y positions to set: `{x,y}`\n***\n## `annie.set_cursor(cursor)`\nSets the cursor position of each linked sprite using `go.animate()` [See Defold API](https://defold.com/ref/stable/go/?q=go.animate#go.animate:url-property-playback-to-easing-duration-[delay]-[complete_function])\n### PARAMETERS\n- `cursor` - (number) cursor position to set (must be \u003e=0 \u0026 \u003c=1)\n***\n## `annie.flip_offset(flip_x, flip_y)`\nMultiplies the affected value in the position vector3 of linked gameobjects by -1 and sets their positions. This is done *after* the initial auto-alignment from `annie.play_anim()`, so unless your animation data changes the output will always be the same! In other words, you should be tracking the flip state in your game logic, NOT with Annie!\nDisabled when the instance is locked.\n### PARAMETERS\n- `flip_x` - (boolean) true to flip the X offset\n- `flip_y` - (boolean) true to flip the Y offset\n***\n## `annie.lock(animation, mode, ...)`\nAttempts to call `annie.play()` if an animation is provided, then locks the instance.\n### PARAMETERS\nSee `annie.play_anim()`\n***\n## `annie.unlock(animation, mode, ...)`\nUnlocks the instance, then attempts to call `annie.play()` if an animation is provided.\n### PARAMETERS\nSee `annie.play_anim()`\n\n# Author\nrocamocha\n\n# License\nMIT\n\n# To-Do\n\n- Improve internal functions to allow Annie to affect gameobjects with multiple sprite components.\n- Add functionality for a separate alignment relative to the grandparent gameobject.\n- Decouple the alignment structure and expose it in a way that makes advanced usage intuitive.\n- Make the animation cursor usable through Annie's public access. (She doesn't have an `update()` right now so the `annie.cursor` value is only used internally for the `keep_cursor` animation mode).\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frocamocha%2Fdefold-annie","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frocamocha%2Fdefold-annie","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frocamocha%2Fdefold-annie/lists"}