diff --git a/.vscode/settings.json b/.vscode/settings.json index cc97795..e920013 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,6 +11,13 @@ "timer", "pprint", "json", - "go" + "go", + "vmath", + "collectionfactory", + "label", + "sprite", + "update", + "on_message", + "final" ] } diff --git a/API_REFERENCE.md b/API_REFERENCE.md new file mode 100644 index 0000000..17402b4 --- /dev/null +++ b/API_REFERENCE.md @@ -0,0 +1,429 @@ +# API Reference + +## Table of Contents + +- [Setup and Initialization](#setup-and-initialization) +- [Animation Functions](#animation-functions) + - [panthera.create_gui](#pantheracreate_gui) + - [panthera.create_go](#pantheracreate_go) + - [panthera.create](#pantheracreate) + - [panthera.clone_state](#pantheraclone_state) + - [panthera.play](#pantheraplay) + - [panthera.stop](#pantherastop) + - [panthera.reload_animation](#pantherareload_animation) + - [panthera.set_time](#pantheraset_time) + - [panthera.get_time](#pantheraget_time) + - [panthera.get_duration](#pantheraget_duration) + - [panthera.is_playing](#pantherais_playing) + - [panthera.get_latest_animation_id](#pantheraget_latest_animation_id) +- [Configuration Functions](#configuration-functions) + - [panthera.set_logger](#pantheraset_logger) + +## Setup and Initialization + +To utilize Panthera Runtime in your Defold project for playing **Panthera 2.0** animations, start by importing the Panthera Runtime module: + +```lua +local panthera = require("panthera.panthera") +``` + +## Animation Functions + +**panthera.create_gui** +--- +Load and create a GUI animation state from a JSON file or Lua table. + +The Panthera uses `sys.load_resource` to load the JSON animation file. Place your animation files inside your [custom resources folder](https://defold.com/manuals/project-settings/#custom-resources) to ensure they are included in the build. + +```lua +panthera.create_gui(animation_path_or_data, [template], [nodes]) +``` + +- **Parameters:** + - `animation_path_or_data`: The path to the animation JSON file or a table with animation data. Example: `/animations/my_gui_animation.json`. + - `template` (optional): The GUI template id to load nodes from. Pass nil if no template is used. + - `nodes` (optional): Table with nodes from `gui.clone_tree()` function. Pass nil if nodes are not cloned. + +- **Returns:** An animation state object or `nil` if the animation cannot be loaded. + +- **Usage Example:** + +```lua +local PATH = "/animations/my_gui_animation.json" + +-- Create over GUI on current scene +local gui_animation = panthera.create_gui(PATH) + +-- Create over GUI template on current scene +local gui_animation = panthera.create_gui(PATH, "template_name") + +-- Create over cloned GUI nodes +local nodes = gui.clone_tree(gui.get_node("root")) +local gui_animation = panthera.create_gui(PATH, nil, nodes) + +-- Create over cloned GUI template +local nodes = gui.clone_tree(gui.get_node("template_name/root")) +local gui_animation = panthera.create_gui(PATH, "template_name", nodes) +``` + +```lua +-- Using Lua table with animation data +local animation_data = require("animations.my_animation_data") +local gui_animation = panthera.create_gui(animation_data) +``` + + +**panthera.create_go** +--- +Load and create a game object (GO) animation state from a JSON file or Lua table. + +The Panthera uses `sys.load_resource` to load the JSON animation file. Place your animation files inside your [custom resources folder](https://defold.com/manuals/project-settings/#custom-resources) to ensure they are included in the build. + +```lua +panthera.create_go(animation_path_or_data, [collection_name], [objects]) +``` + +- **Parameters:** + - `animation_path_or_data`: The path to the animation JSON file or a table with animation data. Example: `/animations/my_animation.json`. + - `collection_name` (optional): The name of the collection to load objects from. Pass `nil` if no collection is used. + - `objects` (optional): Table with object ids from collectionfactory.create() function. Pass `nil` if objects are not created. + +- **Returns:** An animation state object or `nil` if the animation cannot be loaded. + +- **Usage Example:** + +```lua +local PATH = "/animations/my_animation.json" + +-- Create over objects on current scene +local go_animation = panthera.create_go(PATH) + +-- Create over collection on current scene +local go_animation = panthera.create_go(PATH, "collection_name") + +-- Create over Game Object from spawned factory +-- You should create a table with mapping object to created instance. +-- Instead "/pantera" use object id from animation +local object = factory.create("#factory") +local go_animation = panthera.create_go(PATH, nil, { [hash("/panthera")] = object }) + +-- Create over objects from spawned collectionfactory +local objects = collectionfactory.create("#collectionfactory") +local go_animation = panthera.create_go(PATH, nil, objects) + +-- Create over objects from collectionfactory inside spawned collection +local objects = collectionfactory.create("#collectionfactory") +local go_animation = panthera.create_go(PATH, "collection_name", objects) +``` + +```lua +-- Using Lua table with animation data +local animation_data = require("animations.my_animation_data") +local go_animation = panthera.create_go(animation_data) +``` + + +**panthera.create** +--- +Load an animation from a JSON file or Lua table and create an animation state using a specified adapter. This is a foundational method that `create_go` and `create_gui` build upon, allowing for generic animation creation with custom adapters. + +The Panthera uses `sys.load_resource` to load the JSON animation file. Place your animation files inside your [custom resources folder](https://defold.com/manuals/project-settings/#custom-resources) to ensure they are included in the build. + +```lua +panthera.create(animation_path_or_data, adapter, get_node) +``` + +- **Parameters:** + - `animation_path_or_data`: The path to the animation JSON file or a table with animation data. Example: `/animations/my_animation.json`. + - `adapter`: An adapter object that specifies how Panthera Runtime interacts with Engine. + - `get_node`: A custom function to resolve nodes by their ID. This function is used by the adapter to retrieve Defold nodes for animation. + +- **Returns:** An animation state object. This object contains the loaded animation data and state necessary for playback. Returns `nil` and an error message if the animation cannot be loaded. + +- **Usage Example:** + +```lua +-- For GO animations +local adapter_go = require("panthera.adapters.adapter_go") +local go_animation_state = panthera.create("/animations/player_animation.json", adapter_go) + +-- For GUI animations +local adapter_gui = require("panthera.adapters.adapter_gui") +local gui_animation_state = panthera.create("/animations/gui_animation.json", adapter_gui) +``` + +```lua +-- Using Lua table with animation data +local adapter = require("panthera.adapters.adapter_go") +local animation_data = require("animations.my_animation_data") +local go_animation_state = panthera.create(animation_data, adapter) +``` + +This method is essential for advanced users who need to implement custom animation logic or integrate Panthera animations with non-standard Defold components. It provides the flexibility to work directly with the underlying adapters, enabling a wide range of animation functionalities. Read about Panthera adapters in the [adapter documentation](docs/panthera_adapter.md). + + +**panthera.clone_state** +--- +Clone an existing animation state object, enabling multiple instances of the same animation to play simultaneously or independently. + +```lua +panthera.clone_state(animation_state) +``` + +- **Parameters:** + - `animation_state`: The animation state object to clone. + +- **Returns:** A new animation state object that is a copy of the original. + +- **Usage Example:** + +```lua +local go_animation_state = panthera.create_go("/animations/player_animation.json") +local cloned_state = panthera.clone_state(go_animation_state) +``` + + +**panthera.play** +--- +Play an animation with specified ID and options. + +```lua +panthera.play(animation_state, animation_id, [options]) +``` + +- **Parameters:** + - `animation_state`: The animation state object returned by `create_go` or `create_gui`. + - `animation_id`: A string identifier for the animation to play. + - `options` (optional): A table of playback options, as described in the [Animation Playback Options](#animation-playback-options) section. + +- **Usage Example:** + +```lua +panthera.play(go_animation_state, "walk", { is_loop = true, speed = 1 }) +``` + +### Animation Playback Options + +Customize animation behavior in Panthera Runtime using a table of options passed to `panthera.play`. + +**Options:** + +- **`is_loop`**: Loop the animation (`true`/`false`). Triggers the callback at each loop end if set to `true`. +- **`is_skip_init`**: Start animation from its current state, skipping initial setup (`true`/`false`). +- **`is_relative`**: Apply tween values relative to the object's current state (`true`/`false`). +- **`speed`**: Playback speed multiplier (default `1`). Values >1 increase speed, <1 decrease. +- **`callback`**: Function called when the animation finishes. Receives `animation_id`. +- **`callback_event`**: Function triggered by animation events. Receives `event_id`, optional `node`, `string_value`, and `number_value`. + +- **Usage Example:** + +```lua +local options = { + is_loop = true, + speed = 1.2, + callback = function(animation_id) + print("Finished animation: " .. animation_id) + end, + callback_event = function(event_id, node, string_value, number_value) + print("Event: " .. event_id) + end +} + +panthera.play(animation_state, "animation_id", options) +``` + +These options enable precise control over animation playback, enhancing interactivity and visual dynamics in your game projects. + +**panthera.stop** +--- +Stop a currently playing animation. + +```lua +panthera.stop(animation_state) +``` + +- **Parameters:** + - `animation_state`: The animation state object to stop. + +- **Usage Example:** + +```lua +panthera.stop(go_animation_state) +``` + +**panthera.reload_animation** +--- +Reload animations from JSON files, useful for development and debugging. If no path is provided, all loaded animations are reloaded. + +The animations loaded from Lua tables will not be reloaded. + +```lua +panthera.reload_animation([animation_path]) +``` + +- **Parameters:** + - `animation_path` (optional): Specific animation to reload. If omitted, all loaded animations are reloaded. + +- **Usage Example:** + +```lua +-- Reload single animation +panthera.reload_animation("/animations/my_animation.json") + +-- Reload all loaded animations +panthera.reload_animation() +``` + +**panthera.set_time** +--- +Directly set the current playback time of an animation, useful for seeking to a specific point or synchronizing animations. + +```lua +panthera.set_time(animation_state, animation_id, time) +``` + +- **Parameters:** + - `animation_state`: The animation state object returned by `create_go` or `create_gui`. + - `animation_id`: The ID of the animation to modify. + - `time`: The target time in seconds to which the animation should be set. + +- **Usage Example:** + +```lua +-- Set the animation to start playing from 2 seconds in +panthera.set_time(self.go_animation, "run", 2) +``` + +This function stops any currently playing animation and updates the animation state to the specified time, allowing for immediate playback from that point or preparation for a triggered start. + + +**panthera.get_time** +--- +Retrieve the current playback time of an animation, useful for tracking the animation's progress or synchronizing game events. If the animation is not playing, the function returns 0. + +```lua +local time = panthera.get_time(animation_state) +``` + +- **Parameters:** + - `animation_state`: The animation state object. + +- **Returns:** The current playback time of the animation in seconds. + +- **Usage Example:** + +```lua +local time = panthera.get_time(self.go_animation) +print("Current animation time: ", time, "seconds") +``` + +**panthera.get_duration** +--- +Retrieve the total duration of a specific animation, enabling dynamic timing decisions or UI updates based on animation length. + +```lua +local duration = panthera.get_duration(animation_state, animation_id) +``` + +- **Parameters:** + - `animation_state`: The animation state object. + - `animation_id`: The ID of the animation whose duration you want to retrieve. + +- **Returns:** The total duration of the animation in seconds. + +- **Usage Example:** + +```lua +local duration = panthera.get_duration(self.go_animation, "run") +print("Total animation duration: ", duration, "seconds") +``` + +Knowing the duration of an animation is particularly useful for scheduling other events or actions to occur immediately after an animation completes, ensuring smooth transitions and cohesive gameplay experiences. + +**panthera.is_playing** +--- +Check if an animation is currently playing. + +```lua +local is_playing = panthera.is_playing(animation_state) +``` + +- **Parameters:** + - `animation_state`: The animation state object. + +- **Returns:** `true` if the animation is currently playing, `false` otherwise. + +- **Usage Example:** + +```lua +local is_playing = panthera.is_playing(self.go_animation) +if is_playing then + print("The animation is currently playing") +end +``` + +This function is useful for determining whether an animation is active and can be used to trigger other game events or actions based on the animation's state. + + +**panthera.get_latest_animation_id** +--- +Check the ID of the last animation that was started. + +```lua +local animation_id = panthera.get_latest_animation_id(animation_state) +``` + +- **Parameters:** + - `animation_state`: The animation state object. + +- **Returns:** The ID of the last animation that was started. + +- **Usage Example:** + +```lua +local animation_id = panthera.get_latest_animation_id(self.go_animation) +print("Latest started animation ID: ", animation_id) +``` + +This function is useful for tracking the last animation that was started, allowing for dynamic behavior based on the most recent animation played. + + +## Configuration Functions + +**panthera.set_logger** +--- +Customize the logging mechanism used by **Panthera Runtime**. You can use **Defold Log** library or provide a custom logger. + +```lua +panthera.set_logger(logger_instance) +``` + +- **Parameters:** + - `logger_instance`: A logger object that follows the specified logging interface, including methods for `trace`, `debug`, `info`, `warn`, `error`. Pass `nil` to remove the default logger. + +- **Usage Example:** + +Using the [Defold Log](https://github.com/Insality/defold-log) module: +```lua +local log = require("log.log") +local panthera = require("panthera.panthera") + +panthera.set_logger(log.get_logger("panthera")) +``` + +Creating a custom user logger: +```lua +local logger = { + trace = function(_, message, context) end, + debug = function(_, message, context) end, + info = function(_, message, context) end, + warn = function(_, message, context) end, + error = function(_, message, context) end +} +panthera.set_logger(logger) +``` + +Remove the default logger: +```lua +panthera.set_logger(nil) +``` diff --git a/CHANGELOG.md b/CHANGELOG.md index a5164e5..59f3f0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,3 +18,19 @@ Initial resease! - The default sprite color property is `tint`. To use panthera color property, use `/panthera/materials/sprite.material` or any other with `color` property. - GUI: fix slice9 property. - GUI: fix for pie vertices and piebounds properties. + + +## Version v3 + +- **BREAKING CHANGES**: Change `panthera.create_go` and `panthera.create_gui` functions signature. + - Now `panthera.create_go(animation_path_or_data, [collection_name], [objects])` instead of `panthera.create_go(animation_path, [get_node)`. + - Now `panthera.create_gui(animation_path_or_data, [template_name], [nodes])` instead of `panthera.create_gui(animation_path, [get_node])`. + - Animation now can be created with a table with animation data directly. You can load JSON by yourself or load it from lua table. + - Instead of `get_node` parameter, you should pass template/collection name and nodes/objects table. Nodes are created from `gui.clone_tree`, objects are created from `collectionfactory.create`. + - To migrate to new version, you should modify your code to the new signature, or create animations with `panthera.create(animation_path_or_data, adapter, get_node)` function. Adapter can be obtained by `require("panthera.adapters.adapter_[go|gui]")`. +- Add more examples with playing animations in different scenarios. +- Update documentation. +- The `animation_state` table now contains `animation_id`, instead of `animation` table data. It will be better to log or `pprint` the animation state. +- Rename file `panthera_system` to `panthera_internal`. +- Add support for `is_editor_only` timeline key property +- Add support for `easing_custom` timeline key property \ No newline at end of file diff --git a/README.md b/README.md index 0085a25..8cded56 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ - **Seamless Animation Integration**: Import and use Panthera 2.0 animations directly in Defold. - **Full Animation Support**: Supports all animation features provided by Panthera 2.0, including events, animation blending, nested animations and more. -- **Flexible Usage**: Compatible with both game objects and GUI nodes in Defold, allowing for versatile application across different game elements. +- **Flexible Usage**: Compatible with both Game Objects and GUI nodes in Defold, allowing for versatile application across different game elements. - **Animation Cursor**: Provides a way to control animation manually, allowing for precise control over playback and synchronization with game events. - **Hot Reloading**: Reload animations on the fly during development, enabling rapid iteration and testing of animation assets. @@ -30,13 +30,13 @@ Open your `game.project` file and add the following lines to the dependencies fi **[Defold Tweener](https://github.com/Insality/defold-tweener)** ``` -https://github.com/Insality/defold-tweener/archive/refs/tags/2.zip +https://github.com/Insality/defold-tweener/archive/refs/tags/3.zip ``` **[Panthera Runtime](https://github.com/Insality/panthera)** ``` -https://github.com/Insality/panthera/archive/refs/tags/runtime.2.zip +https://github.com/Insality/panthera/archive/refs/tags/runtime.3.zip ``` After that, select `Project ▸ Fetch Libraries` to update [library dependencies]((https://defold.com/manuals/libraries/#setting-up-library-dependencies)). This happens automatically whenever you open a project so you will only need to do this if the dependencies change without re-opening the project. @@ -81,7 +81,7 @@ window.set_listener(function(_, event) end) ``` -> **Note:** Hot reloading is designed for use in development environments only. +> **Note:** Hot reloading is designed for use in development environments only. Hot reloading only works for animations from JSON files. If you using a lua table for animations, hot reloading will not work. ## API Reference @@ -89,9 +89,9 @@ end) ### Quick API Reference ```lua -panthera.create_gui(animation_path, [get_node]) -panthera.create_go(animation_path, [get_node]) -panthera.create(animation_path, adapter, [get_node]) +panthera.create_gui(animation_path_or_data, [template], [nodes]) +panthera.create_go(animation_path_or_data, [collection_name], [objects]) +panthera.create(animation_path_or_data, adapter, get_node) panthera.clone_state(animation_state) panthera.play(animation_state, animation_id, [options]) panthera.stop(animation_state) @@ -104,383 +104,11 @@ panthera.set_logger([logger_instance]) panthera.reload_animation([animation_path]) ``` -### Setup and Initialization -To utilize Panthera Runtime in your Defold project for playing **Panthera 2.0** animations, start by importing the Panthera Runtime module: +### API Reference -```lua -local panthera = require("panthera.panthera") -``` - -### Configuration Functions - -**panthera.set_logger** ---- -Customize the logging mechanism used by Panthera Runtime. You can use **Defold Log** library or provide a custom logger. - -```lua -panthera.set_logger(logger_instance) -``` - -- **Parameters:** - - `logger_instance`: A logger object that follows the specified logging interface, including methods for `trace`, `debug`, `info`, `warn`, `error`. Pass `nil` to remove the default logger. - -- **Usage Example:** - -Using the [Defold Log](https://github.com/Insality/defold-log) module: -```lua -local log = require("log.log") -local panthera = require("panthera.panthera") - -panthera.set_logger(log.get_logger("panthera")) -``` - -Creating a custom user logger: -```lua -local logger = { - trace = function(_, message, context) end, - debug = function(_, message, context) end, - info = function(_, message, context) end, - warn = function(_, message, context) end, - error = function(_, message, context) end -} -panthera.set_logger(logger) -``` - -Remove the default logger: -```lua -panthera.set_logger(nil) -``` - -### Animation Functions - -**panthera.create_gui** ---- -Load and create a GUI animation state from a JSON file. - -The Panthera uses `sys.load_resource` to load the animation file. Place your animation files inside your [custom resources folder](https://defold.com/manuals/project-settings/#custom-resources) to ensure they are included in the build. - -```lua -panthera.create_gui(animation_path, [get_node]) -``` - -- **Parameters:** - - `animation_path`: The path to the animation JSON file. Example: `/animations/my_gui_animation.json`. - - `get_node` (optional): A function to resolve nodes by their ID within the GUI. If not provided, the adapter uses its default method from adapter implementation. - -- **Returns:** An animation state object or `nil` if the animation cannot be loaded. - -- **Usage Example:** - -```lua -local gui_animation = panthera.create_gui("/animations/my_gui_animation.json") -``` - -```lua -local gui_animation = panthera.create_gui("/animations/my_gui_animation.json", function(node_id) - -- If we inside a Druid's component, we can use this function to get a node by its ID instead of gui.get_node(node_id) - return self:get_node(node_id) -end -``` - -**panthera.create_go** ---- -Load and create a game object (GO) animation state from a JSON file. - -The Panthera uses `sys.load_resource` to load the animation file. Place your animation files inside your [custom resources folder](https://defold.com/manuals/project-settings/#custom-resources) to ensure they are included in the build. - -```lua -panthera.create_go(animation_path, [get_node]) -``` - -- **Parameters:** - - `animation_path`: The path to the animation JSON file. Example: `/animations/my_animation.json`. - - `get_node` (optional): A function to resolve nodes by their ID within the GO. If not provided, the adapter uses its default method from adapter implementation. - -- **Returns:** An animation state object or `nil` if the animation cannot be loaded. - -- **Usage Example:** - -```lua -local go_animation = panthera.create_go("/animations/my_animation.json") -``` - -```lua -local go_animation = panthera.create_go("/animations/my_animation.json", function(node_id) - -- This is the default get_node function in adapter_go. - -- The node_id for object components should be in the format "object_id#component_id", example: "player#sprite" - if string.find(node_id, "#") then - local object_id = string.sub(node_id, 1, string.find(node_id, "#") - 1) - local fragment_id = string.sub(node_id, string.find(node_id, "#") + 1) - - local object_url = msg.url(hash("/" .. object_id)) - object_url.fragment = fragment_id - - return object_url - end - - return hash("/" .. node_id) -end -``` - -**panthera.create** ---- -Load an animation from a JSON file and create an animation state using a specified adapter. This is a foundational method that `create_go` and `create_gui` build upon, allowing for generic animation creation with custom adapters. - -The Panthera uses `sys.load_resource` to load the animation file. Place your animation files inside your [custom resources folder](https://defold.com/manuals/project-settings/#custom-resources) to ensure they are included in the build. - -```lua -panthera.create(animation_path, adapter, [get_node]) -``` - -- **Parameters:** - - `animation_path`: The path to the animation JSON file. - - `adapter`: An adapter object that specifies how Panthera Runtime interacts with Engine. - - `get_node` (optional): A custom function to resolve nodes by their ID. This function is used by the adapter to retrieve Defold nodes for animation. If not provided, the adapter uses its default method from adapter implementation. - -- **Returns:** An animation state object. This object contains the loaded animation data and state necessary for playback. Returns `nil` and an error message if the animation cannot be loaded. - -- **Usage Example:** - -```lua --- For GO animations -local adapter_go = require("panthera.adapters.adapter_go") -local go_animation_state = panthera.create("/animations/player_animation.json", adapter_go) - --- For GUI animations -local adapter_gui = require("panthera.adapters.adapter_gui") -local gui_animation_state = panthera.create("/animations/gui_animation.json", adapter_gui) -``` - -This method is essential for advanced users who need to implement custom animation logic or integrate Panthera animations with non-standard Defold components. It provides the flexibility to work directly with the underlying adapters, enabling a wide range of animation functionalities. Read about Panthera adapters in the [adapter documentation](docs/panthera_adapter.md). - -**panthera.clone_state** ---- -Clone an existing animation state object, enabling multiple instances of the same animation to play simultaneously or independently. - -```lua -panthera.clone_state(animation_state) -``` - -- **Parameters:** - - `animation_state`: The animation state object to clone. - -- **Returns:** A new animation state object that is a copy of the original. - -- **Usage Example:** - -```lua -local go_animation_state = panthera.create_go("/animations/player_animation.json") -local cloned_state = panthera.clone_state(go_animation_state) -``` - - -**panthera.play** ---- -Play an animation with specified ID and options. - -```lua -panthera.play(animation_state, animation_id, [options]) -``` - -- **Parameters:** - - `animation_state`: The animation state object returned by `create_go` or `create_gui`. - - `animation_id`: A string identifier for the animation to play. - - `options` (optional): A table of playback options, as described in the [Animation Playback Options](#animation-playback-options) section. - -- **Usage Example:** - -```lua -panthera.play(go_animation_state, "walk", { is_loop = true, speed = 1 }) -``` - -#### Animation Playback Options +Read the [API Reference](API_REFERENCE.md) file to see the full API documentation for the module. -Customize animation behavior in Panthera Runtime using a table of options passed to `panthera.play`. - -**Options:** - -- **`is_loop`**: Loop the animation (`true`/`false`). Triggers the callback at each loop end if set to `true`. -- **`is_skip_init`**: Start animation from its current state, skipping initial setup (`true`/`false`). -- **`is_relative`**: Apply tween values relative to the object's current state (`true`/`false`). -- **`speed`**: Playback speed multiplier (default `1`). Values >1 increase speed, <1 decrease. -- **`callback`**: Function called when the animation finishes. Receives `animation_id`. -- **`callback_event`**: Function triggered by animation events. Receives `event_id`, optional `node`, `string_value`, and `number_value`. - -- **Usage Example:** - -```lua -local options = { - is_loop = true, - speed = 1.2, - callback = function(animation_id) - print("Finished animation: " .. animation_id) - end, - callback_event = function(event_id, node, string_value, number_value) - print("Event: " .. event_id) - end -} - -panthera.play(animation_state, "animation_id", options) -``` - -These options enable precise control over animation playback, enhancing interactivity and visual dynamics in your game projects. - -**panthera.stop** ---- -Stop a currently playing animation. - -```lua -panthera.stop(animation_state) -``` - -- **Parameters:** - - `animation_state`: The animation state object to stop. - -- **Usage Example:** - -```lua -panthera.stop(go_animation_state) -``` - -**panthera.reload_animation** ---- -Reload animations from JSON files, useful for development and debugging. - -```lua -panthera.reload_animation([animation_path]) -``` - -- **Parameters:** - - `animation_path` (optional): Specific animation to reload. If omitted, all loaded animations are reloaded. - -- **Usage Example:** - -```lua --- Reload single animation -panthera.reload_animation("/animations/my_animation.json") - --- Reload all loaded animations -panthera.reload_animation() -``` - -**panthera.set_time** ---- -Directly set the current playback time of an animation, useful for seeking to a specific point or synchronizing animations. - -```lua -panthera.set_time(animation_state, animation_id, time) -``` - -- **Parameters:** - - `animation_state`: The animation state object returned by `create_go` or `create_gui`. - - `animation_id`: The ID of the animation to modify. - - `time`: The target time in seconds to which the animation should be set. - -- **Usage Example:** - -```lua --- Set the animation to start playing from 2 seconds in -panthera.set_time(self.go_animation, "run", 2) -``` - -This function stops any currently playing animation and updates the animation state to the specified time, allowing for immediate playback from that point or preparation for a triggered start. - - -**panthera.get_time** ---- -Retrieve the current playback time of an animation, useful for tracking the animation's progress or synchronizing game events. If the animation is not playing, the function returns 0. - -```lua -local time = panthera.get_time(animation_state) -``` - -- **Parameters:** - - `animation_state`: The animation state object. - -- **Returns:** The current playback time of the animation in seconds. - -- **Usage Example:** - -```lua -local time = panthera.get_time(self.go_animation) -print("Current animation time: ", time, "seconds") -``` - -**panthera.get_duration** ---- -Retrieve the total duration of a specific animation, enabling dynamic timing decisions or UI updates based on animation length. - -```lua -local duration = panthera.get_duration(animation_state, animation_id) -``` - -- **Parameters:** - - `animation_state`: The animation state object. - - `animation_id`: The ID of the animation whose duration you want to retrieve. - -- **Returns:** The total duration of the animation in seconds. - -- **Usage Example:** - -```lua -local duration = panthera.get_duration(self.go_animation, "run") -print("Total animation duration: ", duration, "seconds") -``` - -Knowing the duration of an animation is particularly useful for scheduling other events or actions to occur immediately after an animation completes, ensuring smooth transitions and cohesive gameplay experiences. - -**panthera.is_playing** ---- -Check if an animation is currently playing. - -```lua -local is_playing = panthera.is_playing(animation_state) -``` - -- **Parameters:** - - `animation_state`: The animation state object. - -- **Returns:** `true` if the animation is currently playing, `false` otherwise. - -- **Usage Example:** - -```lua -local is_playing = panthera.is_playing(self.go_animation) -if is_playing then - print("The animation is currently playing") -end -``` - -This function is useful for determining whether an animation is active and can be used to trigger other game events or actions based on the animation's state. - - -**panthera.get_latest_animation_id** ---- -Check the ID of the last animation that was started. - -```lua -local animation_id = panthera.get_latest_animation_id(animation_state) -``` - - -- **Parameters:** - - `animation_state`: The animation state object. - -- **Returns:** The ID of the last animation that was started. - -- **Usage Example:** - -```lua -local animation_id = panthera.get_latest_animation_id(self.go_animation) -print("Latest started animation ID: ", animation_id) -``` - -This function is useful for tracking the last animation that was started, allowing for dynamic behavior based on the most recent animation played. - ---- - -These functions provide a comprehensive interface for integrating and controlling Panthera 2.0 animations within Defold projects, enhancing the visual fidelity and interactivity of your games. ### Usage Examples @@ -571,6 +199,7 @@ Panthera Runtime is licensed under the MIT License - see the [LICENSE](/LICENSE) For any issues, questions, or suggestions, please [create an issue](https://github.com/Insality/panthera/issues). + ## Changelog Read the [CHANGELOG](/CHANGELOG.md) to learn about the latest updates and features in Panthera Runtime. diff --git a/docs/panthera_adapter.md b/docs/panthera_adapter.md index 175bf41..124115f 100644 --- a/docs/panthera_adapter.md +++ b/docs/panthera_adapter.md @@ -6,9 +6,9 @@ In Panthera Runtime for Defold, an adapter is a crucial component that bridges P An adapter takes care of several key tasks: -- **Node Retrieval**: Finds the specific game or GUI node in Defold that an animation should be applied to. - **Property Animation**: Applies animations to properties of nodes, such as position, rotation, or scale, using Defold's animation system. - **Event Handling**: Triggers custom actions defined in Panthera animations, like playing a sound or changing a scene. +- **Easing Functions**: Maps Panthera easing functions to Defold's easing types for smooth transitions. #### Example Adapter @@ -16,10 +16,6 @@ Here’s a simple adapter example for Defold GUI: ```lua local M = { - get_node = function(node_id) - -- Retrieves a Defold node by its ID. - return gui.get_node(node_id) - end, get_easing = function(easing_name) -- Returns a Defold easing type. This example always returns LINEAR easing. -- You might want to map Panthera easing names to Defold's easing constants. @@ -55,7 +51,9 @@ To use your custom adapter with Panthera Runtime, simply pass it when creating a ```lua local my_custom_adapter = require("path.to.my_custom_adapter") -local animation = panthera.create("/path/to/animation.json", my_custom_adapter) +local animation = panthera.create("/path/to/animation.json", my_custom_adapter, function(node_id) + return gui.get_node(node_id) +end) ``` This flexibility allows Panthera Runtime to work seamlessly with any part of Defold, ensuring that you can bring any animation from Panthera 2.0 into your game with precision and ease. diff --git a/docs_editor/README.md b/docs_editor/README.md index 3608f5c..da5a6c2 100644 --- a/docs_editor/README.md +++ b/docs_editor/README.md @@ -8,18 +8,18 @@ **Panthera 2.0 Editor** is a cross-platform animation software solution developed using the [Defold](https://defold.com/) engine, with the goal of streamlining the process of designing layouts and animations for game projects. -Integrated tightly with Defold, Panthera allows the import of `.gui` files, which can then be animated directly within the editor interface. This tool offers support for various animation techniques, including tweening, triggering, events, and animation keys, allowing users to create complex animations effortlessly. +Integrated tightly with Defold, Panthera allows the import of **Defold** files, which can then be animated directly within the editor interface. This tool offers support for various animation techniques, including tweening, triggering, events, and animation keys, allowing users to create complex animations effortlessly. -**Panthera 2.0 Editor** uses a simple animation [JSON format](/docs_editor/animation_data_format.md) for animation files. If you are not using Defold, the animation runtime can be easily implemented in any game engine or framework. +**Panthera 2.0 Editor** uses a simple [animation format](/docs_editor/animation_data_format.md) for animation files. If you are not using Defold, the animation runtime can be easily implemented in any game engine or framework. ## Features - **Intuitive Layout Creation**: Design and arrange your scene elements effortlessly. - **Advanced Animation Tools**: Craft smooth and complex animations to bring your game to life. -- **Simple Animation Format**: Export animations in a simple [JSON format](/docs_editor/animation_data_format.md) for easy integration into your projects. +- **Simple Animation Format**: Export animations in a simple [JSON format](/docs_editor/animation_data_format.md) or as Lua table for easy integration into your projects. - **Compact Size**: Lightweight and easy to install, with no additional dependencies required. -- **Defold Tight Integration**: Seamlessly import **Defold** `*.gui` files for direct animation within the editor. +- **Defold Tight Integration**: Seamlessly import **Defold** `*.gui`, `*.go` and `*.collection` files for direct animation within the editor. ## Animation Editor Features diff --git a/docs_editor/getting_started.md b/docs_editor/getting_started.md index 7ec759f..b671dcc 100644 --- a/docs_editor/getting_started.md +++ b/docs_editor/getting_started.md @@ -43,13 +43,14 @@ Quickly dive into creating animations with **Panthera Editor 2.0** using this st - [Adjust Gizmo Settings](#adjust-gizmo-settings) * [Scene Gizmo Settings](#scene-gizmo-settings) * [Timeline Gizmo Settings](#timeline-gizmo-settings) +- [Adjust Font Size](#adjust-font-size) - [Using Hotkeys](#using-hotkeys) # Important Notes Here are some fast helpful tips and reminders for using Panthera Editor: -- To create animation you have to create a layout with nodes in **Layout mode** (or export GUI layout from **[Defold](https://defold.com/)** project). +- To create animation you have to create a layout with nodes in **Layout mode** (or export GUI/Collection/GO layout from **[Defold](https://defold.com/)** project). - Then layout created, you can switch to **Animation mode** and start making animations. - Pan the editor view by holding `Ctrl` or `Alt` and dragging the view. - Property names in 🔸 orange indicate unapplied changes. ![changed_property](/docs_editor/media/icon_changed_property.png) @@ -319,6 +320,12 @@ To discard changes in the Properties panel, follow these steps: > Note: You can use "Reset all" button in the Properties panel to reset all the properties to the initial state. If there are no changed properties., the node will be reset to the initial state. +## Set Empty or Default Value + +To set the empty or default value for the property, follow these steps: + +1. Right click on the property name in the Properties panel. +2. Select "Set Default". # Working with Timeline Keys diff --git a/example/default.render_script b/example/default.render_script index b61a16a..c3df877 100644 --- a/example/default.render_script +++ b/example/default.render_script @@ -3,10 +3,10 @@ -- Copyright 2009-2014 Ragnar Svensson, Christian Murray -- Licensed under the Defold License version 1.0 (the "License"); you may not use -- this file except in compliance with the License. --- +-- -- You may obtain a copy of the License, together with FAQs at -- https://www.defold.com/license --- +-- -- Unless required by applicable law or agreed to in writing, software distributed -- under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -- CONDITIONS OF ANY KIND, either express or implied. See the License for the diff --git a/example/example.gui_script b/example/example.gui_script index 45b0eac..149894e 100644 --- a/example/example.gui_script +++ b/example/example.gui_script @@ -10,7 +10,7 @@ local logger = log.get_logger("example") ---@field animation_control_zone node ---@field progress_fill node ---@field progress_size vector3 ----@field text_animation_id string +---@field text_animation_id node ---@field is_dragging boolean diff --git a/example/example_druid_component/druid_component.gui b/example/example_druid_component/druid_component.gui new file mode 100644 index 0000000..7ff5bf5 --- /dev/null +++ b/example/example_druid_component/druid_component.gui @@ -0,0 +1,202 @@ +script: "" +fonts { + name: "troika" + font: "/example/assets/troika.font" +} +textures { + name: "example" + texture: "/example/assets/example.atlas" +} +background_color { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 +} +nodes { + position { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 256.0 + y: 196.0 + z: 0.0 + w: 1.0 + } + color { + x: 0.754 + y: 0.924 + z: 0.879 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "example/ui_rect_round_16" + id: "root" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + layer: "" + inherit_alpha: true + slice9 { + x: 16.0 + y: 16.0 + z: 16.0 + w: 16.0 + } + clipping_mode: CLIPPING_MODE_NONE + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + template_node_child: false + size_mode: SIZE_MODE_MANUAL + custom_type: 0 + enabled: true + visible: true + material: "" +} +nodes { + position { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "example/panthera" + id: "panthera" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "root" + layer: "" + inherit_alpha: true + slice9 { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 + } + clipping_mode: CLIPPING_MODE_NONE + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + template_node_child: false + size_mode: SIZE_MODE_AUTO + custom_type: 0 + enabled: true + visible: true + material: "" +} +nodes { + position { + x: 0.0 + y: 80.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 30.0 + z: 0.0 + w: 1.0 + } + color { + x: 0.2 + y: 0.2 + z: 0.2 + w: 1.0 + } + type: TYPE_TEXT + blend_mode: BLEND_MODE_ALPHA + text: "Hello" + font: "troika" + id: "text" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + outline { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + shadow { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + adjust_mode: ADJUST_MODE_FIT + line_break: false + parent: "root" + layer: "" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 0.0 + shadow_alpha: 0.0 + template_node_child: false + text_leading: 1.0 + text_tracking: 0.0 + custom_type: 0 + enabled: true + visible: true + material: "" +} +material: "/builtins/materials/gui.material" +adjust_reference: ADJUST_REFERENCE_PARENT +max_nodes: 512 diff --git a/example/example_druid_component/druid_component.lua b/example/example_druid_component/druid_component.lua new file mode 100644 index 0000000..0eb3d32 --- /dev/null +++ b/example/example_druid_component/druid_component.lua @@ -0,0 +1,39 @@ +local component = require("druid.component") +local panthera = require("panthera.panthera") +local druid_component_animation = require("example.example_druid_component.druid_component_animation") + +---@class druid_component: druid.base_component +---@field root node +---@field text druid.text +---@field druid druid_instance +local M = component.create("druid_component") + +local SCHEME = { + ROOT = "root", + PANTHERA = "panthera", + TEXT = "text" +} + + +---@param template string +---@param nodes table +function M:init(template, nodes) + self:set_template(template) + self:set_nodes(nodes) + self.druid = self:get_druid() + + self.root = self:get_node(SCHEME.ROOT) + self.text = self.druid:new_text(SCHEME.TEXT) + + self.animation_state = panthera.create_gui(druid_component_animation, self:get_template(), nodes) +end + + +function M:run_animation() + panthera.play(self.animation_state, "default", { + is_loop = true + }) +end + + +return M diff --git a/example/example_druid_component/druid_component_animation.lua b/example/example_druid_component/druid_component_animation.lua new file mode 100644 index 0000000..23efa56 --- /dev/null +++ b/example/example_druid_component/druid_component_animation.lua @@ -0,0 +1,148 @@ +return { + type = "animation_editor", + format = "json", + version = 1, + data = { + nodes = { + }, + animations = { + { + duration = 2.2, + animation_keys = { + { + property_id = "position_y", + easing = "outsine", + duration = 0.4, + key_type = "tween", + node_id = "panthera", + end_value = -42, + }, + { + property_id = "scale_x", + easing = "outback", + duration = 0.42, + key_type = "tween", + node_id = "text", + end_value = 1.3, + start_value = 1, + }, + { + property_id = "scale_y", + easing = "outback", + duration = 0.42, + key_type = "tween", + node_id = "text", + end_value = 1.3, + start_value = 1, + }, + { + property_id = "position_y", + easing = "outback", + duration = 0.42, + key_type = "tween", + node_id = "text", + end_value = 45, + start_value = 80, + }, + { + property_id = "position_x", + easing = "outsine", + duration = 0.8, + key_type = "tween", + node_id = "panthera", + end_value = 75, + }, + { + property_id = "text", + easing = "linear", + node_id = "text", + key_type = "trigger", + start_time = 0.22, + data = "Just", + start_data = "Hello", + }, + { + property_id = "position_y", + easing = "outsine", + duration = 1.6, + key_type = "tween", + start_time = 0.4, + node_id = "panthera", + start_value = -42, + }, + { + property_id = "scale_x", + easing = "outsine", + duration = 0.45, + node_id = "text", + key_type = "tween", + start_time = 0.42, + end_value = 1, + start_value = 1.3, + }, + { + property_id = "scale_y", + easing = "outsine", + duration = 0.45, + node_id = "text", + key_type = "tween", + start_time = 0.42, + end_value = 1, + start_value = 1.3, + }, + { + property_id = "position_y", + easing = "outsine", + duration = 0.45, + node_id = "text", + key_type = "tween", + start_time = 0.42, + end_value = 75, + start_value = 45, + }, + { + property_id = "position_x", + easing = "outsine", + duration = 0.6, + node_id = "panthera", + key_type = "tween", + start_time = 0.8, + end_value = -65, + start_value = 75, + }, + { + property_id = "text", + easing = "linear", + node_id = "text", + key_type = "trigger", + start_time = 0.87, + data = "Example", + start_data = "Just", + }, + { + property_id = "position_x", + easing = "outsine", + duration = 0.6, + key_type = "tween", + start_time = 1.4, + node_id = "panthera", + start_value = -65, + }, + }, + animation_id = "default", + }, + }, + metadata = { + gui_path = "/example/example_druid_component/druid_component.gui", + fps = 60, + settings = { + font_size = 40, + }, + layers = { + }, + gizmo_steps = { + time = 0.1, + }, + }, + }, +} \ No newline at end of file diff --git a/example/example_druid_component/exampel_druid_component.gui_script b/example/example_druid_component/exampel_druid_component.gui_script new file mode 100644 index 0000000..75e13a5 --- /dev/null +++ b/example/example_druid_component/exampel_druid_component.gui_script @@ -0,0 +1,23 @@ +local druid = require("druid.druid") + +local druid_component = require("example.example_druid_component.druid_component") + +function init(self) + self.druid = druid.new(self) + + local prefab = gui.get_node("druid_component_template/root") + + local druid_component_on_scene = self.druid:new(druid_component, "druid_component_template") + + local nodes = gui.clone_tree(prefab) + local druid_component_cloned = self.druid:new(druid_component, "druid_component_template", nodes) + gui.set_position(druid_component_cloned.root, vmath.vector3(700, 340, 0)) + + druid_component_on_scene:run_animation() + druid_component_cloned:run_animation() +end + + +function final(self) + self.druid:final() +end diff --git a/example/example_druid_component/example_druid_component.collection b/example/example_druid_component/example_druid_component.collection new file mode 100644 index 0000000..6d333eb --- /dev/null +++ b/example/example_druid_component/example_druid_component.collection @@ -0,0 +1,39 @@ +name: "example_druid_component" +scale_along_z: 0 +embedded_instances { + id: "go" + data: "components {\n" + " id: \"example_druid_component\"\n" + " component: \"/example/example_druid_component/example_druid_component.gui\"\n" + " position {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + " property_decls {\n" + " }\n" + "}\n" + "" + position { + x: 0.0 + y: 0.0 + z: 0.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale3 { + x: 1.0 + y: 1.0 + z: 1.0 + } +} diff --git a/example/example_druid_component/example_druid_component.gui b/example/example_druid_component/example_druid_component.gui new file mode 100644 index 0000000..1f95206 --- /dev/null +++ b/example/example_druid_component/example_druid_component.gui @@ -0,0 +1,295 @@ +script: "/example/example_druid_component/exampel_druid_component.gui_script" +background_color { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 +} +nodes { + position { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "" + id: "root" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + layer: "" + inherit_alpha: true + slice9 { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 + } + clipping_mode: CLIPPING_MODE_NONE + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + template_node_child: false + size_mode: SIZE_MODE_AUTO + custom_type: 0 + enabled: true + visible: false + material: "" +} +nodes { + position { + x: 260.0 + y: 340.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_TEMPLATE + id: "druid_component_template" + parent: "root" + layer: "" + inherit_alpha: true + alpha: 1.0 + template: "/example/example_druid_component/druid_component.gui" + template_node_child: false + custom_type: 0 + enabled: true +} +nodes { + position { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 256.0 + y: 196.0 + z: 0.0 + w: 1.0 + } + color { + x: 0.754 + y: 0.924 + z: 0.879 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "example/ui_rect_round_16" + id: "druid_component_template/root" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "druid_component_template" + layer: "" + inherit_alpha: true + slice9 { + x: 16.0 + y: 16.0 + z: 16.0 + w: 16.0 + } + clipping_mode: CLIPPING_MODE_NONE + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + template_node_child: true + size_mode: SIZE_MODE_MANUAL + custom_type: 0 + enabled: true + visible: true + material: "" +} +nodes { + position { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "example/panthera" + id: "druid_component_template/panthera" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "druid_component_template/root" + layer: "" + inherit_alpha: true + slice9 { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 + } + clipping_mode: CLIPPING_MODE_NONE + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + template_node_child: true + size_mode: SIZE_MODE_AUTO + custom_type: 0 + enabled: true + visible: true + material: "" +} +nodes { + position { + x: 0.0 + y: 80.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 30.0 + z: 0.0 + w: 1.0 + } + color { + x: 0.2 + y: 0.2 + z: 0.2 + w: 1.0 + } + type: TYPE_TEXT + blend_mode: BLEND_MODE_ALPHA + text: "Hello" + font: "troika" + id: "druid_component_template/text" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + outline { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + shadow { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + adjust_mode: ADJUST_MODE_FIT + line_break: false + parent: "druid_component_template/root" + layer: "" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 0.0 + shadow_alpha: 0.0 + template_node_child: true + text_leading: 1.0 + text_tracking: 0.0 + custom_type: 0 + enabled: true + visible: true + material: "" +} +material: "/builtins/materials/gui.material" +adjust_reference: ADJUST_REFERENCE_PARENT +max_nodes: 512 diff --git a/example/example_go_gui_templates/go/example_go_template_nodes.collection b/example/example_go_gui_templates/go/example_go_template_nodes.collection new file mode 100644 index 0000000..4a67e36 --- /dev/null +++ b/example/example_go_gui_templates/go/example_go_template_nodes.collection @@ -0,0 +1,531 @@ +name: "test_collection" +collection_instances { + id: "test_collection" + collection: "/example/example_go_gui_templates/go/test_collection.collection" + position { + x: 480.0 + y: 480.0 + z: 0.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale3 { + x: 1.0 + y: 1.0 + z: 1.0 + } +} +collection_instances { + id: "test_inner_collection" + collection: "/example/example_go_gui_templates/go/test_inner_collection.collection" + position { + x: 840.0 + y: 475.0 + z: 0.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale3 { + x: 1.0 + y: 1.0 + z: 1.0 + } +} +scale_along_z: 0 +embedded_instances { + id: "root" + children: "panthera" + data: "components {\n" + " id: \"example_go_template_nodes\"\n" + " component: \"/example/example_go_gui_templates/go/example_go_template_nodes.script\"\n" + " position {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + " property_decls {\n" + " }\n" + "}\n" + "embedded_components {\n" + " id: \"collectionfactory\"\n" + " type: \"collectionfactory\"\n" + " data: \"prototype: \\\"/example/example_go_gui_templates/go/test_collection.collection\\\"\\n" + "load_dynamically: false\\n" + "dynamic_prototype: false\\n" + "\"\n" + " position {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + "}\n" + "embedded_components {\n" + " id: \"collectionfactory_inner\"\n" + " type: \"collectionfactory\"\n" + " data: \"prototype: \\\"/example/example_go_gui_templates/go/test_inner_collection.collection\\\"\\n" + "load_dynamically: false\\n" + "dynamic_prototype: false\\n" + "\"\n" + " position {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + "}\n" + "embedded_components {\n" + " id: \"label_game_object\"\n" + " type: \"label\"\n" + " data: \"size {\\n" + " x: 128.0\\n" + " y: 32.0\\n" + " z: 0.0\\n" + " w: 0.0\\n" + "}\\n" + "color {\\n" + " x: 1.0\\n" + " y: 1.0\\n" + " z: 1.0\\n" + " w: 1.0\\n" + "}\\n" + "outline {\\n" + " x: 0.0\\n" + " y: 0.0\\n" + " z: 0.0\\n" + " w: 1.0\\n" + "}\\n" + "shadow {\\n" + " x: 0.0\\n" + " y: 0.0\\n" + " z: 0.0\\n" + " w: 1.0\\n" + "}\\n" + "leading: 1.0\\n" + "tracking: 0.0\\n" + "pivot: PIVOT_CENTER\\n" + "blend_mode: BLEND_MODE_ALPHA\\n" + "line_break: false\\n" + "text: \\\"Game Object\\\"\\n" + "font: \\\"/example/assets/troika.font\\\"\\n" + "material: \\\"/builtins/fonts/label-df.material\\\"\\n" + "\"\n" + " position {\n" + " x: -360.0\n" + " y: 280.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + "}\n" + "embedded_components {\n" + " id: \"label_collection\"\n" + " type: \"label\"\n" + " data: \"size {\\n" + " x: 128.0\\n" + " y: 32.0\\n" + " z: 0.0\\n" + " w: 0.0\\n" + "}\\n" + "color {\\n" + " x: 1.0\\n" + " y: 1.0\\n" + " z: 1.0\\n" + " w: 1.0\\n" + "}\\n" + "outline {\\n" + " x: 0.0\\n" + " y: 0.0\\n" + " z: 0.0\\n" + " w: 1.0\\n" + "}\\n" + "shadow {\\n" + " x: 0.0\\n" + " y: 0.0\\n" + " z: 0.0\\n" + " w: 1.0\\n" + "}\\n" + "leading: 1.0\\n" + "tracking: 0.0\\n" + "pivot: PIVOT_CENTER\\n" + "blend_mode: BLEND_MODE_ALPHA\\n" + "line_break: false\\n" + "text: \\\"Collection\\\"\\n" + "font: \\\"/example/assets/troika.font\\\"\\n" + "material: \\\"/builtins/fonts/label-df.material\\\"\\n" + "\"\n" + " position {\n" + " x: 0.0\n" + " y: 280.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + "}\n" + "embedded_components {\n" + " id: \"label_nested_collection\"\n" + " type: \"label\"\n" + " data: \"size {\\n" + " x: 128.0\\n" + " y: 32.0\\n" + " z: 0.0\\n" + " w: 0.0\\n" + "}\\n" + "color {\\n" + " x: 1.0\\n" + " y: 1.0\\n" + " z: 1.0\\n" + " w: 1.0\\n" + "}\\n" + "outline {\\n" + " x: 0.0\\n" + " y: 0.0\\n" + " z: 0.0\\n" + " w: 1.0\\n" + "}\\n" + "shadow {\\n" + " x: 0.0\\n" + " y: 0.0\\n" + " z: 0.0\\n" + " w: 1.0\\n" + "}\\n" + "leading: 1.0\\n" + "tracking: 0.0\\n" + "pivot: PIVOT_CENTER\\n" + "blend_mode: BLEND_MODE_ALPHA\\n" + "line_break: false\\n" + "text: \\\"Nested Collection\\\"\\n" + "font: \\\"/example/assets/troika.font\\\"\\n" + "material: \\\"/builtins/fonts/label-df.material\\\"\\n" + "\"\n" + " position {\n" + " x: 360.0\n" + " y: 275.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + "}\n" + "embedded_components {\n" + " id: \"label_collection_cloned\"\n" + " type: \"label\"\n" + " data: \"size {\\n" + " x: 128.0\\n" + " y: 32.0\\n" + " z: 0.0\\n" + " w: 0.0\\n" + "}\\n" + "color {\\n" + " x: 1.0\\n" + " y: 1.0\\n" + " z: 1.0\\n" + " w: 1.0\\n" + "}\\n" + "outline {\\n" + " x: 0.0\\n" + " y: 0.0\\n" + " z: 0.0\\n" + " w: 1.0\\n" + "}\\n" + "shadow {\\n" + " x: 0.0\\n" + " y: 0.0\\n" + " z: 0.0\\n" + " w: 1.0\\n" + "}\\n" + "leading: 1.0\\n" + "tracking: 0.0\\n" + "pivot: PIVOT_CENTER\\n" + "blend_mode: BLEND_MODE_ALPHA\\n" + "line_break: true\\n" + "text: \\\"Spawned Collection\\\"\\n" + "font: \\\"/example/assets/troika.font\\\"\\n" + "material: \\\"/builtins/fonts/label-df.material\\\"\\n" + "\"\n" + " position {\n" + " x: 0.0\n" + " y: -40.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + " scale {\n" + " x: 0.7\n" + " y: 0.7\n" + " z: 1.0\n" + " }\n" + "}\n" + "embedded_components {\n" + " id: \"label_collection_nested_cloned\"\n" + " type: \"label\"\n" + " data: \"size {\\n" + " x: 128.0\\n" + " y: 32.0\\n" + " z: 0.0\\n" + " w: 0.0\\n" + "}\\n" + "color {\\n" + " x: 1.0\\n" + " y: 1.0\\n" + " z: 1.0\\n" + " w: 1.0\\n" + "}\\n" + "outline {\\n" + " x: 0.0\\n" + " y: 0.0\\n" + " z: 0.0\\n" + " w: 1.0\\n" + "}\\n" + "shadow {\\n" + " x: 0.0\\n" + " y: 0.0\\n" + " z: 0.0\\n" + " w: 1.0\\n" + "}\\n" + "leading: 1.0\\n" + "tracking: 0.0\\n" + "pivot: PIVOT_CENTER\\n" + "blend_mode: BLEND_MODE_ALPHA\\n" + "line_break: true\\n" + "text: \\\"Spawned Nested\\\"\\n" + "font: \\\"/example/assets/troika.font\\\"\\n" + "material: \\\"/builtins/fonts/label-df.material\\\"\\n" + "\"\n" + " position {\n" + " x: 360.0\n" + " y: -40.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + " scale {\n" + " x: 0.7\n" + " y: 0.7\n" + " z: 1.0\n" + " }\n" + "}\n" + "embedded_components {\n" + " id: \"factory\"\n" + " type: \"factory\"\n" + " data: \"prototype: \\\"/example/example_go_gui_templates/go/test_go.go\\\"\\n" + "load_dynamically: false\\n" + "dynamic_prototype: false\\n" + "\"\n" + " position {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + "}\n" + "embedded_components {\n" + " id: \"label_game_object_factory\"\n" + " type: \"label\"\n" + " data: \"size {\\n" + " x: 240.0\\n" + " y: 32.0\\n" + " z: 0.0\\n" + " w: 0.0\\n" + "}\\n" + "color {\\n" + " x: 1.0\\n" + " y: 1.0\\n" + " z: 1.0\\n" + " w: 1.0\\n" + "}\\n" + "outline {\\n" + " x: 0.0\\n" + " y: 0.0\\n" + " z: 0.0\\n" + " w: 1.0\\n" + "}\\n" + "shadow {\\n" + " x: 0.0\\n" + " y: 0.0\\n" + " z: 0.0\\n" + " w: 1.0\\n" + "}\\n" + "leading: 1.0\\n" + "tracking: 0.0\\n" + "pivot: PIVOT_CENTER\\n" + "blend_mode: BLEND_MODE_ALPHA\\n" + "line_break: true\\n" + "text: \\\"Spawner Game Object\\\"\\n" + "font: \\\"/example/assets/troika.font\\\"\\n" + "material: \\\"/builtins/fonts/label-df.material\\\"\\n" + "\"\n" + " position {\n" + " x: -360.0\n" + " y: -40.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + " scale {\n" + " x: 0.7\n" + " y: 0.7\n" + " z: 1.0\n" + " }\n" + "}\n" + "" + position { + x: 480.0 + y: 320.0 + z: 0.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale3 { + x: 1.0 + y: 1.0 + z: 1.0 + } +} +embedded_instances { + id: "panthera" + data: "embedded_components {\n" + " id: \"sprite\"\n" + " type: \"sprite\"\n" + " data: \"default_animation: \\\"panthera\\\"\\n" + "material: \\\"/builtins/materials/sprite.material\\\"\\n" + "blend_mode: BLEND_MODE_ALPHA\\n" + "textures {\\n" + " sampler: \\\"texture_sampler\\\"\\n" + " texture: \\\"/example/assets/example.atlas\\\"\\n" + "}\\n" + "\"\n" + " position {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + "}\n" + "embedded_components {\n" + " id: \"label\"\n" + " type: \"label\"\n" + " data: \"size {\\n" + " x: 128.0\\n" + " y: 32.0\\n" + " z: 0.0\\n" + " w: 0.0\\n" + "}\\n" + "color {\\n" + " x: 1.0\\n" + " y: 1.0\\n" + " z: 1.0\\n" + " w: 1.0\\n" + "}\\n" + "outline {\\n" + " x: 0.0\\n" + " y: 0.0\\n" + " z: 0.0\\n" + " w: 0.0\\n" + "}\\n" + "shadow {\\n" + " x: 0.0\\n" + " y: 0.0\\n" + " z: 0.0\\n" + " w: 0.0\\n" + "}\\n" + "leading: 1.0\\n" + "tracking: 0.0\\n" + "pivot: PIVOT_CENTER\\n" + "blend_mode: BLEND_MODE_ALPHA\\n" + "line_break: false\\n" + "text: \\\"Text\\\"\\n" + "font: \\\"/example/assets/troika.font\\\"\\n" + "material: \\\"/builtins/fonts/label-df.material\\\"\\n" + "\"\n" + " position {\n" + " x: 0.0\n" + " y: 70.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + "}\n" + "" + position { + x: -360.0 + y: 160.0 + z: 0.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale3 { + x: 1.0 + y: 1.0 + z: 1.0 + } +} diff --git a/example/example_go_gui_templates/go/example_go_template_nodes.script b/example/example_go_gui_templates/go/example_go_template_nodes.script new file mode 100644 index 0000000..f90ecac --- /dev/null +++ b/example/example_go_gui_templates/go/example_go_template_nodes.script @@ -0,0 +1,46 @@ +local panthera = require("panthera.panthera") +local PATH = "/resources/test_go.json" + +function init(self) + -- Run Panthera over Game Object in collection + -- No any collection name or created objects + local go_animation = panthera.create_go(PATH) + panthera.play(go_animation, "default", { is_loop = true }) + + + -- Run Panthera over collection + -- Is a collection name, but no any created objects + local go_animation = panthera.create_go(PATH, "test_collection") + panthera.play(go_animation, "default", { is_loop = true }) + + + -- Run Panthera over nested collection + -- Is a still a collection, just inside another collection, so we concatenate collection names + local go_animation = panthera.create_go(PATH, "test_inner_collection/test_collection") + panthera.play(go_animation, "default", { is_loop = true }) + + + -- Run Panthera over Game Object from factory + local object = factory.create("#factory") + go.set_position(vmath.vector3(120, 160, 0), object) + local go_animation = panthera.create_go(PATH, nil, { [hash("/panthera")] = object }) + panthera.play(go_animation, "default", { is_loop = true }) + + + -- Run Panthera over collection from collectionfactory + -- We create a collection from collectionfactory and use this objects to run Panthera + -- The paths to the objects inside this collection are just paths without any collection names + local objects = collectionfactory.create("#collectionfactory") + go.set_position(vmath.vector3(480, 160, 0), objects["/panthera"]) + local go_animation = panthera.create_go(PATH, nil, objects) + panthera.play(go_animation, "default", { is_loop = true }) + + + -- Run Panthera over nested collection from collectionfactory + -- But here when we create a collection from collectionfactory, inside created objects we have a collection + -- So add a collection name and created objects + local objects = collectionfactory.create("#collectionfactory_inner") + go.set_position(vmath.vector3(840, 160, 0), objects["/test_collection/panthera"]) + local go_animation = panthera.create_go(PATH, "test_collection", objects) + panthera.play(go_animation, "default", { is_loop = true }) +end \ No newline at end of file diff --git a/example/example_go_gui_templates/go/test_collection.collection b/example/example_go_gui_templates/go/test_collection.collection new file mode 100644 index 0000000..a224ad2 --- /dev/null +++ b/example/example_go_gui_templates/go/test_collection.collection @@ -0,0 +1,93 @@ +name: "test_collection" +scale_along_z: 0 +embedded_instances { + id: "panthera" + data: "embedded_components {\n" + " id: \"label\"\n" + " type: \"label\"\n" + " data: \"size {\\n" + " x: 128.0\\n" + " y: 32.0\\n" + " z: 0.0\\n" + " w: 0.0\\n" + "}\\n" + "color {\\n" + " x: 1.0\\n" + " y: 1.0\\n" + " z: 1.0\\n" + " w: 1.0\\n" + "}\\n" + "outline {\\n" + " x: 0.0\\n" + " y: 0.0\\n" + " z: 0.0\\n" + " w: 0.0\\n" + "}\\n" + "shadow {\\n" + " x: 0.0\\n" + " y: 0.0\\n" + " z: 0.0\\n" + " w: 0.0\\n" + "}\\n" + "leading: 1.0\\n" + "tracking: 0.0\\n" + "pivot: PIVOT_CENTER\\n" + "blend_mode: BLEND_MODE_ALPHA\\n" + "line_break: false\\n" + "text: \\\"Text\\\"\\n" + "font: \\\"/example/assets/troika.font\\\"\\n" + "material: \\\"/builtins/fonts/label-df.material\\\"\\n" + "\"\n" + " position {\n" + " x: 0.0\n" + " y: 70.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + "}\n" + "embedded_components {\n" + " id: \"sprite\"\n" + " type: \"sprite\"\n" + " data: \"default_animation: \\\"panthera\\\"\\n" + "material: \\\"/builtins/materials/sprite.material\\\"\\n" + "blend_mode: BLEND_MODE_ALPHA\\n" + "textures {\\n" + " sampler: \\\"texture_sampler\\\"\\n" + " texture: \\\"/example/assets/example.atlas\\\"\\n" + "}\\n" + "\"\n" + " position {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + "}\n" + "" + position { + x: 0.0 + y: 0.0 + z: 0.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale3 { + x: 1.0 + y: 1.0 + z: 1.0 + } +} diff --git a/example/example_go_gui_templates/go/test_go.go b/example/example_go_gui_templates/go/test_go.go new file mode 100644 index 0000000..eae6c36 --- /dev/null +++ b/example/example_go_gui_templates/go/test_go.go @@ -0,0 +1,71 @@ +embedded_components { + id: "label" + type: "label" + data: "size {\n" + " x: 128.0\n" + " y: 32.0\n" + " z: 0.0\n" + " w: 0.0\n" + "}\n" + "color {\n" + " x: 1.0\n" + " y: 1.0\n" + " z: 1.0\n" + " w: 1.0\n" + "}\n" + "outline {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 0.0\n" + "}\n" + "shadow {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 0.0\n" + "}\n" + "leading: 1.0\n" + "tracking: 0.0\n" + "pivot: PIVOT_CENTER\n" + "blend_mode: BLEND_MODE_ALPHA\n" + "line_break: false\n" + "text: \"Text\"\n" + "font: \"/example/assets/troika.font\"\n" + "material: \"/builtins/fonts/label-df.material\"\n" + "" + position { + x: 0.0 + y: 70.0 + z: 0.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } +} +embedded_components { + id: "sprite" + type: "sprite" + data: "default_animation: \"panthera\"\n" + "material: \"/builtins/materials/sprite.material\"\n" + "blend_mode: BLEND_MODE_ALPHA\n" + "textures {\n" + " sampler: \"texture_sampler\"\n" + " texture: \"/example/assets/example.atlas\"\n" + "}\n" + "" + position { + x: 0.0 + y: 0.0 + z: 0.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } +} diff --git a/example/example_go_gui_templates/go/test_inner_collection.collection b/example/example_go_gui_templates/go/test_inner_collection.collection new file mode 100644 index 0000000..c6e4db5 --- /dev/null +++ b/example/example_go_gui_templates/go/test_inner_collection.collection @@ -0,0 +1,22 @@ +name: "test_collection" +collection_instances { + id: "test_collection" + collection: "/example/example_go_gui_templates/go/test_collection.collection" + position { + x: 0.0 + y: 0.0 + z: 0.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale3 { + x: 1.0 + y: 1.0 + z: 1.0 + } +} +scale_along_z: 0 diff --git a/example/example_go_gui_templates/gui/example_gui_template_nodes.collection b/example/example_go_gui_templates/gui/example_gui_template_nodes.collection new file mode 100644 index 0000000..ad71c56 --- /dev/null +++ b/example/example_go_gui_templates/gui/example_gui_template_nodes.collection @@ -0,0 +1,39 @@ +name: "example_go_gui_templates" +scale_along_z: 0 +embedded_instances { + id: "gui" + data: "components {\n" + " id: \"example_go_gui_templates\"\n" + " component: \"/example/example_go_gui_templates/gui/example_gui_template_nodes.gui\"\n" + " position {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + " property_decls {\n" + " }\n" + "}\n" + "" + position { + x: 0.0 + y: 0.0 + z: 0.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale3 { + x: 1.0 + y: 1.0 + z: 1.0 + } +} diff --git a/example/example_go_gui_templates/gui/example_gui_template_nodes.gui b/example/example_go_gui_templates/gui/example_gui_template_nodes.gui new file mode 100644 index 0000000..675821d --- /dev/null +++ b/example/example_go_gui_templates/gui/example_gui_template_nodes.gui @@ -0,0 +1,870 @@ +script: "/example/example_go_gui_templates/gui/example_gui_template_nodes.gui_script" +fonts { + name: "troika" + font: "/example/assets/troika.font" +} +textures { + name: "example" + texture: "/example/assets/example.atlas" +} +background_color { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 +} +nodes { + position { + x: 480.0 + y: 320.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 960.0 + y: 640.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "example/empty" + id: "root" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_STRETCH + layer: "" + inherit_alpha: true + slice9 { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 + } + clipping_mode: CLIPPING_MODE_NONE + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + template_node_child: false + size_mode: SIZE_MODE_MANUAL + custom_type: 0 + enabled: true + visible: false + material: "" +} +nodes { + position { + x: -200.0 + y: 290.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_TEXT + blend_mode: BLEND_MODE_ALPHA + text: "GUI Nodes" + font: "troika" + id: "text_gui_nodes" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + outline { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + shadow { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + adjust_mode: ADJUST_MODE_FIT + line_break: false + parent: "root" + layer: "" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 0.0 + shadow_alpha: 0.0 + template_node_child: false + text_leading: 1.0 + text_tracking: 0.0 + custom_type: 0 + enabled: true + visible: true + material: "" +} +nodes { + position { + x: -200.0 + y: 120.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 300.0 + y: 300.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "example/pixel" + id: "test_gui_inline" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "root" + layer: "" + inherit_alpha: true + slice9 { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 + } + clipping_mode: CLIPPING_MODE_NONE + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + template_node_child: false + size_mode: SIZE_MODE_MANUAL + custom_type: 0 + enabled: true + visible: true + material: "" +} +nodes { + position { + x: 0.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 50.0 + z: 0.0 + w: 1.0 + } + color { + x: 0.091 + y: 0.088 + z: 0.088 + w: 1.0 + } + type: TYPE_TEXT + blend_mode: BLEND_MODE_ALPHA + text: "Text" + font: "troika" + id: "text" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + outline { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + shadow { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + adjust_mode: ADJUST_MODE_FIT + line_break: false + parent: "test_gui_inline" + layer: "" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 0.0 + shadow_alpha: 0.0 + template_node_child: false + text_leading: 1.0 + text_tracking: 0.0 + custom_type: 0 + enabled: true + visible: true + material: "" +} +nodes { + position { + x: -60.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "example/panthera" + id: "box" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "test_gui_inline" + layer: "" + inherit_alpha: true + slice9 { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 + } + clipping_mode: CLIPPING_MODE_NONE + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + template_node_child: false + size_mode: SIZE_MODE_AUTO + custom_type: 0 + enabled: true + visible: true + material: "" +} +nodes { + position { + x: 60.0 + y: -2.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_PIE + blend_mode: BLEND_MODE_ALPHA + texture: "example/panthera" + id: "pie" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "test_gui_inline" + layer: "" + inherit_alpha: true + outerBounds: PIEBOUNDS_RECTANGLE + innerRadius: 0.0 + perimeterVertices: 10 + pieFillAngle: 360.0 + clipping_mode: CLIPPING_MODE_NONE + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + template_node_child: false + size_mode: SIZE_MODE_AUTO + custom_type: 0 + enabled: true + visible: true + material: "" +} +nodes { + position { + x: 200.0 + y: 290.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_TEXT + blend_mode: BLEND_MODE_ALPHA + text: "GUI Template" + font: "troika" + id: "text_gui_template" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + outline { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + shadow { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + adjust_mode: ADJUST_MODE_FIT + line_break: false + parent: "root" + layer: "" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 0.0 + shadow_alpha: 0.0 + template_node_child: false + text_leading: 1.0 + text_tracking: 0.0 + custom_type: 0 + enabled: true + visible: true + material: "" +} +nodes { + position { + x: 200.0 + y: 120.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_TEMPLATE + id: "test_gui" + parent: "root" + layer: "" + inherit_alpha: true + alpha: 1.0 + template: "/example/example_go_gui_templates/gui/test_gui.gui" + template_node_child: false + custom_type: 0 + enabled: true +} +nodes { + position { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 300.0 + y: 300.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "example/pixel" + id: "test_gui/root" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "test_gui" + layer: "" + inherit_alpha: true + slice9 { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 + } + clipping_mode: CLIPPING_MODE_NONE + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + template_node_child: true + size_mode: SIZE_MODE_MANUAL + custom_type: 0 + enabled: true + visible: true + material: "" +} +nodes { + position { + x: 0.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 50.0 + z: 0.0 + w: 1.0 + } + color { + x: 0.091 + y: 0.088 + z: 0.088 + w: 1.0 + } + type: TYPE_TEXT + blend_mode: BLEND_MODE_ALPHA + text: "Text" + font: "troika" + id: "test_gui/text" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + outline { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + shadow { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + adjust_mode: ADJUST_MODE_FIT + line_break: false + parent: "test_gui/root" + layer: "" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 0.0 + shadow_alpha: 0.0 + template_node_child: true + text_leading: 1.0 + text_tracking: 0.0 + custom_type: 0 + enabled: true + visible: true + material: "" +} +nodes { + position { + x: -60.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "example/panthera" + id: "test_gui/box" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "test_gui/root" + layer: "" + inherit_alpha: true + slice9 { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 + } + clipping_mode: CLIPPING_MODE_NONE + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + template_node_child: true + size_mode: SIZE_MODE_AUTO + custom_type: 0 + enabled: true + visible: true + material: "" +} +nodes { + position { + x: 60.0 + y: -2.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_PIE + blend_mode: BLEND_MODE_ALPHA + texture: "example/panthera" + id: "test_gui/pie" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "test_gui/root" + layer: "" + inherit_alpha: true + outerBounds: PIEBOUNDS_RECTANGLE + innerRadius: 0.0 + perimeterVertices: 10 + pieFillAngle: 360.0 + clipping_mode: CLIPPING_MODE_NONE + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + template_node_child: true + size_mode: SIZE_MODE_AUTO + custom_type: 0 + enabled: true + visible: true + material: "" +} +nodes { + position { + x: -200.0 + y: -60.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_TEXT + blend_mode: BLEND_MODE_ALPHA + text: "GUI Cloned Nodes" + font: "troika" + id: "text_gui_nodes_clone" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + outline { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + shadow { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + adjust_mode: ADJUST_MODE_FIT + line_break: false + parent: "root" + layer: "" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 0.0 + shadow_alpha: 0.0 + template_node_child: false + text_leading: 1.0 + text_tracking: 0.0 + custom_type: 0 + enabled: true + visible: true + material: "" +} +nodes { + position { + x: 200.0 + y: -60.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_TEXT + blend_mode: BLEND_MODE_ALPHA + text: "GUI Cloned Template" + font: "troika" + id: "text_gui_template_clone" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + outline { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + shadow { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + adjust_mode: ADJUST_MODE_FIT + line_break: false + parent: "root" + layer: "" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 0.0 + shadow_alpha: 0.0 + template_node_child: false + text_leading: 1.0 + text_tracking: 0.0 + custom_type: 0 + enabled: true + visible: true + material: "" +} +material: "/builtins/materials/gui.material" +adjust_reference: ADJUST_REFERENCE_PARENT +max_nodes: 512 diff --git a/example/example_go_gui_templates/gui/example_gui_template_nodes.gui_script b/example/example_go_gui_templates/gui/example_gui_template_nodes.gui_script new file mode 100644 index 0000000..55062ca --- /dev/null +++ b/example/example_go_gui_templates/gui/example_gui_template_nodes.gui_script @@ -0,0 +1,27 @@ +local panthera = require("panthera.panthera") +local PATH = "/resources/test_gui.json" + +function init(self) + -- Run Panthera animation on GUI nodes + local animation_inline = panthera.create_gui(PATH) + panthera.play(animation_inline, "default", { is_loop = true }) + + + -- Run Panthera animation on GUI template nodes + local animation_template = panthera.create_gui(PATH, "test_gui") + panthera.play(animation_template, "default", { is_loop = true }) + + + --- Run Panthera animation on cloned GUI nodes + local nodes = gui.clone_tree(gui.get_node("test_gui_inline")) + gui.set_position(nodes["test_gui_inline"], vmath.vector3(-200, -240, 0)) + local animation_nodes = panthera.create_gui(PATH, nil, nodes) + panthera.play(animation_nodes, "default", { is_loop = true }) + + + -- Run Panthera animation on cloned GUI template nodes + local template_nodes = gui.clone_tree(gui.get_node("test_gui/root")) + gui.set_position(template_nodes["test_gui/root"], vmath.vector3(200, -240, 0)) + local animation_template_nodes = panthera.create_gui(PATH, "test_gui", template_nodes) + panthera.play(animation_template_nodes, "default", { is_loop = true }) +end \ No newline at end of file diff --git a/example/example_go_gui_templates/gui/test_gui.gui b/example/example_go_gui_templates/gui/test_gui.gui new file mode 100644 index 0000000..7ed1a46 --- /dev/null +++ b/example/example_go_gui_templates/gui/test_gui.gui @@ -0,0 +1,259 @@ +script: "" +fonts { + name: "troika" + font: "/example/assets/troika.font" +} +textures { + name: "example" + texture: "/example/assets/example.atlas" +} +background_color { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 +} +nodes { + position { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 300.0 + y: 300.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "example/pixel" + id: "root" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + layer: "" + inherit_alpha: true + slice9 { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 + } + clipping_mode: CLIPPING_MODE_NONE + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + template_node_child: false + size_mode: SIZE_MODE_MANUAL + custom_type: 0 + enabled: true + visible: true + material: "" +} +nodes { + position { + x: 0.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 50.0 + z: 0.0 + w: 1.0 + } + color { + x: 0.091 + y: 0.088 + z: 0.088 + w: 1.0 + } + type: TYPE_TEXT + blend_mode: BLEND_MODE_ALPHA + text: "Text" + font: "troika" + id: "text" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + outline { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + shadow { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + adjust_mode: ADJUST_MODE_FIT + line_break: false + parent: "root" + layer: "" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 0.0 + shadow_alpha: 0.0 + template_node_child: false + text_leading: 1.0 + text_tracking: 0.0 + custom_type: 0 + enabled: true + visible: true + material: "" +} +nodes { + position { + x: -60.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "example/panthera" + id: "box" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "root" + layer: "" + inherit_alpha: true + slice9 { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 + } + clipping_mode: CLIPPING_MODE_NONE + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + template_node_child: false + size_mode: SIZE_MODE_AUTO + custom_type: 0 + enabled: true + visible: true + material: "" +} +nodes { + position { + x: 60.0 + y: -2.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_PIE + blend_mode: BLEND_MODE_ALPHA + texture: "example/panthera" + id: "pie" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "root" + layer: "" + inherit_alpha: true + outerBounds: PIEBOUNDS_RECTANGLE + innerRadius: 0.0 + perimeterVertices: 10 + pieFillAngle: 360.0 + clipping_mode: CLIPPING_MODE_NONE + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + template_node_child: false + size_mode: SIZE_MODE_AUTO + custom_type: 0 + enabled: true + visible: true + material: "" +} +material: "/builtins/materials/gui.material" +adjust_reference: ADJUST_REFERENCE_PARENT +max_nodes: 512 diff --git a/example/example_go_gui_templates/test_game_object.go b/example/example_go_gui_templates/test_game_object.go new file mode 100644 index 0000000..e69de29 diff --git a/example/test_layer/test_layer.collection b/example/test_layer/test_layer.collection new file mode 100644 index 0000000..72bd584 --- /dev/null +++ b/example/test_layer/test_layer.collection @@ -0,0 +1,39 @@ +name: "test_layer" +scale_along_z: 0 +embedded_instances { + id: "go" + data: "components {\n" + " id: \"test_layer\"\n" + " component: \"/example/test_layer/test_layer.gui\"\n" + " position {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + " property_decls {\n" + " }\n" + "}\n" + "" + position { + x: 0.0 + y: 0.0 + z: 0.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale3 { + x: 1.0 + y: 1.0 + z: 1.0 + } +} diff --git a/example/test_layer/test_layer.gui b/example/test_layer/test_layer.gui new file mode 100644 index 0000000..3f816a1 --- /dev/null +++ b/example/test_layer/test_layer.gui @@ -0,0 +1,307 @@ +script: "/example/test_layer/test_layer.gui_script" +background_color { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 +} +nodes { + position { + x: 489.0 + y: 330.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 0.5 + y: 0.5 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "" + id: "root" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + layer: "" + inherit_alpha: true + slice9 { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 + } + clipping_mode: CLIPPING_MODE_NONE + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + template_node_child: false + size_mode: SIZE_MODE_AUTO + custom_type: 0 + enabled: true + visible: false + material: "" +} +nodes { + position { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 300.0 + y: 400.0 + z: 0.0 + w: 1.0 + } + color { + x: 0.2 + y: 0.0 + z: 0.2 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "" + id: "card_1" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "root" + layer: "" + inherit_alpha: true + slice9 { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 + } + clipping_mode: CLIPPING_MODE_NONE + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + template_node_child: false + size_mode: SIZE_MODE_MANUAL + custom_type: 0 + enabled: true + visible: true + material: "" +} +nodes { + position { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 268.0 + y: 370.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "" + id: "inner_1" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "card_1" + layer: "" + inherit_alpha: true + slice9 { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 + } + clipping_mode: CLIPPING_MODE_NONE + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + template_node_child: false + size_mode: SIZE_MODE_MANUAL + custom_type: 0 + enabled: true + visible: true + material: "" +} +nodes { + position { + x: 245.0 + y: -55.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 300.0 + y: 400.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 0.4 + z: 0.4 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "" + id: "card_2" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "root" + layer: "" + inherit_alpha: true + slice9 { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 + } + clipping_mode: CLIPPING_MODE_NONE + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + template_node_child: false + size_mode: SIZE_MODE_MANUAL + custom_type: 0 + enabled: true + visible: true + material: "" +} +nodes { + position { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 268.0 + y: 370.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "" + id: "inner_2" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "card_2" + layer: "" + inherit_alpha: true + slice9 { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 + } + clipping_mode: CLIPPING_MODE_NONE + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + template_node_child: false + size_mode: SIZE_MODE_MANUAL + custom_type: 0 + enabled: true + visible: true + material: "" +} +layers { + name: "up" +} +material: "/builtins/materials/gui.material" +adjust_reference: ADJUST_REFERENCE_PARENT +max_nodes: 512 diff --git a/example/test_layer/test_layer.gui_script b/example/test_layer/test_layer.gui_script new file mode 100644 index 0000000..907a892 --- /dev/null +++ b/example/test_layer/test_layer.gui_script @@ -0,0 +1,8 @@ +local panthera = require("panthera.panthera") + +function init(self) + local animation = panthera.create_gui("/resources/test_layer.json") + panthera.play(animation, "spin", { + is_loop = true + }) +end diff --git a/game.project b/game.project index aa7ed59..899dda1 100644 --- a/game.project +++ b/game.project @@ -18,8 +18,9 @@ version = 2.0 publisher = Insality developer = Maksim Tuprikov, Insality custom_resources = /resources -dependencies#0 = https://github.com/Insality/defold-tweener/archive/refs/tags/2.zip -dependencies#1 = https://github.com/Insality/defold-log/archive/refs/tags/2.zip +dependencies#0 = https://github.com/Insality/defold-log/archive/refs/tags/2.zip +dependencies#1 = https://github.com/Insality/defold-tweener/archive/refs/tags/3.zip +dependencies#2 = https://github.com/Insality/druid/archive/refs/tags/0.11.0.zip [library] include_dirs = panthera diff --git a/panthera/adapters/adapter_go.lua b/panthera/adapters/adapter_go.lua index 6084c1b..a0267f0 100644 --- a/panthera/adapters/adapter_go.lua +++ b/panthera/adapters/adapter_go.lua @@ -92,6 +92,41 @@ local function split(inputstr, sep) end +---@param collection_name string|nil +---@param objects table|nil +---@return function(node_id: string): hash|url +local function create_get_node_function(collection_name, objects) + return function(node_id) + if collection_name then + node_id = collection_name .. "/" .. node_id + end + + -- Acquire component id + local split_index = string.find(node_id, "#") + if split_index then + local object_id = string.sub(node_id, 1, split_index - 1) + local fragment_id = string.sub(node_id, split_index + 1) + + local object_path = hash("/" .. object_id) + if objects then + object_path = objects[object_path] --[[@as hash]] + end + + local object_url = msg.url(object_path) + object_url.fragment = hash(fragment_id) + + return object_url + end + + local object_path = hash("/" .. node_id) + if objects then + object_path = objects[object_path] --[[@as hash]] + end + return object_path + end +end + + ---@param node node ---@param property_id string ---@param value any @@ -125,10 +160,8 @@ local function get_trigger_property_id(node, property_id) return label.get_text(node) end if defold_property_id == "texture" then - local texture_name = sprite.get_flipbook(node) - if texture_name == "" then - return "" - end + local texture_name = go.get(node, "animation") + pprint(texture_name) local splitted = split(texture_name, "/") return splitted[#splitted] end @@ -203,7 +236,7 @@ end ---@param node node ---@param property_id string ----@param easing userdata +---@param easing userdata|number[] ---@param duration number ---@param end_value number local function tween_animation_key(node, property_id, easing, duration, end_value) @@ -216,31 +249,14 @@ local function tween_animation_key(node, property_id, easing, duration, end_valu end ----@param node_id string @If node_id contains "#", split it and get full url ----@return hash|url -local function get_node(node_id) - if string.find(node_id, "#") then - local object_id = string.sub(node_id, 1, string.find(node_id, "#") - 1) - local fragment_id = string.sub(node_id, string.find(node_id, "#") + 1) - - local object_url = msg.url(hash("/" .. object_id)) - object_url.fragment = fragment_id - - return object_url - end - - return hash("/" .. node_id) -end - - local M = { - get_node = get_node, get_easing = get_easing, set_node_property = set_node_property, get_node_property = get_node_property, tween_animation_key = tween_animation_key, stop_tween = stop_tween, trigger_animation_key = trigger_animation_key, + create_get_node_function = create_get_node_function, } diff --git a/panthera/adapters/adapter_gui.lua b/panthera/adapters/adapter_gui.lua index 32afc28..3a401a4 100644 --- a/panthera/adapters/adapter_gui.lua +++ b/panthera/adapters/adapter_gui.lua @@ -1,3 +1,4 @@ +---@diagnostic disable: undefined-field, return-type-mismatch -- In Defold 1.2.180+ gui.set and gui.get functions were added. Rotation was changed to Euler local IS_DEFOLD_180 = (gui.set and gui.get) @@ -38,6 +39,7 @@ local PROPERTY_TO_DEFOLD_TWEEN_PROPERTY = { local PROPERTY_TO_DEFOLD_TRIGGER_PROPERTY = { ["text"] = "text", + ["layer"] = "layer", ["texture"] = "texture", ["enabled"] = "enabled", ["visible"] = "visible", @@ -113,7 +115,7 @@ local BLEND_MODE_TO_DEFOLD_BLEND_MODE = { ["alpha"] = gui.BLEND_ALPHA, ["add"] = gui.BLEND_ADD, ["multiply"] = gui.BLEND_MULT, - ["screen"] = 4, -- No screen blend mode in Defold gui* bindings, pick from source + ["screen"] = gui.BLEND_SCREEN } local OUTER_BOUNDS_TO_DEFOLD_OUTER_BOUNDS = { @@ -174,6 +176,7 @@ local TWEEN_DEFOLD_SET_GET = { local TRIGGER_DEFOLD_SET_GET = { ["text"] = { "text", "text", gui.get_text, gui.set_text }, + ["layer"] = { "layer", "layer", gui.get_layer, gui.set_layer }, ["texture"] = { "texture", "texture", gui.get_texture, gui.set_texture }, ["enabled"] = { "enabled", "enabled", gui.is_enabled, gui.set_enabled }, ["visible"] = { "visible", "visible", gui.get_visible, gui.set_visible }, @@ -204,10 +207,14 @@ local function split(inputstr, sep) end +local LAYER_EMPTY = hash("") local DEFOLD_TRIGGER_SETTER = { ["text"] = function(node, value) gui.set_text(node, value) end, + ["layer"] = function(node, value) + gui.set_layer(node, value or LAYER_EMPTY) + end, ["texture"] = function(node, texture_name) if texture_name ~= "" then local splitted = split(texture_name, "/") @@ -254,6 +261,24 @@ local DEFOLD_TRIGGER_SETTER = { } +---@param template string|nil @GUI template path to load nodes from. Pass nil if no template is used +---@param nodes table|nil @Table with nodes from gui.clone_tree() function. Pass nil if no nodes are used +---@return fun(node_id: string): node +local function create_get_node_function(template, nodes) + return function(node_id) + if template then + node_id = template .. "/" .. node_id + end + + if nodes then + return nodes[node_id] + else + return gui.get_node(node_id) + end + end +end + + ---@param node node ---@param property_id string ---@param value any @@ -366,7 +391,7 @@ end ---@param node node ---@param property_id string ----@param easing userdata +---@param easing userdata|number[] ---@param duration number ---@param end_value number local function tween_animation_key(node, property_id, easing, duration, end_value) @@ -380,13 +405,13 @@ end local M = { - get_node = gui.get_node, get_easing = get_easing, stop_tween = stop_tween, set_node_property = set_node_property, get_node_property = get_node_property, tween_animation_key = tween_animation_key, trigger_animation_key = trigger_animation_key, + create_get_node_function = create_get_node_function, } diff --git a/panthera/annotations.lua b/panthera/annotations.lua index 8f5cc3e..a3012a7 100644 --- a/panthera/annotations.lua +++ b/panthera/annotations.lua @@ -18,6 +18,12 @@ ---@field group_animation_keys table>> @group_animation_keys[animation_id][node_id][property_id]: keys[]. Value filled at loading animation data ---@field animations_dict table @animations_dict[animation_id]: animation. Value filled at loading animation data +---@class panthera.animation.project_file +---@field data panthera.animation.data @Animation data +---@field format string @Animation format +---@field version string @Animation version +---@field type string @Animation type. Example: "animation_editor", "atlas" + ---@class panthera.animation.data.animation_key ---@field key_type string ---@field node_id string @@ -27,9 +33,11 @@ ---@field start_value number ---@field end_value number ---@field easing string +---@field easing_custom number[]|nil ---@field start_data string ---@field data string ---@field event_id string +---@field is_editor_only boolean ---@class panthera.animation.state ---@field adapter panthera.adapter @Adapter to use for animation @@ -38,8 +46,8 @@ ---@field nodes table @Animation nodes used in animation ---@field childs panthera.animation.state[]|nil @List of active child animations ---@field get_node fun(node_id: string): node @Function to get node by node_id. Default is defined in adapter ----@field previous_animation panthera.animation.data.animation|nil @Previous runned animation ----@field animation panthera.animation.data.animation|nil @Current animation +---@field animation_id string|nil @Current animation id +---@field previous_animation_id string|nil @Previous runned animation id ---@field animation_path string @Animation path to JSON file ---@field animation_keys_index number @Animation keys index ---@field timer_id hash|nil @Timer id for animation @@ -55,7 +63,7 @@ ---@class panthera.adapter ---@field get_node fun(node_id: string): node @Function to get node by node_id. ---@field get_easing fun(easing_id: string): hash @Function to get defold easing by easing_id. Default is gui.EASING ----@field tween_animation_key fun(node: node, property_id: string, easing: hash, duration: number, end_value: number): nil @Function to tween animation key. +---@field tween_animation_key fun(node: node, property_id: string, easing: hash|number[], duration: number, end_value: number): nil @Function to tween animation key. ---@field trigger_animation_key fun(node: node, property_id: string, value: any): nil @Function to trigger animation key. ---@field event_animation_key fun(node: node, key: panthera.animation.data.animation_key): nil @Function to trigger event in animation. ---@field set_node_property fun(node: node, property_id: string, value: number|string): boolean @Function to set node property. Return true if success diff --git a/panthera/panthera.lua b/panthera/panthera.lua index b764376..11694dd 100644 --- a/panthera/panthera.lua +++ b/panthera/panthera.lua @@ -1,6 +1,6 @@ local adapter_go = require("panthera.adapters.adapter_go") local adapter_gui = require("panthera.adapters.adapter_gui") -local panthera_system = require("panthera.panthera_system") +local panthera_internal = require("panthera.panthera_internal") ---@class panthera local M = {} @@ -9,37 +9,42 @@ local TIMER_DELAY = 1/60 ---@param logger_instance panthera.logger|nil function M.set_logger(logger_instance) - panthera_system.logger = logger_instance or panthera_system.empty_logger + panthera_internal.logger = logger_instance or panthera_internal.empty_logger end ----Load animation from JSON file and create it with Panthera GO adapter ----@param animation_path string ----@param get_node (fun(node_id: string): hash|url)|nil @Function to get node by node_id. Default is defined in adapter +---Load animation from JSON file or direct data and create it with Panthera GO adapter +---@param animation_path_or_data string|table @Path to JSON animation file in custom resources or table with animation data +---@param collection_name string|nil @Collection name to load nodes from. Pass nil if no collection is used +---@param objects table|nil @Table with game objects from collectionfactory. Pass nil if no objects are used ---@return panthera.animation.state|nil @Animation data or nil if animation can't be loaded, error message -function M.create_go(animation_path, get_node) - return M.create(animation_path, adapter_go, get_node) +function M.create_go(animation_path_or_data, collection_name, objects) + local get_node = adapter_go.create_get_node_function(collection_name, objects) + return M.create(animation_path_or_data, adapter_go, get_node) end ----Load animation from JSON file and create it with Panthera GUI adapter ----@param animation_path string ----@param get_node (fun(node_id: string): node)|nil @Function to get node by node_id. Default is defined in adapter +---Load animation from JSON file or direct data and create it with Panthera GUI adapter +---@param animation_path_or_data string|table @Path to JSON animation file in custom resources or table with animation data +---@param template string|nil @The GUI template id to load nodes from. Pass nil if no template is used +---@param nodes table|nil @Table with nodes from gui.clone_tree() function. Pass nil if no nodes are used ---@return panthera.animation.state|nil @Animation data or nil if animation can't be loaded, error message -function M.create_gui(animation_path, get_node) - return M.create(animation_path, adapter_gui, get_node) +function M.create_gui(animation_path_or_data, template, nodes) + local get_node = adapter_gui.create_get_node_function(template, nodes) + return M.create(animation_path_or_data, adapter_gui, get_node) end ---Load animation from JSON file ----@param animation_path string +---@param animation_path_or_data string|table @Path to JSON animation file in custom resources or table with animation data ---@param adapter panthera.adapter ----@param get_node (fun(node_id: string): node)|nil @Function to get node by node_id. Default is defined in adapter +---@param get_node (fun(node_id: string): node) @Function to get node by node_id. Default is defined in adapter ---@return panthera.animation.state|nil @Animation data or nil if animation can't be loaded, error message -function M.create(animation_path, adapter, get_node) - local animation_data, error_reason = panthera_system.load(animation_path, false) - if not animation_data then - panthera_system.logger:error("Can't load Panthera animation", error_reason) +function M.create(animation_path_or_data, adapter, get_node) + local animation_data, animation_path, error_reason = panthera_internal.load(animation_path_or_data, false) + + if not animation_data or not animation_path then + panthera_internal.logger:error("Can't load Panthera animation", error_reason) return nil end @@ -53,9 +58,9 @@ function M.create(animation_path, adapter, get_node) animation = nil, current_time = 0, adapter = adapter, + get_node = get_node, animation_keys_index = 1, animation_path = animation_path, - get_node = get_node or adapter.get_node, } return animation_state @@ -78,52 +83,52 @@ end ---@param options panthera.options|nil function M.play(animation_state, animation_id, options) if not animation_state then - panthera_system.logger:warn("Can't play animation, animation_state is nil", animation_id) + panthera_internal.logger:warn("Can't play animation, animation_state is nil", animation_id) return end - local animation_data = panthera_system.get_animation_data(animation_state) + local animation_data = panthera_internal.get_animation_data(animation_state) if not animation_data then - panthera_system.logger:warn("Can't play animation, animation_data is nil", { + panthera_internal.logger:warn("Can't play animation, animation_data is nil", { animation_path = animation_state.animation_path, animation_id = animation_id, }) return nil end - local animation = panthera_system.get_animation_by_animation_id(animation_data, animation_id) + local animation = panthera_internal.get_animation_by_animation_id(animation_data, animation_id) if not animation then - panthera_system.logger:warn("Animation is not found", { + panthera_internal.logger:warn("Animation is not found", { animation_path = animation_state.animation_path, animation_id = animation_id, }) return nil end - if animation_state.animation then + if animation_state.animation_id then M.stop(animation_state) end options = options or {} - animation_state.animation = animation + animation_state.animation_id = animation.animation_id animation_state.animation_keys_index = 1 if not options.is_skip_init then -- Reset all previuosly animated nodes to initial state - if animation_state.previous_animation then - panthera_system.reset_animation_state(animation_state, animation_state.previous_animation.animation_id) - animation_state.previous_animation = nil + if animation_state.previous_animation_id then + panthera_internal.reset_animation_state(animation_state, animation_state.previous_animation_id) + animation_state.previous_animation_id = nil end -- If we have initial animation, we should set up it here? if animation.initial_state and animation_state ~= "" then - local initial_animation = panthera_system.get_animation_by_animation_id(animation_data, animation.initial_state) + local initial_animation = panthera_internal.get_animation_by_animation_id(animation_data, animation.initial_state) if initial_animation then - panthera_system.set_animation_state_at_time(animation_state, initial_animation.animation_id, initial_animation.duration) + panthera_internal.set_animation_state_at_time(animation_state, initial_animation.animation_id, initial_animation.duration) end end - panthera_system.set_animation_state_at_time(animation_state, animation.animation_id, 0) + panthera_internal.set_animation_state_at_time(animation_state, animation.animation_id, 0) end -- Start animation update timer @@ -135,17 +140,17 @@ function M.play(animation_state, animation_id, options) local speed = (options.speed or 1) * animation_state.speed animation_state.current_time = animation_state.current_time + dt * speed - M._update_animation(animation_state, options) + M._update_animation(animation, animation_state, options) end) timer.trigger(animation_state.timer_id) end ---@private +---@param animation panthera.animation.data.animation ---@param animation_state panthera.animation.state ---@param options panthera.options -function M._update_animation(animation_state, options) - local animation = animation_state.animation +function M._update_animation(animation, animation_state, options) if not animation then return end @@ -159,7 +164,7 @@ function M._update_animation(animation_state, options) animation_state.animation_keys_index = index + 1 if key.key_type ~= "animation" then - panthera_system.run_timeline_key(animation_state, key, options) + panthera_internal.run_timeline_key(animation_state, key, options) else -- Create a new animation child track local animation_path = animation_state.animation_path @@ -178,7 +183,7 @@ function M._update_animation(animation_state, options) is_skip_init = true, speed = (animation_duration / key.duration) * speed, callback = function() - panthera_system.remove_child_animation(animation_state, child_state) + panthera_internal.remove_child_animation(animation_state, child_state) end }) end @@ -190,7 +195,7 @@ function M._update_animation(animation_state, options) end -- If current time >= animation duration - stop animation - if animation_state.current_time >= animation_state.animation.duration then + if animation_state.current_time >= animation.duration then M.stop(animation_state) if options.callback then @@ -209,26 +214,26 @@ end ---@param animation_id string ---@param time number function M.set_time(animation_state, animation_id, time) - local animation_data = panthera_system.get_animation_data(animation_state) + local animation_data = panthera_internal.get_animation_data(animation_state) assert(animation_data, "Animation data is not loaded") - local animation = panthera_system.get_animation_by_animation_id(animation_data, animation_id) + local animation = panthera_internal.get_animation_by_animation_id(animation_data, animation_id) assert(animation, "Animation is not found: " .. animation_id) if M.is_playing(animation_state) then M.stop(animation_state) end - if animation_state.previous_animation then - panthera_system.reset_animation_state(animation_state, animation_state.previous_animation.animation_id) - animation_state.previous_animation = nil + if animation_state.previous_animation_id then + panthera_internal.reset_animation_state(animation_state, animation_state.previous_animation_id) + animation_state.previous_animation_id = nil end animation_state.current_time = time - animation_state.animation = animation + animation_state.animation_id = animation.animation_id animation_state.animation_keys_index = 1 - panthera_system.set_animation_state_at_time(animation_state, animation.animation_id, time) + panthera_internal.set_animation_state_at_time(animation_state, animation.animation_id, time) end @@ -245,7 +250,7 @@ end ---@return boolean @True if animation was stopped, false if animation is not playing function M.stop(animation_state) if not animation_state then - panthera_system.logger:warn("Can't stop animation, animation_state is nil") + panthera_internal.logger:warn("Can't stop animation, animation_state is nil") return false end @@ -254,22 +259,22 @@ function M.stop(animation_state) animation_state.timer_id = nil end - local previous_animation = animation_state.animation - animation_state.previous_animation = previous_animation + local previous_animation_id = animation_state.animation_id + animation_state.previous_animation_id = previous_animation_id -- Stop all tweens started by animation - if previous_animation then + if previous_animation_id then local adapter = animation_state.adapter - local animation_data = panthera_system.get_animation_data(animation_state) + local animation_data = panthera_internal.get_animation_data(animation_state) if animation_data then - local group_keys = animation_data.group_animation_keys[previous_animation.animation_id] + local group_keys = animation_data.group_animation_keys[previous_animation_id] for node_id, node_keys in pairs(group_keys) do for property_id, keys in pairs(node_keys) do if keys[1] and keys[1].key_type == "tween" then local key_end_time = keys[1].start_time + keys[1].duration local is_finished = key_end_time <= animation_state.current_time - local node = panthera_system.get_node(animation_state, node_id) + local node = panthera_internal.get_node(animation_state, node_id) if node and not is_finished then adapter.stop_tween(node, property_id) end @@ -277,14 +282,14 @@ function M.stop(animation_state) end end else - panthera_system.logger:warn("Can't stop animation, animation_data is nil", { + panthera_internal.logger:warn("Can't stop animation, animation_data is nil", { animation_path = animation_state.animation_path, - animation_id = previous_animation.animation_id + animation_id = previous_animation_id }) end end - animation_state.animation = nil + animation_state.animation_id = nil animation_state.current_time = 0 animation_state.animation_keys_index = 1 @@ -303,10 +308,10 @@ end ---@param animation_id string ---@return number function M.get_duration(animation_state, animation_id) - local animation_data = panthera_system.get_animation_data(animation_state) + local animation_data = panthera_internal.get_animation_data(animation_state) assert(animation_data, "Animation data is not loaded") - local animation = panthera_system.get_animation_by_animation_id(animation_data, animation_id) + local animation = panthera_internal.get_animation_by_animation_id(animation_data, animation_id) assert(animation, "Animation is not found: " .. animation_id) return animation.duration @@ -321,14 +326,11 @@ function M.is_playing(animation_state) end ----Get current animation id +---Get current animation ---@param animation_state panthera.animation.state @Animation state ---@return string|nil @Animation id or nil if animation is not playing function M.get_latest_animation_id(animation_state) - if animation_state.animation then - return animation_state.animation.animation_id - end - return animation_state.previous_animation and animation_state.previous_animation.animation_id or nil + return animation_state.animation_id or animation_state.previous_animation_id end @@ -336,7 +338,7 @@ end ---@param animation_state panthera.animation.state ---@return string[] function M.get_animations(animation_state) - local animation_data = panthera_system.get_animation_data(animation_state) + local animation_data = panthera_internal.get_animation_data(animation_state) if not animation_data then return {} end @@ -358,10 +360,10 @@ end ---@param animation_path string|nil @If nil - reload all loaded animations function M.reload_animation(animation_path) if animation_path then - panthera_system.load(animation_path, true) + panthera_internal.load(animation_path, true) else - for path in pairs(panthera_system.LOADED_ANIMATIONS) do - panthera_system.load(path, true) + for path in pairs(panthera_internal.LOADED_ANIMATIONS) do + panthera_internal.load(path, true) end end end diff --git a/panthera/panthera_system.lua b/panthera/panthera_internal.lua similarity index 86% rename from panthera/panthera_system.lua rename to panthera/panthera_internal.lua index b8d3102..1cd45fd 100644 --- a/panthera/panthera_system.lua +++ b/panthera/panthera_internal.lua @@ -24,32 +24,56 @@ M.logger = { } ----@type table +---The list of loaded animations. +---@type table @Animation path -> animation data M.LOADED_ANIMATIONS = {} +-- The list of animations that loaded directly from the table. We can't reload them on runtime, and we should not clear them on hot reload +---@type table @Animation fake path -> true +M.INLINE_ANIMATIONS = {} + M.PROJECT_FOLDER = nil -- Current game project folder, used for hot reload animations in debug mode M.IS_HOTRELOAD_ANIMATIONS = nil local IS_DEBUG = sys.get_engine_info().is_debug ---Load animation from file and store it in cache ----@param animation_path string ----@param is_cache_reset boolean @If true - animation will be reloaded from file ----@return panthera.animation.data|nil, string|nil @animation_data, error_reason -function M.load(animation_path, is_cache_reset) - if is_cache_reset then +---@param animation_path_or_data string|panthera.animation.project_file @Path to the animation file or animation table +---@param is_cache_reset boolean @If true - animation will be reloaded from file. Will be ignored for inline animations +---@return panthera.animation.data|nil, string|nil, string|nil @animation_data, animation_path, error_reason. +function M.load(animation_path_or_data, is_cache_reset) + -- If we have already loaded animation table + local is_table = type(animation_path_or_data) == "table" + if is_table then + local animation_path = M._get_fake_animation_path() + local project_data = animation_path_or_data --[[@as panthera.animation.project_file]] + + local data = project_data.data + M._preprocess_animation_keys(data) + M.LOADED_ANIMATIONS[animation_path] = data + M.INLINE_ANIMATIONS[animation_path] = true + + return data, animation_path, nil + end + + -- If we have path to the file + assert(type(animation_path_or_data) == "string", "Path should be a string") + local animation_path = animation_path_or_data --[[@as string]] + local is_inline_animation = M.INLINE_ANIMATIONS[animation_path] + if is_cache_reset and not is_inline_animation then M.LOADED_ANIMATIONS[animation_path] = nil end if not M.LOADED_ANIMATIONS[animation_path] then local animation, error_reason = M._get_animation_by_path(animation_path) if not animation then - return nil, error_reason + return nil, nil, error_reason end + M._preprocess_animation_keys(animation) M.LOADED_ANIMATIONS[animation_path] = animation end - return M.LOADED_ANIMATIONS[animation_path], nil + return M.LOADED_ANIMATIONS[animation_path], animation_path, nil end @@ -292,7 +316,7 @@ function M.run_timeline_key(animation_state, key, options) local node = M.get_node(animation_state, key.node_id) if node and key.key_type == "tween" then - local easing = adapter.get_easing(key.easing) + local easing = key.easing_custom or adapter.get_easing(key.easing) local delta = key.end_value - key.start_value local start_value = key.start_value @@ -334,7 +358,7 @@ function M.event_animation_key(node, key, callback_event) if key.duration == 0 then callback_event(key.event_id, node, key.data, key.end_value) else - local easing = tweener[key.easing] or tweener.linear + local easing = key.easing_custom or tweener[key.easing] or tweener.linear tweener.tween(easing, key.start_value, key.end_value, key.duration, function(value) callback_event(key.event_id, node, key.data, value) end) @@ -455,16 +479,13 @@ function M._get_animation_by_path(path) return nil, error end + resource = resource --[[@as panthera.animation.project_file]] local filetype = resource.type if filetype ~= "animation_editor" then return nil, "The JSON file is not an animation editor file" end - ---@type panthera.animation.data - local data = resource.data - M._preprocess_animation_keys(data) - - return data, nil + return resource.data, nil end @@ -495,7 +516,7 @@ function M._preprocess_animation_keys(data) for index = 1, #data.animations do local animation = data.animations[index] - for key_index = 1, #animation.animation_keys do + for key_index = #animation.animation_keys, 1, -1 do -- These default keys can be nil local key = animation.animation_keys[key_index] key.start_value = key.start_value or 0 @@ -503,6 +524,16 @@ function M._preprocess_animation_keys(data) key.end_value = key.end_value or 0 key.duration = key.duration or 0 key.node_id = key.node_id or "" + + -- Remove editor only keys, they are used only in Panthera Editor to preview animations + if key.is_editor_only then + table.remove(animation.animation_keys, key_index) + end + + -- Custom easings have more priority than easing and Defold requires vector for custom easing + if key.easing_custom then + key.easing_custom = vmath.vector(key.easing_custom) + end end table.sort(animation.animation_keys, M._sort_keys_function) @@ -558,7 +589,7 @@ function M._get_key_value_at_time(key, time) return key.end_value end - local easing = tweener[key.easing] or tweener.linear + local easing = key.easing_custom or tweener[key.easing] or tweener.linear local value = tweener.ease(easing, key.start_value, key.end_value, key.duration, time - key.start_time) return value @@ -568,17 +599,13 @@ end ---Get current application folder (only desktop) ---@return string|nil @Current application folder, nil if failed function M._get_current_game_project_folder() - local tmpfile = os.tmpname() - os.execute("pwd > " .. tmpfile) - - local file = io.open(tmpfile, "r") + local file = io.popen("pwd") if not file then return nil end local pwd = file:read("*l") file:close() - os.remove(tmpfile) if not pwd then return nil @@ -596,6 +623,13 @@ function M._get_current_game_project_folder() end +local path_counter = 0 +function M._get_fake_animation_path() + path_counter = path_counter + 1 + return "panthera_animation_table_" .. path_counter +end + + -- Init hot reload animations if IS_DEBUG then M.IS_HOTRELOAD_ANIMATIONS = sys.get_config_int("panthera.hotreload_animations", 0) == 1 diff --git a/resources/test_go.json b/resources/test_go.json new file mode 100644 index 0000000..2bbe33a --- /dev/null +++ b/resources/test_go.json @@ -0,0 +1 @@ +{"data":{"animations":[{"animation_id":"default","animation_keys":[{"duration":1,"easing":"outsine","end_value":1.4,"key_type":"tween","node_id":"panthera#sprite","property_id":"scale_x","start_value":1},{"duration":1,"easing":"outsine","end_value":1.4,"key_type":"tween","node_id":"panthera#sprite","property_id":"scale_y","start_value":1},{"duration":2,"easing":"outsine","end_value":0.189,"key_type":"tween","node_id":"panthera#label","property_id":"color_b","start_value":1},{"duration":2,"easing":"outsine","end_value":0.189,"key_type":"tween","node_id":"panthera#label","property_id":"color_g","start_value":1},{"duration":2,"easing":"outsine","end_value":0.816,"key_type":"tween","node_id":"panthera#label","property_id":"color_r","start_value":1},{"data":"Hey!","easing":"linear","key_type":"trigger","node_id":"panthera#label","property_id":"text","start_data":"Text","start_time":0.5},{"data":"Text is","easing":"linear","key_type":"trigger","node_id":"panthera#label","property_id":"text","start_data":"Hey!","start_time":1},{"data":"example/ui_circle","easing":"linear","key_type":"trigger","node_id":"panthera#sprite","property_id":"texture","start_data":"example/panthera","start_time":1},{"duration":1,"easing":"outsine","end_value":1,"key_type":"tween","node_id":"panthera#sprite","property_id":"scale_x","start_time":1,"start_value":1.4},{"duration":1,"easing":"outsine","end_value":1,"key_type":"tween","node_id":"panthera#sprite","property_id":"scale_y","start_time":1,"start_value":1.4},{"data":"CHANING","easing":"linear","key_type":"trigger","node_id":"panthera#label","property_id":"text","start_data":"Text is","start_time":2},{"duration":1,"easing":"outsine","end_value":1,"key_type":"tween","node_id":"panthera#label","property_id":"color_b","start_time":2,"start_value":0.189},{"duration":1,"easing":"outsine","end_value":1,"key_type":"tween","node_id":"panthera#label","property_id":"color_g","start_time":2,"start_value":0.189},{"duration":1,"easing":"outsine","end_value":1,"key_type":"tween","node_id":"panthera#label","property_id":"color_r","start_time":2,"start_value":0.816}],"duration":3}],"metadata":{"fps":60,"gizmo_steps":{"time":0.1},"settings":{"font_size":40}},"nodes":[{"blend_mode":"alpha","clipping_mode":"none","clipping_visible":true,"color_a":1,"color_b":1,"color_g":1,"color_r":1,"enabled":true,"inherit_alpha":true,"node_id":"panthera","node_index":1,"node_type":"box","pivot":"pivot_center","scale_x":1,"scale_y":1,"scale_z":1,"size_mode":"manual","size_x":1,"size_y":1},{"blend_mode":"alpha","clipping_mode":"none","clipping_visible":true,"color_a":1,"color_b":1,"color_g":1,"color_r":1,"enabled":true,"fill_angle":360,"inherit_alpha":true,"node_id":"panthera#sprite","node_index":2,"node_type":"box","outer_bounds":"ellipse","parent":"panthera","perimeter_vertices":10,"pivot":"pivot_center","scale_x":1,"scale_y":1,"scale_z":1,"size_mode":"auto","size_x":128,"size_y":128,"text_leading":1,"texture":"example/panthera","visible":true},{"blend_mode":"alpha","clipping_mode":"none","clipping_visible":true,"color_a":1,"color_b":1,"color_g":1,"color_r":1,"enabled":true,"fill_angle":360,"inherit_alpha":true,"node_id":"panthera#label","node_index":3,"node_type":"text","outer_bounds":"ellipse","parent":"panthera","perimeter_vertices":10,"pivot":"pivot_center","position_y":70,"scale_x":1,"scale_y":1,"scale_z":1,"size_mode":"manual","size_x":128,"size_y":32,"text":"Text","text_leading":1,"visible":true}]},"format":"json","type":"animation_editor","version":1} \ No newline at end of file diff --git a/resources/test_gui.json b/resources/test_gui.json new file mode 100644 index 0000000..4790db6 --- /dev/null +++ b/resources/test_gui.json @@ -0,0 +1 @@ +{"data":{"animations":[{"animation_id":"default","animation_keys":[{"duration":1,"easing":"outsine","end_value":-1,"key_type":"tween","node_id":"box","property_id":"scale_x","start_value":1},{"duration":1,"easing":"outsine","key_type":"tween","node_id":"pie","property_id":"fill_angle","start_value":360},{"duration":1,"easing":"outsine","end_value":25,"key_type":"tween","node_id":"box","property_id":"rotation_z"},{"duration":1.5,"easing":"outsine","end_value":0.27,"key_type":"tween","node_id":"text","property_id":"color_b","start_value":0.088},{"duration":1.5,"easing":"outsine","end_value":0.27,"key_type":"tween","node_id":"text","property_id":"color_g","start_value":0.088},{"duration":1.5,"easing":"outsine","end_value":0.83,"key_type":"tween","node_id":"text","property_id":"color_r","start_value":0.091},{"data":"Just","easing":"linear","key_type":"trigger","node_id":"text","property_id":"text","start_data":"Text","start_time":0.5},{"data":"Test","easing":"linear","key_type":"trigger","node_id":"text","property_id":"text","start_data":"Just","start_time":1},{"duration":1,"easing":"outsine","key_type":"tween","node_id":"box","property_id":"rotation_z","start_time":1,"start_value":25},{"duration":1,"easing":"outsine","end_value":1,"key_type":"tween","node_id":"box","property_id":"scale_x","start_time":1,"start_value":-1},{"duration":1,"easing":"outsine","end_value":360,"key_type":"tween","node_id":"pie","property_id":"fill_angle","start_time":1},{"data":"Text Change","easing":"linear","key_type":"trigger","node_id":"text","property_id":"text","start_data":"Test","start_time":1.5},{"duration":1.5,"easing":"outsine","end_value":0.09,"key_type":"tween","node_id":"text","property_id":"color_b","start_time":1.5,"start_value":0.27},{"duration":1.5,"easing":"outsine","end_value":0.09,"key_type":"tween","node_id":"text","property_id":"color_g","start_time":1.5,"start_value":0.27},{"duration":1.5,"easing":"outsine","end_value":0.09,"key_type":"tween","node_id":"text","property_id":"color_r","start_time":1.5,"start_value":0.83}],"duration":3}],"metadata":{"fps":60,"gizmo_steps":{"time":0.1},"gui_path":"/example/example_go_gui_templates/test_gui.gui","settings":{"font_size":40}},"nodes":[]},"format":"json","type":"animation_editor","version":1} \ No newline at end of file diff --git a/resources/test_layer.json b/resources/test_layer.json new file mode 100644 index 0000000..cdd8198 --- /dev/null +++ b/resources/test_layer.json @@ -0,0 +1 @@ +{"data":{"animations":[{"animation_id":"default","animation_keys":[{"duration":0.34,"easing":"outsine","end_value":-125,"key_type":"tween","node_id":"card_1","property_id":"position_x"},{"duration":0.34,"easing":"outsine","end_value":-15,"key_type":"tween","node_id":"card_2","property_id":"rotation_z"},{"duration":0.34,"easing":"outsine","end_value":5,"key_type":"tween","node_id":"card_2","property_id":"position_y","start_value":-55},{"duration":0.34,"easing":"outsine","end_value":15,"key_type":"tween","node_id":"card_1","property_id":"rotation_z"},{"duration":0.34,"easing":"outsine","end_value":20,"key_type":"tween","node_id":"card_1","property_id":"position_y"},{"duration":0.34,"easing":"outsine","end_value":365,"key_type":"tween","node_id":"card_2","property_id":"position_x","start_value":245},{"duration":0.35,"easing":"outsine","end_value":-55,"key_type":"tween","node_id":"card_2","property_id":"position_y","start_time":0.34,"start_value":5},{"duration":0.35,"easing":"outsine","key_type":"tween","node_id":"card_1","property_id":"position_y","start_time":0.34,"start_value":20},{"duration":0.35,"easing":"outsine","key_type":"tween","node_id":"card_1","property_id":"rotation_z","start_time":0.34,"start_value":15},{"duration":0.35,"easing":"outsine","key_type":"tween","node_id":"card_2","property_id":"rotation_z","start_time":0.34,"start_value":-15},{"duration":0.35,"easing":"outsine","end_value":30,"key_type":"tween","node_id":"card_1","property_id":"position_x","start_time":0.34,"start_value":-125},{"duration":0.35,"easing":"outsine","end_value":245,"key_type":"tween","node_id":"card_2","property_id":"position_x","start_time":0.34,"start_value":365},{"data":"up","easing":"linear","key_type":"trigger","node_id":"card_1","property_id":"layer","start_time":0.36},{"duration":0.34,"easing":"outsine","end_value":-15,"key_type":"tween","node_id":"card_2","property_id":"rotation_z","start_time":1.11},{"duration":0.34,"easing":"outsine","end_value":5,"key_type":"tween","node_id":"card_2","property_id":"position_y","start_time":1.11,"start_value":-55},{"duration":0.34,"easing":"outsine","end_value":365,"key_type":"tween","node_id":"card_2","property_id":"position_x","start_time":1.11,"start_value":245},{"duration":0.34,"easing":"outsine","end_value":-125,"key_type":"tween","node_id":"card_1","property_id":"position_x","start_time":1.19,"start_value":30},{"duration":0.34,"easing":"outsine","end_value":15,"key_type":"tween","node_id":"card_1","property_id":"rotation_z","start_time":1.19},{"duration":0.34,"easing":"outsine","end_value":20,"key_type":"tween","node_id":"card_1","property_id":"position_y","start_time":1.19},{"duration":0.35,"easing":"outsine","end_value":-55,"key_type":"tween","node_id":"card_2","property_id":"position_y","start_time":1.45,"start_value":5},{"duration":0.35,"easing":"outsine","key_type":"tween","node_id":"card_2","property_id":"rotation_z","start_time":1.45,"start_value":-15},{"duration":0.35,"easing":"outsine","end_value":245,"key_type":"tween","node_id":"card_2","property_id":"position_x","start_time":1.45,"start_value":365},{"duration":0.35,"easing":"outsine","key_type":"tween","node_id":"card_1","property_id":"position_y","start_time":1.53,"start_value":20},{"duration":0.35,"easing":"outsine","key_type":"tween","node_id":"card_1","property_id":"rotation_z","start_time":1.53,"start_value":15},{"duration":0.35,"easing":"outsine","end_value":30,"key_type":"tween","node_id":"card_1","property_id":"position_x","start_time":1.53,"start_value":-125},{"easing":"linear","key_type":"trigger","node_id":"card_1","property_id":"layer","start_data":"up","start_time":1.54}],"duration":2},{"animation_id":"asd","animation_keys":[],"duration":1},{"animation_id":"spin","animation_keys":[{"easing":"outsine","end_value":-200,"key_type":"tween","node_id":"card_1","property_id":"position_x"},{"data":"up","easing":"linear","key_type":"trigger","node_id":"card_1","property_id":"layer"},{"easing":"outsine","key_type":"tween","node_id":"card_2","property_id":"position_y","start_value":-55},{"easing":"outsine","end_value":200,"key_type":"tween","node_id":"card_2","property_id":"position_x","start_value":245},{"duration":0.5,"easing":"linear","end_value":-150,"key_type":"tween","node_id":"card_1","property_id":"position_y"},{"duration":0.5,"easing":"insine","key_type":"tween","node_id":"card_1","property_id":"position_x","start_value":-200},{"duration":0.5,"easing":"insine","key_type":"tween","node_id":"card_2","property_id":"position_x","start_value":200},{"duration":0.5,"easing":"outsine","end_value":0.9,"key_type":"tween","node_id":"card_2","property_id":"scale_x","start_value":1},{"duration":0.5,"easing":"outsine","end_value":0.9,"key_type":"tween","node_id":"card_2","property_id":"scale_y","start_value":1},{"duration":0.5,"easing":"outsine","end_value":1.1,"key_type":"tween","node_id":"card_1","property_id":"scale_x","start_value":1},{"duration":0.5,"easing":"outsine","end_value":1.1,"key_type":"tween","node_id":"card_1","property_id":"scale_y","start_value":1},{"duration":0.5,"easing":"linear","end_value":150,"key_type":"tween","node_id":"card_2","property_id":"position_y"},{"duration":0.5,"easing":"outsine","end_value":-200,"key_type":"tween","node_id":"card_2","property_id":"position_x","start_time":0.5},{"duration":0.5,"easing":"linear","key_type":"tween","node_id":"card_1","property_id":"position_y","start_time":0.5,"start_value":-150},{"duration":0.5,"easing":"linear","key_type":"tween","node_id":"card_2","property_id":"position_y","start_time":0.5,"start_value":150},{"duration":0.5,"easing":"outsine","end_value":1,"key_type":"tween","node_id":"card_1","property_id":"scale_x","start_time":0.5,"start_value":1.1},{"duration":0.5,"easing":"outsine","end_value":1,"key_type":"tween","node_id":"card_1","property_id":"scale_y","start_time":0.5,"start_value":1.1},{"duration":0.5,"easing":"outsine","end_value":1,"key_type":"tween","node_id":"card_2","property_id":"scale_x","start_time":0.5,"start_value":0.9},{"duration":0.5,"easing":"outsine","end_value":1,"key_type":"tween","node_id":"card_2","property_id":"scale_y","start_time":0.5,"start_value":0.9},{"duration":0.5,"easing":"outsine","end_value":200,"key_type":"tween","node_id":"card_1","property_id":"position_x","start_time":0.5},{"easing":"linear","key_type":"trigger","node_id":"card_1","property_id":"layer","start_data":"up","start_time":1},{"duration":0.5,"easing":"linear","end_value":-150,"key_type":"tween","node_id":"card_2","property_id":"position_y","start_time":1},{"duration":0.5,"easing":"insine","key_type":"tween","node_id":"card_1","property_id":"position_x","start_time":1,"start_value":200},{"duration":0.5,"easing":"insine","key_type":"tween","node_id":"card_2","property_id":"position_x","start_time":1,"start_value":-200},{"duration":0.5,"easing":"outsine","end_value":0.9,"key_type":"tween","node_id":"card_1","property_id":"scale_x","start_time":1,"start_value":1},{"duration":0.5,"easing":"outsine","end_value":0.9,"key_type":"tween","node_id":"card_1","property_id":"scale_y","start_time":1,"start_value":1},{"duration":0.5,"easing":"outsine","end_value":1.1,"key_type":"tween","node_id":"card_2","property_id":"scale_x","start_time":1,"start_value":1},{"duration":0.5,"easing":"outsine","end_value":1.1,"key_type":"tween","node_id":"card_2","property_id":"scale_y","start_time":1,"start_value":1},{"duration":0.5,"easing":"linear","end_value":155,"key_type":"tween","node_id":"card_1","property_id":"position_y","start_time":1},{"duration":0.5,"easing":"outsine","end_value":-200,"key_type":"tween","node_id":"card_1","property_id":"position_x","start_time":1.5},{"duration":0.5,"easing":"linear","key_type":"tween","node_id":"card_1","property_id":"position_y","start_time":1.5,"start_value":155},{"duration":0.5,"easing":"linear","key_type":"tween","node_id":"card_2","property_id":"position_y","start_time":1.5,"start_value":-150},{"duration":0.5,"easing":"outsine","end_value":1,"key_type":"tween","node_id":"card_1","property_id":"scale_x","start_time":1.5,"start_value":0.9},{"duration":0.5,"easing":"outsine","end_value":1,"key_type":"tween","node_id":"card_1","property_id":"scale_y","start_time":1.5,"start_value":0.9},{"duration":0.5,"easing":"outsine","end_value":1,"key_type":"tween","node_id":"card_2","property_id":"scale_x","start_time":1.5,"start_value":1.1},{"duration":0.5,"easing":"outsine","end_value":1,"key_type":"tween","node_id":"card_2","property_id":"scale_y","start_time":1.5,"start_value":1.1},{"duration":0.5,"easing":"outsine","end_value":200,"key_type":"tween","node_id":"card_2","property_id":"position_x","start_time":1.5}],"duration":2}],"metadata":{"fps":60,"gizmo_steps":[],"layers":[{"color":"19C352","name":"up"}],"settings":{"font_size":30}},"nodes":[{"blend_mode":"alpha","clipping_mode":"none","clipping_visible":true,"color_a":1,"color_b":0.411,"color_g":0.8,"color_r":0.665,"enabled":true,"inherit_alpha":true,"node_id":"card_1","node_index":1,"node_type":"box","pivot":"pivot_center","scale_x":1,"scale_y":1,"scale_z":1,"size_mode":"manual","size_x":300,"size_y":400,"visible":true},{"blend_mode":"alpha","clipping_mode":"none","clipping_visible":true,"color_a":1,"color_b":1,"color_g":1,"color_r":1,"enabled":true,"inherit_alpha":true,"node_id":"inner_1","node_index":2,"node_type":"box","parent":"card_1","pivot":"pivot_center","scale_x":1,"scale_y":1,"scale_z":1,"size_mode":"manual","size_x":268,"size_y":370,"visible":true},{"blend_mode":"alpha","clipping_mode":"none","clipping_visible":true,"color_a":1,"color_b":0.8,"color_g":0.411,"color_r":0.426,"enabled":true,"inherit_alpha":true,"node_id":"card_2","node_index":3,"node_type":"box","pivot":"pivot_center","position_x":245,"position_y":-55,"scale_x":1,"scale_y":1,"scale_z":1,"size_mode":"manual","size_x":300,"size_y":400,"visible":true},{"blend_mode":"alpha","clipping_mode":"none","clipping_visible":true,"color_a":1,"color_b":1,"color_g":1,"color_r":1,"enabled":true,"inherit_alpha":true,"node_id":"inner_2","node_index":4,"node_type":"box","parent":"card_2","pivot":"pivot_center","scale_x":1,"scale_y":1,"scale_z":1,"size_mode":"manual","size_x":268,"size_y":370,"visible":true}]},"format":"json","type":"animation_editor","version":1} \ No newline at end of file