Last active
December 12, 2025 11:13
-
-
Save HungryProton/4d33370d5cea40d73656fd1bccc7444a to your computer and use it in GitHub Desktop.
Import script example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| @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