From 287230087316fd67ef2beb4625bc7ed7fc1eb030 Mon Sep 17 00:00:00 2001 From: Sebastian Bularca Date: Mon, 6 Apr 2026 20:45:22 +0200 Subject: [PATCH] added code from unity --- Editor.meta | 8 + Editor/GameLogViewerWindow.cs | 338 ++++++++ Editor/GameLogViewerWindow.cs.meta | 2 + Editor/Jovian.InGameLogging.Editor.asmdef | 19 + .../Jovian.InGameLogging.Editor.asmdef.meta | 7 + README.md | 263 +++++- README.md.meta | 7 + Runtime.meta | 8 + Runtime/GameLogSaveData.cs | 17 + Runtime/GameLogSaveData.cs.meta | 2 + Runtime/GameLogStore.cs | 122 +++ Runtime/GameLogStore.cs.meta | 2 + Runtime/IGameLogStore.cs | 25 + Runtime/IGameLogStore.cs.meta | 2 + Runtime/InGameLogger.cs | 36 + Runtime/InGameLogger.cs.meta | 2 + Runtime/Jovian.InGameLogging.asmdef | 19 + Runtime/Jovian.InGameLogging.asmdef.meta | 7 + Runtime/LogChannel.cs | 44 + Runtime/LogChannel.cs.meta | 2 + Runtime/LogChannelJsonConverter.cs | 15 + Runtime/LogChannelJsonConverter.cs.meta | 2 + Runtime/LogEntry.cs | 20 + Runtime/LogEntry.cs.meta | 2 + Runtime/UI.meta | 8 + Runtime/UI/GameLogView.cs | 137 +++ Runtime/UI/GameLogView.cs.meta | 2 + Runtime/UI/LogEntryView.cs | 16 + Runtime/UI/LogEntryView.cs.meta | 2 + Samples~/LogContainer.prefab | 790 ++++++++++++++++++ Samples~/LogEntry.prefab | 210 +++++ Samples~/README.md | 3 + package.json | 20 + package.json.meta | 7 + 34 files changed, 2164 insertions(+), 2 deletions(-) create mode 100644 Editor.meta create mode 100644 Editor/GameLogViewerWindow.cs create mode 100644 Editor/GameLogViewerWindow.cs.meta create mode 100644 Editor/Jovian.InGameLogging.Editor.asmdef create mode 100644 Editor/Jovian.InGameLogging.Editor.asmdef.meta create mode 100644 README.md.meta create mode 100644 Runtime.meta create mode 100644 Runtime/GameLogSaveData.cs create mode 100644 Runtime/GameLogSaveData.cs.meta create mode 100644 Runtime/GameLogStore.cs create mode 100644 Runtime/GameLogStore.cs.meta create mode 100644 Runtime/IGameLogStore.cs create mode 100644 Runtime/IGameLogStore.cs.meta create mode 100644 Runtime/InGameLogger.cs create mode 100644 Runtime/InGameLogger.cs.meta create mode 100644 Runtime/Jovian.InGameLogging.asmdef create mode 100644 Runtime/Jovian.InGameLogging.asmdef.meta create mode 100644 Runtime/LogChannel.cs create mode 100644 Runtime/LogChannel.cs.meta create mode 100644 Runtime/LogChannelJsonConverter.cs create mode 100644 Runtime/LogChannelJsonConverter.cs.meta create mode 100644 Runtime/LogEntry.cs create mode 100644 Runtime/LogEntry.cs.meta create mode 100644 Runtime/UI.meta create mode 100644 Runtime/UI/GameLogView.cs create mode 100644 Runtime/UI/GameLogView.cs.meta create mode 100644 Runtime/UI/LogEntryView.cs create mode 100644 Runtime/UI/LogEntryView.cs.meta create mode 100644 Samples~/LogContainer.prefab create mode 100644 Samples~/LogEntry.prefab create mode 100644 Samples~/README.md create mode 100644 package.json create mode 100644 package.json.meta diff --git a/Editor.meta b/Editor.meta new file mode 100644 index 0000000..ecb1f74 --- /dev/null +++ b/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1e182a45ed498c445b141e9ec6395805 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/GameLogViewerWindow.cs b/Editor/GameLogViewerWindow.cs new file mode 100644 index 0000000..2323e15 --- /dev/null +++ b/Editor/GameLogViewerWindow.cs @@ -0,0 +1,338 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.RegularExpressions; +using Jovian.SaveSystem; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using UnityEditor; +using UnityEngine; + +namespace Jovian.InGameLogging.Editor { + public class GameLogViewerWindow : EditorWindow { + private static readonly Regex richTextRegex = new(@"<[^>]+>", RegexOptions.Compiled); + + private Vector2 slotListScroll; + private Vector2 logEntriesScroll; + + private List sessions = new(); + private SaveSlotEntry selectedSlot; + private List loadedEntries = new(); + private List filteredEntries = new(); + private List channelNames = new(); + private int selectedChannelIndex; + private string savePath; + + private GUIStyle channelTagStyle; + private GUIStyle timeStyle; + private GUIStyle messageStyle; + private GUIStyle slotButtonStyle; + private GUIStyle noDataStyle; + private bool stylesInitialized; + + [MenuItem("Jovian/In-Game Logging/Log Viewer")] + public static void ShowWindow() { + var window = GetWindow("In-Game Log Viewer"); + window.minSize = new Vector2(500, 400); + window.Show(); + } + + private void OnEnable() { + ScanSaves(); + } + + private void InitStyles() { + if(stylesInitialized) { + return; + } + + channelTagStyle = new GUIStyle(EditorStyles.miniLabel) { + fontStyle = FontStyle.Bold, + normal = { textColor = new Color(0.5f, 0.8f, 1f) }, + fixedWidth = 120, + alignment = TextAnchor.MiddleLeft + }; + + timeStyle = new GUIStyle(EditorStyles.miniLabel) { + normal = { textColor = new Color(0.6f, 0.6f, 0.6f) }, + fixedWidth = 70, + alignment = TextAnchor.MiddleRight + }; + + messageStyle = new GUIStyle(EditorStyles.label) { + wordWrap = false, + richText = false + }; + + slotButtonStyle = new GUIStyle(EditorStyles.toolbarButton) { + alignment = TextAnchor.MiddleLeft, + fixedHeight = 22 + }; + + noDataStyle = new GUIStyle(EditorStyles.centeredGreyMiniLabel) { + fontSize = 12, + wordWrap = true + }; + + stylesInitialized = true; + } + + private void OnGUI() { + InitStyles(); + DrawToolbar(); + + if(sessions.Count == 0) { + EditorGUILayout.LabelField("No save files found.\nPlay the game and save to generate log data.", + noDataStyle, GUILayout.ExpandHeight(true)); + return; + } + + DrawSlotList(); + DrawChannelFilter(); + DrawLogEntries(); + } + + private void DrawToolbar() { + EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); + GUILayout.FlexibleSpace(); + if(GUILayout.Button("Refresh", EditorStyles.toolbarButton, GUILayout.Width(60))) { + ScanSaves(); + } + EditorGUILayout.EndHorizontal(); + } + + private void DrawSlotList() { + EditorGUILayout.LabelField("Save Slots", EditorStyles.boldLabel); + slotListScroll = EditorGUILayout.BeginScrollView(slotListScroll, GUILayout.MaxHeight(200)); + + foreach(var session in sessions) { + session.foldout = EditorGUILayout.Foldout(session.foldout, session.displayName, true); + if(!session.foldout) { + continue; + } + + EditorGUI.indentLevel++; + foreach(var slot in session.slots) { + var isSelected = selectedSlot != null && selectedSlot.filePath == slot.filePath; + var style = new GUIStyle(slotButtonStyle); + if(isSelected) { + style.fontStyle = FontStyle.Bold; + } + + if(GUILayout.Button($" {slot.displayName}", style)) { + SelectSlot(slot); + } + } + EditorGUI.indentLevel--; + } + + EditorGUILayout.EndScrollView(); + DrawSeparator(); + } + + private void DrawChannelFilter() { + if(loadedEntries.Count == 0) { + return; + } + + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("Channel filter:", GUILayout.Width(90)); + var newIndex = EditorGUILayout.Popup(selectedChannelIndex, channelNames.ToArray()); + if(newIndex != selectedChannelIndex) { + selectedChannelIndex = newIndex; + ApplyChannelFilter(); + } + EditorGUILayout.EndHorizontal(); + } + + private void DrawLogEntries() { + if(selectedSlot == null) { + EditorGUILayout.LabelField("Select a save slot to view its log entries.", noDataStyle, GUILayout.ExpandHeight(true)); + return; + } + + if(filteredEntries.Count == 0) { + EditorGUILayout.LabelField("No log entries in this save.", noDataStyle, GUILayout.ExpandHeight(true)); + return; + } + + EditorGUILayout.LabelField($"{filteredEntries.Count} entries", EditorStyles.miniLabel); + logEntriesScroll = EditorGUILayout.BeginScrollView(logEntriesScroll, GUILayout.ExpandHeight(true)); + + foreach(var entry in filteredEntries) { + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField($"[{entry.channel}]", channelTagStyle); + EditorGUILayout.LabelField(FormatTime(entry.gameTime), timeStyle); + EditorGUILayout.LabelField(StripRichText(entry.message), messageStyle); + EditorGUILayout.EndHorizontal(); + } + + EditorGUILayout.EndScrollView(); + } + + private void DrawSeparator() { + var rect = EditorGUILayout.GetControlRect(false, 1); + EditorGUI.DrawRect(rect, new Color(0.3f, 0.3f, 0.3f)); + } + + private void ScanSaves() { + sessions.Clear(); + selectedSlot = null; + loadedEntries.Clear(); + filteredEntries.Clear(); + channelNames.Clear(); + selectedChannelIndex = 0; + + var settings = SaveSystemSettings.Load(); + savePath = Path.Combine(Application.persistentDataPath, settings.saveDirectoryName); + var indexPath = Path.Combine(savePath, "index.json"); + + if(!File.Exists(indexPath)) { + return; + } + + try { + var indexJson = File.ReadAllText(indexPath); + var index = JsonConvert.DeserializeObject(indexJson); + if(index?.sessions == null || index.slots == null) { + return; + } + + var slotsBySession = new Dictionary>(); + foreach(var slot in index.slots) { + if(!slotsBySession.ContainsKey(slot.sessionId)) { + slotsBySession[slot.sessionId] = new List(); + } + slotsBySession[slot.sessionId].Add(new SaveSlotEntry { + sessionId = slot.sessionId, + displayName = slot.DisplayLabel + " " + DateTimeOffset.FromUnixTimeMilliseconds(slot.timestampUtc).LocalDateTime.ToString("HH:mm"), + filePath = slot.filePath, + timestampUtc = slot.timestampUtc + }); + } + + foreach(var session in index.sessions) { + if(!slotsBySession.TryGetValue(session.sessionId, out var slots)) { + continue; + } + slots.Sort((a, b) => b.timestampUtc.CompareTo(a.timestampUtc)); + sessions.Add(new SessionGroup { + sessionId = session.sessionId, + displayName = $"Session ({DateTimeOffset.FromUnixTimeMilliseconds(session.lastSaveDateUtc).LocalDateTime:yyyy-MM-dd HH:mm})", + foldout = true, + slots = slots + }); + } + + sessions.Sort((a, b) => string.Compare(b.sessionId, a.sessionId, StringComparison.Ordinal)); + } + catch(Exception e) { + Debug.LogError($"[GameLogViewer] Failed to read save index: {e.Message}"); + } + } + + private void SelectSlot(SaveSlotEntry slot) { + selectedSlot = slot; + loadedEntries.Clear(); + filteredEntries.Clear(); + channelNames.Clear(); + channelNames.Add("All"); + selectedChannelIndex = 0; + + var fullPath = Path.Combine(savePath, slot.filePath); + if(!File.Exists(fullPath)) { + Debug.LogWarning($"[GameLogViewer] Save file not found: {fullPath}"); + return; + } + + try { + var bytes = File.ReadAllBytes(fullPath); + var serializer = new JsonSaveSerializer(); + var envelope = serializer.Deserialize(bytes); + + if(envelope?.payload == null) { + return; + } + + // Navigate via JToken to avoid coupling to NoxSavedDataSet + var gameLogToken = envelope.payload["gameLogData"]; + if(gameLogToken == null) { + return; + } + + var entriesToken = gameLogToken["entries"]; + if(entriesToken == null) { + return; + } + + var logData = entriesToken.ToObject>(); + if(logData == null) { + return; + } + + loadedEntries.AddRange(logData); + + // Build channel list + var uniqueChannels = new HashSet(); + foreach(var entry in loadedEntries) { + var channelId = entry.channel.Id; + if(!string.IsNullOrEmpty(channelId) && uniqueChannels.Add(channelId)) { + channelNames.Add(channelId); + } + } + + ApplyChannelFilter(); + } + catch(Exception e) { + Debug.LogError($"[GameLogViewer] Failed to load save: {e.Message}"); + } + } + + private void ApplyChannelFilter() { + filteredEntries.Clear(); + if(selectedChannelIndex == 0) { + filteredEntries.AddRange(loadedEntries); + } + else { + var channelId = channelNames[selectedChannelIndex]; + foreach(var entry in loadedEntries) { + if(entry.channel.Id == channelId) { + filteredEntries.Add(entry); + } + } + } + } + + private static string StripRichText(string text) { + return string.IsNullOrEmpty(text) ? string.Empty : richTextRegex.Replace(text, string.Empty); + } + + private static string FormatTime(float gameTime) { + var minutes = (int)(gameTime / 60f); + var seconds = gameTime % 60f; + return $"{minutes}:{seconds:00.0}"; + } + + // Internal types for deserialization -- mirrors save system index structure + + [Serializable] + private sealed class SaveIndex { + public List sessions; + public List slots; + } + + private sealed class SessionGroup { + public string sessionId; + public string displayName; + public bool foldout; + public List slots; + } + + private sealed class SaveSlotEntry { + public string sessionId; + public string displayName; + public string filePath; + public long timestampUtc; + } + } +} diff --git a/Editor/GameLogViewerWindow.cs.meta b/Editor/GameLogViewerWindow.cs.meta new file mode 100644 index 0000000..a7307ca --- /dev/null +++ b/Editor/GameLogViewerWindow.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 6f19bfe069d21db41a998154aa0876fb \ No newline at end of file diff --git a/Editor/Jovian.InGameLogging.Editor.asmdef b/Editor/Jovian.InGameLogging.Editor.asmdef new file mode 100644 index 0000000..8fa7f81 --- /dev/null +++ b/Editor/Jovian.InGameLogging.Editor.asmdef @@ -0,0 +1,19 @@ +{ + "name": "Jovian.InGameLogging.Editor", + "rootNamespace": "Jovian.InGameLogging.Editor", + "references": [ + "Jovian.InGameLogging", + "Jovian.SaveSystem" + ], + "includePlatforms": ["Editor"], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "Newtonsoft.Json.dll" + ], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} diff --git a/Editor/Jovian.InGameLogging.Editor.asmdef.meta b/Editor/Jovian.InGameLogging.Editor.asmdef.meta new file mode 100644 index 0000000..8a0a8b5 --- /dev/null +++ b/Editor/Jovian.InGameLogging.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 48c945ba5ea83b144b5bbf4eaf33fe29 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/README.md b/README.md index 25ab8c6..9fb033e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,262 @@ -# unity-ingame-logging +# Jovian In-Game Logging -A low-allocation in-game logging system for Unity with channel-based filtering, pooled UI, and save system integration. This is a player-facing game log (combat feed, event history), not a debug logger. \ No newline at end of file +A low-allocation in-game logging system for Unity with channel-based filtering, pooled UI, and save system integration. This is a player-facing game log (combat feed, event history), not a debug logger. + +## Requirements + +- Unity 2022.3 or later +- Dependencies: + - `com.unity.textmeshpro` 3.0.6+ + - `com.unity.nuget.newtonsoft-json` 3.2.1+ + - `com.jovian.savesystem` 0.1.0+ + +## Quick Start + +### 1. Create the store + +`GameLogStore` is the central service that holds log entries in a ring buffer. Create it once and pass it via constructor injection. + +```csharp +// Default capacity of 500 entries +var logStore = new GameLogStore(); + +// Or specify a custom capacity +var logStore = new GameLogStore(capacity: 1000); +``` + +### 2. Create a logger + +`InGameLogger` is a lightweight facade bound to a specific channel. Create one per system or class that needs to write log entries. + +```csharp +var combatLogger = new InGameLogger(logStore, LogChannel.Combat); +var worldLogger = new InGameLogger(logStore, LogChannel.World); +``` + +### 3. Log messages + +```csharp +combatLogger.Log("Kael strikes the skeleton for 12 damage."); +combatLogger.Log("Critical hit!", "#FF4444"); +``` + +### 4. Set up the UI + +Add a `GameLogView` component to a GameObject with a `ScrollRect`. Assign the `ScrollRect`, a content `RectTransform`, and a `LogEntryView` prefab in the Inspector. Then initialize from code: + +```csharp +// Show all channels +gameLogView.Initialize(logStore); + +// Or filter to a single channel +gameLogView.Initialize(logStore, LogChannel.Combat); +``` + +The view uses object pooling internally. It auto-scrolls to the bottom as new entries arrive and pauses auto-scroll when the player scrolls up manually. + +## Prefab Setup + +The UI requires two prefabs: a **LogEntryView prefab** (the individual log row) and a **GameLogView prefab** (the scrollable container). + +### LogEntryView prefab + +1. Create a new GameObject, rename it `LogEntry` +2. Add a `LayoutElement` component. Set `Preferred Height` to your desired row height (e.g. 24). Enable `Flexible Width` so it stretches to the scroll content width +3. Add a child GameObject with a `TextMeshPro - Text (UI)` component + - Set `Overflow` to `Ellipsis` or `Truncate` (prevents text from spilling outside the row) + - Anchor and stretch it to fill the parent (`Left: 0, Right: 0, Top: 0, Bottom: 0`) + - Set font, font size, and color to match your game's UI style + - `Rich Text` should remain enabled (default) +4. Add the `LogEntryView` component to the root `LogEntry` GameObject +5. Drag the child `TMP_Text` into the `LogEntryView.messageText` field +6. Save as a prefab + +### GameLogView prefab + +1. Create a new UI GameObject with a `ScrollRect` component + - Disable `Horizontal` scrolling, keep `Vertical` enabled + - Set `Movement Type` to `Clamped` or `Elastic` as preferred + - Set `Scroll Sensitivity` to a comfortable value (e.g. 20) +2. Add a child `Content` GameObject as the scroll content area + - Add a `RectTransform` anchored top-left, pivot `(0, 1)`, with a `VerticalLayoutGroup`: + - `Child Alignment`: Upper Left + - `Child Force Expand Width`: true, `Height`: false + - `Spacing`: 2 (gap between log rows) + - `Padding`: set as needed + - Add a `ContentSizeFitter` with `Vertical Fit` set to `Preferred Size` (so it grows as entries are added) + - Assign this as the `ScrollRect.Content` +3. Optionally add a `Scrollbar` child for the vertical scrollbar, and assign it to `ScrollRect.Vertical Scrollbar` +4. Optionally add a `Mask` or `RectMask2D` on the scroll viewport to clip entries outside the visible area +5. Add the `GameLogView` component to the root GameObject +6. Assign the fields in the Inspector: + - `scrollRect`: the `ScrollRect` component + - `content`: the `Content` child's `RectTransform` + - `entryPrefab`: your `LogEntryView` prefab + - `poolSize`: number of pre-instantiated entries (default 20, increase for taller views) + +### Initialization from code + +After instantiating or finding the `GameLogView` in the scene: + +```csharp +var gameLogView = Object.FindFirstObjectByType(); +gameLogView.Initialize(logStore); + +// Or filtered to a single channel: +gameLogView.Initialize(logStore, LogChannel.Combat); +``` + +## Log Channels + +### Built-in channels + +| Channel | Usage | +|---------|-------| +| `LogChannel.Combat` | Combat events, damage, abilities | +| `LogChannel.CharacterCreation` | Character creation flow | +| `LogChannel.World` | World events, exploration | +| `LogChannel.General` | General-purpose messages | + +### Custom channels + +`LogChannel` is a readonly struct keyed by a string ID. Define custom channels as static fields: + +```csharp +public static class MyLogChannels { + public static readonly LogChannel Trading = new("Trading"); + public static readonly LogChannel Dialogue = new("Dialogue"); +} +``` + +Then use them like any built-in channel: + +```csharp +var tradeLogger = new InGameLogger(logStore, MyLogChannels.Trading); +tradeLogger.Log("Sold Iron Sword for 50 gold."); +``` + +## Enable / Disable Channels + +Each logger can be toggled on or off. When disabled, calls to `Log` are silently dropped (no allocation, no entry added). All channels are enabled by default. + +```csharp +var combatLog = new InGameLogger(logStore, LogChannel.Combat); + +combatLog.Disable(); +combatLog.Log("This message is silently dropped."); + +combatLog.Enable(); +combatLog.Log("Back online."); + +if(combatLog.IsEnabled) { + // check state +} +``` + +The state lives in the `IGameLogStore`, so disabling a channel from one `InGameLogger` instance disables it for all instances using the same store and channel. You can also call the store directly: + +```csharp +logStore.DisableChannel(LogChannel.World); +logStore.EnableChannel(LogChannel.World); +bool enabled = logStore.IsChannelEnabled(LogChannel.World); +``` + +## Rich Text + +Log messages support TextMeshPro rich text tags. The `InGameLogger.Log(message, hexColor)` overload wraps the message in a `` tag automatically: + +```csharp +logger.Log("Poisoned!", "#00FF00"); +// Stored as: Poisoned! +``` + +You can also embed TMP tags directly in the message string: + +```csharp +logger.Log("Gained +5 experience."); +``` + +## Save / Load Integration + +`GameLogStore` supports serializing its contents for use with a save system. + +### Save + +```csharp +GameLogSaveData saveData = logStore.GetSaveData(); +// Serialize saveData into your save file +``` + +### Load + +```csharp +// Deserialize saveData from your save file +logStore.RestoreFromSaveData(saveData); +``` + +`RestoreFromSaveData` replaces the current buffer contents. If the save data contains more entries than the store's capacity, only the most recent entries are kept. The `OnCleared` event fires after restoration so the UI can rebuild. + +### JSON serialization + +`LogChannel` is serialized as a plain string via the included `LogChannelJsonConverter` (Newtonsoft.Json). The converter is applied via attribute on `LogEntry.channel`, so no manual converter registration is needed. + +## API Reference + +### LogChannel (readonly struct) + +- `LogChannel(string id)` -- constructor +- `string Id` -- the channel identifier +- Static fields: `Combat`, `CharacterCreation`, `World`, `General` +- Implements `IEquatable`, ordinal string comparison + +### LogEntry (readonly struct) + +- `string message` -- the log message (may contain TMP rich text) +- `LogChannel channel` -- the channel this entry belongs to +- `float gameTime` -- `Time.time` when the entry was added + +### InGameLogger (readonly struct) + +- `InGameLogger(IGameLogStore store, LogChannel channel)` -- constructor +- `void Log(string message)` -- add an entry to the store +- `void Log(string message, string hexColor)` -- add a color-wrapped entry +- `void Enable()` -- enable this logger's channel +- `void Disable()` -- disable this logger's channel (Log calls are silently dropped) +- `bool IsEnabled` -- whether this logger's channel is currently enabled + +### IGameLogStore / GameLogStore + +- `GameLogStore(int capacity = 500)` -- constructor with ring buffer size +- `int Count` -- current number of entries +- `int Capacity` -- maximum entries before oldest are overwritten +- `void Add(LogChannel channel, string message)` -- add an entry (silently dropped if channel is disabled) +- `void EnableChannel(LogChannel channel)` -- enable a channel +- `void DisableChannel(LogChannel channel)` -- disable a channel +- `bool IsChannelEnabled(LogChannel channel)` -- check if a channel is enabled +- `void Clear()` -- remove all entries +- `void Clear(LogChannel channel)` -- remove entries for a specific channel +- `ReadOnlySpan GetEntries()` -- all entries in chronological order +- `int GetEntries(LogChannel channel, List results)` -- filtered entries, returns count +- `event Action OnEntryAdded` -- fires after each new entry +- `event Action OnCleared` -- fires after `Clear()` or `RestoreFromSaveData()` +- `GameLogSaveData GetSaveData()` -- snapshot for serialization +- `void RestoreFromSaveData(GameLogSaveData data)` -- replace contents from save data + +### GameLogSaveData + +- `List entries` -- serializable entry list + +### GameLogView (MonoBehaviour) -- `Jovian.InGameLogging.UI` + +- `void Initialize(IGameLogStore store, LogChannel? channelFilter = null)` -- bind to a store, optionally filtering to one channel +- Requires: `ScrollRect`, content `RectTransform`, and `LogEntryView` prefab assigned in Inspector + +### LogEntryView (MonoBehaviour) -- `Jovian.InGameLogging.UI` + +- `void SetEntry(in LogEntry entry)` -- display an entry +- `void ClearEntry()` -- reset the text +- Requires: `TMP_Text` reference assigned in Inspector + +### LogChannelJsonConverter + +- Newtonsoft.Json `JsonConverter` -- serializes `LogChannel` as its string ID diff --git a/README.md.meta b/README.md.meta new file mode 100644 index 0000000..526c493 --- /dev/null +++ b/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8a8d67e42da5eea4a873d8f632bacbbe +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime.meta b/Runtime.meta new file mode 100644 index 0000000..a6075c4 --- /dev/null +++ b/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 51325b1e7a05b6740a458bdcae9f8998 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/GameLogSaveData.cs b/Runtime/GameLogSaveData.cs new file mode 100644 index 0000000..46259c1 --- /dev/null +++ b/Runtime/GameLogSaveData.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; + +namespace Jovian.InGameLogging { + [Serializable] + public sealed class GameLogSaveData { + public List entries; + + public GameLogSaveData() { + entries = new List(); + } + + public GameLogSaveData(List entries) { + this.entries = entries; + } + } +} diff --git a/Runtime/GameLogSaveData.cs.meta b/Runtime/GameLogSaveData.cs.meta new file mode 100644 index 0000000..caed639 --- /dev/null +++ b/Runtime/GameLogSaveData.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 965eab6edce9dbc49b93d0bda0ad6f6c \ No newline at end of file diff --git a/Runtime/GameLogStore.cs b/Runtime/GameLogStore.cs new file mode 100644 index 0000000..25360bf --- /dev/null +++ b/Runtime/GameLogStore.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace Jovian.InGameLogging { + public sealed class GameLogStore : IGameLogStore { + private readonly LogEntry[] buffer; + private readonly HashSet disabledChannels = new(); + private int head; + private int count; + + public int Count => count; + public int Capacity => buffer.Length; + + public event Action OnEntryAdded; + public event Action OnCleared; + + public GameLogStore(int capacity = 500) { + buffer = new LogEntry[capacity]; + head = 0; + count = 0; + } + + public void Add(LogChannel channel, string message) { + if(disabledChannels.Contains(channel)) { + return; + } + var entry = new LogEntry(message, channel, Time.time); + buffer[head] = entry; + head = (head + 1) % buffer.Length; + if(count < buffer.Length) { + count++; + } + OnEntryAdded?.Invoke(entry); + } + + public void EnableChannel(LogChannel channel) { + disabledChannels.Remove(channel); + } + + public void DisableChannel(LogChannel channel) { + disabledChannels.Add(channel); + } + + public bool IsChannelEnabled(LogChannel channel) { + return !disabledChannels.Contains(channel); + } + + public void Clear() { + head = 0; + count = 0; + OnCleared?.Invoke(); + } + + public void Clear(LogChannel channel) { + var kept = new List(count); + var entries = GetEntries(); + for(var i = 0; i < entries.Length; i++) { + if(entries[i].channel != channel) { + kept.Add(entries[i]); + } + } + + head = 0; + count = 0; + for(var i = 0; i < kept.Count; i++) { + buffer[i] = kept[i]; + count++; + } + head = count % buffer.Length; + OnCleared?.Invoke(); + } + + public ReadOnlySpan GetEntries() { + if(count < buffer.Length) { + return new ReadOnlySpan(buffer, 0, count); + } + var result = new LogEntry[count]; + var start = head; + for(var i = 0; i < count; i++) { + result[i] = buffer[(start + i) % buffer.Length]; + } + return result; + } + + public int GetEntries(LogChannel channel, List results) { + results.Clear(); + var entries = GetEntries(); + for(var i = 0; i < entries.Length; i++) { + if(entries[i].channel == channel) { + results.Add(entries[i]); + } + } + return results.Count; + } + + public GameLogSaveData GetSaveData() { + var entries = GetEntries(); + var list = new List(entries.Length); + foreach(var t in entries) { + list.Add(t); + } + return new GameLogSaveData(list); + } + + public void RestoreFromSaveData(GameLogSaveData data) { + head = 0; + count = 0; + if(data?.entries == null) { + OnCleared?.Invoke(); + return; + } + var startIndex = Math.Max(0, data.entries.Count - buffer.Length); + for(var i = startIndex; i < data.entries.Count; i++) { + buffer[count] = data.entries[i]; + count++; + } + head = count % buffer.Length; + OnCleared?.Invoke(); + } + } +} diff --git a/Runtime/GameLogStore.cs.meta b/Runtime/GameLogStore.cs.meta new file mode 100644 index 0000000..c13567a --- /dev/null +++ b/Runtime/GameLogStore.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1eb9ff03ddd225e46852cb92ba213bc7 \ No newline at end of file diff --git a/Runtime/IGameLogStore.cs b/Runtime/IGameLogStore.cs new file mode 100644 index 0000000..04b80ab --- /dev/null +++ b/Runtime/IGameLogStore.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; + +namespace Jovian.InGameLogging { + public interface IGameLogStore { + int Count { get; } + int Capacity { get; } + + void Add(LogChannel channel, string message); + void EnableChannel(LogChannel channel); + void DisableChannel(LogChannel channel); + bool IsChannelEnabled(LogChannel channel); + void Clear(); + void Clear(LogChannel channel); + + ReadOnlySpan GetEntries(); + int GetEntries(LogChannel channel, List results); + + event Action OnEntryAdded; + event Action OnCleared; + + GameLogSaveData GetSaveData(); + void RestoreFromSaveData(GameLogSaveData data); + } +} diff --git a/Runtime/IGameLogStore.cs.meta b/Runtime/IGameLogStore.cs.meta new file mode 100644 index 0000000..ee500dd --- /dev/null +++ b/Runtime/IGameLogStore.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b5fac89eac17e874e888928c9618e812 \ No newline at end of file diff --git a/Runtime/InGameLogger.cs b/Runtime/InGameLogger.cs new file mode 100644 index 0000000..2bf0e63 --- /dev/null +++ b/Runtime/InGameLogger.cs @@ -0,0 +1,36 @@ +using System.Runtime.CompilerServices; + +namespace Jovian.InGameLogging { + public readonly struct InGameLogger { + private readonly IGameLogStore store; + private readonly LogChannel channel; + + public InGameLogger(IGameLogStore store, LogChannel channel) { + this.store = store; + this.channel = channel; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Log(string message) { + store.Add(channel, message); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Log(string message, string hexColor) { + var prefix = hexColor.Length > 0 && hexColor[0] == '#' ? "" : "#"; + store.Add(channel, $"{message}"); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Enable() { + store.EnableChannel(channel); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Disable() { + store.DisableChannel(channel); + } + + public bool IsEnabled => store.IsChannelEnabled(channel); + } +} diff --git a/Runtime/InGameLogger.cs.meta b/Runtime/InGameLogger.cs.meta new file mode 100644 index 0000000..276d3e9 --- /dev/null +++ b/Runtime/InGameLogger.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 79d11c151d20d2c41a4ad5e288a4f16f \ No newline at end of file diff --git a/Runtime/Jovian.InGameLogging.asmdef b/Runtime/Jovian.InGameLogging.asmdef new file mode 100644 index 0000000..01b3346 --- /dev/null +++ b/Runtime/Jovian.InGameLogging.asmdef @@ -0,0 +1,19 @@ +{ + "name": "Jovian.InGameLogging", + "rootNamespace": "Jovian.InGameLogging", + "references": [ + "Unity.TextMeshPro", + "Jovian.SaveSystem" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "Newtonsoft.Json.dll" + ], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} diff --git a/Runtime/Jovian.InGameLogging.asmdef.meta b/Runtime/Jovian.InGameLogging.asmdef.meta new file mode 100644 index 0000000..307be22 --- /dev/null +++ b/Runtime/Jovian.InGameLogging.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 6053f37e557f955418ef96fdf46f7d6b +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/LogChannel.cs b/Runtime/LogChannel.cs new file mode 100644 index 0000000..a6a6079 --- /dev/null +++ b/Runtime/LogChannel.cs @@ -0,0 +1,44 @@ +using System; +using UnityEngine; + +namespace Jovian.InGameLogging { + [Serializable] + public readonly struct LogChannel : IEquatable { + private readonly string id; + + public string Id => id; + + public LogChannel(string id) { + this.id = id; + } + + public static readonly LogChannel Combat = new("Combat"); + public static readonly LogChannel CharacterCreation = new("CharacterCreation"); + public static readonly LogChannel World = new("World"); + public static readonly LogChannel General = new("General"); + + public bool Equals(LogChannel other) { + return string.Equals(id, other.id, StringComparison.Ordinal); + } + + public override bool Equals(object obj) { + return obj is LogChannel other && Equals(other); + } + + public override int GetHashCode() { + return id != null ? id.GetHashCode() : 0; + } + + public override string ToString() { + return id ?? string.Empty; + } + + public static bool operator ==(LogChannel left, LogChannel right) { + return left.Equals(right); + } + + public static bool operator !=(LogChannel left, LogChannel right) { + return !left.Equals(right); + } + } +} diff --git a/Runtime/LogChannel.cs.meta b/Runtime/LogChannel.cs.meta new file mode 100644 index 0000000..8bb320d --- /dev/null +++ b/Runtime/LogChannel.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 08b1132b10325ce4d922fa7b207db4e0 \ No newline at end of file diff --git a/Runtime/LogChannelJsonConverter.cs b/Runtime/LogChannelJsonConverter.cs new file mode 100644 index 0000000..1a23c4b --- /dev/null +++ b/Runtime/LogChannelJsonConverter.cs @@ -0,0 +1,15 @@ +using System; +using Newtonsoft.Json; + +namespace Jovian.InGameLogging { + public sealed class LogChannelJsonConverter : JsonConverter { + public override void WriteJson(JsonWriter writer, LogChannel value, JsonSerializer serializer) { + writer.WriteValue(value.Id); + } + + public override LogChannel ReadJson(JsonReader reader, Type objectType, LogChannel existingValue, bool hasExistingValue, JsonSerializer serializer) { + var id = reader.Value as string; + return new LogChannel(id); + } + } +} diff --git a/Runtime/LogChannelJsonConverter.cs.meta b/Runtime/LogChannelJsonConverter.cs.meta new file mode 100644 index 0000000..9d8a172 --- /dev/null +++ b/Runtime/LogChannelJsonConverter.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3c3eb6d23e195e345a3546749ca4e96f \ No newline at end of file diff --git a/Runtime/LogEntry.cs b/Runtime/LogEntry.cs new file mode 100644 index 0000000..4099e56 --- /dev/null +++ b/Runtime/LogEntry.cs @@ -0,0 +1,20 @@ +using System; +using Newtonsoft.Json; + +namespace Jovian.InGameLogging { + [Serializable] + public readonly struct LogEntry { + [JsonProperty] public readonly string message; + + [JsonProperty] [JsonConverter(typeof(LogChannelJsonConverter))] + public readonly LogChannel channel; + + [JsonProperty] public readonly float gameTime; + + public LogEntry(string message, LogChannel channel, float gameTime) { + this.message = message; + this.channel = channel; + this.gameTime = gameTime; + } + } +} diff --git a/Runtime/LogEntry.cs.meta b/Runtime/LogEntry.cs.meta new file mode 100644 index 0000000..5d98b52 --- /dev/null +++ b/Runtime/LogEntry.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0f79543f908769543ac8fc14e138df62 \ No newline at end of file diff --git a/Runtime/UI.meta b/Runtime/UI.meta new file mode 100644 index 0000000..7c9d941 --- /dev/null +++ b/Runtime/UI.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b1125aedc37d43948aeef186bccbeac8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/UI/GameLogView.cs b/Runtime/UI/GameLogView.cs new file mode 100644 index 0000000..d9b983e --- /dev/null +++ b/Runtime/UI/GameLogView.cs @@ -0,0 +1,137 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.UI; + +namespace Jovian.InGameLogging.UI { + public class GameLogView : MonoBehaviour { + [SerializeField] ScrollRect scrollRect; + [SerializeField] RectTransform content; + [SerializeField] LogEntryView entryPrefab; + [SerializeField] int poolSize = 20; + + IGameLogStore store; + LogChannel? channelFilter; + bool autoScroll = true; + bool scrollingToBottom; + Coroutine scrollCoroutine; + + readonly List activeEntries = new(); + readonly Stack pool = new(); + + public void Initialize(IGameLogStore store, LogChannel? channelFilter = null) { + this.store = store; + this.channelFilter = channelFilter; + + WarmPool(); + store.OnEntryAdded += HandleEntryAdded; + store.OnCleared += HandleCleared; + scrollRect.onValueChanged.AddListener(HandleScrollChanged); + + RebuildFromStore(); + } + + void OnDestroy() { + if(store != null) { + store.OnEntryAdded -= HandleEntryAdded; + store.OnCleared -= HandleCleared; + } + if(scrollRect != null) { + scrollRect.onValueChanged.RemoveListener(HandleScrollChanged); + } + } + + void WarmPool() { + for(int i = 0; i < poolSize; i++) { + var entry = Instantiate(entryPrefab, content); + entry.gameObject.SetActive(false); + pool.Push(entry); + } + } + + LogEntryView GetFromPool() { + LogEntryView entry; + if(pool.Count > 0) { + entry = pool.Pop(); + } + else { + entry = activeEntries[0]; + activeEntries.RemoveAt(0); + } + entry.gameObject.SetActive(true); + entry.transform.SetAsLastSibling(); + return entry; + } + + void ReturnToPool(LogEntryView entry) { + entry.ClearEntry(); + entry.gameObject.SetActive(false); + pool.Push(entry); + } + + void HandleEntryAdded(LogEntry entry) { + if(channelFilter.HasValue && entry.channel != channelFilter.Value) { + return; + } + + var view = GetFromPool(); + view.SetEntry(in entry); + activeEntries.Add(view); + + if(autoScroll) { + RequestScrollToBottom(); + } + } + + void RequestScrollToBottom() { + if(scrollCoroutine != null) { + StopCoroutine(scrollCoroutine); + } + scrollCoroutine = StartCoroutine(ScrollToBottomRoutine()); + } + + IEnumerator ScrollToBottomRoutine() { + scrollingToBottom = true; + yield return null; + LayoutRebuilder.ForceRebuildLayoutImmediate(content); + scrollRect.verticalNormalizedPosition = 0f; + yield return null; + scrollingToBottom = false; + scrollCoroutine = null; + } + + void HandleCleared() { + for(int i = activeEntries.Count - 1; i >= 0; i--) { + ReturnToPool(activeEntries[i]); + } + activeEntries.Clear(); + + if(store.Count > 0) { + RebuildFromStore(); + } + } + + void RebuildFromStore() { + var entries = store.GetEntries(); + for(int i = 0; i < entries.Length; i++) { + if(channelFilter.HasValue && entries[i].channel != channelFilter.Value) { + continue; + } + var view = GetFromPool(); + view.SetEntry(in entries[i]); + activeEntries.Add(view); + } + + if(autoScroll) { + RequestScrollToBottom(); + } + } + + void HandleScrollChanged(Vector2 position) { + if(scrollingToBottom) { + return; + } + autoScroll = position.y <= 0.01f; + } + } +} diff --git a/Runtime/UI/GameLogView.cs.meta b/Runtime/UI/GameLogView.cs.meta new file mode 100644 index 0000000..f62a959 --- /dev/null +++ b/Runtime/UI/GameLogView.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 786fb23f28122964cb30678ea785bd40 \ No newline at end of file diff --git a/Runtime/UI/LogEntryView.cs b/Runtime/UI/LogEntryView.cs new file mode 100644 index 0000000..3f48b8a --- /dev/null +++ b/Runtime/UI/LogEntryView.cs @@ -0,0 +1,16 @@ +using TMPro; +using UnityEngine; + +namespace Jovian.InGameLogging.UI { + public class LogEntryView : MonoBehaviour { + [SerializeField] TMP_Text messageText; + + public void SetEntry(in LogEntry entry) { + messageText.text = entry.message; + } + + public void ClearEntry() { + messageText.text = string.Empty; + } + } +} diff --git a/Runtime/UI/LogEntryView.cs.meta b/Runtime/UI/LogEntryView.cs.meta new file mode 100644 index 0000000..484b3ce --- /dev/null +++ b/Runtime/UI/LogEntryView.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5526887cdf77a54439357be8b5754ffc \ No newline at end of file diff --git a/Samples~/LogContainer.prefab b/Samples~/LogContainer.prefab new file mode 100644 index 0000000..e1b2523 --- /dev/null +++ b/Samples~/LogContainer.prefab @@ -0,0 +1,790 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &1447740470325782801 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6286131802514671469} + - component: {fileID: 7279458507301375298} + - component: {fileID: 7180659085512946289} + - component: {fileID: 1194623820596100263} + m_Layer: 0 + m_Name: Viewport + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &6286131802514671469 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1447740470325782801} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 3059084814696477984} + m_Father: {fileID: 6248177754200331468} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0, y: 1} +--- !u!222 &7279458507301375298 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1447740470325782801} + m_CullTransparentMesh: 1 +--- !u!114 &7180659085512946289 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1447740470325782801} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10917, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!114 &1194623820596100263 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1447740470325782801} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 31a19414c41e5ae4aae2af33fee712f6, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Mask + m_ShowMaskGraphic: 0 +--- !u!1 &1525412503934350410 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2417638750186197328} + - component: {fileID: 6899372841946195929} + m_Layer: 0 + m_Name: LogContainer + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &2417638750186197328 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1525412503934350410} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 6248177754200331468} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 1.0002, y: -252.13} + m_SizeDelta: {x: 737.42, y: 504.25} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &6899372841946195929 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1525412503934350410} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 786fb23f28122964cb30678ea785bd40, type: 3} + m_Name: + m_EditorClassIdentifier: Jovian.InGameLogging::Jovian.InGameLogging.UI.GameLogView + scrollRect: {fileID: 2554617498295089481} + content: {fileID: 3059084814696477984} + entryPrefab: {fileID: 4832918257971952213, guid: 9d1c7837b0b5a9f45baa84f326fc247c, type: 3} + poolSize: 20 +--- !u!1 &2405629305058117087 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3059084814696477984} + - component: {fileID: 1744225011043606462} + m_Layer: 0 + m_Name: Content + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &3059084814696477984 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2405629305058117087} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 6286131802514671469} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 1} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 487.26} + m_Pivot: {x: 0, y: 1} +--- !u!114 &1744225011043606462 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2405629305058117087} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 59f8146938fff824cb5fd77236b75775, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.VerticalLayoutGroup + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_ChildAlignment: 0 + m_Spacing: 2 + m_ChildForceExpandWidth: 1 + m_ChildForceExpandHeight: 0 + m_ChildControlWidth: 0 + m_ChildControlHeight: 0 + m_ChildScaleWidth: 0 + m_ChildScaleHeight: 0 + m_ReverseArrangement: 0 +--- !u!1 &3952660362247579954 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2688140319784364735} + - component: {fileID: 7390804912840202258} + - component: {fileID: 5246482949485489637} + - component: {fileID: 529710424251508929} + m_Layer: 0 + m_Name: Scrollbar Horizontal + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &2688140319784364735 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3952660362247579954} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 3366448768354673732} + m_Father: {fileID: 6248177754200331468} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 20} + m_Pivot: {x: 0, y: 0} +--- !u!222 &7390804912840202258 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3952660362247579954} + m_CullTransparentMesh: 1 +--- !u!114 &5246482949485489637 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3952660362247579954} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10907, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!114 &529710424251508929 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3952660362247579954} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2a4db7a114972834c8e4117be1d82ba3, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Scrollbar + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 1342770482644207694} + m_HandleRect: {fileID: 2251566621526024109} + m_Direction: 0 + m_Value: 1 + m_Size: 0.99999994 + m_NumberOfSteps: 0 + m_OnValueChanged: + m_PersistentCalls: + m_Calls: [] +--- !u!1 &5969083621566914297 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 374280998538979084} + - component: {fileID: 5436844256530954601} + - component: {fileID: 6879256843609484833} + m_Layer: 0 + m_Name: Handle + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &374280998538979084 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5969083621566914297} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 8977826534581480953} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 20, y: 20} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &5436844256530954601 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5969083621566914297} + m_CullTransparentMesh: 1 +--- !u!114 &6879256843609484833 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5969083621566914297} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &6050805973784497029 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3366448768354673732} + m_Layer: 0 + m_Name: Sliding Area + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &3366448768354673732 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6050805973784497029} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 2251566621526024109} + m_Father: {fileID: 2688140319784364735} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: -8.5, y: 0} + m_SizeDelta: {x: -20, y: -20} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!1 &6074956918412360435 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6248177754200331468} + - component: {fileID: 7678453280902357168} + - component: {fileID: 4027884099002409368} + - component: {fileID: 2554617498295089481} + m_Layer: 0 + m_Name: Scroll View + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &6248177754200331468 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6074956918412360435} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 6286131802514671469} + - {fileID: 2688140319784364735} + - {fileID: 4070132176653801724} + m_Father: {fileID: 2417638750186197328} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0.0024719} + m_SizeDelta: {x: 737.42, y: 504.26} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &7678453280902357168 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6074956918412360435} + m_CullTransparentMesh: 1 +--- !u!114 &4027884099002409368 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6074956918412360435} + m_Enabled: 0 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 0.392} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10907, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!114 &2554617498295089481 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6074956918412360435} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1aa08ab6e0800fa44ae55d278d1423e3, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.ScrollRect + m_Content: {fileID: 3059084814696477984} + m_Horizontal: 0 + m_Vertical: 1 + m_MovementType: 1 + m_Elasticity: 0 + m_Inertia: 1 + m_DecelerationRate: 0.135 + m_ScrollSensitivity: 1 + m_Viewport: {fileID: 6286131802514671469} + m_HorizontalScrollbar: {fileID: 529710424251508929} + m_VerticalScrollbar: {fileID: 9136497547051495148} + m_HorizontalScrollbarVisibility: 2 + m_VerticalScrollbarVisibility: 2 + m_HorizontalScrollbarSpacing: -3 + m_VerticalScrollbarSpacing: -3 + m_OnValueChanged: + m_PersistentCalls: + m_Calls: [] +--- !u!1 &6994548556054523628 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4070132176653801724} + - component: {fileID: 8682590904891029538} + - component: {fileID: 5114829985009070717} + - component: {fileID: 9136497547051495148} + m_Layer: 0 + m_Name: Scrollbar Vertical + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &4070132176653801724 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6994548556054523628} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 8977826534581480953} + m_Father: {fileID: 6248177754200331468} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 1, y: 0} + m_AnchorMax: {x: 1, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 20, y: 0} + m_Pivot: {x: 1, y: 1} +--- !u!222 &8682590904891029538 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6994548556054523628} + m_CullTransparentMesh: 1 +--- !u!114 &5114829985009070717 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6994548556054523628} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10907, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!114 &9136497547051495148 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6994548556054523628} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2a4db7a114972834c8e4117be1d82ba3, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Scrollbar + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 6879256843609484833} + m_HandleRect: {fileID: 374280998538979084} + m_Direction: 2 + m_Value: 1 + m_Size: 1 + m_NumberOfSteps: 0 + m_OnValueChanged: + m_PersistentCalls: + m_Calls: [] +--- !u!1 &7187724186482890403 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8977826534581480953} + m_Layer: 0 + m_Name: Sliding Area + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &8977826534581480953 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7187724186482890403} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 374280998538979084} + m_Father: {fileID: 4070132176653801724} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 8.5} + m_SizeDelta: {x: -20, y: -20} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!1 &7193882241213188023 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2251566621526024109} + - component: {fileID: 1372064744394430487} + - component: {fileID: 1342770482644207694} + m_Layer: 0 + m_Name: Handle + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &2251566621526024109 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7193882241213188023} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 3366448768354673732} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 20, y: 20} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &1372064744394430487 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7193882241213188023} + m_CullTransparentMesh: 1 +--- !u!114 &1342770482644207694 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7193882241213188023} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 diff --git a/Samples~/LogEntry.prefab b/Samples~/LogEntry.prefab new file mode 100644 index 0000000..f8fc8c1 --- /dev/null +++ b/Samples~/LogEntry.prefab @@ -0,0 +1,210 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &6501593483943143564 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 382400732949652569} + - component: {fileID: 4832918257971952213} + - component: {fileID: 1727094465477629914} + m_Layer: 0 + m_Name: LogEntry + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &382400732949652569 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6501593483943143564} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 8597194705437786868} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 1} + m_AnchorMax: {x: 0, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 1093, y: 615} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &4832918257971952213 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6501593483943143564} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5526887cdf77a54439357be8b5754ffc, type: 3} + m_Name: + m_EditorClassIdentifier: Jovian.InGameLogging::Jovian.InGameLogging.UI.LogEntryView + messageText: {fileID: 4259574353180979179} +--- !u!114 &1727094465477629914 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6501593483943143564} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.LayoutElement + m_IgnoreLayout: 0 + m_MinWidth: -1 + m_MinHeight: -1 + m_PreferredWidth: -1 + m_PreferredHeight: 100 + m_FlexibleWidth: 1 + m_FlexibleHeight: -1 + m_LayoutPriority: 1 +--- !u!1 &8033193440249993941 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8597194705437786868} + - component: {fileID: 8331827018725656388} + - component: {fileID: 4259574353180979179} + m_Layer: 0 + m_Name: Text (TMP) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &8597194705437786868 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8033193440249993941} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 382400732949652569} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: -0.0000000044237822} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &8331827018725656388 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8033193440249993941} + m_CullTransparentMesh: 1 +--- !u!114 &4259574353180979179 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8033193440249993941} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: New Text + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4294967295 + m_fontColor: {r: 1, g: 1, b: 1, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: -1183493901 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 24 + m_fontSizeBase: 36 + m_fontWeight: 400 + m_enableAutoSizing: 1 + m_fontSizeMin: 5 + m_fontSizeMax: 24 + m_fontStyle: 0 + m_HorizontalAlignment: 1 + m_VerticalAlignment: 256 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_characterHorizontalScale: 1 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_TextWrappingMode: 1 + m_wordWrappingRatios: 0.4 + m_overflowMode: 3 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 0 + m_ActiveFontFeatures: 6e72656b + m_enableExtraPadding: 0 + checkPaddingRequired: 0 + m_isRichText: 1 + m_EmojiFallbackSupport: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 1 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 0 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} diff --git a/Samples~/README.md b/Samples~/README.md new file mode 100644 index 0000000..2803af2 --- /dev/null +++ b/Samples~/README.md @@ -0,0 +1,3 @@ +# Samples + +This folder is reserved for sample scenes and scripts demonstrating the In-Game Logging system. diff --git a/package.json b/package.json new file mode 100644 index 0000000..4a6e584 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "com.jovian.ingame-logging", + "version": "0.1.0", + "displayName": "Jovian In-Game Logging", + "description": "An optimized, low-allocation in-game logging system with pooled UI, channel-based filtering, and save system integration.", + "unity": "2022.3", + "dependencies": { + "com.unity.textmeshpro": "3.0.6", + "com.unity.nuget.newtonsoft-json": "3.2.1", + "com.jovian.savesystem": "0.1.0" + }, + "keywords": [ + "logging", + "ui", + "ingame" + ], + "author": { + "name": "Jovian" + } +} diff --git a/package.json.meta b/package.json.meta new file mode 100644 index 0000000..b8a33a7 --- /dev/null +++ b/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: acdee7574039a3a48980e2cc9c6fe31d +PackageManifestImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: