Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions AI/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@

## [Unreleased]

### Added — End-of-level tree QR share on Result Screen

- **Scope:** component:ResultScreen · integration
- **What:** Results stats card now includes a `TreeShareSection` (square QR + "Scan to view your binary tree"). `TreeRunTracker` records starting BST insertion order and successful placements; `TreeResultQrDisplay` encodes a seemytree viewer URL via vendored ZXing.Net. Viewer host / query names live in `TreeShareConfig` ScriptableObject (not hardcoded in tracker logic).
- **Why:** Players can scan the existing performance screen to view their constructed tree online without a separate completion popup; share endpoint is tunable per environment.
- **Impact:** `ResultScreenLayoutBuilder` / `ResultScreenController` extended; `TreeRhythmController` + `TwoPlayerGameCoordinator` wired. Requires `Assets/Plugins/ZXing/zxing.dll` and `Assets/ScriptableObjects/Configs/TreeShareConfig.asset`.
- **Refs:** `TreeRunTracker.cs`, `TreeShareConfig.cs`, `TreeResultQrDisplay.cs`, `TreeQrCodeGenerator.cs`, `ResultScreenLayoutBuilder.cs`, `ResultScreenController.cs`

### Changed — Synthwave UI revamp: monochrome-accented dark style across all menus

- **Scope:** framework · token · component:TitleScreen · component:SongSelect · component:ScoreHUD · component:MainMenu · animation
Expand Down
98 changes: 98 additions & 0 deletions Assets/Integration/TreeQrCodeGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using ZXing;
using ZXing.Common;
using ZXing.QrCode;
using ZXing.QrCode.Internal;

/** <summary>
* Runtime QR texture generation via ZXing.Net (Assets/Plugins/ZXing).
* Produces a square, point-filtered Texture2D suitable for a UI RawImage.
* </summary>
*/
public static class TreeQrCodeGenerator
{
private const int DefaultResolution = 512;
private const int QuietZoneModules = 2; // white margin around the QR

/** <summary>
* Encodes <paramref name="contents"/> as a QR code texture.
* Returns null and logs on failure so callers can show a fallback.
* </summary>
* <param name="contents">Full URL (or any string) to encode.</param>
* <param name="resolution">Square pixel size; at least 512 recommended.</param>
*/
public static Texture2D Generate(string contents, int resolution = DefaultResolution)
{
if (string.IsNullOrEmpty(contents))
{
Debug.LogError("[TreeQrCodeGenerator] Cannot encode an empty string.");
return null;
}

if (resolution < 64)
resolution = DefaultResolution;

try
{
var hints = new Dictionary<EncodeHintType, object>
{
{ EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M },
{ EncodeHintType.MARGIN, QuietZoneModules },
{ EncodeHintType.CHARACTER_SET, "UTF-8" },
};

var writer = new QRCodeWriter();
BitMatrix matrix = writer.encode(contents, BarcodeFormat.QR_CODE, resolution, resolution, hints);
if (matrix == null)
{
Debug.LogError("[TreeQrCodeGenerator] QRCodeWriter returned a null matrix.");
return null;
}

return BitMatrixToTexture(matrix);
}
catch (Exception ex)
{
Debug.LogError($"[TreeQrCodeGenerator] QR generation failed: {ex.Message}");
return null;
}
}

/** <summary>
* Converts a ZXing BitMatrix into a black-on-white Texture2D with
* point filtering and no mipmaps so modules stay crisp when scaled.
* </summary>
*/
private static Texture2D BitMatrixToTexture(BitMatrix matrix)
{
int width = matrix.Width;
int height = matrix.Height;

var texture = new Texture2D(width, height, TextureFormat.RGB24, mipChain: false)
{
filterMode = FilterMode.Point,
wrapMode = TextureWrapMode.Clamp,
name = "TreeResultQr",
};

// ZXing BitMatrix is row-major from the top; Unity textures are bottom-up.
var pixels = new Color32[width * height];
for (int y = 0; y < height; y++)
{
int unityY = height - 1 - y;
for (int x = 0; x < width; x++)
{
bool dark = matrix[x, y];
pixels[unityY * width + x] = dark
? new Color32(0, 0, 0, 255)
: new Color32(255, 255, 255, 255);
}
}

texture.SetPixels32(pixels);
texture.Apply(updateMipmaps: false, makeNoLongerReadable: true);
return texture;
}
}
2 changes: 2 additions & 0 deletions Assets/Integration/TreeQrCodeGenerator.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

204 changes: 204 additions & 0 deletions Assets/Integration/TreeResultQrDisplay.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
using TMPro;
using UnityEngine;
using UnityEngine.UI;

