diff --git a/animation/infinite-scroll-animation/.gitattributes b/animation/infinite-scroll-animation/.gitattributes new file mode 100644 index 00000000..187cdf4a --- /dev/null +++ b/animation/infinite-scroll-animation/.gitattributes @@ -0,0 +1,42 @@ +# Defold Protocol Buffer Text Files (https://github.com/github/linguist/issues/5091) +*.animationset linguist-language=JSON5 +*.atlas linguist-language=JSON5 +*.camera linguist-language=JSON5 +*.collection linguist-language=JSON5 +*.collectionfactory linguist-language=JSON5 +*.collectionproxy linguist-language=JSON5 +*.collisionobject linguist-language=JSON5 +*.cubemap linguist-language=JSON5 +*.display_profiles linguist-language=JSON5 +*.factory linguist-language=JSON5 +*.font linguist-language=JSON5 +*.gamepads linguist-language=JSON5 +*.go linguist-language=JSON5 +*.gui linguist-language=JSON5 +*.input_binding linguist-language=JSON5 +*.label linguist-language=JSON5 +*.material linguist-language=JSON5 +*.mesh linguist-language=JSON5 +*.model linguist-language=JSON5 +*.particlefx linguist-language=JSON5 +*.render linguist-language=JSON5 +*.sound linguist-language=JSON5 +*.sprite linguist-language=JSON5 +*.spinemodel linguist-language=JSON5 +*.spinescene linguist-language=JSON5 +*.texture_profiles linguist-language=JSON5 +*.tilemap linguist-language=JSON5 +*.tilesource linguist-language=JSON5 + +# Defold JSON Files +*.buffer linguist-language=JSON + +# Defold GLSL Shaders +*.fp linguist-language=GLSL +*.vp linguist-language=GLSL + +# Defold Lua Files +*.editor_script linguist-language=Lua +*.render_script linguist-language=Lua +*.script linguist-language=Lua +*.gui_script linguist-language=Lua diff --git a/animation/infinite-scroll-animation/.gitignore b/animation/infinite-scroll-animation/.gitignore new file mode 100644 index 00000000..0f4d6130 --- /dev/null +++ b/animation/infinite-scroll-animation/.gitignore @@ -0,0 +1,11 @@ +/.editor_settings +/.internal +/build +.externalToolBuilders +.DS_Store +Thumbs.db +.lock-wscript +*.pyc +.project +.cproject +builtins \ No newline at end of file diff --git a/animation/infinite-scroll-animation/example.md b/animation/infinite-scroll-animation/example.md new file mode 100644 index 00000000..1da48945 --- /dev/null +++ b/animation/infinite-scroll-animation/example.md @@ -0,0 +1,91 @@ +--- +title: Infinite Scroll Animation +brief: Learn how to make infinite scroll for all objects on your level with only two scripts +author: Evgenii Starostin +scripts: scroll_item.script, scroll_controller.script +thumbnail: thumbnail.webp +tags: animation +--- +This example demonstrates a reusable infinite scrolling system for Defold. The setup works with any type of game object and allows you to build multiple scrolling layers, making it useful for parallax backgrounds, decorations, or endlessly moving scenery. + +The project uses assets from the [Shape Characters](https://kenney.nl/assets/shape-characters) by Kenney. + + +## Project Structure +The scene is built from several independent scrolling layers. + +``` +main.collection +├── floor.collection +├── background.collection +├── middle.collection +└── foreground.collection +``` +Each layer is a collection containing: + +a root game object with `scroll_controller.script` +one or more scrolling game objects +`scroll_item.script` attached to every scrolling object + +Each layer can have its own speed, direction and scrolling bounds. + +## Registering Objects +Every scrolling object contains a small helper script. + +During initialization it registers itself with the layer controller by sending its game object id. +```lua +function init(self) + msg.post("root#scroll_controller", "prop:register", { + id = go.get_id() + }) +end +``` +The controller collects all registered objects before starting the scrolling logic. + +## Controller Properties +Each scrolling layer can be configured using script properties. +```lua +go.property("SPEED", 90) +go.property("DIRECTION", -1) +go.property("LEFT_BOUND", -1000) +go.property("RIGHT_BOUND", 1000) +go.property("COUNT_OBJECTS", 1) +``` +| Property | Description | +| -------- | ------- | +| SPEED | Scrolling speed in pixels per second. | +| DIRECTION | Scroll direction (-1 for left, 1 for right). | +| LEFT_BOUND | Position where objects wrap when scrolling left. | +| RIGHT_BOUND | Position where objects wrap when scrolling right. | +| COUNT_OBJECTS | Number of scrolling objects expected in the layer. | + +Different collection instances can override these values to create parallax effects without changing any code. + +## How It Works +Once all objects have registered, the controller: +- Reads the position of every object. +- Sorts them from left to right. +- Calculates the distance between each object and its neighbour. + +Instead of using a fixed spacing value, the controller stores the original distance between neighbouring objects. This means the objects can be placed freely in the editor while preserving their layout during scrolling. + +Every frame, all objects move by the same amount. + +When the leftmost object reaches the left boundary, it is moved to the right side of the layer using the previously calculated spacing. The same logic can also be applied when scrolling in the opposite direction. + +Because the original spacing is preserved, the scrolling loop appears seamless even when objects are unevenly distributed. + +## Customizing +The system is designed to be reusable. + +You can: +- Add or remove scrolling objects. +- Use sprites, animated game objects, labels, or any other game object. +- Create multiple scrolling layers with different speeds. +- Adjust the scrolling bounds for each layer. +- Reverse the scrolling direction. + +No changes to the scripts are required when creating additional layers. + +## Summary +By separating object registration from movement logic, the same scrolling system can be reused across many collections. Each layer manages its own objects independently, making it easy to build infinite scrolling scenes and parallax backgrounds while keeping the project structure simple \ No newline at end of file diff --git a/animation/infinite-scroll-animation/game.project b/animation/infinite-scroll-animation/game.project new file mode 100644 index 00000000..dd58be6b --- /dev/null +++ b/animation/infinite-scroll-animation/game.project @@ -0,0 +1,24 @@ +[bootstrap] +main_collection = /main/main.collectionc + +[script] +shared_state = 1 + +[display] +width = 720 +height = 720 +high_dpi = 1 + +[android] +input_method = HiddenInputField + +[physics] +gravity_y = -1000.0 +scale = 0.01 + +[html5] +scale_mode = stretch + +[project] +title = infinite-scroll-animation + diff --git a/animation/infinite-scroll-animation/input/game.input_binding b/animation/infinite-scroll-animation/input/game.input_binding new file mode 100644 index 00000000..8ed1d4e4 --- /dev/null +++ b/animation/infinite-scroll-animation/input/game.input_binding @@ -0,0 +1,4 @@ +mouse_trigger { + input: MOUSE_BUTTON_1 + action: "touch" +} diff --git a/animation/infinite-scroll-animation/main/atlas.atlas b/animation/infinite-scroll-animation/main/atlas.atlas new file mode 100644 index 00000000..b665927d --- /dev/null +++ b/animation/infinite-scroll-animation/main/atlas.atlas @@ -0,0 +1,20 @@ +images { + image: "/main/images/tile_grey.png" +} +images { + image: "/main/images/blue_body_square.png" + pivot_y: 1.0 +} +images { + image: "/main/images/green_body_square.png" + pivot_y: 1.0 +} +images { + image: "/main/images/purple_body_square.png" + pivot_y: 1.0 +} +images { + image: "/main/images/red_body_square.png" + pivot_y: 1.0 +} +extrude_borders: 2 diff --git a/animation/infinite-scroll-animation/main/images/blue_body_square.png b/animation/infinite-scroll-animation/main/images/blue_body_square.png new file mode 100644 index 00000000..f0d5ba4d Binary files /dev/null and b/animation/infinite-scroll-animation/main/images/blue_body_square.png differ diff --git a/animation/infinite-scroll-animation/main/images/green_body_square.png b/animation/infinite-scroll-animation/main/images/green_body_square.png new file mode 100644 index 00000000..6aff1d25 Binary files /dev/null and b/animation/infinite-scroll-animation/main/images/green_body_square.png differ diff --git a/animation/infinite-scroll-animation/main/images/purple_body_square.png b/animation/infinite-scroll-animation/main/images/purple_body_square.png new file mode 100644 index 00000000..a52ce13c Binary files /dev/null and b/animation/infinite-scroll-animation/main/images/purple_body_square.png differ diff --git a/animation/infinite-scroll-animation/main/images/red_body_square.png b/animation/infinite-scroll-animation/main/images/red_body_square.png new file mode 100644 index 00000000..9b19f3f3 Binary files /dev/null and b/animation/infinite-scroll-animation/main/images/red_body_square.png differ diff --git a/animation/infinite-scroll-animation/main/images/tile_grey.png b/animation/infinite-scroll-animation/main/images/tile_grey.png new file mode 100644 index 00000000..2addcca6 Binary files /dev/null and b/animation/infinite-scroll-animation/main/images/tile_grey.png differ diff --git a/animation/infinite-scroll-animation/main/main.collection b/animation/infinite-scroll-animation/main/main.collection new file mode 100644 index 00000000..494a5408 --- /dev/null +++ b/animation/infinite-scroll-animation/main/main.collection @@ -0,0 +1,48 @@ +name: "main" +collection_instances { + id: "floor" + collection: "/main/scroll_manager/floor.collection" + position { + x: 363.0 + z: 0.9 + } +} +collection_instances { + id: "foreground" + collection: "/main/scroll_manager/red.collection" + position { + x: 360.0 + y: 80.0 + z: 0.5 + } + instance_properties { + id: "root" + properties { + id: "scroll_controller" + properties { + id: "SPEED" + value: "70.0" + type: PROPERTY_TYPE_NUMBER + } + } + } +} +collection_instances { + id: "middle" + collection: "/main/scroll_manager/green.collection" + position { + x: 358.0 + y: 80.0 + z: 0.9 + } +} +collection_instances { + id: "background" + collection: "/main/scroll_manager/puprple.collection" + position { + x: 396.0 + y: 80.0 + z: 0.2 + } +} +scale_along_z: 0 diff --git a/animation/infinite-scroll-animation/main/scroll_manager/floor.collection b/animation/infinite-scroll-animation/main/scroll_manager/floor.collection new file mode 100644 index 00000000..9eb99a44 --- /dev/null +++ b/animation/infinite-scroll-animation/main/scroll_manager/floor.collection @@ -0,0 +1,79 @@ +name: "scroll" +instances { + id: "floor_object" + prototype: "/main/scroll_manager/floor_object.go" + position { + x: -90.0 + y: 40.0 + } +} +instances { + id: "floor_object1" + prototype: "/main/scroll_manager/floor_object.go" + position { + x: -270.0 + y: 40.0 + } +} +instances { + id: "floor_object2" + prototype: "/main/scroll_manager/floor_object.go" + position { + x: 270.0 + y: 40.0 + } +} +instances { + id: "floor_object3" + prototype: "/main/scroll_manager/floor_object.go" + position { + x: 90.0 + y: 40.0 + } +} +instances { + id: "floor_object4" + prototype: "/main/scroll_manager/floor_object.go" + position { + x: 450.0 + y: 40.0 + } +} +instances { + id: "floor_object5" + prototype: "/main/scroll_manager/floor_object.go" + position { + x: -450.0 + y: 40.0 + } +} +scale_along_z: 0 +embedded_instances { + id: "root" + children: "floor_object" + children: "floor_object1" + children: "floor_object2" + children: "floor_object3" + children: "floor_object4" + children: "floor_object5" + data: "components {\n" + " id: \"scroll_controller\"\n" + " component: \"/main/scroll_manager/scroll_controller.script\"\n" + " properties {\n" + " id: \"LEFT_BOUND\"\n" + " value: \"-540.0\"\n" + " type: PROPERTY_TYPE_NUMBER\n" + " }\n" + " properties {\n" + " id: \"RIGHT_BOUND\"\n" + " value: \"540.0\"\n" + " type: PROPERTY_TYPE_NUMBER\n" + " }\n" + " properties {\n" + " id: \"COUNT_OBJECTS\"\n" + " value: \"6.0\"\n" + " type: PROPERTY_TYPE_NUMBER\n" + " }\n" + "}\n" + "" +} diff --git a/animation/infinite-scroll-animation/main/scroll_manager/floor_object.go b/animation/infinite-scroll-animation/main/scroll_manager/floor_object.go new file mode 100644 index 00000000..7964ae30 --- /dev/null +++ b/animation/infinite-scroll-animation/main/scroll_manager/floor_object.go @@ -0,0 +1,26 @@ +components { + id: "prop_controller" + component: "/main/scroll_manager/scroll_item.script" +} +embedded_components { + id: "sprite" + type: "sprite" + data: "default_animation: \"tile_grey\"\n" + "material: \"/builtins/materials/sprite.material\"\n" + "slice9 {\n" + " x: 32.0\n" + " y: 32.0\n" + " z: 32.0\n" + " w: 32.0\n" + "}\n" + "size {\n" + " x: 180.0\n" + " y: 80.0\n" + "}\n" + "size_mode: SIZE_MODE_MANUAL\n" + "textures {\n" + " sampler: \"texture_sampler\"\n" + " texture: \"/main/atlas.atlas\"\n" + "}\n" + "" +} diff --git a/animation/infinite-scroll-animation/main/scroll_manager/green.collection b/animation/infinite-scroll-animation/main/scroll_manager/green.collection new file mode 100644 index 00000000..e0980713 --- /dev/null +++ b/animation/infinite-scroll-animation/main/scroll_manager/green.collection @@ -0,0 +1,57 @@ +name: "scroll" +instances { + id: "green_object" + prototype: "/main/scroll_manager/green_object.go" + position { + x: -289.0 + } +} +instances { + id: "green_object1" + prototype: "/main/scroll_manager/green_object.go" + position { + x: -42.0 + } +} +instances { + id: "green_object2" + prototype: "/main/scroll_manager/green_object.go" + position { + x: 155.0 + } +} +instances { + id: "green_object3" + prototype: "/main/scroll_manager/green_object.go" + position { + x: 482.0 + } +} +scale_along_z: 0 +embedded_instances { + id: "root" + children: "green_object" + children: "green_object1" + children: "green_object2" + children: "green_object3" + data: "components {\n" + " id: \"scroll_controller\"\n" + " component: \"/main/scroll_manager/scroll_controller.script\"\n" + " properties {\n" + " id: \"LEFT_BOUND\"\n" + " value: \"-544.0\"\n" + " type: PROPERTY_TYPE_NUMBER\n" + " }\n" + " properties {\n" + " id: \"RIGHT_BOUND\"\n" + " value: \"544.0\"\n" + " type: PROPERTY_TYPE_NUMBER\n" + " }\n" + " properties {\n" + " id: \"COUNT_OBJECTS\"\n" + " value: \"4.0\"\n" + " type: PROPERTY_TYPE_NUMBER\n" + " }\n" + "}\n" + "" +} diff --git a/animation/infinite-scroll-animation/main/scroll_manager/green_object.go b/animation/infinite-scroll-animation/main/scroll_manager/green_object.go new file mode 100644 index 00000000..e1e8a78b --- /dev/null +++ b/animation/infinite-scroll-animation/main/scroll_manager/green_object.go @@ -0,0 +1,26 @@ +components { + id: "prop_controller" + component: "/main/scroll_manager/scroll_item.script" +} +embedded_components { + id: "sprite" + type: "sprite" + data: "default_animation: \"green_body_square\"\n" + "material: \"/builtins/materials/sprite.material\"\n" + "slice9 {\n" + " x: 12.0\n" + " y: 12.0\n" + " z: 12.0\n" + " w: 12.0\n" + "}\n" + "size {\n" + " x: 64.0\n" + " y: 32.0\n" + "}\n" + "size_mode: SIZE_MODE_MANUAL\n" + "textures {\n" + " sampler: \"texture_sampler\"\n" + " texture: \"/main/atlas.atlas\"\n" + "}\n" + "" +} diff --git a/animation/infinite-scroll-animation/main/scroll_manager/puprple.collection b/animation/infinite-scroll-animation/main/scroll_manager/puprple.collection new file mode 100644 index 00000000..f543a8fe --- /dev/null +++ b/animation/infinite-scroll-animation/main/scroll_manager/puprple.collection @@ -0,0 +1,46 @@ +name: "scroll" +instances { + id: "purple_object" + prototype: "/main/scroll_manager/purple_object.go" + position { + x: -267.0 + } +} +instances { + id: "purple_object1" + prototype: "/main/scroll_manager/purple_object.go" + position { + x: 225.0 + } +} +scale_along_z: 0 +embedded_instances { + id: "root" + children: "purple_object" + children: "purple_object1" + data: "components {\n" + " id: \"scroll_controller\"\n" + " component: \"/main/scroll_manager/scroll_controller.script\"\n" + " properties {\n" + " id: \"SPEED\"\n" + " value: \"45.0\"\n" + " type: PROPERTY_TYPE_NUMBER\n" + " }\n" + " properties {\n" + " id: \"LEFT_BOUND\"\n" + " value: \"-544.0\"\n" + " type: PROPERTY_TYPE_NUMBER\n" + " }\n" + " properties {\n" + " id: \"RIGHT_BOUND\"\n" + " value: \"544.0\"\n" + " type: PROPERTY_TYPE_NUMBER\n" + " }\n" + " properties {\n" + " id: \"COUNT_OBJECTS\"\n" + " value: \"2.0\"\n" + " type: PROPERTY_TYPE_NUMBER\n" + " }\n" + "}\n" + "" +} diff --git a/animation/infinite-scroll-animation/main/scroll_manager/purple_object.go b/animation/infinite-scroll-animation/main/scroll_manager/purple_object.go new file mode 100644 index 00000000..9d2ca435 --- /dev/null +++ b/animation/infinite-scroll-animation/main/scroll_manager/purple_object.go @@ -0,0 +1,26 @@ +components { + id: "prop_controller" + component: "/main/scroll_manager/scroll_item.script" +} +embedded_components { + id: "sprite" + type: "sprite" + data: "default_animation: \"purple_body_square\"\n" + "material: \"/builtins/materials/sprite.material\"\n" + "slice9 {\n" + " x: 12.0\n" + " y: 12.0\n" + " z: 12.0\n" + " w: 12.0\n" + "}\n" + "size {\n" + " x: 120.0\n" + " y: 150.0\n" + "}\n" + "size_mode: SIZE_MODE_MANUAL\n" + "textures {\n" + " sampler: \"texture_sampler\"\n" + " texture: \"/main/atlas.atlas\"\n" + "}\n" + "" +} diff --git a/animation/infinite-scroll-animation/main/scroll_manager/red.collection b/animation/infinite-scroll-animation/main/scroll_manager/red.collection new file mode 100644 index 00000000..3ad342b1 --- /dev/null +++ b/animation/infinite-scroll-animation/main/scroll_manager/red.collection @@ -0,0 +1,77 @@ +name: "scroll" +instances { + id: "red_object" + prototype: "/main/scroll_manager/red_object.go" + position { + x: -386.0 + } +} +instances { + id: "red_object1" + prototype: "/main/scroll_manager/red_object.go" + position { + x: -170.0 + } + scale3 { + x: 0.609204 + y: 0.593882 + } +} +instances { + id: "red_object2" + prototype: "/main/scroll_manager/red_object.go" + position { + x: 53.0 + } +} +instances { + id: "red_object3" + prototype: "/main/scroll_manager/red_object.go" + position { + x: 410.0 + } + scale3 { + x: 1.578199 + y: 1.621207 + } +} +instances { + id: "red_object4" + prototype: "/main/scroll_manager/red_object.go" + position { + x: 230.0 + } + scale3 { + x: 0.482741 + y: 0.458382 + } +} +scale_along_z: 0 +embedded_instances { + id: "root" + children: "red_object" + children: "red_object1" + children: "red_object2" + children: "red_object3" + children: "red_object4" + data: "components {\n" + " id: \"scroll_controller\"\n" + " component: \"/main/scroll_manager/scroll_controller.script\"\n" + " properties {\n" + " id: \"LEFT_BOUND\"\n" + " value: \"-544.0\"\n" + " type: PROPERTY_TYPE_NUMBER\n" + " }\n" + " properties {\n" + " id: \"RIGHT_BOUND\"\n" + " value: \"544.0\"\n" + " type: PROPERTY_TYPE_NUMBER\n" + " }\n" + " properties {\n" + " id: \"COUNT_OBJECTS\"\n" + " value: \"5.0\"\n" + " type: PROPERTY_TYPE_NUMBER\n" + " }\n" + "}\n" + "" +} diff --git a/animation/infinite-scroll-animation/main/scroll_manager/red_object.go b/animation/infinite-scroll-animation/main/scroll_manager/red_object.go new file mode 100644 index 00000000..5b7f438c --- /dev/null +++ b/animation/infinite-scroll-animation/main/scroll_manager/red_object.go @@ -0,0 +1,26 @@ +components { + id: "prop_controller" + component: "/main/scroll_manager/scroll_item.script" +} +embedded_components { + id: "sprite" + type: "sprite" + data: "default_animation: \"red_body_square\"\n" + "material: \"/builtins/materials/sprite.material\"\n" + "slice9 {\n" + " x: 12.0\n" + " y: 12.0\n" + " z: 12.0\n" + " w: 12.0\n" + "}\n" + "size {\n" + " x: 64.0\n" + " y: 80.0\n" + "}\n" + "size_mode: SIZE_MODE_MANUAL\n" + "textures {\n" + " sampler: \"texture_sampler\"\n" + " texture: \"/main/atlas.atlas\"\n" + "}\n" + "" +} diff --git a/animation/infinite-scroll-animation/main/scroll_manager/scroll_controller.script b/animation/infinite-scroll-animation/main/scroll_manager/scroll_controller.script new file mode 100644 index 00000000..985e44ce --- /dev/null +++ b/animation/infinite-scroll-animation/main/scroll_manager/scroll_controller.script @@ -0,0 +1,78 @@ +go.property("SPEED", 90) +go.property("DIRECTION", -1) +go.property("LEFT_BOUND", -1000) +go.property("RIGHT_BOUND", 1000) +go.property("COUNT_OBJECTS", 1) + + +local function register_objects(self) + for i, obj in ipairs(self.OBJECTS) do + obj.pos = go.get_position(obj.id) + end + + table.sort(self.OBJECTS, function (a, b) + return a.pos.x < b.pos.x + end) + + for i = 1, #self.OBJECTS - 1 do + local current = self.OBJECTS[i] + local next = self.OBJECTS[i + 1] + + current.next_offset = next.pos.x - current.pos.x + end + + self.OBJECTS[#self.OBJECTS].next_offset = + (self.RIGHT_BOUND - self.OBJECTS[#self.OBJECTS].pos.x) + + + (self.OBJECTS[1].pos.x - self.LEFT_BOUND) +end + + +function init(self) + self.OBJECTS = {} +end + + +function update(self, dt) + local dx = self.SPEED * self.DIRECTION * dt + + for _, obj in ipairs(self.OBJECTS) do + obj.pos.x = obj.pos.x + dx + go.set_position(obj.pos, obj.id) + end + + if self.DIRECTION == -1 then + local leftmost = self.OBJECTS[1] + if leftmost.pos.x < self.LEFT_BOUND then + local rightmost = self.OBJECTS[#self.OBJECTS] + leftmost.pos.x = rightmost.pos.x + rightmost.next_offset + go.set_position(leftmost.pos, leftmost.id) + + table.sort(self.OBJECTS, function(a,b) + return a.pos.x < b.pos.x + end) + + end + return + else + local rightmost = self.OBJECTS[#self.OBJECTS] + if rightmost.pos.x > self.RIGHT_BOUND then + local leftmost = self.OBJECTS[1] + rightmost.pos.x = leftmost.pos.x -- - self.SPACING + table.sort(self.OBJECTS, function (a, b) + return a.pos.x < b.pos.x + end) + end + return + end +end + + +function on_message(self, message_id, message) + if message_id == hash("prop:register") then + table.insert(self.OBJECTS, { id = message.id }) + if #self.OBJECTS == self.COUNT_OBJECTS then + register_objects(self) + end + end +end \ No newline at end of file diff --git a/animation/infinite-scroll-animation/main/scroll_manager/scroll_item.script b/animation/infinite-scroll-animation/main/scroll_manager/scroll_item.script new file mode 100644 index 00000000..bafffe98 --- /dev/null +++ b/animation/infinite-scroll-animation/main/scroll_manager/scroll_item.script @@ -0,0 +1,3 @@ +function init(self) + msg.post("root#scroll_controller", "prop:register", { id = go.get_id() }) +end \ No newline at end of file diff --git a/animation/infinite-scroll-animation/thumbnail.webp b/animation/infinite-scroll-animation/thumbnail.webp new file mode 100644 index 00000000..93e65315 Binary files /dev/null and b/animation/infinite-scroll-animation/thumbnail.webp differ