Skip to content

Commit a11fae3

Browse files
aaronfrankeStephen Agbete (Steph155)
andcommitted
[WIP] Add 2.5D Demo Projects
Co-authored-by: Stephen Agbete (Steph155) <bgsteph15@mail.com>
1 parent b9bca13 commit a11fae3

File tree

148 files changed

+5088
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

148 files changed

+5088
-0
lines changed

misc/2.5d/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#2.5D Demo
2+
3+
This demo project is designed to serve as a prototype that can be used to create a 2.5D game in Godot.
4+
5+
Some custom node types are added in a Godot plugin for this purpose. Node25D serves as the base for all 2.5D objects, and the first child must be a Spatial, which is used to calculate its position. It also adds YSort25D to sort Node25D nodes, and ShadowMath25D for calculating a shadow.
6+
7+
It uses math inside of Node25D to calculate 2D positions from 3D ones. 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.
8+
9+
To display the objects, add any Node2D-derived children, such as Sprite, to your Node25D objects. Some nodes are unsuitable, such as 2D physics nodes. Keep in mind that the first child must be Spatial-derived for math purposes.
10+
11+
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 textures to display that object in 2D.
12+
13+
Controls: WASD to move, Space to jump, R to reset, and UIOPKL to change view modes.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Currently broken due to this issue:
2+
# https://github.com/godotengine/godot/issues/21461
3+
4+
# Basis25D structure for performing 2.5D transform math.
5+
# Note: All code assumes that Y is UP in 3D, and DOWN in 2D.
6+
# Meaning, a top-down view has a Y axis component of (0, 0), with a Z axis component of (0, 1).
7+
# For a front side view, Y is (0, -1) and Z is (0, 0).
8+
# Remember that Godot's 2D mode has the Y axis pointing DOWN on the screen.
9+
10+
class_name Basis25D
11+
12+
var x : Vector2 = Vector2()
13+
var y : Vector2 = Vector2()
14+
var z : Vector2 = Vector2()
15+
16+
static func top_down():
17+
return init(1, 0, 0, 0, 0, 1)
18+
19+
static func front_side():
20+
return init(1, 0, 0, -1, 0, 0)
21+
22+
static func forty_five():
23+
return init(1, 0, 0, -0.70710678118, 0, 0.70710678118)
24+
25+
static func isometric():
26+
return init(0.86602540378, 0.5, 0, -1, -0.86602540378, 0.5)
27+
28+
static func oblique_y():
29+
return init(1, 0, -1, -1, 0, 1)
30+
31+
static func oblique_z():
32+
return init(1, 0, 0, -1, -1, 1)
33+
34+
# Creates a Dimetric Basis25D from the angle between the Y axis and the others.
35+
# Dimetric(2.09439510239) is the same as Isometric.
36+
# Try to keep this number away from a multiple of Tau/4 (or Pi/2) radians.
37+
static func dimetric(angle):
38+
var sine = sin(angle)
39+
var cosine = cos(angle)
40+
return init(sine, -cosine, 0, -1, -sine, -cosine)
41+
42+
static func init(xx, xy, yx, yy, zx, zy):
43+
var xv = Vector2(xx, xy)
44+
var yv = Vector2(yx, yy)
45+
var zv = Vector2(zx, zy)
46+
return Basis25D.new(xv, yv, zv)
47+
48+
func _init(xAxis : Vector2, yAxis : Vector2, zAxis : Vector2):
49+
x = xAxis
50+
y = yAxis
51+
z = zAxis
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Currently broken due to this issue:
2+
# https://github.com/godotengine/godot/issues/21461
3+
4+
# Calculates the 2D transformation from a 3D position and a Basis25D.
5+
6+
class_name Transform25D
7+
8+
var spatial_position : Vector3 = Vector3()
9+
var basis# : Basis25D
10+
11+
func flat_transform():
12+
return Transform2D(0, flat_position())
13+
14+
func flat_position():
15+
var pos = spatial_position.x * basis.x
16+
pos += spatial_position.y * basis.y
17+
pos += spatial_position.z * basis.z
18+
return pos
19+
20+
func _init(basis25d):
21+
basis = basis25d
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# This node converts a 3D position to 2D using a 2.5D transformation matrix.
2+
# The transformation of its 2D form is controlled by its 3D child.
3+
tool
4+
class_name Node25D
5+
extends Node2D
6+
7+
# 32 in this code is the number of 2D units in one 3D unit.
8+
# The Godot logo is 64x64 and I made the box extents=1 so I used 32 here.
9+
const SCALE = 32
10+
11+
var _spatial_node : Spatial
12+
13+
# GDScript throws errors when Basis25D is its own structure.
14+
# There is a broken implementation in a hidden folder.
15+
# https://github.com/godotengine/godot/issues/21461
16+
var _basisX : Vector2
17+
var _basisY : Vector2
18+
var _basisZ : Vector2
19+
var spatial_position : Vector3
20+
21+
# These are separated in case anyone wishes to easily extend Node25D
22+
func _ready():
23+
Node25D_ready()
24+
25+
func _process(_delta):
26+
Node25D_process()
27+
28+
# Call this method in _Ready, or before you run Node25DProcess.
29+
func Node25D_ready():
30+
_spatial_node = get_child(0)
31+
_basisX = SCALE * Vector2(1, 0)
32+
_basisY = SCALE * Vector2(0, -0.70710678118)
33+
_basisZ = SCALE * Vector2(0, 0.70710678118)
34+
# Changing the values here will change the default for all Node25D instances.
35+
36+
# Call this method in _Process, or whenever the position of this object changes.
37+
func Node25D_process():
38+
_check_view_mode()
39+
if _spatial_node == null:
40+
return
41+
spatial_position = _spatial_node.translation
42+
43+
var flat_pos = spatial_position.x * _basisX
44+
flat_pos += spatial_position.y * _basisY
45+
flat_pos += spatial_position.z * _basisZ
46+
47+
global_position = flat_pos
48+
49+
# Check if anyone presses the view mode buttons and change the basis accordingly.
50+
# This can be changed or removed in actual games where you only need one view mode.
51+
func _check_view_mode():
52+
if Input.is_action_just_pressed("TopDownMode"):
53+
_basisX = SCALE * Vector2(1, 0)
54+
_basisY = SCALE * Vector2(0, 0)
55+
_basisZ = SCALE * Vector2(0, 1)
56+
elif Input.is_action_just_pressed("FrontSideMode"):
57+
_basisX = SCALE * Vector2(1, 0)
58+
_basisY = SCALE * Vector2(0, -1)
59+
_basisZ = SCALE * Vector2(0, 0)
60+
elif Input.is_action_just_pressed("FortyFiveMode"):
61+
_basisX = SCALE * Vector2(1, 0)
62+
_basisY = SCALE * Vector2(0, -0.70710678118)
63+
_basisZ = SCALE * Vector2(0, 0.70710678118)
64+
elif Input.is_action_just_pressed("IsometricMode"):
65+
_basisX = SCALE * Vector2(0.86602540378, 0.5)
66+
_basisY = SCALE * Vector2(0, -1)
67+
_basisZ = SCALE * Vector2(-0.86602540378, 0.5)
68+
elif Input.is_action_just_pressed("ObliqueYMode"):
69+
_basisX = SCALE * Vector2(1, 0)
70+
_basisY = SCALE * Vector2(-1, -1)
71+
_basisZ = SCALE * Vector2(0, 1)
72+
elif Input.is_action_just_pressed("ObliqueZMode"):
73+
_basisX = SCALE * Vector2(1, 0)
74+
_basisY = SCALE * Vector2(0, -1)
75+
_basisZ = SCALE * Vector2(-1, 1)
76+
77+
func get_basis():
78+
return [_basisX, _basisY, _basisZ]
79+
80+
# Used by YSort25D
81+
static func y_sort(a : Node25D, b : Node25D):
82+
return a.spatial_position.y < b.spatial_position.y
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Adds a simple shadow below an object.
2+
# Place this ShadowMath25D node as a child of a Shadow25D, which
3+
# is below the target object in the scene tree (not as a child).
4+
tool
5+
class_name ShadowMath25D
6+
extends KinematicBody
7+
8+
# The maximum distance below objects that shadows will appear.
9+
var shadow_length : float = 1000.0
10+
var _shadow_root : Node25D
11+
var _target_math : Spatial
12+
13+
func _ready():
14+
_shadow_root = get_parent()
15+
var index = _shadow_root.get_position_in_parent()
16+
if (index > 0): # Else, Shadow is not in a valid place.
17+
_target_math = _shadow_root.get_parent().get_child(index - 1).get_child(0)
18+
19+
func _process(_delta):
20+
if _target_math == null:
21+
if _shadow_root != null:
22+
_shadow_root.visible = false
23+
return # Shadow is not in a valid place or you're viewing the Shadow25D scene.
24+
25+
translation = _target_math.translation
26+
var k = move_and_collide(Vector3.DOWN * shadow_length)
27+
if k == null:
28+
_shadow_root.visible = false
29+
else:
30+
_shadow_root.visible = true
31+
global_transform = transform
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
tool
2+
class_name YSort25D
3+
extends Node # Note: NOT Node2D, Node25D, or YSort
4+
5+
# Whether or not to automatically call sort() in _process().
6+
export (bool) var sort_enabled : bool = true
7+
var _parent_node : Node2D # NOT Node25D
8+
9+
func _ready():
10+
_parent_node = get_parent()
11+
12+
func _process(_delta):
13+
if sort_enabled:
14+
sort()
15+
16+
# Call this method in _process, or whenever you want to sort children.
17+
func sort():
18+
if _parent_node == null:
19+
return # _ready() hasn't been run yet
20+
var parent_children = _parent_node.get_children()
21+
if parent_children.size() > 4000:
22+
# The Z index only goes from -4096 to 4096, and we want room for objects having multiple layers.
23+
printerr("Sorting failed: Max number of YSort25D nodes is 4000. ")
24+
return
25+
26+
# We only want to get Node25D children.
27+
# Currently, it also grabs Node2D children.
28+
var node25d_nodes = []
29+
for n in parent_children:
30+
if (n.get_class() == "Node2D"):
31+
node25d_nodes.append(n)
32+
node25d_nodes.sort_custom(Node25D, "y_sort")
33+
34+
var zIndex : int = -4000
35+
for i in range(0, node25d_nodes.size()):
36+
node25d_nodes[i].z_index = zIndex
37+
zIndex += 2
2.82 KB
Loading
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
[remap]
2+
3+
importer="texture"
4+
type="StreamTexture"
5+
path="res://.import/kinematic_body_25d.png-c455d5ccb8ec7543b62fff6e803eee7c.stex"
6+
metadata={
7+
"vram_texture": false
8+
}
9+
10+
[deps]
11+
12+
source_file="res://addons/node25d/icons/kinematic_body_25d.png"
13+
dest_files=[ "res://.import/kinematic_body_25d.png-c455d5ccb8ec7543b62fff6e803eee7c.stex" ]
14+
15+
[params]
16+
17+
compress/mode=0
18+
compress/lossy_quality=0.7
19+
compress/hdr_mode=0
20+
compress/bptc_ldr=0
21+
compress/normal_map=0
22+
flags/repeat=0
23+
flags/filter=true
24+
flags/mipmaps=false
25+
flags/anisotropic=false
26+
flags/srgb=2
27+
process/fix_alpha_border=true
28+
process/premult_alpha=false
29+
process/HDR_as_SRGB=false
30+
process/invert_color=false
31+
stream=false
32+
size_limit=0
33+
detect_3d=true
34+
svg/scale=1.0
3.41 KB
Loading
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
[remap]
2+
3+
importer="texture"
4+
type="StreamTexture"
5+
path="res://.import/node25d.png-deca325dc1330ad07256305d79ddc3ba.stex"
6+
metadata={
7+
"vram_texture": false
8+
}
9+
10+
[deps]
11+
12+
source_file="res://addons/node25d/icons/node25d.png"
13+
dest_files=[ "res://.import/node25d.png-deca325dc1330ad07256305d79ddc3ba.stex" ]
14+
15+
[params]
16+
17+
compress/mode=0
18+
compress/lossy_quality=0.7
19+
compress/hdr_mode=0
20+
compress/bptc_ldr=0
21+
compress/normal_map=0
22+
flags/repeat=0
23+
flags/filter=true
24+
flags/mipmaps=false
25+
flags/anisotropic=false
26+
flags/srgb=2
27+
process/fix_alpha_border=true
28+
process/premult_alpha=false
29+
process/HDR_as_SRGB=false
30+
process/invert_color=false
31+
stream=false
32+
size_limit=0
33+
detect_3d=true
34+
svg/scale=1.0

0 commit comments

Comments
 (0)