/** <summary>
* Prepares and shows the tree-share QR code on the existing result screen.
*
* Does not open or close the performance overlay — that stays the job of
* <see cref="ResultScreenController"/>. Call <see cref="PrepareResultQrCode"/>
* once per completed run, before or as the results UI becomes visible.
* </summary>
*/
public class TreeResultQrDisplay : MonoBehaviour
{
private const string FallbackMessage = "Unable to generate tree QR code";
private const int QrTextureSize = 512;

[Header("References")]
[Tooltip("Tracks start-tree insertion order and successful player placements.")]
[SerializeField] private TreeRunTracker _tracker;

[Tooltip("Square RawImage that receives the generated QR texture.")]
[SerializeField] private RawImage _qrRawImage;

[Tooltip("Short instruction under / beside the QR (e.g. scan prompt).")]
[SerializeField] private TMP_Text _instructionLabel;

#if UNITY_EDITOR || DEVELOPMENT_BUILD
[Tooltip("Optional URL label shown only in development builds.")]
[SerializeField] private TMP_Text _debugUrlLabel;
#endif

private Texture2D _runtimeTexture;
private bool _preparedForCurrentRun;
private string _lastUrl;

#region Wiring

/** <summary>Assigns runtime UI refs created by <see cref="ResultScreenLayoutBuilder"/>.</summary> */
public void SetUiRefs(RawImage qrRawImage, TMP_Text instructionLabel)
{
_qrRawImage = qrRawImage;
_instructionLabel = instructionLabel;
}

/** <summary>Binds the per-player run tracker used to build the viewer URL.</summary> */
public void SetTracker(TreeRunTracker tracker)
{
_tracker = tracker;
_preparedForCurrentRun = false;
}

#endregion

#region Public API

/** <summary>
* Finishes tracking, builds the viewer URL, generates a QR texture, and
* assigns it to the results-screen RawImage. Safe to call once per run;
* subsequent calls for the same run are no-ops until <see cref="ClearQr"/>.
* </summary>
*/
public void PrepareResultQrCode()
{
if (_preparedForCurrentRun)
return;

if (_tracker == null)
{
Debug.LogError("[TreeResultQrDisplay] Missing TreeRunTracker reference.");
ShowFallback();
return;
}

if (_qrRawImage == null)
{
Debug.LogError("[TreeResultQrDisplay] Missing QR RawImage reference.");
// Still finish tracking so placement data stops growing.
_tracker.FinishLevel();
return;
}

_tracker.FinishLevel();
_lastUrl = _tracker.BuildViewerUrl();
if (string.IsNullOrEmpty(_lastUrl))
{
Debug.LogError("[TreeResultQrDisplay] Viewer URL was empty (check TreeShareConfig).");
ShowFallback();
_preparedForCurrentRun = true;
return;
}

#if UNITY_EDITOR || DEVELOPMENT_BUILD
Debug.Log($"[TreeResultQrDisplay] Tree viewer URL: {_lastUrl}");
#endif

DestroyRuntimeTexture();

Texture2D qr = null;
try
{
qr = TreeQrCodeGenerator.Generate(_lastUrl, QrTextureSize);
}
catch (System.Exception ex)
{
Debug.LogError($"[TreeResultQrDisplay] QR generation threw: {ex.Message}");
}

if (qr == null)
{
ShowFallback();
_preparedForCurrentRun = true;
return;
}

_runtimeTexture = qr;
_qrRawImage.texture = qr;
_qrRawImage.color = Color.white;
_qrRawImage.enabled = true;

#if UNITY_EDITOR || DEVELOPMENT_BUILD
if (_debugUrlLabel != null)
{
_debugUrlLabel.text = _lastUrl;
_debugUrlLabel.gameObject.SetActive(true);
}
#endif

_preparedForCurrentRun = true;
}

/** <summary>
* Clears the displayed QR and destroys the runtime texture.
* Call when the results screen closes or before the next run.
* </summary>
*/
public void ClearQr()
{
DestroyRuntimeTexture();

if (_qrRawImage != null)
{
_qrRawImage.texture = null;
_qrRawImage.enabled = false;
}

_preparedForCurrentRun = false;
_lastUrl = null;
}

/** <summary>URL produced by the last successful <see cref="PrepareResultQrCode"/>.</summary> */
public string LastViewerUrl => _lastUrl;

#endregion

#region Lifecycle

private void OnDisable()
{
// Hide QR with the results overlay; texture is destroyed so the next
// PrepareResultQrCode generates a fresh one for the next run.
ClearQr();
}

private void OnDestroy()
{
DestroyRuntimeTexture();
}

#endregion

#region Helpers

private void ShowFallback()
{
if (_qrRawImage != null)
{
_qrRawImage.texture = null;
_qrRawImage.enabled = false;
}

if (_instructionLabel != null)
{
_instructionLabel.text = FallbackMessage;
_instructionLabel.gameObject.SetActive(true);
}

Debug.LogWarning("[TreeResultQrDisplay] Showing QR fallback message.");
}

private void DestroyRuntimeTexture()
{
if (_runtimeTexture == null)
return;

if (_qrRawImage != null && _qrRawImage.texture == _runtimeTexture)
_qrRawImage.texture = null;

Destroy(_runtimeTexture);
_runtimeTexture = null;
}

#endregion
}
2 changes: 2 additions & 0 deletions Assets/Integration/TreeResultQrDisplay.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading