Last active
December 2, 2025 08:31
-
-
Save shar0/e49420fd8554f436af4995866b3499ec to your computer and use it in GitHub Desktop.
Example for using USS + UXML to create Naninovel custom UI.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!-- 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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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