├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── example.png ├── example ├── assets │ ├── 1 │ │ ├── barrel.png │ │ ├── stoneblock2.png │ │ ├── stoneblock4.png │ │ ├── stuff2.png │ │ └── stuff3.png │ ├── animated │ │ ├── a (0000).png │ │ ├── a (0001).png │ │ ├── a (0002).png │ │ ├── a (0003).png │ │ ├── a (0004).png │ │ ├── a (0005).png │ │ ├── a (0006).png │ │ └── a (0007).png │ ├── assets.atlas │ └── card.png ├── gui │ ├── atlas.script │ ├── test.gui │ └── test.gui_script └── main.collection ├── game.project ├── input └── game.input_binding ├── node_repeat ├── material │ ├── gui_repeat.fp │ ├── gui_repeat.material │ └── gui_repeat.vp └── node_repeat.lua └── sprite_repeat ├── material ├── sprite_repeat.fp ├── sprite_repeat.material └── sprite_repeat.vp ├── sprite_repeat.lua └── sprite_repeat.script /.gitattributes: -------------------------------------------------------------------------------- 1 | # Defold Protocol Buffer Text Files (https://github.com/github/linguist/issues/5091) 2 | *.animationset linguist-language=JSON5 3 | *.atlas linguist-language=JSON5 4 | *.camera linguist-language=JSON5 5 | *.collection linguist-language=JSON5 6 | *.collectionfactory linguist-language=JSON5 7 | *.collectionproxy linguist-language=JSON5 8 | *.collisionobject linguist-language=JSON5 9 | *.cubemap linguist-language=JSON5 10 | *.display_profiles linguist-language=JSON5 11 | *.factory linguist-language=JSON5 12 | *.font linguist-language=JSON5 13 | *.gamepads linguist-language=JSON5 14 | *.go linguist-language=JSON5 15 | *.gui linguist-language=JSON5 16 | *.input_binding linguist-language=JSON5 17 | *.label linguist-language=JSON5 18 | *.material linguist-language=JSON5 19 | *.mesh linguist-language=JSON5 20 | *.model linguist-language=JSON5 21 | *.particlefx linguist-language=JSON5 22 | *.render linguist-language=JSON5 23 | *.sound linguist-language=JSON5 24 | *.sprite linguist-language=JSON5 25 | *.spinemodel linguist-language=JSON5 26 | *.spinescene linguist-language=JSON5 27 | *.texture_profiles linguist-language=JSON5 28 | *.tilemap linguist-language=JSON5 29 | *.tilesource linguist-language=JSON5 30 | 31 | # Defold JSON Files 32 | *.buffer linguist-language=JSON 33 | 34 | # Defold GLSL Shaders 35 | *.fp linguist-language=GLSL 36 | *.vp linguist-language=GLSL 37 | 38 | # Defold Lua Files 39 | *.editor_script linguist-language=Lua 40 | *.render_script linguist-language=Lua 41 | *.script linguist-language=Lua 42 | *.gui_script linguist-language=Lua 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.internal 2 | /build 3 | .externalToolBuilders 4 | .DS_Store 5 | Thumbs.db 6 | .lock-wscript 7 | *.pyc 8 | .project 9 | .cproject 10 | builtins 11 | *.der 12 | /.editor_settings -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Igor Suntsev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sprite texture repeat shader. (or tiling) 2 | 3 | --- 4 | Set 'sprite_repeat' material to the sprite. Directly in the editor or with code. 5 | 6 | ```lua 7 | go.set(self.url, "material", self.repeat_material) 8 | ``` 9 | 10 | 11 | Create and go our super-puper repeating magic. 12 | 13 | ```lua 14 | local sprite_repeat = require('sprite_repeat.sprite_repeat') 15 | 16 | function init(self) 17 | 18 | local repeat_x = 4 19 | local repeat_y = 4 20 | 21 | local sr = sprite_repeat.create("#sprite_url") 22 | sr.animate(repeat_x, repeat_y) 23 | 24 | end 25 | ``` 26 | 27 | See file 'sprite_repeat.script' for details. 28 | 29 | Works with both static and animated sprites. 30 | 31 | ![Example](example.png) 32 | 33 | 34 | # GUI box node texture repeat shader. 35 | 36 | --- 37 | Set 'node_repeat' material to the box node. Directly in the editor or with code. 38 | 39 | ```lua 40 | gui.set_material(node, "gui_repeat") 41 | ``` 42 | 43 | Setup constants to the material shader: 44 | 45 | ```lua 46 | local node_repeat = require('node_repeat.node_repeat') 47 | .... 48 | local nr = node_repeat.create(node, nil, atlas_path) 49 | nr.animate(repeat_x, repeat_y) 50 | ``` 51 | 52 | Update the repeat factors if need: 53 | 54 | ```lua 55 | nr.update(1, 4*y) 56 | ``` 57 | 58 | See 'test.gui_script' for details. 59 | 60 | --- 61 | 62 | Happy Defolding! 63 | 64 | --- -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dragosha/defold-sprite-repeat/db6349275bf56313c9cc867b62593849e6f77773/example.png -------------------------------------------------------------------------------- /example/assets/1/barrel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dragosha/defold-sprite-repeat/db6349275bf56313c9cc867b62593849e6f77773/example/assets/1/barrel.png -------------------------------------------------------------------------------- /example/assets/1/stoneblock2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dragosha/defold-sprite-repeat/db6349275bf56313c9cc867b62593849e6f77773/example/assets/1/stoneblock2.png -------------------------------------------------------------------------------- /example/assets/1/stoneblock4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dragosha/defold-sprite-repeat/db6349275bf56313c9cc867b62593849e6f77773/example/assets/1/stoneblock4.png -------------------------------------------------------------------------------- /example/assets/1/stuff2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dragosha/defold-sprite-repeat/db6349275bf56313c9cc867b62593849e6f77773/example/assets/1/stuff2.png -------------------------------------------------------------------------------- /example/assets/1/stuff3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dragosha/defold-sprite-repeat/db6349275bf56313c9cc867b62593849e6f77773/example/assets/1/stuff3.png -------------------------------------------------------------------------------- /example/assets/animated/a (0000).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dragosha/defold-sprite-repeat/db6349275bf56313c9cc867b62593849e6f77773/example/assets/animated/a (0000).png -------------------------------------------------------------------------------- /example/assets/animated/a (0001).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dragosha/defold-sprite-repeat/db6349275bf56313c9cc867b62593849e6f77773/example/assets/animated/a (0001).png -------------------------------------------------------------------------------- /example/assets/animated/a (0002).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dragosha/defold-sprite-repeat/db6349275bf56313c9cc867b62593849e6f77773/example/assets/animated/a (0002).png -------------------------------------------------------------------------------- /example/assets/animated/a (0003).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dragosha/defold-sprite-repeat/db6349275bf56313c9cc867b62593849e6f77773/example/assets/animated/a (0003).png -------------------------------------------------------------------------------- /example/assets/animated/a (0004).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dragosha/defold-sprite-repeat/db6349275bf56313c9cc867b62593849e6f77773/example/assets/animated/a (0004).png -------------------------------------------------------------------------------- /example/assets/animated/a (0005).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dragosha/defold-sprite-repeat/db6349275bf56313c9cc867b62593849e6f77773/example/assets/animated/a (0005).png -------------------------------------------------------------------------------- /example/assets/animated/a (0006).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dragosha/defold-sprite-repeat/db6349275bf56313c9cc867b62593849e6f77773/example/assets/animated/a (0006).png -------------------------------------------------------------------------------- /example/assets/animated/a (0007).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dragosha/defold-sprite-repeat/db6349275bf56313c9cc867b62593849e6f77773/example/assets/animated/a (0007).png -------------------------------------------------------------------------------- /example/assets/assets.atlas: -------------------------------------------------------------------------------- 1 | images { 2 | image: "/example/assets/card.png" 3 | } 4 | images { 5 | image: "/builtins/assets/images/logo/logo_256.png" 6 | } 7 | images { 8 | image: "/example/assets/1/barrel.png" 9 | } 10 | images { 11 | image: "/example/assets/1/stoneblock2.png" 12 | } 13 | images { 14 | image: "/example/assets/1/stoneblock4.png" 15 | } 16 | images { 17 | image: "/example/assets/1/stuff2.png" 18 | } 19 | images { 20 | image: "/example/assets/1/stuff3.png" 21 | } 22 | animations { 23 | id: "animation" 24 | images { 25 | image: "/example/assets/animated/a (0000).png" 26 | } 27 | images { 28 | image: "/example/assets/animated/a (0001).png" 29 | } 30 | images { 31 | image: "/example/assets/animated/a (0002).png" 32 | } 33 | images { 34 | image: "/example/assets/animated/a (0003).png" 35 | } 36 | images { 37 | image: "/example/assets/animated/a (0004).png" 38 | } 39 | images { 40 | image: "/example/assets/animated/a (0005).png" 41 | } 42 | images { 43 | image: "/example/assets/animated/a (0006).png" 44 | } 45 | images { 46 | image: "/example/assets/animated/a (0007).png" 47 | } 48 | playback: PLAYBACK_NONE 49 | fps: 8 50 | } 51 | margin: 1 52 | -------------------------------------------------------------------------------- /example/assets/card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dragosha/defold-sprite-repeat/db6349275bf56313c9cc867b62593849e6f77773/example/assets/card.png -------------------------------------------------------------------------------- /example/gui/atlas.script: -------------------------------------------------------------------------------- 1 | go.property("atlas", resource.atlas()) 2 | function init(self) 3 | msg.post("#gui", "atlas", {atlas = self.atlas}) 4 | end 5 | -------------------------------------------------------------------------------- /example/gui/test.gui: -------------------------------------------------------------------------------- 1 | script: "/example/gui/test.gui_script" 2 | textures { 3 | name: "assets" 4 | texture: "/example/assets/assets.atlas" 5 | } 6 | nodes { 7 | position { 8 | x: 640.0 9 | y: 360.0 10 | } 11 | rotation { 12 | z: 20.131 13 | } 14 | scale { 15 | x: 8.0 16 | y: 8.0 17 | } 18 | type: TYPE_BOX 19 | texture: "assets/barrel" 20 | id: "fit_box" 21 | inherit_alpha: true 22 | size_mode: SIZE_MODE_AUTO 23 | enabled: false 24 | } 25 | nodes { 26 | size { 27 | x: 200.0 28 | y: 100.0 29 | } 30 | type: TYPE_BOX 31 | id: "border" 32 | adjust_mode: ADJUST_MODE_STRETCH 33 | inherit_alpha: true 34 | size_mode: SIZE_MODE_AUTO 35 | visible: false 36 | } 37 | nodes { 38 | position { 39 | y: 720.0 40 | } 41 | size { 42 | x: 1280.0 43 | y: 128.0 44 | } 45 | type: TYPE_BOX 46 | texture: "assets/card" 47 | id: "top" 48 | xanchor: XANCHOR_LEFT 49 | yanchor: YANCHOR_TOP 50 | pivot: PIVOT_NW 51 | adjust_mode: ADJUST_MODE_STRETCH 52 | parent: "border" 53 | inherit_alpha: true 54 | material: "gui_repeat" 55 | } 56 | nodes { 57 | size { 58 | x: 1280.0 59 | y: 128.0 60 | } 61 | type: TYPE_BOX 62 | texture: "assets/card" 63 | id: "bottom" 64 | xanchor: XANCHOR_LEFT 65 | yanchor: YANCHOR_TOP 66 | pivot: PIVOT_SW 67 | adjust_mode: ADJUST_MODE_STRETCH 68 | parent: "border" 69 | inherit_alpha: true 70 | material: "gui_repeat" 71 | } 72 | nodes { 73 | size { 74 | x: 128.0 75 | y: 720.0 76 | } 77 | type: TYPE_BOX 78 | texture: "assets/card" 79 | id: "left" 80 | xanchor: XANCHOR_LEFT 81 | yanchor: YANCHOR_BOTTOM 82 | pivot: PIVOT_SW 83 | adjust_mode: ADJUST_MODE_STRETCH 84 | parent: "border" 85 | inherit_alpha: true 86 | material: "gui_repeat" 87 | } 88 | nodes { 89 | position { 90 | x: 1280.0 91 | } 92 | size { 93 | x: 128.0 94 | y: 720.0 95 | } 96 | type: TYPE_BOX 97 | texture: "assets/card" 98 | id: "right" 99 | xanchor: XANCHOR_RIGHT 100 | yanchor: YANCHOR_BOTTOM 101 | pivot: PIVOT_SE 102 | adjust_mode: ADJUST_MODE_STRETCH 103 | parent: "border" 104 | inherit_alpha: true 105 | material: "gui_repeat" 106 | } 107 | material: "/builtins/materials/gui.material" 108 | adjust_reference: ADJUST_REFERENCE_PARENT 109 | materials { 110 | name: "gui_repeat" 111 | material: "/node_repeat/material/gui_repeat.material" 112 | } 113 | -------------------------------------------------------------------------------- /example/gui/test.gui_script: -------------------------------------------------------------------------------- 1 | local node_repeat = require('node_repeat.node_repeat') 2 | 3 | local function adjust_border(self) 4 | local x, y = node_repeat.get_screen_aspect_ratio() 5 | local aspect = x/y 6 | print("aspect = ", aspect) 7 | if self.left then 8 | 9 | gui.set_scale(self.left.node, vmath.vector3(1/x, 1, 1)) 10 | gui.set_scale(self.right.node, vmath.vector3(1/x, 1, 1)) 11 | gui.set_scale(self.top.node, vmath.vector3(1, 1/y, 1)) 12 | gui.set_scale(self.bottom.node, vmath.vector3(1, 1/y, 1)) 13 | self.left.update(nil, 4*y) 14 | self.right.update(nil, 4*y) 15 | self.top.update(12*x, nil) 16 | self.bottom.update(12*x, nil) 17 | end 18 | end 19 | 20 | 21 | function init(self) 22 | 23 | -- An example. Adjust border nodes in depend screen size/aspect ratio 24 | window.set_listener(function(self, event, data) 25 | if event == window.WINDOW_EVENT_RESIZED then 26 | adjust_border(self) 27 | end 28 | end) 29 | 30 | end 31 | 32 | function on_message(self, message_id, message, sender) 33 | if message_id == hash("atlas") then 34 | 35 | -- to correct work, we need access to the atlas our node is using, 36 | -- for some reason gui.get_texture(node) only returns the atlas name, not the full path like resource.atlas() 37 | -- on the other hand, resource.atlas() can only be placed in go.property() 38 | -- so we just send this info from the atlas.script to the test.gui_script 39 | -- If you can make this easier, please leave a comment to fix this point. 40 | 41 | local atlas_path = message.atlas 42 | local node = gui.get_node("fit_box") 43 | local scale = gui.get_scale(node) 44 | 45 | -- Set repeat material to the box node. 46 | gui.set_material(node, "gui_repeat") 47 | 48 | -- Create and go our super-puper repeating magic. 49 | local nr = node_repeat.create(node, nil, atlas_path) 50 | nr.animate(scale.x, scale.y) 51 | 52 | 53 | -- Border box nodes have already setted repeat-material in gui editor properties panel. 54 | self.left = node_repeat.create("left", nil, atlas_path) 55 | self.left.animate(1, 4) 56 | self.right = node_repeat.create("right", nil, atlas_path) 57 | self.right.animate(1, 4) 58 | self.top = node_repeat.create("top", nil, atlas_path) 59 | self.top.animate(12, 1) 60 | self.bottom = node_repeat.create("bottom", nil, atlas_path) 61 | self.bottom.animate(12, 1) 62 | 63 | adjust_border(self) 64 | 65 | end 66 | end 67 | 68 | -------------------------------------------------------------------------------- /example/main.collection: -------------------------------------------------------------------------------- 1 | name: "main" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "go" 5 | data: "components {\n" 6 | " id: \"sprite_repeat\"\n" 7 | " component: \"/sprite_repeat/sprite_repeat.script\"\n" 8 | " properties {\n" 9 | " id: \"repeat_x\"\n" 10 | " value: \"1.0\"\n" 11 | " type: PROPERTY_TYPE_NUMBER\n" 12 | " }\n" 13 | " properties {\n" 14 | " id: \"repeat_y\"\n" 15 | " value: \"1.0\"\n" 16 | " type: PROPERTY_TYPE_NUMBER\n" 17 | " }\n" 18 | " properties {\n" 19 | " id: \"auto_tiling\"\n" 20 | " value: \"true\"\n" 21 | " type: PROPERTY_TYPE_BOOLEAN\n" 22 | " }\n" 23 | "}\n" 24 | "embedded_components {\n" 25 | " id: \"sprite\"\n" 26 | " type: \"sprite\"\n" 27 | " data: \"default_animation: \\\"logo_256\\\"\\n" 28 | "material: \\\"/builtins/materials/sprite.material\\\"\\n" 29 | "textures {\\n" 30 | " sampler: \\\"texture_sampler\\\"\\n" 31 | " texture: \\\"/example/assets/assets.atlas\\\"\\n" 32 | "}\\n" 33 | "\"\n" 34 | " scale {\n" 35 | " x: 10.0\n" 36 | " y: 10.0\n" 37 | " }\n" 38 | "}\n" 39 | "" 40 | position { 41 | x: 640.0 42 | y: 360.0 43 | } 44 | } 45 | embedded_instances { 46 | id: "gui" 47 | data: "components {\n" 48 | " id: \"gui\"\n" 49 | " component: \"/example/gui/test.gui\"\n" 50 | "}\n" 51 | "components {\n" 52 | " id: \"atlas\"\n" 53 | " component: \"/example/gui/atlas.script\"\n" 54 | " properties {\n" 55 | " id: \"atlas\"\n" 56 | " value: \"/example/assets/assets.atlas\"\n" 57 | " type: PROPERTY_TYPE_HASH\n" 58 | " }\n" 59 | "}\n" 60 | "" 61 | } 62 | -------------------------------------------------------------------------------- /game.project: -------------------------------------------------------------------------------- 1 | [bootstrap] 2 | main_collection = /example/main.collectionc 3 | 4 | [script] 5 | shared_state = 1 6 | 7 | [display] 8 | width = 1280 9 | height = 720 10 | high_dpi = 1 11 | 12 | [android] 13 | input_method = HiddenInputField 14 | 15 | [project] 16 | title = defold-sprite-repeat 17 | developer = Dragosha 18 | 19 | -------------------------------------------------------------------------------- /input/game.input_binding: -------------------------------------------------------------------------------- 1 | mouse_trigger { 2 | input: MOUSE_BUTTON_1 3 | action: "touch" 4 | } 5 | -------------------------------------------------------------------------------- /node_repeat/material/gui_repeat.fp: -------------------------------------------------------------------------------- 1 | varying mediump vec2 var_texcoord0; 2 | varying lowp vec4 var_color; 3 | 4 | uniform lowp sampler2D texture_sampler; 5 | varying highp vec4 var_uv; 6 | varying highp vec4 var_repeat; 7 | varying highp vec4 var_rotated; 8 | 9 | void main() 10 | { 11 | 12 | float local_position_x = (var_uv.z - var_texcoord0.x) / (var_uv.z - var_uv.x); 13 | float local_position_y = (var_uv.w - var_texcoord0.y) / (var_uv.w - var_uv.y); 14 | vec2 var_boo = vec2(local_position_x, local_position_y); 15 | float u = var_boo.x * var_repeat.x - floor(var_boo.x * var_repeat.x); 16 | float v = var_boo.y * var_repeat.y - floor(var_boo.y * var_repeat.y); 17 | // It looks like the V coordinate axis in atlas space and shader space are in opposite directions. 18 | // In case the atlas has a sprite rotated clockwise we need to swap U and V. 19 | vec2 uv = vec2( 20 | mix(var_uv.x, var_uv.z, 1. -(u * var_rotated.x + v * var_rotated.y)), 21 | mix(var_uv.y, var_uv.w, 1. -(u * var_rotated.y + v * var_rotated.x)) 22 | ); 23 | 24 | lowp vec4 tex = texture2D(texture_sampler, uv); 25 | gl_FragColor = tex * var_color; 26 | } 27 | -------------------------------------------------------------------------------- /node_repeat/material/gui_repeat.material: -------------------------------------------------------------------------------- 1 | name: "gui_repeat" 2 | tags: "gui" 3 | vertex_program: "/node_repeat/material/gui_repeat.vp" 4 | fragment_program: "/node_repeat/material/gui_repeat.fp" 5 | vertex_constants { 6 | name: "view_proj" 7 | type: CONSTANT_TYPE_VIEWPROJ 8 | } 9 | vertex_constants { 10 | name: "uv_coord" 11 | type: CONSTANT_TYPE_USER 12 | value { 13 | } 14 | } 15 | vertex_constants { 16 | name: "uv_repeat" 17 | type: CONSTANT_TYPE_USER 18 | value { 19 | } 20 | } 21 | vertex_constants { 22 | name: "uv_rotated" 23 | type: CONSTANT_TYPE_USER 24 | value { 25 | x: 1.0 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /node_repeat/material/gui_repeat.vp: -------------------------------------------------------------------------------- 1 | uniform highp mat4 view_proj; 2 | 3 | // positions are in world space 4 | attribute mediump vec3 position; 5 | attribute mediump vec2 texcoord0; 6 | attribute lowp vec4 color; 7 | 8 | uniform highp vec4 uv_coord; 9 | uniform highp vec4 uv_repeat; 10 | uniform highp vec4 uv_rotated; 11 | 12 | varying mediump vec2 var_texcoord0; 13 | varying lowp vec4 var_color; 14 | varying highp vec4 var_uv; 15 | varying highp vec4 var_repeat; 16 | varying highp vec4 var_rotated; 17 | 18 | void main() 19 | { 20 | var_texcoord0 = texcoord0; 21 | var_color = vec4(color.rgb * color.a, color.a); 22 | var_uv = uv_coord; 23 | var_repeat = uv_repeat; 24 | var_rotated = uv_rotated; 25 | gl_Position = view_proj * vec4(position.xyz, 1.0); 26 | } 27 | -------------------------------------------------------------------------------- /node_repeat/node_repeat.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | -- @param node_or_string -- name of node 4 | -- @param animation_id -- string or id, if [nil] than will be taken from get_flipbook (node) 5 | -- @param atlas_path -- path to get atlas info through resource.get_atlas 6 | function M.create(node_or_string, animation_id, atlas_path) 7 | 8 | local node = type(node_or_string) == "string" and gui.get_node(node_or_string) or node_or_string 9 | 10 | assert(atlas_path, "Need an atlas path resource") 11 | local atlas_data = resource.get_atlas(atlas_path) 12 | local tex_info = resource.get_texture_info(atlas_data.texture) 13 | local tex_w = tex_info.width 14 | local tex_h = tex_info.height 15 | 16 | local animation_data 17 | 18 | local in_animation_id = animation_id 19 | if animation_id and type(animation_id) == "string" then 20 | in_animation_id = hash(animation_id) 21 | end 22 | 23 | local sprite_image_id = in_animation_id or gui.get_flipbook(node) 24 | for i, animation in ipairs(atlas_data.animations) do 25 | if hash(animation.id) == sprite_image_id then 26 | animation_data = animation 27 | -- print(i, animation.id, animation.width, animation.height, animation.frame_start) 28 | break 29 | end 30 | end 31 | assert(animation_data, "Unable to find image " .. sprite_image_id) 32 | 33 | local frames = {} 34 | for index = animation_data.frame_start, animation_data.frame_end - 1 do 35 | 36 | local uvs = atlas_data.geometries[index].uvs 37 | assert(#uvs == 8, "Sprite trim mode should be disabled for the images.") 38 | 39 | -- UV texture coordinates 40 | -- 1 41 | -- ^ V 42 | -- | 43 | -- | 44 | -- | U 45 | -- 0-------> 1 46 | 47 | -- uvs = { 48 | -- 0, 0, 49 | -- 0, height, 50 | -- width, height, 51 | -- width, 0 52 | -- }, 53 | -- geometries.indices = {0 (1,2), 1(3,4), 2(5,6), 0(1,2), 2(5,6), 3(7,8)} 54 | -- 1------2 55 | -- | / | 56 | -- | A / | 57 | -- | / B | 58 | -- | / | 59 | -- 0------3 60 | 61 | local width = uvs[5] - uvs[1] 62 | local height = uvs[2] - uvs[6] 63 | local u1 = uvs[1] 64 | local v1 = uvs[6] 65 | local u2 = uvs[5] 66 | local v2 = uvs[2] 67 | local is_rotated = false 68 | -- pprint(atlas_data.geometries[index]) 69 | if height < 0 then 70 | -- In case the atlas has clockwise rotated sprite. 71 | -- 0------1 72 | -- |\ A | 73 | -- | \ | 74 | -- | \ | 75 | -- | B \ | 76 | -- 3------2 77 | 78 | height = uvs[5] - uvs[1] 79 | width = uvs[6] - uvs[2] 80 | -- print("rotated", width, height) 81 | is_rotated = true 82 | end 83 | 84 | local frame = { 85 | uv_coord = vmath.vector4( 86 | u1 / tex_w, 87 | (tex_h - v1) / tex_h, 88 | u2 / tex_w, 89 | (tex_h - v2) / tex_h 90 | ), 91 | w = width, 92 | h = height, 93 | -- For some reason uv_rotated info isn't needed for the gui shader against sprite shader. why? :-\ 94 | uv_rotated = is_rotated and vmath.vector4(0, 1, 0, 0) or vmath.vector4(1, 0, 0, 0) 95 | } 96 | table.insert(frames, frame) 97 | end 98 | 99 | local animation = { 100 | frames = frames, 101 | width = animation_data.width, 102 | height = animation_data.height, 103 | fps = animation_data.fps, 104 | v = vmath.vector4(1, 1, animation_data.width, animation_data.height), 105 | current_frame = 1, 106 | node = node, 107 | } 108 | 109 | -- Start our repeat shader work 110 | -- @param repeat_x -- X factor 111 | -- @param repeat_y -- Y factor 112 | function animation.animate(repeat_x, repeat_y) 113 | 114 | local frame = animation.frames[1] 115 | gui.set(node, "uv_coord", frame.uv_coord) 116 | -- gui.set(node, "uv_rotated", frame.uv_rotated) 117 | animation.v.x = repeat_x 118 | animation.v.y = repeat_y 119 | animation.v.z = frame.w 120 | animation.v.w = frame.h 121 | gui.set(node, "uv_repeat", animation.v) 122 | 123 | if #animation.frames > 1 and animation.fps > 0 then 124 | animation.handle = 125 | timer.delay(1/animation.fps, true, function(self, handle, time_elapsed) 126 | local frame = animation.frames[animation.current_frame] 127 | gui.set(node, "uv_coord", frame.uv_coord) 128 | -- gui.set(node, "uv_rotated", frame.uv_rotated) 129 | animation.v.z = frame.w 130 | animation.v.w = frame.h 131 | gui.set(node, "uv_repeat", animation.v) 132 | 133 | animation.current_frame = animation.current_frame + 1 134 | if animation.current_frame > #animation.frames then 135 | animation.current_frame = 1 136 | end 137 | end) 138 | end 139 | end 140 | 141 | function animation.stop() 142 | if animation.handle then 143 | timer.cancel(animation.handle) 144 | animation.handle = nil 145 | end 146 | end 147 | 148 | -- Update repeat factor values 149 | -- @param repeat_x 150 | -- @param repeat_y 151 | function animation.update(repeat_x, repeat_y) 152 | if repeat_x then animation.v.x = repeat_x end 153 | if repeat_y then animation.v.y = repeat_y end 154 | gui.set(node, "uv_repeat", animation.v) 155 | end 156 | 157 | return animation 158 | end 159 | 160 | 161 | M.x_ratio = 1 162 | M.y_ratio = 1 163 | 164 | function M.get_screen_aspect_ratio() 165 | local window_x, window_y = window.get_size() 166 | local stretch_x = window_x / gui.get_width() 167 | local stretch_y = window_y / gui.get_height() 168 | 169 | M.x_ratio = stretch_x / math.min(stretch_x, stretch_y) 170 | M.y_ratio = stretch_y / math.min(stretch_x, stretch_y) 171 | 172 | return M.x_ratio, M.y_ratio 173 | end 174 | 175 | return M -------------------------------------------------------------------------------- /sprite_repeat/material/sprite_repeat.fp: -------------------------------------------------------------------------------- 1 | varying mediump vec2 var_texcoord0; 2 | 3 | uniform lowp sampler2D texture_sampler; 4 | uniform lowp vec4 tint; 5 | 6 | uniform lowp vec4 direction; 7 | 8 | 9 | varying highp vec2 var_boo; 10 | varying highp vec4 var_uv; 11 | varying highp vec4 var_repeat; 12 | varying highp vec4 var_rotated; 13 | 14 | void main() 15 | { 16 | // Pre-multiply alpha since all runtime textures already are 17 | lowp vec4 tint_pm = vec4(tint.xyz * tint.w, tint.w); 18 | 19 | // Calculate the final UV coord based on local fragment position and texture UV space. 20 | float u = mod((var_boo.x + direction.x*direction.w) * var_repeat.x, 1); 21 | float v = mod((var_boo.y + direction.y*direction.w) * var_repeat.y, 1); 22 | 23 | // It looks like the V coordinate axis in atlas space and shader space are in opposite directions. 24 | // In case the atlas has a sprite rotated clockwise we need to swap U and V. 25 | vec2 uv = vec2( 26 | mix(var_uv.x, var_uv.z, u * var_rotated.x + v * var_rotated.y), 27 | mix(var_uv.y, var_uv.w, 1. - (u * var_rotated.y + v * var_rotated.x)) 28 | ); 29 | 30 | gl_FragColor = texture2D(texture_sampler, uv) * tint_pm; 31 | } 32 | -------------------------------------------------------------------------------- /sprite_repeat/material/sprite_repeat.material: -------------------------------------------------------------------------------- 1 | name: "sprite_repeat" 2 | tags: "tile" 3 | vertex_program: "/sprite_repeat/material/sprite_repeat.vp" 4 | fragment_program: "/sprite_repeat/material/sprite_repeat.fp" 5 | vertex_constants { 6 | name: "view_proj" 7 | type: CONSTANT_TYPE_VIEWPROJ 8 | } 9 | vertex_constants { 10 | name: "uv_coord" 11 | type: CONSTANT_TYPE_USER 12 | value { 13 | } 14 | } 15 | vertex_constants { 16 | name: "uv_repeat" 17 | type: CONSTANT_TYPE_USER 18 | value { 19 | } 20 | } 21 | vertex_constants { 22 | name: "uv_rotated" 23 | type: CONSTANT_TYPE_USER 24 | value { 25 | x: 1.0 26 | } 27 | } 28 | fragment_constants { 29 | name: "tint" 30 | type: CONSTANT_TYPE_USER 31 | value { 32 | x: 1.0 33 | y: 1.0 34 | z: 1.0 35 | w: 1.0 36 | } 37 | } 38 | fragment_constants { 39 | name: "direction" 40 | type: CONSTANT_TYPE_USER 41 | value { 42 | } 43 | } 44 | samplers { 45 | name: "texture_sampler" 46 | wrap_u: WRAP_MODE_CLAMP_TO_EDGE 47 | wrap_v: WRAP_MODE_CLAMP_TO_EDGE 48 | filter_min: FILTER_MODE_MIN_DEFAULT 49 | filter_mag: FILTER_MODE_MAG_DEFAULT 50 | } 51 | attributes { 52 | name: "local_position" 53 | semantic_type: SEMANTIC_TYPE_POSITION 54 | vector_type: VECTOR_TYPE_VEC3 55 | } 56 | -------------------------------------------------------------------------------- /sprite_repeat/material/sprite_repeat.vp: -------------------------------------------------------------------------------- 1 | uniform highp mat4 view_proj; 2 | 3 | // positions are in world space 4 | attribute highp vec4 position; 5 | attribute mediump vec2 texcoord0; 6 | 7 | // add custom vertex attribute and uniforms 8 | attribute highp vec3 local_position; 9 | uniform highp vec4 uv_coord; 10 | uniform highp vec4 uv_repeat; 11 | uniform highp vec4 uv_rotated; 12 | 13 | varying mediump vec2 var_texcoord0; 14 | varying highp vec2 var_boo; 15 | varying highp vec4 var_uv; 16 | varying highp vec4 var_repeat; 17 | varying highp vec4 var_rotated; 18 | 19 | void main() 20 | { 21 | gl_Position = view_proj * vec4(position.xyz, 1.0); 22 | var_texcoord0 = texcoord0; 23 | 24 | // 25 | var_boo = vec2(local_position.x / uv_repeat.z + 0.5, local_position.y / uv_repeat.w + 0.5); 26 | var_uv = uv_coord; 27 | var_repeat = uv_repeat; 28 | var_rotated = uv_rotated; 29 | } 30 | -------------------------------------------------------------------------------- /sprite_repeat/sprite_repeat.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | function M.create(sprite_url, sprite_id, self) 4 | 5 | local atlas_data 6 | local tex_info 7 | -- use precashed atlas info if them is available 8 | if self and self.atlas_data and self.tex_info then 9 | atlas_data = self.atlas_data 10 | tex_info = self.tex_info 11 | else 12 | local atlas = go.get(sprite_url, "image") 13 | atlas_data = resource.get_atlas(atlas) 14 | tex_info = resource.get_texture_info(atlas_data.texture) 15 | if self then 16 | self.atlas_data = atlas_data 17 | self.tex_info = tex_info 18 | end 19 | end 20 | local tex_w = tex_info.width 21 | local tex_h = tex_info.height 22 | 23 | -- pprint(atlas_data) 24 | local animation_data 25 | 26 | local in_sprite_id = sprite_id 27 | if sprite_id and type(sprite_id) == "string" then 28 | in_sprite_id = hash(sprite_id) 29 | end 30 | 31 | local sprite_image_id = in_sprite_id or go.get(sprite_url, "animation") 32 | for i, animation in ipairs(atlas_data.animations) do 33 | if hash(animation.id) == sprite_image_id then 34 | animation_data = animation 35 | -- print(i, animation.id, animation.width, animation.height, animation.frame_start) 36 | break 37 | end 38 | end 39 | assert(animation_data, "Unable to find image " .. sprite_image_id) 40 | 41 | local frames = {} 42 | for index = animation_data.frame_start, animation_data.frame_end - 1 do 43 | 44 | local uvs = atlas_data.geometries[index].uvs 45 | assert(#uvs == 8, "Sprite trim mode should be disabled for the images.") 46 | 47 | -- UV texture coordinates 48 | -- 1 49 | -- ^ V 50 | -- | 51 | -- | 52 | -- | U 53 | -- 0-------> 1 54 | 55 | -- uvs = { 56 | -- 0, 0, 57 | -- 0, height, 58 | -- width, height, 59 | -- width, 0 60 | -- }, 61 | -- geometries.indices = {0 (1,2), 1(3,4), 2(5,6), 0(1,2), 2(5,6), 3(7,8)} 62 | -- 1------2 63 | -- | / | 64 | -- | A / | 65 | -- | / B | 66 | -- | / | 67 | -- 0------3 68 | 69 | local width = uvs[5] - uvs[1] 70 | local height = uvs[2] - uvs[6] 71 | local u1 = uvs[1] 72 | local v1 = uvs[6] 73 | local u2 = uvs[5] 74 | local v2 = uvs[2] 75 | local is_rotated = false 76 | -- pprint(atlas_data.geometries[index]) 77 | if height < 0 then 78 | -- In case the atlas has clockwise rotated sprite. 79 | -- 0------1 80 | -- |\ A | 81 | -- | \ | 82 | -- | \ | 83 | -- | B \ | 84 | -- 3------2 85 | 86 | height = uvs[5] - uvs[1] 87 | width = uvs[6] - uvs[2] 88 | -- print("rotated", width, height) 89 | is_rotated = true 90 | end 91 | 92 | local frame = { 93 | uv_coord = vmath.vector4( 94 | u1 / tex_w, 95 | (tex_h - v1) / tex_h, 96 | u2 / tex_w, 97 | (tex_h - v2) / tex_h 98 | ), 99 | w = width, 100 | h = height, 101 | uv_rotated = is_rotated and vmath.vector4(0, 1, 0, 0) or vmath.vector4(1, 0, 0, 0) 102 | } 103 | table.insert(frames, frame) 104 | end 105 | 106 | local animation = { 107 | frames = frames, 108 | width = animation_data.width, 109 | height = animation_data.height, 110 | fps = animation_data.fps, 111 | v = vmath.vector4(1, 1, animation_data.width, animation_data.height), 112 | current_frame = 1, 113 | } 114 | 115 | -- @param repeat_x 116 | -- @param repeat_y 117 | function animation.animate(repeat_x, repeat_y) 118 | 119 | local frame = animation.frames[1] 120 | go.set(sprite_url, "uv_coord", frame.uv_coord) 121 | go.set(sprite_url, "uv_rotated", frame.uv_rotated) 122 | animation.v.x = repeat_x 123 | animation.v.y = repeat_y 124 | animation.v.z = frame.w 125 | animation.v.w = frame.h 126 | go.set(sprite_url, "uv_repeat", animation.v) 127 | 128 | if #animation.frames > 1 and animation.fps > 0 then 129 | animation.handle = 130 | timer.delay(1/animation.fps, true, function(self, handle, time_elapsed) 131 | local frame = animation.frames[animation.current_frame] 132 | go.set(sprite_url, "uv_coord", frame.uv_coord) 133 | go.set(sprite_url, "uv_rotated", frame.uv_rotated) 134 | animation.v.z = frame.w 135 | animation.v.w = frame.h 136 | go.set(sprite_url, "uv_repeat", animation.v) 137 | 138 | animation.current_frame = animation.current_frame + 1 139 | if animation.current_frame > #animation.frames then 140 | animation.current_frame = 1 141 | end 142 | end) 143 | end 144 | end 145 | 146 | function animation.stop() 147 | if animation.handle then 148 | timer.cancel(animation.handle) 149 | animation.handle = nil 150 | end 151 | end 152 | 153 | return animation 154 | end 155 | 156 | return M -------------------------------------------------------------------------------- /sprite_repeat/sprite_repeat.script: -------------------------------------------------------------------------------- 1 | go.property("url", msg.url("#sprite")) 2 | go.property("repeat_x", 2) 3 | go.property("repeat_y", 2) 4 | go.property("auto_tiling", false) 5 | 6 | go.property("repeat_material", resource.material("/sprite_repeat/material/sprite_repeat.material")) 7 | 8 | local sprite_repeat = require('sprite_repeat.sprite_repeat') 9 | 10 | function init(self) 11 | 12 | -- Send 'fit projection' to render script. 13 | msg.post("@render:", "use_fixed_fit_projection", { near = -1, far = 1 }) 14 | 15 | -- If auto tiling is enabled then use sprite 'scale' value as the sprite repeat factor. 16 | if self.auto_tiling then 17 | local scale = go.get(self.url, "scale") 18 | self.repeat_x = scale.x 19 | self.repeat_y = scale.y 20 | end 21 | 22 | -- Set repeat material to the sprite. 23 | go.set(self.url, "material", self.repeat_material) 24 | 25 | -- Create and go our super-puper repeating magic. 26 | local sr = sprite_repeat.create(self.url) 27 | sr.animate(self.repeat_x, self.repeat_y) 28 | 29 | -- Shifting (Scrolling) animation: 30 | -- Constant direction is... a direction vector (x, y). Where direction.w is shifting delta (0...1). 31 | -- Changing the 'direction.w' value you will get a scrolling texture. 32 | go.set(self.url, "direction", vmath.vector4(-1, -1, 0, 0)) 33 | go.animate(self.url, "direction.x", go.PLAYBACK_LOOP_PINGPONG, 1, go.EASING_INOUTSINE, 10) 34 | go.animate(self.url, "direction.y", go.PLAYBACK_LOOP_PINGPONG, 1, go.EASING_INOUTSINE, 20) 35 | go.animate(self.url, "direction.w", go.PLAYBACK_LOOP_FORWARD, 1, go.EASING_LINEAR, 10) 36 | 37 | 38 | -- How to change repeat factor at runtime: 39 | -- go.animate(self.url, "uv_repeat.x", go.PLAYBACK_ONCE_FORWARD, 10, go.EASING_LINEAR, 0) 40 | -- go.animate(self.url, "uv_repeat.y", go.PLAYBACK_ONCE_FORWARD, 10, go.EASING_LINEAR, 0) 41 | -- or: 42 | -- go.set(self.url, "uv_repeat.x", 10) 43 | 44 | end 45 | 46 | function final(self) 47 | end 48 | --------------------------------------------------------------------------------