Skip to content

CCTV Widget

The CCTV monitoring UI is implemented in WBP_CCTV, a Blueprint child of UOperatingSystemCCTVProgramWidget. The C++ base wires program lifecycle events; you implement feed display and controls in Blueprint.

Plugin path:

OperatingSystemContent/Shared/Blueprints/Widgets/Programs/CCTV/WBP_CCTV

flowchart TB
  subgraph widget["WBP_CCTV"]
    Main["Main feed Image"] --> MID1["Main View MID"]
    Grid["Grid tile Images"] --> MID2["Grid View MIDs"]
    Controls["Layout · camera · zoom buttons"]
  end

  subgraph events["Blueprint events"]
    OL["On Layout Changed"]
    CS["On Camera State Changed"]
  end

  Parent["BP_Prog_CCTV"] --> OL & CS
  OL --> widget
  CS --> widget
  Cam["BP_CCTV_Actor"] --> MID1 & MID2

Override these Blueprint Implementable Events in WBP_CCTV:

EventWhen it firesTypical use
On Layout ChangedSet Layout succeedsShow/hide grid vs single-view panels
On Camera State ChangedCamera enabled/disabled via Toggle StateGrey out offline feeds, update status icons

C++ binds OnLayoutChanged from the parent program automatically when the widget starts.

Each AOperatingSystemCCTVActor exposes dynamic material instances bound to its render target:

FunctionUse when
Get Main View MIDSingleView layout — one large Image widget
Get Grid View MIDGrid layouts — smaller tile Images

The material expects a Texture parameter (default: M_UI_CCTV from CCTV settings).

Event On Layout Changed (New Layout)
→ Branch (New Layout == Single View)
true:
→ Get All CCTV Actors
→ Get index 0 (or use stored Selected Camera)
→ Get Main View MID
→ Set Brush from Material (Main Feed Image)
false:
→ Call Refresh Grid Feeds (custom function)

Blueprint example — populate a 4-up grid

Section titled “Blueprint example — populate a 4-up grid”
Function Refresh Grid Feeds
→ Get All CCTV Actors → Cameras
→ For Each Loop (Cameras)
→ Get Grid View MID
→ Set Brush from Material (Grid Image at Loop Index)
flowchart LR
  subgraph grid["Layout Four"]
    T1["Tile 0 · Cam 1"]
    T2["Tile 1 · Cam 2"]
    T3["Tile 2 · Cam 3"]
    T4["Tile 3 · Cam 4"]
  end
  Cameras["Get All CCTV Actors"] --> T1 & T2 & T3 & T4

Wire toolbar buttons to the program API:

Button Layout 1 (Single)
→ Get Parent Program (as UOperatingSystemCCTVProgram)
→ Set Layout (Single View)
Button Layout 4
→ Set Layout (Layout Four)
Button Layout 9
→ Set Layout (Layout Nine)
stateDiagram-v2
  [*] --> SinglePanel: Single View
  [*] --> GridPanel: Layout Four / Six / Eight / Nine
  SinglePanel --> GridPanel: Layout button
  GridPanel --> SinglePanel: Single view button
  SinglePanel --> SinglePanel: On Layout Changed → refresh main MID
  GridPanel --> GridPanel: On Layout Changed → refresh all grid MIDs

Build a scrollable list of camera names; clicking an entry switches single view:

Function Build Camera List
→ Clear Children (Camera List Vertical Box)
→ Get All CCTV Actors
→ For Each Loop
→ Create Widget (WBP_CCTV_CameraEntry) // your entry widget
→ Set Camera Name on entry
→ Bind On Clicked → Set Selected Camera + Set Layout Single View
→ Add Child to Vertical Box

Use Get Camera Name on each AOperatingSystemCCTVActor for the label.

Zoom is handled on the camera actor, not the widget:

Button Zoom In
→ Selected Camera → Zoom In
Button Zoom Out
→ Selected Camera → Zoom Out
Button Reset Zoom
→ Selected Camera → Reset Zoom

FOV is clamped by each camera’s Min Max Zoom range (default 25°–100°).

Event On Camera State Changed (Camera Id)
→ Get Operating System CCTV World Subsystem
→ Camera Ids → Find (Camera Id) → Camera Actor
→ Branch (Is Enabled on camera)
false: Set opacity 0.3 on matching grid tile
true: Refresh that tile's MID

The world subsystem broadcasts On State Changed Delegate when any camera calls Toggle State.

BP_Prog_CCTV sets bCanRunOnPortableDevice = true, so WBP_CCTV should use responsive anchors and touch-friendly button sizes when you expect laptop or tablet devices in your project.

Create WBP_CCTV_CameraEntry as a simple User Widget:

ElementPurpose
Text BlockCamera name
ImageThumbnail using Get Grid View MID
ButtonSelect camera for single view

Keep thumbnails cheap by relying on the program’s frame-based capture — do not call Capture every tick from the widget.