For whatever reason bullet holes created when firing weapons do not rotate properly when the surface fired at is perfectly flat or level.

My code for bullet hole creation (r is the hitscan raycast node):

var b = bullethole.instantiate()
r.get_collider().add_child(b)
b.global_transform.origin = r.get_collision_point()
b.look_at(r.get_collision_point() + r.get_collision_normal())
  • Lethn After a bit of tweaking, I got a working function! Here it is:

    func create_bullethole(r):
    	var b = bullethole.instantiate()
    	r.get_collider().add_child(b)
    	b.global_transform.origin = r.get_collision_point()
    	if r.get_collision_normal() == Vector3(0,1,0):
    		b.look_at(r.get_collision_point() + r.get_collision_normal(), Vector3.RIGHT)
    	elif r.get_collision_normal() == Vector3(0,-1,0):
    		b.look_at(r.get_collision_point() + r.get_collision_normal(), Vector3.RIGHT)
    	else:
    		b.look_at(r.get_collision_point() + r.get_collision_normal())

    (variable r represents the hitscan raycast)

    I did end up leaving the bullet holes as children of the collided object because in my game bullet holes are also created on physics bodies able to move, and making them children of the root caused the hole to not stay on the object.

It's because you're adding the decal as a child of the collider, I've run into this problem before as well, I'm trying to remember but I think if you add it to the root or set it as TopLevel it should work, it's copying the scale of the collided object which is why it turns out so weird, you don't need to add the decal to the collider anymore for the decal to work properly, that's old code.

    Lethn The size isn't the issue, the rotation is. When I call the look_at() function when creating a decal on a level surface, the decal doesn't rotate to face upwards properly, instead facing forwards on the Z axis.

    		if SingleRaycast.is_colliding() == true:
    			
    			FRPistolAnimationPlayer.play("recoilFRPistol")
    			var plasmaBulletHoleChild = plasmaBulletHole.instantiate()
    			get_tree().get_root().add_child(plasmaBulletHoleChild)
    			plasmaBulletHoleChild.global_transform.origin = SingleRaycast.get_collision_point()
    			
    			FRPistolMuzzleFlash.emitting = true
    			FRPistolMuzzleFlash2.emitting = true
    			FRPistolMuzzleFlash3.emitting = true
    			muzzleFlashLight.visible = true
    			
    			
    			SingleRaycast.transform.origin = Vector3(randf_range(-0.8, 0.8), randf_range(-0.8, 0.8), -0.032)
    			
    			if SingleRaycast.get_collision_normal() == surfaceDirectionUp:
    				plasmaBulletHoleChild.look_at(SingleRaycast.get_collision_point() + SingleRaycast.get_collision_normal(), Vector3.RIGHT)
    			elif SingleRaycast.get_collision_normal() == surfaceDirectionDown:
    				plasmaBulletHoleChild.look_at(SingleRaycast.get_collision_point() + SingleRaycast.get_collision_normal(), Vector3.RIGHT)
    			else:
    				plasmaBulletHoleChild.look_at(SingleRaycast.get_collision_point() + SingleRaycast.get_collision_normal(), Vector3.UP)
    			
    			canFire = false
    			FireRateTimer.start()

    There were extra rotations that were posted up in the youtube comments I saw which I forgot about, my bad, this is what I ended up with for my controller, still wouldn't recommend adding as a child I think that causes problems with the aforementioned scaling, you don't want your decal node being stretched and if you aren't using a decal node, you absolutely should.

      Lethn After a bit of tweaking, I got a working function! Here it is:

      func create_bullethole(r):
      	var b = bullethole.instantiate()
      	r.get_collider().add_child(b)
      	b.global_transform.origin = r.get_collision_point()
      	if r.get_collision_normal() == Vector3(0,1,0):
      		b.look_at(r.get_collision_point() + r.get_collision_normal(), Vector3.RIGHT)
      	elif r.get_collision_normal() == Vector3(0,-1,0):
      		b.look_at(r.get_collision_point() + r.get_collision_normal(), Vector3.RIGHT)
      	else:
      		b.look_at(r.get_collision_point() + r.get_collision_normal())

      (variable r represents the hitscan raycast)

      I did end up leaving the bullet holes as children of the collided object because in my game bullet holes are also created on physics bodies able to move, and making them children of the root caused the hole to not stay on the object.

        5 months later

        09kingarthur hi there. I have a similar issue, except mine is being distorted when the bullet is fired at walls. Works fine on the floor.
        Video evidence:

        relevant code:
        func _physics_process(delta):
        if hit_cast.is_colliding():
        var col_nor = hit_cast.get_collision_normal()
        var col_point = hit_cast.get_collision_point()
        var b = bullet_impact.instantiate()
        hit_cast.get_collider().add_child(b)
        b.global_transform.origin = col_point
        if col_nor == Vector3.DOWN:
        b.rotation_degrees.x = 90
        elif col_nor != Vector3.UP:
        b.look_at(col_point - col_nor, Vector3(0, 1, 0))

          hivequ33n I think the Vector3 in the elif is incorrect. Try making it (1,0,0) instead of (0,1,0). Please tell me if this works!

            hivequ33n sorry I should say that I have been having this same error since the start

            Hmmm... I'm not sure. I haven't played around with Godot in a hot second. I'll look into this a bit and see if I can get it to work.

            hivequ33n Check if direction and up vectors coincide. If yes then use a different up vector.