Skip to content

Instantly share code, notes, and snippets.

@HungryProton
Last active December 12, 2025 11:13
Show Gist options
  • Select an option

  • Save HungryProton/4d33370d5cea40d73656fd1bccc7444a to your computer and use it in GitHub Desktop.

Select an option

Save HungryProton/4d33370d5cea40d73656fd1bccc7444a to your computer and use it in GitHub Desktop.
Import script example
@tool
extends EditorScenePostImport
## Import script example
##
## GENERAL
## + Removes the empty root node (if any)
##
## COLLISIONS:
## + Append '-static' to the node name to mark it as a static body
## + Add child empties to create the collision shapes
## - Supported shapes: -collider_box, -collider_sphere, -collider_cylinder
##
const MAIN_MATERIAL: ShaderMaterial = preload("uid://crgogfd6g7nbe")
const SIMPLE_MATERIAL: ShaderMaterial = preload("uid://cwuralgaypox7")
func _post_import(scene: Node) -> Object:
_remove_no_import(scene)
scene = _process_colliders(scene)
_update_materials(scene)
# If the imported asset has only a single root node, that's the only node
# we're interested in:
if scene.get_child_count() == 1:
return _remove_root_node(scene)
# If the asset contains animations, Godot's importer will put them into an
# AnimationPlayer node. If there's only a single root object, but we also
# have animations, we need to handle this explicitly:
elif scene.get_child_count() == 2 and scene.get_child(1) is AnimationPlayer:
# Grab the AnimationPlayer that was generated from the asset's animations.
var anim_player : AnimationPlayer = scene.get_child(1)
# Convert the scene using our little trick.
var new_scene := _remove_root_node(scene)
# The following might seem a little verbose, but we have to be this
# exact in order to not trigger various Godot warnings.
scene.remove_child(anim_player)
anim_player.owner = null
new_scene.add_child(anim_player)
anim_player.owner = new_scene
return new_scene
# In all other cases, we will just return the scene as originally imported.
else:
return scene
func _update_materials(root: Node) -> void:
var instance: GeometryInstance3D = root as GeometryInstance3D
if instance:
var material: ShaderMaterial = MAIN_MATERIAL
for c in root.get_children():
if c is StaticBody3D:
material = SIMPLE_MATERIAL
break
instance.material_override = material
for child: Node in root.get_children():
_update_materials(child)
func _remove_root_node(scene: Node) -> Node:
var new_root: Node = scene.get_child(0)
var parent: Node = new_root.get_parent()
parent.remove_child(new_root)
new_root.owner = null
_set_new_owner(new_root, new_root)
return new_root
const NO_IMPORT := "-noimport"
func _remove_no_import(scene: Node) -> void:
if scene.name.ends_with(NO_IMPORT):
scene.queue_free()
return
for c in scene.get_children():
_remove_no_import(c)
const COLLISION_STATIC := &"static"
const COLLISION_ANIMATABLE := &"animatable"
const COLLISION_KINEMATIC := &"kinematic"
const COLLISION_RIGID := &"rigid"
const COLLIDER_ROOT := &"-collider_"
const COLLIDER_BOX := &"_box"
const COLLIDER_SPHERE := &"_sphere"
const COLLIDER_CYLINDER := &"_cylinder"
const COLLIDER_CAPSULE := &"_capsule"
const COLLISIONS := [COLLISION_STATIC, COLLISION_ANIMATABLE, COLLISION_KINEMATIC, COLLISION_RIGID]
func _process_colliders(root: Node) -> Node:
if root is StaticBody3D: # Auto generated with -col & -colonly
var static_body: StaticBody3D = root
static_body.collision_layer = PhysicsUtil.LAYER_LEVEL
static_body.collision_mask = 0
elif root is Node3D:
root = _process_colliders_for_node(root as Node3D)
for child in root.get_children():
@warning_ignore("return_value_discarded")
_process_colliders(child)
return root
func _process_colliders_for_node(node: Node3D) -> Node3D:
var splits := node.name.split("-")
var type: String = splits[-1]
if not type in COLLISIONS:
return node
node.name = node.name.trim_suffix("-" + type)
var body: PhysicsBody3D
match type:
COLLISION_STATIC:
body = StaticBody3D.new()
body.collision_layer = PhysicsUtil.LAYER_LEVEL
body.collision_mask = 0
COLLISION_ANIMATABLE:
body = AnimatableBody3D.new()
COLLISION_KINEMATIC:
body = CharacterBody3D.new()
COLLISION_RIGID:
body = RigidBody3D.new()
body.name = node.name + "Collider"
body.scale = Vector3.ONE
for child: Node in node.get_children():
if not child is Node3D:
continue
var ref: Node3D = child as Node3D
var shape := CollisionShape3D.new()
shape.transform = ref.transform
shape.scale = Vector3.ONE
if ref.name.ends_with(COLLIDER_BOX):
var box_shape := BoxShape3D.new()
box_shape.size = ref.scale * 2.0
shape.shape = box_shape
elif ref.name.ends_with(COLLIDER_SPHERE):
var sphere_shape := SphereShape3D.new()
# TODO: Check is the scale is the same on all 3 axis
sphere_shape.radius = (ref.scale.x + ref.scale.y + ref.scale.z) / 3.0 # HACK
shape.shape = sphere_shape
elif ref.name.ends_with(COLLIDER_CYLINDER):
var cylinder_shape := CylinderShape3D.new()
cylinder_shape.height = ref.scale.y * 2.0
cylinder_shape.radius = (ref.scale.x + ref.scale.z) / 2.0
shape.shape = cylinder_shape
elif ref.name.ends_with(COLLIDER_CAPSULE):
var capsule_shape := CapsuleShape3D.new()
capsule_shape.height = ref.scale.y * 2.0
capsule_shape.radius = (ref.scale.x + ref.scale.z) / 2.0
shape.shape = capsule_shape
if shape.shape:
# Rename the collision shape and reparent the child nodes if any
var strip_from: int = ref.name.rfind(&"-")
shape.name = ref.name.left(strip_from)
body.add_child(shape, true)
for ref_child: Node in ref.get_children():
ref_child.reparent(shape)
ref.queue_free()
else:
shape.queue_free()
if body.get_child_count() > 0:
node.add_child(body, true)
else:
body.queue_free() # No collision shape references found.
return node
func _set_new_owner(node: Node, owner: Node) -> void:
# If we set a node's owner to itself, we'll get an error
if node != owner:
node.owner = owner
for child in node.get_children():
_set_new_owner(child, owner)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment