Skip to content

Instantly share code, notes, and snippets.

@shar0
Last active December 2, 2025 08:31
Show Gist options
  • Select an option

  • Save shar0/e49420fd8554f436af4995866b3499ec to your computer and use it in GitHub Desktop.

Select an option

Save shar0/e49420fd8554f436af4995866b3499ec to your computer and use it in GitHub Desktop.
Example for using USS + UXML to create Naninovel custom UI.
// File: Assets/UI/USS/HelloWorld.uss
root {
/* Set default opacity to 0 */
opacity: 0;
}
.container {
flex-grow: 1;
justify-content: center;
align-items: center;
background-color: rgba(0, 0, 0, 0.8);
}
.hello-text {
font-size: 48px;
color: rgb(255, 255, 255);
-unity-font-style: bold;
margin-bottom: 30px;
}
.click-button {
font-size: 24px;
padding: 15px 40px;
background-color: rgb(50, 150, 250);
color: rgb(255, 255, 255);
border-radius: 10px;
border-width: 0;
margin-bottom: 20px;
}
.click-button:hover {
background-color: rgb(70, 170, 255);
}
.click-button:active {
background-color: rgb(30, 130, 230);
}
.click-count {
font-size: 20px;
color: rgb(200, 200, 200);
}
<!-- File: Assets/UI/UXML/HelloWorld.uxml -->
<?xml version="1.0" encoding="utf-8"?>
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements"
xsi="http://www.w3.org/2001/XMLSchema-instance"
engine="UnityEngine.UIElements"
editor="UnityEditor.UIElements"
noNamespaceSchemaLocation="../../../UIElementsSchema/UIElements.xsd"
editor-extension-mode="False">
<ui:Style src="../USS/HelloWorld.uss" />
<ui:VisualElement name="container" class="container">
<ui:Label name="hello-label" text="Hello World!" class="hello-text" />
<ui:Button name="click-button" text="Click Me!" class="click-button" />
<ui:Label name="click-count" text="Clicks: 0" class="click-count" />
<ui:Button name="close-button" text="Close" class="click-button fade-out" />
</ui:VisualElement>
</ui:UXML>
// File: Assets/Scripts/UI/HelloWorld.cs
using Naninovel;
using UnityEngine;
using UnityEngine.UIElements;
using Naninovel.UI;
// You need to create a prefab contains HelloWorldUI + UIDocument components
public class HelloWorldUI : CustomUI
{
// Drag HelloWorld.uxml to this field.
[SerializeField] private UIDocument uiDocument;
[Header("Fade Settings")]
[Tooltip("Custom fade-in duration (seconds)")]
[SerializeField] private float customFadeInTime = 0.5f;
[Tooltip("Custom fade-out duration (seconds)")]
[SerializeField] private float customFadeOutTime = 0.3f;
[Tooltip("Fade-in delay (seconds)")]
[SerializeField] private float fadeInDelay = 0f;
[Tooltip("Fade-out delay (seconds)")]
[SerializeField] private float fadeOutDelay = 0f;
private Label clickCountLabel;
private Label fadeStatusLabel;
private Button clickButton;
private Button closeBtn;
private VisualElement rootElement;
private int clickCount = 0;
private bool isCustomFading = false;
public override async UniTask Initialize()
{
await base.Initialize();
if (uiDocument == null)
{
uiDocument = GetComponent<UIDocument>();
}
rootElement = uiDocument.rootVisualElement;
// Set initial UI state based on visibility
if (VisibleOnAwake)
{
rootElement.style.display = DisplayStyle.Flex;
rootElement.style.opacity = 1f;
}
else
{
rootElement.style.display = DisplayStyle.None;
rootElement.style.opacity = 0f;
}
// Fetch UI elements
clickButton = rootElement.Q<Button>("click-button");
closeBtn = rootElement.Q<Button>("close-button");
clickCountLabel = rootElement.Q<Label>("click-count");
// Bind button events
if (clickButton != null)
{
clickButton.clicked += OnButtonClicked;
}
if (closeBtn != null)
{
closeBtn.clicked += Hide;
}
// Initialize UI display
UpdateClickCountDisplay();
UpdateFadeStatusDisplay();
// Add custom visibility change handler
OnVisibilityChanged += HandleCustomVisibilityChanged;
}
private void OnButtonClicked()
{
clickCount++;
UpdateClickCountDisplay();
}
/// <summary>
/// Custom fade-in effect using UI Toolkit
/// </summary>
public async void CustomFadeIn()
{
if (isCustomFading || rootElement == null) return;
isCustomFading = true;
// Ensure the root element is visible
rootElement.style.display = DisplayStyle.Flex;
// Start fading after optional delay
if (fadeInDelay > 0)
{
await UniTask.Delay(System.TimeSpan.FromSeconds(fadeInDelay), cancellationToken: destroyCancellationToken);
}
// Disable all interactive elements
SetInteractivity(false);
// Fade in using UI Toolkit style animation
float elapsed = 0f;
while (elapsed < customFadeInTime)
{
elapsed += Time.deltaTime;
float progress = Mathf.Clamp01(elapsed / customFadeInTime);
// Set opacity
rootElement.style.opacity = progress;
// Enable interaction near the end of the fade-in
if (progress > 0.8f)
{
SetInteractivity(true);
}
await UniTask.Yield();
}
// Ensure the final state is correct
rootElement.style.opacity = 1f;
SetInteractivity(true);
isCustomFading = false;
UpdateFadeStatusDisplay();
}
/// <summary>
/// Custom fade-out effect using UI Toolkit
/// </summary>
public async void CustomFadeOut()
{
if (isCustomFading || rootElement == null) return;
isCustomFading = true;
// Disable interaction immediately
SetInteractivity(false);
// Start fading after optional delay
if (fadeOutDelay > 0)
{
await UniTask.Delay(System.TimeSpan.FromSeconds(fadeOutDelay), cancellationToken: destroyCancellationToken);
}
// Fade out using UI Toolkit style animation
float elapsed = 0f;
float startOpacity = rootElement.style.opacity.value;
while (elapsed < customFadeOutTime)
{
elapsed += Time.deltaTime;
float progress = Mathf.Clamp01(elapsed / customFadeOutTime);
// Set opacity
rootElement.style.opacity = Mathf.Lerp(startOpacity, 0f, progress);
await UniTask.Yield();
}
// Ensure the final state is correct
rootElement.style.opacity = 0f;
rootElement.style.display = DisplayStyle.None;
isCustomFading = false;
UpdateFadeStatusDisplay();
}
/// <summary>
/// Enable or disable all interactive elements
/// </summary>
private void SetInteractivity(bool enabled)
{
if (clickButton != null) clickButton.SetEnabled(enabled);
}
/// <summary>
/// Override the base visibility change handling using the UI Toolkit approach
/// </summary>
protected override void HandleVisibilityChanged(bool visible)
{
base.HandleVisibilityChanged(visible);
// Ensure the root element is initialized
if (!isCustomFading && rootElement != null)
{
if (visible)
{
rootElement.style.display = DisplayStyle.Flex;
rootElement.style.opacity = 1f;
SetInteractivity(true);
}
else
{
rootElement.style.opacity = 0f;
rootElement.style.display = DisplayStyle.None;
SetInteractivity(false);
}
UpdateFadeStatusDisplay();
}
}
/// <summary>
/// Handle custom visibility change events
/// </summary>
private void HandleCustomVisibilityChanged(bool visible)
{
if (visible)
CustomFadeIn();
else
CustomFadeOut();
}
/// <summary>
/// Update the click count label
/// </summary>
private void UpdateClickCountDisplay()
{
if (clickCountLabel != null)
{
clickCountLabel.text = $"Clicks: {clickCount}";
}
}
/// <summary>
/// Update fade-in/out status display
/// </summary>
private void UpdateFadeStatusDisplay()
{
if (fadeStatusLabel != null && rootElement != null)
{
string status = isCustomFading ? "Fading..." :
(rootElement.style.display == DisplayStyle.Flex ? "Visible" : "Hidden");
string opacity = rootElement.style.opacity.value.ToString("F2");
string interactable = clickButton != null && clickButton.enabledInHierarchy ? "True" : "False";
fadeStatusLabel.text = $"Status: {status}\nOpacity: {opacity}, Interactable: {interactable}";
}
}
protected override void OnDestroy()
{
// Remove event listeners
OnVisibilityChanged -= HandleCustomVisibilityChanged;
// Clean up UI events
if (clickButton != null)
{
clickButton.clicked -= OnButtonClicked;
}
if (closeBtn != null)
{
closeBtn.clicked -= Hide;
}
base.OnDestroy();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment