Browse Source

Added basic 3D FPP example of movement and look around. (#131)

* Added basic 3D FPP example of movement and look around.

* Fixed name of the character controller script.
Pawel 1 month ago
parent
commit
59f523597c
40 changed files with 1918 additions and 0 deletions
  1. 18 0
      movement/3d_fps/all.texture_profiles
  2. BIN
      movement/3d_fps/collection.png
  3. 53 0
      movement/3d_fps/example.md
  4. 600 0
      movement/3d_fps/example/3d_fps.collection
  5. 5 0
      movement/3d_fps/example/assets/default.font
  6. BIN
      movement/3d_fps/example/assets/models/Tree_1_B_Color1.bin
  7. 136 0
      movement/3d_fps/example/assets/models/Tree_1_B_Color1.gltf
  8. BIN
      movement/3d_fps/example/assets/models/Tree_2_D_Color1.bin
  9. 136 0
      movement/3d_fps/example/assets/models/Tree_2_D_Color1.gltf
  10. BIN
      movement/3d_fps/example/assets/models/Tree_2_E_Color1.bin
  11. 136 0
      movement/3d_fps/example/assets/models/Tree_2_E_Color1.gltf
  12. BIN
      movement/3d_fps/example/assets/models/Tree_3_A_Color1.bin
  13. 136 0
      movement/3d_fps/example/assets/models/Tree_3_A_Color1.gltf
  14. 10 0
      movement/3d_fps/example/assets/models/cube_blue.model
  15. 10 0
      movement/3d_fps/example/assets/models/cube_orange.model
  16. 10 0
      movement/3d_fps/example/assets/models/cube_white.model
  17. 10 0
      movement/3d_fps/example/assets/models/plane.model
  18. 10 0
      movement/3d_fps/example/assets/models/tree_1_B.model
  19. 10 0
      movement/3d_fps/example/assets/models/tree_2_D.model
  20. 10 0
      movement/3d_fps/example/assets/models/tree_2_E.model
  21. 10 0
      movement/3d_fps/example/assets/models/tree_3_A.model
  22. BIN
      movement/3d_fps/example/assets/textures/1024x1024_texture_blue.png
  23. BIN
      movement/3d_fps/example/assets/textures/1024x1024_texture_orange.png
  24. BIN
      movement/3d_fps/example/assets/textures/1024x1024_texture_white.png
  25. 4 0
      movement/3d_fps/example/assets/textures/example.atlas
  26. BIN
      movement/3d_fps/example/assets/textures/forest_texture.png
  27. BIN
      movement/3d_fps/example/assets/textures/larger.png
  28. 129 0
      movement/3d_fps/example/character_controller.script
  29. 71 0
      movement/3d_fps/example/example.gui
  30. 31 0
      movement/3d_fps/example/materials/model_instanced_cube.fp
  31. 47 0
      movement/3d_fps/example/materials/model_instanced_cube.material
  32. 40 0
      movement/3d_fps/example/materials/model_instanced_cube.vp
  33. 31 0
      movement/3d_fps/example/materials/model_instanced_floor.fp
  34. 47 0
      movement/3d_fps/example/materials/model_instanced_floor.material
  35. 40 0
      movement/3d_fps/example/materials/model_instanced_floor.vp
  36. 31 0
      movement/3d_fps/example/materials/model_lit.fp
  37. 40 0
      movement/3d_fps/example/materials/model_lit.material
  38. 39 0
      movement/3d_fps/example/materials/model_lit.vp
  39. 68 0
      movement/3d_fps/game.project
  40. BIN
      movement/3d_fps/thumbnail.png

+ 18 - 0
movement/3d_fps/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
movement/3d_fps/collection.png


+ 53 - 0
movement/3d_fps/example.md

@@ -0,0 +1,53 @@
+---
+tags: movement
+title: First-person 3D camera and movement
+brief: Control a first-person camera using WASD and mouse to look with cursor lock.
+author: Defold Foundation
+scripts: character_controller.script
+thumbnail: collection.png
+---
+
+This example shows how to build a simple first-person controller for a 3D scene. You can look around with the mouse and move on the XZ plane using the keyboard (WSAD).
+
+## What you'll learn?
+- How to implement a FPP camera with mouse to look around.
+- How to lock/unlock the mouse cursor for immersive camera control.
+- How to move on a simple XZ plane logic with keyboard input.
+
+## Controls
+| Input                  | Action                                               |
+|------------------------|------------------------------------------------------|
+| Left mouse click       | Lock the cursor and enable mouse look                |
+| Mouse movement         | Rotate camera                                        |
+| `Esc`                  | Unlock the cursor                                    |
+| `W`/`S`/`A`/`D`        | Move forward/backward/left/right on the ground plane |
+
+## How it works?
+When the cursor is locked, the script reads mouse movement deltas and rotates the camera accordingly. Movement is normalized to keep a consistent speed in all directions and is clamped within a square area so you cannot wander off the demo scene.
+
+Example collection consists of 3 main parts:
+
+- `character` - The player character game object includes:
+  - A *script* `character_controller.script` component that implements mouse look, cursor lock/unlock, and WASD movement.
+  - A *camera* component configured with perspective projection.
+
+- `scene` - the models used to create a basic 3D environment:
+  - Ground plane scaled to form a walkable area with prototype texture
+  - Walls built from simple cube models with prototype textures
+  - Some decorative trees
+
+- `gui` - An on-screen GUI with short instructions.
+
+![](collection.png)
+
+### Assets
+Tree models with textures by Kay Louseberg: https://kaylousberg.itch.io/kaykit-forest
+Prototype textures for Defold by Visionaire: https://github.com/Thevisionaire1/3Deforms
+
+## Script
+
+Tuning parameters are defined at the top of `character_controller.script`:
+- `look_sensitivity` (degrees per pixel) controls how fast the camera rotates
+- `move_speed` (world units per second) controls walking speed
+- `move_limit` (half-size in world units) clamps movement within bounds
+

+ 600 - 0
movement/3d_fps/example/3d_fps.collection

@@ -0,0 +1,600 @@
+name: "3d_fps"
+scale_along_z: 0
+embedded_instances {
+  id: "character"
+  data: "components {\n"
+  "  id: \"character_controller\"\n"
+  "  component: \"/example/character_controller.script\"\n"
+  "}\n"
+  "embedded_components {\n"
+  "  id: \"camera\"\n"
+  "  type: \"camera\"\n"
+  "  data: \"aspect_ratio: 1.0\\n"
+  "fov: 0.7854\\n"
+  "near_z: 0.1\\n"
+  "far_z: 10.0\\n"
+  "auto_aspect_ratio: 1\\n"
+  "\"\n"
+  "}\n"
+  ""
+  position {
+    y: 0.3
+  }
+}
+embedded_instances {
+  id: "scene"
+  children: "ground"
+  children: "trees"
+  children: "wall"
+  children: "wall1"
+  children: "wall2"
+  children: "wall3"
+  data: ""
+}
+embedded_instances {
+  id: "ground"
+  data: "components {\n"
+  "  id: \"plane\"\n"
+  "  component: \"/example/assets/models/plane.model\"\n"
+  "  rotation {\n"
+  "    x: -0.70710677\n"
+  "    w: 0.70710677\n"
+  "  }\n"
+  "}\n"
+  ""
+  scale3 {
+    x: 5.0
+    z: 5.0
+  }
+}
+embedded_instances {
+  id: "trees"
+  data: "components {\n"
+  "  id: \"tree\"\n"
+  "  component: \"/example/assets/models/tree_1_B.model\"\n"
+  "  position {\n"
+  "    x: 43.333332\n"
+  "    z: -26.666668\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"tree1\"\n"
+  "  component: \"/example/assets/models/tree_2_E.model\"\n"
+  "  position {\n"
+  "    x: 6.666666\n"
+  "    z: 10.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"tree2\"\n"
+  "  component: \"/example/assets/models/tree_3_A.model\"\n"
+  "  position {\n"
+  "    x: 54.0\n"
+  "    z: 10.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"tree3\"\n"
+  "  component: \"/example/assets/models/tree_1_B.model\"\n"
+  "  position {\n"
+  "    x: 13.333333\n"
+  "    z: 19.999998\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"tree4\"\n"
+  "  component: \"/example/assets/models/tree_2_D.model\"\n"
+  "  position {\n"
+  "    x: 40.0\n"
+  "    z: 22.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"tree6\"\n"
+  "  component: \"/example/assets/models/tree_2_E.model\"\n"
+  "  position {\n"
+  "    x: 35.0\n"
+  "    z: -25.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"tree7\"\n"
+  "  component: \"/example/assets/models/tree_2_D.model\"\n"
+  "  position {\n"
+  "    x: 60.0\n"
+  "    z: -18.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"tree8\"\n"
+  "  component: \"/example/assets/models/tree_2_E.model\"\n"
+  "  position {\n"
+  "    x: 60.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"tree9\"\n"
+  "  component: \"/example/assets/models/tree_2_D.model\"\n"
+  "  position {\n"
+  "    x: 14.0\n"
+  "    z: -12.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"tree10\"\n"
+  "  component: \"/example/assets/models/tree_2_E.model\"\n"
+  "  position {\n"
+  "    x: 23.333332\n"
+  "    z: 25.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"tree11\"\n"
+  "  component: \"/example/assets/models/tree_1_B.model\"\n"
+  "  position {\n"
+  "    x: 5.0\n"
+  "    z: -10.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"tree12\"\n"
+  "  component: \"/example/assets/models/tree_2_D.model\"\n"
+  "  position {\n"
+  "    x: 5.0\n"
+  "    z: 26.0\n"
+  "  }\n"
+  "  rotation {\n"
+  "    y: 0.70710677\n"
+  "    w: 0.70710677\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"tree13\"\n"
+  "  component: \"/example/assets/models/tree_2_D.model\"\n"
+  "  position {\n"
+  "    x: 26.0\n"
+  "    z: -20.0\n"
+  "  }\n"
+  "  rotation {\n"
+  "    y: 0.38268343\n"
+  "    w: 0.9238795\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"tree14\"\n"
+  "  component: \"/example/assets/models/tree_2_E.model\"\n"
+  "  position {\n"
+  "    x: 15.000002\n"
+  "    z: 3.333333\n"
+  "  }\n"
+  "  rotation {\n"
+  "    y: 0.70710677\n"
+  "    w: 0.70710677\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"tree15\"\n"
+  "  component: \"/example/assets/models/tree_3_A.model\"\n"
+  "  position {\n"
+  "    x: 45.0\n"
+  "    z: -24.0\n"
+  "  }\n"
+  "  rotation {\n"
+  "    y: 0.38268343\n"
+  "    w: 0.9238795\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"tree5\"\n"
+  "  component: \"/example/assets/models/tree_2_D.model\"\n"
+  "  position {\n"
+  "    x: 54.0\n"
+  "    z: 20.0\n"
+  "  }\n"
+  "  rotation {\n"
+  "    y: 0.8660254\n"
+  "    w: 0.5\n"
+  "  }\n"
+  "}\n"
+  ""
+  position {
+    x: -5.0
+  }
+  scale3 {
+    x: 0.15
+    y: 0.15
+    z: 0.15
+  }
+}
+embedded_instances {
+  id: "wall2"
+  data: "components {\n"
+  "  id: \"cube\"\n"
+  "  component: \"/example/assets/models/cube_blue.model\"\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube1\"\n"
+  "  component: \"/example/assets/models/cube_white.model\"\n"
+  "  position {\n"
+  "    x: 1.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube2\"\n"
+  "  component: \"/example/assets/models/cube_white.model\"\n"
+  "  position {\n"
+  "    x: -1.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube3\"\n"
+  "  component: \"/example/assets/models/cube_orange.model\"\n"
+  "  position {\n"
+  "    x: 2.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube4\"\n"
+  "  component: \"/example/assets/models/cube_orange.model\"\n"
+  "  position {\n"
+  "    x: -2.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube5\"\n"
+  "  component: \"/example/assets/models/cube_white.model\"\n"
+  "  position {\n"
+  "    x: 3.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube6\"\n"
+  "  component: \"/example/assets/models/cube_white.model\"\n"
+  "  position {\n"
+  "    x: -3.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube7\"\n"
+  "  component: \"/example/assets/models/cube_blue.model\"\n"
+  "  position {\n"
+  "    x: 4.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube8\"\n"
+  "  component: \"/example/assets/models/cube_blue.model\"\n"
+  "  position {\n"
+  "    x: -4.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube9\"\n"
+  "  component: \"/example/assets/models/cube_white.model\"\n"
+  "  position {\n"
+  "    x: 5.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube10\"\n"
+  "  component: \"/example/assets/models/cube_white.model\"\n"
+  "  position {\n"
+  "    x: -5.0\n"
+  "  }\n"
+  "}\n"
+  ""
+  position {
+    y: 0.125
+    z: 1.5
+  }
+  scale3 {
+    x: 0.25
+    y: 0.25
+    z: 0.25
+  }
+}
+embedded_instances {
+  id: "gui"
+  data: "components {\n"
+  "  id: \"example\"\n"
+  "  component: \"/example/example.gui\"\n"
+  "}\n"
+  ""
+}
+embedded_instances {
+  id: "wall"
+  data: "components {\n"
+  "  id: \"cube\"\n"
+  "  component: \"/example/assets/models/cube_blue.model\"\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube1\"\n"
+  "  component: \"/example/assets/models/cube_white.model\"\n"
+  "  position {\n"
+  "    x: 1.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube2\"\n"
+  "  component: \"/example/assets/models/cube_white.model\"\n"
+  "  position {\n"
+  "    x: -1.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube3\"\n"
+  "  component: \"/example/assets/models/cube_orange.model\"\n"
+  "  position {\n"
+  "    x: 2.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube4\"\n"
+  "  component: \"/example/assets/models/cube_orange.model\"\n"
+  "  position {\n"
+  "    x: -2.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube5\"\n"
+  "  component: \"/example/assets/models/cube_white.model\"\n"
+  "  position {\n"
+  "    x: 3.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube6\"\n"
+  "  component: \"/example/assets/models/cube_white.model\"\n"
+  "  position {\n"
+  "    x: -3.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube7\"\n"
+  "  component: \"/example/assets/models/cube_blue.model\"\n"
+  "  position {\n"
+  "    x: 4.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube8\"\n"
+  "  component: \"/example/assets/models/cube_blue.model\"\n"
+  "  position {\n"
+  "    x: -4.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube9\"\n"
+  "  component: \"/example/assets/models/cube_white.model\"\n"
+  "  position {\n"
+  "    x: 5.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube10\"\n"
+  "  component: \"/example/assets/models/cube_white.model\"\n"
+  "  position {\n"
+  "    x: -5.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube11\"\n"
+  "  component: \"/example/assets/models/cube_white.model\"\n"
+  "  position {\n"
+  "    x: 6.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube12\"\n"
+  "  component: \"/example/assets/models/cube_white.model\"\n"
+  "  position {\n"
+  "    x: -6.0\n"
+  "  }\n"
+  "}\n"
+  ""
+  position {
+    x: -1.5
+    y: 0.125
+  }
+  rotation {
+    y: 0.70710677
+    w: 0.70710677
+  }
+  scale3 {
+    x: 0.25
+    y: 0.25
+    z: 0.25
+  }
+}
+embedded_instances {
+  id: "wall1"
+  data: "components {\n"
+  "  id: \"cube\"\n"
+  "  component: \"/example/assets/models/cube_blue.model\"\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube1\"\n"
+  "  component: \"/example/assets/models/cube_white.model\"\n"
+  "  position {\n"
+  "    x: 1.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube2\"\n"
+  "  component: \"/example/assets/models/cube_white.model\"\n"
+  "  position {\n"
+  "    x: -1.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube3\"\n"
+  "  component: \"/example/assets/models/cube_orange.model\"\n"
+  "  position {\n"
+  "    x: 2.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube4\"\n"
+  "  component: \"/example/assets/models/cube_orange.model\"\n"
+  "  position {\n"
+  "    x: -2.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube5\"\n"
+  "  component: \"/example/assets/models/cube_white.model\"\n"
+  "  position {\n"
+  "    x: 3.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube6\"\n"
+  "  component: \"/example/assets/models/cube_white.model\"\n"
+  "  position {\n"
+  "    x: -3.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube7\"\n"
+  "  component: \"/example/assets/models/cube_blue.model\"\n"
+  "  position {\n"
+  "    x: 4.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube8\"\n"
+  "  component: \"/example/assets/models/cube_blue.model\"\n"
+  "  position {\n"
+  "    x: -4.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube9\"\n"
+  "  component: \"/example/assets/models/cube_white.model\"\n"
+  "  position {\n"
+  "    x: 5.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube10\"\n"
+  "  component: \"/example/assets/models/cube_white.model\"\n"
+  "  position {\n"
+  "    x: -5.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube11\"\n"
+  "  component: \"/example/assets/models/cube_white.model\"\n"
+  "  position {\n"
+  "    x: 6.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube12\"\n"
+  "  component: \"/example/assets/models/cube_white.model\"\n"
+  "  position {\n"
+  "    x: -6.0\n"
+  "  }\n"
+  "}\n"
+  ""
+  position {
+    x: 1.5
+    y: 0.125
+  }
+  rotation {
+    y: 0.70710677
+    w: 0.70710677
+  }
+  scale3 {
+    x: 0.25
+    y: 0.25
+    z: 0.25
+  }
+}
+embedded_instances {
+  id: "wall3"
+  data: "components {\n"
+  "  id: \"cube\"\n"
+  "  component: \"/example/assets/models/cube_blue.model\"\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube1\"\n"
+  "  component: \"/example/assets/models/cube_white.model\"\n"
+  "  position {\n"
+  "    x: 1.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube2\"\n"
+  "  component: \"/example/assets/models/cube_white.model\"\n"
+  "  position {\n"
+  "    x: -1.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube3\"\n"
+  "  component: \"/example/assets/models/cube_orange.model\"\n"
+  "  position {\n"
+  "    x: 2.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube4\"\n"
+  "  component: \"/example/assets/models/cube_orange.model\"\n"
+  "  position {\n"
+  "    x: -2.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube5\"\n"
+  "  component: \"/example/assets/models/cube_white.model\"\n"
+  "  position {\n"
+  "    x: 3.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube6\"\n"
+  "  component: \"/example/assets/models/cube_white.model\"\n"
+  "  position {\n"
+  "    x: -3.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube7\"\n"
+  "  component: \"/example/assets/models/cube_blue.model\"\n"
+  "  position {\n"
+  "    x: 4.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube8\"\n"
+  "  component: \"/example/assets/models/cube_blue.model\"\n"
+  "  position {\n"
+  "    x: -4.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube9\"\n"
+  "  component: \"/example/assets/models/cube_white.model\"\n"
+  "  position {\n"
+  "    x: 5.0\n"
+  "  }\n"
+  "}\n"
+  "components {\n"
+  "  id: \"cube10\"\n"
+  "  component: \"/example/assets/models/cube_white.model\"\n"
+  "  position {\n"
+  "    x: -5.0\n"
+  "  }\n"
+  "}\n"
+  ""
+  position {
+    y: 0.125
+    z: -1.5
+  }
+  scale3 {
+    x: 0.25
+    y: 0.25
+    z: 0.25
+  }
+}

+ 5 - 0
movement/3d_fps/example/assets/default.font

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

BIN
movement/3d_fps/example/assets/models/Tree_1_B_Color1.bin


+ 136 - 0
movement/3d_fps/example/assets/models/Tree_1_B_Color1.gltf

@@ -0,0 +1,136 @@
+{
+    "asset" : {
+        "generator" : "Khronos glTF Blender I/O v3.4.50",
+        "version" : "2.0"
+    },
+    "scene" : 0,
+    "scenes" : [
+        {
+            "name" : "Scene",
+            "nodes" : [
+                0
+            ]
+        }
+    ],
+    "nodes" : [
+        {
+            "mesh" : 0,
+            "name" : "Tree_1_B_Color1"
+        }
+    ],
+    "materials" : [
+        {
+            "name" : "forest",
+            "pbrMetallicRoughness" : {
+                "baseColorTexture" : {
+                    "index" : 0
+                },
+                "metallicFactor" : 0,
+                "roughnessFactor" : 0.6000000238418579
+            }
+        }
+    ],
+    "meshes" : [
+        {
+            "name" : "Tree_1_B_Color1",
+            "primitives" : [
+                {
+                    "attributes" : {
+                        "POSITION" : 0,
+                        "TEXCOORD_0" : 1,
+                        "NORMAL" : 2
+                    },
+                    "indices" : 3,
+                    "material" : 0
+                }
+            ]
+        }
+    ],
+    "textures" : [
+        {
+            "sampler" : 0,
+            "source" : 0
+        }
+    ],
+    "images" : [
+        {
+            "mimeType" : "image/png",
+            "name" : "forest_texture",
+            "uri" : "forest_texture.png"
+        }
+    ],
+    "accessors" : [
+        {
+            "bufferView" : 0,
+            "componentType" : 5126,
+            "count" : 475,
+            "max" : [
+                2.217304229736328,
+                4.588586807250977,
+                1.722744107246399
+            ],
+            "min" : [
+                -1.7895218133926392,
+                -0.3410367965698242,
+                -1.7227431535720825
+            ],
+            "type" : "VEC3"
+        },
+        {
+            "bufferView" : 1,
+            "componentType" : 5126,
+            "count" : 475,
+            "type" : "VEC2"
+        },
+        {
+            "bufferView" : 2,
+            "componentType" : 5126,
+            "count" : 475,
+            "type" : "VEC3"
+        },
+        {
+            "bufferView" : 3,
+            "componentType" : 5123,
+            "count" : 1908,
+            "type" : "SCALAR"
+        }
+    ],
+    "bufferViews" : [
+        {
+            "buffer" : 0,
+            "byteLength" : 5700,
+            "byteOffset" : 0,
+            "target" : 34962
+        },
+        {
+            "buffer" : 0,
+            "byteLength" : 3800,
+            "byteOffset" : 5700,
+            "target" : 34962
+        },
+        {
+            "buffer" : 0,
+            "byteLength" : 5700,
+            "byteOffset" : 9500,
+            "target" : 34962
+        },
+        {
+            "buffer" : 0,
+            "byteLength" : 3816,
+            "byteOffset" : 15200,
+            "target" : 34963
+        }
+    ],
+    "samplers" : [
+        {
+            "magFilter" : 9729,
+            "minFilter" : 9987
+        }
+    ],
+    "buffers" : [
+        {
+            "byteLength" : 19016,
+            "uri" : "Tree_1_B_Color1.bin"
+        }
+    ]
+}

BIN
movement/3d_fps/example/assets/models/Tree_2_D_Color1.bin


+ 136 - 0
movement/3d_fps/example/assets/models/Tree_2_D_Color1.gltf

@@ -0,0 +1,136 @@
+{
+    "asset" : {
+        "generator" : "Khronos glTF Blender I/O v3.4.50",
+        "version" : "2.0"
+    },
+    "scene" : 0,
+    "scenes" : [
+        {
+            "name" : "Scene",
+            "nodes" : [
+                0
+            ]
+        }
+    ],
+    "nodes" : [
+        {
+            "mesh" : 0,
+            "name" : "Tree_2_D_Color1"
+        }
+    ],
+    "materials" : [
+        {
+            "name" : "forest",
+            "pbrMetallicRoughness" : {
+                "baseColorTexture" : {
+                    "index" : 0
+                },
+                "metallicFactor" : 0,
+                "roughnessFactor" : 0.6000000238418579
+            }
+        }
+    ],
+    "meshes" : [
+        {
+            "name" : "Tree_2_D_Color1",
+            "primitives" : [
+                {
+                    "attributes" : {
+                        "POSITION" : 0,
+                        "TEXCOORD_0" : 1,
+                        "NORMAL" : 2
+                    },
+                    "indices" : 3,
+                    "material" : 0
+                }
+            ]
+        }
+    ],
+    "textures" : [
+        {
+            "sampler" : 0,
+            "source" : 0
+        }
+    ],
+    "images" : [
+        {
+            "mimeType" : "image/png",
+            "name" : "forest_texture",
+            "uri" : "forest_texture.png"
+        }
+    ],
+    "accessors" : [
+        {
+            "bufferView" : 0,
+            "componentType" : 5126,
+            "count" : 761,
+            "max" : [
+                2.2887001037597656,
+                7.694401264190674,
+                1.6433088779449463
+            ],
+            "min" : [
+                -2.450906991958618,
+                -0.39324718713760376,
+                -2.391054391860962
+            ],
+            "type" : "VEC3"
+        },
+        {
+            "bufferView" : 1,
+            "componentType" : 5126,
+            "count" : 761,
+            "type" : "VEC2"
+        },
+        {
+            "bufferView" : 2,
+            "componentType" : 5126,
+            "count" : 761,
+            "type" : "VEC3"
+        },
+        {
+            "bufferView" : 3,
+            "componentType" : 5123,
+            "count" : 4284,
+            "type" : "SCALAR"
+        }
+    ],
+    "bufferViews" : [
+        {
+            "buffer" : 0,
+            "byteLength" : 9132,
+            "byteOffset" : 0,
+            "target" : 34962
+        },
+        {
+            "buffer" : 0,
+            "byteLength" : 6088,
+            "byteOffset" : 9132,
+            "target" : 34962
+        },
+        {
+            "buffer" : 0,
+            "byteLength" : 9132,
+            "byteOffset" : 15220,
+            "target" : 34962
+        },
+        {
+            "buffer" : 0,
+            "byteLength" : 8568,
+            "byteOffset" : 24352,
+            "target" : 34963
+        }
+    ],
+    "samplers" : [
+        {
+            "magFilter" : 9729,
+            "minFilter" : 9987
+        }
+    ],
+    "buffers" : [
+        {
+            "byteLength" : 32920,
+            "uri" : "Tree_2_D_Color1.bin"
+        }
+    ]
+}

BIN
movement/3d_fps/example/assets/models/Tree_2_E_Color1.bin


+ 136 - 0
movement/3d_fps/example/assets/models/Tree_2_E_Color1.gltf

@@ -0,0 +1,136 @@
+{
+    "asset" : {
+        "generator" : "Khronos glTF Blender I/O v3.4.50",
+        "version" : "2.0"
+    },
+    "scene" : 0,
+    "scenes" : [
+        {
+            "name" : "Scene",
+            "nodes" : [
+                0
+            ]
+        }
+    ],
+    "nodes" : [
+        {
+            "mesh" : 0,
+            "name" : "Tree_2_E_Color1"
+        }
+    ],
+    "materials" : [
+        {
+            "name" : "forest",
+            "pbrMetallicRoughness" : {
+                "baseColorTexture" : {
+                    "index" : 0
+                },
+                "metallicFactor" : 0,
+                "roughnessFactor" : 0.6000000238418579
+            }
+        }
+    ],
+    "meshes" : [
+        {
+            "name" : "Tree_2_E_Color1",
+            "primitives" : [
+                {
+                    "attributes" : {
+                        "POSITION" : 0,
+                        "TEXCOORD_0" : 1,
+                        "NORMAL" : 2
+                    },
+                    "indices" : 3,
+                    "material" : 0
+                }
+            ]
+        }
+    ],
+    "textures" : [
+        {
+            "sampler" : 0,
+            "source" : 0
+        }
+    ],
+    "images" : [
+        {
+            "mimeType" : "image/png",
+            "name" : "forest_texture",
+            "uri" : "forest_texture.png"
+        }
+    ],
+    "accessors" : [
+        {
+            "bufferView" : 0,
+            "componentType" : 5126,
+            "count" : 833,
+            "max" : [
+                3.106861114501953,
+                8.355417251586914,
+                2.8980064392089844
+            ],
+            "min" : [
+                -2.944042682647705,
+                -0.43637216091156006,
+                -2.7789015769958496
+            ],
+            "type" : "VEC3"
+        },
+        {
+            "bufferView" : 1,
+            "componentType" : 5126,
+            "count" : 833,
+            "type" : "VEC2"
+        },
+        {
+            "bufferView" : 2,
+            "componentType" : 5126,
+            "count" : 833,
+            "type" : "VEC3"
+        },
+        {
+            "bufferView" : 3,
+            "componentType" : 5123,
+            "count" : 4680,
+            "type" : "SCALAR"
+        }
+    ],
+    "bufferViews" : [
+        {
+            "buffer" : 0,
+            "byteLength" : 9996,
+            "byteOffset" : 0,
+            "target" : 34962
+        },
+        {
+            "buffer" : 0,
+            "byteLength" : 6664,
+            "byteOffset" : 9996,
+            "target" : 34962
+        },
+        {
+            "buffer" : 0,
+            "byteLength" : 9996,
+            "byteOffset" : 16660,
+            "target" : 34962
+        },
+        {
+            "buffer" : 0,
+            "byteLength" : 9360,
+            "byteOffset" : 26656,
+            "target" : 34963
+        }
+    ],
+    "samplers" : [
+        {
+            "magFilter" : 9729,
+            "minFilter" : 9987
+        }
+    ],
+    "buffers" : [
+        {
+            "byteLength" : 36016,
+            "uri" : "Tree_2_E_Color1.bin"
+        }
+    ]
+}

BIN
movement/3d_fps/example/assets/models/Tree_3_A_Color1.bin


+ 136 - 0
movement/3d_fps/example/assets/models/Tree_3_A_Color1.gltf

@@ -0,0 +1,136 @@
+{
+    "asset" : {
+        "generator" : "Khronos glTF Blender I/O v3.4.50",
+        "version" : "2.0"
+    },
+    "scene" : 0,
+    "scenes" : [
+        {
+            "name" : "Scene",
+            "nodes" : [
+                0
+            ]
+        }
+    ],
+    "nodes" : [
+        {
+            "mesh" : 0,
+            "name" : "Tree_3_A_Color1"
+        }
+    ],
+    "materials" : [
+        {
+            "name" : "forest",
+            "pbrMetallicRoughness" : {
+                "baseColorTexture" : {
+                    "index" : 0
+                },
+                "metallicFactor" : 0,
+                "roughnessFactor" : 0.6000000238418579
+            }
+        }
+    ],
+    "meshes" : [
+        {
+            "name" : "Tree_3_A_Color1",
+            "primitives" : [
+                {
+                    "attributes" : {
+                        "POSITION" : 0,
+                        "TEXCOORD_0" : 1,
+                        "NORMAL" : 2
+                    },
+                    "indices" : 3,
+                    "material" : 0
+                }
+            ]
+        }
+    ],
+    "textures" : [
+        {
+            "sampler" : 0,
+            "source" : 0
+        }
+    ],
+    "images" : [
+        {
+            "mimeType" : "image/png",
+            "name" : "forest_texture",
+            "uri" : "forest_texture.png"
+        }
+    ],
+    "accessors" : [
+        {
+            "bufferView" : 0,
+            "componentType" : 5126,
+            "count" : 746,
+            "max" : [
+                1.5410791635513306,
+                3.245279550552368,
+                1.5465989112854004
+            ],
+            "min" : [
+                -1.552119255065918,
+                -0.26131799817085266,
+                -1.5465991497039795
+            ],
+            "type" : "VEC3"
+        },
+        {
+            "bufferView" : 1,
+            "componentType" : 5126,
+            "count" : 746,
+            "type" : "VEC2"
+        },
+        {
+            "bufferView" : 2,
+            "componentType" : 5126,
+            "count" : 746,
+            "type" : "VEC3"
+        },
+        {
+            "bufferView" : 3,
+            "componentType" : 5123,
+            "count" : 2934,
+            "type" : "SCALAR"
+        }
+    ],
+    "bufferViews" : [
+        {
+            "buffer" : 0,
+            "byteLength" : 8952,
+            "byteOffset" : 0,
+            "target" : 34962
+        },
+        {
+            "buffer" : 0,
+            "byteLength" : 5968,
+            "byteOffset" : 8952,
+            "target" : 34962
+        },
+        {
+            "buffer" : 0,
+            "byteLength" : 8952,
+            "byteOffset" : 14920,
+            "target" : 34962
+        },
+        {
+            "buffer" : 0,
+            "byteLength" : 5868,
+            "byteOffset" : 23872,
+            "target" : 34963
+        }
+    ],
+    "samplers" : [
+        {
+            "magFilter" : 9729,
+            "minFilter" : 9987
+        }
+    ],
+    "buffers" : [
+        {
+            "byteLength" : 29740,
+            "uri" : "Tree_3_A_Color1.bin"
+        }
+    ]
+}

+ 10 - 0
movement/3d_fps/example/assets/models/cube_blue.model

@@ -0,0 +1,10 @@
+mesh: "/builtins/assets/meshes/cube.dae"
+name: "cube_white"
+materials {
+  name: "default"
+  material: "/example/materials/model_instanced_cube.material"
+  textures {
+    sampler: "tex0"
+    texture: "/example/assets/textures/1024x1024_texture_blue.png"
+  }
+}

+ 10 - 0
movement/3d_fps/example/assets/models/cube_orange.model

@@ -0,0 +1,10 @@
+mesh: "/builtins/assets/meshes/cube.dae"
+name: "cube_white"
+materials {
+  name: "default"
+  material: "/example/materials/model_instanced_cube.material"
+  textures {
+    sampler: "tex0"
+    texture: "/example/assets/textures/1024x1024_texture_orange.png"
+  }
+}

+ 10 - 0
movement/3d_fps/example/assets/models/cube_white.model

@@ -0,0 +1,10 @@
+mesh: "/builtins/assets/meshes/cube.dae"
+name: "cube_white"
+materials {
+  name: "default"
+  material: "/example/materials/model_instanced_cube.material"
+  textures {
+    sampler: "tex0"
+    texture: "/example/assets/textures/1024x1024_texture_white.png"
+  }
+}

+ 10 - 0
movement/3d_fps/example/assets/models/plane.model

@@ -0,0 +1,10 @@
+mesh: "/builtins/assets/meshes/quad_2x2.dae"
+name: "plane"
+materials {
+  name: "default"
+  material: "/example/materials/model_instanced_floor.material"
+  textures {
+    sampler: "tex0"
+    texture: "/example/assets/textures/1024x1024_texture_white.png"
+  }
+}

+ 10 - 0
movement/3d_fps/example/assets/models/tree_1_B.model

@@ -0,0 +1,10 @@
+mesh: "/example/assets/models/Tree_1_B_Color1.gltf"
+name: "rock_L"
+materials {
+  name: "forest"
+  material: "/example/materials/model_lit.material"
+  textures {
+    sampler: "tex0"
+    texture: "/example/assets/textures/forest_texture.png"
+  }
+}

+ 10 - 0
movement/3d_fps/example/assets/models/tree_2_D.model

@@ -0,0 +1,10 @@
+mesh: "/example/assets/models/Tree_2_D_Color1.gltf"
+name: "rock_L"
+materials {
+  name: "forest"
+  material: "/example/materials/model_lit.material"
+  textures {
+    sampler: "tex0"
+    texture: "/example/assets/textures/forest_texture.png"
+  }
+}

+ 10 - 0
movement/3d_fps/example/assets/models/tree_2_E.model

@@ -0,0 +1,10 @@
+mesh: "/example/assets/models/Tree_2_E_Color1.gltf"
+name: "rock_L"
+materials {
+  name: "forest"
+  material: "/example/materials/model_lit.material"
+  textures {
+    sampler: "tex0"
+    texture: "/example/assets/textures/forest_texture.png"
+  }
+}

+ 10 - 0
movement/3d_fps/example/assets/models/tree_3_A.model

@@ -0,0 +1,10 @@
+mesh: "/example/assets/models/Tree_3_A_Color1.gltf"
+name: "rock_L"
+materials {
+  name: "forest"
+  material: "/example/materials/model_lit.material"
+  textures {
+    sampler: "tex0"
+    texture: "/example/assets/textures/forest_texture.png"
+  }
+}

BIN
movement/3d_fps/example/assets/textures/1024x1024_texture_blue.png


BIN
movement/3d_fps/example/assets/textures/1024x1024_texture_orange.png


BIN
movement/3d_fps/example/assets/textures/1024x1024_texture_white.png


+ 4 - 0
movement/3d_fps/example/assets/textures/example.atlas

@@ -0,0 +1,4 @@
+images {
+  image: "/example/assets/textures/larger.png"
+}
+extrude_borders: 2

BIN
movement/3d_fps/example/assets/textures/forest_texture.png


BIN
movement/3d_fps/example/assets/textures/larger.png


+ 129 - 0
movement/3d_fps/example/character_controller.script

@@ -0,0 +1,129 @@
+-- First-person 3D camera controller
+
+-- Tuning parameters
+local look_sensitivity = 0.15 -- degrees of camera rotation per 1 pixel of mouse movement
+local move_speed = 0.5        -- world units per second for camera movement on XZ plane
+local move_limit = 1.25       -- bounds (half-size) for camera movement on XZ to keep it in a square area
+
+function init(self)
+	-- Acquire input focus to receive input events from the engine
+	msg.post(".", "acquire_input_focus")
+
+	-- Mouse lock state: when true, mouse deltas rotate the camera
+	self.mouse_locked = false
+
+	-- Initialize yaw/pitch from current rotation (stored in degrees in Defold)
+	self.yaw = go.get(".", "euler.y")
+	self.pitch = go.get(".", "euler.x")
+
+	-- Input state for continuous movement (WASD)
+	self.input = {
+		forward = false,
+		backward = false,
+		left = false,
+		right = false,
+	}
+end
+
+function update(self, dt)
+	-- Clamp pitch to avoid flipping the camera upside down
+	if self.pitch > 89 then self.pitch = 89 end
+	if self.pitch < -89 then self.pitch = -89 end
+
+	-- Apply rotation directly via Euler angles (in degrees)
+	go.set(".", "euler", vmath.vector3(self.pitch, self.yaw, 0))
+
+	-- Build desired movement direction on XZ plane from input flags
+	local x = (self.input.right and 1 or 0) - (self.input.left and 1 or 0)
+	local z = (self.input.backward and 1 or 0) - (self.input.forward and 1 or 0)
+
+	-- If there is any movement input, move the camera
+	if x ~= 0 or z ~= 0 then
+		-- Local space direction (camera space)
+		local local_dir = vmath.vector3(x, 0, z)
+		local len = math.sqrt(local_dir.x * local_dir.x + local_dir.z * local_dir.z)
+
+		if len > 0 then
+			-- Normalize to keep speed consistent diagonally
+			local_dir.x = local_dir.x / len
+			local_dir.z = local_dir.z / len
+
+			-- Convert the yaw to a quaternion
+			local q_yaw = vmath.quat_rotation_y(math.rad(self.yaw))
+
+			-- Convert local movement to world space using current yaw
+			local world_dir = vmath.rotate(q_yaw, local_dir)
+
+			-- Get the current position of the character
+			local pos = go.get_position()
+
+			-- Integrate the position
+			pos.x = pos.x + world_dir.x * move_speed * dt
+			pos.z = pos.z + world_dir.z * move_speed * dt
+
+			-- Clamp the position within the square bounds
+			if pos.x > move_limit then pos.x = move_limit end
+			if pos.x < -move_limit then pos.x = -move_limit end
+			if pos.z > move_limit then pos.z = move_limit end
+			if pos.z < -move_limit then pos.z = -move_limit end
+
+			-- Set the new position
+			go.set_position(pos)
+		end
+	end
+end
+
+-- Pre-hashed input action ids (must match project input bindings)
+local KEY_W = hash("key_w")
+local KEY_S = hash("key_s")
+local KEY_A = hash("key_a")
+local KEY_D = hash("key_d")
+local KEY_ESC = hash("key_esc")
+local TOUCH = hash("touch")
+local MOUSE_BUTTON_1 = hash("mouse_button_1")
+
+function on_input(self, action_id, action)
+	-- Mouse look when locked: engine provides action.dx/dy even while cursor is locked
+	if self.mouse_locked and (action.dx or action.dy) then
+		-- Rotate the camera based on the mouse movement
+		self.yaw = self.yaw - (action.dx or 0) * look_sensitivity
+		self.pitch = self.pitch + (action.dy or 0) * look_sensitivity
+	end
+
+	-- Lock on first click (touch or left mouse button)
+	if not self.mouse_locked and action.pressed
+		and (action_id == TOUCH or action_id == MOUSE_BUTTON_1) then
+		-- Lock the mouse
+		window.set_mouse_lock(true)
+		self.mouse_locked = true
+	end
+
+	-- WSAD - Continuous movement input state (pressed/released)
+	if action_id == KEY_W then
+		-- Set the forward input flag to true if the W key is pressed
+		if action.pressed then self.input.forward = true end
+		if action.released then self.input.forward = false end
+	end
+	if action_id == KEY_S then
+		-- Set the backward input flag to true if the S key is pressed
+		if action.pressed then self.input.backward = true end
+		if action.released then self.input.backward = false end
+	end
+	if action_id == KEY_A then
+		-- Set the left input flag to true if the A key is pressed
+		if action.pressed then self.input.left = true end
+		if action.released then self.input.left = false end
+	end
+	if action_id == KEY_D then
+		-- Set the right input flag to true if the D key is pressed
+		if action.pressed then self.input.right = true end
+		if action.released then self.input.right = false end
+	end
+
+	-- ESC unlocks the mouse so the cursor is free again
+	if action_id == KEY_ESC and action.pressed then
+		-- Unlock the mouse
+		window.set_mouse_lock(false)
+		self.mouse_locked = false
+	end
+end

+ 71 - 0
movement/3d_fps/example/example.gui

@@ -0,0 +1,71 @@
+fonts {
+  name: "default"
+  font: "/example/assets/default.font"
+}
+textures {
+  name: "example"
+  texture: "/example/assets/textures/example.atlas"
+}
+nodes {
+  position {
+    x: 350.0
+    y: 100.0
+  }
+  size {
+    x: 600.0
+    y: 100.0
+  }
+  type: TYPE_BOX
+  id: "bg"
+  inherit_alpha: true
+  size_mode: SIZE_MODE_AUTO
+}
+nodes {
+  position {
+    x: 100.0
+    y: 100.0
+  }
+  scale {
+    x: 0.7
+    y: 0.7
+  }
+  color {
+    x: 0.2
+    y: 0.302
+    z: 0.702
+  }
+  type: TYPE_BOX
+  texture: "example/larger"
+  id: "box"
+  inherit_alpha: true
+  size_mode: SIZE_MODE_AUTO
+}
+nodes {
+  position {
+    x: 150.0
+    y: 100.0
+  }
+  scale {
+    x: 0.4
+    y: 0.4
+  }
+  size {
+    x: 200.0
+    y: 100.0
+  }
+  color {
+    x: 0.2
+    y: 0.302
+    z: 0.702
+  }
+  type: TYPE_TEXT
+  text: "Click to lock in mouse. ESC to unlock.\n"
+  "Then move mouse around to rotate camera.\n"
+  "Use WSAD keys to move around."
+  font: "default"
+  id: "text"
+  pivot: PIVOT_W
+  inherit_alpha: true
+}
+material: "/builtins/materials/gui.material"
+adjust_reference: ADJUST_REFERENCE_PARENT

+ 31 - 0
movement/3d_fps/example/materials/model_instanced_cube.fp

@@ -0,0 +1,31 @@
+#version 140
+
+in highp vec4 var_position;
+in mediump vec3 var_normal;
+in mediump vec2 var_texcoord0;
+in mediump vec4 var_light;
+
+out vec4 out_fragColor;
+
+uniform mediump sampler2D tex0;
+
+uniform fs_uniforms
+{
+    mediump vec4 tint;
+};
+
+void main()
+{
+    // Pre-multiply alpha since all runtime textures already are
+    vec4 tint_pm = vec4(tint.xyz * tint.w, tint.w);
+    vec4 color = texture(tex0, var_texcoord0.xy) * tint_pm;
+
+    // Lighting
+    vec3 ambient_light = vec3(0.8);
+    vec3 L = normalize(var_light.xyz - var_position.xyz);
+    float diff = max(dot(var_normal, L), 0.0);
+    vec3 lighting = clamp(ambient_light + diff, 0.0, 1.0);
+
+    out_fragColor = vec4(color.rgb * lighting, 1.0);
+}
+

+ 47 - 0
movement/3d_fps/example/materials/model_instanced_cube.material

@@ -0,0 +1,47 @@
+name: "model_instanced"
+tags: "model"
+vertex_program: "/example/materials/model_instanced_floor.vp"
+fragment_program: "/example/materials/model_instanced_floor.fp"
+vertex_space: VERTEX_SPACE_LOCAL
+vertex_constants {
+  name: "mtx_view"
+  type: CONSTANT_TYPE_VIEW
+}
+vertex_constants {
+  name: "mtx_proj"
+  type: CONSTANT_TYPE_PROJECTION
+}
+vertex_constants {
+  name: "light"
+  type: CONSTANT_TYPE_USER
+  value {
+    x: 10.0
+    y: 10.0
+    z: -10.0
+    w: 1.0
+  }
+}
+vertex_constants {
+  name: "zoom"
+  type: CONSTANT_TYPE_USER
+  value {
+    x: 0.5
+  }
+}
+fragment_constants {
+  name: "tint"
+  type: CONSTANT_TYPE_USER
+  value {
+    x: 1.0
+    y: 1.0
+    z: 1.0
+    w: 1.0
+  }
+}
+samplers {
+  name: "tex0"
+  wrap_u: WRAP_MODE_REPEAT
+  wrap_v: WRAP_MODE_REPEAT
+  filter_min: FILTER_MODE_MIN_LINEAR
+  filter_mag: FILTER_MODE_MAG_LINEAR
+}

+ 40 - 0
movement/3d_fps/example/materials/model_instanced_cube.vp

@@ -0,0 +1,40 @@
+#version 140
+
+// Positions can be world or local space, since world and normal
+// matrices are identity for world vertex space materials.
+// If world vertex space is selected, you can remove the
+// normal matrix multiplication for optimal performance.
+
+in highp vec4 position;
+in mediump vec2 texcoord0;
+in mediump vec3 normal;
+
+// When 'mtx_world' and 'mtx_normal' is specified as attributes,
+// instanced rendering is automatically triggered.
+// To read more about instancing in Defold, navigate to
+// https://defold.com/manuals/material/#instancing
+in mediump mat4 mtx_world;
+in mediump mat4 mtx_normal;
+
+out highp vec4 var_position;
+out mediump vec3 var_normal;
+out mediump vec2 var_texcoord0;
+out mediump vec4 var_light;
+
+uniform vs_uniforms
+{
+    mediump mat4 mtx_view;
+    mediump mat4 mtx_proj;
+    mediump vec4 light;
+    mediump vec4 zoom;
+};
+
+void main()
+{
+    vec4 p = mtx_view * mtx_world * vec4(position.xyz, 1.0);
+    var_light = mtx_view * vec4(light.xyz, 1.0);
+    var_position = p;
+    var_texcoord0 = texcoord0 * zoom.x;
+    var_normal = normalize((mtx_normal * vec4(normal, 0.0)).xyz);
+    gl_Position = mtx_proj * p;
+}

+ 31 - 0
movement/3d_fps/example/materials/model_instanced_floor.fp

@@ -0,0 +1,31 @@
+#version 140
+
+in highp vec4 var_position;
+in mediump vec3 var_normal;
+in mediump vec2 var_texcoord0;
+in mediump vec4 var_light;
+
+out vec4 out_fragColor;
+
+uniform mediump sampler2D tex0;
+
+uniform fs_uniforms
+{
+    mediump vec4 tint;
+};
+
+void main()
+{
+    // Pre-multiply alpha since all runtime textures already are
+    vec4 tint_pm = vec4(tint.xyz * tint.w, tint.w);
+    vec4 color = texture(tex0, var_texcoord0.xy) * tint_pm;
+
+    // Lighting
+    vec3 ambient_light = vec3(0.8);
+    vec3 L = normalize(var_light.xyz - var_position.xyz);
+    float diff = max(dot(var_normal, L), 0.0);
+    vec3 lighting = clamp(ambient_light + diff, 0.0, 1.0);
+
+    out_fragColor = vec4(color.rgb * lighting, 1.0);
+}
+

+ 47 - 0
movement/3d_fps/example/materials/model_instanced_floor.material

@@ -0,0 +1,47 @@
+name: "model_instanced"
+tags: "model"
+vertex_program: "/example/materials/model_instanced_floor.vp"
+fragment_program: "/example/materials/model_instanced_floor.fp"
+vertex_space: VERTEX_SPACE_LOCAL
+vertex_constants {
+  name: "mtx_view"
+  type: CONSTANT_TYPE_VIEW
+}
+vertex_constants {
+  name: "mtx_proj"
+  type: CONSTANT_TYPE_PROJECTION
+}
+vertex_constants {
+  name: "light"
+  type: CONSTANT_TYPE_USER
+  value {
+    x: 10.0
+    y: 10.0
+    z: -10.0
+    w: 1.0
+  }
+}
+vertex_constants {
+  name: "zoom"
+  type: CONSTANT_TYPE_USER
+  value {
+    x: 20.0
+  }
+}
+fragment_constants {
+  name: "tint"
+  type: CONSTANT_TYPE_USER
+  value {
+    x: 1.0
+    y: 1.0
+    z: 1.0
+    w: 1.0
+  }
+}
+samplers {
+  name: "tex0"
+  wrap_u: WRAP_MODE_REPEAT
+  wrap_v: WRAP_MODE_REPEAT
+  filter_min: FILTER_MODE_MIN_LINEAR
+  filter_mag: FILTER_MODE_MAG_LINEAR
+}

+ 40 - 0
movement/3d_fps/example/materials/model_instanced_floor.vp

@@ -0,0 +1,40 @@
+#version 140
+
+// Positions can be world or local space, since world and normal
+// matrices are identity for world vertex space materials.
+// If world vertex space is selected, you can remove the
+// normal matrix multiplication for optimal performance.
+
+in highp vec4 position;
+in mediump vec2 texcoord0;
+in mediump vec3 normal;
+
+// When 'mtx_world' and 'mtx_normal' is specified as attributes,
+// instanced rendering is automatically triggered.
+// To read more about instancing in Defold, navigate to
+// https://defold.com/manuals/material/#instancing
+in mediump mat4 mtx_world;
+in mediump mat4 mtx_normal;
+
+out highp vec4 var_position;
+out mediump vec3 var_normal;
+out mediump vec2 var_texcoord0;
+out mediump vec4 var_light;
+
+uniform vs_uniforms
+{
+    mediump mat4 mtx_view;
+    mediump mat4 mtx_proj;
+    mediump vec4 light;
+    mediump vec4 zoom;
+};
+
+void main()
+{
+    vec4 p = mtx_view * mtx_world * vec4(position.xyz, 1.0);
+    var_light = mtx_view * vec4(light.xyz, 1.0);
+    var_position = p;
+    var_texcoord0 = texcoord0 * zoom.x;
+    var_normal = normalize((mtx_normal * vec4(normal, 0.0)).xyz);
+    gl_Position = mtx_proj * p;
+}

+ 31 - 0
movement/3d_fps/example/materials/model_lit.fp

@@ -0,0 +1,31 @@
+#version 140
+
+in highp vec4 var_position;
+in mediump vec3 var_normal;
+in mediump vec2 var_texcoord0;
+in mediump vec4 var_light;
+
+out vec4 out_fragColor;
+
+uniform mediump sampler2D tex0;
+
+uniform fs_uniforms
+{
+    mediump vec4 tint;
+};
+
+void main()
+{
+    // Pre-multiply alpha since all runtime textures already are
+    vec4 tint_pm = vec4(tint.xyz * tint.w, tint.w);
+    vec4 color = texture(tex0, var_texcoord0.xy) * tint_pm;
+
+    // Diffuse light calculations
+    vec3 ambient_light = vec3(0.8);
+    vec3 diff_light = vec3(normalize(var_light.xyz - var_position.xyz));
+    diff_light = max(dot(var_normal,diff_light), 0.0) + ambient_light;
+    diff_light = clamp(diff_light, 0.0, 1.0);
+
+    out_fragColor = vec4(color.rgb*diff_light,1.0);
+}
+

+ 40 - 0
movement/3d_fps/example/materials/model_lit.material

@@ -0,0 +1,40 @@
+name: "model"
+tags: "model"
+vertex_program: "/example/materials/model_lit.vp"
+fragment_program: "/example/materials/model_lit.fp"
+vertex_space: VERTEX_SPACE_LOCAL
+vertex_constants {
+  name: "mtx_view"
+  type: CONSTANT_TYPE_VIEW
+}
+vertex_constants {
+  name: "mtx_proj"
+  type: CONSTANT_TYPE_PROJECTION
+}
+vertex_constants {
+  name: "light"
+  type: CONSTANT_TYPE_USER
+  value {
+    x: 10.0
+    y: 10.0
+    z: -10.0
+    w: 1.0
+  }
+}
+fragment_constants {
+  name: "tint"
+  type: CONSTANT_TYPE_USER
+  value {
+    x: 1.0
+    y: 1.0
+    z: 1.0
+    w: 1.0
+  }
+}
+samplers {
+  name: "tex0"
+  wrap_u: WRAP_MODE_CLAMP_TO_EDGE
+  wrap_v: WRAP_MODE_CLAMP_TO_EDGE
+  filter_min: FILTER_MODE_MIN_LINEAR
+  filter_mag: FILTER_MODE_MAG_LINEAR
+}

+ 39 - 0
movement/3d_fps/example/materials/model_lit.vp

@@ -0,0 +1,39 @@
+#version 140
+
+// Positions can be world or local space, since world and normal
+// matrices are identity for world vertex space materials.
+// If world vertex space is selected, you can remove the
+// normal matrix multiplication for optimal performance.
+
+in highp vec4 position;
+in mediump vec2 texcoord0;
+in mediump vec3 normal;
+
+// When 'mtx_world' and 'mtx_normal' is specified as attributes,
+// instanced rendering is automatically triggered.
+// To read more about instancing in Defold, navigate to
+// https://defold.com/manuals/material/#instancing
+in mediump mat4 mtx_world;
+in mediump mat4 mtx_normal;
+
+out highp vec4 var_position;
+out mediump vec3 var_normal;
+out mediump vec2 var_texcoord0;
+out mediump vec4 var_light;
+
+uniform vs_uniforms
+{
+    mediump mat4 mtx_view;
+    mediump mat4 mtx_proj;
+    mediump vec4 light;
+};
+
+void main()
+{
+    vec4 p = mtx_view * mtx_world * vec4(position.xyz, 1.0);
+    var_light = mtx_view * vec4(light.xyz, 1.0);
+    var_position = p;
+    var_texcoord0 = texcoord0;
+    var_normal = normalize((mtx_normal * vec4(normal, 0.0)).xyz);
+    gl_Position = mtx_proj * p;
+}

+ 68 - 0
movement/3d_fps/game.project

@@ -0,0 +1,68 @@
+[project]
+title = 3D Basic Movement
+version = 0.1
+dependencies = 
+
+[bootstrap]
+main_collection = /example/3d_fps.collectionc
+render = /builtins/render/default.renderc
+
+[input]
+game_binding = /builtins/input/all.input_bindingc
+repeat_interval = 0.05
+use_accelerometer = 0
+
+[display]
+width = 720
+height = 720
+high_dpi = 1
+
+[physics]
+scale = 0.02
+gravity_y = -500.0
+
+[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
movement/3d_fps/thumbnail.png