Basics

Influencing Cyberpunk's UI with REDscript

Summary

Published: Mar 31 2024 by manavortex Last documented update: Mar 31 2024 by manavortex

This page will tell you how to script the UI (turning widgets on and off).

Wait, this is not what I want!

General information

All different kinds of UI are loaded at the same time. Components don't get added or removed, but simply rendered inactive (hidden).

The code snippets on this page are function calls which do nothing on their own; you need to add them to your class and call them from your internal logic.

InkSystemLayers

You can access any existing layer via the game instance's InkSystem. Simply pass the layer's name as CName (for a list of existing options, see the table below):

GameInstance.GetInkSystem().GetLayer(n"inkHUDLayer")

Querying existing layers

private static func DumpInkHudLayers() {
  let inkSystem = GameInstance.GetInkSystem();
  let layers = inkSystem.GetLayers();

  for layer in layers {
    LogChannel(n"DEBUG", s"UI Layer: \(layer.GetLayerName()) \(layer.GetGameController().GetClassName())");
  }
}

List of existing layers (as of 2.12_a)

Layer name (as CName)Class

inkHUDLayer

gameuiRootHudGameController

the generic hud

inkWatermarksLayer

inkWaitingSignLayer

inkSystemNotificationsLayer

inkLoadingLayer

inkGameNotificationsLayer

gameuiPopupsManager

inkMenuLayer

gameuiInGameMenuGameController

ingame menu (ESC)

inkVideoLayer

gameuiHUDVideoPlayerController

inkWorldLayer

inkOffscreenLayer

inkAdvertisementsLayer

inkStreetSignsLayer

inkPhotoModeLayer

gameuiWidgetGameController

photo mode HUD

Accessing layers

To find functions for traversing widget trees, check the nativeDB page for inkCompoundWidget.

Naturally, CDPR did not give their view elements unique names. Check the Example at the end of the page for how to find and enable a certain widget.

If there are multiple widgets with the same name, the call below can return any of them.

let window = GameInstance.GetInkSystem().GetLayer(n"inkHUDLayer").GetVirtualWindow();
let root = window.GetWidgetByPathName(n"Root") as inkCanvas;

// Those two are the same:
let hudMiddle1 = root.GetWidgetByPathName(n"HUDMiddleWidget");
let hudMiddle2 = window.GetWidgetByPathName(n"Root/HUDMiddleWidget");

Example

This example only contains the scripting logic. For how to trigger the code, check either How to create a hook or How to use Codeware callbacks.

  private let fpsWidget: wref<inkWidget>;
  
  // Since CDPR couldn't be arsed to give unique names to HUD stuff, we need to iterate to find the widget we want.
  // This function checks the prerequisites.
  public func InitWidget() {
    // Don't run this if we already have our widget
    if (IsDefined(this.fpsWidget)) {
      return;
    }
    
    let window = GameInstance.GetInkSystem().GetLayer(n"inkHUDLayer").GetVirtualWindow();
    let rootWidget = window.GetWidgetByPathName(n"Root") as inkCanvas;

    // Put this into a variable in case you end up adding layers in your loop
    let numChildren = rootWidget.GetNumChildren();

    let i = 0;
    while (i < numChildren && i < 100) {
      let child = rootWidget.GetWidgetByIndex(i);
      // Since Redscript doesn't support continue, keep this in a function for early retrurns
      this.CheckWidget(child as inkCompoundWidget);
      i = i + 1;
    }
  }
  
  private func CheckWidget(widget: wref<inkCompoundWidget>) {

    // The widget we're looking for is nested like this (2.12_a):
    // ----------------------------------------------------------------------
    //  |-- HUDMiddleWidget - inkCanvasWidget
    //      |-- Root - inkCanvasWidget
    //          |-- inkRectangleWidget5 - inkRectangleWidget
    //          |-- inkHorizontalPanelWidget5 - inkHorizontalPanelWidget
    //              |-- fpsText - inkTextWidget
    //              |-- fpsCounter - inkTextWidget

    if (!Equals(n"HUDMiddleWidget", widget.GetName())) {
      return;
    }

    let rootWidget = widget.GetWidgetByPathName(n"Root") as inkCanvas;

    if (!IsDefined(rootWidget)) { return; }

    let horizontalWidget = rootWidget.GetWidgetByPathName(n"inkHorizontalPanelWidget5") as inkHorizontalPanel;

    if (!IsDefined(horizontalWidget)) { return; }

    let fpsTextWidget = horizontalWidget.GetWidgetByPathName(n"fpsText") as inkText;

    if (!IsDefined(fpsTextWidget)) { return; }

    widget.SetVisible(true);
    rootWidget.SetVisible(true);
    horizontalWidget.SetVisible(true);
    fpsTextWidget.SetVisible(true);
    
    this.fpsWidget = widget;

    let fpsCounterWidget = horizontalWidget.GetWidgetByPathName(n"fpsCounter") as inkText;
    if (!IsDefined(fpsCounterWidget)) { return; }

    fpsCounterWidget.SetVisible(true);
  }

Last updated