node_25d.gd 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  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. @icon("res://addons/node25d/icons/node_25d_icon.png")
  5. extends Node2D
  6. class_name Node25D
  7. # SCALE is the number of 2D units in one 3D unit. Ideally, but not necessarily, an integer.
  8. const SCALE = 32
  9. # Exported spatial position for editor usage.
  10. @export var spatial_position: Vector3:
  11. get:
  12. # TODO: Manually copy the code from this method.
  13. return get_spatial_position()
  14. set(value):
  15. # TODO: Manually copy the code from this method.
  16. set_spatial_position(value)
  17. # GDScript throws errors when Basis25D is its own structure.
  18. # There is a broken implementation in a hidden folder.
  19. # https://github.com/godotengine/godot/issues/21461
  20. # https://github.com/godotengine/godot-proposals/issues/279
  21. var _basisX: Vector2
  22. var _basisY: Vector2
  23. var _basisZ: Vector2
  24. # Cache the spatial stuff for internal use.
  25. var _spatial_position: Vector3
  26. var _spatial_node: Node3D
  27. # These are separated in case anyone wishes to easily extend Node25D.
  28. func _ready():
  29. Node25D_ready()
  30. func _process(_delta):
  31. Node25D_process()
  32. # Call this method in _ready, or before Node25D_process is first ran.
  33. func Node25D_ready():
  34. _spatial_node = get_child(0)
  35. # Changing the values here will change the default for all Node25D instances.
  36. _basisX = SCALE * Vector2(1, 0)
  37. _basisY = SCALE * Vector2(0, -0.70710678118)
  38. _basisZ = SCALE * Vector2(0, 0.70710678118)
  39. # Call this method in _process, or whenever the position of this object changes.
  40. func Node25D_process():
  41. _check_view_mode()
  42. if _spatial_node == null:
  43. return
  44. _spatial_position = _spatial_node.position
  45. var flat_pos = _spatial_position.x * _basisX
  46. flat_pos += _spatial_position.y * _basisY
  47. flat_pos += _spatial_position.z * _basisZ
  48. global_position = flat_pos
  49. func get_basis():
  50. return [_basisX, _basisY, _basisZ]
  51. func get_spatial_position():
  52. if not _spatial_node:
  53. _spatial_node = get_child(0)
  54. return _spatial_node.position
  55. func set_spatial_position(value):
  56. _spatial_position = value
  57. if _spatial_node:
  58. _spatial_node.position = value
  59. elif get_child_count() > 0:
  60. _spatial_node = get_child(0)
  61. # Change the basis based on the view_mode_index argument.
  62. # This can be changed or removed in actual games where you only need one view mode.
  63. func set_view_mode(view_mode_index):
  64. match view_mode_index:
  65. 0: # 45 Degrees
  66. _basisX = SCALE * Vector2(1, 0)
  67. _basisY = SCALE * Vector2(0, -0.70710678118)
  68. _basisZ = SCALE * Vector2(0, 0.70710678118)
  69. 1: # Isometric
  70. _basisX = SCALE * Vector2(0.86602540378, 0.5)
  71. _basisY = SCALE * Vector2(0, -1)
  72. _basisZ = SCALE * Vector2(-0.86602540378, 0.5)
  73. 2: # Top Down
  74. _basisX = SCALE * Vector2(1, 0)
  75. _basisY = SCALE * Vector2(0, 0)
  76. _basisZ = SCALE * Vector2(0, 1)
  77. 3: # Front Side
  78. _basisX = SCALE * Vector2(1, 0)
  79. _basisY = SCALE * Vector2(0, -1)
  80. _basisZ = SCALE * Vector2(0, 0)
  81. 4: # Oblique Y
  82. _basisX = SCALE * Vector2(1, 0)
  83. _basisY = SCALE * Vector2(-0.70710678118, -0.70710678118)
  84. _basisZ = SCALE * Vector2(0, 1)
  85. 5: # Oblique Z
  86. _basisX = SCALE * Vector2(1, 0)
  87. _basisY = SCALE * Vector2(0, -1)
  88. _basisZ = SCALE * Vector2(-0.70710678118, 0.70710678118)
  89. # Check if anyone presses the view mode buttons and change the basis accordingly.
  90. # This can be changed or removed in actual games where you only need one view mode.
  91. func _check_view_mode():
  92. if not Engine.is_editor_hint():
  93. if Input.is_action_just_pressed(&"forty_five_mode"):
  94. set_view_mode(0)
  95. elif Input.is_action_just_pressed(&"isometric_mode"):
  96. set_view_mode(1)
  97. elif Input.is_action_just_pressed(&"top_down_mode"):
  98. set_view_mode(2)
  99. elif Input.is_action_just_pressed(&"front_side_mode"):
  100. set_view_mode(3)
  101. elif Input.is_action_just_pressed(&"oblique_y_mode"):
  102. set_view_mode(4)
  103. elif Input.is_action_just_pressed(&"oblique_z_mode"):
  104. set_view_mode(5)
  105. # Used by YSort25D
  106. static func y_sort(a: Node25D, b: Node25D):
  107. return a._spatial_position.y < b._spatial_position.y
  108. static func y_sort_slight_xz(a: Node25D, b: Node25D):
  109. var a_index = a._spatial_position.y + 0.001 * (a._spatial_position.x + a._spatial_position.z)
  110. var b_index = b._spatial_position.y + 0.001 * (b._spatial_position.x + b._spatial_position.z)
  111. return a_index < b_index