Skip to content

Commit

Permalink
[WIP] Add 2.5D Demo Projects
Browse files Browse the repository at this point in the history
Co-authored-by: Stephen Agbete (Steph155) <bgsteph15@mail.com>
  • Loading branch information
aaronfranke and Stephen Agbete (Steph155) committed Jun 8, 2019
1 parent 47be86a commit 5906046
Show file tree
Hide file tree
Showing 154 changed files with 5,293 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export_presets.cfg

# Mono-specific ignores
.mono/
mono_crash.*.json

# System/tool-specific ignores
.directory
Expand Down
7 changes: 7 additions & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# 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

19 changes: 19 additions & 0 deletions misc/2.5d/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# 2.5D Demo Project (GDScript)

This demo project is an example of how a 2.5D game could be created in Godot.

Controls: WASD to move, Space to jump, R to reset, and UIOPKL to change view modes.

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 Spatial, which is used to calculate its position. It also adds YSort25D to sort Node25D nodes, and ShadowMath25D for calculating a shadow (a simple KinematicBody that tries to cast downward).

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.

To display the objects, add a Sprite or any other Node2D-derived children 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.

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.

## Screenshots
51 changes: 51 additions & 0 deletions misc/2.5d/addons/node25d/.broken-gdscripts/Basis25D.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Currently broken due to this issue:
# https://github.com/godotengine/godot/issues/21461

# 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
21 changes: 21 additions & 0 deletions misc/2.5d/addons/node25d/.broken-gdscripts/Transform25D.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Currently broken due to this issue:
# https://github.com/godotengine/godot/issues/21461

# 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
82 changes: 82 additions & 0 deletions misc/2.5d/addons/node25d/Node25D.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# 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/node25d_icon.png"

# 32 in this code is the number of 2D units in one 3D unit.
# The Godot logo is 64x64 and I made the box extents=1 so I used 32 here.
const SCALE = 32

var _spatial_node : Spatial

# 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
var _basisX : Vector2
var _basisY : Vector2
var _basisZ : Vector2
var spatial_position : Vector3

# 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 you run Node25DProcess.
func Node25D_ready():
_spatial_node = get_child(0)
_basisX = SCALE * Vector2(1, 0)
_basisY = SCALE * Vector2(0, -0.70710678118)
_basisZ = SCALE * Vector2(0, 0.70710678118)
# Changing the values here will change the default for all Node25D instances.

# 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

# 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("TopDownMode"):
_basisX = SCALE * Vector2(1, 0)
_basisY = SCALE * Vector2(0, 0)
_basisZ = SCALE * Vector2(0, 1)
elif Input.is_action_just_pressed("FrontSideMode"):
_basisX = SCALE * Vector2(1, 0)
_basisY = SCALE * Vector2(0, -1)
_basisZ = SCALE * Vector2(0, 0)
elif Input.is_action_just_pressed("FortyFiveMode"):
_basisX = SCALE * Vector2(1, 0)
_basisY = SCALE * Vector2(0, -0.70710678118)
_basisZ = SCALE * Vector2(0, 0.70710678118)
elif Input.is_action_just_pressed("IsometricMode"):
_basisX = SCALE * Vector2(0.86602540378, 0.5)
_basisY = SCALE * Vector2(0, -1)
_basisZ = SCALE * Vector2(-0.86602540378, 0.5)
elif Input.is_action_just_pressed("ObliqueYMode"):
_basisX = SCALE * Vector2(1, 0)
_basisY = SCALE * Vector2(-1, -1)
_basisZ = SCALE * Vector2(0, 1)
elif Input.is_action_just_pressed("ObliqueZMode"):
_basisX = SCALE * Vector2(1, 0)
_basisY = SCALE * Vector2(0, -1)
_basisZ = SCALE * Vector2(-1, 1)

func get_basis():
return [_basisX, _basisY, _basisZ]

# Used by YSort25D
static func y_sort(a : Node25D, b : Node25D):
return a.spatial_position.y < b.spatial_position.y
31 changes: 31 additions & 0 deletions misc/2.5d/addons/node25d/ShadowMath25D.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# 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/shadowmath25d_icon.png"

# The maximum distance below objects that shadows will appear.
var shadow_length : float = 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
41 changes: 41 additions & 0 deletions misc/2.5d/addons/node25d/YSort25D.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# 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/ysort25d_icon.png"

# Whether or not to automatically call sort() in _process().
export (bool) var sort_enabled : bool = 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")

var zIndex : int = -4000
for i in range(0, node25d_nodes.size()):
node25d_nodes[i].z_index = zIndex
zIndex += 2
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 34 additions & 0 deletions misc/2.5d/addons/node25d/icons/kinematic_body_25d.png.import
Original file line number Diff line number Diff line change
@@ -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
Binary file added misc/2.5d/addons/node25d/icons/node25d.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 34 additions & 0 deletions misc/2.5d/addons/node25d/icons/node25d.png.import
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[remap]

importer="texture"
type="StreamTexture"
path="res://.import/node25d.png-deca325dc1330ad07256305d79ddc3ba.stex"
metadata={
"vram_texture": false
}

[deps]

source_file="res://addons/node25d/icons/node25d.png"
dest_files=[ "res://.import/node25d.png-deca325dc1330ad07256305d79ddc3ba.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
Binary file added misc/2.5d/addons/node25d/icons/node25d_icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 34 additions & 0 deletions misc/2.5d/addons/node25d/icons/node25d_icon.png.import
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[remap]

importer="texture"
type="StreamTexture"
path="res://.import/node25d_icon.png-075c4b266c832f0f269670bad017ac93.stex"
metadata={
"vram_texture": false
}

[deps]

source_file="res://addons/node25d/icons/node25d_icon.png"
dest_files=[ "res://.import/node25d_icon.png-075c4b266c832f0f269670bad017ac93.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
Binary file added misc/2.5d/addons/node25d/icons/shadowmath25d.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 5906046

Please sign in to comment.