Interior Maps #13
Binary file not shown.
BIN
map_editor.scn (Stored with Git LFS)
BIN
map_editor.scn (Stored with Git LFS)
Binary file not shown.
|
@ -0,0 +1,18 @@
|
|||
class_name MapEditorMenu extends VBoxContainer
|
||||
|
||||
##
|
||||
##
|
||||
##
|
||||
signal edit_activated(edit_action: Callable)
|
||||
|
||||
##
|
||||
##
|
||||
##
|
||||
func activate_editor(edit_action: Callable) -> void:
|
||||
edit_activated.emit(edit_action)
|
||||
|
||||
##
|
||||
##
|
||||
##
|
||||
func reset() -> void:
|
||||
pass
|
Binary file not shown.
|
@ -0,0 +1,122 @@
|
|||
class_name MeshGrid extends Node3D
|
||||
|
||||
const _CELL_COUNT := 8
|
||||
|
||||
class _Chunk:
|
||||
var multimesh_instances: Array[_MultimeshInstance] = []
|
||||
|
||||
var meshes: Array[Mesh] = []
|
||||
|
||||
func _init() -> void:
|
||||
self.meshes.resize(_CELL_COUNT * _CELL_COUNT)
|
||||
|
||||
class _MultimeshInstance:
|
||||
var _instance_rid := RID()
|
||||
|
||||
var _multimesh_rid := RID()
|
||||
|
||||
func _init(scenario_rid: RID, mesh: Mesh, transforms: Array) -> void:
|
||||
_multimesh_rid = RenderingServer.multimesh_create()
|
||||
_instance_rid = RenderingServer.instance_create2(_multimesh_rid, scenario_rid)
|
||||
|
||||
RenderingServer.multimesh_set_mesh(_multimesh_rid, mesh.get_rid())
|
||||
|
||||
var transform_count := transforms.size()
|
||||
|
||||
RenderingServer.multimesh_allocate_data(_multimesh_rid,
|
||||
transform_count, RenderingServer.MULTIMESH_TRANSFORM_3D, true)
|
||||
|
||||
for i in transform_count:
|
||||
RenderingServer.multimesh_instance_set_transform(_multimesh_rid, i, transforms[i])
|
||||
|
||||
var _chunks: Array[_Chunk] = []
|
||||
|
||||
var _grid_origin := Vector2(0.5, 0.5)
|
||||
|
||||
##
|
||||
## The number of horizontal and vertical cells in the current grid.
|
||||
##
|
||||
## Setting this value will result in the current grid data being completely overwritten to return
|
||||
## it to a sensible default.
|
||||
##
|
||||
@export
|
||||
var size: Vector2i:
|
||||
set(value):
|
||||
self._chunks.resize(int(ceil((value.x * value.y) / float(_CELL_COUNT))))
|
||||
|
||||
for i in self._chunks.size():
|
||||
self._chunks[i] = _Chunk.new()
|
||||
|
||||
size = value
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
match what:
|
||||
NOTIFICATION_TRANSFORM_CHANGED:
|
||||
for chunk in _chunks:
|
||||
for multimesh_instance in chunk.multimesh_instances:
|
||||
RenderingServer.instance_set_transform(
|
||||
multimesh_instance._instance_rid, multimesh_instance.global_transform)
|
||||
|
||||
##
|
||||
## Sets the cell at [code]coordinate[/code] to display [code]mesh[/code].
|
||||
##
|
||||
## Note that this function assumes [code]coordinate[/code] is within the bounds of the grid
|
||||
## coordinate space.
|
||||
##
|
||||
## Note that changes to a cell result in the chunk it resides in being completely regenerated as
|
||||
## part of its modification, and therefore has an implicitly higher overhead to other setter
|
||||
## operations defined by [MeshGrid].
|
||||
##
|
||||
func set_mesh(coordinate: Vector2i, mesh: Mesh) -> void:
|
||||
assert(Rect2i(Vector2i.ZERO, size).has_point(coordinate), "coordinate must be within grid")
|
||||
|
||||
# TODO: Once this is all lowered into native code, look for ways to parallelize the loops.
|
||||
var chunk_coordinate := coordinate / _CELL_COUNT
|
||||
var cell_coordinate := coordinate % _CELL_COUNT
|
||||
var chunk := _chunks[(size.x * chunk_coordinate.y) + chunk_coordinate.x]
|
||||
var chunk_meshes := chunk.meshes
|
||||
|
||||
chunk_meshes[(_CELL_COUNT * cell_coordinate.y) + cell_coordinate.x] = mesh
|
||||
|
||||
# Invalide any existing baked multimeshes in the chunk.
|
||||
for multimesh_instance in chunk.multimesh_instances:
|
||||
RenderingServer.free_rid(multimesh_instance._instance_rid)
|
||||
RenderingServer.free_rid(multimesh_instance._multimesh_rid)
|
||||
|
||||
chunk.multimesh_instances.clear()
|
||||
|
||||
# Normalize mesh instance data for the chunk.
|
||||
var mesh_transform_sets := {}
|
||||
|
||||
for i in chunk_meshes.size():
|
||||
var chunk_mesh := chunk_meshes[i]
|
||||
|
||||
if chunk_mesh != null:
|
||||
if not(chunk_mesh in mesh_transform_sets):
|
||||
mesh_transform_sets[chunk_mesh] = []
|
||||
|
||||
mesh_transform_sets[chunk_mesh].push_back(Transform3D(Basis.IDENTITY, Vector3(
|
||||
(float(chunk_coordinate.x * _CELL_COUNT) + (i % _CELL_COUNT)) -
|
||||
(float(size.x) * _grid_origin.x), 0.0,
|
||||
(float(chunk_coordinate.y * _CELL_COUNT) + (i / _CELL_COUNT)) -
|
||||
(float(size.y) * _grid_origin.y))))
|
||||
|
||||
# Bake into multimesh instances for the chunk.
|
||||
var scenario_rid := get_world_3d().scenario
|
||||
|
||||
for chunk_mesh in mesh_transform_sets:
|
||||
var multimesh_instance :=\
|
||||
_MultimeshInstance.new(scenario_rid, chunk_mesh, mesh_transform_sets[chunk_mesh])
|
||||
|
||||
RenderingServer.instance_set_transform(multimesh_instance._instance_rid, global_transform)
|
||||
|
||||
chunk.multimesh_instances.push_back(multimesh_instance)
|
||||
|
||||
##
|
||||
## Returns [code]world_position[/code] converted into a coordinate aligned with the [MeshGrid].
|
||||
##
|
||||
## Note that [code]world_position[/code] values not within the [MeshGrid] will produce grid
|
||||
## coordinates outside of the [MeshGrid] bounds as well.
|
||||
##
|
||||
func world_to_grid(world_position: Vector2) -> Vector2i:
|
||||
return Vector2i((world_position + (Vector2(size) * _grid_origin)).floor())
|
|
@ -192,7 +192,7 @@ func get_cursor_point() -> Vector2:
|
|||
## Successful point intersection will return the plane coordinates in a [class Vector2], otherwise
|
||||
## [code]null[/code] is returned.
|
||||
##
|
||||
func screen_to_plane(screen_point: Vector2):
|
||||
func screen_to_plane(screen_point: Vector2) -> Variant:
|
||||
if self._camera != null:
|
||||
var plane_target = Plane(Vector3.UP, 0.0).intersects_ray(
|
||||
self._camera.global_position, self._camera.project_ray_normal(screen_point))
|
||||
|
|
|
@ -9,21 +9,36 @@
|
|||
config_version=5
|
||||
|
||||
_global_script_classes=[{
|
||||
"base": "Control",
|
||||
"class": &"ActionPrompt",
|
||||
"language": &"GDScript",
|
||||
"path": "res://user_interface/action_prompt.gd"
|
||||
}, {
|
||||
"base": "HFlowContainer",
|
||||
"class": &"ItemSelection",
|
||||
"language": &"GDScript",
|
||||
"path": "res://user_interface/button_selection.gd"
|
||||
}, {
|
||||
"base": "Node",
|
||||
"class": &"MapEditorTerrainCanvas",
|
||||
"base": "VBoxContainer",
|
||||
"class": &"MapEditorMenu",
|
||||
"language": &"GDScript",
|
||||
"path": "res://map_editor/map_editor_terrain_canvas.gd"
|
||||
"path": "res://map_editor/map_editor_menu.gd"
|
||||
}, {
|
||||
"base": "Node3D",
|
||||
"class": &"MeshGrid",
|
||||
"language": &"GDScript",
|
||||
"path": "res://mesh_grid.gd"
|
||||
}, {
|
||||
"base": "Node3D",
|
||||
"class": &"PlayerController",
|
||||
"language": &"GDScript",
|
||||
"path": "res://player_controller.gd"
|
||||
}, {
|
||||
"base": "Control",
|
||||
"class": &"SelectionPrompt",
|
||||
"language": &"GDScript",
|
||||
"path": "res://user_interface/selection_prompt.gd"
|
||||
}, {
|
||||
"base": "Node",
|
||||
"class": &"Settings",
|
||||
"language": &"GDScript",
|
||||
|
@ -34,18 +49,33 @@ _global_script_classes=[{
|
|||
"language": &"GDScript",
|
||||
"path": "res://terrain/terrain_instance_3d.gd"
|
||||
}, {
|
||||
"base": "Node",
|
||||
"class": &"TerrainMapCanvas",
|
||||
"language": &"GDScript",
|
||||
"path": "res://terrain/terrain_map_canvas.gd"
|
||||
}, {
|
||||
"base": "Resource",
|
||||
"class": &"TerrainPaint",
|
||||
"language": &"GDScript",
|
||||
"path": "res://terrain/paints/terrain_paint.gd"
|
||||
}, {
|
||||
"base": "Control",
|
||||
"class": &"WorkerPrompt",
|
||||
"language": &"GDScript",
|
||||
"path": "res://user_interface/worker_prompt.gd"
|
||||
}]
|
||||
_global_script_class_icons={
|
||||
"ActionPrompt": "",
|
||||
"ItemSelection": "",
|
||||
"MapEditorTerrainCanvas": "",
|
||||
"MapEditorMenu": "",
|
||||
"MeshGrid": "",
|
||||
"PlayerController": "",
|
||||
"SelectionPrompt": "",
|
||||
"Settings": "",
|
||||
"TerrainInstance3D": "",
|
||||
"TerrainPaint": ""
|
||||
"TerrainMapCanvas": "",
|
||||
"TerrainPaint": "",
|
||||
"WorkerPrompt": ""
|
||||
}
|
||||
|
||||
[application]
|
||||
|
@ -59,6 +89,7 @@ config/icon="res://icon.png"
|
|||
[autoload]
|
||||
|
||||
GameSettings="*res://settings.gd"
|
||||
LocalPlayer="*res://local_player.scn"
|
||||
|
||||
[debug]
|
||||
|
||||
|
|
|
@ -10,9 +10,9 @@ const _SHADER := preload("res://terrain/terrain.gdshader")
|
|||
##
|
||||
enum PaintSlot {
|
||||
ERASE,
|
||||
RED,
|
||||
GREEN,
|
||||
BLUE,
|
||||
PAINT_1,
|
||||
PAINT_2,
|
||||
PAINT_3,
|
||||
}
|
||||
|
||||
var _albedo_maps: Array[Texture2D] = [null, null, null, null]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
class_name MapEditorTerrainCanvas extends Node
|
||||
class_name TerrainMapCanvas extends Node
|
||||
|
||||
##
|
||||
## Blank sample value.
|
||||
|
@ -37,7 +37,11 @@ func clear(clear_elevation: float) -> void:
|
|||
_editable_texture.update(_editable_image)
|
||||
|
||||
##
|
||||
## Returns the canvas data as a [Texture2D].
|
||||
## Returns the canvas data as a [Texture2D] where RGB channels are encoded in the red, green, and
|
||||
## blue, component of each pixel and the height is the alpha component.
|
||||
##
|
||||
## The returned value is designed for use with a [TerrainInstance3D] or other class compatible with
|
||||
## the encoded format specified above.
|
||||
##
|
||||
func get_texture() -> Texture2D:
|
||||
return _editable_texture
|
|
@ -0,0 +1,43 @@
|
|||
@tool
|
||||
class_name ActionPrompt extends Control
|
||||
|
||||
##
|
||||
##
|
||||
##
|
||||
signal prompt_accepted()
|
||||
kayomn marked this conversation as resolved
|
||||
|
||||
##
|
||||
##
|
||||
##
|
||||
signal prompted()
|
||||
kayomn marked this conversation as resolved
kayomn
commented
Missing doc comment. Missing doc comment.
|
||||
|
||||
@export
|
||||
var _accept_button: BaseButton = null
|
||||
|
||||
@export
|
||||
var initial_focus: Control = null
|
||||
|
||||
func _get_configuration_warnings() -> PackedStringArray:
|
||||
var warnings := PackedStringArray()
|
||||
|
||||
if _accept_button == null:
|
||||
warnings.append("`Accept Button` must point to a valid BaseButton instance")
|
||||
|
||||
return warnings
|
||||
|
||||
func _ready() -> void:
|
||||
assert(_accept_button != null, "accept button cannot be null")
|
||||
|
||||
_accept_button.pressed.connect(func () -> void:
|
||||
prompt_accepted.emit()
|
||||
hide())
|
||||
|
||||
##
|
||||
##
|
||||
##
|
||||
func prompt() -> void:
|
||||
kayomn marked this conversation as resolved
kayomn
commented
Missing doc comment. Missing doc comment.
|
||||
show()
|
||||
prompted.emit()
|
||||
|
||||
if initial_focus != null:
|
||||
initial_focus.grab_focus()
|
|
@ -0,0 +1,68 @@
|
|||
@tool
|
||||
class_name SelectionPrompt extends Control
|
||||
|
||||
##
|
||||
##
|
||||
##
|
||||
signal prompted()
|
||||
kayomn marked this conversation as resolved
kayomn
commented
Missing doc comment. Missing doc comment.
|
||||
|
||||
##
|
||||
##
|
||||
##
|
||||
signal prompt_selected(selected_item: int)
|
||||
kayomn marked this conversation as resolved
kayomn
commented
Missing doc comment. Missing doc comment.
|
||||
|
||||
@export
|
||||
var _button_group: ButtonGroup = null
|
||||
|
||||
##
|
||||
## If set to [code]true[/code], the selection prompt dismisses itself after a selection is made.
|
||||
## Otherwise, if set to [code]false[/code], the prompt remains present after a selection is made.
|
||||
##
|
||||
@export
|
||||
var dismiss_on_selection := false
|
||||
|
||||
func _get_configuration_warnings() -> PackedStringArray:
|
||||
var warnings := PackedStringArray()
|
||||
|
||||
if _button_group == null:
|
||||
warnings.append("`Button Group` must point to a valid BaseButton instance")
|
||||
|
||||
return warnings
|
||||
|
||||
func _ready() -> void:
|
||||
assert(_button_group != null, "button group cannot be null")
|
||||
|
||||
_button_group.pressed.connect(func (button: BaseButton) -> void:
|
||||
var selected_item = _button_group.get_buttons().find(button)
|
||||
|
||||
assert(selected_item > -1, "selected item cannot be less than 0")
|
||||
prompt_selected.emit(selected_item)
|
||||
|
||||
if dismiss_on_selection:
|
||||
hide())
|
||||
|
||||
##
|
||||
## Returns the [ButtonGroup] used by the selection prompt to handle selections.
|
||||
##
|
||||
func get_button_group() -> ButtonGroup:
|
||||
return _button_group
|
||||
|
||||
##
|
||||
## Displays the selection prompt, with [code]initial_selection[/code] as the initial value selected.
|
||||
##
|
||||
## Once a value has been manually selected in the prompt, [signal prompt_selected] is emitted.
|
||||
##
|
||||
## *Note* that [code]initial_selection[/code] *must* be a valid index in the range of the button
|
||||
## group used by the selection prompt, which may be accessed by the calling logic via
|
||||
## [member get_button_group]
|
||||
##
|
||||
func prompt(initial_selection: int) -> void:
|
||||
assert(_button_group != null, "button group cannot be null")
|
||||
|
||||
var selected_button := _button_group.get_buttons()[initial_selection]
|
||||
|
||||
selected_button.button_pressed = true
|
||||
|
||||
show()
|
||||
selected_button.grab_focus()
|
||||
prompted.emit()
|
|
@ -0,0 +1,46 @@
|
|||
class_name WorkerPrompt extends Control
|
||||
|
||||
##
|
||||
##
|
||||
##
|
||||
signal prompted()
|
||||
kayomn marked this conversation as resolved
kayomn
commented
Missing doc comment. Missing doc comment.
|
||||
|
||||
var _worker_thread := Thread.new()
|
||||
|
||||
##
|
||||
##
|
||||
kayomn marked this conversation as resolved
kayomn
commented
Missing doc comment. Missing doc comment.
|
||||
##
|
||||
@export
|
||||
var label: Label = null
|
||||
|
||||
##
|
||||
##
|
||||
##
|
||||
@export
|
||||
kayomn marked this conversation as resolved
kayomn
commented
Missing doc comment. Missing doc comment.
|
||||
var progress: Range = null
|
||||
|
||||
##
|
||||
##
|
||||
##
|
||||
func prompt(display_message: String, steps: Array) -> void:
|
||||
kayomn marked this conversation as resolved
kayomn
commented
Missing doc comment. Missing doc comment.
|
||||
show()
|
||||
prompted.emit()
|
||||
|
||||
if label != null:
|
||||
label.text = display_message
|
||||
|
||||
_worker_thread.start(func () -> void:
|
||||
var count := steps.size()
|
||||
var total := float(count)
|
||||
|
||||
for i in count:
|
||||
steps[i].call()
|
||||
|
||||
if progress != null:
|
||||
progress.value = i / total)
|
||||
|
||||
while _worker_thread.is_alive():
|
||||
await get_tree().process_frame
|
||||
|
||||
_worker_thread.wait_to_finish()
|
||||
hide()
|
Loading…
Reference in New Issue
Missing doc comment.