Skip to content

Commit

Permalink
Rework demo and cache materials
Browse files Browse the repository at this point in the history
  • Loading branch information
Jummit committed Sep 28, 2023
1 parent 94c01e4 commit 3947a05
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 57 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 7.1

### Changed

* The demo now features multiple cubes, which can be destroyed by clicking on them.
* The background now is a more neutral color.

### Added

* Reuse modified shard materials if they are the same.

## 7.0

### Changed
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ For Godot **4** and above, download the addon from the [releases](https://github

## Performance

The plugin is only tested in very small scenes. It currently creates a new material for each shard, resulting in a lot of unecessary draw calls. It also creates the shards on the main thread by default. Set the thread group to separate when that becomes an issue.
The plugin is only tested in very small scenes.

## License

Expand Down
70 changes: 40 additions & 30 deletions addons/destruction/destruction.gd
Original file line number Diff line number Diff line change
Expand Up @@ -32,23 +32,41 @@ extends Node
@export_flags_3d_physics var collision_mask = 1

## Cached shard meshes (instantiated from [member fragmented]).
static var cached_meshes := {}
static var _cached_meshes := {}
## Cached collision shapes.
static var cached_shapes := {}
static var _cached_shapes := {}

var _modified_materials := {}

## Remove the parent node and add shards to the shard container.
func destroy(explosion_power := 1.0) -> void:
if not fragmented in cached_meshes:
cached_meshes[fragmented] = fragmented.instantiate()
for shard_mesh in cached_meshes[fragmented].get_children():
cached_shapes[shard_mesh] = shard_mesh.mesh.create_convex_shape()
var original_meshes = cached_meshes[fragmented]
for original in original_meshes.get_children():
if original is MeshInstance3D:
_add_shard(original, explosion_power)
for shard in _get_shards():
_add_shard(shard, explosion_power)
get_parent().queue_free()


## Returns the list of shard meshes in the [member fragmented] scene.
func _get_shards() -> Array[Node]:
if not fragmented in _cached_meshes:
_cached_meshes[fragmented] = fragmented.instantiate()
for shard_mesh in _cached_meshes[fragmented].get_children():
_cached_shapes[shard_mesh] = shard_mesh.mesh.create_convex_shape()
return (_cached_meshes[fragmented].get_children() as Array)\
.filter(func(node): return node is MeshInstance3D)


func set_fragmented(to: PackedScene) -> void:
fragmented = to
if is_inside_tree():
get_tree().node_configuration_warning_changed.emit(self)


func _get_configuration_warnings() -> PackedStringArray:
return ["No fragmented version set"] if not fragmented else []


## Turns a mesh shard into a rigid body and adds it to the
## [member shard_container].
func _add_shard(original: MeshInstance3D, explosion_power: float) -> void:
var body := RigidBody3D.new()
var mesh := MeshInstance3D.new()
Expand All @@ -59,21 +77,23 @@ func _add_shard(original: MeshInstance3D, explosion_power: float) -> void:
body.global_position = get_parent().global_transform.origin + original.position
body.collision_layer = collision_layer
body.collision_mask = collision_mask
shape.shape = cached_shapes[original]
shape.shape = _cached_shapes[original]
mesh.mesh = original.mesh
if fade_delay >= 0:
var material = original.mesh.surface_get_material(0)
if material is StandardMaterial3D:
material = material.duplicate()
material.flags_transparent = true
get_tree().create_tween().tween_property(material, "albedo_color",
Color(1, 1, 1, 0), animation_length)\
.set_delay(fade_delay)\
.set_trans(Tween.TRANS_EXPO)\
.set_ease(Tween.EASE_OUT)
mesh.material_override = material
if not material in _modified_materials:
var modified = material.duplicate()
modified.flags_transparent = true
get_tree().create_tween().tween_property(modified,
"albedo_color", Color(1, 1, 1, 0), animation_length)\
.set_delay(fade_delay)\
.set_trans(Tween.TRANS_EXPO)\
.set_ease(Tween.EASE_OUT)
_modified_materials[material] = modified
mesh.material_override = _modified_materials[material]
else:
push_warning("Shard doesn't use a StandardMaterial3D, can't add transparency.")
push_warning("Shard doesn't use a StandardMaterial3D, can't add transparency. Set fade_delay to -1 to remove this warning.")
body.apply_impulse(_random_direction() * explosion_power,
-original.position.normalized())
if shrink_delay < 0 and fade_delay < 0:
Expand All @@ -86,15 +106,5 @@ func _add_shard(original: MeshInstance3D, explosion_power: float) -> void:
tween.finished.connect(func(): body.queue_free())


func set_fragmented(to: PackedScene) -> void:
fragmented = to
if is_inside_tree():
get_tree().node_configuration_warning_changed.emit(self)


func _get_configuration_warnings() -> PackedStringArray:
return ["No fragmented version set"] if not fragmented else []


static func _random_direction() -> Vector3:
return (Vector3(randf(), randf(), randf()) - Vector3.ONE / 2.0).normalized() * 2.0
3 changes: 2 additions & 1 deletion cube/destructible_cube.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ mesh = SubResource("BoxMesh_h5eed")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
shape = SubResource("BoxShape3D_uo2w7")

[node name="Destruction" type="Node" parent="."]
[node name="Destruction" type="Node" parent="." node_paths=PackedStringArray("shard_container")]
script = ExtResource("2_gffgg")
fragmented = ExtResource("3_c1ox2")
shard_container = NodePath("")
33 changes: 23 additions & 10 deletions demo.gd
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,31 @@ extends Node3D

## Demo of the Destruction plugin.
##
## Shows a [RigidBody3D] cube which can be destroyed by clicking a button.

@onready var _destruction: Destruction = $DestructibleCube/Destruction
@onready var _destroy_button: Button = $DestroyButton
## Shows multiple [RigidBody3D] cubes which can be destroyed by
## clicking on them.

var _destructible_cube_scene := preload("res://cube/destructible_cube.tscn")

func _on_destroy_button_pressed() -> void:
_destruction.destroy(5)
_destroy_button.disabled = true
await get_tree().create_timer(1).timeout
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("destroy"):
var camera := get_viewport().get_camera_3d()
var parameters := PhysicsRayQueryParameters3D.create(
camera.global_position,
camera.project_position(get_viewport().get_mouse_position(), 30))
parameters.collision_mask = 1
var result := get_world_3d().direct_space_state.intersect_ray(parameters)
if not result or not result.collider.has_node("Destruction"):
return
_destroy_and_respawn(result.collider)
elif event.is_action_pressed("destroy_all"):
for child in get_children():
if child.has_node("Destruction"):
_destroy_and_respawn(child)


func _destroy_and_respawn(cube):
cube.get_node("Destruction").destroy(7)
var new := _destructible_cube_scene.instantiate()
new.position = cube.position
await get_tree().create_timer(1).timeout
add_child(new)
_destruction = new.get_node("Destruction")
_destroy_button.disabled = false
35 changes: 20 additions & 15 deletions demo.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ data = PackedVector3Array(-10, 0.25, 10, 10, 0.25, 10, -10, -0.25, 10, 10, 0.25,

[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_d8soj"]
sky_horizon_color = Color(0.64625, 0.65575, 0.67075, 1)
ground_bottom_color = Color(0.819608, 0.764706, 0.705882, 1)
ground_horizon_color = Color(0.64625, 0.65575, 0.67075, 1)
ground_curve = 0.710125

[sub_resource type="Sky" id="Sky_mukl1"]
sky_material = SubResource("ProceduralSkyMaterial_d8soj")
Expand All @@ -28,6 +30,14 @@ script = ExtResource("1_xvv5r")
editor_description = "The cube is replaced with a new instance once destroyed."
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.490263, 0)

[node name="DestructibleCube2" parent="." instance=ExtResource("2_1ot51")]
editor_description = "The cube is replaced with a new instance once destroyed."
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.67448, 0.490263, 3.44743)

[node name="DestructibleCube3" parent="." instance=ExtResource("2_1ot51")]
editor_description = "The cube is replaced with a new instance once destroyed."
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.36574, 0.490263, -2.25104)

[node name="Floor" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.759737, 0)
mesh = SubResource("BoxMesh_yig7p")
Expand All @@ -46,18 +56,13 @@ transform = Transform3D(-0.866023, -0.433016, 0.250001, 0, 0.499998, 0.866027, -
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
environment = SubResource("Environment_hh15e")

[node name="DestroyButton" type="Button" parent="."]
anchors_preset = 7
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -84.5
offset_top = -97.0
offset_right = 84.5
offset_bottom = -21.0
grow_horizontal = 2
grow_vertical = 0
text = "Destroy"

[connection signal="pressed" from="DestroyButton" to="." method="_on_destroy_button_pressed"]
[node name="HintLabel" type="Label" parent="."]
offset_left = 21.0
offset_top = 20.0
offset_right = 293.0
offset_bottom = 43.0
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
theme_override_constants/outline_size = 15
theme_override_font_sizes/font_size = 31
text = "Click on a cube to destroy it.
Press [A] or right mouse button to destroy all."

0 comments on commit 3947a05

Please sign in to comment.