Commit pending working-tree changes (local edits, line endings)

This commit is contained in:
cthangover committed 2026-10-21 12:31:54 +02:00
1 parent 5121419adc
commit c77c8ec960
6 files changed
+474 -474

No files matched your search

+37 -37
View File
@@ -1,37 +1,37 @@
# Godot 4.x # Godot 4.x
.godot/ .godot/
.idea/ .idea/
sdk/ sdk/
bin/ bin/
obj/ obj/
#ai #ai
.omo/ .omo/
.opencode/ .opencode/
gen/ gen/
obj/ obj/
bin/ bin/
*.tres~ *.tres~
*.tscn~ *.tscn~
# GDScript LSP # GDScript LSP
.godot/ .godot/
# Export # Export
*.exe *.exe
*.dmg *.dmg
*.apk *.apk
*.aab *.aab
*.pck *.pck
*.zip *.zip
*.log *.log
# OS # OS
.DS_Store .DS_Store
Thumbs.db Thumbs.db
# Editor # Editor
*.import *.import
*.godot.uid *.godot.uid
+8 -8
View File
@@ -1,16 +1,16 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Cthangover.Core.Factories; using Cthangover.Core.Factories;
using Cthangover.Core.UI.Theming; using Cthangover.Core.UI.Theming;
using Godot; using Godot;
namespace Cthangover.Core.Interactive namespace Cthangover.Core.Interactive
{ {
/// <summary> /// <summary>
/// JSON-driven definition of an interactive scene object loaded from mods. /// JSON-driven definition of an interactive scene object loaded from mods.
/// The texture is always full-screen (alpha determines visibility outside the collider). /// The texture is always full-screen (alpha determines visibility outside the collider).
/// The collider defines the clickable region in normalised 0..1 viewport coordinates. /// The collider defines the clickable region in normalised 0..1 viewport coordinates.
/// Interactives keep their own resolver and <c>{"Items": [...]}</c> envelope; /// Interactives keep their own resolver and <c>{"Items": [...]}</c> envelope;
/// unlike other entity kinds they are not part of the kind-routed pipeline. /// unlike other entity kinds they are not part of the kind-routed pipeline.
/// </summary> /// </summary>
public class InteractiveDefinition : IIdentifiable public class InteractiveDefinition : IIdentifiable
{ {
@@ -166,4 +166,4 @@ namespace Cthangover.Core.Interactive
public string[] Commands { get; set; } public string[] Commands { get; set; }
} }
} }
+399 -399
View File
@@ -1,399 +1,399 @@
using System; using System;
using Cthangover.Core.Mods; using Cthangover.Core.Mods;
using Cthangover.Core.Mods.Resolvers; using Cthangover.Core.Mods.Resolvers;
using Cthangover.Core.Scenes; using Cthangover.Core.Scenes;
using Cthangover.Core.UI.Executable; using Cthangover.Core.UI.Executable;
using Cthangover.Core.Utils; using Cthangover.Core.Utils;
using Cthangover.Core.UI.Theming; using Cthangover.Core.UI.Theming;
using Godot; using Godot;
namespace Cthangover.Core.Interactive namespace Cthangover.Core.Interactive
{ {
/// <summary> /// <summary>
/// Runtime node for an interactive scene object. The Control fills its parent /// Runtime node for an interactive scene object. The Control fills its parent
/// (the interactive layer container) and displays a full-screen texture with /// (the interactive layer container) and displays a full-screen texture with
/// alpha. The collider (rect, circle, or polygon) defines the clickable region /// alpha. The collider (rect, circle, or polygon) defines the clickable region
/// in normalised viewport coordinates, converted to pixels at runtime. /// in normalised viewport coordinates, converted to pixels at runtime.
/// ///
/// Hit detection uses <c>_HasPoint</c> overrides verified against global /// Hit detection uses <c>_HasPoint</c> overrides verified against global
/// mouse position and the pixel-space collider geometry. /// mouse position and the pixel-space collider geometry.
/// </summary> /// </summary>
public partial class InteractiveObject : Control, IEventObject public partial class InteractiveObject : Control, IEventObject
{ {
private static readonly Color _transparent = new(0f, 0f, 0f, 0f); private static readonly Color _transparent = new(0f, 0f, 0f, 0f);
/// <summary>Unique instance identifier, used for registry and <c>IEventObject</c> tracking.</summary> /// <summary>Unique instance identifier, used for registry and <c>IEventObject</c> tracking.</summary>
public string ID { get; set; } public string ID { get; set; }
/// <summary>Reference back to the <c>InteractiveDefinition.ID</c> that spawned this object.</summary> /// <summary>Reference back to the <c>InteractiveDefinition.ID</c> that spawned this object.</summary>
public string DefinitionId { get; private set; } public string DefinitionId { get; private set; }
/// <summary>Whether the object responds to mouse input.</summary> /// <summary>Whether the object responds to mouse input.</summary>
public bool IsEnabled { get; set; } = true; public bool IsEnabled { get; set; } = true;
/// <summary>True while the pointer is within the collider.</summary> /// <summary>True while the pointer is within the collider.</summary>
public bool IsHovered { get; private set; } public bool IsHovered { get; private set; }
/// <summary>Invoked on hover enter with the definition's hover-enter DSL (wired by <see cref="InteractiveManager"/>).</summary> /// <summary>Invoked on hover enter with the definition's hover-enter DSL (wired by <see cref="InteractiveManager"/>).</summary>
internal Action<string> OnHoverEnterCallback { get; set; } internal Action<string> OnHoverEnterCallback { get; set; }
/// <summary>Invoked on hover leave with the definition's hover-leave DSL.</summary> /// <summary>Invoked on hover leave with the definition's hover-leave DSL.</summary>
internal Action<string> OnHoverLeaveCallback { get; set; } internal Action<string> OnHoverLeaveCallback { get; set; }
/// <summary>Invoked on click with the definition id (wired by <see cref="InteractiveManager"/>).</summary> /// <summary>Invoked on click with the definition id (wired by <see cref="InteractiveManager"/>).</summary>
internal Action<string> OnClickCallback { get; set; } internal Action<string> OnClickCallback { get; set; }
private TextureRect _visual; private TextureRect _visual;
private ShaderMaterial _highlightMaterial; private ShaderMaterial _highlightMaterial;
private Tween _highlightTween; private Tween _highlightTween;
private HitAreaType _hitType = HitAreaType.Rect; private HitAreaType _hitType = HitAreaType.Rect;
private float _hitRadiusPx; private float _hitRadiusPx;
private Vector2[] _hitPolygonPx; private Vector2[] _hitPolygonPx;
private Rect2 _hitRectPx; private Rect2 _hitRectPx;
private Vector2 _contentSize; private Vector2 _contentSize;
private Color _highlightColor = _transparent; private Color _highlightColor = _transparent;
private float _highlightScale = 1f; private float _highlightScale = 1f;
private float _highlightDuration = 0.15f; private float _highlightDuration = 0.15f;
private string _onHoverEnterDsl; private string _onHoverEnterDsl;
private string _onHoverLeaveDsl; private string _onHoverLeaveDsl;
private ColorRect _debugRect; private ColorRect _debugRect;
private Label _debugLabel; private Label _debugLabel;
private bool _visualReady; private bool _visualReady;
/// <summary>Prepares the full-screen visual, highlight material and mouse handlers.</summary> /// <summary>Prepares the full-screen visual, highlight material and mouse handlers.</summary>
public override void _Ready() public override void _Ready()
{ {
MouseFilter = MouseFilterEnum.Stop; MouseFilter = MouseFilterEnum.Stop;
ClipContents = true; ClipContents = true;
EnsureVisualReady(); EnsureVisualReady();
MouseEntered += OnMouseEntered; MouseEntered += OnMouseEntered;
MouseExited += OnMouseExited; MouseExited += OnMouseExited;
GameLogger.Log("INTERACTIVE", $"_Ready: id='{ID}' size=({Size.X:F0}x{Size.Y:F0}) visible={Visible} enabled={IsEnabled}"); GameLogger.Log("INTERACTIVE", $"_Ready: id='{ID}' size=({Size.X:F0}x{Size.Y:F0}) visible={Visible} enabled={IsEnabled}");
} }
/// <summary> /// <summary>
/// Applies a definition to this instance. The Control is made full-screen /// Applies a definition to this instance. The Control is made full-screen
/// within its parent, the texture fills the entire area, and the collider /// within its parent, the texture fills the entire area, and the collider
/// geometry is computed from normalised coordinates multiplied by the /// geometry is computed from normalised coordinates multiplied by the
/// logical viewport size. /// logical viewport size.
/// </summary> /// </summary>
public void Configure(InteractiveDefinition def, Vector2 contentSize) public void Configure(InteractiveDefinition def, Vector2 contentSize)
{ {
if (def == null) if (def == null)
return; return;
DefinitionId = def.ID; DefinitionId = def.ID;
_contentSize = contentSize; _contentSize = contentSize;
EnsureVisualReady(); EnsureVisualReady();
AnchorLeft = 0f; AnchorLeft = 0f;
AnchorTop = 0f; AnchorTop = 0f;
AnchorRight = 1f; AnchorRight = 1f;
AnchorBottom = 1f; AnchorBottom = 1f;
ZIndex = def.ZIndex; ZIndex = def.ZIndex;
Visible = def.Visible; Visible = def.Visible;
IsEnabled = def.Enabled; IsEnabled = def.Enabled;
if (!string.IsNullOrEmpty(def.Texture)) if (!string.IsNullOrEmpty(def.Texture))
{ {
var tex = Textures.Resolve(def.Texture); var tex = Textures.Resolve(def.Texture);
if (tex != null) if (tex != null)
_visual.Texture = tex; _visual.Texture = tex;
else else
GameLogger.Log("INTERACTIVE", $"Configure: texture '{def.Texture}' for '{def.ID}' not found in any mod", LogLevel.Warning); GameLogger.Log("INTERACTIVE", $"Configure: texture '{def.Texture}' for '{def.ID}' not found in any mod", LogLevel.Warning);
} }
ApplyHitArea(def.HitArea, contentSize); ApplyHitArea(def.HitArea, contentSize);
if (def.Highlight != null) if (def.Highlight != null)
{ {
_highlightColor = def.Highlight.Color; _highlightColor = def.Highlight.Color;
_highlightScale = def.Highlight.Scale; _highlightScale = def.Highlight.Scale;
_highlightDuration = def.Highlight.Duration; _highlightDuration = def.Highlight.Duration;
} }
_onHoverEnterDsl = def.Actions?.OnHoverEnter; _onHoverEnterDsl = def.Actions?.OnHoverEnter;
_onHoverLeaveDsl = def.Actions?.OnHoverLeave; _onHoverLeaveDsl = def.Actions?.OnHoverLeave;
SetCursor(def.Cursor); SetCursor(def.Cursor);
GameLogger.Log("INTERACTIVE", $"Configure: id='{def.ID}' fullScreen contentSize=({contentSize.X:F0},{contentSize.Y:F0}) hitType={_hitType}"); GameLogger.Log("INTERACTIVE", $"Configure: id='{def.ID}' fullScreen contentSize=({contentSize.X:F0},{contentSize.Y:F0}) hitType={_hitType}");
} }
/// <summary> /// <summary>
/// Checks whether the global mouse position falls within the collider /// Checks whether the global mouse position falls within the collider
/// geometry in pixel space. Bypasses Godot's local coordinate issues. /// geometry in pixel space. Bypasses Godot's local coordinate issues.
/// </summary> /// </summary>
public override bool _HasPoint(Vector2 point) public override bool _HasPoint(Vector2 point)
{ {
if (!IsEnabled || !Visible) if (!IsEnabled || !Visible)
return false; return false;
var globalMouse = GetGlobalMousePosition(); var globalMouse = GetGlobalMousePosition();
var globalPos = GlobalPosition; var globalPos = GlobalPosition;
switch (_hitType) switch (_hitType)
{ {
case HitAreaType.Rect: case HitAreaType.Rect:
{ {
var rect = new Rect2(globalPos + _hitRectPx.Position, _hitRectPx.Size); var rect = new Rect2(globalPos + _hitRectPx.Position, _hitRectPx.Size);
return rect.HasPoint(globalMouse); return rect.HasPoint(globalMouse);
} }
case HitAreaType.Circle: case HitAreaType.Circle:
{ {
var center = globalPos + new Vector2(_hitRectPx.Position.X + _hitRectPx.Size.X / 2f, _hitRectPx.Position.Y + _hitRectPx.Size.Y / 2f); var center = globalPos + new Vector2(_hitRectPx.Position.X + _hitRectPx.Size.X / 2f, _hitRectPx.Position.Y + _hitRectPx.Size.Y / 2f);
return (globalMouse - center).Length() <= _hitRadiusPx; return (globalMouse - center).Length() <= _hitRadiusPx;
} }
case HitAreaType.Polygon: case HitAreaType.Polygon:
{ {
if (_hitPolygonPx == null || _hitPolygonPx.Length < 3) if (_hitPolygonPx == null || _hitPolygonPx.Length < 3)
return false; return false;
var local = globalMouse - globalPos; var local = globalMouse - globalPos;
return Geometry2D.IsPointInPolygon(local, _hitPolygonPx); return Geometry2D.IsPointInPolygon(local, _hitPolygonPx);
} }
} }
return false; return false;
} }
/// <summary> /// <summary>
/// Unregisters from the <c>SceneContextNode</c> event-object tracker and /// Unregisters from the <c>SceneContextNode</c> event-object tracker and
/// frees the Godot node. Called by <c>InteractiveManager.ClearAll</c> on /// frees the Godot node. Called by <c>InteractiveManager.ClearAll</c> on
/// scene transition and by <c>InteractiveManager.Remove</c> for targeted /// scene transition and by <c>InteractiveManager.Remove</c> for targeted
/// cleanup, ensuring no dangling references survive scene teardown. /// cleanup, ensuring no dangling references survive scene teardown.
/// </summary> /// </summary>
public void Destruct() public void Destruct()
{ {
SceneContextNode.Instance?.RemoveEventObject(ID); SceneContextNode.Instance?.RemoveEventObject(ID);
QueueFree(); QueueFree();
} }
/// <summary> /// <summary>
/// Creates or hides a debug overlay showing the hit-area bounds rectangle /// Creates or hides a debug overlay showing the hit-area bounds rectangle
/// and the object's <c>ID</c> label. The rectangle colour changes to /// and the object's <c>ID</c> label. The rectangle colour changes to
/// yellow when the pointer is inside the collider. Used by /// yellow when the pointer is inside the collider. Used by
/// <c>InteractiveManager.ShowDebugBounds</c> to toggle all objects at once. /// <c>InteractiveManager.ShowDebugBounds</c> to toggle all objects at once.
/// </summary> /// </summary>
/// <param name="show">Whether to create/show or hide the debug visuals.</param> /// <param name="show">Whether to create/show or hide the debug visuals.</param>
public void UpdateDebugVisual(bool show) public void UpdateDebugVisual(bool show)
{ {
if (show) if (show)
{ {
if (_debugRect == null) if (_debugRect == null)
{ {
_debugRect = new ColorRect _debugRect = new ColorRect
{ {
Name = "DebugRect", Name = "DebugRect",
MouseFilter = MouseFilterEnum.Ignore, MouseFilter = MouseFilterEnum.Ignore,
ZIndex = 100 ZIndex = 100
}; };
AddChild(_debugRect); AddChild(_debugRect);
} }
if (_debugLabel == null) if (_debugLabel == null)
{ {
_debugLabel = new Label _debugLabel = new Label
{ {
Name = "DebugLabel", Name = "DebugLabel",
MouseFilter = MouseFilterEnum.Ignore, MouseFilter = MouseFilterEnum.Ignore,
ZIndex = 101, ZIndex = 101,
Position = new Vector2(2f, 2f) Position = new Vector2(2f, 2f)
}; };
AddChild(_debugLabel); AddChild(_debugLabel);
} }
_debugRect.Position = _hitRectPx.Position; _debugRect.Position = _hitRectPx.Position;
_debugRect.Size = _hitRectPx.Size; _debugRect.Size = _hitRectPx.Size;
_debugRect.Visible = true; _debugRect.Visible = true;
_debugRect.Color = IsHovered _debugRect.Color = IsHovered
? UiTokens.Alpha(UiTokens.Accent, 0.35f) ? UiTokens.Alpha(UiTokens.Accent, 0.35f)
: UiTokens.Alpha(UiTokens.Success, 0.2f); : UiTokens.Alpha(UiTokens.Success, 0.2f);
_debugLabel.Visible = true; _debugLabel.Visible = true;
_debugLabel.Text = DefinitionId ?? ID; _debugLabel.Text = DefinitionId ?? ID;
} }
else else
{ {
if (_debugRect != null) if (_debugRect != null)
_debugRect.Visible = false; _debugRect.Visible = false;
if (_debugLabel != null) if (_debugLabel != null)
_debugLabel.Visible = false; _debugLabel.Visible = false;
} }
} }
private void SetupHighlightMaterial() private void SetupHighlightMaterial()
{ {
var shader = Shaders.Resolve("interactive_highlight"); var shader = Shaders.Resolve("interactive_highlight");
if (shader == null) if (shader == null)
{ {
GameLogger.Log("INTERACTIVE", "highlight shader 'interactive_highlight' not found", LogLevel.Warning); GameLogger.Log("INTERACTIVE", "highlight shader 'interactive_highlight' not found", LogLevel.Warning);
return; return;
} }
_highlightMaterial = new ShaderMaterial { Shader = shader }; _highlightMaterial = new ShaderMaterial { Shader = shader };
_highlightMaterial.SetShaderParameter("modulate_color", _transparent); _highlightMaterial.SetShaderParameter("modulate_color", _transparent);
_visual.Material = _highlightMaterial; _visual.Material = _highlightMaterial;
} }
private void EnsureVisualReady() private void EnsureVisualReady()
{ {
if (_visualReady) if (_visualReady)
return; return;
_visualReady = true; _visualReady = true;
_visual = new TextureRect _visual = new TextureRect
{ {
Name = "Visual", Name = "Visual",
ExpandMode = TextureRect.ExpandModeEnum.IgnoreSize, ExpandMode = TextureRect.ExpandModeEnum.IgnoreSize,
StretchMode = TextureRect.StretchModeEnum.Scale, StretchMode = TextureRect.StretchModeEnum.Scale,
MouseFilter = MouseFilterEnum.Ignore, MouseFilter = MouseFilterEnum.Ignore,
AnchorRight = 1f, AnchorRight = 1f,
AnchorBottom = 1f AnchorBottom = 1f
}; };
AddChild(_visual); AddChild(_visual);
SetupHighlightMaterial(); SetupHighlightMaterial();
} }
private void ApplyHitArea(HitAreaDefinition hitDef, Vector2 contentSize) private void ApplyHitArea(HitAreaDefinition hitDef, Vector2 contentSize)
{ {
_hitType = HitAreaType.Rect; _hitType = HitAreaType.Rect;
var vpW = contentSize.X; var vpW = contentSize.X;
var vpH = contentSize.Y; var vpH = contentSize.Y;
if (hitDef == null) if (hitDef == null)
{ {
_hitRectPx = new Rect2(0, 0, vpW, vpH); _hitRectPx = new Rect2(0, 0, vpW, vpH);
return; return;
} }
switch (hitDef.Type?.ToLowerInvariant()) switch (hitDef.Type?.ToLowerInvariant())
{ {
case "circle": case "circle":
_hitType = HitAreaType.Circle; _hitType = HitAreaType.Circle;
_hitRadiusPx = hitDef.Radius * Mathf.Min(vpW, vpH); _hitRadiusPx = hitDef.Radius * Mathf.Min(vpW, vpH);
var cx = hitDef.X * vpW; var cx = hitDef.X * vpW;
var cy = hitDef.Y * vpH; var cy = hitDef.Y * vpH;
_hitRectPx = new Rect2(cx - _hitRadiusPx, cy - _hitRadiusPx, _hitRadiusPx * 2f, _hitRadiusPx * 2f); _hitRectPx = new Rect2(cx - _hitRadiusPx, cy - _hitRadiusPx, _hitRadiusPx * 2f, _hitRadiusPx * 2f);
break; break;
case "polygon": case "polygon":
_hitType = HitAreaType.Polygon; _hitType = HitAreaType.Polygon;
if (hitDef.Vertices != null && hitDef.Vertices.Length >= 3) if (hitDef.Vertices != null && hitDef.Vertices.Length >= 3)
{ {
_hitPolygonPx = new Vector2[hitDef.Vertices.Length]; _hitPolygonPx = new Vector2[hitDef.Vertices.Length];
float minX = float.MaxValue, minY = float.MaxValue, maxX = float.MinValue, maxY = float.MinValue; float minX = float.MaxValue, minY = float.MaxValue, maxX = float.MinValue, maxY = float.MinValue;
for (var i = 0; i < hitDef.Vertices.Length; i++) for (var i = 0; i < hitDef.Vertices.Length; i++)
{ {
var v = new Vector2(hitDef.Vertices[i].X * vpW, hitDef.Vertices[i].Y * vpH); var v = new Vector2(hitDef.Vertices[i].X * vpW, hitDef.Vertices[i].Y * vpH);
_hitPolygonPx[i] = v; _hitPolygonPx[i] = v;
minX = Mathf.Min(minX, v.X); minX = Mathf.Min(minX, v.X);
minY = Mathf.Min(minY, v.Y); minY = Mathf.Min(minY, v.Y);
maxX = Mathf.Max(maxX, v.X); maxX = Mathf.Max(maxX, v.X);
maxY = Mathf.Max(maxY, v.Y); maxY = Mathf.Max(maxY, v.Y);
} }
_hitRectPx = new Rect2(minX, minY, maxX - minX, maxY - minY); _hitRectPx = new Rect2(minX, minY, maxX - minX, maxY - minY);
} }
break; break;
default: default:
_hitType = HitAreaType.Rect; _hitType = HitAreaType.Rect;
var rx = hitDef.X * vpW; var rx = hitDef.X * vpW;
var ry = hitDef.Y * vpH; var ry = hitDef.Y * vpH;
var rw = hitDef.Width * vpW; var rw = hitDef.Width * vpW;
var rh = hitDef.Height * vpH; var rh = hitDef.Height * vpH;
_hitRectPx = new Rect2(rx, ry, rw, rh); _hitRectPx = new Rect2(rx, ry, rw, rh);
break; break;
} }
} }
private void SetCursor(string cursorName) private void SetCursor(string cursorName)
{ {
if (string.IsNullOrEmpty(cursorName)) if (string.IsNullOrEmpty(cursorName))
return; return;
if (Enum.TryParse<CursorShape>(cursorName, true, out var shape)) if (Enum.TryParse<CursorShape>(cursorName, true, out var shape))
MouseDefaultCursorShape = shape; MouseDefaultCursorShape = shape;
} }
private void OnMouseEntered() private void OnMouseEntered()
{ {
GameLogger.Log("INTERACTIVE", $"OnMouseEntered: id='{ID}' enabled={IsEnabled} hitType={_hitType}"); GameLogger.Log("INTERACTIVE", $"OnMouseEntered: id='{ID}' enabled={IsEnabled} hitType={_hitType}");
if (!IsEnabled) if (!IsEnabled)
return; return;
IsHovered = true; IsHovered = true;
ApplyHighlight(true); ApplyHighlight(true);
UpdateDebugVisual(InteractiveManager.Instance?.ShowDebugBounds ?? false); UpdateDebugVisual(InteractiveManager.Instance?.ShowDebugBounds ?? false);
OnHoverEnterCallback?.Invoke(_onHoverEnterDsl); OnHoverEnterCallback?.Invoke(_onHoverEnterDsl);
} }
private void OnMouseExited() private void OnMouseExited()
{ {
GameLogger.Log("INTERACTIVE", $"OnMouseExited: id='{ID}'"); GameLogger.Log("INTERACTIVE", $"OnMouseExited: id='{ID}'");
if (!IsEnabled) if (!IsEnabled)
return; return;
IsHovered = false; IsHovered = false;
ApplyHighlight(false); ApplyHighlight(false);
UpdateDebugVisual(InteractiveManager.Instance?.ShowDebugBounds ?? false); UpdateDebugVisual(InteractiveManager.Instance?.ShowDebugBounds ?? false);
OnHoverLeaveCallback?.Invoke(_onHoverLeaveDsl); OnHoverLeaveCallback?.Invoke(_onHoverLeaveDsl);
} }
private void ApplyHighlight(bool active) private void ApplyHighlight(bool active)
{ {
if (_highlightMaterial == null) if (_highlightMaterial == null)
return; return;
_highlightTween?.Kill(); _highlightTween?.Kill();
var targetScale = active ? _highlightScale : 1f; var targetScale = active ? _highlightScale : 1f;
_highlightTween = CreateTween(); _highlightTween = CreateTween();
_highlightTween.SetParallel(true); _highlightTween.SetParallel(true);
_highlightTween.TweenProperty(this, "scale", Vector2.One * targetScale, _highlightDuration); _highlightTween.TweenProperty(this, "scale", Vector2.One * targetScale, _highlightDuration);
if (_highlightColor.A > 0f) if (_highlightColor.A > 0f)
{ {
_highlightTween.TweenMethod( _highlightTween.TweenMethod(
Callable.From<float>(f => Callable.From<float>(f =>
{ {
var c = new Color(_highlightColor.R, _highlightColor.G, _highlightColor.B, _highlightColor.A * f); var c = new Color(_highlightColor.R, _highlightColor.G, _highlightColor.B, _highlightColor.A * f);
_highlightMaterial.SetShaderParameter("modulate_color", c); _highlightMaterial.SetShaderParameter("modulate_color", c);
}), }),
active ? 0f : 1f, active ? 0f : 1f,
active ? 1f : 0f, active ? 1f : 0f,
_highlightDuration _highlightDuration
); );
} }
} }
/// <summary>Handles left-click press: fires the click callback and consumes the event.</summary> /// <summary>Handles left-click press: fires the click callback and consumes the event.</summary>
public override void _GuiInput(InputEvent @event) public override void _GuiInput(InputEvent @event)
{ {
if (!IsEnabled) if (!IsEnabled)
return; return;
if (@event is InputEventMouseButton mb && mb.ButtonIndex == MouseButton.Left && mb.Pressed) if (@event is InputEventMouseButton mb && mb.ButtonIndex == MouseButton.Left && mb.Pressed)
{ {
GameLogger.Log("INTERACTIVE", $"clicked: '{DefinitionId}'"); GameLogger.Log("INTERACTIVE", $"clicked: '{DefinitionId}'");
OnClickCallback?.Invoke(DefinitionId); OnClickCallback?.Invoke(DefinitionId);
AcceptEvent(); AcceptEvent();
} }
} }
} }
} }
@@ -1,28 +1,28 @@
{ {
"Items": [{ "Items": [{
"id": "kitchen_lamp", "id": "kitchen_lamp",
"texture": "lamp", "texture": "lamp",
"layer": "foreground", "layer": "foreground",
"zIndex": 10, "zIndex": 10,
"hitArea": { "hitArea": {
"type": "rect", "type": "rect",
"x": 0.68, "x": 0.68,
"y": 0.40, "y": 0.40,
"width": 0.08, "width": 0.08,
"height": 0.08 "height": 0.08
}, },
"highlight": { "highlight": {
"color": "#FFFF0033", "color": "#FFFF0033",
"scale": 1.1, "scale": 1.1,
"duration": 0.12 "duration": 0.12
}, },
"cursor": "PointingHand", "cursor": "PointingHand",
"actions": { "actions": {
"onClick": { "onClick": {
"scenario": "scenarios/kitchen_clicked.scenario" "scenario": "scenarios/kitchen_clicked.scenario"
}, },
"onHoverEnter": "set cursor_hint Кликни_лампу", "onHoverEnter": "set cursor_hint Кликни_лампу",
"onHoverLeave": "set cursor_hint \"\"" "onHoverLeave": "set cursor_hint \"\""
} }
}] }]
} }
@@ -1 +1 @@
test_interactive/click = You clicked the lamp! The interactive object works. test_interactive/click = You clicked the lamp! The interactive object works.
@@ -1 +1 @@
test_interactive/click = Вы кликнули по лампе! Интерактивный объект работает. test_interactive/click = Вы кликнули по лампе! Интерактивный объект работает.