added code from unity
This commit is contained in:
8
Editor.meta
Normal file
8
Editor.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 1e182a45ed498c445b141e9ec6395805
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
338
Editor/GameLogViewerWindow.cs
Normal file
338
Editor/GameLogViewerWindow.cs
Normal file
@@ -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<SessionGroup> sessions = new();
|
||||||
|
private SaveSlotEntry selectedSlot;
|
||||||
|
private List<LogEntry> loadedEntries = new();
|
||||||
|
private List<LogEntry> filteredEntries = new();
|
||||||
|
private List<string> 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<GameLogViewerWindow>("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<SaveIndex>(indexJson);
|
||||||
|
if(index?.sessions == null || index.slots == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var slotsBySession = new Dictionary<string, List<SaveSlotEntry>>();
|
||||||
|
foreach(var slot in index.slots) {
|
||||||
|
if(!slotsBySession.ContainsKey(slot.sessionId)) {
|
||||||
|
slotsBySession[slot.sessionId] = new List<SaveSlotEntry>();
|
||||||
|
}
|
||||||
|
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<SaveEnvelope>(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<List<LogEntry>>();
|
||||||
|
if(logData == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadedEntries.AddRange(logData);
|
||||||
|
|
||||||
|
// Build channel list
|
||||||
|
var uniqueChannels = new HashSet<string>();
|
||||||
|
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<SaveSessionInfo> sessions;
|
||||||
|
public List<SaveSlotInfo> slots;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class SessionGroup {
|
||||||
|
public string sessionId;
|
||||||
|
public string displayName;
|
||||||
|
public bool foldout;
|
||||||
|
public List<SaveSlotEntry> slots;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class SaveSlotEntry {
|
||||||
|
public string sessionId;
|
||||||
|
public string displayName;
|
||||||
|
public string filePath;
|
||||||
|
public long timestampUtc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Editor/GameLogViewerWindow.cs.meta
Normal file
2
Editor/GameLogViewerWindow.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6f19bfe069d21db41a998154aa0876fb
|
||||||
19
Editor/Jovian.InGameLogging.Editor.asmdef
Normal file
19
Editor/Jovian.InGameLogging.Editor.asmdef
Normal file
@@ -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
|
||||||
|
}
|
||||||
7
Editor/Jovian.InGameLogging.Editor.asmdef.meta
Normal file
7
Editor/Jovian.InGameLogging.Editor.asmdef.meta
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 48c945ba5ea83b144b5bbf4eaf33fe29
|
||||||
|
AssemblyDefinitionImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
263
README.md
263
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.
|
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>();
|
||||||
|
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 `<color>` tag automatically:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
logger.Log("Poisoned!", "#00FF00");
|
||||||
|
// Stored as: <color=#00FF00>Poisoned!</color>
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also embed TMP tags directly in the message string:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
logger.Log("Gained <b>+5</b> <color=#FFD700>experience</color>.");
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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<LogChannel>`, 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<LogEntry> GetEntries()` -- all entries in chronological order
|
||||||
|
- `int GetEntries(LogChannel channel, List<LogEntry> results)` -- filtered entries, returns count
|
||||||
|
- `event Action<LogEntry> 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<LogEntry> 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<LogChannel>` -- serializes `LogChannel` as its string ID
|
||||||
|
|||||||
7
README.md.meta
Normal file
7
README.md.meta
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8a8d67e42da5eea4a873d8f632bacbbe
|
||||||
|
TextScriptImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
8
Runtime.meta
Normal file
8
Runtime.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 51325b1e7a05b6740a458bdcae9f8998
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
17
Runtime/GameLogSaveData.cs
Normal file
17
Runtime/GameLogSaveData.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Jovian.InGameLogging {
|
||||||
|
[Serializable]
|
||||||
|
public sealed class GameLogSaveData {
|
||||||
|
public List<LogEntry> entries;
|
||||||
|
|
||||||
|
public GameLogSaveData() {
|
||||||
|
entries = new List<LogEntry>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public GameLogSaveData(List<LogEntry> entries) {
|
||||||
|
this.entries = entries;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Runtime/GameLogSaveData.cs.meta
Normal file
2
Runtime/GameLogSaveData.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 965eab6edce9dbc49b93d0bda0ad6f6c
|
||||||
122
Runtime/GameLogStore.cs
Normal file
122
Runtime/GameLogStore.cs
Normal file
@@ -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<LogChannel> disabledChannels = new();
|
||||||
|
private int head;
|
||||||
|
private int count;
|
||||||
|
|
||||||
|
public int Count => count;
|
||||||
|
public int Capacity => buffer.Length;
|
||||||
|
|
||||||
|
public event Action<LogEntry> 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<LogEntry>(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<LogEntry> GetEntries() {
|
||||||
|
if(count < buffer.Length) {
|
||||||
|
return new ReadOnlySpan<LogEntry>(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<LogEntry> 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<LogEntry>(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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Runtime/GameLogStore.cs.meta
Normal file
2
Runtime/GameLogStore.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 1eb9ff03ddd225e46852cb92ba213bc7
|
||||||
25
Runtime/IGameLogStore.cs
Normal file
25
Runtime/IGameLogStore.cs
Normal file
@@ -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<LogEntry> GetEntries();
|
||||||
|
int GetEntries(LogChannel channel, List<LogEntry> results);
|
||||||
|
|
||||||
|
event Action<LogEntry> OnEntryAdded;
|
||||||
|
event Action OnCleared;
|
||||||
|
|
||||||
|
GameLogSaveData GetSaveData();
|
||||||
|
void RestoreFromSaveData(GameLogSaveData data);
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Runtime/IGameLogStore.cs.meta
Normal file
2
Runtime/IGameLogStore.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b5fac89eac17e874e888928c9618e812
|
||||||
36
Runtime/InGameLogger.cs
Normal file
36
Runtime/InGameLogger.cs
Normal file
@@ -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, $"<color={prefix}{hexColor}>{message}</color>");
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Enable() {
|
||||||
|
store.EnableChannel(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Disable() {
|
||||||
|
store.DisableChannel(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsEnabled => store.IsChannelEnabled(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Runtime/InGameLogger.cs.meta
Normal file
2
Runtime/InGameLogger.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 79d11c151d20d2c41a4ad5e288a4f16f
|
||||||
19
Runtime/Jovian.InGameLogging.asmdef
Normal file
19
Runtime/Jovian.InGameLogging.asmdef
Normal file
@@ -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
|
||||||
|
}
|
||||||
7
Runtime/Jovian.InGameLogging.asmdef.meta
Normal file
7
Runtime/Jovian.InGameLogging.asmdef.meta
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6053f37e557f955418ef96fdf46f7d6b
|
||||||
|
AssemblyDefinitionImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
44
Runtime/LogChannel.cs
Normal file
44
Runtime/LogChannel.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Jovian.InGameLogging {
|
||||||
|
[Serializable]
|
||||||
|
public readonly struct LogChannel : IEquatable<LogChannel> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Runtime/LogChannel.cs.meta
Normal file
2
Runtime/LogChannel.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 08b1132b10325ce4d922fa7b207db4e0
|
||||||
15
Runtime/LogChannelJsonConverter.cs
Normal file
15
Runtime/LogChannelJsonConverter.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Jovian.InGameLogging {
|
||||||
|
public sealed class LogChannelJsonConverter : JsonConverter<LogChannel> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Runtime/LogChannelJsonConverter.cs.meta
Normal file
2
Runtime/LogChannelJsonConverter.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3c3eb6d23e195e345a3546749ca4e96f
|
||||||
20
Runtime/LogEntry.cs
Normal file
20
Runtime/LogEntry.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Runtime/LogEntry.cs.meta
Normal file
2
Runtime/LogEntry.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 0f79543f908769543ac8fc14e138df62
|
||||||
8
Runtime/UI.meta
Normal file
8
Runtime/UI.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b1125aedc37d43948aeef186bccbeac8
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
137
Runtime/UI/GameLogView.cs
Normal file
137
Runtime/UI/GameLogView.cs
Normal file
@@ -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<LogEntryView> activeEntries = new();
|
||||||
|
readonly Stack<LogEntryView> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Runtime/UI/GameLogView.cs.meta
Normal file
2
Runtime/UI/GameLogView.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 786fb23f28122964cb30678ea785bd40
|
||||||
16
Runtime/UI/LogEntryView.cs
Normal file
16
Runtime/UI/LogEntryView.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Runtime/UI/LogEntryView.cs.meta
Normal file
2
Runtime/UI/LogEntryView.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 5526887cdf77a54439357be8b5754ffc
|
||||||
790
Samples~/LogContainer.prefab
Normal file
790
Samples~/LogContainer.prefab
Normal file
@@ -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
|
||||||
210
Samples~/LogEntry.prefab
Normal file
210
Samples~/LogEntry.prefab
Normal file
@@ -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}
|
||||||
3
Samples~/README.md
Normal file
3
Samples~/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Samples
|
||||||
|
|
||||||
|
This folder is reserved for sample scenes and scripts demonstrating the In-Game Logging system.
|
||||||
20
package.json
Normal file
20
package.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
7
package.json.meta
Normal file
7
package.json.meta
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: acdee7574039a3a48980e2cc9c6fe31d
|
||||||
|
PackageManifestImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Reference in New Issue
Block a user