From 28aed55cd30d81a4c18fdd879dc5a2abcc23b908 Mon Sep 17 00:00:00 2001 From: "j.foucher" Date: Fri, 6 Mar 2026 17:07:03 +0100 Subject: [PATCH] Allow switching agents mid-conversation by looking at another agent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Conversation lock no longer prevents switching to a different agent. When in an active conversation, the player can look at another nearby agent for ConversationSwitchDelay seconds (default 1s) to switch. Looking at empty space keeps the current agent selected (no deselect). Works in multiplayer — each player has independent switch tracking. Co-Authored-By: Claude Opus 4.6 --- .../PS_AI_ConvAgent_InteractionComponent.cpp | 69 ++++++++++++++++--- .../PS_AI_ConvAgent_InteractionComponent.h | 16 ++++- 2 files changed, 73 insertions(+), 12 deletions(-) diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_InteractionComponent.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_InteractionComponent.cpp index dd2d182..31372b1 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_InteractionComponent.cpp +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Private/PS_AI_ConvAgent_InteractionComponent.cpp @@ -165,7 +165,7 @@ void UPS_AI_ConvAgent_InteractionComponent::TickComponent(float DeltaTime, ELeve // ───────────────────────────────────────────────────────────────────────────── // Selection evaluation // ───────────────────────────────────────────────────────────────────────────── -UPS_AI_ConvAgent_ElevenLabsComponent* UPS_AI_ConvAgent_InteractionComponent::EvaluateBestAgent() const +UPS_AI_ConvAgent_ElevenLabsComponent* UPS_AI_ConvAgent_InteractionComponent::EvaluateBestAgent() { UWorld* World = GetWorld(); if (!World) return nullptr; @@ -190,23 +190,25 @@ UPS_AI_ConvAgent_ElevenLabsComponent* UPS_AI_ConvAgent_InteractionComponent::Eva UPS_AI_ConvAgent_ElevenLabsComponent* CurrentAgent = SelectedAgent.Get(); // ── Conversation lock ────────────────────────────────────────────── - // While we're actively conversing with an agent, keep it selected as - // long as it's within interaction distance — ignore the view cone. - // This prevents deselect/reselect flicker when the player turns quickly - // (which would cause spurious OnAgentConnected re-broadcasts in - // persistent session mode). - if (CurrentAgent && CurrentAgent->bNetIsConversing) + // While we're actively conversing with an agent, keep it selected UNLESS + // the player is looking directly at a DIFFERENT agent within range. + // This allows switching between nearby agents by looking at them, while + // preventing deselect when looking at empty space (no agent in view cone). + // If no other agent is in the view cone, the current agent stays selected + // regardless of look direction — only distance can break the lock. + const bool bConversationLocked = CurrentAgent && CurrentAgent->bNetIsConversing; + bool bCurrentAgentInRange = false; + if (bConversationLocked) { if (AActor* AgentActor = CurrentAgent->GetOwner()) { const FVector AgentLoc = AgentActor->GetActorLocation() + FVector(0.0f, 0.0f, AgentEyeLevelOffset); const float DistSq = (AgentLoc - ViewLocation).SizeSquared(); - if (DistSq <= MaxDistSq) - { - return CurrentAgent; // Keep conversing agent selected. - } + bCurrentAgentInRange = (DistSq <= MaxDistSq); } + // If current agent is out of range, fall through to normal evaluation + // (which will select a new agent or nullptr). } for (UPS_AI_ConvAgent_ElevenLabsComponent* Agent : Agents) @@ -259,6 +261,51 @@ UPS_AI_ConvAgent_ElevenLabsComponent* UPS_AI_ConvAgent_InteractionComponent::Eva } } + // ── Conversation lock fallback ──────────────────────────────────── + // If we're in conversation and the current agent is still in range: + // - No other agent in view cone → keep current agent (don't deselect). + // - Different agent in view cone → switch after ConversationSwitchDelay. + if (bConversationLocked && bCurrentAgentInRange) + { + if (!BestCandidate || BestCandidate == CurrentAgent) + { + // Looking at current agent or empty space → keep current, reset switch timer. + PendingSwitchAgent.Reset(); + PendingSwitchStartTime = 0.0; + return CurrentAgent; + } + + // Player is looking at a different agent. Apply switch delay. + if (ConversationSwitchDelay <= 0.0f) + { + // Instant switch (delay = 0). + PendingSwitchAgent.Reset(); + PendingSwitchStartTime = 0.0; + return BestCandidate; + } + + // Start or continue the switch timer. + if (PendingSwitchAgent.Get() != BestCandidate) + { + // Player started looking at a new candidate — reset timer. + PendingSwitchAgent = BestCandidate; + PendingSwitchStartTime = FPlatformTime::Seconds(); + return CurrentAgent; // Not yet — keep current. + } + + // Same candidate as before — check if delay has elapsed. + const double Elapsed = FPlatformTime::Seconds() - PendingSwitchStartTime; + if (Elapsed < static_cast(ConversationSwitchDelay)) + { + return CurrentAgent; // Still waiting. + } + + // Delay elapsed → allow the switch. + PendingSwitchAgent.Reset(); + PendingSwitchStartTime = 0.0; + return BestCandidate; + } + return BestCandidate; } diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_InteractionComponent.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_InteractionComponent.h index e1caa6e..3294b78 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_InteractionComponent.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_ConvAgent/Source/PS_AI_ConvAgent/Public/PS_AI_ConvAgent_InteractionComponent.h @@ -117,6 +117,14 @@ public: // ── Conversation management ────────────────────────────────────────────── + /** How long (seconds) the player must look at a different agent before switching + * during an active conversation. Prevents accidental switches when glancing around. + * Only applies when a conversation is active (bNetIsConversing). */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS AI ConvAgent|Interaction", + meta = (ClampMin = "0.0", ClampMax = "5.0", + ToolTip = "Seconds the player must look at another agent before switching mid-conversation.\nPrevents accidental switches when glancing around.\n0 = instant switch.")) + float ConversationSwitchDelay = 1.0f; + /** Automatically start the WebSocket conversation when an agent is selected * (enters range + view cone). When false, selecting an agent only manages * gaze and visual awareness — the conversation must be started explicitly @@ -251,7 +259,7 @@ private: // ── Selection logic ────────────────────────────────────────────────────── /** Evaluate all registered agents, return the best candidate (or null). */ - UPS_AI_ConvAgent_ElevenLabsComponent* EvaluateBestAgent() const; + UPS_AI_ConvAgent_ElevenLabsComponent* EvaluateBestAgent(); /** Apply a new selection — fire events, reroute mic. */ void SetSelectedAgent(UPS_AI_ConvAgent_ElevenLabsComponent* NewAgent); @@ -296,4 +304,10 @@ private: FTimerHandle GazeAttachTimerHandle; FTimerHandle GazeDetachTimerHandle; + + // ── Conversation switch delay ──────────────────────────────────────── + // Tracks how long the player has been looking at a different agent + // while in an active conversation. Switch only happens after the delay. + TWeakObjectPtr PendingSwitchAgent; + double PendingSwitchStartTime = 0.0; };