diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000000..c04eb75f2a
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,6 @@
+# Lines starting with '#' are comments.
+# Each line is a file pattern followed by one or more owners.
+# Owners can be @users, @org/teams or emails
+
+/misc/2.5d @aaronfranke
+/mono/2.5d @aaronfranke
diff --git a/misc/2.5d/README.md b/misc/2.5d/README.md
new file mode 100644
index 0000000000..01b3d95d45
--- /dev/null
+++ b/misc/2.5d/README.md
@@ -0,0 +1,35 @@
+# 2.5D Demo Project (GDScript)
+
+This demo project shows a way to create a 2.5D game in Godot by mixing 2D and 3D nodes. It also adds a 2.5D editor viewport for easily editing 2.5D levels.
+
+Note: There is a Mono C# version available [here](https://github.com/godotengine/godot-demo-projects/tree/master/mono/2.5d).
+
+## How does it work?
+
+Custom node types are added in a Godot plugin to allow 2.5D objects. Node25D serves as the base for all 2.5D objects. Its first child must be a 3D Spatial, which is used to calculate its position. Then, add a 2D Sprite (or similar) to display the object.
+
+Inside of Node25D, a 2.5D transformation matrix made of three Vector2 is used to calculate the 2D position from the 3D position. For getting a 3D position, this project uses KinematicBody and StaticBody (3D), but these only exist for math - the camera is 2D and all sprites are 2D. You are able to use any Spatial node for math.
+
+Several view modes are implemented, including top down, front side, 45 degree, isometric, and two oblique modes. To implement a different view angle, all you need to do is create a new set of basis vectors in Node25D, use it on all instances, and of course create sprites to display that object in 2D.
+
+The plugin also adds YSort25D to sort Node25D nodes, and ShadowMath25D for calculating a shadow (a simple KinematicBody that tries to cast downward).
+
+## Screenshots
+
+![Forty Five Degrees](screenshots/forty_five.png)
+
+![Isometric](screenshots/isometric.png)
+
+![Oblique Z](screenshots/oblique_z.png)
+
+![Oblique Y](screenshots/oblique_y.png)
+
+![Front Side](screenshots/front_side.png)
+
+![Cube](screenshots/cube.png)
+
+![2.5D Editor Viewport](screenshots/editor.png)
+
+## Music License
+
+`assets/mr_mrs_robot.ogg` Copyright © circa 2008 Juan Linietsky, CC-BY: Attribution.
diff --git a/misc/2.5d/addons/node25d/.broken-gdscripts/Basis25D.gd b/misc/2.5d/addons/node25d/.broken-gdscripts/Basis25D.gd
new file mode 100644
index 0000000000..6b7817fd09
--- /dev/null
+++ b/misc/2.5d/addons/node25d/.broken-gdscripts/Basis25D.gd
@@ -0,0 +1,52 @@
+# Currently broken unless Godot makes this kind of thing possible:
+# https://github.com/godotengine/godot/issues/21461
+# https://github.com/godotengine/godot-proposals/issues/279
+
+# Basis25D structure for performing 2.5D transform math.
+# Note: All code assumes that Y is UP in 3D, and DOWN in 2D.
+# Meaning, a top-down view has a Y axis component of (0, 0), with a Z axis component of (0, 1).
+# For a front side view, Y is (0, -1) and Z is (0, 0).
+# Remember that Godot's 2D mode has the Y axis pointing DOWN on the screen.
+
+class_name Basis25D
+
+var x: Vector2 = Vector2()
+var y: Vector2 = Vector2()
+var z: Vector2 = Vector2()
+
+static func top_down():
+ return init(1, 0, 0, 0, 0, 1)
+
+static func front_side():
+ return init(1, 0, 0, -1, 0, 0)
+
+static func forty_five():
+ return init(1, 0, 0, -0.70710678118, 0, 0.70710678118)
+
+static func isometric():
+ return init(0.86602540378, 0.5, 0, -1, -0.86602540378, 0.5)
+
+static func oblique_y():
+ return init(1, 0, -1, -1, 0, 1)
+
+static func oblique_z():
+ return init(1, 0, 0, -1, -1, 1)
+
+# Creates a Dimetric Basis25D from the angle between the Y axis and the others.
+# Dimetric(2.09439510239) is the same as Isometric.
+# Try to keep this number away from a multiple of Tau/4 (or Pi/2) radians.
+static func dimetric(angle):
+ var sine = sin(angle)
+ var cosine = cos(angle)
+ return init(sine, -cosine, 0, -1, -sine, -cosine)
+
+static func init(xx, xy, yx, yy, zx, zy):
+ var xv = Vector2(xx, xy)
+ var yv = Vector2(yx, yy)
+ var zv = Vector2(zx, zy)
+ return Basis25D.new(xv, yv, zv)
+
+func _init(xAxis: Vector2, yAxis: Vector2, zAxis: Vector2):
+ x = xAxis
+ y = yAxis
+ z = zAxis
diff --git a/misc/2.5d/addons/node25d/.broken-gdscripts/Transform25D.gd b/misc/2.5d/addons/node25d/.broken-gdscripts/Transform25D.gd
new file mode 100644
index 0000000000..d4ed88102f
--- /dev/null
+++ b/misc/2.5d/addons/node25d/.broken-gdscripts/Transform25D.gd
@@ -0,0 +1,22 @@
+# Currently broken unless Godot makes this kind of thing possible:
+# https://github.com/godotengine/godot/issues/21461
+# https://github.com/godotengine/godot-proposals/issues/279
+
+# Calculates the 2D transformation from a 3D position and a Basis25D.
+
+class_name Transform25D
+
+var spatial_position: Vector3 = Vector3()
+var basis #: Basis25D
+
+func flat_transform():
+ return Transform2D(0, flat_position())
+
+func flat_position():
+ var pos = spatial_position.x * basis.x
+ pos += spatial_position.y * basis.y
+ pos += spatial_position.z * basis.z
+ return pos
+
+func _init(basis25d):
+ basis = basis25d
diff --git a/misc/2.5d/addons/node25d/icons/kinematic_body_25d.png b/misc/2.5d/addons/node25d/icons/kinematic_body_25d.png
new file mode 100644
index 0000000000..2780a094fb
Binary files /dev/null and b/misc/2.5d/addons/node25d/icons/kinematic_body_25d.png differ
diff --git a/misc/2.5d/addons/node25d/icons/kinematic_body_25d.png.import b/misc/2.5d/addons/node25d/icons/kinematic_body_25d.png.import
new file mode 100644
index 0000000000..cfa964d52d
--- /dev/null
+++ b/misc/2.5d/addons/node25d/icons/kinematic_body_25d.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/kinematic_body_25d.png-c455d5ccb8ec7543b62fff6e803eee7c.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/node25d/icons/kinematic_body_25d.png"
+dest_files=[ "res://.import/kinematic_body_25d.png-c455d5ccb8ec7543b62fff6e803eee7c.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/addons/node25d/icons/node_25d.png b/misc/2.5d/addons/node25d/icons/node_25d.png
new file mode 100644
index 0000000000..ede1d823fd
Binary files /dev/null and b/misc/2.5d/addons/node25d/icons/node_25d.png differ
diff --git a/misc/2.5d/addons/node25d/icons/node_25d.png.import b/misc/2.5d/addons/node25d/icons/node_25d.png.import
new file mode 100644
index 0000000000..1eba533ef1
--- /dev/null
+++ b/misc/2.5d/addons/node25d/icons/node_25d.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/node_25d.png-72e45d8600ccbde01c6d9ad51f5fc530.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/node25d/icons/node_25d.png"
+dest_files=[ "res://.import/node_25d.png-72e45d8600ccbde01c6d9ad51f5fc530.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/addons/node25d/icons/node_25d_icon.png b/misc/2.5d/addons/node25d/icons/node_25d_icon.png
new file mode 100644
index 0000000000..12b13bb58b
Binary files /dev/null and b/misc/2.5d/addons/node25d/icons/node_25d_icon.png differ
diff --git a/misc/2.5d/addons/node25d/icons/node_25d_icon.png.import b/misc/2.5d/addons/node25d/icons/node_25d_icon.png.import
new file mode 100644
index 0000000000..29f4dda834
--- /dev/null
+++ b/misc/2.5d/addons/node25d/icons/node_25d_icon.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/node_25d_icon.png-2ad780313818706789bbb15408797db2.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/node25d/icons/node_25d_icon.png"
+dest_files=[ "res://.import/node_25d_icon.png-2ad780313818706789bbb15408797db2.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/addons/node25d/icons/shadow_math_25d.png b/misc/2.5d/addons/node25d/icons/shadow_math_25d.png
new file mode 100644
index 0000000000..0dbc8c4b05
Binary files /dev/null and b/misc/2.5d/addons/node25d/icons/shadow_math_25d.png differ
diff --git a/misc/2.5d/addons/node25d/icons/shadow_math_25d.png.import b/misc/2.5d/addons/node25d/icons/shadow_math_25d.png.import
new file mode 100644
index 0000000000..54046259e2
--- /dev/null
+++ b/misc/2.5d/addons/node25d/icons/shadow_math_25d.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/shadow_math_25d.png-333790a3285ee4c26792088985815eba.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/node25d/icons/shadow_math_25d.png"
+dest_files=[ "res://.import/shadow_math_25d.png-333790a3285ee4c26792088985815eba.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/addons/node25d/icons/shadow_math_25d_icon.png b/misc/2.5d/addons/node25d/icons/shadow_math_25d_icon.png
new file mode 100644
index 0000000000..ae13ea3823
Binary files /dev/null and b/misc/2.5d/addons/node25d/icons/shadow_math_25d_icon.png differ
diff --git a/misc/2.5d/addons/node25d/icons/shadow_math_25d_icon.png.import b/misc/2.5d/addons/node25d/icons/shadow_math_25d_icon.png.import
new file mode 100644
index 0000000000..3c8a11d040
--- /dev/null
+++ b/misc/2.5d/addons/node25d/icons/shadow_math_25d_icon.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/shadow_math_25d_icon.png-f286bd905218b9a04121a430c1fdd042.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/node25d/icons/shadow_math_25d_icon.png"
+dest_files=[ "res://.import/shadow_math_25d_icon.png-f286bd905218b9a04121a430c1fdd042.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/addons/node25d/icons/viewport_25d.svg b/misc/2.5d/addons/node25d/icons/viewport_25d.svg
new file mode 100644
index 0000000000..22bd96ec2d
--- /dev/null
+++ b/misc/2.5d/addons/node25d/icons/viewport_25d.svg
@@ -0,0 +1 @@
+
diff --git a/misc/2.5d/addons/node25d/icons/viewport_25d.svg.import b/misc/2.5d/addons/node25d/icons/viewport_25d.svg.import
new file mode 100644
index 0000000000..72df11da53
--- /dev/null
+++ b/misc/2.5d/addons/node25d/icons/viewport_25d.svg.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/viewport_25d.svg-5df077fb699779f821141e20086cbf11.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/node25d/icons/viewport_25d.svg"
+dest_files=[ "res://.import/viewport_25d.svg-5df077fb699779f821141e20086cbf11.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/addons/node25d/icons/y_sort_25d.png b/misc/2.5d/addons/node25d/icons/y_sort_25d.png
new file mode 100644
index 0000000000..e37e57502d
Binary files /dev/null and b/misc/2.5d/addons/node25d/icons/y_sort_25d.png differ
diff --git a/misc/2.5d/addons/node25d/icons/y_sort_25d.png.import b/misc/2.5d/addons/node25d/icons/y_sort_25d.png.import
new file mode 100644
index 0000000000..64a7c359da
--- /dev/null
+++ b/misc/2.5d/addons/node25d/icons/y_sort_25d.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/y_sort_25d.png-2e15f3765afd8b0136201cb9dea4049b.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/node25d/icons/y_sort_25d.png"
+dest_files=[ "res://.import/y_sort_25d.png-2e15f3765afd8b0136201cb9dea4049b.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/addons/node25d/icons/y_sort_25d_icon.png b/misc/2.5d/addons/node25d/icons/y_sort_25d_icon.png
new file mode 100644
index 0000000000..1e362220e2
Binary files /dev/null and b/misc/2.5d/addons/node25d/icons/y_sort_25d_icon.png differ
diff --git a/misc/2.5d/addons/node25d/icons/y_sort_25d_icon.png.import b/misc/2.5d/addons/node25d/icons/y_sort_25d_icon.png.import
new file mode 100644
index 0000000000..f1dab61a48
--- /dev/null
+++ b/misc/2.5d/addons/node25d/icons/y_sort_25d_icon.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/y_sort_25d_icon.png-48050bfa8b299992a68b4f1e12bd5d44.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/node25d/icons/y_sort_25d_icon.png"
+dest_files=[ "res://.import/y_sort_25d_icon.png-48050bfa8b299992a68b4f1e12bd5d44.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/addons/node25d/main_screen/gizmo_25d.gd b/misc/2.5d/addons/node25d/main_screen/gizmo_25d.gd
new file mode 100644
index 0000000000..54a386c144
--- /dev/null
+++ b/misc/2.5d/addons/node25d/main_screen/gizmo_25d.gd
@@ -0,0 +1,105 @@
+tool
+extends Node2D
+
+# Not pixel perfect for all axes in all modes, but works well enough.
+# Rounding is not done until after the movement is finished.
+const ROUGHLY_ROUND_TO_PIXELS = true
+
+# Set when the node is created.
+var node_25d: Node25D
+var spatial_node
+
+# Input from Viewport25D, represents if the mouse is clicked.
+var wants_to_move = false
+
+# Used to control the state of movement.
+var _moving = false
+var _start_position = Vector2()
+
+# Stores state of closest or currently used axis.
+var dominant_axis
+
+onready var lines_root = $Lines
+onready var lines = [$Lines/X, $Lines/Y, $Lines/Z]
+
+func _process(_delta):
+ if !lines:
+ return # Somehow this node hasn't been set up yet.
+ if !node_25d:
+ return # We're most likely viewing the Gizmo25D scene.
+ # While getting the mouse position works in any viewport, it doesn't do
+ # anything significant unless the mouse is in the 2.5D viewport.
+ var mouse_position = get_local_mouse_position()
+ if !_moving:
+ # If the mouse is farther than this many pixels, it won't grab anything.
+ var closest_distance = 20.0
+ dominant_axis = -1
+ for i in range(3):
+ lines[i].modulate.a = 0.8 # Unrelated, but needs a loop too.
+ var distance = _distance_to_segment_at_index(i, mouse_position)
+ if distance < closest_distance:
+ closest_distance = distance
+ dominant_axis = i
+ if dominant_axis == -1:
+ # If we're not hovering over a line, ensure they are placed correctly.
+ lines_root.global_position = node_25d.global_position
+ return
+
+ lines[dominant_axis].modulate.a = 1
+ if !wants_to_move:
+ _moving = false
+ elif wants_to_move and !_moving:
+ _moving = true
+ _start_position = mouse_position
+
+ if _moving:
+ # Change modulate of unselected axes.
+ lines[(dominant_axis + 1) % 3].modulate.a = 0.5
+ lines[(dominant_axis + 2) % 3].modulate.a = 0.5
+ # Calculate mouse movement and reset for next frame.
+ var mouse_diff = mouse_position - _start_position
+ _start_position = mouse_position
+ # Calculate movement.
+ var projected_diff = mouse_diff.project(lines[dominant_axis].points[1])
+ var movement = projected_diff.length() / Node25D.SCALE
+ if is_equal_approx(PI, projected_diff.angle_to(lines[dominant_axis].points[1])):
+ movement *= -1
+ # Apply movement.
+ spatial_node.transform.origin += spatial_node.transform.basis[dominant_axis] * movement
+ else:
+ # Make sure the gizmo is located at the object.
+ global_position = node_25d.global_position
+ if ROUGHLY_ROUND_TO_PIXELS:
+ spatial_node.transform.origin = (spatial_node.transform.origin * Node25D.SCALE).round() / Node25D.SCALE
+ # Move the gizmo lines appropriately.
+ lines_root.global_position = node_25d.global_position
+ node_25d.property_list_changed_notify()
+
+
+# Initializes after _ready due to the onready vars, called manually in Viewport25D.gd.
+# Sets up the points based on the basis values of the Node25D.
+func initialize():
+ var basis = node_25d.get_basis()
+ for i in range(3):
+ lines[i].points[1] = basis[i] * 3
+ global_position = node_25d.global_position
+ spatial_node = node_25d.get_child(0)
+
+
+# Figures out if the mouse is very close to a segment. This method is
+# specialized for this script, it assumes that each segment starts at
+# (0, 0) and it provides a deadzone around the origin.
+func _distance_to_segment_at_index(index, point):
+ if !lines:
+ return INF
+ if point.length_squared() < 400:
+ return INF
+
+ var segment_end = lines[index].points[1]
+ var length_squared = segment_end.length_squared()
+ if length_squared < 400:
+ return INF
+
+ var t = clamp(point.dot(segment_end) / length_squared, 0, 1)
+ var projection = t * segment_end
+ return point.distance_to(projection)
diff --git a/misc/2.5d/addons/node25d/main_screen/gizmo_25d.tscn b/misc/2.5d/addons/node25d/main_screen/gizmo_25d.tscn
new file mode 100644
index 0000000000..5a565dc62b
--- /dev/null
+++ b/misc/2.5d/addons/node25d/main_screen/gizmo_25d.tscn
@@ -0,0 +1,23 @@
+[gd_scene load_steps=2 format=2]
+
+[ext_resource path="res://addons/node25d/main_screen/gizmo_25d.gd" type="Script" id=1]
+
+[node name="Gizmo25D" type="Node2D"]
+script = ExtResource( 1 )
+
+[node name="Lines" type="Node2D" parent="."]
+
+[node name="X" type="Line2D" parent="Lines"]
+modulate = Color( 1, 1, 1, 0.8 )
+points = PoolVector2Array( 0, 0, 100, 0 )
+default_color = Color( 0.91, 0.273, 0, 1 )
+
+[node name="Y" type="Line2D" parent="Lines"]
+modulate = Color( 1, 1, 1, 0.8 )
+points = PoolVector2Array( 0, 0, 0, -100 )
+default_color = Color( 0, 0.91, 0.273, 1 )
+
+[node name="Z" type="Line2D" parent="Lines"]
+modulate = Color( 1, 1, 1, 0.8 )
+points = PoolVector2Array( 0, 0, 0, 100 )
+default_color = Color( 0.3, 0, 1, 1 )
diff --git a/misc/2.5d/addons/node25d/main_screen/main_screen_25d.tscn b/misc/2.5d/addons/node25d/main_screen/main_screen_25d.tscn
new file mode 100644
index 0000000000..a4ea542096
--- /dev/null
+++ b/misc/2.5d/addons/node25d/main_screen/main_screen_25d.tscn
@@ -0,0 +1,173 @@
+[gd_scene load_steps=5 format=2]
+
+[ext_resource path="res://addons/node25d/main_screen/viewport_25d.gd" type="Script" id=1]
+[ext_resource path="res://addons/node25d/main_screen/view_mode_button_group.tres" type="ButtonGroup" id=2]
+
+[sub_resource type="ViewportTexture" id=1]
+viewport_path = NodePath("Viewport25D/Viewport2D")
+
+[sub_resource type="ViewportTexture" id=2]
+viewport_path = NodePath("Viewport25D/ViewportOverlay")
+
+[node name="MainScreen25D" type="VBoxContainer"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="TopBar" type="HBoxContainer" parent="."]
+margin_right = 1600.0
+margin_bottom = 32.0
+rect_min_size = Vector2( 0, 32 )
+size_flags_horizontal = 3
+
+[node name="ViewModeButtons" type="HBoxContainer" parent="TopBar"]
+margin_right = 798.0
+margin_bottom = 32.0
+size_flags_horizontal = 3
+
+[node name="45Degree" type="CheckBox" parent="TopBar/ViewModeButtons"]
+margin_right = 94.0
+margin_bottom = 32.0
+pressed = true
+group = ExtResource( 2 )
+text = "45 Degree"
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Isometric" type="CheckBox" parent="TopBar/ViewModeButtons"]
+margin_left = 98.0
+margin_right = 188.0
+margin_bottom = 32.0
+group = ExtResource( 2 )
+text = "Isometric"
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="TopDown" type="CheckBox" parent="TopBar/ViewModeButtons"]
+margin_left = 192.0
+margin_right = 283.0
+margin_bottom = 32.0
+group = ExtResource( 2 )
+text = "Top Down"
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="FrontSide" type="CheckBox" parent="TopBar/ViewModeButtons"]
+margin_left = 287.0
+margin_right = 379.0
+margin_bottom = 32.0
+group = ExtResource( 2 )
+text = "Front Side"
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="ObliqueY" type="CheckBox" parent="TopBar/ViewModeButtons"]
+margin_left = 383.0
+margin_right = 473.0
+margin_bottom = 32.0
+group = ExtResource( 2 )
+text = "Oblique Y"
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="ObliqueZ" type="CheckBox" parent="TopBar/ViewModeButtons"]
+margin_left = 477.0
+margin_right = 568.0
+margin_bottom = 32.0
+group = ExtResource( 2 )
+text = "Oblique Z"
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Zoom" type="HBoxContainer" parent="TopBar"]
+margin_left = 802.0
+margin_right = 1600.0
+margin_bottom = 32.0
+size_flags_horizontal = 3
+alignment = 2
+
+[node name="ZoomOut" type="Button" parent="TopBar/Zoom"]
+margin_left = 680.0
+margin_right = 710.0
+margin_bottom = 32.0
+rect_min_size = Vector2( 30, 0 )
+text = "-"
+
+[node name="ZoomPercent" type="Label" parent="TopBar/Zoom"]
+margin_left = 714.0
+margin_top = 9.0
+margin_right = 764.0
+margin_bottom = 23.0
+rect_min_size = Vector2( 50, 0 )
+text = "100%"
+align = 1
+clip_text = true
+
+[node name="ZoomReset" type="Button" parent="TopBar/Zoom/ZoomPercent"]
+modulate = Color( 1, 1, 1, 0 )
+anchor_right = 1.0
+anchor_bottom = 1.0
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="ZoomIn" type="Button" parent="TopBar/Zoom"]
+margin_left = 768.0
+margin_right = 798.0
+margin_bottom = 32.0
+rect_min_size = Vector2( 30, 0 )
+text = "+"
+
+[node name="Viewport25D" type="ColorRect" parent="."]
+margin_top = 36.0
+margin_right = 1600.0
+margin_bottom = 900.0
+rect_clip_content = true
+size_flags_horizontal = 3
+size_flags_vertical = 3
+color = Color( 0.301961, 0.301961, 0.301961, 1 )
+script = ExtResource( 1 )
+
+[node name="Viewport2D" type="Viewport" parent="Viewport25D"]
+size = Vector2( 1600, 864 )
+transparent_bg = true
+disable_3d = true
+usage = 1
+render_target_v_flip = true
+
+[node name="ViewportOverlay" type="Viewport" parent="Viewport25D"]
+size = Vector2( 1600, 864 )
+transparent_bg = true
+disable_3d = true
+usage = 1
+render_target_v_flip = true
+
+[node name="ViewportTexture" type="TextureRect" parent="Viewport25D"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+texture = SubResource( 1 )
+expand = true
+__meta__ = {
+"_edit_use_anchors_": false
+}
+
+[node name="Overlay" type="TextureRect" parent="Viewport25D/ViewportTexture"]
+anchor_right = 1.0
+anchor_bottom = 1.0
+texture = SubResource( 2 )
+__meta__ = {
+"_edit_use_anchors_": false
+}
+[connection signal="pressed" from="TopBar/Zoom/ZoomOut" to="Viewport25D" method="_on_ZoomOut_pressed"]
+[connection signal="pressed" from="TopBar/Zoom/ZoomPercent/ZoomReset" to="Viewport25D" method="_on_ZoomReset_pressed"]
+[connection signal="pressed" from="TopBar/Zoom/ZoomIn" to="Viewport25D" method="_on_ZoomIn_pressed"]
diff --git a/misc/2.5d/addons/node25d/main_screen/view_mode_button_group.tres b/misc/2.5d/addons/node25d/main_screen/view_mode_button_group.tres
new file mode 100644
index 0000000000..0e55d74023
--- /dev/null
+++ b/misc/2.5d/addons/node25d/main_screen/view_mode_button_group.tres
@@ -0,0 +1,3 @@
+[gd_resource type="ButtonGroup" format=2]
+
+[resource]
diff --git a/misc/2.5d/addons/node25d/main_screen/viewport_25d.gd b/misc/2.5d/addons/node25d/main_screen/viewport_25d.gd
new file mode 100644
index 0000000000..c6d83d6690
--- /dev/null
+++ b/misc/2.5d/addons/node25d/main_screen/viewport_25d.gd
@@ -0,0 +1,147 @@
+tool
+extends Control
+
+var zoom_level := 0
+var is_panning = false
+var pan_center: Vector2
+var viewport_center: Vector2
+var view_mode_index := 0
+
+var editor_interface: EditorInterface # Set in node25d_plugin.gd
+var moving = false
+
+onready var viewport_2d = $Viewport2D
+onready var viewport_overlay = $ViewportOverlay
+onready var view_mode_button_group: ButtonGroup = $"../TopBar/ViewModeButtons/45Degree".group
+onready var zoom_label: Label = $"../TopBar/Zoom/ZoomPercent"
+onready var gizmo_25d_scene = preload("res://addons/node25d/main_screen/gizmo_25d.tscn")
+
+func _ready():
+ # Give Godot a chance to fully load the scene. Should take two frames.
+ yield(get_tree(), "idle_frame")
+ yield(get_tree(), "idle_frame")
+ var edited_scene_root = get_tree().edited_scene_root
+ if !edited_scene_root:
+ # Godot hasn't finished loading yet, so try loading the plugin again.
+ editor_interface.set_plugin_enabled("node25d", false)
+ editor_interface.set_plugin_enabled("node25d", true)
+ return
+ # Alright, we're loaded up. Now check if we have a valid world and assign it.
+ var world_2d = edited_scene_root.get_viewport().world_2d
+ if world_2d == get_viewport().world_2d:
+ return # This is the MainScreen25D scene opened in the editor!
+ viewport_2d.world_2d = world_2d
+
+
+func _process(delta):
+ if !editor_interface: # Something's not right... bail!
+ return
+
+ # View mode polling.
+ var view_mode_changed_this_frame = false
+ var new_view_mode = view_mode_button_group.get_pressed_button().get_index()
+ if view_mode_index != new_view_mode:
+ view_mode_index = new_view_mode
+ view_mode_changed_this_frame = true
+ _recursive_change_view_mode(get_tree().edited_scene_root)
+
+ # Zooming.
+ if Input.is_mouse_button_pressed(BUTTON_WHEEL_UP):
+ zoom_level += 1
+ elif Input.is_mouse_button_pressed(BUTTON_WHEEL_DOWN):
+ zoom_level -= 1
+ var zoom = _get_zoom_amount()
+
+ # Viewport size.
+ var size = get_global_rect().size
+ viewport_2d.size = size
+
+ # Viewport transform.
+ var viewport_trans = Transform2D.IDENTITY
+ viewport_trans.x *= zoom
+ viewport_trans.y *= zoom
+ viewport_trans.origin = viewport_trans.basis_xform(viewport_center) + size / 2
+ viewport_2d.canvas_transform = viewport_trans
+ viewport_overlay.canvas_transform = viewport_trans
+
+ # Delete unused gizmos.
+ var selection = editor_interface.get_selection().get_selected_nodes()
+ var overlay_children = viewport_overlay.get_children()
+ for overlay_child in overlay_children:
+ var contains = false
+ for selected in selection:
+ if selected == overlay_child.node_25d and !view_mode_changed_this_frame:
+ contains = true
+ if !contains:
+ overlay_child.queue_free()
+
+ # Add new gizmos.
+ for selected in selection:
+ if selected is Node25D:
+ var new = true
+ for overlay_child in overlay_children:
+ if selected == overlay_child.node_25d:
+ new = false
+ if new:
+ var gizmo = gizmo_25d_scene.instance()
+ viewport_overlay.add_child(gizmo)
+ gizmo.node_25d = selected
+ gizmo.initialize()
+
+
+# This only accepts input when the mouse is inside of the 2.5D viewport.
+func _gui_input(event):
+ if event is InputEventMouseButton:
+ if event.is_pressed():
+ if event.button_index == BUTTON_WHEEL_UP:
+ zoom_level += 1
+ accept_event()
+ elif event.button_index == BUTTON_WHEEL_DOWN:
+ zoom_level -= 1
+ accept_event()
+ elif event.button_index == BUTTON_MIDDLE:
+ is_panning = true
+ pan_center = viewport_center - event.position
+ accept_event()
+ elif event.button_index == BUTTON_LEFT:
+ var overlay_children = viewport_overlay.get_children()
+ for overlay_child in overlay_children:
+ overlay_child.wants_to_move = true
+ accept_event()
+ elif event.button_index == BUTTON_MIDDLE:
+ is_panning = false
+ accept_event()
+ elif event.button_index == BUTTON_LEFT:
+ var overlay_children = viewport_overlay.get_children()
+ for overlay_child in overlay_children:
+ overlay_child.wants_to_move = false
+ accept_event()
+ elif event is InputEventMouseMotion:
+ if is_panning:
+ viewport_center = pan_center + event.position
+ accept_event()
+
+
+func _recursive_change_view_mode(current_node):
+ if current_node.has_method("set_view_mode"):
+ current_node.set_view_mode(view_mode_index)
+ for child in current_node.get_children():
+ _recursive_change_view_mode(child)
+
+
+func _get_zoom_amount():
+ var zoom_amount = pow(1.05476607648, zoom_level) # 13th root of 2
+ zoom_label.text = str(round(zoom_amount * 1000) / 10) + "%"
+ return zoom_amount
+
+
+func _on_ZoomOut_pressed():
+ zoom_level -= 1
+
+
+func _on_ZoomIn_pressed():
+ zoom_level += 1
+
+
+func _on_ZoomReset_pressed():
+ zoom_level = 0
diff --git a/misc/2.5d/addons/node25d/node25d_plugin.gd b/misc/2.5d/addons/node25d/node25d_plugin.gd
new file mode 100644
index 0000000000..a5d179e7d2
--- /dev/null
+++ b/misc/2.5d/addons/node25d/node25d_plugin.gd
@@ -0,0 +1,47 @@
+tool
+extends EditorPlugin
+
+const MainPanel = preload("res://addons/node25d/main_screen/main_screen_25d.tscn")
+
+var main_panel_instance
+
+func _enter_tree():
+ main_panel_instance = MainPanel.instance()
+ main_panel_instance.get_child(1).editor_interface = get_editor_interface()
+
+ # Add the main panel to the editor's main viewport.
+ get_editor_interface().get_editor_viewport().add_child(main_panel_instance)
+
+ # Hide the main panel.
+ make_visible(false)
+ # When this plugin node enters tree, add the custom types.
+ add_custom_type("Node25D", "Node2D", preload("node_25d.gd"), preload("icons/node_25d_icon.png"))
+ add_custom_type("YSort25D", "Node", preload("y_sort_25d.gd"), preload("icons/y_sort_25d_icon.png"))
+ add_custom_type("ShadowMath25D", "KinematicBody", preload("shadow_math_25d.gd"), preload("icons/shadow_math_25d_icon.png"))
+
+
+func _exit_tree():
+ main_panel_instance.queue_free()
+ # When the plugin node exits the tree, remove the custom types.
+ remove_custom_type("ShadowMath25D")
+ remove_custom_type("YSort25D")
+ remove_custom_type("Node25D")
+
+
+func has_main_screen():
+ return true
+
+
+func make_visible(visible):
+ if visible:
+ main_panel_instance.show()
+ else:
+ main_panel_instance.hide()
+
+
+func get_plugin_name():
+ return "2.5D"
+
+
+func get_plugin_icon():
+ return preload("res://addons/node25d/icons/viewport_25d.svg")
diff --git a/misc/2.5d/addons/node25d/node_25d.gd b/misc/2.5d/addons/node25d/node_25d.gd
new file mode 100644
index 0000000000..be8b00b67f
--- /dev/null
+++ b/misc/2.5d/addons/node25d/node_25d.gd
@@ -0,0 +1,131 @@
+# This node converts a 3D position to 2D using a 2.5D transformation matrix.
+# The transformation of its 2D form is controlled by its 3D child.
+tool
+extends Node2D
+class_name Node25D, "res://addons/node25d/icons/node_25d_icon.png"
+
+# SCALE is the number of 2D units in one 3D unit. Ideally, but not necessarily, an integer.
+const SCALE = 32
+
+# Exported spatial position for editor usage.
+export(Vector3) var spatial_position setget set_spatial_position, get_spatial_position
+
+# GDScript throws errors when Basis25D is its own structure.
+# There is a broken implementation in a hidden folder.
+# https://github.com/godotengine/godot/issues/21461
+# https://github.com/godotengine/godot-proposals/issues/279
+var _basisX: Vector2
+var _basisY: Vector2
+var _basisZ: Vector2
+
+# Cache the spatial stuff for internal use.
+var _spatial_position: Vector3
+var _spatial_node: Spatial
+
+
+# These are separated in case anyone wishes to easily extend Node25D.
+func _ready():
+ Node25D_ready()
+
+
+func _process(_delta):
+ Node25D_process()
+
+
+# Call this method in _ready, or before Node25D_process is first ran.
+func Node25D_ready():
+ _spatial_node = get_child(0)
+ # Changing the values here will change the default for all Node25D instances.
+ _basisX = SCALE * Vector2(1, 0)
+ _basisY = SCALE * Vector2(0, -0.70710678118)
+ _basisZ = SCALE * Vector2(0, 0.70710678118)
+
+
+# Call this method in _process, or whenever the position of this object changes.
+func Node25D_process():
+ _check_view_mode()
+ if _spatial_node == null:
+ return
+ _spatial_position = _spatial_node.translation
+
+ var flat_pos = _spatial_position.x * _basisX
+ flat_pos += _spatial_position.y * _basisY
+ flat_pos += _spatial_position.z * _basisZ
+
+ global_position = flat_pos
+
+
+func get_basis():
+ return [_basisX, _basisY, _basisZ]
+
+
+func get_spatial_position():
+ if !_spatial_node:
+ _spatial_node = get_child(0)
+ return _spatial_node.translation
+
+
+func set_spatial_position(value):
+ _spatial_position = value
+ if _spatial_node:
+ _spatial_node.translation = value
+ elif get_child_count() > 0:
+ _spatial_node = get_child(0)
+
+
+# Change the basis based on the view_mode_index argument.
+# This can be changed or removed in actual games where you only need one view mode.
+func set_view_mode(view_mode_index):
+ match view_mode_index:
+ 0: # 45 Degrees
+ _basisX = SCALE * Vector2(1, 0)
+ _basisY = SCALE * Vector2(0, -0.70710678118)
+ _basisZ = SCALE * Vector2(0, 0.70710678118)
+ 1: # Isometric
+ _basisX = SCALE * Vector2(0.86602540378, 0.5)
+ _basisY = SCALE * Vector2(0, -1)
+ _basisZ = SCALE * Vector2(-0.86602540378, 0.5)
+ 2: # Top Down
+ _basisX = SCALE * Vector2(1, 0)
+ _basisY = SCALE * Vector2(0, 0)
+ _basisZ = SCALE * Vector2(0, 1)
+ 3: # Front Side
+ _basisX = SCALE * Vector2(1, 0)
+ _basisY = SCALE * Vector2(0, -1)
+ _basisZ = SCALE * Vector2(0, 0)
+ 4: # Oblique Y
+ _basisX = SCALE * Vector2(1, 0)
+ _basisY = SCALE * Vector2(-0.70710678118, -0.70710678118)
+ _basisZ = SCALE * Vector2(0, 1)
+ 5: # Oblique Z
+ _basisX = SCALE * Vector2(1, 0)
+ _basisY = SCALE * Vector2(0, -1)
+ _basisZ = SCALE * Vector2(-0.70710678118, 0.70710678118)
+
+
+# Check if anyone presses the view mode buttons and change the basis accordingly.
+# This can be changed or removed in actual games where you only need one view mode.
+func _check_view_mode():
+ if Input.is_action_just_pressed("forty_five_mode"):
+ set_view_mode(0)
+ elif Input.is_action_just_pressed("isometric_mode"):
+ set_view_mode(1)
+ elif Input.is_action_just_pressed("top_down_mode"):
+ set_view_mode(2)
+ elif Input.is_action_just_pressed("front_side_mode"):
+ set_view_mode(3)
+ elif Input.is_action_just_pressed("oblique_y_mode"):
+ set_view_mode(4)
+ elif Input.is_action_just_pressed("oblique_z_mode"):
+ set_view_mode(5)
+
+
+# Used by YSort25D
+static func y_sort(a: Node25D, b: Node25D):
+ return a._spatial_position.y < b._spatial_position.y
+
+
+static func y_sort_slight_xz(a: Node25D, b: Node25D):
+ var a_index = a._spatial_position.y + 0.001 * (a._spatial_position.x + a._spatial_position.z)
+ var b_index = b._spatial_position.y + 0.001 * (b._spatial_position.x + b._spatial_position.z)
+ return a_index < b_index
diff --git a/misc/2.5d/addons/node25d/plugin.cfg b/misc/2.5d/addons/node25d/plugin.cfg
new file mode 100644
index 0000000000..3336dfa810
--- /dev/null
+++ b/misc/2.5d/addons/node25d/plugin.cfg
@@ -0,0 +1,7 @@
+[plugin]
+
+name="Node25D"
+description="Adds Node25D"
+author="Aaron Franke"
+version="1.0"
+script="node25d_plugin.gd"
diff --git a/misc/2.5d/addons/node25d/shadow_math_25d.gd b/misc/2.5d/addons/node25d/shadow_math_25d.gd
new file mode 100644
index 0000000000..8df0182abb
--- /dev/null
+++ b/misc/2.5d/addons/node25d/shadow_math_25d.gd
@@ -0,0 +1,33 @@
+# Adds a simple shadow below an object.
+# Place this ShadowMath25D node as a child of a Shadow25D, which
+# is below the target object in the scene tree (not as a child).
+tool
+extends KinematicBody
+class_name ShadowMath25D, "res://addons/node25d/icons/shadow_math_25d_icon.png"
+
+# The maximum distance below objects that shadows will appear (in 3D units).
+var shadow_length = 1000.0
+var _shadow_root: Node25D
+var _target_math: Spatial
+
+
+func _ready():
+ _shadow_root = get_parent()
+ var index = _shadow_root.get_position_in_parent()
+ if (index > 0): # Else, Shadow is not in a valid place.
+ _target_math = _shadow_root.get_parent().get_child(index - 1).get_child(0)
+
+
+func _process(_delta):
+ if _target_math == null:
+ if _shadow_root != null:
+ _shadow_root.visible = false
+ return # Shadow is not in a valid place or you're viewing the Shadow25D scene.
+
+ translation = _target_math.translation
+ var k = move_and_collide(Vector3.DOWN * shadow_length)
+ if k == null:
+ _shadow_root.visible = false
+ else:
+ _shadow_root.visible = true
+ global_transform = transform
diff --git a/misc/2.5d/addons/node25d/y_sort_25d.gd b/misc/2.5d/addons/node25d/y_sort_25d.gd
new file mode 100644
index 0000000000..1f289820c4
--- /dev/null
+++ b/misc/2.5d/addons/node25d/y_sort_25d.gd
@@ -0,0 +1,46 @@
+# Sorts all Node25D children of its parent.
+# This is different from the C# version of this project
+# because the execution order is different and otherwise
+# sorting is delayed by one frame.
+tool
+extends Node # Note: NOT Node2D, Node25D, or YSort
+class_name YSort25D, "res://addons/node25d/icons/y_sort_25d_icon.png"
+
+# Whether or not to automatically call sort() in _process().
+export(bool) var sort_enabled := true
+var _parent_node: Node2D # NOT Node25D
+
+
+func _ready():
+ _parent_node = get_parent()
+
+
+func _process(_delta):
+ if sort_enabled:
+ sort()
+
+
+# Call this method in _process, or whenever you want to sort children.
+func sort():
+ if _parent_node == null:
+ return # _ready() hasn't been run yet
+ var parent_children = _parent_node.get_children()
+ if parent_children.size() > 4000:
+ # The Z index only goes from -4096 to 4096, and we want room for objects having multiple layers.
+ printerr("Sorting failed: Max number of YSort25D nodes is 4000.")
+ return
+
+ # We only want to get Node25D children.
+ # Currently, it also grabs Node2D children.
+ var node25d_nodes = []
+ for n in parent_children:
+ if n.get_class() == "Node2D":
+ node25d_nodes.append(n)
+ node25d_nodes.sort_custom(Node25D, "y_sort_slight_xz")
+
+ var z_index = -4000
+ for i in range(0, node25d_nodes.size()):
+ node25d_nodes[i].z_index = z_index
+ # Increment by 2 each time, to allow for shadows in-between.
+ # This does mean that we have a limit of 4000 total sorted Node25Ds.
+ z_index += 2
diff --git a/misc/2.5d/assets/cube/cube.tscn b/misc/2.5d/assets/cube/cube.tscn
new file mode 100644
index 0000000000..6034f1c8bc
--- /dev/null
+++ b/misc/2.5d/assets/cube/cube.tscn
@@ -0,0 +1,22 @@
+[gd_scene load_steps=5 format=2]
+
+[ext_resource path="res://addons/node25d/icons/y_sort_25d_icon.png" type="Texture" id=1]
+[ext_resource path="res://assets/ui/overlay_cube.tscn" type="PackedScene" id=2]
+[ext_resource path="res://assets/cube/cube_math.gd" type="Script" id=3]
+[ext_resource path="res://addons/node25d/y_sort_25d.gd" type="Script" id=4]
+
+[node name="Cube" type="Node2D"]
+
+[node name="Overlay" parent="." instance=ExtResource( 2 )]
+
+[node name="Camera2D" type="Camera2D" parent="."]
+current = true
+
+[node name="CubeMath" type="Spatial" parent="."]
+script = ExtResource( 3 )
+
+[node name="YSort25D" type="Node" parent="."]
+script = ExtResource( 4 )
+__meta__ = {
+"_editor_icon": ExtResource( 1 )
+}
diff --git a/misc/2.5d/assets/cube/cube_math.gd b/misc/2.5d/assets/cube/cube_math.gd
new file mode 100644
index 0000000000..bde261fbb0
--- /dev/null
+++ b/misc/2.5d/assets/cube/cube_math.gd
@@ -0,0 +1,52 @@
+extends Spatial
+
+onready var _cube_point_scene: PackedScene = preload("res://assets/cube/cube_point.tscn")
+
+onready var _parent = get_parent()
+var _is_parent_ready := false
+var _cube_points_math = []
+var _cube_math_spatials = []
+
+func _ready():
+ _parent = get_parent()
+
+ for i in range(27):
+ # warning-ignore:integer_division
+ var a: int = (i / 9) - 1
+ # warning-ignore:integer_division
+ var b: int = (i / 3) % 3 - 1
+ var c: int = (i % 3) - 1
+ var spatial_position: Vector3 = 5 * (a * Vector3.RIGHT + b * Vector3.UP + c * Vector3.BACK)
+ _cube_math_spatials.append(Spatial.new())
+ _cube_math_spatials[i].translation = spatial_position
+ _cube_math_spatials[i].name = "CubeMath #" + str(i) + ", " + str(a) + " " + str(b) + " " + str(c)
+ add_child(_cube_math_spatials[i])
+
+
+func _process(delta):
+ if Input.is_action_pressed("exit"):
+ get_tree().quit()
+
+ if Input.is_action_just_pressed("view_cube_demo"):
+ # warning-ignore:return_value_discarded
+ get_tree().change_scene("res://assets/demo_scene.tscn")
+ return
+
+ if _is_parent_ready:
+ if Input.is_action_just_pressed("reset_position"):
+ transform = Transform.IDENTITY
+ else:
+ rotate_x(delta * (Input.get_action_strength("move_back") - Input.get_action_strength("move_forward")))
+ rotate_y(delta * (Input.get_action_strength("move_right") - Input.get_action_strength("move_left")))
+ rotate_z(delta * (Input.get_action_strength("move_counterclockwise") - Input.get_action_strength("move_clockwise")))
+ for i in range(27):
+ _cube_points_math[i].global_transform = _cube_math_spatials[i].global_transform
+ else:
+ # This code block will be run only once. It's not in _ready() because the parent isn't set up there.
+ for i in range(27):
+ var my_cube_point_scene = _cube_point_scene.duplicate(true)
+ var cube_point = my_cube_point_scene.instance()
+ cube_point.name = "CubePoint #" + str(i)
+ _cube_points_math.append(cube_point.get_child(0))
+ _parent.add_child(cube_point)
+ _is_parent_ready = true
diff --git a/misc/2.5d/assets/cube/cube_point.tscn b/misc/2.5d/assets/cube/cube_point.tscn
new file mode 100644
index 0000000000..d880e9fb02
--- /dev/null
+++ b/misc/2.5d/assets/cube/cube_point.tscn
@@ -0,0 +1,16 @@
+[gd_scene load_steps=4 format=2]
+
+[ext_resource path="res://addons/node25d/node_25d.gd" type="Script" id=1]
+[ext_resource path="res://addons/node25d/icons/node_25d_icon.png" type="Texture" id=2]
+[ext_resource path="res://assets/cube/godot.png" type="Texture" id=3]
+
+[node name="CubePoint" type="Node2D"]
+script = ExtResource( 1 )
+__meta__ = {
+"_editor_icon": ExtResource( 2 )
+}
+
+[node name="CubePointMath" type="Spatial" parent="."]
+
+[node name="CubePointSprite" type="Sprite" parent="."]
+texture = ExtResource( 3 )
diff --git a/misc/2.5d/assets/cube/godot.png b/misc/2.5d/assets/cube/godot.png
new file mode 100644
index 0000000000..b66446484e
Binary files /dev/null and b/misc/2.5d/assets/cube/godot.png differ
diff --git a/misc/2.5d/assets/cube/godot.png.import b/misc/2.5d/assets/cube/godot.png.import
new file mode 100644
index 0000000000..0cc11e6bc9
--- /dev/null
+++ b/misc/2.5d/assets/cube/godot.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/godot.png-a942b208c71d1b44958f34d302d011ec.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/cube/godot.png"
+dest_files=[ "res://.import/godot.png-a942b208c71d1b44958f34d302d011ec.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/assets/demo_scene.tscn b/misc/2.5d/assets/demo_scene.tscn
new file mode 100644
index 0000000000..aed9dbabcc
--- /dev/null
+++ b/misc/2.5d/assets/demo_scene.tscn
@@ -0,0 +1,621 @@
+[gd_scene load_steps=14 format=2]
+
+[ext_resource path="res://assets/ui/overlay.tscn" type="PackedScene" id=1]
+[ext_resource path="res://assets/player/player_25d.tscn" type="PackedScene" id=2]
+[ext_resource path="res://assets/shadow/shadow_25d.tscn" type="PackedScene" id=3]
+[ext_resource path="res://addons/node25d/node_25d.gd" type="Script" id=4]
+[ext_resource path="res://addons/node25d/icons/node_25d_icon.png" type="Texture" id=5]
+[ext_resource path="res://assets/platform/textures/forty_five.png" type="Texture" id=6]
+[ext_resource path="res://assets/platform/platform_sprite.gd" type="Script" id=7]
+[ext_resource path="res://addons/node25d/y_sort_25d.gd" type="Script" id=8]
+[ext_resource path="res://addons/node25d/icons/y_sort_25d_icon.png" type="Texture" id=9]
+[ext_resource path="res://assets/mr_mrs_robot.ogg" type="AudioStream" id=10]
+
+[sub_resource type="BoxShape" id=1]
+extents = Vector3( 5, 0.5, 5 )
+
+[sub_resource type="BoxShape" id=2]
+extents = Vector3( 5, 0.5, 5 )
+
+[sub_resource type="BoxShape" id=3]
+extents = Vector3( 5, 0.5, 5 )
+
+[node name="DemoScene" type="Node2D"]
+
+[node name="Overlay" parent="." instance=ExtResource( 1 )]
+
+[node name="Player25D" parent="." instance=ExtResource( 2 )]
+position = Vector2( 0, -226.274 )
+z_index = -3952
+
+[node name="Shadow25D" parent="." instance=ExtResource( 3 )]
+visible = true
+position = Vector2( 1.00261e-06, 11.2685 )
+z_index = -3958
+spatial_position = Vector3( 3.13315e-08, -0.498, 3.13315e-08 )
+
+[node name="Platform0" type="Node2D" parent="."]
+position = Vector2( -256, -113.137 )
+z_index = -3954
+script = ExtResource( 4 )
+__meta__ = {
+"_editor_icon": ExtResource( 5 )
+}
+spatial_position = Vector3( -8, 5, 0 )
+
+[node name="PlatformMath" type="StaticBody" parent="Platform0"]
+transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -8, 5, 0 )
+collision_layer = 1048575
+collision_mask = 1048575
+
+[node name="CollisionShape" type="CollisionShape" parent="Platform0/PlatformMath"]
+shape = SubResource( 1 )
+__meta__ = {
+"_edit_lock_": true
+}
+
+[node name="PlatformSprite" type="Sprite" parent="Platform0"]
+scale = Vector2( 0.5, 0.5 )
+texture = ExtResource( 6 )
+script = ExtResource( 7 )
+
+[node name="Platform1" type="Node2D" parent="."]
+position = Vector2( -256, -339.411 )
+z_index = -3956
+script = ExtResource( 4 )
+__meta__ = {
+"_editor_icon": ExtResource( 5 )
+}
+spatial_position = Vector3( -8, 5, -10 )
+
+[node name="PlatformMath" type="StaticBody" parent="Platform1"]
+transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -8, 5, -10 )
+collision_layer = 1048575
+collision_mask = 1048575
+
+[node name="CollisionShape" type="CollisionShape" parent="Platform1/PlatformMath"]
+shape = SubResource( 1 )
+__meta__ = {
+"_edit_lock_": true
+}
+
+[node name="PlatformSprite" type="Sprite" parent="Platform1"]
+scale = Vector2( 0.5, 0.5 )
+texture = ExtResource( 6 )
+script = ExtResource( 7 )
+
+[node name="Platform2" type="Node2D" parent="."]
+position = Vector2( 0, 22.6274 )
+z_index = -3962
+script = ExtResource( 4 )
+__meta__ = {
+"_editor_icon": ExtResource( 5 )
+}
+spatial_position = Vector3( 0, -1, 0 )
+
+[node name="PlatformMath" type="StaticBody" parent="Platform2"]
+transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1, 0 )
+collision_layer = 1048575
+collision_mask = 1048575
+
+[node name="CollisionShape" type="CollisionShape" parent="Platform2/PlatformMath"]
+shape = SubResource( 1 )
+__meta__ = {
+"_edit_lock_": true
+}
+
+[node name="PlatformSprite" type="Sprite" parent="Platform2"]
+scale = Vector2( 0.5, 0.5 )
+texture = ExtResource( 6 )
+script = ExtResource( 7 )
+
+[node name="Platform3" type="Node2D" parent="."]
+position = Vector2( 320, 22.6274 )
+z_index = -3960
+script = ExtResource( 4 )
+__meta__ = {
+"_editor_icon": ExtResource( 5 )
+}
+spatial_position = Vector3( 10, -1, 0 )
+
+[node name="PlatformMath" type="StaticBody" parent="Platform3"]
+transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 10, -1, 0 )
+collision_layer = 1048575
+collision_mask = 1048575
+
+[node name="CollisionShape" type="CollisionShape" parent="Platform3/PlatformMath"]
+shape = SubResource( 1 )
+__meta__ = {
+"_edit_lock_": true
+}
+
+[node name="PlatformSprite" type="Sprite" parent="Platform3"]
+scale = Vector2( 0.5, 0.5 )
+texture = ExtResource( 6 )
+script = ExtResource( 7 )
+
+[node name="Platform4" type="Node2D" parent="."]
+position = Vector2( 0, -203.647 )
+z_index = -3966
+script = ExtResource( 4 )
+__meta__ = {
+"_editor_icon": ExtResource( 5 )
+}
+spatial_position = Vector3( 0, -1, -10 )
+
+[node name="PlatformMath" type="StaticBody" parent="Platform4"]
+transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1, -10 )
+collision_layer = 1048575
+collision_mask = 1048575
+
+[node name="CollisionShape" type="CollisionShape" parent="Platform4/PlatformMath"]
+shape = SubResource( 1 )
+__meta__ = {
+"_edit_lock_": true
+}
+
+[node name="PlatformSprite" type="Sprite" parent="Platform4"]
+scale = Vector2( 0.5, 0.5 )
+texture = ExtResource( 6 )
+script = ExtResource( 7 )
+
+[node name="Platform5" type="Node2D" parent="."]
+position = Vector2( 320, -113.137 )
+z_index = -3984
+script = ExtResource( 4 )
+__meta__ = {
+"_editor_icon": ExtResource( 5 )
+}
+spatial_position = Vector3( 10, -5, -10 )
+
+[node name="PlatformMath" type="StaticBody" parent="Platform5"]
+transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 10, -5, -10 )
+collision_layer = 1048575
+collision_mask = 1048575
+
+[node name="CollisionShape" type="CollisionShape" parent="Platform5/PlatformMath"]
+shape = SubResource( 1 )
+__meta__ = {
+"_edit_lock_": true
+}
+
+[node name="PlatformSprite" type="Sprite" parent="Platform5"]
+scale = Vector2( 0.5, 0.5 )
+texture = ExtResource( 6 )
+script = ExtResource( 7 )
+
+[node name="Platform6" type="Node2D" parent="."]
+position = Vector2( 320, 113.137 )
+z_index = -3982
+script = ExtResource( 4 )
+__meta__ = {
+"_editor_icon": ExtResource( 5 )
+}
+spatial_position = Vector3( 10, -5, 0 )
+
+[node name="PlatformMath" type="StaticBody" parent="Platform6"]
+transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 10, -5, 0 )
+collision_layer = 1048575
+collision_mask = 1048575
+
+[node name="CollisionShape" type="CollisionShape" parent="Platform6/PlatformMath"]
+shape = SubResource( 1 )
+__meta__ = {
+"_edit_lock_": true
+}
+
+[node name="PlatformSprite" type="Sprite" parent="Platform6"]
+scale = Vector2( 0.5, 0.5 )
+texture = ExtResource( 6 )
+script = ExtResource( 7 )
+
+[node name="Platform7" type="Node2D" parent="."]
+position = Vector2( 320, 339.411 )
+z_index = -3978
+script = ExtResource( 4 )
+__meta__ = {
+"_editor_icon": ExtResource( 5 )
+}
+spatial_position = Vector3( 10, -5, 10 )
+
+[node name="PlatformMath" type="StaticBody" parent="Platform7"]
+transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 10, -5, 10 )
+collision_layer = 1048575
+collision_mask = 1048575
+
+[node name="CollisionShape" type="CollisionShape" parent="Platform7/PlatformMath"]
+shape = SubResource( 2 )
+__meta__ = {
+"_edit_lock_": true
+}
+
+[node name="PlatformSprite" type="Sprite" parent="Platform7"]
+scale = Vector2( 0.5, 0.5 )
+texture = ExtResource( 6 )
+script = ExtResource( 7 )
+
+[node name="Platform20" type="Node2D" parent="."]
+position = Vector2( 320, 565.685 )
+z_index = -3976
+script = ExtResource( 4 )
+__meta__ = {
+"_editor_icon": ExtResource( 5 )
+}
+spatial_position = Vector3( 10, -5, 20 )
+
+[node name="PlatformMath" type="StaticBody" parent="Platform20"]
+transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 10, -5, 20 )
+collision_layer = 1048575
+collision_mask = 1048575
+
+[node name="CollisionShape" type="CollisionShape" parent="Platform20/PlatformMath"]
+shape = SubResource( 2 )
+__meta__ = {
+"_edit_lock_": true
+}
+
+[node name="PlatformSprite" type="Sprite" parent="Platform20"]
+scale = Vector2( 0.5, 0.5 )
+texture = ExtResource( 6 )
+script = ExtResource( 7 )
+
+[node name="Platform21" type="Node2D" parent="."]
+position = Vector2( 320, 791.96 )
+z_index = -3972
+script = ExtResource( 4 )
+__meta__ = {
+"_editor_icon": ExtResource( 5 )
+}
+spatial_position = Vector3( 10, -5, 30 )
+
+[node name="PlatformMath" type="StaticBody" parent="Platform21"]
+transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 10, -5, 30 )
+collision_layer = 1048575
+collision_mask = 1048575
+
+[node name="CollisionShape" type="CollisionShape" parent="Platform21/PlatformMath"]
+shape = SubResource( 2 )
+__meta__ = {
+"_edit_lock_": true
+}
+
+[node name="PlatformSprite" type="Sprite" parent="Platform21"]
+scale = Vector2( 0.5, 0.5 )
+texture = ExtResource( 6 )
+script = ExtResource( 7 )
+
+[node name="Platform22" type="Node2D" parent="."]
+position = Vector2( 320, 1018.23 )
+z_index = -3970
+script = ExtResource( 4 )
+__meta__ = {
+"_editor_icon": ExtResource( 5 )
+}
+spatial_position = Vector3( 10, -5, 40 )
+
+[node name="PlatformMath" type="StaticBody" parent="Platform22"]
+transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 10, -5, 40 )
+collision_layer = 1048575
+collision_mask = 1048575
+
+[node name="CollisionShape" type="CollisionShape" parent="Platform22/PlatformMath"]
+shape = SubResource( 2 )
+__meta__ = {
+"_edit_lock_": true
+}
+
+[node name="PlatformSprite" type="Sprite" parent="Platform22"]
+scale = Vector2( 0.5, 0.5 )
+texture = ExtResource( 6 )
+script = ExtResource( 7 )
+
+[node name="Platform9" type="Node2D" parent="."]
+position = Vector2( 640, 339.411 )
+z_index = -3974
+script = ExtResource( 4 )
+__meta__ = {
+"_editor_icon": ExtResource( 5 )
+}
+spatial_position = Vector3( 20, -5, 10 )
+
+[node name="PlatformMath" type="StaticBody" parent="Platform9"]
+transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 20, -5, 10 )
+collision_layer = 1048575
+collision_mask = 1048575
+
+[node name="CollisionShape" type="CollisionShape" parent="Platform9/PlatformMath"]
+shape = SubResource( 3 )
+__meta__ = {
+"_edit_lock_": true
+}
+
+[node name="PlatformSprite" type="Sprite" parent="Platform9"]
+scale = Vector2( 0.5, 0.5 )
+texture = ExtResource( 6 )
+script = ExtResource( 7 )
+
+[node name="Platform10" type="Node2D" parent="."]
+position = Vector2( 896, 294.156 )
+z_index = -3994
+script = ExtResource( 4 )
+__meta__ = {
+"_editor_icon": ExtResource( 5 )
+}
+spatial_position = Vector3( 28, -10, 3 )
+
+[node name="PlatformMath" type="StaticBody" parent="Platform10"]
+transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 28, -10, 3 )
+collision_layer = 1048575
+collision_mask = 1048575
+
+[node name="CollisionShape" type="CollisionShape" parent="Platform10/PlatformMath"]
+shape = SubResource( 3 )
+__meta__ = {
+"_edit_lock_": true
+}
+
+[node name="PlatformSprite" type="Sprite" parent="Platform10"]
+scale = Vector2( 0.5, 0.5 )
+texture = ExtResource( 6 )
+script = ExtResource( 7 )
+
+[node name="Platform11" type="Node2D" parent="."]
+position = Vector2( 896, 520.431 )
+z_index = -3992
+script = ExtResource( 4 )
+__meta__ = {
+"_editor_icon": ExtResource( 5 )
+}
+spatial_position = Vector3( 28, -10, 13 )
+
+[node name="PlatformMath" type="StaticBody" parent="Platform11"]
+transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 28, -10, 13 )
+collision_layer = 1048575
+collision_mask = 1048575
+
+[node name="CollisionShape" type="CollisionShape" parent="Platform11/PlatformMath"]
+shape = SubResource( 3 )
+__meta__ = {
+"_edit_lock_": true
+}
+
+[node name="PlatformSprite" type="Sprite" parent="Platform11"]
+scale = Vector2( 0.5, 0.5 )
+texture = ExtResource( 6 )
+script = ExtResource( 7 )
+
+[node name="Platform12" type="Node2D" parent="."]
+position = Vector2( 896, 746.705 )
+z_index = -3988
+script = ExtResource( 4 )
+__meta__ = {
+"_editor_icon": ExtResource( 5 )
+}
+spatial_position = Vector3( 28, -10, 23 )
+
+[node name="PlatformMath" type="StaticBody" parent="Platform12"]
+transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 28, -10, 23 )
+collision_layer = 1048575
+collision_mask = 1048575
+
+[node name="CollisionShape" type="CollisionShape" parent="Platform12/PlatformMath"]
+shape = SubResource( 3 )
+__meta__ = {
+"_edit_lock_": true
+}
+
+[node name="PlatformSprite" type="Sprite" parent="Platform12"]
+scale = Vector2( 0.5, 0.5 )
+texture = ExtResource( 6 )
+script = ExtResource( 7 )
+
+[node name="Platform13" type="Node2D" parent="."]
+position = Vector2( 576, 746.705 )
+z_index = -3990
+script = ExtResource( 4 )
+__meta__ = {
+"_editor_icon": ExtResource( 5 )
+}
+spatial_position = Vector3( 18, -10, 23 )
+
+[node name="PlatformMath" type="StaticBody" parent="Platform13"]
+transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 18, -10, 23 )
+collision_layer = 1048575
+collision_mask = 1048575
+
+[node name="CollisionShape" type="CollisionShape" parent="Platform13/PlatformMath"]
+shape = SubResource( 3 )
+__meta__ = {
+"_edit_lock_": true
+}
+
+[node name="PlatformSprite" type="Sprite" parent="Platform13"]
+scale = Vector2( 0.5, 0.5 )
+texture = ExtResource( 6 )
+script = ExtResource( 7 )
+
+[node name="Platform14" type="Node2D" parent="."]
+position = Vector2( 256, 746.705 )
+z_index = -3996
+script = ExtResource( 4 )
+__meta__ = {
+"_editor_icon": ExtResource( 5 )
+}
+spatial_position = Vector3( 8, -10, 23 )
+
+[node name="PlatformMath" type="StaticBody" parent="Platform14"]
+transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 8, -10, 23 )
+collision_layer = 1048575
+collision_mask = 1048575
+
+[node name="CollisionShape" type="CollisionShape" parent="Platform14/PlatformMath"]
+shape = SubResource( 3 )
+__meta__ = {
+"_edit_lock_": true
+}
+
+[node name="PlatformSprite" type="Sprite" parent="Platform14"]
+scale = Vector2( 0.5, 0.5 )
+texture = ExtResource( 6 )
+script = ExtResource( 7 )
+
+[node name="Platform15" type="Node2D" parent="."]
+position = Vector2( -64, 746.705 )
+z_index = -3998
+script = ExtResource( 4 )
+__meta__ = {
+"_editor_icon": ExtResource( 5 )
+}
+spatial_position = Vector3( -2, -10, 23 )
+
+[node name="PlatformMath" type="StaticBody" parent="Platform15"]
+transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -2, -10, 23 )
+collision_layer = 1048575
+collision_mask = 1048575
+
+[node name="CollisionShape" type="CollisionShape" parent="Platform15/PlatformMath"]
+shape = SubResource( 3 )
+__meta__ = {
+"_edit_lock_": true
+}
+
+[node name="PlatformSprite" type="Sprite" parent="Platform15"]
+scale = Vector2( 0.5, 0.5 )
+texture = ExtResource( 6 )
+script = ExtResource( 7 )
+
+[node name="Platform23" type="Node2D" parent="."]
+position = Vector2( -384, 746.705 )
+z_index = -4000
+script = ExtResource( 4 )
+__meta__ = {
+"_editor_icon": ExtResource( 5 )
+}
+spatial_position = Vector3( -12, -10, 23 )
+
+[node name="PlatformMath" type="StaticBody" parent="Platform23"]
+transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -12, -10, 23 )
+collision_layer = 1048575
+collision_mask = 1048575
+
+[node name="CollisionShape" type="CollisionShape" parent="Platform23/PlatformMath"]
+shape = SubResource( 3 )
+__meta__ = {
+"_edit_lock_": true
+}
+
+[node name="PlatformSprite" type="Sprite" parent="Platform23"]
+scale = Vector2( 0.5, 0.5 )
+texture = ExtResource( 6 )
+script = ExtResource( 7 )
+
+[node name="Platform16" type="Node2D" parent="."]
+position = Vector2( -320, 565.685 )
+z_index = -3980
+script = ExtResource( 4 )
+__meta__ = {
+"_editor_icon": ExtResource( 5 )
+}
+spatial_position = Vector3( -10, -5, 20 )
+
+[node name="PlatformMath" type="StaticBody" parent="Platform16"]
+transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -10, -5, 20 )
+collision_layer = 1048575
+collision_mask = 1048575
+
+[node name="CollisionShape" type="CollisionShape" parent="Platform16/PlatformMath"]
+shape = SubResource( 3 )
+__meta__ = {
+"_edit_lock_": true
+}
+
+[node name="PlatformSprite" type="Sprite" parent="Platform16"]
+scale = Vector2( 0.5, 0.5 )
+texture = ExtResource( 6 )
+script = ExtResource( 7 )
+
+[node name="Platform19" type="Node2D" parent="."]
+position = Vector2( -320, 339.411 )
+z_index = -3986
+script = ExtResource( 4 )
+__meta__ = {
+"_editor_icon": ExtResource( 5 )
+}
+spatial_position = Vector3( -10, -5, 10 )
+
+[node name="PlatformMath" type="StaticBody" parent="Platform19"]
+transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -10, -5, 10 )
+collision_layer = 1048575
+collision_mask = 1048575
+
+[node name="CollisionShape" type="CollisionShape" parent="Platform19/PlatformMath"]
+shape = SubResource( 3 )
+__meta__ = {
+"_edit_lock_": true
+}
+
+[node name="PlatformSprite" type="Sprite" parent="Platform19"]
+scale = Vector2( 0.5, 0.5 )
+texture = ExtResource( 6 )
+script = ExtResource( 7 )
+
+[node name="Platform17" type="Node2D" parent="."]
+position = Vector2( -480, 248.902 )
+z_index = -3964
+script = ExtResource( 4 )
+__meta__ = {
+"_editor_icon": ExtResource( 5 )
+}
+spatial_position = Vector3( -15, -1, 10 )
+
+[node name="PlatformMath" type="StaticBody" parent="Platform17"]
+transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -15, -1, 10 )
+collision_layer = 1048575
+collision_mask = 1048575
+
+[node name="CollisionShape" type="CollisionShape" parent="Platform17/PlatformMath"]
+shape = SubResource( 3 )
+__meta__ = {
+"_edit_lock_": true
+}
+
+[node name="PlatformSprite" type="Sprite" parent="Platform17"]
+scale = Vector2( 0.5, 0.5 )
+texture = ExtResource( 6 )
+script = ExtResource( 7 )
+
+[node name="Platform18" type="Node2D" parent="."]
+position = Vector2( -480, 22.6274 )
+z_index = -3968
+script = ExtResource( 4 )
+__meta__ = {
+"_editor_icon": ExtResource( 5 )
+}
+spatial_position = Vector3( -15, -1, 0 )
+
+[node name="PlatformMath" type="StaticBody" parent="Platform18"]
+transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -15, -1, 0 )
+collision_layer = 1048575
+collision_mask = 1048575
+
+[node name="CollisionShape" type="CollisionShape" parent="Platform18/PlatformMath"]
+shape = SubResource( 3 )
+__meta__ = {
+"_edit_lock_": true
+}
+
+[node name="PlatformSprite" type="Sprite" parent="Platform18"]
+scale = Vector2( 0.5, 0.5 )
+texture = ExtResource( 6 )
+script = ExtResource( 7 )
+
+[node name="YSort25D" type="Node" parent="."]
+script = ExtResource( 8 )
+__meta__ = {
+"_editor_icon": ExtResource( 9 )
+}
+
+[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."]
+stream = ExtResource( 10 )
+volume_db = -20.0
+autoplay = true
diff --git a/misc/2.5d/assets/mr_mrs_robot.ogg b/misc/2.5d/assets/mr_mrs_robot.ogg
new file mode 100644
index 0000000000..76c5e8a5b3
Binary files /dev/null and b/misc/2.5d/assets/mr_mrs_robot.ogg differ
diff --git a/misc/2.5d/assets/mr_mrs_robot.ogg.import b/misc/2.5d/assets/mr_mrs_robot.ogg.import
new file mode 100644
index 0000000000..96d6caf4bb
--- /dev/null
+++ b/misc/2.5d/assets/mr_mrs_robot.ogg.import
@@ -0,0 +1,15 @@
+[remap]
+
+importer="ogg_vorbis"
+type="AudioStreamOGGVorbis"
+path="res://.import/mr_mrs_robot.ogg-04d8a930124c76b878f30fed4f47903c.oggstr"
+
+[deps]
+
+source_file="res://assets/mr_mrs_robot.ogg"
+dest_files=[ "res://.import/mr_mrs_robot.ogg-04d8a930124c76b878f30fed4f47903c.oggstr" ]
+
+[params]
+
+loop=true
+loop_offset=0
diff --git a/misc/2.5d/assets/platform/platform.tscn b/misc/2.5d/assets/platform/platform.tscn
new file mode 100644
index 0000000000..5885e6bbbe
--- /dev/null
+++ b/misc/2.5d/assets/platform/platform.tscn
@@ -0,0 +1,31 @@
+[gd_scene load_steps=6 format=2]
+
+[ext_resource path="res://addons/node25d/node_25d.gd" type="Script" id=1]
+[ext_resource path="res://addons/node25d/icons/node_25d_icon.png" type="Texture" id=2]
+[ext_resource path="res://assets/platform/textures/forty_five.png" type="Texture" id=3]
+[ext_resource path="res://assets/platform/platform_sprite.gd" type="Script" id=4]
+
+[sub_resource type="BoxShape" id=1]
+extents = Vector3( 5, 0.5, 5 )
+
+[node name="Platform" type="Node2D"]
+z_index = -3996
+script = ExtResource( 1 )
+__meta__ = {
+"_editor_icon": ExtResource( 2 )
+}
+
+[node name="PlatformMath" type="StaticBody" parent="."]
+collision_layer = 1048575
+collision_mask = 1048575
+
+[node name="CollisionShape" type="CollisionShape" parent="PlatformMath"]
+shape = SubResource( 1 )
+__meta__ = {
+"_edit_lock_": true
+}
+
+[node name="PlatformSprite" type="Sprite" parent="."]
+scale = Vector2( 0.5, 0.5 )
+texture = ExtResource( 3 )
+script = ExtResource( 4 )
diff --git a/misc/2.5d/assets/platform/platform_sprite.gd b/misc/2.5d/assets/platform/platform_sprite.gd
new file mode 100644
index 0000000000..4b6dcb43cf
--- /dev/null
+++ b/misc/2.5d/assets/platform/platform_sprite.gd
@@ -0,0 +1,39 @@
+tool
+extends Sprite
+
+onready var _forty_five = preload("res://assets/platform/textures/forty_five.png")
+onready var _isometric = preload("res://assets/platform/textures/isometric.png")
+onready var _top_down = preload("res://assets/platform/textures/top_down.png")
+onready var _front_side = preload("res://assets/platform/textures/front_side.png")
+onready var _oblique_y = preload("res://assets/platform/textures/oblique_y.png")
+onready var _oblique_z = preload("res://assets/platform/textures/oblique_z.png")
+
+func _process(_delta):
+ if Input.is_action_pressed("forty_five_mode"):
+ set_view_mode(0)
+ elif Input.is_action_pressed("isometric_mode"):
+ set_view_mode(1)
+ elif Input.is_action_pressed("top_down_mode"):
+ set_view_mode(2)
+ elif Input.is_action_pressed("front_side_mode"):
+ set_view_mode(3)
+ elif Input.is_action_pressed("oblique_y_mode"):
+ set_view_mode(4)
+ elif Input.is_action_pressed("oblique_z_mode"):
+ set_view_mode(5)
+
+
+func set_view_mode(view_mode_index):
+ match view_mode_index:
+ 0: # 45 Degrees
+ texture = _forty_five;
+ 1: # Isometric
+ texture = _isometric
+ 2: # Top Down
+ texture = _top_down
+ 3: # Front Side
+ texture = _front_side
+ 4: # Oblique Y
+ texture = _oblique_y
+ 5: # Oblique Z
+ texture = _oblique_z
diff --git a/misc/2.5d/assets/platform/textures/forty_five.png b/misc/2.5d/assets/platform/textures/forty_five.png
new file mode 100644
index 0000000000..e4422bb3d1
Binary files /dev/null and b/misc/2.5d/assets/platform/textures/forty_five.png differ
diff --git a/misc/2.5d/assets/platform/textures/forty_five.png.import b/misc/2.5d/assets/platform/textures/forty_five.png.import
new file mode 100644
index 0000000000..3fbd8972a6
--- /dev/null
+++ b/misc/2.5d/assets/platform/textures/forty_five.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/forty_five.png-d90cd8ed1241c4a5270d87a83aafe24d.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/platform/textures/forty_five.png"
+dest_files=[ "res://.import/forty_five.png-d90cd8ed1241c4a5270d87a83aafe24d.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/assets/platform/textures/fortyfive.png.import b/misc/2.5d/assets/platform/textures/fortyfive.png.import
new file mode 100644
index 0000000000..f2b0448b5b
--- /dev/null
+++ b/misc/2.5d/assets/platform/textures/fortyfive.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/fortyfive.png-ed5c66b01afe0b53153c3d09ee5b6584.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/platform/textures/fortyfive.png"
+dest_files=[ "res://.import/fortyfive.png-ed5c66b01afe0b53153c3d09ee5b6584.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/assets/platform/textures/front_side.png b/misc/2.5d/assets/platform/textures/front_side.png
new file mode 100644
index 0000000000..aa3bdd7858
Binary files /dev/null and b/misc/2.5d/assets/platform/textures/front_side.png differ
diff --git a/misc/2.5d/assets/platform/textures/front_side.png.import b/misc/2.5d/assets/platform/textures/front_side.png.import
new file mode 100644
index 0000000000..eb4230ffbe
--- /dev/null
+++ b/misc/2.5d/assets/platform/textures/front_side.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/front_side.png-057b43bb7270572907c729580068368b.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/platform/textures/front_side.png"
+dest_files=[ "res://.import/front_side.png-057b43bb7270572907c729580068368b.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/assets/platform/textures/frontside.png.import b/misc/2.5d/assets/platform/textures/frontside.png.import
new file mode 100644
index 0000000000..00089ecae2
--- /dev/null
+++ b/misc/2.5d/assets/platform/textures/frontside.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/frontside.png-59d804a7bfdefb2229c160b22085f140.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/platform/textures/frontside.png"
+dest_files=[ "res://.import/frontside.png-59d804a7bfdefb2229c160b22085f140.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/assets/platform/textures/isometric.png b/misc/2.5d/assets/platform/textures/isometric.png
new file mode 100644
index 0000000000..7045d86bf7
Binary files /dev/null and b/misc/2.5d/assets/platform/textures/isometric.png differ
diff --git a/misc/2.5d/assets/platform/textures/isometric.png.import b/misc/2.5d/assets/platform/textures/isometric.png.import
new file mode 100644
index 0000000000..e722a8d6e2
--- /dev/null
+++ b/misc/2.5d/assets/platform/textures/isometric.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/isometric.png-364f65b60f600b10cfb048c20ea82124.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/platform/textures/isometric.png"
+dest_files=[ "res://.import/isometric.png-364f65b60f600b10cfb048c20ea82124.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/assets/platform/textures/obliqueY.png.import b/misc/2.5d/assets/platform/textures/obliqueY.png.import
new file mode 100644
index 0000000000..9ef359977a
--- /dev/null
+++ b/misc/2.5d/assets/platform/textures/obliqueY.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/obliqueY.png-835238e1a682fa9039cff7ef5cfcacd4.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/platform/textures/obliqueY.png"
+dest_files=[ "res://.import/obliqueY.png-835238e1a682fa9039cff7ef5cfcacd4.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/assets/platform/textures/obliqueZ.png.import b/misc/2.5d/assets/platform/textures/obliqueZ.png.import
new file mode 100644
index 0000000000..ac8ea96549
--- /dev/null
+++ b/misc/2.5d/assets/platform/textures/obliqueZ.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/obliqueZ.png-ccf2b8e0c4fa1369940c3976d1e9a334.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/platform/textures/obliqueZ.png"
+dest_files=[ "res://.import/obliqueZ.png-ccf2b8e0c4fa1369940c3976d1e9a334.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/assets/platform/textures/oblique_y.png b/misc/2.5d/assets/platform/textures/oblique_y.png
new file mode 100644
index 0000000000..1b0b0d24bf
Binary files /dev/null and b/misc/2.5d/assets/platform/textures/oblique_y.png differ
diff --git a/misc/2.5d/assets/platform/textures/oblique_y.png.import b/misc/2.5d/assets/platform/textures/oblique_y.png.import
new file mode 100644
index 0000000000..d7c1330f26
--- /dev/null
+++ b/misc/2.5d/assets/platform/textures/oblique_y.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/oblique_y.png-ed89b3ef35707993300443a84f7ebbd1.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/platform/textures/oblique_y.png"
+dest_files=[ "res://.import/oblique_y.png-ed89b3ef35707993300443a84f7ebbd1.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/assets/platform/textures/oblique_z.png b/misc/2.5d/assets/platform/textures/oblique_z.png
new file mode 100644
index 0000000000..a859ccb6e1
Binary files /dev/null and b/misc/2.5d/assets/platform/textures/oblique_z.png differ
diff --git a/misc/2.5d/assets/platform/textures/oblique_z.png.import b/misc/2.5d/assets/platform/textures/oblique_z.png.import
new file mode 100644
index 0000000000..044a1c0f38
--- /dev/null
+++ b/misc/2.5d/assets/platform/textures/oblique_z.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/oblique_z.png-270f041a55370c5ba68850a072597e97.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/platform/textures/oblique_z.png"
+dest_files=[ "res://.import/oblique_z.png-270f041a55370c5ba68850a072597e97.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/assets/platform/textures/top_down.png b/misc/2.5d/assets/platform/textures/top_down.png
new file mode 100644
index 0000000000..53f9b83ee7
Binary files /dev/null and b/misc/2.5d/assets/platform/textures/top_down.png differ
diff --git a/misc/2.5d/assets/platform/textures/top_down.png.import b/misc/2.5d/assets/platform/textures/top_down.png.import
new file mode 100644
index 0000000000..fccd60c82a
--- /dev/null
+++ b/misc/2.5d/assets/platform/textures/top_down.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/top_down.png-3df3f4c204d6337fdc9aa208196ed940.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/platform/textures/top_down.png"
+dest_files=[ "res://.import/top_down.png-3df3f4c204d6337fdc9aa208196ed940.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/assets/platform/textures/topdown.png.import b/misc/2.5d/assets/platform/textures/topdown.png.import
new file mode 100644
index 0000000000..65f68479b4
--- /dev/null
+++ b/misc/2.5d/assets/platform/textures/topdown.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/topdown.png-66f9d3553daec51a7ec6739d98ef44ef.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/platform/textures/topdown.png"
+dest_files=[ "res://.import/topdown.png-66f9d3553daec51a7ec6739d98ef44ef.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/assets/player/player_25d.tscn b/misc/2.5d/assets/player/player_25d.tscn
new file mode 100644
index 0000000000..eec8f300fb
--- /dev/null
+++ b/misc/2.5d/assets/player/player_25d.tscn
@@ -0,0 +1,41 @@
+[gd_scene load_steps=7 format=2]
+
+[ext_resource path="res://addons/node25d/node_25d.gd" type="Script" id=1]
+[ext_resource path="res://addons/node25d/icons/node_25d_icon.png" type="Texture" id=2]
+[ext_resource path="res://assets/player/player_math_25d.gd" type="Script" id=3]
+[ext_resource path="res://assets/player/textures/jump.png" type="Texture" id=4]
+[ext_resource path="res://assets/player/player_sprite.gd" type="Script" id=5]
+
+[sub_resource type="BoxShape" id=1]
+extents = Vector3( 0.5, 1, 0.5 )
+
+[node name="Player25D" type="Node2D"]
+position = Vector2( 0, -226.274 )
+z_index = 100
+script = ExtResource( 1 )
+__meta__ = {
+"_editor_icon": ExtResource( 2 )
+}
+spatial_position = Vector3( 0, 10, 0 )
+
+[node name="PlayerMath25D" type="KinematicBody" parent="."]
+transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 10, 0 )
+script = ExtResource( 3 )
+
+[node name="CollisionShape" type="CollisionShape" parent="PlayerMath25D"]
+shape = SubResource( 1 )
+__meta__ = {
+"_edit_lock_": true
+}
+
+[node name="PlayerSprite" type="Sprite" parent="."]
+scale = Vector2( 1, 0.75 )
+z_index = 1
+texture = ExtResource( 4 )
+offset = Vector2( 0, 4 )
+vframes = 5
+hframes = 2
+script = ExtResource( 5 )
+
+[node name="PlayerCamera" type="Camera2D" parent="PlayerSprite"]
+current = true
diff --git a/misc/2.5d/assets/player/player_math_25d.gd b/misc/2.5d/assets/player/player_math_25d.gd
new file mode 100644
index 0000000000..adf0e6fce4
--- /dev/null
+++ b/misc/2.5d/assets/player/player_math_25d.gd
@@ -0,0 +1,58 @@
+# Handles Player-specific behavior like moving. We calculate such things with KinematicBody.
+extends KinematicBody
+class_name PlayerMath25D # No icon necessary
+
+var vertical_speed := 0.0
+var isometric_controls := true
+onready var _parent_node25d: Node25D = get_parent()
+
+func _process(delta):
+ if Input.is_action_pressed("exit"):
+ get_tree().quit()
+
+ if Input.is_action_just_pressed("view_cube_demo"):
+ #warning-ignore:return_value_discarded
+ get_tree().change_scene("res://assets/cube/cube.tscn")
+ return
+
+ if Input.is_action_just_pressed("toggle_isometric_controls"):
+ isometric_controls = !isometric_controls
+ if Input.is_action_just_pressed("reset_position"):
+ transform = Transform(Basis(), Vector3.UP * 10)
+ vertical_speed = 0
+ else:
+ _horizontal_movement(delta)
+ _vertical_movement(delta)
+
+
+# Checks WASD and Shift for horizontal movement via move_and_slide.
+func _horizontal_movement(delta):
+ var localX = Vector3.RIGHT
+ var localZ = Vector3.BACK
+
+ if isometric_controls && is_equal_approx(Node25D.SCALE * 0.86602540378, _parent_node25d.get_basis()[0].x):
+ localX = Vector3(0.70710678118, 0, -0.70710678118)
+ localZ = Vector3(0.70710678118, 0, 0.70710678118)
+
+ # Gather player input and add directional movement to a Vector3 variable.
+ var move_dir = Vector3()
+ move_dir += localX * (Input.get_action_strength("move_right") - Input.get_action_strength("move_left"))
+ move_dir += localZ * (Input.get_action_strength("move_back") - Input.get_action_strength("move_forward"))
+
+ move_dir = move_dir.normalized() * delta * 600;
+ if Input.is_action_pressed("movement_modifier"):
+ move_dir /= 2;
+
+ #warning-ignore:return_value_discarded
+ move_and_slide(move_dir)
+
+
+# Checks Jump and applies gravity and vertical speed via move_and_collide.
+func _vertical_movement(delta):
+ var localY = Vector3.UP
+ if Input.is_action_just_pressed("jump"):
+ vertical_speed = 1.25
+ vertical_speed -= delta * 5 # Gravity
+ var k = move_and_collide(localY * vertical_speed);
+ if k != null:
+ vertical_speed = 0
diff --git a/misc/2.5d/assets/player/player_sprite.gd b/misc/2.5d/assets/player/player_sprite.gd
new file mode 100644
index 0000000000..aef9362edc
--- /dev/null
+++ b/misc/2.5d/assets/player/player_sprite.gd
@@ -0,0 +1,143 @@
+tool
+extends Sprite
+
+onready var _stand = preload("res://assets/player/textures/stand.png")
+onready var _jump = preload("res://assets/player/textures/jump.png")
+onready var _run = preload("res://assets/player/textures/run.png")
+
+const FRAMERATE = 15
+
+var _direction := 0
+var _progress := 0.0
+var _parent_node25d: Node25D
+var _parent_math: PlayerMath25D
+
+func _ready():
+ _parent_node25d = get_parent()
+ _parent_math = _parent_node25d.get_child(0)
+
+
+func _process(delta):
+ if Engine.is_editor_hint():
+ return # Don't run this in the editor.
+
+ _sprite_basis()
+ var movement = _check_movement() # Always run to get direction, but don't always use return bool.
+
+ # Test-only move and collide, check if the player is on the ground.
+ var k = _parent_math.move_and_collide(Vector3.DOWN * 10 * delta, true, true, true)
+ if k != null:
+ if movement:
+ hframes = 6
+ texture = _run
+ if (Input.is_action_pressed("movement_modifier")):
+ delta /= 2
+ _progress = fmod((_progress + FRAMERATE * delta), 6)
+ frame = _direction * 6 + int(_progress)
+ else:
+ hframes = 1
+ texture = _stand
+ _progress = 0
+ frame = _direction
+ else:
+ hframes = 2
+ texture = _jump
+ _progress = 0
+ var jumping = 1 if _parent_math.vertical_speed < 0 else 0
+ frame = _direction * 2 + jumping
+
+
+func set_view_mode(view_mode_index):
+ match view_mode_index:
+ 0: # 45 Degrees
+ transform.x = Vector2(1, 0)
+ transform.y = Vector2(0, 0.75)
+ 1: # Isometric
+ transform.x = Vector2(1, 0)
+ transform.y = Vector2(0, 1)
+ 2: # Top Down
+ transform.x = Vector2(1, 0)
+ transform.y = Vector2(0, 0.5)
+ 3: # Front Side
+ transform.x = Vector2(1, 0)
+ transform.y = Vector2(0, 1)
+ 4: # Oblique Y
+ transform.x = Vector2(1, 0)
+ transform.y = Vector2(0.75, 0.75)
+ 5: # Oblique Z
+ transform.x = Vector2(1, 0.25)
+ transform.y = Vector2(0, 1)
+
+
+# Change the 2D basis of the sprite to try and make it "fit" multiple view modes.
+func _sprite_basis():
+ if Input.is_action_pressed("forty_five_mode"):
+ set_view_mode(0)
+ elif Input.is_action_pressed("isometric_mode"):
+ set_view_mode(1)
+ elif Input.is_action_pressed("top_down_mode"):
+ set_view_mode(2)
+ elif Input.is_action_pressed("front_side_mode"):
+ set_view_mode(3)
+ elif Input.is_action_pressed("oblique_y_mode"):
+ set_view_mode(4)
+ elif Input.is_action_pressed("oblique_z_mode"):
+ set_view_mode(5)
+
+
+# This method returns a bool but if true it also outputs to the direction variable.
+func _check_movement() -> bool:
+ # Gather player input and store movement to these int variables. Note: These indeed have to be integers.
+ var x := 0
+ var z := 0
+
+ if Input.is_action_pressed("move_right"):
+ x += 1
+ if Input.is_action_pressed("move_left"):
+ x -= 1
+ if Input.is_action_pressed("move_forward"):
+ z -= 1
+ if Input.is_action_pressed("move_back"):
+ z += 1
+
+ # Check for isometric controls and add more to movement accordingly.
+ # For efficiency, only check the X axis since this X axis value isn't used anywhere else.
+ if !_parent_math.isometric_controls && is_equal_approx(Node25D.SCALE * 0.86602540378, _parent_node25d.get_basis()[0].x):
+ if Input.is_action_pressed("move_right"):
+ z += 1
+ if Input.is_action_pressed("move_left"):
+ z -= 1
+ if Input.is_action_pressed("move_forward"):
+ x += 1
+ if Input.is_action_pressed("move_back"):
+ x -= 1
+
+ # Set the direction based on which inputs were pressed.
+ if x == 0:
+ if z == 0:
+ return false # No movement.
+ elif z > 0:
+ _direction = 0
+ else:
+ _direction = 4
+ elif x > 0:
+ if z == 0:
+ _direction = 2
+ flip_h = true
+ elif z > 0:
+ _direction = 1
+ flip_h = true
+ else:
+ _direction = 3
+ flip_h = true
+ else:
+ if z == 0:
+ _direction = 2
+ flip_h = false
+ elif z > 0:
+ _direction = 1
+ flip_h = false
+ else:
+ _direction = 3
+ flip_h = false
+ return true # There is movement.
diff --git a/misc/2.5d/assets/player/textures/jump.png b/misc/2.5d/assets/player/textures/jump.png
new file mode 100755
index 0000000000..809467dadd
Binary files /dev/null and b/misc/2.5d/assets/player/textures/jump.png differ
diff --git a/misc/2.5d/assets/player/textures/jump.png.import b/misc/2.5d/assets/player/textures/jump.png.import
new file mode 100644
index 0000000000..bdbb55e431
--- /dev/null
+++ b/misc/2.5d/assets/player/textures/jump.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/jump.png-ee91d86ec39d8c1dde239a382e843e86.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/player/textures/jump.png"
+dest_files=[ "res://.import/jump.png-ee91d86ec39d8c1dde239a382e843e86.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/assets/player/textures/run.png b/misc/2.5d/assets/player/textures/run.png
new file mode 100644
index 0000000000..900742aea3
Binary files /dev/null and b/misc/2.5d/assets/player/textures/run.png differ
diff --git a/misc/2.5d/assets/player/textures/run.png.import b/misc/2.5d/assets/player/textures/run.png.import
new file mode 100644
index 0000000000..90a63b5175
--- /dev/null
+++ b/misc/2.5d/assets/player/textures/run.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/run.png-6110949046e0632be1a9b1c8ac504217.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/player/textures/run.png"
+dest_files=[ "res://.import/run.png-6110949046e0632be1a9b1c8ac504217.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/assets/player/textures/stand.png b/misc/2.5d/assets/player/textures/stand.png
new file mode 100644
index 0000000000..1fbc364fda
Binary files /dev/null and b/misc/2.5d/assets/player/textures/stand.png differ
diff --git a/misc/2.5d/assets/player/textures/stand.png.import b/misc/2.5d/assets/player/textures/stand.png.import
new file mode 100644
index 0000000000..8525343755
--- /dev/null
+++ b/misc/2.5d/assets/player/textures/stand.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/stand.png-4d65e60dbd5f40d1f70da6aa2507ebe3.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/player/textures/stand.png"
+dest_files=[ "res://.import/stand.png-4d65e60dbd5f40d1f70da6aa2507ebe3.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/assets/shadow/shadow_25d.tscn b/misc/2.5d/assets/shadow/shadow_25d.tscn
new file mode 100644
index 0000000000..ea6164851a
--- /dev/null
+++ b/misc/2.5d/assets/shadow/shadow_25d.tscn
@@ -0,0 +1,40 @@
+[gd_scene load_steps=8 format=2]
+
+[ext_resource path="res://addons/node25d/node_25d.gd" type="Script" id=1]
+[ext_resource path="res://addons/node25d/icons/node_25d_icon.png" type="Texture" id=2]
+[ext_resource path="res://addons/node25d/shadow_math_25d.gd" type="Script" id=3]
+[ext_resource path="res://addons/node25d/icons/shadow_math_25d_icon.png" type="Texture" id=4]
+[ext_resource path="res://assets/shadow/textures/forty_five.png" type="Texture" id=5]
+[ext_resource path="res://assets/shadow/shadow_sprite.gd" type="Script" id=6]
+
+[sub_resource type="BoxShape" id=1]
+extents = Vector3( 0.5, 0.001, 0.5 )
+
+[node name="Shadow25D" type="Node2D"]
+visible = false
+position = Vector2( 0, 22401.1 )
+script = ExtResource( 1 )
+__meta__ = {
+"_editor_icon": ExtResource( 2 )
+}
+spatial_position = Vector3( 0, -990, 0 )
+
+[node name="ShadowMath25D" type="KinematicBody" parent="."]
+transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -990, 0 )
+collision_layer = 16
+collision_mask = 16
+script = ExtResource( 3 )
+__meta__ = {
+"_editor_icon": ExtResource( 4 )
+}
+
+[node name="CollisionShape" type="CollisionShape" parent="ShadowMath25D"]
+shape = SubResource( 1 )
+__meta__ = {
+"_edit_lock_": true
+}
+
+[node name="ShadowSprite" type="Sprite" parent="."]
+scale = Vector2( 0.5, 0.5 )
+texture = ExtResource( 5 )
+script = ExtResource( 6 )
diff --git a/misc/2.5d/assets/shadow/shadow_sprite.gd b/misc/2.5d/assets/shadow/shadow_sprite.gd
new file mode 100644
index 0000000000..f3e4b55270
--- /dev/null
+++ b/misc/2.5d/assets/shadow/shadow_sprite.gd
@@ -0,0 +1,39 @@
+tool
+extends Sprite
+
+onready var _forty_five = preload("res://assets/shadow/textures/forty_five.png")
+onready var _isometric = preload("res://assets/shadow/textures/isometric.png")
+onready var _top_down = preload("res://assets/shadow/textures/top_down.png")
+onready var _front_side = preload("res://assets/shadow/textures/front_side.png")
+onready var _oblique_y = preload("res://assets/shadow/textures/oblique_y.png")
+onready var _oblique_z = preload("res://assets/shadow/textures/oblique_z.png")
+
+func _process(_delta):
+ if Input.is_action_pressed("forty_five_mode"):
+ set_view_mode(0)
+ elif Input.is_action_pressed("isometric_mode"):
+ set_view_mode(1)
+ elif Input.is_action_pressed("top_down_mode"):
+ set_view_mode(2)
+ elif Input.is_action_pressed("front_side_mode"):
+ set_view_mode(3)
+ elif Input.is_action_pressed("oblique_y_mode"):
+ set_view_mode(4)
+ elif Input.is_action_pressed("oblique_z_mode"):
+ set_view_mode(5)
+
+
+func set_view_mode(view_mode_index):
+ match view_mode_index:
+ 0: # 45 Degrees
+ texture = _forty_five;
+ 1: # Isometric
+ texture = _isometric
+ 2: # Top Down
+ texture = _top_down
+ 3: # Front Side
+ texture = _front_side
+ 4: # Oblique Y
+ texture = _oblique_y
+ 5: # Oblique Z
+ texture = _oblique_z
diff --git a/misc/2.5d/assets/shadow/textures/forty_five.png b/misc/2.5d/assets/shadow/textures/forty_five.png
new file mode 100644
index 0000000000..ab28eea280
Binary files /dev/null and b/misc/2.5d/assets/shadow/textures/forty_five.png differ
diff --git a/misc/2.5d/assets/shadow/textures/forty_five.png.import b/misc/2.5d/assets/shadow/textures/forty_five.png.import
new file mode 100644
index 0000000000..cd931cd007
--- /dev/null
+++ b/misc/2.5d/assets/shadow/textures/forty_five.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/forty_five.png-22dcfa54db51531b3612f686997a3fbe.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/shadow/textures/forty_five.png"
+dest_files=[ "res://.import/forty_five.png-22dcfa54db51531b3612f686997a3fbe.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/assets/shadow/textures/fortyfive.png.import b/misc/2.5d/assets/shadow/textures/fortyfive.png.import
new file mode 100644
index 0000000000..0a1e44de89
--- /dev/null
+++ b/misc/2.5d/assets/shadow/textures/fortyfive.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/fortyfive.png-efb54d3840c2ab97f9652a523a4e1e58.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/shadow/textures/fortyfive.png"
+dest_files=[ "res://.import/fortyfive.png-efb54d3840c2ab97f9652a523a4e1e58.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/assets/shadow/textures/front_side.png b/misc/2.5d/assets/shadow/textures/front_side.png
new file mode 100644
index 0000000000..8af59aa74a
Binary files /dev/null and b/misc/2.5d/assets/shadow/textures/front_side.png differ
diff --git a/misc/2.5d/assets/shadow/textures/front_side.png.import b/misc/2.5d/assets/shadow/textures/front_side.png.import
new file mode 100644
index 0000000000..d43de4b075
--- /dev/null
+++ b/misc/2.5d/assets/shadow/textures/front_side.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/front_side.png-1470842d27848ecf4de63924b0b98f42.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/shadow/textures/front_side.png"
+dest_files=[ "res://.import/front_side.png-1470842d27848ecf4de63924b0b98f42.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/assets/shadow/textures/frontside.png.import b/misc/2.5d/assets/shadow/textures/frontside.png.import
new file mode 100644
index 0000000000..f692468b6b
--- /dev/null
+++ b/misc/2.5d/assets/shadow/textures/frontside.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/frontside.png-aab37f0cda9f5e8056a5178bf22351fb.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/shadow/textures/frontside.png"
+dest_files=[ "res://.import/frontside.png-aab37f0cda9f5e8056a5178bf22351fb.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/assets/shadow/textures/isometric.png b/misc/2.5d/assets/shadow/textures/isometric.png
new file mode 100644
index 0000000000..3b50ae08fb
Binary files /dev/null and b/misc/2.5d/assets/shadow/textures/isometric.png differ
diff --git a/misc/2.5d/assets/shadow/textures/isometric.png.import b/misc/2.5d/assets/shadow/textures/isometric.png.import
new file mode 100644
index 0000000000..9c56cf1f70
--- /dev/null
+++ b/misc/2.5d/assets/shadow/textures/isometric.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/isometric.png-1a91c869806816b66a8fb886d4801f31.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/shadow/textures/isometric.png"
+dest_files=[ "res://.import/isometric.png-1a91c869806816b66a8fb886d4801f31.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/assets/shadow/textures/oblique_y.png b/misc/2.5d/assets/shadow/textures/oblique_y.png
new file mode 100644
index 0000000000..f48cf507ec
Binary files /dev/null and b/misc/2.5d/assets/shadow/textures/oblique_y.png differ
diff --git a/misc/2.5d/assets/shadow/textures/oblique_y.png.import b/misc/2.5d/assets/shadow/textures/oblique_y.png.import
new file mode 100644
index 0000000000..eccac431d8
--- /dev/null
+++ b/misc/2.5d/assets/shadow/textures/oblique_y.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/oblique_y.png-47d60a179a2cdeff15364f0e389e6008.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/shadow/textures/oblique_y.png"
+dest_files=[ "res://.import/oblique_y.png-47d60a179a2cdeff15364f0e389e6008.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/assets/shadow/textures/oblique_z.png b/misc/2.5d/assets/shadow/textures/oblique_z.png
new file mode 100644
index 0000000000..0df5de9db0
Binary files /dev/null and b/misc/2.5d/assets/shadow/textures/oblique_z.png differ
diff --git a/misc/2.5d/assets/shadow/textures/oblique_z.png.import b/misc/2.5d/assets/shadow/textures/oblique_z.png.import
new file mode 100644
index 0000000000..e781a965eb
--- /dev/null
+++ b/misc/2.5d/assets/shadow/textures/oblique_z.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/oblique_z.png-d8378bf8b95f890e76162d62a82022de.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/shadow/textures/oblique_z.png"
+dest_files=[ "res://.import/oblique_z.png-d8378bf8b95f890e76162d62a82022de.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/assets/shadow/textures/top_down.png b/misc/2.5d/assets/shadow/textures/top_down.png
new file mode 100644
index 0000000000..5cf9a46f48
Binary files /dev/null and b/misc/2.5d/assets/shadow/textures/top_down.png differ
diff --git a/misc/2.5d/assets/shadow/textures/top_down.png.import b/misc/2.5d/assets/shadow/textures/top_down.png.import
new file mode 100644
index 0000000000..1817944c99
--- /dev/null
+++ b/misc/2.5d/assets/shadow/textures/top_down.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/top_down.png-a3a98721249636eff54d8113d6075229.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/shadow/textures/top_down.png"
+dest_files=[ "res://.import/top_down.png-a3a98721249636eff54d8113d6075229.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/assets/shadow/textures/topdown.png.import b/misc/2.5d/assets/shadow/textures/topdown.png.import
new file mode 100644
index 0000000000..c618104ec5
--- /dev/null
+++ b/misc/2.5d/assets/shadow/textures/topdown.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/topdown.png-967f612d383afa56cee62320ceaf8a99.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/shadow/textures/topdown.png"
+dest_files=[ "res://.import/topdown.png-967f612d383afa56cee62320ceaf8a99.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/assets/ui/control_hints.gd b/misc/2.5d/assets/ui/control_hints.gd
new file mode 100644
index 0000000000..5f8d21f74e
--- /dev/null
+++ b/misc/2.5d/assets/ui/control_hints.gd
@@ -0,0 +1,5 @@
+extends Control
+
+func _process(_delta):
+ if Input.is_action_just_pressed("toggle_control_hints"):
+ visible = !visible
diff --git a/misc/2.5d/assets/ui/overlay.tscn b/misc/2.5d/assets/ui/overlay.tscn
new file mode 100644
index 0000000000..cc5d123f4f
--- /dev/null
+++ b/misc/2.5d/assets/ui/overlay.tscn
@@ -0,0 +1,28 @@
+[gd_scene load_steps=2 format=2]
+
+[ext_resource path="res://assets/ui/control_hints.gd" type="Script" id=1]
+
+[node name="Overlay" type="CanvasLayer"]
+
+[node name="ControlHints" type="CenterContainer" parent="."]
+anchor_right = 1.0
+margin_bottom = 200.0
+script = ExtResource( 1 )
+
+[node name="Label" type="Label" parent="ControlHints"]
+margin_left = 348.0
+margin_top = 25.0
+margin_right = 1251.0
+margin_bottom = 175.0
+rect_min_size = Vector2( 500, 50 )
+text = "
+Controls: WASD to move, Space to jump, R to reset, Shift to walk, T to toggle isometric controls, C to view cube demo, Tab to toggle hints.
+
+UIOJKL to change view mode. U = Forty Five deg, I = Isometric,
+O = Top Down, J = Front Side, K = Oblique Y, L = Oblique Z
+
+Not every view mode is meant to be good, it's just to showcase what the system can do.
+In actual games, shadows, resizing, parallax, and other hints of depth could be added to make the world seem more 3D.
+"
+align = 1
+valign = 1
diff --git a/misc/2.5d/assets/ui/overlay_cube.tscn b/misc/2.5d/assets/ui/overlay_cube.tscn
new file mode 100644
index 0000000000..4cd80f1e80
--- /dev/null
+++ b/misc/2.5d/assets/ui/overlay_cube.tscn
@@ -0,0 +1,28 @@
+[gd_scene load_steps=2 format=2]
+
+[ext_resource path="res://assets/ui/control_hints.gd" type="Script" id=1]
+
+[node name="Overlay" type="CanvasLayer"]
+
+[node name="ControlHints" type="CenterContainer" parent="."]
+anchor_right = 1.0
+margin_bottom = 200.0
+script = ExtResource( 1 )
+
+[node name="Label" type="Label" parent="ControlHints"]
+margin_left = 416.0
+margin_top = 25.0
+margin_right = 1183.0
+margin_bottom = 175.0
+rect_min_size = Vector2( 500, 50 )
+text = "
+Controls: WASDQE to rotate, R to reset, C to return to the world, Tab to toggle hints.
+
+UIOKL to change view mode. U = Forty Five deg, I = Isometric,
+O = Top Down, K = Oblique Y, L = Oblique Z
+
+Not every view mode is meant to be good, it's just to showcase what the system can do.
+In actual games, shadows, resizing, parallax, and other hints of depth could be added to make the world seem more 3D.
+"
+align = 1
+valign = 1
diff --git a/misc/2.5d/default_env.tres b/misc/2.5d/default_env.tres
new file mode 100644
index 0000000000..20207a4aa2
--- /dev/null
+++ b/misc/2.5d/default_env.tres
@@ -0,0 +1,7 @@
+[gd_resource type="Environment" load_steps=2 format=2]
+
+[sub_resource type="ProceduralSky" id=1]
+
+[resource]
+background_mode = 2
+background_sky = SubResource( 1 )
diff --git a/misc/2.5d/icon.png b/misc/2.5d/icon.png
new file mode 100644
index 0000000000..d8efa1a073
Binary files /dev/null and b/misc/2.5d/icon.png differ
diff --git a/misc/2.5d/icon.png.import b/misc/2.5d/icon.png.import
new file mode 100644
index 0000000000..96cbf4629a
--- /dev/null
+++ b/misc/2.5d/icon.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://icon.png"
+dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/misc/2.5d/project.godot b/misc/2.5d/project.godot
new file mode 100644
index 0000000000..a46f1aef09
--- /dev/null
+++ b/misc/2.5d/project.godot
@@ -0,0 +1,196 @@
+; Engine configuration file.
+; It's best edited using the editor UI and not directly,
+; since the parameters that go here are not all obvious.
+;
+; Format:
+; [section] ; section goes between []
+; param=value ; assign values to parameters
+
+config_version=4
+
+_global_script_classes=[ {
+"base": "Node2D",
+"class": "Node25D",
+"language": "GDScript",
+"path": "res://addons/node25d/node_25d.gd"
+}, {
+"base": "KinematicBody",
+"class": "PlayerMath25D",
+"language": "GDScript",
+"path": "res://assets/player/player_math_25d.gd"
+}, {
+"base": "KinematicBody",
+"class": "ShadowMath25D",
+"language": "GDScript",
+"path": "res://addons/node25d/shadow_math_25d.gd"
+}, {
+"base": "Node",
+"class": "YSort25D",
+"language": "GDScript",
+"path": "res://addons/node25d/y_sort_25d.gd"
+} ]
+_global_script_class_icons={
+"Node25D": "res://addons/node25d/icons/node_25d_icon.png",
+"PlayerMath25D": "",
+"ShadowMath25D": "res://addons/node25d/icons/shadow_math_25d_icon.png",
+"YSort25D": "res://addons/node25d/icons/y_sort_25d_icon.png"
+}
+
+[application]
+
+config/name="2.5D Demo (GDScript)"
+run/main_scene="res://assets/demo_scene.tscn"
+config/icon="res://icon.png"
+
+[display]
+
+window/size/width=1600
+window/size/height=900
+
+[editor_plugins]
+
+enabled=PoolStringArray( "node25d" )
+
+[input]
+
+move_right={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":68,"unicode":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777233,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":1.0,"script":null)
+ ]
+}
+move_left={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":65,"unicode":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777231,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":0,"axis_value":-1.0,"script":null)
+ ]
+}
+move_forward={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":87,"unicode":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777232,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":-1.0,"script":null)
+ ]
+}
+move_back={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":83,"unicode":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777234,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":1.0,"script":null)
+ ]
+}
+movement_modifier={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777237,"unicode":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777348,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":1,"pressure":0.0,"pressed":false,"script":null)
+ ]
+}
+jump={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":32,"unicode":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777350,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":0,"pressure":0.0,"pressed":false,"script":null)
+ ]
+}
+reset_position={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":82,"unicode":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777222,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":3,"pressure":0.0,"pressed":false,"script":null)
+ ]
+}
+forty_five_mode={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":85,"unicode":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777354,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":13,"pressure":0.0,"pressed":false,"script":null)
+ ]
+}
+isometric_mode={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":73,"unicode":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777355,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":15,"pressure":0.0,"pressed":false,"script":null)
+ ]
+}
+top_down_mode={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":79,"unicode":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777356,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":12,"pressure":0.0,"pressed":false,"script":null)
+ ]
+}
+front_side_mode={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":74,"unicode":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777351,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":14,"pressure":0.0,"pressed":false,"script":null)
+ ]
+}
+oblique_y_mode={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":75,"unicode":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777352,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":4,"pressure":0.0,"pressed":false,"script":null)
+ ]
+}
+oblique_z_mode={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":76,"unicode":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777353,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":5,"pressure":0.0,"pressed":false,"script":null)
+ ]
+}
+toggle_isometric_controls={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":84,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":8,"pressure":0.0,"pressed":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777349,"unicode":0,"echo":false,"script":null)
+ ]
+}
+toggle_control_hints={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777218,"unicode":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777347,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":10,"pressure":0.0,"pressed":false,"script":null)
+ ]
+}
+move_clockwise={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":69,"unicode":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777359,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":7,"pressure":0.0,"pressed":false,"script":null)
+ ]
+}
+move_counterclockwise={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":81,"unicode":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777357,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":6,"pressure":0.0,"pressed":false,"script":null)
+ ]
+}
+view_cube_demo={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":67,"unicode":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777358,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":2,"pressure":0.0,"pressed":false,"script":null)
+ ]
+}
+exit={
+"deadzone": 0.5,
+"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777217,"unicode":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":11,"pressure":0.0,"pressed":false,"script":null)
+ ]
+}
+
+[physics]
+
+3d/physics_engine="Bullet"
+
+[rendering]
+
+quality/driver/driver_name="GLES2"
+environment/default_environment="res://default_env.tres"
diff --git a/misc/2.5d/screenshots/.gdignore b/misc/2.5d/screenshots/.gdignore
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/misc/2.5d/screenshots/cube.png b/misc/2.5d/screenshots/cube.png
new file mode 100644
index 0000000000..47c4f575f7
Binary files /dev/null and b/misc/2.5d/screenshots/cube.png differ
diff --git a/misc/2.5d/screenshots/editor.png b/misc/2.5d/screenshots/editor.png
new file mode 100644
index 0000000000..369ef3289b
Binary files /dev/null and b/misc/2.5d/screenshots/editor.png differ
diff --git a/misc/2.5d/screenshots/forty_five.png b/misc/2.5d/screenshots/forty_five.png
new file mode 100644
index 0000000000..e593aaadac
Binary files /dev/null and b/misc/2.5d/screenshots/forty_five.png differ
diff --git a/misc/2.5d/screenshots/front_side.png b/misc/2.5d/screenshots/front_side.png
new file mode 100644
index 0000000000..a3e97338e6
Binary files /dev/null and b/misc/2.5d/screenshots/front_side.png differ
diff --git a/misc/2.5d/screenshots/isometric.png b/misc/2.5d/screenshots/isometric.png
new file mode 100644
index 0000000000..41ca63dd87
Binary files /dev/null and b/misc/2.5d/screenshots/isometric.png differ
diff --git a/misc/2.5d/screenshots/oblique_y.png b/misc/2.5d/screenshots/oblique_y.png
new file mode 100644
index 0000000000..415276e188
Binary files /dev/null and b/misc/2.5d/screenshots/oblique_y.png differ
diff --git a/misc/2.5d/screenshots/oblique_z.png b/misc/2.5d/screenshots/oblique_z.png
new file mode 100644
index 0000000000..65be79e247
Binary files /dev/null and b/misc/2.5d/screenshots/oblique_z.png differ
diff --git a/mono/2.5d/2.5D Demo (Mono C#).csproj b/mono/2.5d/2.5D Demo (Mono C#).csproj
new file mode 100644
index 0000000000..0dbfc3d109
--- /dev/null
+++ b/mono/2.5d/2.5D Demo (Mono C#).csproj
@@ -0,0 +1,67 @@
+
+
+
+ Debug
+ AnyCPU
+ {5CA791DB-5050-44D0-989B-41D559AB1D50}
+ Library
+ .mono/temp/bin/$(Configuration)
+ Empty.DMono
+ 2.5D Demo (Mono C#)
+ v4.5
+ .mono/temp/obj
+ $(BaseIntermediateOutputPath)/$(Configuration)
+ Debug
+ Release
+
+
+ true
+ portable
+ false
+ $(GodotDefineConstants);GODOT;DEBUG;
+ prompt
+ 4
+ false
+
+
+ portable
+ true
+ $(GodotDefineConstants);GODOT;
+ prompt
+ 4
+ false
+
+
+ true
+ portable
+ false
+ $(GodotDefineConstants);GODOT;DEBUG;TOOLS;
+ prompt
+ 4
+ false
+
+
+
+ False
+ $(ProjectDir)/.mono/assemblies/$(ApiConfiguration)/GodotSharp.dll
+
+
+ False
+ $(ProjectDir)/.mono/assemblies/$(ApiConfiguration)/GodotSharpEditor.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mono/2.5d/2.5D Demo (Mono C#).sln b/mono/2.5d/2.5D Demo (Mono C#).sln
new file mode 100644
index 0000000000..d979d4573d
--- /dev/null
+++ b/mono/2.5d/2.5D Demo (Mono C#).sln
@@ -0,0 +1,19 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2012
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "2.5D Demo (Mono C#)", "2.5D Demo (Mono C#).csproj", "{5CA791DB-5050-44D0-989B-41D559AB1D50}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ Tools|Any CPU = Tools|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {5CA791DB-5050-44D0-989B-41D559AB1D50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5CA791DB-5050-44D0-989B-41D559AB1D50}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5CA791DB-5050-44D0-989B-41D559AB1D50}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5CA791DB-5050-44D0-989B-41D559AB1D50}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5CA791DB-5050-44D0-989B-41D559AB1D50}.Tools|Any CPU.ActiveCfg = Tools|Any CPU
+ {5CA791DB-5050-44D0-989B-41D559AB1D50}.Tools|Any CPU.Build.0 = Tools|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/mono/2.5d/Properties/AssemblyInfo.cs b/mono/2.5d/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..c562b72271
--- /dev/null
+++ b/mono/2.5d/Properties/AssemblyInfo.cs
@@ -0,0 +1,25 @@
+using System.Reflection;
+
+// Information about this assembly is defined by the following attributes.
+// Change them to the values specific to your project.
+
+[assembly: AssemblyTitle("2.5D Demo (Mono C#)")]
+[assembly: AssemblyDescription("2.5D demo project for Godot, Mono C# version.")]
+[assembly: AssemblyConfiguration("Debug")]
+[assembly: AssemblyCompany("Godot")]
+[assembly: AssemblyProduct("Godot Demo Projects")]
+[assembly: AssemblyCopyright("Copyright © 2020 Aaron Franke, Godot Engine contributors")]
+[assembly: AssemblyTrademark("Godot Engine")]
+[assembly: AssemblyCulture("")]
+
+// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
+// The form "{Major}.{Minor}.*" will automatically update the build and revision,
+// and "{Major}.{Minor}.{Build}.*" will update just the revision.
+
+[assembly: AssemblyVersion("1.0.*")]
+
+// The following attributes are used to specify the signing key for the assembly,
+// if desired. See the Mono documentation for more information about signing.
+
+//[assembly: AssemblyDelaySign(false)]
+//[assembly: AssemblyKeyFile("")]
diff --git a/mono/2.5d/README.md b/mono/2.5d/README.md
new file mode 100644
index 0000000000..d190e25352
--- /dev/null
+++ b/mono/2.5d/README.md
@@ -0,0 +1,35 @@
+# 2.5D Demo Project (Mono C#)
+
+This demo project shows a way to create a 2.5D game in Godot by mixing 2D and 3D nodes. It also adds a 2.5D editor viewport for easily editing 2.5D levels.
+
+Note: There is a GDScript version available [here](https://github.com/godotengine/godot-demo-projects/tree/master/misc/2.5d).
+
+## How does it work?
+
+Custom node types are added in a Godot plugin to allow 2.5D objects. Node25D serves as the base for all 2.5D objects. Its first child must be a 3D Spatial, which is used to calculate its position. Then, add a 2D Sprite (or similar) to display the object.
+
+Inside of Node25D, new structs called Basis25D and Transform25D are used to calculate the 2D position from the 3D position. For getting a 3D position, this project uses KinematicBody and StaticBody (3D), but these only exist for math - the camera is 2D and all sprites are 2D. You are able to use any Spatial node for math.
+
+Several view modes are implemented, including top down, front side, 45 degree, isometric, and two oblique modes. To implement a different view angle, all you need to do is create a new Basis25D, use it in all your Node25D transforms, and of course create sprites to display that object in 2D.
+
+The plugin also adds YSort25D to sort Node25D nodes, and ShadowMath25D for calculating a shadow (a simple KinematicBody that tries to cast downward).
+
+## Screenshots
+
+![Forty Five Degrees](../../misc/2.5d/screenshots/forty_five.png)
+
+![Isometric](../../misc/2.5d/screenshots/isometric.png)
+
+![Oblique Z](../../misc/2.5d/screenshots/oblique_z.png)
+
+![Oblique Y](../../misc/2.5d/screenshots/oblique_y.png)
+
+![Front Side](../../misc/2.5d/screenshots/front_side.png)
+
+![Cube](../../misc/2.5d/screenshots/cube.png)
+
+![2.5D Editor Viewport](../../misc/2.5d/screenshots/editor.png)
+
+## Music License
+
+`assets/mr_mrs_robot.ogg` Copyright © circa 2008 Juan Linietsky, CC-BY: Attribution.
diff --git a/mono/2.5d/addons/node25d-cs/Basis25D.cs b/mono/2.5d/addons/node25d-cs/Basis25D.cs
new file mode 100644
index 0000000000..d5f82b0603
--- /dev/null
+++ b/mono/2.5d/addons/node25d-cs/Basis25D.cs
@@ -0,0 +1,207 @@
+using Godot;
+using System;
+using System.Runtime.InteropServices;
+
+#if GODOT_REAL_T_IS_DOUBLE
+using real_t = System.Double;
+#else
+using real_t = System.Single;
+#endif
+
+///
+/// Basis25D structure for performing 2.5D transform math.
+/// Note: All code assumes that Y is UP in 3D, and DOWN in 2D.
+/// A top-down view has a Y axis component of (0, 0), with a Z axis component of (0, 1).
+/// For a front side view, Y is (0, -1) and Z is (0, 0).
+/// Remember that Godot's 2D mode has the Y axis pointing DOWN on the screen.
+///
+[Serializable]
+[StructLayout(LayoutKind.Sequential)]
+public struct Basis25D : IEquatable
+{
+ // Also matrix columns, the directions to move on screen for each unit change in 3D.
+ public Vector2 x;
+ public Vector2 y;
+ public Vector2 z;
+
+ // Also matrix rows, the parts of each vector that contribute to moving in a screen direction.
+ // Setting a row to zero means no movement in that direction.
+ public Vector3 Row0
+ {
+ get { return new Vector3(x.x, y.x, z.x); }
+ set
+ {
+ x.x = value.x;
+ y.x = value.y;
+ z.x = value.z;
+ }
+ }
+
+ public Vector3 Row1
+ {
+ get { return new Vector3(x.y, y.y, z.y); }
+ set
+ {
+ x.y = value.x;
+ y.y = value.y;
+ z.y = value.z;
+ }
+ }
+
+ public Vector2 this[int columnIndex]
+ {
+ get
+ {
+ switch (columnIndex)
+ {
+ case 0: return x;
+ case 1: return y;
+ case 2: return z;
+ default: throw new IndexOutOfRangeException();
+ }
+ }
+ set
+ {
+ switch (columnIndex)
+ {
+ case 0: x = value; return;
+ case 1: y = value; return;
+ case 2: z = value; return;
+ default: throw new IndexOutOfRangeException();
+ }
+ }
+ }
+
+ public real_t this[int columnIndex, int rowIndex]
+ {
+ get
+ {
+ return this[columnIndex][rowIndex];
+ }
+ set
+ {
+ Vector2 v = this[columnIndex];
+ v[rowIndex] = value;
+ this[columnIndex] = v;
+ }
+ }
+
+ private static readonly Basis25D _topDown = new Basis25D(1, 0, 0, 0, 0, 1);
+ private static readonly Basis25D _frontSide = new Basis25D(1, 0, 0, -1, 0, 0);
+ private static readonly Basis25D _fortyFive = new Basis25D(1, 0, 0, -0.70710678118f, 0, 0.70710678118f);
+ private static readonly Basis25D _isometric = new Basis25D(0.86602540378f, 0.5f, 0, -1, -0.86602540378f, 0.5f);
+ private static readonly Basis25D _obliqueY = new Basis25D(1, 0, -0.70710678118f, -0.70710678118f, 0, 1);
+ private static readonly Basis25D _obliqueZ = new Basis25D(1, 0, 0, -1, -0.70710678118f, 0.70710678118f);
+
+ public static Basis25D TopDown { get { return _topDown; } }
+ public static Basis25D FrontSide { get { return _frontSide; } }
+ public static Basis25D FortyFive { get { return _fortyFive; } }
+ public static Basis25D Isometric { get { return _isometric; } }
+ public static Basis25D ObliqueY { get { return _obliqueY; } }
+ public static Basis25D ObliqueZ { get { return _obliqueZ; } }
+
+ ///
+ /// Creates a Dimetric Basis25D from the angle between the Y axis and the others.
+ /// Dimetric(Tau/3) or Dimetric(2.09439510239) is the same as Isometric.
+ /// Try to keep this number away from a multiple of Tau/4 (or Pi/2) radians.
+ ///
+ /// The angle, in radians, between the Y axis and the X/Z axes.
+ public static Basis25D Dimetric(real_t angle)
+ {
+ real_t sin = Mathf.Sin(angle);
+ real_t cos = Mathf.Cos(angle);
+ return new Basis25D(sin, -cos, 0, -1, -sin, -cos);
+ }
+
+ // Constructors
+ public Basis25D(Basis25D b)
+ {
+ x = b.x;
+ y = b.y;
+ z = b.z;
+ }
+ public Basis25D(Vector2 xAxis, Vector2 yAxis, Vector2 zAxis)
+ {
+ x = xAxis;
+ y = yAxis;
+ z = zAxis;
+ }
+ public Basis25D(real_t xx, real_t xy, real_t yx, real_t yy, real_t zx, real_t zy)
+ {
+ x = new Vector2(xx, xy);
+ y = new Vector2(yx, yy);
+ z = new Vector2(zx, zy);
+ }
+
+ public static Basis25D operator *(Basis25D b, real_t s)
+ {
+ b.x *= s;
+ b.y *= s;
+ b.z *= s;
+ return b;
+ }
+
+ public static Basis25D operator /(Basis25D b, real_t s)
+ {
+ b.x /= s;
+ b.y /= s;
+ b.z /= s;
+ return b;
+ }
+
+ public static bool operator ==(Basis25D left, Basis25D right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(Basis25D left, Basis25D right)
+ {
+ return !left.Equals(right);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is Basis25D)
+ {
+ return Equals((Basis25D)obj);
+ }
+ return false;
+ }
+
+ public bool Equals(Basis25D other)
+ {
+ return x.Equals(other.x) && y.Equals(other.y) && z.Equals(other.z);
+ }
+
+ public bool IsEqualApprox(Basis25D other)
+ {
+ return x.IsEqualApprox(other.x) && y.IsEqualApprox(other.y) && z.IsEqualApprox(other.z);
+ }
+
+ public override int GetHashCode()
+ {
+ return y.GetHashCode() ^ x.GetHashCode() ^ z.GetHashCode();
+ }
+
+ public override string ToString()
+ {
+ string s = String.Format("({0}, {1}, {2})", new object[]
+ {
+ x.ToString(),
+ y.ToString(),
+ z.ToString()
+ });
+ return s;
+ }
+
+ public string ToString(string format)
+ {
+ string s = String.Format("({0}, {1}, {2})", new object[]
+ {
+ x.ToString(format),
+ y.ToString(format),
+ z.ToString(format)
+ });
+ return s;
+ }
+}
diff --git a/mono/2.5d/addons/node25d-cs/Node25D.cs b/mono/2.5d/addons/node25d-cs/Node25D.cs
new file mode 100644
index 0000000000..bdfc9d9029
--- /dev/null
+++ b/mono/2.5d/addons/node25d-cs/Node25D.cs
@@ -0,0 +1,181 @@
+using Godot;
+using System;
+#if REAL_T_IS_DOUBLE
+using real_t = System.Double;
+#else
+using real_t = System.Single;
+#endif
+
+///
+/// This node converts a 3D position to 2D using a 2.5D transformation matrix.
+/// The transformation of its 2D form is controlled by its 3D child.
+///
+[Tool]
+public class Node25D : Node2D, IComparable
+{
+ ///
+ /// The number of 2D units in one 3D unit. Ideally, but not necessarily, an integer.
+ ///
+ public const int SCALE = 32;
+
+ [Export] public Vector3 spatialPosition
+ {
+ get
+ {
+ if (spatialNode == null)
+ {
+ spatialNode = GetChild(0);
+ }
+ return spatialNode.Translation;
+ }
+ set
+ {
+ transform25D.spatialPosition = value;
+ if (spatialNode != null)
+ {
+ spatialNode.Translation = value;
+ }
+ else if (GetChildCount() > 0)
+ {
+ spatialNode = GetChild(0);
+ }
+ }
+ }
+
+ private Spatial spatialNode;
+ private Transform25D transform25D;
+
+ public Basis25D Basis25D
+ {
+ get { return transform25D.basis; }
+ }
+
+ public Transform25D Transform25D
+ {
+ get { return transform25D; }
+ }
+
+ public override void _Ready()
+ {
+ Node25DReady();
+ }
+
+ public override void _Process(real_t delta)
+ {
+ Node25DProcess();
+ }
+
+ ///
+ /// Call this method in _Ready, or before Node25DProcess is run.
+ ///
+ protected void Node25DReady()
+ {
+ if (GetChildCount() > 0)
+ {
+ spatialNode = GetChild(0);
+ }
+ // Changing the basis here will change the default for all Node25D instances.
+ transform25D = new Transform25D(Basis25D.FortyFive * SCALE);
+ }
+
+ ///
+ /// Call this method in _Process, or whenever the position of this object changes.
+ ///
+ protected void Node25DProcess()
+ {
+ if (transform25D.basis == new Basis25D())
+ {
+ SetViewMode(0);
+ }
+ CheckViewMode();
+ if (spatialNode != null)
+ {
+ transform25D.spatialPosition = spatialNode.Translation;
+ }
+ else if (GetChildCount() > 0)
+ {
+ spatialNode = GetChild(0);
+ }
+
+ GlobalPosition = transform25D.FlatPosition;
+ }
+
+ public void SetViewMode(int viewModeIndex)
+ {
+ switch (viewModeIndex)
+ {
+ case 0:
+ transform25D.basis = Basis25D.FortyFive * SCALE;
+ break;
+ case 1:
+ transform25D.basis = Basis25D.Isometric * SCALE;
+ break;
+ case 2:
+ transform25D.basis = Basis25D.TopDown * SCALE;
+ break;
+ case 3:
+ transform25D.basis = Basis25D.FrontSide * SCALE;
+ break;
+ case 4:
+ transform25D.basis = Basis25D.ObliqueY * SCALE;
+ break;
+ case 5:
+ transform25D.basis = Basis25D.ObliqueZ * SCALE;
+ break;
+ }
+ }
+
+ private void CheckViewMode()
+ {
+ if (Input.IsActionJustPressed("forty_five_mode"))
+ {
+ SetViewMode(0);
+ }
+ else if (Input.IsActionJustPressed("isometric_mode"))
+ {
+ SetViewMode(1);
+ }
+ else if (Input.IsActionJustPressed("top_down_mode"))
+ {
+ SetViewMode(2);
+ }
+ else if (Input.IsActionJustPressed("front_side_mode"))
+ {
+ SetViewMode(3);
+ }
+ else if (Input.IsActionJustPressed("oblique_y_mode"))
+ {
+ SetViewMode(4);
+ }
+ else if (Input.IsActionJustPressed("oblique_z_mode"))
+ {
+ SetViewMode(5);
+ }
+ }
+
+ public int CompareTo(object obj)
+ {
+ if (obj is Node25D)
+ {
+ return CompareTo((Node25D)obj);
+ }
+ return 1;
+ }
+
+ public int CompareTo(Node25D other)
+ {
+ real_t thisIndex = transform25D.spatialPosition.y + 0.001f * (transform25D.spatialPosition.x + transform25D.spatialPosition.z);
+ real_t otherIndex = other.transform25D.spatialPosition.y + 0.001f * (other.transform25D.spatialPosition.x + other.transform25D.spatialPosition.z);
+ real_t diff = thisIndex - otherIndex;
+ if (diff > 0)
+ {
+ return 1;
+ }
+ if (diff < 0)
+ {
+ return -1;
+ }
+ return 0;
+ }
+
+}
diff --git a/mono/2.5d/addons/node25d-cs/ShadowMath25D.cs b/mono/2.5d/addons/node25d-cs/ShadowMath25D.cs
new file mode 100644
index 0000000000..64b8061f22
--- /dev/null
+++ b/mono/2.5d/addons/node25d-cs/ShadowMath25D.cs
@@ -0,0 +1,54 @@
+using Godot;
+#if REAL_T_IS_DOUBLE
+using real_t = System.Double;
+#else
+using real_t = System.Single;
+#endif
+
+///
+/// Adds a simple shadow below an object.
+/// Place this ShadowMath25D node as a child of a Shadow25D, which
+/// is below the target object in the scene tree (not as a child).
+///
+[Tool]
+public class ShadowMath25D : KinematicBody
+{
+ ///
+ /// The maximum distance below objects that shadows will appear.
+ ///
+ public real_t shadowLength = 1000;
+ private Node25D shadowRoot;
+ private Spatial targetMath;
+
+ public override void _Ready()
+ {
+ shadowRoot = GetParent();
+ int index = shadowRoot.GetPositionInParent();
+ targetMath = shadowRoot.GetParent().GetChild(index - 1).GetChild(0);
+ }
+
+ public override void _Process(real_t delta)
+ {
+ if (targetMath == null)
+ {
+ if (shadowRoot != null)
+ {
+ shadowRoot.Visible = false;
+ }
+ return; // Shadow is not in a valid place.
+ }
+
+ Translation = targetMath.Translation;
+ var k = MoveAndCollide(Vector3.Down * shadowLength);
+ if (k == null)
+ {
+ shadowRoot.Visible = false;
+ }
+ else
+ {
+ shadowRoot.Visible = true;
+ GlobalTransform = Transform;
+ }
+ }
+
+}
diff --git a/mono/2.5d/addons/node25d-cs/Transform25D.cs b/mono/2.5d/addons/node25d-cs/Transform25D.cs
new file mode 100644
index 0000000000..2c41e2c3e4
--- /dev/null
+++ b/mono/2.5d/addons/node25d-cs/Transform25D.cs
@@ -0,0 +1,131 @@
+using Godot;
+using System;
+using System.Runtime.InteropServices;
+
+///
+/// Calculates the 2D transformation from a 3D position and a Basis25D.
+///
+[Serializable]
+[StructLayout(LayoutKind.Sequential)]
+public struct Transform25D : IEquatable
+{
+ // Public fields store information that is used to calculate the properties.
+
+ ///
+ /// Controls how the 3D position is transformed into 2D.
+ ///
+ public Basis25D basis;
+
+ ///
+ /// The 3D position of the object. Should be updated on every frame before everything else.
+ ///
+ public Vector3 spatialPosition;
+
+ // Public properties calculate on-the-fly.
+
+ ///
+ /// The 2D transformation of this object. Slower than FlatPosition.
+ ///
+ public Transform2D FlatTransform
+ {
+ get
+ {
+ return new Transform2D(0, FlatPosition);
+ }
+ }
+
+ ///
+ /// The 2D position of this object.
+ ///
+ public Vector2 FlatPosition
+ {
+ get
+ {
+ Vector2 pos = spatialPosition.x * basis.x;
+ pos += spatialPosition.y * basis.y;
+ pos += spatialPosition.z * basis.z;
+ return pos;
+ }
+ }
+
+ // Constructors
+ public Transform25D(Transform25D transform25D)
+ {
+ basis = transform25D.basis;
+ spatialPosition = transform25D.spatialPosition;
+ }
+ public Transform25D(Basis25D basis25D)
+ {
+ basis = basis25D;
+ spatialPosition = Vector3.Zero;
+ }
+ public Transform25D(Basis25D basis25D, Vector3 position3D)
+ {
+ basis = basis25D;
+ spatialPosition = position3D;
+ }
+ public Transform25D(Vector2 xAxis, Vector2 yAxis, Vector2 zAxis)
+ {
+ basis = new Basis25D(xAxis, yAxis, zAxis);
+ spatialPosition = Vector3.Zero;
+ }
+ public Transform25D(Vector2 xAxis, Vector2 yAxis, Vector2 zAxis, Vector3 position3D)
+ {
+ basis = new Basis25D(xAxis, yAxis, zAxis);
+ spatialPosition = position3D;
+ }
+
+ public static bool operator ==(Transform25D left, Transform25D right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(Transform25D left, Transform25D right)
+ {
+ return !left.Equals(right);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is Transform25D)
+ {
+ return Equals((Transform25D)obj);
+ }
+ return false;
+ }
+
+ public bool Equals(Transform25D other)
+ {
+ return basis.Equals(other.basis) && spatialPosition.Equals(other.spatialPosition);
+ }
+
+ public bool IsEqualApprox(Transform25D other)
+ {
+ return basis.IsEqualApprox(other.basis) && spatialPosition.IsEqualApprox(other.spatialPosition);
+ }
+
+ public override int GetHashCode()
+ {
+ return basis.GetHashCode() ^ spatialPosition.GetHashCode();
+ }
+
+ public override string ToString()
+ {
+ string s = String.Format("({0}, {1})", new object[]
+ {
+ basis.ToString(),
+ spatialPosition.ToString()
+ });
+ return s;
+ }
+
+ public string ToString(string format)
+ {
+ string s = String.Format("({0}, {1})", new object[]
+ {
+ basis.ToString(format),
+ spatialPosition.ToString(format)
+ });
+ return s;
+ }
+}
diff --git a/mono/2.5d/addons/node25d-cs/YSort25D.cs b/mono/2.5d/addons/node25d-cs/YSort25D.cs
new file mode 100644
index 0000000000..9b1506c0be
--- /dev/null
+++ b/mono/2.5d/addons/node25d-cs/YSort25D.cs
@@ -0,0 +1,60 @@
+using Godot;
+using System.Collections.Generic;
+#if REAL_T_IS_DOUBLE
+using real_t = System.Double;
+#else
+using real_t = System.Single;
+#endif
+
+///
+/// Assigns Z-index values to Node25D children.
+///
+[Tool] // Commented out because it sometimes crashes the editor when running the game...
+public class YSort25D : Node // Note: NOT Node2D, Node25D, or YSort
+{
+ ///
+ /// Whether or not to automatically call Sort() in _Process().
+ ///
+ [Export]
+ public bool sortEnabled = true;
+
+ public override void _Process(real_t delta)
+ {
+ if (sortEnabled)
+ {
+ Sort();
+ }
+ }
+
+ ///
+ /// Call this method in _Process, or whenever you want to sort children.
+ ///
+ public void Sort()
+ {
+ var children = GetParent().GetChildren();
+ if (children.Count > 4000)
+ {
+ GD.PrintErr("Sorting failed: Max number of YSort25D nodes is 4000.");
+ }
+ List node25dChildren = new List();
+
+ foreach (Node n in children)
+ {
+ if (n is Node25D node25d)
+ {
+ node25dChildren.Add(node25d);
+ }
+ }
+
+ node25dChildren.Sort();
+
+ int zIndex = -4000;
+ for (int i = 0; i < node25dChildren.Count; i++)
+ {
+ node25dChildren[i].ZIndex = zIndex;
+ // Increment by 2 each time, to allow for shadows in-between.
+ // This does mean that we have a limit of 4000 total sorted Node25Ds.
+ zIndex += 2;
+ }
+ }
+}
diff --git a/mono/2.5d/addons/node25d-cs/icons/kinematic_body_25d.png b/mono/2.5d/addons/node25d-cs/icons/kinematic_body_25d.png
new file mode 100644
index 0000000000..2780a094fb
Binary files /dev/null and b/mono/2.5d/addons/node25d-cs/icons/kinematic_body_25d.png differ
diff --git a/mono/2.5d/addons/node25d-cs/icons/kinematic_body_25d.png.import b/mono/2.5d/addons/node25d-cs/icons/kinematic_body_25d.png.import
new file mode 100644
index 0000000000..56b6b828e0
--- /dev/null
+++ b/mono/2.5d/addons/node25d-cs/icons/kinematic_body_25d.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/kinematic_body_25d.png-791432e863e44720a1390f5b1fbf09be.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/node25d-cs/icons/kinematic_body_25d.png"
+dest_files=[ "res://.import/kinematic_body_25d.png-791432e863e44720a1390f5b1fbf09be.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/mono/2.5d/addons/node25d-cs/icons/node_25d.png b/mono/2.5d/addons/node25d-cs/icons/node_25d.png
new file mode 100644
index 0000000000..ede1d823fd
Binary files /dev/null and b/mono/2.5d/addons/node25d-cs/icons/node_25d.png differ
diff --git a/mono/2.5d/addons/node25d-cs/icons/node_25d.png.import b/mono/2.5d/addons/node25d-cs/icons/node_25d.png.import
new file mode 100644
index 0000000000..571965ba5a
--- /dev/null
+++ b/mono/2.5d/addons/node25d-cs/icons/node_25d.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/node_25d.png-ecf0b5959e83c044c288582c27f3f4c9.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/node25d-cs/icons/node_25d.png"
+dest_files=[ "res://.import/node_25d.png-ecf0b5959e83c044c288582c27f3f4c9.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/mono/2.5d/addons/node25d-cs/icons/node_25d_icon.png b/mono/2.5d/addons/node25d-cs/icons/node_25d_icon.png
new file mode 100644
index 0000000000..12b13bb58b
Binary files /dev/null and b/mono/2.5d/addons/node25d-cs/icons/node_25d_icon.png differ
diff --git a/mono/2.5d/addons/node25d-cs/icons/node_25d_icon.png.import b/mono/2.5d/addons/node25d-cs/icons/node_25d_icon.png.import
new file mode 100644
index 0000000000..1142ec2a1c
--- /dev/null
+++ b/mono/2.5d/addons/node25d-cs/icons/node_25d_icon.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/node_25d_icon.png-c9b692824a2a2a3ddca2c0df67f60add.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/node25d-cs/icons/node_25d_icon.png"
+dest_files=[ "res://.import/node_25d_icon.png-c9b692824a2a2a3ddca2c0df67f60add.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/mono/2.5d/addons/node25d-cs/icons/shadow_math_25d.png b/mono/2.5d/addons/node25d-cs/icons/shadow_math_25d.png
new file mode 100644
index 0000000000..0dbc8c4b05
Binary files /dev/null and b/mono/2.5d/addons/node25d-cs/icons/shadow_math_25d.png differ
diff --git a/mono/2.5d/addons/node25d-cs/icons/shadow_math_25d.png.import b/mono/2.5d/addons/node25d-cs/icons/shadow_math_25d.png.import
new file mode 100644
index 0000000000..131ab1ffda
--- /dev/null
+++ b/mono/2.5d/addons/node25d-cs/icons/shadow_math_25d.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/shadow_math_25d.png-0cd88127f233ec7b6b2959e12e9f275a.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/node25d-cs/icons/shadow_math_25d.png"
+dest_files=[ "res://.import/shadow_math_25d.png-0cd88127f233ec7b6b2959e12e9f275a.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/mono/2.5d/addons/node25d-cs/icons/shadow_math_25d_icon.png b/mono/2.5d/addons/node25d-cs/icons/shadow_math_25d_icon.png
new file mode 100644
index 0000000000..ae13ea3823
Binary files /dev/null and b/mono/2.5d/addons/node25d-cs/icons/shadow_math_25d_icon.png differ
diff --git a/mono/2.5d/addons/node25d-cs/icons/shadow_math_25d_icon.png.import b/mono/2.5d/addons/node25d-cs/icons/shadow_math_25d_icon.png.import
new file mode 100644
index 0000000000..2e1fab4b17
--- /dev/null
+++ b/mono/2.5d/addons/node25d-cs/icons/shadow_math_25d_icon.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/shadow_math_25d_icon.png-4dbc225f4d5f7ef06072b06e2f163301.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/node25d-cs/icons/shadow_math_25d_icon.png"
+dest_files=[ "res://.import/shadow_math_25d_icon.png-4dbc225f4d5f7ef06072b06e2f163301.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/mono/2.5d/addons/node25d-cs/icons/viewport_25d.svg b/mono/2.5d/addons/node25d-cs/icons/viewport_25d.svg
new file mode 100644
index 0000000000..22bd96ec2d
--- /dev/null
+++ b/mono/2.5d/addons/node25d-cs/icons/viewport_25d.svg
@@ -0,0 +1 @@
+
diff --git a/mono/2.5d/addons/node25d-cs/icons/viewport_25d.svg.import b/mono/2.5d/addons/node25d-cs/icons/viewport_25d.svg.import
new file mode 100644
index 0000000000..3fd1336d9c
--- /dev/null
+++ b/mono/2.5d/addons/node25d-cs/icons/viewport_25d.svg.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/viewport_25d.svg-104006b56693c8e3ae613ee52de431c7.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/node25d-cs/icons/viewport_25d.svg"
+dest_files=[ "res://.import/viewport_25d.svg-104006b56693c8e3ae613ee52de431c7.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/mono/2.5d/addons/node25d-cs/icons/y_sort_25d.png b/mono/2.5d/addons/node25d-cs/icons/y_sort_25d.png
new file mode 100644
index 0000000000..e37e57502d
Binary files /dev/null and b/mono/2.5d/addons/node25d-cs/icons/y_sort_25d.png differ
diff --git a/mono/2.5d/addons/node25d-cs/icons/y_sort_25d.png.import b/mono/2.5d/addons/node25d-cs/icons/y_sort_25d.png.import
new file mode 100644
index 0000000000..53087b1af0
--- /dev/null
+++ b/mono/2.5d/addons/node25d-cs/icons/y_sort_25d.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/y_sort_25d.png-d7ffa1c0d05a9139ab514ec27ad8da9d.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/node25d-cs/icons/y_sort_25d.png"
+dest_files=[ "res://.import/y_sort_25d.png-d7ffa1c0d05a9139ab514ec27ad8da9d.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/mono/2.5d/addons/node25d-cs/icons/y_sort_25d_icon.png b/mono/2.5d/addons/node25d-cs/icons/y_sort_25d_icon.png
new file mode 100644
index 0000000000..1e362220e2
Binary files /dev/null and b/mono/2.5d/addons/node25d-cs/icons/y_sort_25d_icon.png differ
diff --git a/mono/2.5d/addons/node25d-cs/icons/y_sort_25d_icon.png.import b/mono/2.5d/addons/node25d-cs/icons/y_sort_25d_icon.png.import
new file mode 100644
index 0000000000..db9724c066
--- /dev/null
+++ b/mono/2.5d/addons/node25d-cs/icons/y_sort_25d_icon.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/y_sort_25d_icon.png-852bb2b2c54661e1957a46372d9a6d8f.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/node25d-cs/icons/y_sort_25d_icon.png"
+dest_files=[ "res://.import/y_sort_25d_icon.png-852bb2b2c54661e1957a46372d9a6d8f.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/mono/2.5d/addons/node25d-cs/main_screen/.broken-cs-files/Viewport25D.cs b/mono/2.5d/addons/node25d-cs/main_screen/.broken-cs-files/Viewport25D.cs
new file mode 100644
index 0000000000..f1ead376f7
--- /dev/null
+++ b/mono/2.5d/addons/node25d-cs/main_screen/.broken-cs-files/Viewport25D.cs
@@ -0,0 +1,230 @@
+using Godot;
+
+// This is identical to the GDScript version, yet it doesn't work.
+[Tool]
+public class Viewport25D : Control
+{
+ private int zoomLevel = 0;
+ private bool isPanning = false;
+ private Vector2 panCenter;
+ private Vector2 viewportCenter;
+ private int viewModeIndex = 0;
+
+ // The type or namespace name 'EditorInterface' could not be found (are you missing a using directive or an assembly reference?)
+ // No idea why this error shows up in VS Code. It builds fine...
+ public EditorInterface editorInterface; // Set in node25d_plugin.gd
+ private bool moving = false;
+
+ private Viewport viewport2d;
+ private Viewport viewportOverlay;
+ private ButtonGroup viewModeButtonGroup;
+ private Label zoomLabel;
+ private PackedScene gizmo25dScene;
+
+ public async override void _Ready()
+ {
+ // Give Godot a chance to fully load the scene. Should take two frames.
+ //yield(get_tree(), "idle_frame");
+ //yield(get_tree(), "idle_frame");
+ await ToSignal(GetTree(), "idle_frame");
+ await ToSignal(GetTree(), "idle_frame");
+ var editedSceneRoot = GetTree().EditedSceneRoot;
+ if (editedSceneRoot == null)
+ {
+ // Godot hasn't finished loading yet, so try loading the plugin again.
+ //editorInterface.SetPluginEnabled("node25d", false);
+ //editorInterface.SetPluginEnabled("node25d", true);
+ return;
+ }
+ // Alright, we're loaded up. Now check if we have a valid world and assign it.
+ var world2d = editedSceneRoot.GetViewport().World2d;
+ if (world2d == GetViewport().World2d)
+ {
+ return; // This is the MainScreen25D scene opened in the editor!
+ }
+ viewport2d.World2d = world2d;
+
+ // Onready vars.
+ viewport2d = GetNode("Viewport2D");
+ viewportOverlay = GetNode("ViewportOverlay");
+ viewModeButtonGroup = GetParent().GetNode("TopBar").GetNode("ViewModeButtons").GetNode