> ## Documentation Index
> Fetch the complete documentation index at: https://docs.liv.tv/llms.txt
> Use this file to discover all available pages before exploring further.

# Integration

> How to integrate the LIV Camera Kit (LCK) tablet into your Unreal Engine VR game, including subsystem access, tablet lifecycle, grab setup, widget components, async callback patterns, and safe delegate binding.

## Overview

This guide covers accessing the LCK subsystem and service, tablet actor lifecycle, widget component setup, and async callback patterns.

***

## Accessing LCK Service

### Via World Subsystem

The recommended way to access LCK functionality:

```cpp theme={null}
ULCKSubsystem* GetLCKSubsystem(UWorld* World)
{
    if (World)
    {
        return World->GetSubsystem<ULCKSubsystem>();
    }
    return nullptr;
}

ULCKService* GetLCKService(UWorld* World)
{
    if (ULCKSubsystem* Subsystem = GetLCKSubsystem(World))
    {
        return Subsystem->GetService();
    }
    return nullptr;
}
```

### From Any Actor

```cpp theme={null}
void AMyActor::UseRecording()
{
    ULCKSubsystem* Subsystem = GetWorld()->GetSubsystem<ULCKSubsystem>();
    if (!Subsystem)
    {
        UE_LOG(LogTemp, Error, TEXT("LCK Subsystem not available"));
        return;
    }

    ULCKService* Service = Subsystem->GetService();
    if (Service)
    {
        Service->StartRecording();
    }
}
```

### Blueprint Access

```cpp theme={null}
UFUNCTION(BlueprintCallable, Category = "LCK", meta = (WorldContext = "WorldContextObject"))
static ULCKService* GetLCKService(UObject* WorldContextObject)
{
    if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull))
    {
        if (ULCKSubsystem* Subsystem = World->GetSubsystem<ULCKSubsystem>())
        {
            return Subsystem->GetService();
        }
    }
    return nullptr;
}
```

***

## Tablet Lifecycle

### Spawning

```cpp theme={null}
void AMyGameMode::SpawnLCKTablet()
{
    if (!TabletClass)
    {
        UE_LOG(LogTemp, Error, TEXT("TabletClass not set"));
        return;
    }

    FVector SpawnLocation = GetTabletSpawnLocation();
    FRotator SpawnRotation = FRotator::ZeroRotator;

    FActorSpawnParameters SpawnParams;
    SpawnParams.SpawnCollisionHandlingOverride =
        ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;

    SpawnedTablet = GetWorld()->SpawnActor<ALCKTablet>(
        TabletClass,
        SpawnLocation,
        SpawnRotation,
        SpawnParams
    );

    if (SpawnedTablet)
    {
        OnTabletSpawned(SpawnedTablet);
    }
}
```

### Destruction

```cpp theme={null}
void AMyGameMode::DestroyLCKTablet()
{
    if (SpawnedTablet)
    {
        // Stop any active recording first
        if (ULCKService* Service = GetLCKService(GetWorld()))
        {
            if (Service->IsRecording())
            {
                Service->StopRecording();
            }
        }

        SpawnedTablet->Destroy();
        SpawnedTablet = nullptr;
    }
}
```

### Lifecycle Events

```cpp theme={null}
void ALCKTablet::BeginPlay()
{
    Super::BeginPlay();

    // Load saved settings
    LoadSettings();

    // Initialize camera
    InitializeCamera();

    // Register with telemetry
    if (ULCKTelemetrySubsystem* Telemetry = GetGameInstance()->GetSubsystem<ULCKTelemetrySubsystem>())
    {
        FLCKTelemetryEvent Event;
        Event.EventType = ELCKTelemetryEventType::CameraEnabled;
        Telemetry->SendTelemetry(Event);
    }
}

void ALCKTablet::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
    // Save current settings
    SaveSettings();

    // Stop any active recording
    if (ULCKService* Service = GetLCKService(GetWorld()))
    {
        if (Service->IsRecording())
        {
            Service->StopRecording();
        }
    }

    // Telemetry
    if (ULCKTelemetrySubsystem* Telemetry = GetGameInstance()->GetSubsystem<ULCKTelemetrySubsystem>())
    {
        FLCKTelemetryEvent Event;
        Event.EventType = ELCKTelemetryEventType::CameraDisabled;
        Telemetry->SendTelemetry(Event);
    }

    Super::EndPlay(EndPlayReason);
}
```

***

## Enabling Grabbing

The UI doesn't handle grabbing mechanics. Grabbing is game-specific and it's up to developers to integrate.

Here's an example using the **GrabComponent** from Unreal's VR Template. Make sure you call **GrabStarted** and **GrabEnded** methods on **LCKTablet**. You can set them up in C++ or Blueprint.

<img src="https://mintcdn.com/liv/MwCn4kLMBfHUSvUi/images/unreal-images/image28.png?fit=max&auto=format&n=MwCn4kLMBfHUSvUi&q=85&s=08c2a60669b37e6a965a88292689a7d2" alt="GrabComponent setup" width="1577" height="642" data-path="images/unreal-images/image28.png" />

***

## Widget Component Setup

### ULCKWidgetComponent

The tablet uses a widget component for its 3D UI:

```cpp theme={null}
UCLASS()
class ALCKTablet : public ALCKTabletUI
{
    GENERATED_BODY()

    // Components are inherited from ALCKTabletUI:
    // - UStaticMeshComponent* TabletRoot (root mesh)
    // - USceneCaptureComponent2D* CaptureComponent (video source)
    // - ULCKUISystem* UISystem (3D UI management)
    // - ULCKDisplay* Display (render target display)

public:
    // Constructor — components are created by parent ALCKTabletUI
    // Do not create TabletRoot, CaptureComponent, UISystem, or Display manually
};
```

