Просмотр исходного кода

Merge branch 'master' of https://github.com/defold/examples

Björn Ritzl 2 недель назад
Родитель
Сommit
89696d26f6
44 измененных файлов с 1512 добавлено и 0 удалено
  1. BIN
      gui/localization/appmanifest.png
  2. BIN
      gui/localization/appmanifestgameproject.png
  3. BIN
      gui/localization/assets/fonts/NotoSansArabic-Medium.ttf
  4. BIN
      gui/localization/assets/fonts/NotoSansJP-Regular.ttf
  5. BIN
      gui/localization/assets/fonts/SourceSansPro-Semibold.ttf
  6. 5 0
      gui/localization/assets/fonts/latin_small.font
  7. 5 0
      gui/localization/assets/fonts/noto_ar.font
  8. 5 0
      gui/localization/assets/fonts/noto_ja.font
  9. 15 0
      gui/localization/assets/img/main.atlas
  10. BIN
      gui/localization/assets/img/panel-transparent-border-029.png
  11. BIN
      gui/localization/assets/img/panel-transparent-center-025.png
  12. 4 0
      gui/localization/assets/texts/text_ar.json
  13. 4 0
      gui/localization/assets/texts/text_en.json
  14. 4 0
      gui/localization/assets/texts/text_ja.json
  15. 4 0
      gui/localization/assets/texts/text_pt.json
  16. BIN
      gui/localization/customresources.png
  17. 120 0
      gui/localization/example.md
  18. 18 0
      gui/localization/example/lang_ar.collection
  19. 17 0
      gui/localization/example/lang_ja.collection
  20. 90 0
      gui/localization/example/localization_helper.lua
  21. 111 0
      gui/localization/example/main.appmanifest
  22. 22 0
      gui/localization/example/main.collection
  23. 249 0
      gui/localization/example/main.gui
  24. 124 0
      gui/localization/example/main.gui_script
  25. 169 0
      gui/localization/example/ui_helper.lua
  26. 34 0
      gui/localization/game.project
  27. 4 0
      gui/localization/input/game.input_binding
  28. BIN
      gui/localization/runtimefont.png
  29. BIN
      gui/localization/setup.png
  30. BIN
      gui/localization/thumbnail.png
  31. 18 0
      physics/apply_force/all.texture_profiles
  32. BIN
      physics/apply_force/assets/SourceSansPro-Semibold.ttf
  33. BIN
      physics/apply_force/assets/images/elementMetal001.png
  34. BIN
      physics/apply_force/assets/images/elementStone019.png
  35. BIN
      physics/apply_force/assets/images/elementStone023.png
  36. 15 0
      physics/apply_force/assets/sprites.atlas
  37. 5 0
      physics/apply_force/assets/text48.font
  38. 38 0
      physics/apply_force/example.md
  39. 312 0
      physics/apply_force/example/apply_force.collection
  40. 53 0
      physics/apply_force/example/apply_force.script
  41. 67 0
      physics/apply_force/game.project
  42. BIN
      physics/apply_force/gameproject.png
  43. BIN
      physics/apply_force/setup.png
  44. BIN
      physics/apply_force/thumbnail.png

BIN
gui/localization/appmanifest.png


BIN
gui/localization/appmanifestgameproject.png


BIN
gui/localization/assets/fonts/NotoSansArabic-Medium.ttf


BIN
gui/localization/assets/fonts/NotoSansJP-Regular.ttf


BIN
gui/localization/assets/fonts/SourceSansPro-Semibold.ttf


+ 5 - 0
gui/localization/assets/fonts/latin_small.font

@@ -0,0 +1,5 @@
+font: "/assets/fonts/SourceSansPro-Semibold.ttf"
+material: "/builtins/fonts/font-df.material"
+size: 24
+output_format: TYPE_DISTANCE_FIELD
+characters: " !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"

+ 5 - 0
gui/localization/assets/fonts/noto_ar.font

@@ -0,0 +1,5 @@
+font: "/assets/fonts/NotoSansArabic-Medium.ttf"
+material: "/builtins/fonts/font-df.material"
+size: 24
+output_format: TYPE_DISTANCE_FIELD
+characters: " !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"

+ 5 - 0
gui/localization/assets/fonts/noto_ja.font

@@ -0,0 +1,5 @@
+font: "/assets/fonts/NotoSansJP-Regular.ttf"
+material: "/builtins/fonts/font-df.material"
+size: 24
+output_format: TYPE_DISTANCE_FIELD
+characters: " !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"

+ 15 - 0
gui/localization/assets/img/main.atlas

@@ -0,0 +1,15 @@
+animations {
+  id: "button"
+  images {
+    image: "/assets/img/panel-transparent-border-029.png"
+  }
+  playback: PLAYBACK_NONE
+}
+animations {
+  id: "panel"
+  images {
+    image: "/assets/img/panel-transparent-center-025.png"
+  }
+  playback: PLAYBACK_LOOP_FORWARD
+}
+extrude_borders: 2

BIN
gui/localization/assets/img/panel-transparent-border-029.png


BIN
gui/localization/assets/img/panel-transparent-center-025.png


+ 4 - 0
gui/localization/assets/texts/text_ar.json

@@ -0,0 +1,4 @@
+{
+  "title": "Arabic",
+  "text": "هذا مثال بسيط للتعريب يوضح اتجاه الكتابة من اليمين إلى اليسار.أوفيسيا أوتي دولور ريروم أوفيسيا سايبي دويز كويوس إيسي إيد ديسيرونت ديبيتيس أوت فينيام نوبيس ريروم كومكوي أوت ألياس كومكوي كيو نام موليت أسوميندا فاسيري ريروم كويبوسدام دولوريبوس أوفيسيا كوي دولوروم."
+}

+ 4 - 0
gui/localization/assets/texts/text_en.json

@@ -0,0 +1,4 @@
+{
+  "title": "English",
+  "text": "This is a localization example. Lorem ipsum dolor sit amet, consequat molestiae quibusdam ea rerum aliquip quis nostrud reprehenderit culpa rerum quibusdam qui proident sed delectus nobis libero temporibus repellendus magna anim exercitation cillum quo mollit excepteur."
+}

+ 4 - 0
gui/localization/assets/texts/text_ja.json

@@ -0,0 +1,4 @@
+{
+  "title": "Japanese",
+  "text": "これはローカライズの簡単なサンプルです。読帝わめ横掲ぎ下禎っ確家アイノラ講各ぶきごと場条ハツセケ地況大レそば島直ナリメ転記左ス仙情テヒカマ出極企ぱて。露むぎ護毎ヘチオ当載るすぴフ面問ホナワ能減つ人多ツ下切サ期軍エ責9覧ク部際はぜ。"
+}

+ 4 - 0
gui/localization/assets/texts/text_pt.json

@@ -0,0 +1,4 @@
+{
+  "title": "Portuguese",
+  "text": "Este e um exemplo de localizacao. Lorem ipsum quidem ex maxime occaecat cum impedit qui saepe cumque culpa quis assumenda consectetur sit esse laborum repudiandae esse possimus tempor non autem optio non tempore optio adipiscing quis in eveniet."
+}

BIN
gui/localization/customresources.png


+ 120 - 0
gui/localization/example.md

