更新自研插件
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
@tool
|
||||
class_name MeshColliderSettings
|
||||
extends RefCounted
|
||||
|
||||
## Configuration for Mesh Collider.
|
||||
##
|
||||
## Everything lives in `project.godot` under `mesh_collider/*`, so it is
|
||||
## per-project and shows up in **Project Settings → Mesh Collider**.
|
||||
|
||||
const ROOT := "mesh_collider/"
|
||||
|
||||
const KEY_AUTO := ROOT + "auto_add_collision"
|
||||
const KEY_SHAPE := ROOT + "collision_shape"
|
||||
const KEY_BODY := ROOT + "collision_body"
|
||||
const KEY_SKIP_EXISTING := ROOT + "skip_meshes_with_collision"
|
||||
const KEY_LAYER := ROOT + "collision_layer"
|
||||
const KEY_MASK := ROOT + "collision_mask"
|
||||
const KEY_EXTENSIONS := ROOT + "model_extensions"
|
||||
const KEY_SKIP_DIRS := ROOT + "skip_dirs"
|
||||
|
||||
## NOTE: a function rather than a `const` dictionary on purpose. GDScript
|
||||
## requires a const dictionary's *values* to be constant expressions, so a
|
||||
## PackedStringArray cannot be built inside one ("isn't a constant expression").
|
||||
##
|
||||
## The enum-backed defaults below are written as literal indices for the same
|
||||
## reason applied one level out: they must not depend on another script's enum
|
||||
## resolving. Keep them in step with MeshCollider's enum order.
|
||||
static func project_info() -> Dictionary:
|
||||
return {
|
||||
KEY_AUTO: {
|
||||
"name": KEY_AUTO,
|
||||
"type": TYPE_BOOL,
|
||||
"default": true,
|
||||
"hint_string": "Add collision automatically to models dragged into a scene",
|
||||
},
|
||||
KEY_SHAPE: {
|
||||
"name": KEY_SHAPE,
|
||||
"type": TYPE_INT,
|
||||
"default": 0, # MeshCollider.ShapeType.CONVEX_HULL
|
||||
"hint": PROPERTY_HINT_ENUM,
|
||||
"hint_string": "Convex Hull,Trimesh,Box,Sphere,Capsule,Cylinder",
|
||||
},
|
||||
KEY_BODY: {
|
||||
"name": KEY_BODY,
|
||||
"type": TYPE_INT,
|
||||
"default": 0, # MeshCollider.BodyType.STATIC
|
||||
"hint": PROPERTY_HINT_ENUM,
|
||||
"hint_string": "StaticBody3D,RigidBody3D,Area3D,CharacterBody3D",
|
||||
},
|
||||
KEY_SKIP_EXISTING: {
|
||||
"name": KEY_SKIP_EXISTING,
|
||||
"type": TYPE_BOOL,
|
||||
"default": false,
|
||||
"hint_string": "Skip meshes that already sit under a physics body "
|
||||
+ "(turn on for models that ship their own collision)",
|
||||
},
|
||||
KEY_LAYER: {
|
||||
"name": KEY_LAYER,
|
||||
"type": TYPE_INT,
|
||||
"default": 1,
|
||||
"hint": PROPERTY_HINT_LAYERS_3D_PHYSICS,
|
||||
},
|
||||
KEY_MASK: {
|
||||
"name": KEY_MASK,
|
||||
"type": TYPE_INT,
|
||||
"default": 1,
|
||||
"hint": PROPERTY_HINT_LAYERS_3D_PHYSICS,
|
||||
},
|
||||
KEY_EXTENSIONS: {
|
||||
"name": KEY_EXTENSIONS,
|
||||
"type": TYPE_PACKED_STRING_ARRAY,
|
||||
"default": PackedStringArray([
|
||||
"fbx", "glb", "gltf", "dae", "obj", "blend", "md2", "md3", "vrm",
|
||||
]),
|
||||
"hint": PROPERTY_HINT_ARRAY_TYPE,
|
||||
"hint_string": "String",
|
||||
},
|
||||
KEY_SKIP_DIRS: {
|
||||
"name": KEY_SKIP_DIRS,
|
||||
"type": TYPE_PACKED_STRING_ARRAY,
|
||||
"default": PackedStringArray(["res://addons/", "res://.godot/"]),
|
||||
"hint": PROPERTY_HINT_ARRAY_TYPE,
|
||||
"hint_string": "String",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
## Cached copy of `project_info()`. The dictionary is rebuilt from scratch on
|
||||
## every call, and the getters below are called on every scan, so keeping one
|
||||
## instance avoids pointless allocation.
|
||||
static var _cached_info: Dictionary = {}
|
||||
|
||||
|
||||
static func _all_info() -> Dictionary:
|
||||
if _cached_info.is_empty():
|
||||
_cached_info = project_info()
|
||||
return _cached_info
|
||||
|
||||
|
||||
static func _info(key: String) -> Dictionary:
|
||||
var all := _all_info()
|
||||
if all.has(key):
|
||||
return all[key]
|
||||
return {"name": key, "type": TYPE_NIL, "default": null}
|
||||
|
||||
|
||||
## Register every setting with ProjectSettings, so the values appear in the UI
|
||||
## and carry a usable default even before the user touches anything.
|
||||
static func install() -> void:
|
||||
var all := _all_info()
|
||||
for key in all:
|
||||
var info: Dictionary = all[key]
|
||||
if not ProjectSettings.has_setting(key):
|
||||
ProjectSettings.set_setting(key, info["default"])
|
||||
ProjectSettings.add_property_info(info)
|
||||
# Record the default as the initial value, so an untouched setting is not
|
||||
# reported as a modified project setting.
|
||||
ProjectSettings.set_initial_value(key, info["default"])
|
||||
|
||||
|
||||
static func get_bool(key: String) -> bool:
|
||||
return bool(ProjectSettings.get_setting(key, _info(key)["default"]))
|
||||
|
||||
|
||||
static func get_int(key: String) -> int:
|
||||
return int(ProjectSettings.get_setting(key, _info(key)["default"]))
|
||||
|
||||
|
||||
static func get_strings(key: String) -> PackedStringArray:
|
||||
var value = ProjectSettings.get_setting(key, _info(key)["default"])
|
||||
if value is PackedStringArray:
|
||||
return value
|
||||
var out := PackedStringArray()
|
||||
if value is Array:
|
||||
for v in value:
|
||||
out.append(String(v))
|
||||
return out
|
||||
|
||||
|
||||
static func set_value(key: String, value) -> void:
|
||||
ProjectSettings.set_setting(key, value)
|
||||
|
||||
|
||||
## The configured shape type, clamped to the valid enum range.
|
||||
static func shape_type() -> MeshCollider.ShapeType:
|
||||
return clampi(get_int(KEY_SHAPE), 0,
|
||||
MeshCollider.SHAPE_TYPE_NAMES.size() - 1) as MeshCollider.ShapeType
|
||||
|
||||
|
||||
## The configured body type, clamped to the valid enum range.
|
||||
static func body_type() -> MeshCollider.BodyType:
|
||||
return clampi(get_int(KEY_BODY), 0,
|
||||
MeshCollider.BODY_TYPE_NAMES.size() - 1) as MeshCollider.BodyType
|
||||
|
||||
|
||||
## True when `path` looks like a model, according to the configured extensions.
|
||||
static func is_configured_model(path: String) -> bool:
|
||||
if path.is_empty():
|
||||
return false
|
||||
var ext := path.get_extension().to_lower()
|
||||
if ext.is_empty():
|
||||
return false
|
||||
var configured := get_strings(KEY_EXTENSIONS)
|
||||
if configured.is_empty():
|
||||
return MeshCollider.is_model_file(path)
|
||||
for e in configured:
|
||||
if String(e).to_lower().trim_prefix(".") == ext:
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
## True when `path` is inside a directory the plugin must never scan.
|
||||
static func should_skip(path: String) -> bool:
|
||||
var p := path.replace("\\", "/")
|
||||
for dir in get_strings(KEY_SKIP_DIRS):
|
||||
var d := String(dir).replace("\\", "/")
|
||||
if d.is_empty():
|
||||
continue
|
||||
if not d.ends_with("/"):
|
||||
d += "/"
|
||||
if p.begins_with(d):
|
||||
return true
|
||||
return false
|
||||
Reference in New Issue
Block a user