I’m currently quite happy how fireball turned out with sound and impact blast, but missing one thing to make smoked/burned area which this fireball hits.
Some idea how to make it ? some decals or other way to do it ?
I’m currently quite happy how fireball turned out with sound and impact blast, but missing one thing to make smoked/burned area which this fireball hits.
Some idea how to make it ? some decals or other way to do it ?
Maybe a combination of decals and some particles? Though with decals you tend to hit a limit pretty quickly I think, have to look into counting/ despawning them procedurally or on a timer
Use the normal of the surface you hit to place a decal
What role the particles would play? I thought about smoke but for fantasy it’s unnecessary.
Yes the timer is good idea, maybe some other mechanic which stop from being layered in one spot.
Do you mind elaborating a bit on normals? Do you mean get collisions normal or normal as resource for decal?
For now only albedo texture and global_position being used for it
Obviously there is no effect on houses or tree’s .
@Captain_Euphoria is certainly talking about the collision’s normal. If you are only using global_position the decal might not project properly on angled surfaces.
my current code looks like this
extends RayCast3D
@export var speed := 50.0
@export var explosion_scene: PackedScene
@export var decal_scene: PackedScene
@onready var remote_transform = RemoteTransform3D.new()
@onready var audio_stream_player_3d: AudioStreamPlayer3D = $AudioStreamPlayer3D
func _ready() -> void:
audio_stream_player_3d.play()
func _physics_process(delta: float) -> void:
position += global_basis * Vector3.FORWARD * delta * speed
target_position = Vector3.FORWARD * delta * speed
force_raycast_update()
var collider = get_collider()
if is_colliding():
audio_stream_player_3d.stop()
global_position = get_collision_point()
set_physics_process(false)
collider.add_child(remote_transform)
remote_transform.global_transform = global_transform
remote_transform.global_position = global_position
remote_transform.remote_path = remote_transform.get_path_to(self)
remote_transform.tree_exited.connect(cleanup)
_explode()
func cleanup() -> void:
queue_free()
print("cleaned")
func _explode() -> void:
if explosion_scene:
var explosion := explosion_scene.instantiate()
var decal := decal_scene.instantiate()
get_tree().current_scene.add_child(explosion)
get_tree().current_scene.add_child(decal)
explosion.global_position = global_position
decal.global_position = global_position
explosion.blast()
queue_free()
decal script itself
extends Node3D
var decals_amount
@export var max_decals_amount: int = 10
func _physics_process(delta: float) -> void:
decals_amount = get_tree().get_nodes_in_group("Decals").size()
if decals_amount >= max_decals_amount:
print("decals removed")
queue_free()
func _on_timer_timeout() -> void:
queue_free()
Current looks of decals, I noticed some square look on house .
How would I implement the get_collision_normal() ?
Your raycast can get normals.
var collider = get_collider()
if is_colliding():
var normal := get_collision_normal()
but then it seems like you are using remote transforms to copy this positional data instead of moving your _explode function under this if is_colliding() statement
if is_colliding():
audio_stream_player_3d.stop()
var point := get_collision_point()
var normal := get_collision_normal() # get normal
var rotate_up := Quaternion(Vector3.UP, normal) # use normal to rotate
var explosion := explosion_scene.instantiate()
var decal := decal_scene.instantiate()
explosion.position = point
decal.position = point
decal.quaternion = rotate_up # assign rotation
get_tree().current_scene.add_child(explosion)
get_tree().current_scene.add_child(decal)
explosion.blast()
queue_free()
using physics process to get_nodes_in_group is pretty bad; and every other decal will be doing this too. Your decals script could track the total decals with a static variable instead. A static variable is shared between every instance of the script, so increasing it on ready, and decrementing on _exit_tree works as cheap way to count every living instance.
static var decals_amount: int = 0
const MAX_DECALS_AMOUNT: int = 10
func _ready() -> void:
decals_amount += 1
if decals_amount > MAX_DECALS_AMOUNT:
var all_decals := get_tree().get_nodes_in_group("Decals")
# a better solution would find the oldest
all_decals.pick_random().queue_free()
func _exit_tree() -> void:
decals_amount -= 1
func _on_timer_timeout() -> void:
queue_free()
There is a decals exanple in the godot demo projects repository.
Thank you so much, code is so neat
.
I have tested it and had no idea what this normals was doing, when I tried to print it was in format of Vector3.
to find oldest decal I have added:
all_decals.pop_at(0).queue_free()
Had no idea I can use static variable in GDScript.
this is something new as well, might be worth to use this trick more often.
Idea came from arrow sticking to moving object, and it becomes independent of parent node via top level.
current look