@@ -0,0 +1,120 @@
+---
+tags: gui, font
+title: Localization (RTL/LTR)
+brief: This example demonstrates how to handle localization in games, Unicode text layout, RTL rendering, and runtime font switching for localization.
+author: The Defold Foundation
+scripts: main.gui_script, localization_helper.lua, ui_helper.lua
+thumbnail: thumbnail.png
+---
+
+This example demonstrates how to handle localization in games, Unicode text layout, RTL rendering, and runtime font switching for localization.
+
+Click the buttons (EN, AR, PT, JA) to switch between 4 languages.
+
+Arabic demonstrates right-to-left layout, while English/Portuguese/Japanese show left-to-right layout.
+
+## Approach overview
+
+The example is intentionally modularized and the flow in the program is linear:
+
+1. Read localized strings from a JSON file for the requested language.
+2. If the language needs a non-default font, load the font collection proxy asynchronously.
+3. Attach the font at runtime to the default GUI font resource.
+4. Prewarm glyphs for the requested text so the first render is smooth.
+5. Update the GUI text and layout (LTR/RTL) and restore input focus.
+
+This illustrates a practical localization flow in Defold:
+
+- Runtime font switching via `font.add_font()` / `font.remove_font()`.
+- Asynchronous proxy loading for large font resources.
+- Glyph prewarming with `font.prewarm_text()`.
+- LTR/RTL layout using text node pivot and position.
+
+To recreate such an example:
+
+1. Add to your project an [App Manifest](https://defold.com/manuals/app-manifest/) file with the option `Use full font layout system` enabled. The rest of the settings doesn't matter for this example, so are left as default.
+![appmanifest](appmanifest.png)
+
+2. Use this App Manifest file in the `game.project` file in the `Native Extension` section in the `App Manifest` setting.
+![appmanifestgameproject](appmanifestgameproject.png)
+
+3. Then, also in the `game.project` file in the `Font` section enable the `Runtime Generation` setting.
+![runtimefont](runtimefont.png)
+
+4. In the end, add the localization of the used JSON files in the `Project` section in the `Custom Resources` setting.
+![customresources](customresources.png)
+
+Project setup used by this example:
+
+- `game.project` with runtime font generation enabled and custom app manifest `main.appmanifest`.
+- `main.collection` with:
+    - game object `go` with:
+        - `ar_proxy` - collection proxy component referring to `lang_ar.collection` file.
+        - `ja_proxy` - collection proxy component referring to `lang_ja.collection` file.
+        - `main.gui` - GUI component with `main.gui_script`.
+
+The 2 mentioned collections (`lang_ar.collection` / `lang_ja.collection`) contain:
+- `go` game object with:
+    - `label` - component with `Font` property set to `noto_ar.font`/`noto_ja.font`
+
+In the `assets` folder there are two subfolders:
+- `fonts` - containing `.ttf` font files and `.font` Defold resources referencing those font files respectively
+- `img` - containing images and atlas used for GUI nodes.
+- `texts` - containing `.json` files with text examples and information about language used.
+
+![setup](setup.png)
+
+The separate Collections for components with fonts and Collection Proxies to load and unload them are added to show good practices on handling fonts - usually, you only need the one font that user selected in the settings, so rest of the fonts should be unloaded from memory.
+
+Therefore, we have in the example only one font that is defined in the GUI component, that is `latin`. The rest (Arabic `noto_ar` and Japanese `noto_ja`) are loaded using respective Collection Proxy components `ar_proxy` and `ja_proxy`.
+
+Those collections contain the game object with component in order to assign there a Font resource - `noto_ar` and `noto_ja`.
+
+A [Collection Proxy](https://defold.com/manuals/collection-proxy/) in Defold is a special component that allows you to load and unload entire collections (groups of game objects, components and resources) dynamically at runtime. In this example, proxies are used to manage fonts, so that only the font needed by the user is kept in memory at any time.
+
+### How are proxies loaded and unloaded?
+
+1. **Loading a proxy:**
+    - When the user clicks a language button (like "AR" or "JA"), the script checks if that language requires a special font.
+    - If so, it determines which proxy to use (`ar_proxy` for Arabic, `ja_proxy` for Japanese, etc.).
+    - The script sends the `async_load` message to the appropriate proxy, and Defold begins loading the target collection (`lang_ar.collection`, `lang_ja.collection`, etc.) asynchronously.
+    - Once the collection is loaded, the related assets (mainly the font resource) are available in RAM.
+    - The script receives a `proxy_loaded` message, and can now activate the font for GUI text.
+
+2. **Unloading a proxy:**
+    - When the user switches to another language that uses a different font (or goes back to a language using the default font), the no-longer-needed proxy should be unloaded.
+    - The script sends the `unload` message to the relevant proxy.
+    - After unloading, Defold automatically releases all resources from that collection—freeing the memory taken by the font and any related resources.
+    - Once unloading is finished, the script receives a `proxy_unloaded` message and may proceed to load or activate the next font as needed.
+
+### How are these collections (`lang_ar.collection`, `lang_ja.collection`) constructed?
+
+- Each collection contains just a single game object with an empty label (or text) component.
+- This component is configured to use the specific font file needed for that language (e.g., a TTF that supports Arabic or Japanese.)
+- No additional game logic or nodes are needed inside—these collections simply act as packages for the required font resource.
+
+This structure is a Defold best practice: the font is only referenced as long as the proxy is loaded. When the proxy is unloaded, Defold can fully release the font and its memory, keeping the application efficient. This is important for games with large multilingual font files; only the currently active font consumes RAM, even when switching languages at runtime.
+
+
+
+
+
+The localized text strings are loaded from disk (`text_en.json`, `text_ar.json`, `text_pt.json`, `text_ja.json`) using `sys.load_resource()`.
+
+## Helper modules
+
+The logic is split into two small helpers to keep the GUI script concise and focused on flow:
+
+- `localization_helper.lua`: Handles the language switch flow (load/unload proxies, attach runtime fonts, prewarm glyphs, finalize switch). It owns the small state machine around proxies and fonts and exposes a simple API to the GUI script.
+- `ui_helper.lua`: Handles GUI node lookup, button states and visuals, LTR/RTL layout changes, and input handling details. It keeps GUI operations in one place so the core localization logic stays easy to follow.
+
+## Assumptions and simplifications
+
+This example intentionally trades robustness for clarity:
+
+- Sequential flow only. It assumes one language switch at a time (unload old font, then load new font, then update UI). Because of this, `on_message` does not verify which proxy sent `proxy_loaded` / `proxy_unloaded`.
+- No JSON caching. The JSON is small and read on demand via `sys.load_resource()` and `json.decode()`. For larger or frequent loads, caching should be demonstrated in a separate example.
+- Data is trusted. The `languages` table is assumed to be correct and complete (including `json`, `layout`, `proxy`, and `ttf_hash` where required). The JSON is assumed to contain the expected fields (`title`, `text`).
+- Minimal defensive checks. Assertions and guards are kept light to avoid clutter.
+
+These choices keep the example readable and focused on the key idea.

+ 18 - 0
gui/localization/example/lang_ar.collection

@@ -0,0 +1,18 @@
+name: "lang_ar"
+scale_along_z: 0
+embedded_instances {
+  id: "go"
+  data: "embedded_components {\n"
+  "  id: \"label\"\n"
+  "  type: \"label\"\n"
+  "  data: \"size {\\n"
+  "  x: 128.0\\n"
+  "  y: 32.0\\n"
+  "}\\n"
+  "text: \\\"\\\\331\\\\205\\\\330\\\\254\\\\331\\\\205\\\\331\\\\210\\\\330\\\\271\\\\330\\\\247\\\\330\\\\252 \\\\330\\\\247\\\\331\\\\204\\\\330\\\\243\\\\330\\\\264\\\\331\\\\203\\\\330\\\\247\\\\331\\\\204 \\\\330\\\\247\\\\331\\\\204\\\\330\\\\252\\\\331\\\\212 \\\\331\\\\202\\\\331\\\\205\\\\330\\\\252 \\\\330\\\\250\\\\331\\\\201\\\\330\\\\252\\\\330\\\\255\\\\331\\\\207\\\\330\\\\247 \\\\331\\\\205\\\\331\\\\206 \\\\330\\\\264\\\\330\\\\247\\\\330\\\\264\\\\330\\\\247\\\\330\\\\252 \\\\330\\\\247\\\\331\\\\204\\\\330\\\\256\\\\330\\\\261\\\\331\\\\212\\\\330\\\\267\\\\330\\\\251 \\\\330\\\\243\\\\331\\\\210 \\\\330\\\\247\\\\331\\\\204\\\\331\\\\201\\\\330\\\\271\\\\330\\\\247\\\\331\\\\204\\\\331\\\\212\\\\330\\\\247\\\\330\\\\252 \\\\330\\\\263\\\\330\\\\252\\\\330\\\\270\\\\331\\\\207\\\\330\\\\261 \\\\331\\\\207\\\\331\\\\206\\\\330\\\\247.\\\"\\n"
+  "font: \\\"/assets/fonts/noto_ar.font\\\"\\n"
+  "material: \\\"/builtins/fonts/label-df.material\\\"\\n"
+  "\"\n"
+  "}\n"
+  ""
+}

+ 17 - 0
gui/localization/example/lang_ja.collection

@@ -0,0 +1,17 @@
+name: "lang_ar"
+scale_along_z: 0
+embedded_instances {
+  id: "go"
+  data: "embedded_components {\n"
+  "  id: \"label\"\n"
+  "  type: \"label\"\n"
+  "  data: \"size {\\n"
+  "  x: 128.0\\n"
+  "  y: 32.0\\n"
+  "}\\n"
+  "font: \\\"/assets/fonts/noto_ja.font\\\"\\n"
+  "material: \\\"/builtins/fonts/label-df.material\\\"\\n"
+  "\"\n"
+  "}\n"
+  ""
+}

+ 90 - 0
gui/localization/example/localization_helper.lua

@@ -0,0 +1,90 @@
+local M = {}
+
+-- Attach the font resource to the default font resource
+-- and prewarm the glyphs for smooth rendering.
+function M.set_runtime_font_and_prewarm(self, on_font_ready_callback)
+
+	-- If the requested language has a proxy, attach the runtime font:
+	if self.languages[self.requested_lang].proxy then
+		-- Add the runtime font to the default font resource.
+		font.add_font(self.default_font_resource, self.languages[self.requested_lang].ttf_hash)
+	end
+
+	-- Prewarm the glyphs for smooth rendering for the given requested text.
+	local text = self.requested_text.text
+	font.prewarm_text(self.default_font_resource, text, on_font_ready_callback)
+end
+
+-- Finalize language switch once the required font can render requested text.
+function M.finish_language_change(self, on_font_ready_callback)
+	-- Prepare a callback function that will be called when fonts are ready.
+	local on_language_changed = function()
+		
+		-- New language has been set, change the current language variable.
+		self.current_lang = self.requested_lang
+
+		on_font_ready_callback(self)
+	end
+
+	-- Set the runtime font and prewarm the glyphs for smooth rendering.
+	M.set_runtime_font_and_prewarm(self, on_language_changed)
+end
+
+-- Process the language change.
+function M.process_language_change(self, callback_on_font_ready)
+	-- If the requested language is the same as the current language:
+	if self.requested_lang == self.current_lang then
+
+		-- No change needed, skip the rest of the language change process.
+		return
+	end
+
+	-- Otherwise:
+	-- Release the input focus during the language change,
+	-- so the user cannot interact in the meantime.
+	-- You can still allow it, if needed in your game,
+	-- but in that case keep track of the status of (un)loading.
+	msg.post(".", "release_input_focus")
+
+	local requested_language_proxy = self.languages[self.requested_lang].proxy
+	local current_language_proxy = self.languages[self.current_lang].proxy
+
+	-- If the requested language font is the same as the current:
+	if requested_language_proxy == current_language_proxy then
+		-- Finish the language change with prewarm.
+		M.finish_language_change(self, callback_on_font_ready)
+
+		-- And skip the rest of the language change process.
+		return
+	end
+
+	-- Otherwise:
+	-- If the current language is with a loaded proxy:
+	if current_language_proxy then
+
+		-- Unload this collection with the font resource first.
+		msg.post(current_language_proxy, "unload")
+
+		-- And skip the rest of the language change process.
+		return
+	end
+
+	-- Load the new font.
+	msg.post(requested_language_proxy, "async_load")
+end
+
+
+-- JSON file handling ------------------------------------------
+
+-- Get the text for the requested language from the JSON file.
+function M.get_content_from_json(self)
+	-- Load the JSON file for the requested language.
+	local language = self.languages[self.requested_lang]
+	local json_file = sys.load_resource(language.json)
+	assert(json_file, "Failed to load JSON file for language: " .. self.requested_lang)
+
+	-- Return the decoded JSON file as a table.
+	return json.decode(json_file) or {}
+end
+
+return M

+ 111 - 0
gui/localization/example/main.appmanifest

@@ -0,0 +1,111 @@
+platforms:
+  armv7-ios:
+    context:
+      excludeLibs: [font]
+      excludeSymbols: []
+      symbols: []
+      libs: [font_skribidi, harfbuzz, sheenbidi, unibreak, skribidi]
+      frameworks: []
+      linkFlags: []
+  arm64-ios:
+    context:
+      excludeLibs: [font]
+      excludeSymbols: []
+      symbols: []
+      libs: [font_skribidi, harfbuzz, sheenbidi, unibreak, skribidi]
+      frameworks: []
+      linkFlags: []
+  x86_64-ios:
+    context:
+      excludeLibs: [font]
+      excludeSymbols: []
+      symbols: []
+      libs: [font_skribidi, harfbuzz, sheenbidi, unibreak, skribidi]
+      frameworks: []
+      linkFlags: []
+  armv7-android:
+    context:
+      excludeLibs: [font]
+      excludeJars: []
+      excludeSymbols: []
+      symbols: []
+      libs: [font_skribidi, harfbuzz, sheenbidi, unibreak, skribidi]
+      linkFlags: []
+      jetifier: true
+  arm64-android:
+    context:
+      excludeLibs: [font]
+      excludeJars: []
+      excludeSymbols: []
+      symbols: []
+      libs: [font_skribidi, harfbuzz, sheenbidi, unibreak, skribidi]
+      linkFlags: []
+      jetifier: true
+  arm64-osx:
+    context:
+      excludeLibs: [font]
+      excludeSymbols: []
+      symbols: []
+      libs: [font_skribidi, harfbuzz, sheenbidi, unibreak, skribidi]
+      frameworks: []
+      linkFlags: []
+  x86_64-osx:
+    context:
+      excludeLibs: [font]
+      excludeSymbols: []
+      symbols: []
+      libs: [font_skribidi, harfbuzz, sheenbidi, unibreak, skribidi]
+      frameworks: []
+      linkFlags: []
+  x86_64-linux:
+    context:
+      excludeLibs: [font]
+      excludeSymbols: []
+      symbols: []
+      libs: [font_skribidi, harfbuzz, sheenbidi, unibreak, skribidi]
+      linkFlags: []
+  arm64-linux:
+    context:
+      excludeLibs: [font]
+      excludeSymbols: []
+      symbols: []
+      libs: [font_skribidi, harfbuzz, sheenbidi, unibreak, skribidi]
+      linkFlags: []
+  x86-win32:
+    context:
+      excludeLibs: [libfont]
+      excludeSymbols: []
+      symbols: []
+      libs: [libfont_skribidi.lib, libharfbuzz.lib, libsheenbidi.lib, libunibreak.lib, libskribidi.lib]
+      linkFlags: []
+  x86_64-win32:
+    context:
+      excludeLibs: [libfont]
+      excludeSymbols: []
+      symbols: []
+      libs: [libfont_skribidi.lib, libharfbuzz.lib, libsheenbidi.lib, libunibreak.lib, libskribidi.lib]
+      linkFlags: []
+  js-web:
+    context:
+      excludeLibs: [font]
+      excludeJsLibs: []
+      excludeSymbols: []
+      symbols: []
+      libs: [font_skribidi, harfbuzz, sheenbidi, unibreak, skribidi]
+      linkFlags: []
+  wasm-web:
+    context:
+      excludeLibs: [font]
+      excludeJsLibs: []
+      excludeSymbols: []
+      symbols: []
+      libs: [font_skribidi, harfbuzz, sheenbidi, unibreak, skribidi]
+      linkFlags: []
+  wasm_pthread-web:
+    context:
+      excludeLibs: [font]
+      excludeJsLibs: []
+      excludeSymbols: []
+      symbols: []
+      libs: [font_skribidi, harfbuzz, sheenbidi, unibreak, skribidi]
+      linkFlags: []

+ 22 - 0
gui/localization/example/main.collection

@@ -0,0 +1,22 @@
+name: "main"
+scale_along_z: 0
+embedded_instances {
+  id: "go"
+  data: "components {\n"
+  "  id: \"main\"\n"
+  "  component: \"/example/main.gui\"\n"
+  "}\n"
+  "embedded_components {\n"
+  "  id: \"ja_proxy\"\n"
+  "  type: \"collectionproxy\"\n"
+  "  data: \"collection: \\\"/example/lang_ja.collection\\\"\\n"
+  "\"\n"
+  "}\n"
+  "embedded_components {\n"
+  "  id: \"ar_proxy\"\n"
+  "  type: \"collectionproxy\"\n"
+  "  data: \"collection: \\\"/example/lang_ar.collection\\\"\\n"
+  "\"\n"
+  "}\n"
+  ""
+}

+ 249 - 0
gui/localization/example/main.gui

@@ -0,0 +1,249 @@
+script: "/example/main.gui_script"
+fonts {
+  name: "latin"
+  font: "/assets/fonts/latin_small.font"
+}
+textures {
+  name: "main"
+  texture: "/assets/img/main.atlas"
+}
+nodes {
+  position {
+    x: 480.0
+    y: 320.0
+  }
+  size {
+    x: 200.0
+    y: 100.0
+  }
+  type: TYPE_BOX
+  id: "center"
+  adjust_mode: ADJUST_MODE_STRETCH
+  inherit_alpha: true
+  size_mode: SIZE_MODE_AUTO
+  visible: false
+}
+nodes {
+  position {
+    y: 100.0
+  }
+  size {
+    x: 800.0
+    y: 200.0
+  }
+  type: TYPE_BOX
+  texture: "main/panel"
+  id: "panel_text"
+  parent: "center"
+  layer: "box"
+  inherit_alpha: true
+  slice9 {
+    x: 24.0
+    y: 24.0
+    z: 24.0
+    w: 24.0
+  }
+}
+nodes {
+  position {
+    x: -380.0
+  }
+  size {
+    x: 760.0
+    y: 160.0
+  }
+  type: TYPE_TEXT
+  text: "Text"
+  font: "latin"
+  id: "text"
+  pivot: PIVOT_W
+  line_break: true
+  parent: "panel_text"
+  layer: "text"
+  inherit_alpha: true
+}
+nodes {
+  position {
+    y: 250.0
+  }
+  size {
+    x: 800.0
+    y: 100.0
+  }
+  type: TYPE_TEXT
+  text: "Language"
+  font: "latin"
+  id: "language_name"
+  line_break: true
+  parent: "center"
+  layer: "text"
+  inherit_alpha: true
+}
+nodes {
+  position {
+    x: -300.0
+    y: -200.0
+  }
+  size {
+    x: 144.0
+    y: 96.0
+  }
+  type: TYPE_BOX
+  texture: "main/button"
+  id: "btn_en"
+  parent: "center"
+  layer: "box"
+  inherit_alpha: true
+  slice9 {
+    x: 24.0
+    y: 24.0
+    z: 24.0
+    w: 24.0
+  }
+}
+nodes {
+  size {
+    x: 200.0
+    y: 100.0
+  }
+  color {
+    x: 0.0
+    y: 0.0
+    z: 0.0
+  }
+  type: TYPE_TEXT
+  text: "EN"
+  font: "latin"
+  id: "btn_en_label"
+  parent: "btn_en"
+  layer: "text"
+  inherit_alpha: true
+}
+nodes {
+  position {
+    x: -100.0
+    y: -200.0
+  }
+  size {
+    x: 144.0
+    y: 96.0
+  }
+  type: TYPE_BOX
+  texture: "main/button"
+  id: "btn_ar"
+  parent: "center"
+  layer: "box"
+  inherit_alpha: true
+  slice9 {
+    x: 24.0
+    y: 24.0
+    z: 24.0
+    w: 24.0
+  }
+}
+nodes {
+  size {
+    x: 200.0
+    y: 100.0
+  }
+  color {
+    x: 0.0
+    y: 0.0
+    z: 0.0
+  }
+  type: TYPE_TEXT
+  text: "AR"
+  font: "latin"
+  id: "btn_ar_label"
+  parent: "btn_ar"
+  layer: "text"
+  inherit_alpha: true
+}
+nodes {
+  position {
+    x: 100.0
+    y: -200.0
+  }
+  size {
+    x: 144.0
+    y: 96.0
+  }
+  type: TYPE_BOX
+  texture: "main/button"
+  id: "btn_pt"
+  parent: "center"
+  layer: "box"
+  inherit_alpha: true
+  slice9 {
+    x: 24.0
+    y: 24.0
+    z: 24.0
+    w: 24.0
+  }
+}
+nodes {
+  size {
+    x: 200.0
+    y: 100.0
+  }
+  color {
+    x: 0.0
+    y: 0.0
+    z: 0.0
+  }
+  type: TYPE_TEXT
+  text: "PT"
+  font: "latin"
+  id: "btn_pt_label"
+  parent: "btn_pt"
+  layer: "text"
+  inherit_alpha: true
+}
+nodes {
+  position {
+    x: 300.0
+    y: -200.0
+  }
+  size {
+    x: 144.0
+    y: 96.0
+  }
+  type: TYPE_BOX
+  texture: "main/button"
+  id: "btn_ja"
+  parent: "center"
+  layer: "box"
+  inherit_alpha: true
+  slice9 {
+    x: 24.0
+    y: 24.0
+    z: 24.0
+    w: 24.0
+  }
+}
+nodes {
+  size {
+    x: 200.0
+    y: 100.0
+  }
+  color {
+    x: 0.0
+    y: 0.0
+    z: 0.0
+  }
+  type: TYPE_TEXT
+  text: "JA"
+  font: "latin"
+  id: "btn_ja_label"
+  parent: "btn_ja"
+  layer: "text"
+  inherit_alpha: true
+}
+layers {
+  name: "box"
+}
+layers {
+  name: "text"
+}
+material: "/builtins/materials/gui.material"
+adjust_reference: ADJUST_REFERENCE_PARENT

+ 124 - 0
gui/localization/example/main.gui_script

@@ -0,0 +1,124 @@
+-- Helper module for UI operations
+-- Separated in order not to clutter the example.
+local ui = require "example.ui_helper"
+
+-- Helper module for localization operations
+local loc = require "example.localization_helper"
+
+
+function init(self)
+	-- Per-language content path, layout (LTR/RTL),
+	-- and an optional proxy with font resource to load,
+	-- and a True Type Font (TTF) file pre-hashed path.
+	self.languages = {
+		en = {
+			json = "/assets/texts/text_en.json",
+			layout = ui.layout.ltr,
+			proxy = false,
+		},
+		ar = {
+			json = "/assets/texts/text_ar.json",
+			layout = ui.layout.rtl,
+			proxy = "#ar_proxy",
+			ttf_hash = hash("/assets/fonts/NotoSansArabic-Medium.ttf"),
+		},
+		pt = {
+			json = "/assets/texts/text_pt.json",
+			layout = ui.layout.ltr,
+			proxy = false,
+		},
+		ja = {
+			json = "/assets/texts/text_ja.json",
+			layout = ui.layout.ltr,
+			proxy = "#ja_proxy",
+			ttf_hash = hash("/assets/fonts/NotoSansJP-Regular.ttf"),
+		},
+	}
+
+	-- We delegate UI handling to a separate helper module
+	-- in order not to clutter the example.
+	ui.initialize_ui(self)
+
+	-- Store the font resource of the default font
+	-- that is initally used for the text gui node.
+	self.default_font_resource = ui.get_font_resource(self.text_node)
+
+	-- Set the GUI initial current language.
+	self.current_lang = "en"
+
+	-- Set the initial requested language to the same one.
+	self.requested_lang = "en"
+
+	-- Get the text for the requested language from the JSON file.
+	self.requested_text = loc.get_content_from_json(self)
+
+	-- Clear texts and update after fonts are prewarmed.
+	ui.clear_text_nodes(self)
+	loc.finish_language_change(self, ui.update_ui_content_callback)
+end
+
+-- Pre-hashed message IDs.
+local msg_proxy_loaded = hash("proxy_loaded")
+local msg_proxy_unloaded = hash("proxy_unloaded")
+
+function on_message(self, message_id, message, sender)
+	-- React to proxy lifecycle messages and continue pending language switch.
+	if message_id == msg_proxy_unloaded then
+
+		-- Remove runtime font once its owning proxy is unloaded.
+		font.remove_font(self.default_font_resource, self.languages[self.current_lang].ttf_hash)
+
+		-- If old font resource was unloaded, load the new one (or finish with default).
+		if self.languages[self.requested_lang].proxy then
+			msg.post(self.languages[self.requested_lang].proxy, "async_load")
+		else
+			loc.finish_language_change(self, ui.update_ui_content_callback)
+		end
+
+	elseif message_id == msg_proxy_loaded then
+		loc.finish_language_change(self, ui.update_ui_content_callback)
+	end
+end
+
+-- Pre-hashed action ID.
+local action_touch = hash("touch")
+
+function on_input(self, action_id, action)
+	-- Pointer move arrives with nil action_id in Defold.
+	if action_id == nil and action.x and action.y then
+		ui.on_pointer_moved(self, action.x, action.y)
+	end
+
+	-- If the action is not a touch:
+	if action_id ~= action_touch then
+		-- Skip the rest of the input handling.
+		return
+	end
+
+	-- If the action is a touch and pressed:
+	if action.pressed then
+		-- Get the selected language on pressed.
+		local selected_language = ui.get_selected_language_on_pressed(self, action.x, action.y)
+
+		-- Set the requested language and text.
+		self.requested_lang = selected_language or self.requested_lang
+
+		-- If the requested language is different from the current language:
+		if self.requested_lang ~= self.current_lang then
+			-- Clear current texts while the new font is prepared.
+			ui.clear_text_nodes(self)
+
+			-- Get the text for the requested language from the JSON file.
+			self.requested_text = loc.get_content_from_json(self)
+		end
+
+		-- Process the language change.
+		loc.process_language_change(self, ui.update_ui_content_callback)
+	end
+
+	-- If the action is a touch and released:
+	if action.released then
+		-- Handle the touch released event.
+		ui.on_touch_released(self, action.x, action.y)
+	end
+end

+ 169 - 0
gui/localization/example/ui_helper.lua

@@ -0,0 +1,169 @@
+local M = {}
+
+-- Font resource ------------------------------------------------
+
+-- Get the font resource of the default font.
+-- @param node_id The ID of the node to get the font resource of.
+-- @return The font resource of the default font.
+function M.get_font_resource(node_id)
+	-- Get the font resource of the default font.
+	return gui.get_font_resource(gui.get_font(node_id))
+end
+
+-- Buttons visuals ----------------------------------------------
+
+-- Visual states used by language selection buttons.
+M.button_state = {
+	released = 1,
+	hovered = 2,
+	pressed = 3,
+}
+
+-- Predefined button colors for the given button state.
+local button_color = {
+	[M.button_state.released] = vmath.vector4(1, 1, 1, 0.7),
+	[M.button_state.hovered] = vmath.vector4(1, 1, 1, 0.9),
+	[M.button_state.pressed] = vmath.vector4(1, 1, 1, 1.0),
+}
+
+-- UI Initialization --------------------------------------------
+
+-- Initialize UI with the given languages.
+-- @param self The self table from the GUI script.
+function M.initialize_ui(self)
+	-- Resolve GUI text nodes for information display once during init.
+	self.text_node = gui.get_node("text")
+	self.language_name_node = gui.get_node("language_name")
+
+	-- Create a table of all buttons with their language name, node, and state.
+	self.buttons = {}
+	for language_name, _ in pairs(self.languages) do
+		-- Set initial button state to released.
+		local state = M.button_state.released
+
+		-- Get the button node.
+		local node = gui.get_node("btn_" .. language_name)
+
+		-- Add the button to the buttons table.
+		self.buttons[language_name] = {
+			language_name = language_name,
+			node = node,
+			state = state
+		}
+
+		-- Update initial button color according to the state.
+		gui.set_color(node, button_color[state])
+	end
+
+	msg.post(".", "acquire_input_focus")
+end
+
+-- Layout definitions ------------------------------------------
+
+-- Supported text flow directions used by language layout.
+M.layout = {
+	ltr = "LTR",
+	rtl = "RTL",
+}
+
+-- Predefined text node positions for the given layout.
+local text_position = {
+	[M.layout.ltr] = vmath.vector3(-380, 0, 0),
+	[M.layout.rtl] = vmath.vector3(380, 0, 0),
+}
+
+-- Predefined text node pivots for the given layout.
+local text_pivot = {
+	[M.layout.ltr] = gui.PIVOT_W,
+	[M.layout.rtl] = gui.PIVOT_E,
+}
+
+-- Update UI content --------------------------------------------
+
+-- Update the UI content with the requested language.
+-- @param self The self table from the GUI script.
+function M.update_ui_content_callback(self)
+	-- Update the language text.
+	gui.set_text(self.text_node, self.requested_text.text or "")
+
+	-- Update the language name and layout information.
+	local layout = self.languages[self.requested_lang].layout
+	local header_text = string.format("%s (%s)", self.requested_text.title, layout)
+	gui.set_text(self.language_name_node, header_text)
+
+	-- Update the text layout.
+	gui.set_pivot(self.text_node, text_pivot[layout])
+	gui.set_position(self.text_node, text_position[layout])
+
+	-- Update the input focus.
+	msg.post(".", "acquire_input_focus")
+end
+
+-- Clear text nodes while a language change is in progress.
+-- @param self The self table from the GUI script.
+function M.clear_text_nodes(self)
+	gui.set_text(self.text_node, "")
+	gui.set_text(self.language_name_node, "")
+end
+
+-- Input handling -----------------------------------------------
+
+-- Handle pointer move event.
+-- @param self The self table from the GUI script.
+-- @param x The x position of the pointer.
+-- @param y The y position of the pointer.
+function M.on_pointer_moved(self, x, y)
+	-- Update the state of all buttons according to the pointer position.
+	for _, button in pairs(self.buttons) do
+		-- Check if the pointer is over the button.
+		if gui.pick_node(button.node, x, y) then
+			-- If so, set the button state to hovered.
+			button.state = M.button_state.hovered
+		else
+			-- Otherwise, set the button state to released.
+			button.state = M.button_state.released
+		end
+		-- Update the button color according to the new state.
+		gui.set_color(button.node, button_color[button.state])
+	end
+end
+
+-- Get the button pressed and return the language name.
+-- @param self The self table from the GUI script.
+-- @param x The x position of the pointer.
+-- @param y The y position of the pointer.
+function M.get_selected_language_on_pressed(self, x, y)
+	local selected_language = nil
+	-- Find the button pressed.
+	for _, button in pairs(self.buttons) do
+		-- Check if the pointer is over the button.
+		if gui.pick_node(button.node, x, y) then
+			-- If so, set the button state to pressed.
+			button.state = M.button_state.pressed
+			selected_language = button.language_name
+		end
+		-- Update the button color according to the new state.
+		gui.set_color(button.node, button_color[button.state])
+	end
+	-- Return the language name of the button pressed, nil if not found
+	return selected_language
+end
+
+-- Handle touch released event.
+-- @param self The self table from the GUI script.
+-- @param x The x position of the pointer.
+-- @param y The y position of the pointer.
+function M.on_touch_released(self, x, y)
+	-- Find the button released.
+	for _, button in pairs(self.buttons) do
+		-- Check if the pointer is over the button.
+		if gui.pick_node(button.node, x, y) then
+			-- If so, set the button state to released.
+			button.state = M.button_state.released
+		end
+		-- Update the button color according to the new state.
+		gui.set_color(button.node, button_color[button.state])
+	end
+end
+
+return M

+ 34 - 0
gui/localization/game.project

@@ -0,0 +1,34 @@
+[bootstrap]
+main_collection = /example/main.collectionc
+
+[input]
+game_binding = /input/game.input_bindingc
+
+[script]
+shared_state = 1
+
+[display]
+width = 960
+height = 640
+high_dpi = 1
+
+[android]
+input_method = HiddenInputField
+
+[physics]
+gravity_y = -1000.0
+scale = 0.01
+
+[html5]
+scale_mode = stretch
+
+[project]
+title = localization
+custom_resources = assets/texts
+
+[font]
+runtime_generation = 1
+
+[native_extension]
+app_manifest = /example/main.appmanifest
+

+ 4 - 0
gui/localization/input/game.input_binding

@@ -0,0 +1,4 @@
+mouse_trigger {
+  input: MOUSE_BUTTON_1
+  action: "touch"
+}

BIN
gui/localization/runtimefont.png


BIN
gui/localization/setup.png


BIN
gui/localization/thumbnail.png


+ 18 - 0
physics/apply_force/all.texture_profiles

@@ -0,0 +1,18 @@
+path_settings {
+  path: "**"
+  profile: "Default"
+}
+profiles {
+  name: "Default"
+  platforms {
+    os: OS_ID_GENERIC
+    formats {
+      format: TEXTURE_FORMAT_RGBA
+      compression_level: BEST
+      compression_type: COMPRESSION_TYPE_DEFAULT
+    }
+    mipmaps: false
+    max_texture_size: 0
+    premultiply_alpha: true
+  }
+}

BIN
physics/apply_force/assets/SourceSansPro-Semibold.ttf


BIN
physics/apply_force/assets/images/elementMetal001.png


BIN
physics/apply_force/assets/images/elementStone019.png


BIN
physics/apply_force/assets/images/elementStone023.png


+ 15 - 0
physics/apply_force/assets/sprites.atlas

@@ -0,0 +1,15 @@
+images {
+  image: "/assets/images/elementStone019.png"
+}
+images {
+  image: "/assets/images/elementStone023.png"
+}
+images {
+  image: "/assets/images/elementMetal001.png"
+}
+animations {
+  id: "coin"
+  playback: PLAYBACK_LOOP_FORWARD
+  fps: 8
+}
+extrude_borders: 2

+ 5 - 0
physics/apply_force/assets/text48.font

@@ -0,0 +1,5 @@
+font: "/assets/SourceSansPro-Semibold.ttf"
+material: "/builtins/fonts/font.material"
+size: 48
+outline_alpha: 0.0
+outline_width: 0.0

+ 38 - 0
physics/apply_force/example.md

@@ -0,0 +1,38 @@
+---
+tags: physics
+title: Apply force
+brief: This example demonstrates how to apply directional force to all dynamic blocks on touch/click and draws debug direction lines.
+thumbnail: thumbnail.png
+scripts: apply_force.script
+---
+
+This example demonstrates how to apply directional force to all dynamic blocks on touch/click and draws debug direction lines.
+
+## Setup
+
+![setup](setup.png)
+
+Scene consists of a few game objects:
+
+- `controller`
+  - Main object that contains `/example/apply_force.script` and a label with usage text.
+- `block1`, `block2`, `block3`, `block4`
+  - Dynamic rigid bodies (sprite + dynamic collision object).
+- `walls`
+  - Static boundary collision object around the screen.
+
+Proposed settings regarding physics in the `game.project` file:
+
+![gameproject](gameproject.png)
+
+## Script flow
+
+A single controller script handles input for the whole scene.  
+When you touch/click, it loops over all dynamic blocks, computes a force vector for each one, applies the force, and draws a debug line that visualizes the direction.
+
+1. acquires input focus in `init()`
+2. listens to `hash("touch")` in `on_input()`
+3. iterates over all block ids
+4. computes `force = (touch - center) * force_factor`
+5. posts `apply_force` to each block
+6. posts `@render: draw_line` for debug visualization

+ 312 - 0
physics/apply_force/example/apply_force.collection

@@ -0,0 +1,312 @@
+name: "default"
+scale_along_z: 0
+embedded_instances {
+  id: "walls"
+  data: "embedded_components {\n"
+  "  id: \"collisionobject\"\n"
+  "  type: \"collisionobject\"\n"
+  "  data: \"type: COLLISION_OBJECT_TYPE_STATIC\\n"
+  "mass: 0.0\\n"
+  "friction: 0.9\\n"
+  "restitution: 0.1\\n"
+  "group: \\\"default\\\"\\n"
+  "mask: \\\"default\\\"\\n"
+  "embedded_collision_shape {\\n"
+  "  shapes {\\n"
+  "    shape_type: TYPE_BOX\\n"
+  "    position {\\n"
+  "      x: -50.0\\n"
+  "      y: 360.0\\n"
+  "    }\\n"
+  "    rotation {\\n"
+  "    }\\n"
+  "    index: 0\\n"
+  "    count: 3\\n"
+  "  }\\n"
+  "  shapes {\\n"
+  "    shape_type: TYPE_BOX\\n"
+  "    position {\\n"
+  "      x: 770.0\\n"
+  "      y: 360.0\\n"
+  "    }\\n"
+  "    rotation {\\n"
+  "    }\\n"
+  "    index: 3\\n"
+  "    count: 3\\n"
+  "  }\\n"
+  "  shapes {\\n"
+  "    shape_type: TYPE_BOX\\n"
+  "    position {\\n"
+  "      x: 360.0\\n"
+  "      y: 770.0\\n"
+  "    }\\n"
+  "    rotation {\\n"
+  "    }\\n"
+  "    index: 6\\n"
+  "    count: 3\\n"
+  "  }\\n"
+  "  shapes {\\n"
+  "    shape_type: TYPE_BOX\\n"
+  "    position {\\n"
+  "      x: 360.0\\n"
+  "      y: -50.0\\n"
+  "    }\\n"
+  "    rotation {\\n"
+  "    }\\n"
+  "    index: 9\\n"
+  "    count: 3\\n"
+  "  }\\n"
+  "  data: 50.0\\n"
+  "  data: 360.0\\n"
+  "  data: 10.0\\n"
+  "  data: 50.0\\n"
+  "  data: 360.0\\n"
+  "  data: 10.0\\n"
+  "  data: 460.0\\n"
+  "  data: 50.0\\n"
+  "  data: 10.0\\n"
+  "  data: 460.0\\n"
+  "  data: 50.0\\n"
+  "  data: 10.0\\n"
+  "}\\n"
+  "\"\n"
+  "}\n"
+  ""
+}
+embedded_instances {
+  id: "block1"
+  data: "embedded_components {\n"
+  "  id: \"collisionobject\"\n"
+  "  type: \"collisionobject\"\n"
+  "  data: \"type: COLLISION_OBJECT_TYPE_DYNAMIC\\n"
+  "mass: 2.0\\n"
+  "friction: 0.5\\n"
+  "restitution: 0.2\\n"
+  "group: \\\"default\\\"\\n"
+  "mask: \\\"default\\\"\\n"
+  "embedded_collision_shape {\\n"
+  "  shapes {\\n"
+  "    shape_type: TYPE_BOX\\n"
+  "    position {\\n"
+  "    }\\n"
+  "    rotation {\\n"
+  "    }\\n"
+  "    index: 0\\n"
+  "    count: 3\\n"
+  "  }\\n"
+  "  data: 109.4645\\n"
+  "  data: 68.3975\\n"
+  "  data: 10.0\\n"
+  "}\\n"
+  "linear_damping: 0.8\\n"
+  "angular_damping: 0.8\\n"
+  "event_collision: false\\n"
+  "event_contact: false\\n"
+  "event_trigger: false\\n"
+  "\"\n"
+  "}\n"
+  "embedded_components {\n"
+  "  id: \"sprite\"\n"
+  "  type: \"sprite\"\n"
+  "  data: \"default_animation: \\\"elementStone019\\\"\\n"
+  "material: \\\"/builtins/materials/sprite.material\\\"\\n"
+  "textures {\\n"
+  "  sampler: \\\"texture_sampler\\\"\\n"
+  "  texture: \\\"/assets/sprites.atlas\\\"\\n"
+  "}\\n"
+  "\"\n"
+  "}\n"
+  ""
+  position {
+    x: 220.14
+    y: 559.457
+  }
+}
+embedded_instances {
+  id: "block2"
+  data: "embedded_components {\n"
+  "  id: \"sprite\"\n"
+  "  type: \"sprite\"\n"
+  "  data: \"default_animation: \\\"elementStone023\\\"\\n"
+  "material: \\\"/builtins/materials/sprite.material\\\"\\n"
+  "textures {\\n"
+  "  sampler: \\\"texture_sampler\\\"\\n"
+  "  texture: \\\"/assets/sprites.atlas\\\"\\n"
+  "}\\n"
+  "\"\n"
+  "}\n"
+  "embedded_components {\n"
+  "  id: \"collisionobject\"\n"
+  "  type: \"collisionobject\"\n"
+  "  data: \"type: COLLISION_OBJECT_TYPE_DYNAMIC\\n"
+  "mass: 1.0\\n"
+  "friction: 0.5\\n"
+  "restitution: 0.2\\n"
+  "group: \\\"default\\\"\\n"
+  "mask: \\\"default\\\"\\n"
+  "embedded_collision_shape {\\n"
+  "  shapes {\\n"
+  "    shape_type: TYPE_BOX\\n"
+  "    position {\\n"
+  "    }\\n"
+  "    rotation {\\n"
+  "    }\\n"
+  "    index: 0\\n"
+  "    count: 3\\n"
+  "  }\\n"
+  "  data: 68.0\\n"
+  "  data: 68.3975\\n"
+  "  data: 10.0\\n"
+  "}\\n"
+  "linear_damping: 0.8\\n"
+  "angular_damping: 0.8\\n"
+  "event_collision: false\\n"
+  "event_contact: false\\n"
+  "event_trigger: false\\n"
+  "\"\n"
+  "}\n"
+  ""
+  position {
+    x: 385.289
+    y: 385.021
+  }
+}
+embedded_instances {
+  id: "block3"
+  data: "embedded_components {\n"
+  "  id: \"sprite\"\n"
+  "  type: \"sprite\"\n"
+  "  data: \"default_animation: \\\"elementStone023\\\"\\n"
+  "material: \\\"/builtins/materials/sprite.material\\\"\\n"
+  "size {\\n"
+  "  x: 140.0\\n"
+  "  y: 140.0\\n"
+  "}\\n"
+  "textures {\\n"
+  "  sampler: \\\"texture_sampler\\\"\\n"
+  "  texture: \\\"/assets/sprites.atlas\\\"\\n"
+  "}\\n"
+  "\"\n"
+  "}\n"
+  "embedded_components {\n"
+  "  id: \"collisionobject\"\n"
+  "  type: \"collisionobject\"\n"
+  "  data: \"type: COLLISION_OBJECT_TYPE_DYNAMIC\\n"
+  "mass: 1.0\\n"
+  "friction: 0.5\\n"
+  "restitution: 0.2\\n"
+  "group: \\\"default\\\"\\n"
+  "mask: \\\"default\\\"\\n"
+  "embedded_collision_shape {\\n"
+  "  shapes {\\n"
+  "    shape_type: TYPE_BOX\\n"
+  "    position {\\n"
+  "    }\\n"
+  "    rotation {\\n"
+  "    }\\n"
+  "    index: 0\\n"
+  "    count: 3\\n"
+  "  }\\n"
+  "  data: 68.0\\n"
+  "  data: 68.3975\\n"
+  "  data: 10.0\\n"
+  "}\\n"
+  "linear_damping: 0.8\\n"
+  "angular_damping: 0.8\\n"
+  "event_collision: false\\n"
+  "event_contact: false\\n"
+  "event_trigger: false\\n"
+  "\"\n"
+  "}\n"
+  ""
+  position {
+    x: 508.289
+    y: 547.021
+  }
+}
+embedded_instances {
+  id: "block4"
+  data: "embedded_components {\n"
+  "  id: \"sprite\"\n"
+  "  type: \"sprite\"\n"
+  "  data: \"default_animation: \\\"elementStone023\\\"\\n"
+  "material: \\\"/builtins/materials/sprite.material\\\"\\n"
+  "size {\\n"
+  "  x: 140.0\\n"
+  "  y: 140.0\\n"
+  "}\\n"
+  "textures {\\n"
+  "  sampler: \\\"texture_sampler\\\"\\n"
+  "  texture: \\\"/assets/sprites.atlas\\\"\\n"
+  "}\\n"
+  "\"\n"
+  "}\n"
+  "embedded_components {\n"
+  "  id: \"collisionobject\"\n"
+  "  type: \"collisionobject\"\n"
+  "  data: \"type: COLLISION_OBJECT_TYPE_DYNAMIC\\n"
+  "mass: 1.0\\n"
+  "friction: 0.5\\n"
+  "restitution: 0.2\\n"
+  "group: \\\"default\\\"\\n"
+  "mask: \\\"default\\\"\\n"
+  "embedded_collision_shape {\\n"
+  "  shapes {\\n"
+  "    shape_type: TYPE_BOX\\n"
+  "    position {\\n"
+  "    }\\n"
+  "    rotation {\\n"
+  "    }\\n"
+  "    index: 0\\n"
+  "    count: 3\\n"
+  "  }\\n"
+  "  data: 68.0\\n"
+  "  data: 68.3975\\n"
+  "  data: 10.0\\n"
+  "}\\n"
+  "linear_damping: 0.8\\n"
+  "angular_damping: 0.8\\n"
+  "event_collision: false\\n"
+  "event_contact: false\\n"
+  "event_trigger: false\\n"
+  "\"\n"
+  "}\n"
+  ""
+  position {
+    x: 608.289
+    y: 395.021
+  }
+}
+embedded_instances {
+  id: "controller"
+  data: "components {\n"
+  "  id: \"apply_force\"\n"
+  "  component: \"/example/apply_force.script\"\n"
+  "}\n"
+  "embedded_components {\n"
+  "  id: \"label\"\n"
+  "  type: \"label\"\n"
+  "  data: \"size {\\n"
+  "  x: 700.0\\n"
+  "  y: 50.0\\n"
+  "}\\n"
+  "color {\\n"
+  "  x: 0.101960786\\n"
+  "  y: 0.2\\n"
+  "  z: 0.6\\n"
+  "}\\n"
+  "text: \\\"Click to apply force to all blocks\\\"\\n"
+  "font: \\\"/assets/text48.font\\\"\\n"
+  "material: \\\"/builtins/fonts/label.material\\\"\\n"
+  "\"\n"
+  "  position {\n"
+  "    x: 360.0\n"
+  "    y: 680.0\n"
+  "  }\n"
+  "  scale {\n"
+  "    x: 0.75\n"
+  "    y: 0.75\n"
+  "  }\n"
+  "}\n"
+  ""
+}

+ 53 - 0
physics/apply_force/example/apply_force.script

@@ -0,0 +1,53 @@
+-- Multiplier applied to the touch->block direction vector.
+local force_factor = 30
+
+-- Debug line color used by @render:draw_line.
+local debug_line_color = vmath.vector4(0, 0.5, 1, 1)
+
+-- Game object ids of dynamic blocks controlled by this script.
+-- The script is attached to the "controller" object in the collection.
+local blocks = {
+	[1] = "block1",
+	[2] = "block2",
+	[3] = "block3",
+	[4] = "block4",
+}
+
+function init(self)
+	-- <1> Receive touch/mouse input in on_input().
+	msg.post(".", "acquire_input_focus")
+end
+
+-- Pre-hashed action id for the touch action.
+local touch_action_id = hash("touch")
+
+function on_input(self, action_id, action)
+	-- <2> Built-in "touch" also maps mouse clicks on desktop.
+	if action_id == touch_action_id then
+		-- <3> Iterate over all blocks and apply force to each.
+		for _, block in pairs(blocks) do
+			-- <4> Compute the force vector by subtracting the block center from the touch position.
+			local center = go.get_world_position(block)
+			local touch = vmath.vector3(action.x, action.y, center.z)
+			local force = (touch - center) * force_factor
+
+			-- <5> Send force to the block's dynamic collision object.
+			msg.post(block, "apply_force", {
+				force = force,
+				position = center
+			})
+
+			-- <6> Visualize direction from touch point to block center.
+			msg.post("@render:", "draw_line", {
+				start_point = touch,
+				end_point = center,
+				color = debug_line_color
+			})
+		end
+	end
+end
+
+function final(self)
+	-- Stop receiving input when the controller is destroyed.
+	msg.post(".", "release_input_focus")
+end

+ 67 - 0
physics/apply_force/game.project

@@ -0,0 +1,67 @@
+[project]
+title = Apply Force example
+version = 0.1
+
+[bootstrap]
+main_collection = /example/apply_force.collectionc
+
+[input]
+game_binding = /builtins/input/all.input_bindingc
+repeat_interval = 0.05
+
+[display]
+width = 720
+height = 720
+high_dpi = 1
+
+[physics]
+scale = 0.02
+gravity_y = -900.0
+use_fixed_timestep = 1
+max_fixed_timesteps = 20
+
+[script]
+shared_state = 1
+
+[collection_proxy]
+max_count = 256
+
+[label]
+subpixels = 1
+
+[sprite]
+subpixels = 1
+max_count = 32765
+
+[windows]
+iap_provider = 
+
+[android]
+package = com.defold.examples
+
+[ios]
+bundle_identifier = com.defold.examples
+
+[osx]
+bundle_identifier = com.defold.examples
+
+[html5]
+show_fullscreen_button = 0
+show_made_with_defold = 0
+scale_mode = no_scale
+heap_size = 64
+
+[graphics]
+texture_profiles = /all.texture_profiles
+
+[collection]
+max_instances = 32765
+
+[particle_fx]
+max_emitter_count = 1024
+
+[render]
+clear_color_blue = 1.0
+clear_color_green = 1.0
+clear_color_red = 1.0
+

BIN
physics/apply_force/gameproject.png


BIN
physics/apply_force/setup.png


BIN
physics/apply_force/thumbnail.png