### Collision Setup

```cpp theme={null}
void ALCKTablet::SetupCollision()
{
    // Configure collision for VR hand interaction
    TabletMesh->SetCollisionProfileName(TEXT("OverlapOnlyPawn"));
    TabletMesh->SetGenerateOverlapEvents(true);

    // Set custom collision responses
    TabletMesh->SetCollisionResponseToChannel(
        ECC_Pawn,
        ECR_Overlap
    );
}
```

***

## Weak Pointer Patterns

For safe async callbacks, always use weak pointers:

### TWeakObjectPtr Pattern

```cpp theme={null}
void UMyComponent::StartRecordingWithCallback()
{
    ULCKService* Service = GetLCKService(GetWorld());
    if (!Service)
    {
        return;
    }

    // Capture weak pointer to this
    TWeakObjectPtr<UMyComponent> WeakThis(this);

    bool bSuccess = Service->StartRecording();
    HandleRecordingStarted(bSuccess);
}

void UMyComponent::HandleRecordingStarted(bool bSuccess)
{
    if (bSuccess)
    {
        UpdateUIForRecording();
    }
    else
    {
        ShowRecordingError();
    }
}
```

### TWeakPtr for Shared Pointers

```cpp theme={null}
void FMyAudioHandler::BindToSource(TSharedPtr<ILCKAudioSource> Source)
{
    TWeakPtr<FMyAudioHandler> WeakThis = AsShared();

    Source->OnAudioDataDelegate.BindLambda(
        [WeakThis](TArrayView<const float> PCM, int32 Channels, int32 SampleRate, ELCKAudioChannel SourceChannel)
        {
            if (TSharedPtr<FMyAudioHandler> This = WeakThis.Pin())
            {
                This->ProcessAudio(PCM, Channels, SampleRate, SourceChannel);
            }
        }
    );
}
```

***

## Event Subscription

### Safe Delegate Binding

```cpp theme={null}
void UMyWidget::NativeConstruct()
{
    Super::NativeConstruct();

    // Find tablet and bind to events
    if (ALCKTablet* Tablet = FindLCKTablet())
    {
        ULCKTabletDataModel* DataModel = Tablet->GetDataModel();

        // Store binding handle for cleanup
        RecordingStateHandle = DataModel->OnRecordStateChanged.AddUObject(
            this, &UMyWidget::OnRecordingStateChanged
        );

        CameraModeHandle = DataModel->OnTabletCameraModeChanged.AddUObject(
            this, &UMyWidget::OnCameraModeChanged
        );
    }
}

void UMyWidget::NativeDestruct()
{
    // Unbind all delegates
    if (ALCKTablet* Tablet = FindLCKTablet())
    {
        ULCKTabletDataModel* DataModel = Tablet->GetDataModel();
        DataModel->OnRecordStateChanged.Remove(RecordingStateHandle);
        DataModel->OnTabletCameraModeChanged.Remove(CameraModeHandle);
    }

    Super::NativeDestruct();
}
```

***

## Initialization Order

### Recommended Initialization Sequence

```cpp theme={null}
void ALCKTablet::BeginPlay()
{
    Super::BeginPlay();

    // 1. Create and initialize data model
    DataModel = NewObject<ULCKTabletDataModel>(this);
    DataModel->Initialize();

    // 2. Load saved settings
    LoadSettings();

    // 3. Initialize camera system (CaptureComponent is inherited from ALCKTabletUI)
    InitializeCameraCapture();

    // 5. Bind to service events
    BindToServiceEvents();

    // 6. Send spawn telemetry
    SendSpawnTelemetry();
}
```

***

## Troubleshooting Integration

<AccordionGroup>
  <Accordion icon="bug" title="Subsystem returns nullptr">
    **Cause:** World not valid or subsystem not registered.

    **Solution:**

    ```cpp theme={null}
    // Ensure world is valid
    if (!GetWorld())
    {
        UE_LOG(LogLCK, Error, TEXT("No world available"));
        return;
    }

    // Check if game is still running
    if (GetWorld()->bIsTearingDown)
    {
        return;
    }
    ```
  </Accordion>

  <Accordion icon="bolt-slash" title="Callbacks not firing">
    **Cause:** Object destroyed before callback or delegate unbound.

    **Solution:** Use weak pointers and verify binding:

    ```cpp theme={null}
    // Verify delegate is bound
    if (Service->OnRecordingError.IsBound())
    {
        UE_LOG(LogLCK, Log, TEXT("Delegate properly bound"));
    }
    ```
  </Accordion>

  <Accordion icon="eye-slash" title="Widget not rendering">
    **Cause:** Widget component not properly configured.

    **Solution:**

    ```cpp theme={null}
    TabletWidget->SetWidgetSpace(EWidgetSpace::World);
    TabletWidget->SetDrawSize(FVector2D(800, 600));
    TabletWidget->SetTwoSided(true);  // Visible from both sides
    TabletWidget->RequestRedraw();
    ```
  </Accordion>
</AccordionGroup>

***

## See Also

<CardGroup cols={2}>
  <Card icon="code" href="../../api-reference/unreal/service-interface" title="Core SDK API Reference">
    ULCKService API reference
  </Card>

  <Card icon="wand-magic-sparkles" href="/unreal/tablet-interface/spawning" title="Spawning">
    Best practices for tablet spawning
  </Card>
</CardGroup>
