diff --git a/.gitignore b/.gitignore index 95a3b9f6..f716d48c 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,11 @@ export_presets.cfg .godot/ -# Docs specic ignores +# CSharp Dev +*.DotSettings +*.csproj.old + +# Docs specific ignores #/docs/ node_modules cache diff --git a/PhantomCamera.csproj b/PhantomCamera.csproj new file mode 100644 index 00000000..30e9280b --- /dev/null +++ b/PhantomCamera.csproj @@ -0,0 +1,12 @@ + + + net8.0 + net7.0 + net8.0 + true + 11.0 + + + + + \ No newline at end of file diff --git a/PhantomCamera.sln b/PhantomCamera.sln new file mode 100644 index 00000000..4a0d21cc --- /dev/null +++ b/PhantomCamera.sln @@ -0,0 +1,19 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhantomCamera", "PhantomCamera.csproj", "{74ABEBAD-177B-4436-87F5-58340D7F4EE5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + ExportDebug|Any CPU = ExportDebug|Any CPU + ExportRelease|Any CPU = ExportRelease|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {74ABEBAD-177B-4436-87F5-58340D7F4EE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {74ABEBAD-177B-4436-87F5-58340D7F4EE5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {74ABEBAD-177B-4436-87F5-58340D7F4EE5}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU + {74ABEBAD-177B-4436-87F5-58340D7F4EE5}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU + {74ABEBAD-177B-4436-87F5-58340D7F4EE5}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU + {74ABEBAD-177B-4436-87F5-58340D7F4EE5}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU + EndGlobalSection +EndGlobal diff --git a/addons/phantom_camera/csharp/PCam3D.cs b/addons/phantom_camera/csharp/PCam3D.cs new file mode 100644 index 00000000..d387cb47 --- /dev/null +++ b/addons/phantom_camera/csharp/PCam3D.cs @@ -0,0 +1,188 @@ +using Godot; + +namespace PhantomCamera; + +public class PCam3D +{ + public delegate void PCam3DEventHandler(); + public delegate void PCam3DTweenInterruptedEventHandler(Variant pCam3D); + + public event PCam3DEventHandler BecameActive; + public event PCam3DEventHandler BecameInactive; + public event PCam3DEventHandler FollowTargetChanged; + public event PCam3DEventHandler LookAtTargetChanged; + public event PCam3DEventHandler DeadZoneChanged; + public event PCam3DEventHandler TweenStarted; + public event PCam3DEventHandler IsTweening; + public event PCam3DEventHandler TweenCompleted; + public event PCam3DTweenInterruptedEventHandler TweenInterrupted; + + private readonly GodotObject _godotObject; + + private readonly Callable _callableBecameActive; + private readonly Callable _callableBecameInactive; + private readonly Callable _callableFollowTargetChanged; + private readonly Callable _callableLookAtTargetChanged; + private readonly Callable _callableDeadZoneChanged; + private readonly Callable _callableTweenStarted; + private readonly Callable _callableIsTweening; + private readonly Callable _callableTweenCompleted; + private readonly Callable _callableTweenInterrupted; + + public FollowMode FollowMode => _godotObject.Call(MethodName.GetFollowMode).As(); + + public LookAtMode LookAtMode => _godotObject.Call(MethodName.GetLookAtMode).As(); + + public bool IsActive => _godotObject.Call(MethodName.IsActive).As(); + + public int Priority + { + get => _godotObject.Call(MethodName.GetPriority).As(); + set => _godotObject.Call(MethodName.SetPriority, value); + } + + public Vector3 ThirdPersonRotation + { + get => _godotObject.Call(MethodName.GetThirdPersonRotation).As(); + set => _godotObject.Call(MethodName.SetThirdPersonRotation, value); + } + + public Vector3 ThirdPersonRotationDegrees + { + get => _godotObject.Call(MethodName.GetThirdPersonRotationDegrees).As(); + set => _godotObject.Call(MethodName.SetThirdPersonRotationDegrees, value); + } + + public Quaternion ThirdPersonQuaternion + { + get => _godotObject.Call(MethodName.GetThirdPersonQuaternion).As(); + set => _godotObject.Call(MethodName.SetThirdPersonQuaternion, value); + } + + public float SpringLength + { + get => _godotObject.Call(MethodName.GetSpringLength).As(); + set => _godotObject.Call(MethodName.SetSpringLength, value); + } + + public static PCam3D FromScript(string path) => GD.Load(path).AsPCam3D(); + + public PCam3D(GodotObject godotObject) + { + _godotObject = godotObject; + + _callableBecameActive = Callable.From(OnBecameActive); + _callableBecameInactive = Callable.From(OnBecameInactive); + _callableFollowTargetChanged = Callable.From(OnFollowTargetChanged); + _callableLookAtTargetChanged = Callable.From(OnLookAtTargetChanged); + _callableDeadZoneChanged = Callable.From(OnDeadZoneChanged); + _callableTweenStarted = Callable.From(OnTweenStarted); + _callableIsTweening = Callable.From(OnIsTweening); + _callableTweenCompleted = Callable.From(OnTweenCompleted); + _callableTweenInterrupted = Callable.From(OnTweenInterrupted); + + _godotObject.Connect(SignalName.BecameActive, _callableBecameActive); + _godotObject.Connect(SignalName.BecameInactive, _callableBecameInactive); + _godotObject.Connect(SignalName.FollowTargetChanged, _callableFollowTargetChanged); + _godotObject.Connect(SignalName.LookAtTargetChanged, _callableLookAtTargetChanged); + _godotObject.Connect(SignalName.DeadZoneChanged, _callableDeadZoneChanged); + _godotObject.Connect(SignalName.TweenStarted, _callableTweenStarted); + _godotObject.Connect(SignalName.IsTweening, _callableIsTweening); + _godotObject.Connect(SignalName.TweenCompleted, _callableTweenCompleted); + _godotObject.Connect(SignalName.TweenInterrupted, _callableTweenInterrupted); + } + + ~PCam3D() + { + _godotObject.Disconnect(SignalName.BecameActive, _callableBecameActive); + _godotObject.Disconnect(SignalName.BecameInactive, _callableBecameInactive); + _godotObject.Disconnect(SignalName.FollowTargetChanged, _callableFollowTargetChanged); + _godotObject.Disconnect(SignalName.LookAtTargetChanged, _callableLookAtTargetChanged); + _godotObject.Disconnect(SignalName.DeadZoneChanged, _callableDeadZoneChanged); + _godotObject.Disconnect(SignalName.TweenStarted, _callableTweenStarted); + _godotObject.Disconnect(SignalName.IsTweening, _callableIsTweening); + _godotObject.Disconnect(SignalName.TweenCompleted, _callableTweenCompleted); + _godotObject.Disconnect(SignalName.TweenInterrupted, _callableTweenInterrupted); + } + + protected virtual void OnBecameActive() + { + BecameActive?.Invoke(); + } + + protected virtual void OnBecameInactive() + { + BecameInactive?.Invoke(); + } + + protected virtual void OnFollowTargetChanged() + { + FollowTargetChanged?.Invoke(); + } + + protected virtual void OnLookAtTargetChanged() + { + LookAtTargetChanged?.Invoke(); + } + + protected virtual void OnDeadZoneChanged() + { + DeadZoneChanged?.Invoke(); + } + + protected virtual void OnTweenStarted() + { + TweenStarted?.Invoke(); + } + + protected virtual void OnIsTweening() + { + IsTweening?.Invoke(); + } + + protected virtual void OnTweenInterrupted(Variant pCam3D) + { + TweenInterrupted?.Invoke(pCam3D); + } + + protected virtual void OnTweenCompleted() + { + TweenCompleted?.Invoke(); + } + + + public static class MethodName + { + public const string GetFollowMode = "get_follow_mode"; + public const string GetLookAtMode = "get_look_at_mode"; + public const string IsActive = "is_active"; + + public const string GetPriority = "get_priority"; + public const string SetPriority = "set_priority"; + + public const string GetThirdPersonRotation = "get_third_person_rotation"; + public const string SetThirdPersonRotation = "set_third_person_rotation"; + + public const string GetThirdPersonRotationDegrees = "get_third_person_rotation_degrees"; + public const string SetThirdPersonRotationDegrees = "set_third_person_rotation_degrees"; + + public const string GetThirdPersonQuaternion = "get_third_person_quaternion"; + public const string SetThirdPersonQuaternion = "set_third_person_quaternion"; + + public const string GetSpringLength = "get_spring_length"; + public const string SetSpringLength = "set_spring_length"; + } + + public static class SignalName + { + public const string BecameActive = "became_active"; + public const string BecameInactive = "became_inactive"; + public const string FollowTargetChanged = "follow_target_changed"; + public const string LookAtTargetChanged = "look_at_target_changed"; + public const string DeadZoneChanged = "dead_zone_changed"; + public const string TweenStarted = "tween_started"; + public const string IsTweening = "is_tweening"; + public const string TweenInterrupted = "tween_interrupted"; + public const string TweenCompleted = "tween_completed"; + } +} \ No newline at end of file diff --git a/addons/phantom_camera/csharp/PhantomCamera.cs b/addons/phantom_camera/csharp/PhantomCamera.cs new file mode 100644 index 00000000..3ea3a960 --- /dev/null +++ b/addons/phantom_camera/csharp/PhantomCamera.cs @@ -0,0 +1,35 @@ +using Godot; + +namespace PhantomCamera; + +public static class GodotExtension +{ + public static PCam3D AsPCam3D(this GodotObject godotObject) + { + return new PCam3D(godotObject); + } + + public static PCam3D AsPCam3D(this GDScript godotScript) + { + return new PCam3D(godotScript.New().AsGodotObject()); + } +} + +public enum FollowMode +{ + None, + Glued, + Simple, + Group, + Path, + Framed, + ThirdPerson +} + +public enum LookAtMode +{ + None, + Mimic, + Simple, + Group +} \ No newline at end of file diff --git a/addons/phantom_camera/examples/textures/3D/checker_pattern_dark.png.import b/addons/phantom_camera/examples/textures/3D/checker_pattern_dark.png.import index 98915e8d..cfcc34e5 100644 --- a/addons/phantom_camera/examples/textures/3D/checker_pattern_dark.png.import +++ b/addons/phantom_camera/examples/textures/3D/checker_pattern_dark.png.import @@ -4,16 +4,15 @@ importer="texture" type="CompressedTexture2D" uid="uid://c7ja4woxol8yc" path.bptc="res://.godot/imported/checker_pattern_dark.png-70cedad2d3abf4ad6166d6614eefa7fb.bptc.ctex" -path.astc="res://.godot/imported/checker_pattern_dark.png-70cedad2d3abf4ad6166d6614eefa7fb.astc.ctex" metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], +"imported_formats": ["s3tc_bptc"], "vram_texture": true } [deps] source_file="res://addons/phantom_camera/examples/textures/3D/checker_pattern_dark.png" -dest_files=["res://.godot/imported/checker_pattern_dark.png-70cedad2d3abf4ad6166d6614eefa7fb.bptc.ctex", "res://.godot/imported/checker_pattern_dark.png-70cedad2d3abf4ad6166d6614eefa7fb.astc.ctex"] +dest_files=["res://.godot/imported/checker_pattern_dark.png-70cedad2d3abf4ad6166d6614eefa7fb.bptc.ctex"] [params] diff --git a/addons/phantom_camera/icons/phantom_camera_gizmo.svg.import b/addons/phantom_camera/icons/phantom_camera_gizmo.svg.import index e89d99c5..7b49608d 100644 --- a/addons/phantom_camera/icons/phantom_camera_gizmo.svg.import +++ b/addons/phantom_camera/icons/phantom_camera_gizmo.svg.import @@ -4,16 +4,15 @@ importer="texture" type="CompressedTexture2D" uid="uid://e36npe2rbxyg" path.s3tc="res://.godot/imported/phantom_camera_gizmo.svg-ba1aacb9b1c5f4ef401d3bd3697a542b.s3tc.ctex" -path.etc2="res://.godot/imported/phantom_camera_gizmo.svg-ba1aacb9b1c5f4ef401d3bd3697a542b.etc2.ctex" metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], +"imported_formats": ["s3tc_bptc"], "vram_texture": true } [deps] source_file="res://addons/phantom_camera/icons/phantom_camera_gizmo.svg" -dest_files=["res://.godot/imported/phantom_camera_gizmo.svg-ba1aacb9b1c5f4ef401d3bd3697a542b.s3tc.ctex", "res://.godot/imported/phantom_camera_gizmo.svg-ba1aacb9b1c5f4ef401d3bd3697a542b.etc2.ctex"] +dest_files=["res://.godot/imported/phantom_camera_gizmo.svg-ba1aacb9b1c5f4ef401d3bd3697a542b.s3tc.ctex"] [params] diff --git a/addons/phantom_camera/scripts/PhantomCamera.cs b/addons/phantom_camera/scripts/PhantomCamera.cs new file mode 100644 index 00000000..2da8606f --- /dev/null +++ b/addons/phantom_camera/scripts/PhantomCamera.cs @@ -0,0 +1,28 @@ +using Godot; +using PhantomCamera.Cameras; +using PhantomCamera.Hosts; + +namespace PhantomCamera; + +public static class PhantomCameraExtension +{ + public static PhantomCamera3D AsPhantomCamera3D(this Node3D node3D) + { + return new PhantomCamera3D(node3D); + } + + public static PhantomCamera2D AsPhantomCamera2D(this Node2D node2D) + { + return new PhantomCamera2D(node2D); + } + + public static PhantomCameraHost AsPhantomCameraHost(this Node node) + { + return new PhantomCameraHost(node); + } +} + + + + + diff --git a/addons/phantom_camera/scripts/managers/PhantomCameraManager.cs b/addons/phantom_camera/scripts/managers/PhantomCameraManager.cs new file mode 100644 index 00000000..9d41647e --- /dev/null +++ b/addons/phantom_camera/scripts/managers/PhantomCameraManager.cs @@ -0,0 +1,34 @@ +using System.Linq; +using Godot; +using PhantomCamera.Cameras; +using PhantomCamera.Hosts; + +#nullable enable + +namespace PhantomCamera.Managers; + +public static class PhantomCameraManager +{ + private static GodotObject? _instance; + + public static GodotObject Instance => _instance ??= Engine.GetSingleton("PhantomCameraManager"); + + public static PhantomCamera2D[] PhantomCamera2Ds => + Instance.Call(MethodName.GetPhantomCamera2Ds).AsGodotArray() + .Select(node => new PhantomCamera2D(node)).ToArray(); + + public static PhantomCamera3D[] PhantomCamera3Ds => + Instance.Call(MethodName.GetPhantomCamera3Ds).AsGodotArray() + .Select(node => new PhantomCamera3D(node)).ToArray(); + + public static PhantomCameraHost[] PhantomCameraHosts => + Instance.Call(MethodName.GetPhantomCameraHosts).AsGodotArray() + .Select(node => new PhantomCameraHost(node)).ToArray(); + + public static class MethodName + { + public const string GetPhantomCamera2Ds = "get_phantom_camera_2ds"; + public const string GetPhantomCamera3Ds = "get_phantom_camera_3ds"; + public const string GetPhantomCameraHosts = "get_phantom_camera_hosts"; + } +} \ No newline at end of file diff --git a/addons/phantom_camera/scripts/managers/phantom_camera_manager.gd b/addons/phantom_camera/scripts/managers/phantom_camera_manager.gd index 8a53e059..999df238 100644 --- a/addons/phantom_camera/scripts/managers/phantom_camera_manager.gd +++ b/addons/phantom_camera/scripts/managers/phantom_camera_manager.gd @@ -18,6 +18,9 @@ var phantom_camera_3ds: Array: ## Note: To support disable_3d export templates f return _phantom_camera_3d_list var _phantom_camera_3d_list: Array ## Note: To support disable_3d export templates for 2D projects, this is purposely not strongly typed. +func _ready(): + if not Engine.has_singleton(PHANTOM_CAMERA_CONSTS.PCAM_MANAGER_NODE_NAME): + Engine.register_singleton(PHANTOM_CAMERA_CONSTS.PCAM_MANAGER_NODE_NAME, self) func _enter_tree(): Engine.physics_jitter_fix = 0 diff --git a/addons/phantom_camera/scripts/phantom_camera/PhantomCamera.cs b/addons/phantom_camera/scripts/phantom_camera/PhantomCamera.cs new file mode 100644 index 00000000..d6b1b820 --- /dev/null +++ b/addons/phantom_camera/scripts/phantom_camera/PhantomCamera.cs @@ -0,0 +1,239 @@ +using Godot; +using PhantomCamera.Hosts; +using PhantomCamera.Resources; + +#nullable enable + +namespace PhantomCamera.Cameras; + +public enum FollowMode +{ + None, + Glued, + Simple, + Group, + Path, + Framed, + ThirdPerson +} + +public enum LookAtMode +{ + None, + Mimic, + Simple, + Group +} + +public enum InactiveUpdateMode +{ + Always, + Never +} + +public abstract class PhantomCamera +{ + protected readonly GodotObject Node; + + public delegate void BecameActiveEventHandler(); + public delegate void BecameInactiveEventHandler(); + public delegate void FollowTargetChangedEventHandler(); + public delegate void DeadZoneChangedEventHandler(); + public delegate void TweenStartedEventHandler(); + public delegate void IsTweeningEventHandler(); + public delegate void TweenCompletedEventHandler(); + + public event BecameActiveEventHandler? BecameActive; + public event BecameInactiveEventHandler? BecameInactive; + public event FollowTargetChangedEventHandler? FollowTargetChanged; + public event DeadZoneChangedEventHandler? DeadZoneChanged; + public event TweenStartedEventHandler? TweenStarted; + public event IsTweeningEventHandler? IsTweening; + public event TweenCompletedEventHandler? TweenCompleted; + + private readonly Callable _callableBecameActive; + private readonly Callable _callableBecameInactive; + private readonly Callable _callableFollowTargetChanged; + private readonly Callable _callableDeadZoneChanged; + private readonly Callable _callableTweenStarted; + private readonly Callable _callableIsTweening; + private readonly Callable _callableTweenCompleted; + + public int Priority + { + get => (int)Node.Call(MethodName.GetPriority); + set => Node.Call(MethodName.SetPriority, value); + } + + public FollowMode FollowMode => (FollowMode)(int)Node.Call(MethodName.GetFollowMode); + + public bool IsActive => (bool)Node.Call(MethodName.IsActive); + + public PhantomCameraHost PhantomCameraHostOwner + { + get => new((Node)Node.Call(MethodName.GetPCamHostOwner)); + set => Node.Call(MethodName.SetPCamHostOwner, value.Node); + } + + public bool FollowDamping + { + get => (bool)Node.Call(MethodName.GetFollowDamping); + set => Node.Call(MethodName.SetFollowDamping, value); + } + + public float DeadZoneWidth + { + get => (float)Node.Get(PropertyName.DeadZoneWidth); + set => Node.Set(PropertyName.DeadZoneWidth, value); + } + + public float DeadZoneHeight + { + get => (float)Node.Get(PropertyName.DeadZoneHeight); + set => Node.Set(PropertyName.DeadZoneHeight, value); + } + + public PhantomCameraTween TweenResource + { + get => new((Resource)Node.Call(MethodName.GetTweenResource)); + set => Node.Call(MethodName.SetTweenResource, (GodotObject)value.Resource); + } + + public bool TweenSkip + { + get => (bool)Node.Call(MethodName.GetTweenSkip); + set => Node.Call(MethodName.SetTweenSkip, value); + } + + public float TweenDuration + { + get => (float)Node.Call(MethodName.GetTweenDuration); + set => Node.Call(MethodName.SetTweenDuration, value); + } + + public TransitionType TweenTransition + { + get => (TransitionType)(int)Node.Call(MethodName.GetTweenTransition); + set => Node.Call(MethodName.GetTweenTransition, (int)value); + } + + public EaseType TweenEase + { + get => (EaseType)(int)Node.Call(MethodName.GetTweenEase); + set => Node.Call(MethodName.GetTweenEase, (int)value); + } + + public bool TweenOnLoad + { + get => (bool)Node.Call(MethodName.GetTweenOnLoad); + set => Node.Call(MethodName.SetTweenOnLoad, value); + } + + public InactiveUpdateMode InactiveUpdateMode + { + get => (InactiveUpdateMode)(int)Node.Call(MethodName.GetInactiveUpdateMode); + set => Node.Call(MethodName.SetInactiveUpdateMode, (int)value); + } + + protected PhantomCamera(GodotObject phantomCameraNode) + { + Node = phantomCameraNode; + + _callableBecameActive = Callable.From(() => BecameActive?.Invoke()); + _callableBecameInactive = Callable.From(() => BecameInactive?.Invoke()); + _callableFollowTargetChanged = Callable.From(() => FollowTargetChanged?.Invoke()); + _callableDeadZoneChanged = Callable.From(() => DeadZoneChanged?.Invoke()); + _callableTweenStarted = Callable.From(() => TweenStarted?.Invoke()); + _callableIsTweening = Callable.From(() => IsTweening?.Invoke()); + _callableTweenCompleted = Callable.From(() => TweenCompleted?.Invoke()); + + Node.Connect(SignalName.BecameActive, _callableBecameActive); + Node.Connect(SignalName.BecameInactive, _callableBecameInactive); + Node.Connect(SignalName.FollowTargetChanged, _callableFollowTargetChanged); + Node.Connect(SignalName.DeadZoneChanged, _callableDeadZoneChanged); + Node.Connect(SignalName.TweenStarted, _callableTweenStarted); + Node.Connect(SignalName.IsTweening, _callableIsTweening); + Node.Connect(SignalName.TweenCompleted, _callableTweenCompleted); + } + + ~PhantomCamera() + { + Node.Disconnect(SignalName.BecameActive, _callableBecameActive); + Node.Disconnect(SignalName.BecameInactive, _callableBecameInactive); + Node.Disconnect(SignalName.FollowTargetChanged, _callableFollowTargetChanged); + Node.Disconnect(SignalName.DeadZoneChanged, _callableDeadZoneChanged); + Node.Disconnect(SignalName.TweenStarted, _callableTweenStarted); + Node.Disconnect(SignalName.IsTweening, _callableIsTweening); + Node.Disconnect(SignalName.TweenCompleted, _callableTweenCompleted); + } + + public static class MethodName + { + public const string GetFollowMode = "get_follow_mode"; + public const string IsActive = "is_active"; + + public const string GetPriority = "get_priority"; + public const string SetPriority = "set_priority"; + + public const string GetPCamHostOwner = "get_pcam_host_owner"; + public const string SetPCamHostOwner = "set_pcam_host_owner"; + + public const string GetFollowTarget = "get_follow_target"; + public const string SetFollowTarget = "set_follow_target"; + + public const string GetFollowTargets = "get_follow_targets"; + public const string SetFollowTargets = "set_follow_targets"; + + public const string GetFollowPath = "get_follow_path"; + public const string SetFollowPath = "set_follow_path"; + + public const string GetFollowOffset = "get_follow_offset"; + public const string SetFollowOffset = "set_follow_offset"; + + public const string GetFollowDamping = "get_follow_damping"; + public const string SetFollowDamping = "set_follow_damping"; + + public const string GetFollowDampingValue = "get_follow_damping_value"; + public const string SetFollowDampingValue = "set_follow_damping_value"; + + public const string GetTweenResource = "get_tween_resource"; + public const string SetTweenResource = "set_tween_resource"; + + public const string GetTweenSkip = "get_tween_skip"; + public const string SetTweenSkip = "set_tween_skip"; + + public const string GetTweenDuration = "get_tween_duration"; + public const string SetTweenDuration = "set_tween_duration"; + + public const string GetTweenTransition = "get_tween_transition"; + public const string SetTweenTransition = "set_tween_transition"; + + public const string GetTweenEase = "get_tween_ease"; + public const string SetTweenEase = "set_tween_ease"; + + public const string GetTweenOnLoad = "get_tween_on_load"; + public const string SetTweenOnLoad = "set_tween_on_load"; + + public const string GetInactiveUpdateMode = "get_inactive_update_mode"; + public const string SetInactiveUpdateMode = "set_inactive_update_mode"; + } + + public static class PropertyName + { + public const string DeadZoneWidth = "dead_zone_width"; + public const string DeadZoneHeight = "dead_zone_height"; + } + + public static class SignalName + { + public const string BecameActive = "became_active"; + public const string BecameInactive = "became_inactive"; + public const string FollowTargetChanged = "follow_target_changed"; + public const string LookAtTargetChanged = "look_at_target_changed"; + public const string DeadZoneChanged = "dead_zone_changed"; + public const string TweenStarted = "tween_started"; + public const string IsTweening = "is_tweening"; + public const string TweenCompleted = "tween_completed"; + public const string TweenInterrupted = "tween_interrupted"; + } +} \ No newline at end of file diff --git a/addons/phantom_camera/scripts/phantom_camera/PhantomCamera2D.cs b/addons/phantom_camera/scripts/phantom_camera/PhantomCamera2D.cs new file mode 100644 index 00000000..820578aa --- /dev/null +++ b/addons/phantom_camera/scripts/phantom_camera/PhantomCamera2D.cs @@ -0,0 +1,239 @@ +using System.Linq; +using Godot; + +#nullable enable + +namespace PhantomCamera.Cameras; + +public class PhantomCamera2D : PhantomCamera +{ + public Node2D Node2D => (Node2D)Node; + + public delegate void TweenInterruptedEventHandler(Node2D pCam); + + public event TweenInterruptedEventHandler? TweenInterrupted; + + private readonly Callable _callableTweenInterrupted; + + public Node2D FollowTarget + { + get => (Node2D)Node2D.Call(PhantomCamera.MethodName.GetFollowTarget); + set => Node2D.Call(PhantomCamera.MethodName.SetFollowTarget, value); + } + + public Node2D[] FollowTargets + { + get => Node2D.Call(PhantomCamera.MethodName.GetFollowTargets).AsGodotArray().ToArray(); + set => Node2D.Call(PhantomCamera.MethodName.SetFollowTargets, value); + } + + public Path2D FollowPath + { + get => (Path2D)Node2D.Call(PhantomCamera.MethodName.GetFollowPath); + set => Node2D.Call(PhantomCamera.MethodName.SetFollowPath, value); + } + + public Vector2 FollowOffset + { + get => (Vector2)Node2D.Call(PhantomCamera.MethodName.GetFollowOffset); + set => Node2D.Call(PhantomCamera.MethodName.SetFollowOffset, value); + } + + public Vector2 FollowDampingValue + { + get => (Vector2)Node2D.Call(PhantomCamera.MethodName.GetFollowDampingValue); + set => Node2D.Call(PhantomCamera.MethodName.SetFollowDampingValue, value); + } + + public Vector2 Zoom + { + get => (Vector2)Node.Call(MethodName.GetZoom); + set => Node.Call(MethodName.SetZoom, value); + } + + public bool SnapToPixel + { + get => (bool)Node.Call(MethodName.GetSnapToPixel); + set => Node.Call(MethodName.SetSnapToPixel, value); + } + + public int LimitLeft + { + get => (int)Node.Call(MethodName.GetLimitLeft); + set => Node.Call(MethodName.SetLimitLeft, value); + } + + public int LimitTop + { + get => (int)Node.Call(MethodName.GetLimitTop); + set => Node.Call(MethodName.SetLimitTop, value); + } + + public int LimitRight + { + get => (int)Node.Call(MethodName.GetLimitRight); + set => Node.Call(MethodName.SetLimitRight, value); + } + + public int LimitBottom + { + get => (int)Node.Call(MethodName.GetLimitBottom); + set => Node.Call(MethodName.SetLimitBottom, value); + } + + public Vector4I LimitMargin + { + get => (Vector4I)Node.Call(MethodName.GetLimitMargin); + set => Node.Call(MethodName.SetLimitMargin, value); + } + + public bool AutoZoom + { + get => (bool)Node2D.Call(MethodName.GetAutoZoom); + set => Node2D.Call(MethodName.SetAutoZoom, value); + } + + public float AutoZoomMin + { + get => (float)Node2D.Call(MethodName.GetAutoZoomMin); + set => Node2D.Call(MethodName.SetAutoZoomMin, value); + } + + public float AutoZoomMax + { + get => (float)Node2D.Call(MethodName.GetAutoZoomMax); + set => Node2D.Call(MethodName.SetAutoZoomMax, value); + } + + public Vector4 AutoZoomMargin + { + get => (Vector4)Node2D.Call(MethodName.GetAutoZoomMargin); + set => Node2D.Call(MethodName.SetAutoZoomMargin, value); + } + + public bool DrawLimits + { + get => (bool)Node2D.Get(PropertyName.DrawLimits); + set => Node2D.Set(PropertyName.DrawLimits, value); + } + + public static PhantomCamera2D FromScript(string path) => new(GD.Load(path).New().AsGodotObject()); + public static PhantomCamera2D FromScript(GDScript script) => new(script.New().AsGodotObject()); + + public PhantomCamera2D(GodotObject phantomCameraNode) : base(phantomCameraNode) + { + _callableTweenInterrupted = Callable.From(pCam => TweenInterrupted?.Invoke(pCam)); + Node.Connect(SignalName.TweenInterrupted, _callableTweenInterrupted); + } + + ~PhantomCamera2D() + { + Node.Disconnect(SignalName.TweenInterrupted, _callableTweenInterrupted); + } + + public void SetLimitTarget(TileMap tileMap) + { + Node.Call(MethodName.SetLimitTarget, tileMap.GetPath()); + } + + public void SetLimitTarget(TileMapLayer tileMapLayer) + { + Node.Call(MethodName.SetLimitTarget, tileMapLayer.GetPath()); + } + + public void SetLimitTarget(CollisionShape2D shape2D) + { + Node.Call(MethodName.SetLimitTarget, shape2D.GetPath()); + } + + public LimitTargetQueryResult? GetLimitTarget() + { + var result = (NodePath)Node.Call(MethodName.GetLimitTarget); + return result.IsEmpty ? null : new LimitTargetQueryResult(Node2D.GetNode(result)); + } + + public void SetLimit(Side side, int value) + { + Node.Call(MethodName.SetLimit, (int)side, value); + } + + public int GetLimit(Side side) + { + return (int)Node.Call(MethodName.GetLimit, (int)side); + } + + public new static class MethodName + { + public const string GetZoom = "get_zoom"; + public const string SetZoom = "set_zoom"; + + public const string GetSnapToPixel = "get_snap_to_pixel"; + public const string SetSnapToPixel = "set_snap_to_pixel"; + + public const string GetLimit = "get_limit"; + public const string SetLimit = "set_limit"; + + public const string GetLimitLeft = "get_limit_left"; + public const string SetLimitLeft = "set_limit_left"; + + public const string GetLimitTop = "get_limit_top"; + public const string SetLimitTop = "set_limit_top"; + + public const string GetLimitRight = "get_limit_right"; + public const string SetLimitRight = "set_limit_right"; + + public const string GetLimitBottom = "get_limit_bottom"; + public const string SetLimitBottom = "set_limit_bottom"; + + public const string GetLimitTarget = "get_limit_target"; + public const string SetLimitTarget = "set_limit_target"; + + public const string GetLimitMargin = "get_limit_margin"; + public const string SetLimitMargin = "set_limit_margin"; + + public const string GetAutoZoom = "get_auto_zoom"; + public const string SetAutoZoom = "set_auto_zoom"; + + public const string GetAutoZoomMin = "get_auto_zoom_min"; + public const string SetAutoZoomMin = "set_auto_zoom_min"; + + public const string GetAutoZoomMax = "get_auto_zoom_max"; + public const string SetAutoZoomMax = "set_auto_zoom_max"; + + public const string GetAutoZoomMargin = "get_auto_zoom_margin"; + public const string SetAutoZoomMargin = "set_auto_zoom_margin"; + } + + public new static class PropertyName + { + public const string DrawLimits = "draw_limits"; + } +} + +public class LimitTargetQueryResult +{ + private readonly GodotObject _obj; + + public bool IsTileMap => _obj.IsClass("TileMap"); + + public bool IsTileMapLayer => _obj.IsClass("TileMapLayer"); + + public bool IsCollisionShape2D => _obj.IsClass("CollisionShape2D"); + + public LimitTargetQueryResult(GodotObject godotObject) => _obj = godotObject; + + public TileMap? AsTileMap() + { + return IsTileMap ? (TileMap)_obj : null; + } + + public TileMapLayer? AsTileMapLayer() + { + return IsTileMapLayer ? (TileMapLayer)_obj : null; + } + + public CollisionShape2D? AsCollisionShape2D() + { + return IsCollisionShape2D ? (CollisionShape2D)_obj : null; + } +} \ No newline at end of file diff --git a/addons/phantom_camera/scripts/phantom_camera/PhantomCamera3D.cs b/addons/phantom_camera/scripts/phantom_camera/PhantomCamera3D.cs new file mode 100644 index 00000000..8502c6bf --- /dev/null +++ b/addons/phantom_camera/scripts/phantom_camera/PhantomCamera3D.cs @@ -0,0 +1,347 @@ +using System.Linq; +using Godot; +using PhantomCamera.Resources; + +#nullable enable + +namespace PhantomCamera.Cameras; + +public class PhantomCamera3D : PhantomCamera +{ + public Node3D Node3D => (Node3D)Node; + + public delegate void LookAtTargetChangedEventHandler(); + public delegate void TweenInterruptedEventHandler(Node3D pCam); + + public event LookAtTargetChangedEventHandler? LookAtTargetChanged; + public event TweenInterruptedEventHandler? TweenInterrupted; + + private readonly Callable _callableLookAtTargetChanged; + private readonly Callable _callableTweenInterrupted; + + public Node3D FollowTarget + { + get => (Node3D)Node3D.Call(PhantomCamera.MethodName.GetFollowTarget); + set => Node3D.Call(PhantomCamera.MethodName.SetFollowTarget, value); + } + + public Node3D[] FollowTargets + { + get => Node3D.Call(PhantomCamera.MethodName.GetFollowTargets).AsGodotArray().ToArray(); + set => Node3D.Call(PhantomCamera.MethodName.SetFollowTargets, value); + } + + public Path3D FollowPath + { + get => (Path3D)Node3D.Call(PhantomCamera.MethodName.GetFollowPath); + set => Node3D.Call(PhantomCamera.MethodName.SetFollowPath, value); + } + + public Vector3 FollowOffset + { + get => (Vector3)Node3D.Call(PhantomCamera.MethodName.GetFollowOffset); + set => Node3D.Call(PhantomCamera.MethodName.SetFollowOffset, value); + } + + public Vector3 FollowDampingValue + { + get => (Vector3)Node3D.Call(PhantomCamera.MethodName.GetFollowDampingValue); + set => Node3D.Call(PhantomCamera.MethodName.SetFollowDampingValue, value); + } + + public LookAtMode LookAtMode => (LookAtMode)(int)Node.Call(MethodName.GetLookAtMode); + + public Camera3DResource Camera3DResource + { + get => new((Resource)Node.Call(MethodName.GetCamera3DResource)); + set => Node.Call(MethodName.SetCamera3DResource, value.Resource); + } + + public Vector3 ThirdPersonRotation + { + get => (Vector3)Node.Call(MethodName.GetThirdPersonRotation); + set => Node.Call(MethodName.SetThirdPersonRotation, value); + } + + public Vector3 ThirdPersonRotationDegrees + { + get => (Vector3)Node.Call(MethodName.GetThirdPersonRotationDegrees); + set => Node.Call(MethodName.SetThirdPersonRotationDegrees, value); + } + + public Quaternion ThirdPersonQuaternion + { + get => (Quaternion)Node.Call(MethodName.GetThirdPersonQuaternion); + set => Node.Call(MethodName.SetThirdPersonQuaternion, value); + } + + public float SpringLength + { + get => (float)Node.Call(MethodName.GetSpringLength); + set => Node.Call(MethodName.SetSpringLength, value); + } + + public float FollowDistance + { + get => (float)Node3D.Call(MethodName.GetFollowDistance); + set => Node3D.Call(MethodName.SetFollowDistance, value); + } + + public bool AutoFollowDistance + { + get => (bool)Node3D.Call(MethodName.GetAutoFollowDistance); + set => Node3D.Call(MethodName.SetAutoFollowDistance, value); + } + + public float AutoFollowDistanceMin + { + get => (float)Node3D.Call(MethodName.GetAutoFollowDistanceMin); + set => Node3D.Call(MethodName.SetAutoFollowDistanceMin, value); + } + + public float AutoFollowDistanceMax + { + get => (float)Node3D.Call(MethodName.GetAutoFollowDistanceMax); + set => Node3D.Call(MethodName.SetAutoFollowDistanceMax, value); + } + + public float AutoFollowDistanceDivisor + { + get => (float)Node3D.Call(MethodName.GetAutoFollowDistanceDivisor); + set => Node3D.Call(MethodName.SetAutoFollowDistanceDivisor, value); + } + + public Node3D LookAtTarget + { + get => (Node3D)Node3D.Call(MethodName.GetLookAtTarget); + set => Node3D.Call(MethodName.SetLookAtTarget, value); + } + + public Node3D[] LookAtTargets + { + get => Node3D.Call(MethodName.GetLookAtTargets).AsGodotArray().ToArray(); + set => Node3D.Call(MethodName.SetLookAtTargets, value); + } + + public Vector2 ViewportPosition + { + get => (Vector2)Node3D.Call(MethodName.GetViewportPosition); + set => Node3D.Call(MethodName.SetViewportPosition, value); + } + + public int CollisionMask + { + get => (int)Node3D.Call(MethodName.GetCollisionMask); + set => Node3D.Call(MethodName.SetCollisionMask, value); + } + + public Shape3D Shape + { + get => (Shape3D)Node3D.Call(MethodName.GetShape); + set => Node3D.Call(MethodName.SetShape, value); + } + + public float Margin + { + get => (float)Node3D.Call(MethodName.GetMargin); + set => Node3D.Call(MethodName.SetMargin, value); + } + + public Vector3 LookAtOffset + { + get => (Vector3)Node3D.Call(MethodName.GetLookAtOffset); + set => Node3D.Call(MethodName.SetLookAtOffset, value); + } + + public bool LookAtDamping + { + get => (bool)Node3D.Call(MethodName.GetLookAtDamping); + set => Node3D.Call(MethodName.SetLookAtDamping, value); + } + + public float LookAtDampingValue + { + get => (float)Node3D.Call(MethodName.GetLookAtDampingValue); + set => Node3D.Call(MethodName.SetLookAtDampingValue, value); + } + + public int CullMask + { + get => (int)Node.Call(MethodName.GetCullMask); + set => Node.Call(MethodName.SetCullMask, value); + } + + public float HOffset + { + get => (float)Node.Call(MethodName.GetHOffset); + set => Node.Call(MethodName.SetHOffset, value); + } + + public float VOffset + { + get => (float)Node.Call(MethodName.GetVOffset); + set => Node.Call(MethodName.SetVOffset, value); + } + + public ProjectionType Projection + { + get => (ProjectionType)(int)Node.Call(MethodName.GetProjection); + set => Node.Call(MethodName.SetProjection, (int)value); + } + + public float Fov + { + get => (float)Node.Call(MethodName.GetFov); + set => Node.Call(MethodName.SetFov, value); + } + + public float Size + { + get => (float)Node.Call(MethodName.GetSize); + set => Node.Call(MethodName.SetSize, value); + } + + public Vector2 FrustumOffset + { + get => (Vector2)Node.Call(MethodName.GetFrustumOffset); + set => Node.Call(MethodName.SetFrustumOffset, value); + } + + public float Far + { + get => (float)Node.Call(MethodName.GetFar); + set => Node.Call(MethodName.SetFar, value); + } + + public float Near + { + get => (float)Node.Call(MethodName.GetNear); + set => Node.Call(MethodName.SetNear, value); + } + + public Environment Environment + { + get => (Environment)Node.Call(MethodName.GetEnvironment); + set => Node.Call(MethodName.SetEnvironment, value); + } + + public CameraAttributes Attributes + { + get => (CameraAttributes)Node.Call(MethodName.GetAttributes); + set => Node.Call(MethodName.SetAttributes, value); + } + + + public static PhantomCamera3D FromScript(string path) => new(GD.Load(path).New().AsGodotObject()); + public static PhantomCamera3D FromScript(GDScript script) => new(script.New().AsGodotObject()); + + public PhantomCamera3D(GodotObject phantomCamera3DNode) : base(phantomCamera3DNode) + { + _callableLookAtTargetChanged = Callable.From(() => LookAtTargetChanged?.Invoke()); + _callableTweenInterrupted = Callable.From(pCam => TweenInterrupted?.Invoke(pCam)); + + Node.Connect(SignalName.LookAtTargetChanged, _callableLookAtTargetChanged); + Node.Connect(SignalName.TweenInterrupted, _callableTweenInterrupted); + } + + ~PhantomCamera3D() + { + Node.Disconnect(SignalName.LookAtTargetChanged, _callableLookAtTargetChanged); + Node.Disconnect(SignalName.TweenInterrupted, _callableTweenInterrupted); + } + + public new static class MethodName + { + public const string GetLookAtMode = "get_look_at_mode"; + + public const string GetCamera3DResource = "get_camera_3d_resource"; + public const string SetCamera3DResource = "set_camera_3d_resource"; + + public const string GetThirdPersonRotation = "get_third_person_rotation"; + public const string SetThirdPersonRotation = "set_third_person_rotation"; + + public const string GetThirdPersonRotationDegrees = "get_third_person_rotation_degrees"; + public const string SetThirdPersonRotationDegrees = "set_third_person_rotation_degrees"; + + public const string GetThirdPersonQuaternion = "get_third_person_quaternion"; + public const string SetThirdPersonQuaternion = "set_third_person_quaternion"; + + public const string GetSpringLength = "get_spring_length"; + public const string SetSpringLength = "set_spring_length"; + + public const string GetFollowDistance = "get_follow_distance"; + public const string SetFollowDistance = "set_follow_distance"; + + public const string GetAutoFollowDistance = "get_auto_follow_distance"; + public const string SetAutoFollowDistance = "set_auto_follow_distance"; + + public const string GetAutoFollowDistanceMin = "get_auto_follow_distance_min"; + public const string SetAutoFollowDistanceMin = "set_auto_follow_distance_min"; + + public const string GetAutoFollowDistanceMax = "get_auto_follow_distance_max"; + public const string SetAutoFollowDistanceMax = "set_auto_follow_distance_max"; + + public const string GetAutoFollowDistanceDivisor = "get_auto_follow_distance_divisor"; + public const string SetAutoFollowDistanceDivisor = "set_auto_follow_distance_divisor"; + + public const string GetLookAtTarget = "get_look_at_target"; + public const string SetLookAtTarget = "set_look_at_target"; + + public const string GetLookAtTargets = "get_look_at_targets"; + public const string SetLookAtTargets = "set_look_at_targets"; + + public const string GetViewportPosition = "get_viewport_position"; + public const string SetViewportPosition = "set_viewport_position"; + + public const string GetCollisionMask = "get_collision_mask"; + public const string SetCollisionMask = "set_collision_mask"; + + public const string GetShape = "get_shape"; + public const string SetShape = "set_shape"; + + public const string GetMargin = "get_margin"; + public const string SetMargin = "set_margin"; + + public const string GetLookAtOffset = "get_look_at_offset"; + public const string SetLookAtOffset = "set_look_at_offset"; + + public const string GetLookAtDamping = "get_look_at_damping"; + public const string SetLookAtDamping = "set_look_at_damping"; + + public const string GetLookAtDampingValue = "get_look_at_damping_value"; + public const string SetLookAtDampingValue = "set_look_at_damping_value"; + + public const string GetCullMask = "get_cull_mask"; + public const string SetCullMask = "set_cull_mask"; + + public const string GetHOffset = "get_h_offset"; + public const string SetHOffset = "set_h_offset"; + + public const string GetVOffset = "get_v_offset"; + public const string SetVOffset = "set_v_offset"; + + public const string GetProjection = "get_projection"; + public const string SetProjection = "set_projection"; + + public const string GetFov = "get_fov"; + public const string SetFov = "set_fov"; + + public const string GetSize = "get_size"; + public const string SetSize = "set_size"; + + public const string GetFrustumOffset = "get_frustum_offset"; + public const string SetFrustumOffset = "set_frustum_offset"; + + public const string GetFar = "get_far"; + public const string SetFar = "set_far"; + + public const string GetNear = "get_near"; + public const string SetNear = "set_near"; + + public const string GetEnvironment = "get_environment"; + public const string SetEnvironment = "set_environment"; + + public const string GetAttributes = "get_attributes"; + public const string SetAttributes = "set_attributes"; + } +} \ No newline at end of file diff --git a/addons/phantom_camera/scripts/phantom_camera_host/PhantomCameraHost.cs b/addons/phantom_camera/scripts/phantom_camera_host/PhantomCameraHost.cs new file mode 100644 index 00000000..34cf663b --- /dev/null +++ b/addons/phantom_camera/scripts/phantom_camera_host/PhantomCameraHost.cs @@ -0,0 +1,75 @@ +using Godot; +using PhantomCamera.Cameras; + +#nullable enable + +namespace PhantomCamera.Hosts; + +public enum InterpolationMode +{ + Auto, + Idle, + Physics +} + +public class PhantomCameraHost +{ + public Node Node { get; } + + // TODO: For Godot 4.3 + // public InterpolationMode InterpolationMode + // { + // get => (InterpolationMode)(int)Node.Call(MethodName.GetInterpolationMode); + // set => Node.Call(MethodName.SetInterpolationMode, (int)value); + // } + + public Camera2D? Camera2D => (Camera2D?)Node.Get(PropertyName.Camera2D); + + public Camera3D? Camera3D => (Camera3D?)Node.Get(PropertyName.Camera3D); + + public bool TriggerPhantomCameraTween => (bool)Node.Call(MethodName.GetTriggerPhantomCameraTween); + + public PhantomCameraHost(Node node) => Node = node; + + public ActivePhantomCameraQueryResult? GetActivePhantomCamera() + { + var result = Node.Call(MethodName.GetActivePhantomCamera); + return result.VariantType == Variant.Type.Nil ? null : new ActivePhantomCameraQueryResult(result.AsGodotObject()); + } + + public static class PropertyName + { + public const string Camera2D = "camera_2d"; + public const string Camera3D = "camera_3d"; + } + + public static class MethodName + { + public const string GetActivePhantomCamera = "get_active_pcam"; + public const string GetTriggerPhantomCameraTween = "get_trigger_pcam_tween"; + + public const string GetInterpolationMode = "get_interpolation_mode"; + public const string SetInterpolationMode = "set_interpolation_mode"; + } +} + +public class ActivePhantomCameraQueryResult +{ + private readonly GodotObject _obj; + + public bool Is2D => _obj.IsClass("Node2D"); + + public bool Is3D => _obj.IsClass("Node3D"); + + public ActivePhantomCameraQueryResult(GodotObject godotObject) => _obj = godotObject; + + public PhantomCamera2D? AsPhantomCamera2D() + { + return Is2D ? new PhantomCamera2D(_obj) : null; + } + + public PhantomCamera3D? AsPhantomCamera3D() + { + return Is3D ? new PhantomCamera3D(_obj) : null; + } +} \ No newline at end of file diff --git a/addons/phantom_camera/scripts/resources/Camera3DResource.cs b/addons/phantom_camera/scripts/resources/Camera3DResource.cs new file mode 100644 index 00000000..0d5dd399 --- /dev/null +++ b/addons/phantom_camera/scripts/resources/Camera3DResource.cs @@ -0,0 +1,122 @@ +using Godot; + +namespace PhantomCamera.Resources; + +public enum ProjectionType +{ + Perspective, + Orthogonal, + Frustum +} + +public class Camera3DResource +{ + public readonly Resource Resource; + + public const float MinOffset = 0; + public const float MaxOffset = 1; + + public const float MinFov = 1; + public const float MaxFov = 179; + + public const float MinSize = 0.001f; + public const float MaxSize = 100; + + public const float MinNear = 0.001f; + public const float MaxNear = 10; + + public const float MinFar = 0.01f; + public const float MaxFar = 4000; + + public int CullMask + { + get => (int)Resource.Call(MethodName.GetCullMask); + set => Resource.Call(MethodName.SetCullMask, value); + } + + public float HOffset + { + get => (float)Resource.Call(MethodName.GetHOffset); + set => Resource.Call(MethodName.SetHOffset, Mathf.Clamp(value, MinOffset, MaxOffset)); + } + + public float VOffset + { + get => (float)Resource.Call(MethodName.GetVOffset); + set => Resource.Call(MethodName.SetVOffset, Mathf.Clamp(value, MinOffset, MaxOffset)); + } + + public ProjectionType Projection + { + get => (ProjectionType)(int)Resource.Call(MethodName.GetProjection); + set => Resource.Call(MethodName.SetProjection, (int)value); + } + + public float Fov + { + get => (float)Resource.Call(MethodName.GetFov); + set => Resource.Call(MethodName.SetFov, Mathf.Clamp(value, MinFov, MaxFov)); + } + + public float Size + { + get => (float)Resource.Call(MethodName.GetSize); + set => Resource.Call(MethodName.SetSize, Mathf.Clamp(value, MinSize, MaxSize)); + } + + public Vector2 FrustumOffset + { + get => (Vector2)Resource.Call(MethodName.GetFrustumOffset); + set => Resource.Call(MethodName.SetFrustumOffset, value); + } + + public float Near + { + get => (float)Resource.Call(MethodName.GetNear); + set => Resource.Call(MethodName.SetNear, Mathf.Clamp(value, MinNear, MaxNear)); + } + + public float Far + { + get => (float)Resource.Call(MethodName.GetFar); + set => Resource.Call(MethodName.SetFar, Mathf.Clamp(value, MinFar, MaxFar)); + } + + public Camera3DResource(Resource resource) => Resource = resource; + + public void SetCullMaskValue(int layerNumber, bool value) + { + Resource.Call(MethodName.SetCullMaskValue, layerNumber, value); + } + + public static class MethodName + { + public const string GetCullMask = "get_cull_mask"; + public const string SetCullMask = "set_cull_mask"; + public const string SetCullMaskValue = "set_cull_mask_value"; + + public const string GetHOffset = "get_h_offset"; + public const string SetHOffset = "set_h_offset"; + + public const string GetVOffset = "get_v_offset"; + public const string SetVOffset = "set_v_offset"; + + public const string GetProjection = "get_projection"; + public const string SetProjection = "set_projection"; + + public const string GetFov = "get_fov"; + public const string SetFov = "set_fov"; + + public const string GetSize = "get_size"; + public const string SetSize = "set_size"; + + public const string GetFrustumOffset = "get_frustum_offset"; + public const string SetFrustumOffset = "set_frustum_offset"; + + public const string GetNear = "get_near"; + public const string SetNear = "set_near"; + + public const string GetFar = "get_far"; + public const string SetFar = "set_far"; + } +} \ No newline at end of file diff --git a/addons/phantom_camera/scripts/resources/PhantomCameraTween.cs b/addons/phantom_camera/scripts/resources/PhantomCameraTween.cs new file mode 100644 index 00000000..b596f0a8 --- /dev/null +++ b/addons/phantom_camera/scripts/resources/PhantomCameraTween.cs @@ -0,0 +1,58 @@ +using Godot; + +namespace PhantomCamera.Resources; + +public enum TransitionType +{ + Linear, + Sine, + Quintic, + Quartic, + Quadratic, + Exponential, + Elastic, + Cubic, + Circ, + Bounce, + Back +} + +public enum EaseType +{ + In, + Out, + InOut, + OutIn +} + +public class PhantomCameraTween +{ + public Resource Resource { get; } + + public float Duration + { + get => (float)Resource.Get(PropertyName.Duration); + set => Resource.Set(PropertyName.Duration, value); + } + + public TransitionType Transition + { + get => (TransitionType)(int)Resource.Get(PropertyName.Transition); + set => Resource.Set(PropertyName.Transition, (int)value); + } + + public EaseType Ease + { + get => (EaseType)(int)Resource.Get(PropertyName.Ease); + set => Resource.Set(PropertyName.Ease, (int)value); + } + + public PhantomCameraTween(Resource tweenResource) => Resource = tweenResource; + + public static class PropertyName + { + public const string Duration = "durartion"; + public const string Transition = "transition"; + public const string Ease = "ease"; + } +} \ No newline at end of file diff --git a/dev_scenes/3d/dev_scene_3d.tscn b/dev_scenes/3d/dev_scene_3d.tscn index 36a7af77..f350e28e 100644 --- a/dev_scenes/3d/dev_scene_3d.tscn +++ b/dev_scenes/3d/dev_scene_3d.tscn @@ -17,6 +17,27 @@ fog_density = 0.0392 adjustment_enabled = true adjustment_contrast = 1.19 +<<<<<<< HEAD +[sub_resource type="Resource" id="Resource_sj6ok"] +script = ExtResource("6_pmc8r") +duration = 0.6 +transition = 2 +ease = 2 + +[sub_resource type="Resource" id="Resource_a3u85"] +script = ExtResource("7_fioii") +cull_mask = 1048575 +h_offset = 0.0 +v_offset = 0.0 +projection = 0 +fov = 75.0 +size = 1.0 +frustum_offset = Vector2(0, 0) +near = 0.05 +far = 4000.0 + +======= +>>>>>>> main [sub_resource type="Resource" id="Resource_6c6yi"] script = ExtResource("6_pmc8r") duration = 1.0 @@ -33,6 +54,9 @@ fov = 75.0 size = 1.0 frustum_offset = Vector2(0, 0) near = 0.05 +<<<<<<< HEAD +far = 4000.0 +======= far = 2000.0 [sub_resource type="Resource" id="Resource_sj6ok"] @@ -52,6 +76,7 @@ size = 1.0 frustum_offset = Vector2(0, 0) near = 0.05 far = 2000.0 +>>>>>>> main [node name="Node3D" type="Node3D"] script = ExtResource("1_gnrfx") diff --git a/dev_scenes/3d/dev_scene_csharp_3d.tscn b/dev_scenes/3d/dev_scene_csharp_3d.tscn new file mode 100644 index 00000000..7b44bb82 --- /dev/null +++ b/dev_scenes/3d/dev_scene_csharp_3d.tscn @@ -0,0 +1,81 @@ +[gd_scene load_steps=13 format=3 uid="uid://brb8fl27ofqhv"] + +[ext_resource type="Script" path="res://dev_scenes/3d/scripts/DevSceneCSharp3D.cs" id="1_fd2f4"] +[ext_resource type="Script" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="2_m8s1w"] +[ext_resource type="Script" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd" id="3_xni2u"] +[ext_resource type="Script" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="4_c7bav"] +[ext_resource type="Script" path="res://addons/phantom_camera/scripts/resources/camera_3d_resource.gd" id="5_jea0a"] + +[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_vuocs"] +sky_horizon_color = Color(0.64625, 0.65575, 0.67075, 1) +ground_horizon_color = Color(0.64625, 0.65575, 0.67075, 1) + +[sub_resource type="Sky" id="Sky_evu1o"] +sky_material = SubResource("ProceduralSkyMaterial_vuocs") + +[sub_resource type="Environment" id="Environment_5sile"] +background_mode = 2 +sky = SubResource("Sky_evu1o") +tonemap_mode = 2 +glow_enabled = true + +[sub_resource type="CapsuleMesh" id="CapsuleMesh_vcumb"] + +[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_04sci"] + +[sub_resource type="Resource" id="Resource_nsh7j"] +script = ExtResource("4_c7bav") +duration = 1.0 +transition = 0 +ease = 2 + +[sub_resource type="Resource" id="Resource_ujhhy"] +script = ExtResource("5_jea0a") +cull_mask = 1048575 +h_offset = 0.0 +v_offset = 0.0 +projection = 0 +fov = 75.0 +size = 1.0 +frustum_offset = Vector2(0, 0) +near = 0.05 +far = 4000.0 + +[node name="Node3D" type="Node3D"] +script = ExtResource("1_fd2f4") + +[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."] +transform = Transform3D(-0.866023, -0.433016, 0.250001, 0, 0.499998, 0.866027, -0.500003, 0.749999, -0.43301, 0, 0, 0) +shadow_enabled = true + +[node name="WorldEnvironment" type="WorldEnvironment" parent="."] +environment = SubResource("Environment_5sile") + +[node name="Camera3D" type="Camera3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.25252, 4) + +[node name="PhantomCameraHost" type="Node" parent="Camera3D"] +script = ExtResource("2_m8s1w") + +[node name="CSGBox3D" type="CSGBox3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.514567, 0) +use_collision = true +size = Vector3(10, 1, 10) + +[node name="Player" type="CharacterBody3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.25252, 0) + +[node name="MeshInstance3D" type="MeshInstance3D" parent="Player"] +mesh = SubResource("CapsuleMesh_vcumb") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="Player"] +shape = SubResource("CapsuleShape3D_04sci") + +[node name="PlayerCam" type="Node3D" parent="Player" node_paths=PackedStringArray("follow_target")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 4) +script = ExtResource("3_xni2u") +follow_mode = 6 +follow_target = NodePath("..") +tween_resource = SubResource("Resource_nsh7j") +camera_3d_resource = SubResource("Resource_ujhhy") +follow_offset = Vector3(0, 1, 3) diff --git a/dev_scenes/3d/scripts/DevSceneCSharp3D.cs b/dev_scenes/3d/scripts/DevSceneCSharp3D.cs new file mode 100644 index 00000000..a191968c --- /dev/null +++ b/dev_scenes/3d/scripts/DevSceneCSharp3D.cs @@ -0,0 +1,14 @@ +using Godot; +using PhantomCamera; + +public partial class DevSceneCSharp3D : Node3D +{ + public override void _Ready() + { + var pCam = GetNode("Player/PlayerCam").AsPhantomCamera3D(); + + GD.Print(pCam.Node3D); + + + } +} diff --git a/tests/scenes/test_runner.tscn b/tests/scenes/test_runner.tscn new file mode 100644 index 00000000..c17a6eff --- /dev/null +++ b/tests/scenes/test_runner.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://bu4xjlkrknuo5"] + +[ext_resource type="Script" path="res://tests/scripts/test_runner.gd" id="1_m0lqx"] + +[node name="TestRunner" type="Node"] +script = ExtResource("1_m0lqx") diff --git a/tests/scenes/test_scene_2d.tscn b/tests/scenes/test_scene_2d.tscn new file mode 100644 index 00000000..f2bd1424 --- /dev/null +++ b/tests/scenes/test_scene_2d.tscn @@ -0,0 +1,37 @@ +[gd_scene load_steps=7 format=3 uid="uid://bx61t0ytiwtwm"] + +[ext_resource type="Script" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="1_qr8fr"] +[ext_resource type="Script" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_2d.gd" id="2_53jhm"] +[ext_resource type="Script" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="3_aqfsy"] + +[sub_resource type="Resource" id="Resource_f0onm"] +script = ExtResource("3_aqfsy") +duration = 1.0 +transition = 0 +ease = 2 + +[sub_resource type="TileSet" id="TileSet_lpbxs"] + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_4v04e"] + +[node name="TestScene2D" type="Node2D"] + +[node name="Camera2D" type="Camera2D" parent="."] + +[node name="PhantomCameraHost" type="Node" parent="Camera2D"] +script = ExtResource("1_qr8fr") + +[node name="PhantomCamera2D" type="Node2D" parent="."] +script = ExtResource("2_53jhm") +tween_resource = SubResource("Resource_f0onm") + +[node name="TileMap" type="TileMap" parent="."] +tile_set = SubResource("TileSet_lpbxs") +format = 2 + +[node name="Area2D" type="Area2D" parent="."] + +[node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"] +shape = SubResource("RectangleShape2D_4v04e") + +[node name="Marker2D" type="Marker2D" parent="."] diff --git a/tests/scenes/test_scene_3d.tscn b/tests/scenes/test_scene_3d.tscn new file mode 100644 index 00000000..d654f4a9 --- /dev/null +++ b/tests/scenes/test_scene_3d.tscn @@ -0,0 +1,36 @@ +[gd_scene load_steps=7 format=3 uid="uid://bry2ltsrujg02"] + +[ext_resource type="Script" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="1_egs8c"] +[ext_resource type="Script" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd" id="2_fln48"] +[ext_resource type="Script" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="3_ir72h"] +[ext_resource type="Script" path="res://addons/phantom_camera/scripts/resources/camera_3d_resource.gd" id="4_w22nl"] + +[sub_resource type="Resource" id="Resource_om1dn"] +script = ExtResource("3_ir72h") +duration = 1.0 +transition = 0 +ease = 2 + +[sub_resource type="Resource" id="Resource_omnwe"] +script = ExtResource("4_w22nl") +cull_mask = 1048575 +h_offset = 0.0 +v_offset = 0.0 +projection = 0 +fov = 75.0 +size = 1.0 +frustum_offset = Vector2(0, 0) +near = 0.05 +far = 4000.0 + +[node name="TestScene3D" type="Node3D"] + +[node name="Camera3D" type="Camera3D" parent="."] + +[node name="PhantomCameraHost" type="Node" parent="Camera3D"] +script = ExtResource("1_egs8c") + +[node name="PhantomCamera3D" type="Node3D" parent="."] +script = ExtResource("2_fln48") +tween_resource = SubResource("Resource_om1dn") +camera_3d_resource = SubResource("Resource_omnwe") diff --git a/tests/scripts/TestPhantomCameraWrapper.cs b/tests/scripts/TestPhantomCameraWrapper.cs new file mode 100644 index 00000000..537dcca6 --- /dev/null +++ b/tests/scripts/TestPhantomCameraWrapper.cs @@ -0,0 +1,186 @@ +using System.Diagnostics; +using Godot; +using PhantomCamera; +using PhantomCamera.Cameras; +using PhantomCamera.Managers; +using PhantomCamera.Resources; + +namespace PhantomCameraTests; + +public partial class TestPhantomCameraWrapper: Node +{ + private PackedScene _scene2d; + + private PackedScene _scene3d; + + public override void _Ready() + { + _scene2d = GD.Load("res://tests/scenes/test_scene_2d.tscn"); + _scene3d = GD.Load("res://tests/scenes/test_scene_3d.tscn"); + } + + public void Test() + { + Test2D(); + Test3D(); + GD.Print("PhantomCameraWrapper tests complete"); + } + + private void Test2D() + { + var testScene = _scene2d.Instantiate(); + AddChild(testScene); + + // PhantomCameraManager tests + Debug.Assert(PhantomCameraManager.Instance != null); + Debug.Assert(PhantomCameraManager.PhantomCamera3Ds.Length == 0); + Debug.Assert(PhantomCameraManager.PhantomCamera2Ds.Length == 1); + Debug.Assert(PhantomCameraManager.PhantomCameraHosts.Length == 1); + + // PhantomCameraHost tests + var cameraHost = testScene.GetNode("Camera2D/PhantomCameraHost").AsPhantomCameraHost(); + Debug.Assert(cameraHost.Node != null); + Debug.Assert(cameraHost.Camera2D != null); + Debug.Assert(cameraHost.Camera3D == null); + Debug.Assert(cameraHost.TriggerPhantomCameraTween); + + var cameraQuery = cameraHost.GetActivePhantomCamera(); + Debug.Assert(cameraQuery != null); + Debug.Assert(cameraQuery.Is2D); + Debug.Assert(!cameraQuery.Is3D); + Debug.Assert(cameraQuery.AsPhantomCamera3D() == null); + + // PhantomCamera shared tests + var camera = cameraQuery.AsPhantomCamera2D(); + Debug.Assert(camera != null); + Debug.Assert(camera.Node2D != null); + + Debug.Assert(camera.FollowMode == FollowMode.None); + Debug.Assert(camera.IsActive); + + var priority = camera.Priority; + camera.Priority += 10; + Debug.Assert(camera.Priority == priority + 10); + + var tweenOnLoad = camera.TweenOnLoad; + camera.TweenOnLoad = !camera.TweenOnLoad; + Debug.Assert(camera.TweenOnLoad != tweenOnLoad); + + Debug.Assert(camera.InactiveUpdateMode == InactiveUpdateMode.Always); + camera.InactiveUpdateMode = InactiveUpdateMode.Never; + Debug.Assert(camera.InactiveUpdateMode == InactiveUpdateMode.Never); + + // TweenResource tests + var tweenResource = camera.TweenResource; + Debug.Assert(tweenResource.Resource != null); + + var tweenDuration = tweenResource.Duration; + tweenResource.Duration += 1.0f; + Debug.Assert((tweenResource.Duration - (tweenDuration + 1.0f)) <= float.Epsilon); + + tweenResource.Ease = EaseType.Out; + Debug.Assert(tweenResource.Ease == EaseType.Out); + + tweenResource.Transition = TransitionType.Sine; + Debug.Assert(tweenResource.Transition == TransitionType.Sine); + + var tweenResourceScript = GD.Load("res://addons/phantom_camera/scripts/resources/tween_resource.gd"); + var newTweenResource = new PhantomCameraTween(tweenResourceScript.New().As()) + { + Duration = 1.5f, + Ease = EaseType.In, + Transition = TransitionType.Cubic + }; + camera.TweenResource = newTweenResource; + + Debug.Assert((camera.TweenResource.Duration - 1.5f) <= float.Epsilon); + Debug.Assert(camera.TweenResource.Ease == EaseType.In); + Debug.Assert(camera.TweenResource.Transition == TransitionType.Cubic); + + // PhantomCamera2D tests + camera.Zoom = new Vector2(2, 2); + Debug.Assert(camera.Zoom.Equals(new Vector2(2, 2))); + + var snapToPixel = camera.SnapToPixel; + camera.SnapToPixel = !camera.SnapToPixel; + Debug.Assert(camera.SnapToPixel != snapToPixel); + + camera.LimitLeft = 2; + camera.LimitTop = 3; + camera.LimitRight = 4; + camera.LimitBottom = 5; + Debug.Assert(camera.LimitLeft == camera.GetLimit(Side.Left)); + Debug.Assert(camera.LimitTop == camera.GetLimit(Side.Top)); + Debug.Assert(camera.LimitRight == camera.GetLimit(Side.Right)); + Debug.Assert(camera.LimitBottom == camera.GetLimit(Side.Bottom)); + + camera.SetLimit(Side.Left, 5); + camera.SetLimit(Side.Top, 4); + camera.SetLimit(Side.Right, 3); + camera.SetLimit(Side.Bottom, 2); + Debug.Assert(camera.LimitLeft == camera.GetLimit(Side.Left)); + Debug.Assert(camera.LimitTop == camera.GetLimit(Side.Top)); + Debug.Assert(camera.LimitRight == camera.GetLimit(Side.Right)); + Debug.Assert(camera.LimitBottom == camera.GetLimit(Side.Bottom)); + + Debug.Assert(camera.GetLimitTarget() == null); + + var tileMap = testScene.GetNode("TileMap"); + camera.SetLimitTarget(tileMap); + var limitTarget = camera.GetLimitTarget(); + Debug.Assert(limitTarget != null); + Debug.Assert(limitTarget.IsTileMap); + Debug.Assert(limitTarget.AsTileMap() != null); + + var tileMapLayer = testScene.GetNode("TileMapLayer"); + camera.SetLimitTarget(tileMapLayer); + limitTarget = camera.GetLimitTarget(); + Debug.Assert(limitTarget != null); + Debug.Assert(limitTarget.IsTileMapLayer); + Debug.Assert(limitTarget.AsTileMapLayer() != null); + + var shape2D = testScene.GetNode("Area2D/CollisionShape2D"); + camera.SetLimitTarget(shape2D); + limitTarget = camera.GetLimitTarget(); + Debug.Assert(limitTarget != null); + Debug.Assert(limitTarget.IsCollisionShape2D); + Debug.Assert(limitTarget.AsCollisionShape2D() != null); + + // TODO: test LimitMargin + + // TODO: test signals + + RemoveChild(testScene); + } + + private void Test3D() + { + var testScene = _scene3d.Instantiate(); + AddChild(testScene); + + // PhantomCameraManager Tests + Debug.Assert(PhantomCameraManager.Instance != null); + Debug.Assert(PhantomCameraManager.PhantomCamera2Ds.Length == 0); + Debug.Assert(PhantomCameraManager.PhantomCamera3Ds.Length == 1); + Debug.Assert(PhantomCameraManager.PhantomCameraHosts.Length == 1); + + // PhantomCameraHost Tests + var cameraHost = testScene.GetNode("Camera3D/PhantomCameraHost").AsPhantomCameraHost(); + Debug.Assert(cameraHost.Node != null); + Debug.Assert(cameraHost.Camera2D == null); + Debug.Assert(cameraHost.Camera3D != null); + Debug.Assert(cameraHost.TriggerPhantomCameraTween); + + var cameraQuery = cameraHost.GetActivePhantomCamera(); + Debug.Assert(cameraQuery != null); + Debug.Assert(!cameraQuery.Is2D); + Debug.Assert(cameraQuery.Is3D); + Debug.Assert(cameraQuery.AsPhantomCamera2D() == null); + + // PhantomCamera3D Tests + var camera = cameraQuery.AsPhantomCamera3D(); + Debug.Assert(camera != null); + + RemoveChild(testScene); + } +} \ No newline at end of file diff --git a/tests/scripts/test_phantom_camera.gd b/tests/scripts/test_phantom_camera.gd new file mode 100644 index 00000000..e5f101f7 --- /dev/null +++ b/tests/scripts/test_phantom_camera.gd @@ -0,0 +1,7 @@ +extends Node + +@onready var scene2d = load("res://tests/scenes/test_scene_2d.tscn") +@onready var scene3d = load("res://tests/scenes/test_scene_3d.tscn") + +func Test() -> void: + print("No GDScript tests implemented") diff --git a/tests/scripts/test_runner.gd b/tests/scripts/test_runner.gd new file mode 100644 index 00000000..fa4b5795 --- /dev/null +++ b/tests/scripts/test_runner.gd @@ -0,0 +1,15 @@ +extends Node + +var test_scripts: Array[Script] = [ + load("res://tests/scripts/test_phantom_camera.gd"), + load("res://tests/scripts/TestPhantomCameraWrapper.cs"), +] + +func _ready() -> void: + for script: Script in test_scripts: + var test: Object = script.new() + if test.has_method("Test"): + add_child(test) + test.Test() + remove_child(test) + get_tree().quit()