Relationship System

v1.1.0

UI System

View interfaces, base classes, and ready-to-use UI components for displaying relationships.

Overview

The UI System provides interfaces, base classes, and production-ready implementations for displaying relationships in your game. Build custom UI by implementing interfaces or extending optimized base classes.

Start with the Simple views for quick prototyping, then create custom implementations extending RelationshipViewBase for your final UI.

View Interfaces

Core interfaces defining contracts for relationship UI components.

IRelationshipView

Core interface for any relationship display

void SetRelationship(RelationshipState state)
void UpdateValue(float value)
void UpdateLevel(RelationshipLevel level)
void Clear()

IFactionView

Interface for faction displays

void SetFaction(Faction faction)
void UpdateMemberCount(int count)
void ShowHierarchy(bool show)

IRelationshipNotification

Interface for popup notifications

void ShowValueChange(IRelationshipEntity entityA, IRelationshipEntity entityB, float delta)
void ShowLevelChange(..., RelationshipLevel newLevel)
void SetDuration(float seconds)
void Hide()

Base Classes

Abstract base classes that handle event subscription, caching, and common functionality.

RelationshipViewBase

Base class for relationship displays. Handles events, caching, and auto-updates.

  • Auto-subscribes to RelationshipManager events
  • Filters by entity pair and optional definition
  • Caches GameObjects, Entity IDs, and Factions
  • Dirty flag prevents redundant refreshes
  • Abstract DisplayRelationship() method

FactionViewBase

Base class for faction displays. Tracks member changes automatically.

  • Subscribes to faction member events
  • Caches member count
  • Tracks hierarchy changes
  • Abstract UpdateFactionDisplay() method
// Extend RelationshipViewBase for custom UI
public class MyCustomView : RelationshipViewBase
{
    [SerializeField] private TextMeshProUGUI m_ValueText;
    [SerializeField] private Image m_FillBar;
    
    // Called when relationship data changes
    protected override void DisplayRelationship(RelationshipState state, 
        RelationshipSource source)
    {
        if (state == null) {
            m_ValueText.text = "No Relationship";
            m_FillBar.fillAmount = 0;
            return;
        }
        
        m_ValueText.text = $"{state.CurrentValue:F0}";
        m_FillBar.fillAmount = state.CurrentValue / 100f;
    }
    
    // Optional: Called specifically when value changes
    protected override void UpdateValue(float newValue)
    {
        m_ValueText.text = $"{newValue:F0}";
        m_FillBar.fillAmount = newValue / 100f;
    }
}

Simple Views

Ready-to-use UI components for quick implementation. Drop onto Canvas and configure.

SimpleRelationshipView

Bar Display

Fill bar with value and level display. Auto-updates on relationship changes.

  • Progress bar fill
  • Value text display
  • Level name display
  • Color coding by level
  • Auto-update on changes
  • Definition-scoped filtering

SimpleFactionView

Info Panel

Faction icon, name, description, and member count.

  • Faction icon display
  • Member count
  • Description text
  • Hierarchy display
  • Auto-update

SimpleRelationshipNotification

Popup

Animated popup for relationship changes with auto-hide.

  • Fade in/out animations
  • Value delta display (+/-)
  • Level change announcements
  • Entity name resolution
  • Faction name resolution
  • Frame-batching for rapid changes

Notifications

Use SimpleRelationshipNotification for popup feedback when relationships change.

Setup Steps

  1. Add SimpleRelationshipNotification to Canvas
  2. Configure animation timing (fade in/out duration)
  3. Set display duration and positioning
  4. Component auto-subscribes to RelationshipManager events

Frame Batching (v1.1)

When multiple relationship changes occur in the same frame (e.g., faction broadcasts), notifications are batched to show a single combined message instead of rapid flashes.

// Notification displays:
// "+15 Reputation with Merchant Guild"
// "Reached Level: Friendly"

// For faction-level relationships, entity names are resolved:
// "Town Guards → Bandits: -25"

Definition-Scoped Views (v1.1)

Views can now filter by RelationshipDefinition, enabling multiple UI elements for different relationship dimensions between the same entity pair.

Use Case: Multi-Dimensional Relationships

Player ↔ Merchant can have separate Trust, Trade, and Respect dimensions. Each needs its own UI bar tracking only that definition.

// In SimpleRelationshipView Inspector:
// Entity A: Player
// Entity B: Merchant  
// Definition: "Trust" (RelationshipDefinition asset)

// The view will ONLY show/update the Trust dimension,
// ignoring Trade and Respect changes for the same pair.

How It Works

  • m_Definition field filters which relationship to display
  • Event handlers check IsRelevantRelationship() before updating
  • Lookup uses definition-scoped key: entityA + entityB + definitionId
  • Falls back to unscoped lookup if definition is null
Leave the Definition field empty for backward compatibility — the view will track the default/unscoped relationship between the entities.

Performance Optimizations (v1.1)

RelationshipViewBase includes several optimizations for smooth performance with many active views.

GameObject Caching

Cached references to EntityA/EntityB GameObjects

Eliminates repeated PropertyGet evaluations

Entity ID Caching

Cached string IDs for fast comparison in event handlers

Avoids string allocations during event filtering

Faction Lookup Caching

Cached Faction references with invalidation on entity change

Reduces Registry lookups for faction-level relationships

Dirty Flag Pattern

m_NeedsRefresh flag prevents redundant LoadRelationship calls

Only reloads when state actually changes

// Cache invalidation happens automatically when:
// - SetEntities() is called with new entities
// - OnEnable() initializes the view
// - Refresh() is explicitly called

// Example: Switching displayed relationship
view.SetEntities(newEntityA, newEntityB);
// All caches cleared, fresh lookup performed

Event Filtering

Views only process events for their tracked relationship. The filtering uses cached IDs for fast string comparison without allocations.

// Internal event handler (simplified)
protected virtual void OnRelationshipValueChanged(
    string entityAId, string entityBId, float oldValue, float newValue)
{
    // Fast check using cached IDs
    if (!IsTrackedRelationship(entityAId, entityBId))
        return; // Not our relationship, skip
    
    // Also checks faction-level if entities don't match directly
    if (!IsRelevantRelationship(entityAId, entityBId))
        return;
    
    UpdateValue(newValue);
}

Custom Implementations

Create custom UI by implementing interfaces or extending base classes.

Recommended approach: Extend RelationshipViewBase for relationship displays to get event handling, caching, and filtering for free. Implement IRelationshipView directly only for completely custom solutions.

Testing Custom Views

Use the built-in test helpers to verify your custom implementation:

// In Play Mode tests
var view = gameObject.AddComponent<MyCustomView>();
view.SetEntities(entityA, entityB);

// Trigger a relationship change
RelationshipManager.Instance.ModifyRelationship(
    entityA.EntityId, entityB.EntityId, 25f);

// Verify your view updated
Assert.AreEqual(25f, view.DisplayedValue);