Restructured for new direction.
This commit is contained in:
37
Assets/Scripts/AGENTS.md
Normal file
37
Assets/Scripts/AGENTS.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# A Fairytale Gone Bad: The Briar Queen
|
||||
|
||||
Unity based Point and Click Adventure.
|
||||
|
||||
## Summary
|
||||
|
||||
The Briar Queen is the first entry in a series of horror/thriller based Point and Click Adventure.
|
||||
It revolves around our central heroine, who's returned to her birthplace after having been adopted 20 years prior.
|
||||
But not all is what she thought, she was expecting a wondrous and vibrant land of mysticism and magicks, but instead
|
||||
she comes across a desolate land of despair and terror. Vines have overgrown the villages, houses sit abandoned, and
|
||||
the streets desolate. Her task (and the players) is to discover what happened to the lands once known as prosperous
|
||||
and wonderful, and perhaps, just perhaps, discover herself in the process.
|
||||
|
||||
## Gameplay Design
|
||||
|
||||
1. **Canvas** - Our game is entirely canvas based and pre-drawn artwork. No 3D Objects or 2D objects. Users interact by clicking on objects, solving puzzles.
|
||||
2. **Puzzles** - We use Resident-Evil styled puzzles which can be composed of as many parts as necessary. Items get picked up and used to complete or interact with these puzzles.
|
||||
3. **Registries** - Scriptable Objects should live in a registry of somekind. Asset References, Audio, etc.
|
||||
4. **Braces required on all control flow** — `if`, `else`, `for`, `foreach`, `while`, `do`, `switch` must always have braces, including two-line statements.
|
||||
5. **Naming** — `_camelCase` private fields, `PascalCase` properties/methods/classes.
|
||||
6. **Player** - We consider everything from the player's point of view. How Audio is going to sound or how a puzzle plays out.
|
||||
7. **UniTask** - Any async-capable code should be put into UniTasks and awaited. See below for documentation link.
|
||||
8. **PrimeTween** - Tweening is done using the PrimeTween Library. Each class that has a Sequence should also have a cancellation token.
|
||||
9. **Tokens** - Cancellation Tokens should be re-used where possible.
|
||||
10. **Versions** - We're using C# 9 with Unity 6.3
|
||||
|
||||
|
||||
## Documentation
|
||||
|
||||
UniTask - https://github.com/Cysharp/UniTask
|
||||
PrimeTween - https://github.com/KyryloKuzyk/PrimeTween
|
||||
|
||||
## AI Direction
|
||||
|
||||
1. Never just apply code. Always present it to me first so I can review it. Prefer full classes over simple methods so I can get the full context.
|
||||
2. I am autisic, so please present explanations for any code changes, what it'll fix or achieve, and why we should do it.
|
||||
3.
|
||||
3
Assets/Scripts/AGENTS.md.meta
Normal file
3
Assets/Scripts/AGENTS.md.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f3851666fa8045b6985cd88cbc091a30
|
||||
timeCreated: 1777915419
|
||||
BIN
Assets/Scripts/Archive.zip
Normal file
BIN
Assets/Scripts/Archive.zip
Normal file
Binary file not shown.
7
Assets/Scripts/Archive.zip.meta
Normal file
7
Assets/Scripts/Archive.zip.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2424908ceda2f438a98e74be97301a50
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -15,6 +15,9 @@ namespace BriarQueen.Data.IO.Saves
|
||||
{
|
||||
public string SaveVersion = "0.0.2-alpha";
|
||||
public string SaveFileName;
|
||||
|
||||
// Key Unlocks
|
||||
public bool CodexUnlocked = false;
|
||||
|
||||
// Inventory & item tracking
|
||||
public List<ItemSaveData> InventoryData = new();
|
||||
@@ -95,22 +98,7 @@ namespace BriarQueen.Data.IO.Saves
|
||||
public enum LevelFlag
|
||||
{
|
||||
None = 0,
|
||||
FountainVinesCut,
|
||||
PumpHouseOpened,
|
||||
PumpHousePipesFixed,
|
||||
PumpWaterRestored,
|
||||
WorkshopBagHoleDug,
|
||||
WorkshopSafeUnlocked,
|
||||
WorkshopDownstairsDoorOpen,
|
||||
WorkshopDownstairsLightOn,
|
||||
WorkshopGrindstoneRepaired,
|
||||
VillageStreetGateOpen,
|
||||
VillageStreetVinesCut,
|
||||
LaxleyFireplaceExtinguished,
|
||||
LaxleyLockboxOpened,
|
||||
LaxleyClockSolved,
|
||||
LaxleyHourHandRetrieved,
|
||||
LaxleyMinuteHandRetrieved,
|
||||
MarketGateOpen,
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
@@ -123,14 +111,6 @@ namespace BriarQueen.Data.IO.Saves
|
||||
// Tracks completed puzzles
|
||||
public Dictionary<string, bool> PuzzleCompleted = new();
|
||||
|
||||
// Candle slots
|
||||
public Dictionary<int, string> WorkshopCandleSlotsFilled = new()
|
||||
{
|
||||
{ 0, ItemIDs.Pickups[ItemKey.BlueCandle] },
|
||||
{ 3, ItemIDs.Pickups[ItemKey.OrangeCandle] },
|
||||
{ 5, ItemIDs.Pickups[ItemKey.RedCandle] }
|
||||
};
|
||||
|
||||
// -------- Helper Methods --------
|
||||
public bool IsPuzzleCompleted(PuzzleKey puzzle)
|
||||
{
|
||||
|
||||
@@ -24,56 +24,16 @@ namespace BriarQueen.Data.Identifiers
|
||||
public enum LevelKey
|
||||
{
|
||||
None = 0,
|
||||
ChapterOneVillageEdge,
|
||||
ChapterOneVillage,
|
||||
ChapterOneVillageFurther,
|
||||
ChapterOnePumphouse,
|
||||
ChapterOneFountain,
|
||||
ChapterOneWorkshop,
|
||||
ChapterOnePumphousePipes,
|
||||
ChapterOneWorkshopDrawer,
|
||||
ChapterOneWorkshopUpstairs,
|
||||
ChapterOneWorkshopCandlePuzzle,
|
||||
ChapterOneWorkshopJewelleryBox,
|
||||
ChapterOneWorkshopJewelleryBoxOpen,
|
||||
ChapterOneWorkshopSafe,
|
||||
ChapterOneWorkshopBag,
|
||||
ChapterOneWorkshopDownstairs,
|
||||
ChapterOnePumphouseTable,
|
||||
ChapterOneWorkshopBookcase,
|
||||
ChapterOneFountainPuzzle,
|
||||
ChapterOneStreetGateSign,
|
||||
ChapterOneLaxleyHouse,
|
||||
ChapterOneLaxleyHouseClock,
|
||||
ChapterOneLaxleyHouseClockPuzzle,
|
||||
ChapterOneLaxleyHouseTable,
|
||||
ChapterOneLaxleyHouseUpstairs,
|
||||
ChapterOneLaxleyHouseFireplace,
|
||||
ChapterOneVillageMarketSquare,
|
||||
ChapterOneVillageMarketSquareStatue,
|
||||
ChapterOneVillageMarketSquareFirepit,
|
||||
ChapterOneVillageEnd,
|
||||
ChapterOneVillageEndChurch,
|
||||
ChapterOneLaxleyHouseLockbox,
|
||||
ChapterOneLaxleyHouseUpstairsStudy,
|
||||
ChapterOneLaxleyUpstairsPortrait,
|
||||
ChapterOneLaxleyUpstairsTable,
|
||||
ChapterOneLaxleyUpstairsStorage,
|
||||
ChapterOneStreetCart,
|
||||
ChapterOneArrivalRoad,
|
||||
ChapterOneAshwickRidgeway,
|
||||
ChapterOneInsideBrokenDownCar,
|
||||
ChapterOneAshwickMarketplace,
|
||||
ChapterOneHarrowVale,
|
||||
}
|
||||
|
||||
public enum AssetItemKey
|
||||
{
|
||||
None = 0,
|
||||
ChapterOneBoxPuzzlePiece1,
|
||||
ChapterOneBoxPuzzlePiece2,
|
||||
ChapterOneBoxPuzzlePiece3,
|
||||
ChapterOneBoxPuzzlePiece4,
|
||||
ChapterOneBoxPuzzlePiece5,
|
||||
ChapterOneBoxPuzzlePiece6,
|
||||
ChapterOneBoxPuzzlePiece7,
|
||||
ChapterOneBoxPuzzlePiece8,
|
||||
ChapterOneWorkshopWornBook
|
||||
}
|
||||
|
||||
public static class AssetKeyIdentifiers
|
||||
|
||||
@@ -11,17 +11,8 @@ namespace BriarQueen.Data.Identifiers
|
||||
public enum SFXKey
|
||||
{
|
||||
None = 0,
|
||||
WorkshopSafeUnlocked,
|
||||
WorkshopPuzzleBoxUnlocked,
|
||||
DoorCreek,
|
||||
ItemCollected,
|
||||
GateOpening,
|
||||
PuzzleIncorrect,
|
||||
ResetPuzzle,
|
||||
SharpenKnife,
|
||||
LockBoxNumberReel,
|
||||
LockboxOpening,
|
||||
ClockOpening,
|
||||
CarDoorOpening,
|
||||
ItemPickup,
|
||||
}
|
||||
|
||||
public enum UIFXKey
|
||||
@@ -48,32 +39,23 @@ namespace BriarQueen.Data.Identifiers
|
||||
new Dictionary<MusicKey, string>
|
||||
{
|
||||
// Add when you have music
|
||||
// { MusicKey.SomeTrack, "MUSIC_SomeTrack" }
|
||||
// { MusicKey.SomeTrack, "Music:SomeTrack" }
|
||||
});
|
||||
|
||||
public static readonly IReadOnlyDictionary<SFXKey, string> SFX =
|
||||
new ReadOnlyDictionary<SFXKey, string>(
|
||||
new Dictionary<SFXKey, string>
|
||||
{
|
||||
{ SFXKey.WorkshopSafeUnlocked, "SFX_WorkshopSafeUnlocked" },
|
||||
{ SFXKey.WorkshopPuzzleBoxUnlocked, "SFX_WorkshopPuzzleBoxUnlocked" },
|
||||
{ SFXKey.DoorCreek, "SFX_DoorCreek" },
|
||||
{ SFXKey.ItemCollected, "SFX_ItemCollected" },
|
||||
{ SFXKey.GateOpening, "SFX_GateOpening" },
|
||||
{ SFXKey.PuzzleIncorrect, "SFX_PuzzleIncorrect" },
|
||||
{ SFXKey.ResetPuzzle, "SFX_ResetPuzzle" },
|
||||
{ SFXKey.SharpenKnife, "SFX_SharpenKnife"},
|
||||
{ SFXKey.LockBoxNumberReel, "SFX_LockBoxNumberReel" },
|
||||
{ SFXKey.LockboxOpening, "SFX_LockboxOpening" },
|
||||
{ SFXKey.ClockOpening, "SFX_ClockOpening" },
|
||||
{ SFXKey.CarDoorOpening, "SFX:CarDoorOpening" },
|
||||
{ SFXKey.ItemPickup, "SFX:ItemPickup" },
|
||||
});
|
||||
|
||||
public static readonly IReadOnlyDictionary<UIFXKey, string> UIFX =
|
||||
new ReadOnlyDictionary<UIFXKey, string>(
|
||||
new Dictionary<UIFXKey, string>
|
||||
{
|
||||
{ UIFXKey.AchievementUnlocked, "UIFX_AchievementUnlocked" },
|
||||
{ UIFXKey.CodexEntryUnlocked, "UIFX_CodexEntryUnlocked" },
|
||||
{ UIFXKey.AchievementUnlocked, "UIFX:AchievementUnlocked" },
|
||||
{ UIFXKey.CodexEntryUnlocked, "UIFX:CodexEntryUnlocked" },
|
||||
});
|
||||
|
||||
public static readonly IReadOnlyDictionary<AmbienceKey, string> Ambience =
|
||||
@@ -115,4 +97,4 @@ namespace BriarQueen.Data.Identifiers
|
||||
return Voice.TryGetValue(key, out var value) ? value : string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,20 +3,16 @@ namespace BriarQueen.Data.Identifiers
|
||||
public static class AudioMixerParameters
|
||||
{
|
||||
public const string MASTER_VOLUME = "Master_Volume";
|
||||
public const string MUSIC_VOLUME = "Music_Volume";
|
||||
public const string SFX_VOLUME = "SFX_Volume";
|
||||
public const string AMBIENCE_VOLUME = "Ambience_Volume";
|
||||
public const string VOICE_VOLUME = "Voice_Volume";
|
||||
public const string UI_VOLUME = "UI_Volume";
|
||||
public const string MUSIC_VOLUME = "Music_Volume";
|
||||
public const string SFX_VOLUME = "SFX_Volume";
|
||||
public const string VOICE_VOLUME = "Voice_Volume";
|
||||
}
|
||||
|
||||
public static class AudioMixerGroups
|
||||
{
|
||||
public const string MASTER_GROUP = "Master";
|
||||
public const string MUSIC_GROUP = "Music";
|
||||
public const string SFX_GROUP = "SFX";
|
||||
public const string AMBIENCE_GROUP = "Ambience";
|
||||
public const string VOICE_GROUP = "Voice";
|
||||
public const string UI_GROUP = "UI";
|
||||
public const string MUSIC_GROUP = "Music";
|
||||
public const string SFX_GROUP = "SFX";
|
||||
public const string VOICE_GROUP = "Voice";
|
||||
}
|
||||
}
|
||||
@@ -3,62 +3,52 @@ using System.Collections.ObjectModel;
|
||||
|
||||
namespace BriarQueen.Data.Identifiers
|
||||
{
|
||||
public enum BookEntryID
|
||||
public enum DocumentEntryID
|
||||
{
|
||||
None = 0,
|
||||
WorkshopDiary = 1,
|
||||
LaxleyHouseBillOfSale = 2,
|
||||
GranddfatherClockPlaque = 3
|
||||
C1CarNewspaper,
|
||||
}
|
||||
|
||||
public enum ClueEntryID
|
||||
{
|
||||
None = 0,
|
||||
WorkshopBookshelfClue = 1,
|
||||
WorkshopRainbowClue = 2,
|
||||
PumphouseScratchedTable = 3,
|
||||
StreetGatePlaque = 4,
|
||||
GranddfatherClockPlaqueClue = 5
|
||||
AshwickMarketGate,
|
||||
}
|
||||
|
||||
public enum PhotoEntryID
|
||||
{
|
||||
None = 0,
|
||||
WorkshopFadedPhoto = 1
|
||||
}
|
||||
|
||||
public static class CodexEntryIDs
|
||||
{
|
||||
public static readonly IReadOnlyDictionary<BookEntryID, string> Books =
|
||||
new ReadOnlyDictionary<BookEntryID, string>(
|
||||
new Dictionary<BookEntryID, string>
|
||||
private const string DOCUMENT_PREFIX = "Codex:Document:";
|
||||
private const string CLUE_PREFIX = "Codex:Clue:";
|
||||
private const string PHOTO_PREFIX = "Codex:Photo:";
|
||||
|
||||
public static readonly IReadOnlyDictionary<DocumentEntryID, string> Documents =
|
||||
new ReadOnlyDictionary<DocumentEntryID, string>(
|
||||
new Dictionary<DocumentEntryID, string>
|
||||
{
|
||||
{ BookEntryID.WorkshopDiary, "BOOK_WorkshopDiary" },
|
||||
{ BookEntryID.LaxleyHouseBillOfSale, "BOOK_LaxleyHouseBillOfSale" },
|
||||
{ BookEntryID.GranddfatherClockPlaque, "BOOK_GranddfatherClockPlaque" },
|
||||
{ DocumentEntryID.C1CarNewspaper, GetDocumentIdentifier(DocumentEntryID.C1CarNewspaper) },
|
||||
});
|
||||
|
||||
public static readonly IReadOnlyDictionary<ClueEntryID, string> Clues =
|
||||
new ReadOnlyDictionary<ClueEntryID, string>(
|
||||
new Dictionary<ClueEntryID, string>
|
||||
{
|
||||
{ ClueEntryID.WorkshopBookshelfClue, "CLUE_WorkshopBookshelf" },
|
||||
{ ClueEntryID.WorkshopRainbowClue , "CLUE_WorkshopRainbow" },
|
||||
{ ClueEntryID.PumphouseScratchedTable, "CLUE_PumphouseScratchedTable" },
|
||||
{ ClueEntryID.StreetGatePlaque, "CLUE_StreetGatePlaque" },
|
||||
{ ClueEntryID.GranddfatherClockPlaqueClue, "CLUE_GranddfatherClockPlaque" },
|
||||
{ ClueEntryID.AshwickMarketGate, $"{PHOTO_PREFIX}:AshwickMarketGate" },
|
||||
});
|
||||
|
||||
public static readonly IReadOnlyDictionary<PhotoEntryID, string> Photos =
|
||||
new ReadOnlyDictionary<PhotoEntryID, string>(
|
||||
new Dictionary<PhotoEntryID, string>
|
||||
{
|
||||
{ PhotoEntryID.WorkshopFadedPhoto, "PHOTO_WorkshopFadedPhoto" }
|
||||
});
|
||||
|
||||
public static string Get(BookEntryID id)
|
||||
public static string Get(DocumentEntryID id)
|
||||
{
|
||||
return Books.TryGetValue(id, out var value) ? value : string.Empty;
|
||||
return Documents.TryGetValue(id, out var value) ? value : string.Empty;
|
||||
}
|
||||
|
||||
public static string Get(ClueEntryID id)
|
||||
@@ -70,5 +60,20 @@ namespace BriarQueen.Data.Identifiers
|
||||
{
|
||||
return Photos.TryGetValue(id, out var value) ? value : string.Empty;
|
||||
}
|
||||
|
||||
private static string GetDocumentIdentifier(DocumentEntryID id)
|
||||
{
|
||||
return $"{DOCUMENT_PREFIX}{id}";
|
||||
}
|
||||
|
||||
private static string GetClueIdentifier(ClueEntryID id)
|
||||
{
|
||||
return $"{CLUE_PREFIX}{id}";
|
||||
}
|
||||
|
||||
private static string GetPhotoIdentifier(PhotoEntryID id)
|
||||
{
|
||||
return $"{PHOTO_PREFIX}{id}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ namespace BriarQueen.Data.Identifiers
|
||||
public enum CodexType
|
||||
{
|
||||
None = 0,
|
||||
BookEntry = 1,
|
||||
DocumentEntry = 1,
|
||||
PuzzleClue = 2,
|
||||
Photo = 3
|
||||
}
|
||||
|
||||
@@ -8,53 +8,30 @@ namespace BriarQueen.Data.Identifiers
|
||||
None = 0,
|
||||
EmptyHands = 1,
|
||||
CantUseItem = 2,
|
||||
RustyKnife = 3,
|
||||
SomethingMissing = 4,
|
||||
PliersSnapped = 5,
|
||||
CarefulInteract = 6,
|
||||
RagFallsApart = 7,
|
||||
LooksImportant = 8,
|
||||
WrongTool = 9,
|
||||
CollectEndlessGoblets = 10,
|
||||
SomethingMissing = 3,
|
||||
CarefulInteract = 4,
|
||||
LooksImportant = 5,
|
||||
WrongTool = 6,
|
||||
CodexLocked = 7
|
||||
}
|
||||
|
||||
public enum LevelInteractKey
|
||||
{
|
||||
None = 0,
|
||||
WaterValve = 1,
|
||||
ClearVinesOutside = 2,
|
||||
PumphouseChain = 3,
|
||||
CutVines = 4,
|
||||
WorkshopLockedSafe = 5,
|
||||
UnlockedPumphouse = 6,
|
||||
}
|
||||
|
||||
public enum EnvironmentInteractKey
|
||||
{
|
||||
None = 0,
|
||||
BrokenLantern = 1,
|
||||
WorkshopWriting = 2,
|
||||
UseGrindstone = 3,
|
||||
WorkshopBookDisintegrating = 4,
|
||||
UsingKnife = 5,
|
||||
AlreadySharpened = 6,
|
||||
Locked = 7,
|
||||
CantGoThere = 8,
|
||||
DirtyWindow = 9,
|
||||
WorkshopBagNoItems = 10,
|
||||
FindCandle = 11,
|
||||
DoesntBelong = 12,
|
||||
SharpGlass = 13,
|
||||
FreshAndCoolWater = 14,
|
||||
WorkshopBooks = 15,
|
||||
PumpTurnOn = 16,
|
||||
FireHot = 17,
|
||||
ExtinguishFire = 18,
|
||||
CauldronBoiledAway = 19,
|
||||
LaxleyHouseBrokenClock = 20,
|
||||
LaxleyGrandfatherClockMissingBothHands = 21,
|
||||
LaxleyGrandfatherClockMissingHourHand = 22,
|
||||
LaxleyGrandfatherClockMissingMinuteHand = 23,
|
||||
Locked = 1,
|
||||
CantGoThere = 2,
|
||||
DoesntBelong = 3,
|
||||
FireHot = 4,
|
||||
AshwickHallowSign = 5,
|
||||
AshwickRidgewayStatue = 6,
|
||||
AshwickBlockedRidgwayRoad = 7,
|
||||
AshwickRidgewaySkeleton = 8,
|
||||
AskwickMarketplaceSign = 9,
|
||||
}
|
||||
|
||||
public enum UIInteractKey
|
||||
@@ -71,14 +48,11 @@ namespace BriarQueen.Data.Identifiers
|
||||
{
|
||||
{ ItemInteractKey.EmptyHands, "My hands are too full for that." },
|
||||
{ ItemInteractKey.CantUseItem, "That won’t work here." },
|
||||
{ ItemInteractKey.RustyKnife, "Too dull to be of any use." },
|
||||
{ ItemInteractKey.SomethingMissing, "Something isn’t right." },
|
||||
{ ItemInteractKey.PliersSnapped, "The pliers snap. That’s the end of them." },
|
||||
{ ItemInteractKey.CarefulInteract, "I should take care with this." },
|
||||
{ ItemInteractKey.RagFallsApart, "It fell apart in my hands." },
|
||||
{ ItemInteractKey.LooksImportant, "This feels important." },
|
||||
{ ItemInteractKey.WrongTool, "This isn’t the right tool." },
|
||||
{ ItemInteractKey.CollectEndlessGoblets, "Faint symbols coil across the goblet’s surface." },
|
||||
{ ItemInteractKey.CodexLocked, "I have nowhere to put that." },
|
||||
|
||||
});
|
||||
|
||||
@@ -86,41 +60,19 @@ namespace BriarQueen.Data.Identifiers
|
||||
new ReadOnlyDictionary<LevelInteractKey, string>(
|
||||
new Dictionary<LevelInteractKey, string>
|
||||
{
|
||||
{ LevelInteractKey.WaterValve, "The water is already flowing." },
|
||||
{ LevelInteractKey.ClearVinesOutside, "The vines still block the way outside." },
|
||||
{ LevelInteractKey.PumphouseChain, "It’s locked by something more than rust." },
|
||||
{ LevelInteractKey.CutVines, "These won’t give way by hand." },
|
||||
{ LevelInteractKey.WorkshopLockedSafe, "Sealed tight." },
|
||||
{ LevelInteractKey.UnlockedPumphouse, "The lock gives with a dull click." },
|
||||
});
|
||||
|
||||
public static readonly IReadOnlyDictionary<EnvironmentInteractKey, string> EnvironmentInteractions =
|
||||
new ReadOnlyDictionary<EnvironmentInteractKey, string>(
|
||||
new Dictionary<EnvironmentInteractKey, string>
|
||||
{
|
||||
{ EnvironmentInteractKey.BrokenLantern, "Beyond repair." },
|
||||
{ EnvironmentInteractKey.WorkshopWriting, "At least it isn’t blood." },
|
||||
{ EnvironmentInteractKey.UseGrindstone, "This could still sharpen an edge." },
|
||||
{ EnvironmentInteractKey.WorkshopBookDisintegrating, "It crumbles at a touch." },
|
||||
{ EnvironmentInteractKey.UsingKnife, "Careful… one slip." },
|
||||
{ EnvironmentInteractKey.AlreadySharpened, "That edge will do." },
|
||||
{ EnvironmentInteractKey.Locked, "Locked." },
|
||||
{ EnvironmentInteractKey.Locked, "It's locked." },
|
||||
{ EnvironmentInteractKey.CantGoThere, "No way through." },
|
||||
{ EnvironmentInteractKey.DirtyWindow, "Nothing visible through the grime." },
|
||||
{ EnvironmentInteractKey.WorkshopBagNoItems, "Picked clean." },
|
||||
{ EnvironmentInteractKey.FindCandle, "I need the candle that goes here." },
|
||||
{ EnvironmentInteractKey.DoesntBelong, "This feels out of place." },
|
||||
{ EnvironmentInteractKey.SharpGlass, "Still sharp enough to bite." },
|
||||
{ EnvironmentInteractKey.FreshAndCoolWater, "Cool. Clean. Unexpected." },
|
||||
{ EnvironmentInteractKey.WorkshopBooks, "Time hasn’t been kind to these." },
|
||||
{ EnvironmentInteractKey.PumpTurnOn, "The pumps shudder back to life." },
|
||||
{ EnvironmentInteractKey.FireHot, "Too hot to get close." },
|
||||
{ EnvironmentInteractKey.CauldronBoiledAway, "Whatever it held is long gone." },
|
||||
{ EnvironmentInteractKey.ExtinguishFire, "The symbols begin to glow as the goblet fills." },
|
||||
{ EnvironmentInteractKey.LaxleyHouseBrokenClock, "The clock stopped at three-thirty-three." },
|
||||
{ EnvironmentInteractKey.LaxleyGrandfatherClockMissingBothHands, "It's missing both hands. "},
|
||||
{ EnvironmentInteractKey.LaxleyGrandfatherClockMissingHourHand, "The hour hand is missing."},
|
||||
{ EnvironmentInteractKey.LaxleyGrandfatherClockMissingMinuteHand, "The minute hand is missing."},
|
||||
{ EnvironmentInteractKey.AshwickHallowSign, "Ashwick Hallow… Even the name feels like a warning."},
|
||||
{ EnvironmentInteractKey.AshwickRidgewayStatue, "Lovely sculpture. Mildly terrifying, but lovely."},
|
||||
{ EnvironmentInteractKey.AshwickBlockedRidgwayRoad, "Right. Closed road. Of course it is."}
|
||||
});
|
||||
|
||||
public static readonly IReadOnlyDictionary<UIInteractKey, string> UIInteractions =
|
||||
|
||||
@@ -6,71 +6,20 @@ namespace BriarQueen.Data.Identifiers
|
||||
public enum ItemKey
|
||||
{
|
||||
None = 0,
|
||||
RustedKnife = 1,
|
||||
SharpenedKnife = 2,
|
||||
EmeraldAmulet = 3,
|
||||
DustyMirror = 4,
|
||||
SmallRag = 5,
|
||||
GreenCandle = 6,
|
||||
IndigoCandle = 7,
|
||||
DirtyMagnifyingGlass = 8,
|
||||
PumphouseKey = 9,
|
||||
RedCandle = 10,
|
||||
OrangeCandle = 11,
|
||||
YellowCandle = 12,
|
||||
BlueCandle = 13,
|
||||
VioletCandle = 14,
|
||||
Pliers = 15,
|
||||
Emerald = 16,
|
||||
Sapphire = 17,
|
||||
Ruby = 18,
|
||||
RubyRing = 19,
|
||||
SilverCoin = 20,
|
||||
GoldCoin = 21,
|
||||
GrindstoneAxlePin = 22,
|
||||
Diamond = 23,
|
||||
DiamondTiara = 24,
|
||||
DustySapphire = 25,
|
||||
TornPage1 = 26,
|
||||
TornPage2 = 27,
|
||||
TornPage3 = 28,
|
||||
TornPage4 = 29,
|
||||
TornPage5 = 30,
|
||||
IncompleteBook = 31,
|
||||
CompleteBook = 32,
|
||||
Stamp = 33,
|
||||
LaxleyClockHourHand = 34,
|
||||
LaxleyClockMinuteHand = 35,
|
||||
S_Key,
|
||||
DirtyTeddyBear,
|
||||
GoldAmulet,
|
||||
}
|
||||
|
||||
public enum EnvironmentKey
|
||||
{
|
||||
None = 0,
|
||||
ChainLock = 1,
|
||||
FountainVines = 2,
|
||||
GrindingStone = 3,
|
||||
WorkshopWindow = 4,
|
||||
WorkshopDamagedBook = 5,
|
||||
WorkshopBookSlot = 6,
|
||||
WorkshopDiary = 7,
|
||||
PumphouseWaterValve = 8,
|
||||
WorkshopFadedPhoto = 9,
|
||||
WorkshopDownstairsLight = 10,
|
||||
WorkshopBrokenLantern = 11,
|
||||
WorkshopWriting = 12,
|
||||
StreetVines = 13,
|
||||
GrandfatherClockPlaque = 14,
|
||||
C1CarNewspaper,
|
||||
}
|
||||
|
||||
public enum PuzzleSlotKey
|
||||
{
|
||||
None = 0,
|
||||
WorkshopCandleSlot = 1,
|
||||
WorkshopPuzzleBoxSlot = 2,
|
||||
FountainGemSlot = 3,
|
||||
FireplaceLockboxSlot = 4,
|
||||
GrandfatherClockFace = 5,
|
||||
GrandfatherClockHand = 6,
|
||||
}
|
||||
|
||||
public static class ItemIDs
|
||||
@@ -79,73 +28,21 @@ namespace BriarQueen.Data.Identifiers
|
||||
new ReadOnlyDictionary<PuzzleSlotKey, string>(
|
||||
new Dictionary<PuzzleSlotKey, string>
|
||||
{
|
||||
{ PuzzleSlotKey.WorkshopCandleSlot, "PUZ_WorkshopCandleSlot" },
|
||||
{ PuzzleSlotKey.WorkshopPuzzleBoxSlot, "PUZ_WorkshopPuzzleBoxSlot" },
|
||||
{ PuzzleSlotKey.FountainGemSlot, "PUZ_FountainGemSlot" },
|
||||
{ PuzzleSlotKey.FireplaceLockboxSlot, "PUZ_FireplaceLockboxSlot" },
|
||||
{ PuzzleSlotKey.GrandfatherClockFace, "PUZ_GrandfatherClockFace" },
|
||||
{ PuzzleSlotKey.GrandfatherClockHand, "PUZ_GrandfatherClockHand" },
|
||||
});
|
||||
|
||||
public static readonly IReadOnlyDictionary<EnvironmentKey, string> Environment =
|
||||
new ReadOnlyDictionary<EnvironmentKey, string>(
|
||||
new Dictionary<EnvironmentKey, string>
|
||||
{
|
||||
{ EnvironmentKey.ChainLock, "ENV_ChainLock" },
|
||||
{ EnvironmentKey.FountainVines, "ENV_FountainVines" },
|
||||
{ EnvironmentKey.GrindingStone, "ENV_GrindingStone" },
|
||||
{ EnvironmentKey.WorkshopWindow, "ENV_WorkshopWindow" },
|
||||
{ EnvironmentKey.WorkshopDamagedBook, "ENV_WorkshopDamagedBook" },
|
||||
{ EnvironmentKey.WorkshopBookSlot, "ENV_WorkshopBookSlot" },
|
||||
{ EnvironmentKey.WorkshopDiary, "ENV_WorkshopDiary" },
|
||||
{ EnvironmentKey.PumphouseWaterValve, "ENV_PumphouseWaterValve" },
|
||||
{ EnvironmentKey.WorkshopFadedPhoto, "ENV_WorkshopFadedPhoto" },
|
||||
{ EnvironmentKey.WorkshopDownstairsLight, "ENV_WorkshopDownstairsLight" },
|
||||
{ EnvironmentKey.WorkshopBrokenLantern, "ENV_WorkshopBrokenLantern" },
|
||||
{ EnvironmentKey.WorkshopWriting, "ENV_WorkshopWriting" },
|
||||
{ EnvironmentKey.StreetVines, "ENV_StreetVines" },
|
||||
{ EnvironmentKey.GrandfatherClockPlaque, "ENV_GrandfatherClockPlaque" },
|
||||
});
|
||||
|
||||
public static readonly IReadOnlyDictionary<ItemKey, string> Pickups =
|
||||
new ReadOnlyDictionary<ItemKey, string>(
|
||||
new Dictionary<ItemKey, string>
|
||||
{
|
||||
{ ItemKey.RustedKnife, "01_RustedKnife" },
|
||||
{ ItemKey.SharpenedKnife, "02_SharpenedKnife" },
|
||||
{ ItemKey.EmeraldAmulet, "03_EmeraldAmulet" },
|
||||
{ ItemKey.DustyMirror, "04_DustyMirror" },
|
||||
{ ItemKey.SmallRag, "05_SmallRag" },
|
||||
{ ItemKey.GreenCandle, "06_GreenCandle" },
|
||||
{ ItemKey.IndigoCandle, "07_IndigoCandle" },
|
||||
{ ItemKey.DirtyMagnifyingGlass, "08_DirtyMagnifyingGlass" },
|
||||
{ ItemKey.PumphouseKey, "09_PumphouseKey" },
|
||||
{ ItemKey.RedCandle, "10_RedCandle" },
|
||||
{ ItemKey.OrangeCandle, "11_OrangeCandle" },
|
||||
{ ItemKey.YellowCandle, "12_YellowCandle" },
|
||||
{ ItemKey.BlueCandle, "13_BlueCandle" },
|
||||
{ ItemKey.VioletCandle, "14_VioletCandle" },
|
||||
{ ItemKey.Pliers, "15_Pliers" },
|
||||
{ ItemKey.Emerald, "16_Emerald" },
|
||||
{ ItemKey.Sapphire, "17_Sapphire" },
|
||||
{ ItemKey.Ruby, "18_Ruby" },
|
||||
{ ItemKey.RubyRing, "19_RubyRing" },
|
||||
{ ItemKey.SilverCoin, "20_SilverCoin" },
|
||||
{ ItemKey.GoldCoin, "21_GoldCoin" },
|
||||
{ ItemKey.GrindstoneAxlePin, "22_GrindstoneAxlePin" },
|
||||
{ ItemKey.Diamond, "23_Diamond" },
|
||||
{ ItemKey.DiamondTiara, "24_DiamondTiara" },
|
||||
{ ItemKey.DustySapphire, "25_DustySapphire" },
|
||||
{ ItemKey.TornPage1, "26_TornPage1" },
|
||||
{ ItemKey.TornPage2, "27_TornPage2" },
|
||||
{ ItemKey.TornPage3, "28_TornPage3" },
|
||||
{ ItemKey.TornPage4, "29_TornPage4" },
|
||||
{ ItemKey.TornPage5, "30_TornPage5" },
|
||||
{ ItemKey.IncompleteBook, "31_IncompleteBook" },
|
||||
{ ItemKey.CompleteBook, "32_CompleteBook" },
|
||||
{ ItemKey.Stamp, "33_Stamp" },
|
||||
{ ItemKey.LaxleyClockHourHand, "34_LaxleyClockHourHand" },
|
||||
{ ItemKey.LaxleyClockMinuteHand, "35_LaxleyClockMinuteHand" },
|
||||
{ ItemKey.S_Key, "Item:Pickup:S_Key" },
|
||||
{ ItemKey.DirtyTeddyBear, "Item:Pickup:DirtyTeddyBear" },
|
||||
{ ItemKey.GoldAmulet, "Item:Pickup:GoldAmulet" },
|
||||
});
|
||||
|
||||
public static string Get(ItemKey key)
|
||||
@@ -183,4 +80,4 @@ namespace BriarQueen.Data.Identifiers
|
||||
return Pickups.Values;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,7 @@ namespace BriarQueen.Data.Identifiers
|
||||
public enum Location
|
||||
{
|
||||
None = 0,
|
||||
Village = 1,
|
||||
Workshop = 2,
|
||||
LaxleyHouse = 3,
|
||||
Pumphouse = 4,
|
||||
TheOldRidgeway,
|
||||
AshwickHallow,
|
||||
}
|
||||
}
|
||||
@@ -4,22 +4,14 @@ namespace BriarQueen.Data.Identifiers
|
||||
{
|
||||
public enum PuzzleKey
|
||||
{
|
||||
WorkshopCandlePuzzle,
|
||||
WorkshopPuzzleBox,
|
||||
FountainGemPuzzle,
|
||||
FireplaceLockboxPuzzle,
|
||||
LaxleyClock
|
||||
AshwickMarketGate,
|
||||
}
|
||||
|
||||
public static class PuzzleIdentifiers
|
||||
{
|
||||
public static readonly Dictionary<PuzzleKey, string> AllPuzzles = new()
|
||||
{
|
||||
{ PuzzleKey.WorkshopCandlePuzzle, "CH1:Puzzle:WorkshopCandles" },
|
||||
{ PuzzleKey.WorkshopPuzzleBox, "CH1:Puzzle:WorkshopBox" },
|
||||
{ PuzzleKey.FountainGemPuzzle , "CH1:Puzzle:FountainGems" },
|
||||
{ PuzzleKey.FireplaceLockboxPuzzle, "CH1:Puzzle:FireplaceLockboxPuzzle" },
|
||||
{ PuzzleKey.LaxleyClock, "CH1:Puzzle:LaxleyClock" },
|
||||
{ PuzzleKey.AshwickMarketGate, "CH1:Puzzle:AshwickMarketGate" },
|
||||
};
|
||||
|
||||
// Optional helper to get all puzzle IDs
|
||||
|
||||
@@ -17,8 +17,8 @@ namespace BriarQueen.Data.Identifiers
|
||||
new ReadOnlyDictionary<SubtitleKey, string>(
|
||||
new Dictionary<SubtitleKey, string>
|
||||
{
|
||||
// { SubtitleKey.IntroLine, "SUB_IntroLine" },
|
||||
// { SubtitleKey.TutorialTip, "SUB_TutorialTip" }
|
||||
// { SubtitleKey.IntroLine, "Subtitle:IntroLine" },
|
||||
// { SubtitleKey.TutorialTip, "Subtitle:TutorialTip" }
|
||||
});
|
||||
|
||||
public static string Get(SubtitleKey key)
|
||||
@@ -31,4 +31,4 @@ namespace BriarQueen.Data.Identifiers
|
||||
return Subtitles.Values;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@ namespace BriarQueen.Data.Identifiers
|
||||
None = 0,
|
||||
[DisplayName("Sharpened Knife")]
|
||||
Knife = 1,
|
||||
[DisplayName("Water Goblet")]
|
||||
EndlessGoblet,
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -6,11 +6,11 @@ namespace BriarQueen.Data.Identifiers
|
||||
{
|
||||
ReturnToPreviousLevel,
|
||||
UsingItemsTogether,
|
||||
HideHUD,
|
||||
HideHUDKeyboard,
|
||||
ExitItems,
|
||||
MultipleUseItems,
|
||||
DarkRooms,
|
||||
Codex,
|
||||
CodexKeyboard,
|
||||
HiddenItems,
|
||||
ResetPuzzles,
|
||||
Tools,
|
||||
@@ -32,12 +32,12 @@ namespace BriarQueen.Data.Identifiers
|
||||
"Select one item, then click another to use them together."
|
||||
},
|
||||
{
|
||||
TutorialPopupID.HideHUD,
|
||||
"Press 'H' to hide the HUD."
|
||||
TutorialPopupID.HideHUDKeyboard,
|
||||
"Press '{Hide_HUD}' to hide the HUD."
|
||||
},
|
||||
{
|
||||
TutorialPopupID.ExitItems,
|
||||
"Right-click to exit the current interaction."
|
||||
"Press '{Right_Click}' to exit the current interaction."
|
||||
},
|
||||
{
|
||||
TutorialPopupID.MultipleUseItems,
|
||||
@@ -48,8 +48,8 @@ namespace BriarQueen.Data.Identifiers
|
||||
"Dark rooms can hide important details. Use light to reveal them."
|
||||
},
|
||||
{
|
||||
TutorialPopupID.Codex,
|
||||
"The Codex stores information you've discovered. Press 'C' to open it."
|
||||
TutorialPopupID.CodexKeyboard,
|
||||
"The Codex is used to collect any documents you encounter. Press '{Codex}' to open it."
|
||||
},
|
||||
{
|
||||
TutorialPopupID.HiddenItems,
|
||||
@@ -61,25 +61,22 @@ namespace BriarQueen.Data.Identifiers
|
||||
},
|
||||
{
|
||||
TutorialPopupID.Tools,
|
||||
"You'll find tools as you explore. Try them on different objects. Press 'Y' to view your tools."
|
||||
"You'll find tools as you explore. Try them on different objects. Press '{Show_Tools}' to view your tools."
|
||||
},
|
||||
{
|
||||
TutorialPopupID.ItemsAway,
|
||||
"Right-click to put away the current item."
|
||||
"Press '{Right_Click}' to put away the current item."
|
||||
},
|
||||
{
|
||||
TutorialPopupID.ItemCycling,
|
||||
"Press '[' or ']' to cycle through your backpack."
|
||||
"Press '{Previous_Item}' or '{Next_Item}' to cycle through your backpack."
|
||||
},
|
||||
{
|
||||
TutorialPopupID.ToolCycling,
|
||||
"Press 'Q' or 'E' to cycle through your tools."
|
||||
"Press '{Previous_Tool}' or '{Next_Tool}' to cycle through your tools."
|
||||
}
|
||||
};
|
||||
|
||||
public static IEnumerable<string> GetAllTexts()
|
||||
{
|
||||
return AllPopups.Values;
|
||||
}
|
||||
public static IEnumerable<string> GetAllTexts() => AllPopups.Values;
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ namespace BriarQueen.Editor.Drawers.Registries
|
||||
|
||||
switch (entry.EntryType)
|
||||
{
|
||||
case CodexType.BookEntry:
|
||||
case CodexType.DocumentEntry:
|
||||
books.Add(entry);
|
||||
break;
|
||||
|
||||
|
||||
@@ -435,17 +435,17 @@ namespace BriarQueen.Editor.Windows
|
||||
|
||||
if (_createBookEntries)
|
||||
{
|
||||
ProcessEnum<BookEntryID, CodexEntrySo>(
|
||||
ProcessEnum<DocumentEntryID, CodexEntrySo>(
|
||||
rootFolder: _codexRootFolder,
|
||||
folderName: "Books",
|
||||
noneValue: BookEntryID.None,
|
||||
folderName: "Documents",
|
||||
noneValue: DocumentEntryID.None,
|
||||
assetExtensionName: key => key.ToString(),
|
||||
loadExistingAsset: path => AssetDatabase.LoadAssetAtPath<CodexEntrySo>(path),
|
||||
createAsset: () => CreateInstance<CodexEntrySo>(),
|
||||
applyValues: (so, key) =>
|
||||
{
|
||||
SetEnumField(so, "_codexType", (int)CodexType.BookEntry);
|
||||
SetEnumField(so, "_bookEntryID", (int)key);
|
||||
SetEnumField(so, "_codexType", (int)CodexType.DocumentEntry);
|
||||
SetEnumField(so, "_documentEntryID", (int)key);
|
||||
SetEnumField(so, "_clueEntryID", (int)ClueEntryID.None);
|
||||
SetEnumField(so, "_photoEntryID", (int)PhotoEntryID.None);
|
||||
SetStringFieldIfEmpty(so, "_title", ObjectNames.NicifyVariableName(key.ToString()));
|
||||
@@ -467,7 +467,7 @@ namespace BriarQueen.Editor.Windows
|
||||
applyValues: (so, key) =>
|
||||
{
|
||||
SetEnumField(so, "_codexType", (int)CodexType.PuzzleClue);
|
||||
SetEnumField(so, "_bookEntryID", (int)BookEntryID.None);
|
||||
SetEnumField(so, "_documentEntryID", (int)DocumentEntryID.None);
|
||||
SetEnumField(so, "_clueEntryID", (int)key);
|
||||
SetEnumField(so, "_photoEntryID", (int)PhotoEntryID.None);
|
||||
SetStringFieldIfEmpty(so, "_title", ObjectNames.NicifyVariableName(key.ToString()));
|
||||
@@ -489,7 +489,7 @@ namespace BriarQueen.Editor.Windows
|
||||
applyValues: (so, key) =>
|
||||
{
|
||||
SetEnumField(so, "_codexType", (int)CodexType.Photo);
|
||||
SetEnumField(so, "_bookEntryID", (int)BookEntryID.None);
|
||||
SetEnumField(so, "_documentEntryID", (int)DocumentEntryID.None);
|
||||
SetEnumField(so, "_clueEntryID", (int)ClueEntryID.None);
|
||||
SetEnumField(so, "_photoEntryID", (int)key);
|
||||
SetStringFieldIfEmpty(so, "_title", ObjectNames.NicifyVariableName(key.ToString()));
|
||||
@@ -652,4 +652,4 @@ namespace BriarQueen.Editor.Windows
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
"GUID:593a5b492d29ac6448b1ebf7f035ef33",
|
||||
"GUID:84651a3751eca9349aac36a66bba901b",
|
||||
"GUID:75469ad4d38634e559750d17036d5f7c",
|
||||
"GUID:776d03a35f1b52c4a9aed9f56d7b4229"
|
||||
"GUID:776d03a35f1b52c4a9aed9f56d7b4229",
|
||||
"GUID:6055be8ebefd69e48b49212b09b47b2f"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
|
||||
8
Assets/Scripts/Framework/Effects.meta
Normal file
8
Assets/Scripts/Framework/Effects.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eca349fcd212e4ac18902c56b71847e0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
499
Assets/Scripts/Framework/Effects/UIDissolveImage.cs
Normal file
499
Assets/Scripts/Framework/Effects/UIDissolveImage.cs
Normal file
@@ -0,0 +1,499 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using BriarQueen.Framework.Services.Destruction;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using PrimeTween;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using VContainer;
|
||||
|
||||
namespace BriarQueen.Framework.Effects
|
||||
{
|
||||
[ExecuteAlways]
|
||||
public class UIDissolveImage : MonoBehaviour
|
||||
{
|
||||
private static readonly int _dissolveAmountId = Shader.PropertyToID("_DissolveAmount");
|
||||
private static readonly int _reverseDirectionId = Shader.PropertyToID("_ReverseDirection");
|
||||
|
||||
[Header("References")]
|
||||
[SerializeField]
|
||||
private Image _image;
|
||||
|
||||
[SerializeField]
|
||||
private CanvasGroup _canvasGroup;
|
||||
|
||||
[SerializeField]
|
||||
private Material _dissolveMaterialTemplate;
|
||||
|
||||
[Header("Targeting")]
|
||||
[SerializeField]
|
||||
private bool _dissolveChildGraphics;
|
||||
|
||||
[SerializeField]
|
||||
private bool _includeInactiveChildGraphics = true;
|
||||
|
||||
[SerializeField]
|
||||
private bool _includeTextGraphics;
|
||||
|
||||
[Header("Tween")]
|
||||
[SerializeField]
|
||||
private float _duration = 0.75f;
|
||||
|
||||
[SerializeField]
|
||||
private Ease _ease = Ease.InOutSine;
|
||||
|
||||
[SerializeField]
|
||||
private bool _useUnscaledTime = true;
|
||||
|
||||
[Header("Direction")]
|
||||
[SerializeField]
|
||||
private bool _reverseDirection;
|
||||
|
||||
[Header("Destruction")]
|
||||
[SerializeField]
|
||||
private bool _destroyWhenFullyDissolved = true;
|
||||
|
||||
[Header("Editor Preview")]
|
||||
[SerializeField]
|
||||
private bool _previewInEditMode;
|
||||
|
||||
[SerializeField]
|
||||
[Range(0f, 1f)]
|
||||
private float _previewDissolveAmount;
|
||||
|
||||
private readonly List<Graphic> _targetGraphics = new();
|
||||
private readonly List<Material> _originalMaterials = new();
|
||||
|
||||
private CancellationTokenSource _dissolveCts;
|
||||
private DestructionService _destructionService;
|
||||
private bool _hasOriginalMaterials;
|
||||
private bool _isPreviewMaterial;
|
||||
private Material _runtimeMaterial;
|
||||
private Sequence _dissolveSequence;
|
||||
|
||||
public float DissolveAmount
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_runtimeMaterial == null)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
return _runtimeMaterial.GetFloat(_dissolveAmountId);
|
||||
}
|
||||
set => SetDissolveAmount(value);
|
||||
}
|
||||
|
||||
[Inject]
|
||||
public void Construct(DestructionService destructionService)
|
||||
{
|
||||
_destructionService = destructionService;
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
ResolveReferences();
|
||||
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
CreateRuntimeMaterial(false);
|
||||
SetDissolveAmount(0f);
|
||||
return;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
RefreshEditModePreview();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
RefreshEditModePreview();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
RestoreEditModeMaterial();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
CancelDissolve();
|
||||
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
DestroyRuntimeMaterial(false);
|
||||
return;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
RestoreEditModeMaterial();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
ResolveReferences();
|
||||
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
SetReverseDirection(_reverseDirection);
|
||||
return;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
RestoreEditModeMaterial();
|
||||
RefreshEditModePreview();
|
||||
#endif
|
||||
}
|
||||
|
||||
public UniTask DissolveIn()
|
||||
{
|
||||
gameObject.SetActive(true);
|
||||
return TweenDissolve(1f, 0f, _duration, false);
|
||||
}
|
||||
|
||||
public UniTask DissolveOut()
|
||||
{
|
||||
return TweenDissolve(0f, 1f, _duration, _destroyWhenFullyDissolved);
|
||||
}
|
||||
|
||||
public UniTask DissolveOut(bool destroyWhenComplete)
|
||||
{
|
||||
return TweenDissolve(0f, 1f, _duration, destroyWhenComplete);
|
||||
}
|
||||
|
||||
public UniTask DissolveOutAndDestroy()
|
||||
{
|
||||
return TweenDissolve(0f, 1f, _duration, true);
|
||||
}
|
||||
|
||||
public UniTask TweenDissolve(float from, float to, float duration)
|
||||
{
|
||||
return TweenDissolve(from, to, duration, false);
|
||||
}
|
||||
|
||||
public async UniTask TweenDissolve(float from, float to, float duration, bool destroyWhenComplete)
|
||||
{
|
||||
if (_runtimeMaterial == null)
|
||||
{
|
||||
CreateRuntimeMaterial(false);
|
||||
}
|
||||
|
||||
CancelDissolve();
|
||||
|
||||
_dissolveCts = new CancellationTokenSource();
|
||||
|
||||
SetDissolveAmount(from);
|
||||
|
||||
_dissolveSequence = Sequence.Create(useUnscaledTime: _useUnscaledTime)
|
||||
.Group(Tween.Custom(
|
||||
from,
|
||||
to,
|
||||
Mathf.Max(0f, duration),
|
||||
SetDissolveAmount,
|
||||
_ease,
|
||||
useUnscaledTime: _useUnscaledTime));
|
||||
|
||||
try
|
||||
{
|
||||
await _dissolveSequence.ToUniTask(cancellationToken: _dissolveCts.Token);
|
||||
SetDissolveAmount(to);
|
||||
|
||||
if (destroyWhenComplete && Mathf.Approximately(to, 1f))
|
||||
{
|
||||
await DestroyAfterDissolve();
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Interrupted by another dissolve request or object destruction.
|
||||
}
|
||||
finally
|
||||
{
|
||||
_dissolveSequence = default;
|
||||
|
||||
if (_dissolveCts != null)
|
||||
{
|
||||
_dissolveCts.Dispose();
|
||||
_dissolveCts = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetDissolveAmount(float amount)
|
||||
{
|
||||
if (_runtimeMaterial == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_runtimeMaterial.SetFloat(_dissolveAmountId, Mathf.Clamp01(amount));
|
||||
SetTargetsMaterialDirty();
|
||||
}
|
||||
|
||||
public void SetReverseDirection(bool reverseDirection)
|
||||
{
|
||||
_reverseDirection = reverseDirection;
|
||||
|
||||
if (_runtimeMaterial == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_runtimeMaterial.SetFloat(_reverseDirectionId, _reverseDirection ? 1f : 0f);
|
||||
SetTargetsMaterialDirty();
|
||||
}
|
||||
|
||||
public void CancelDissolve()
|
||||
{
|
||||
if (_dissolveSequence.isAlive)
|
||||
{
|
||||
_dissolveSequence.Stop();
|
||||
_dissolveSequence = default;
|
||||
}
|
||||
|
||||
if (_dissolveCts != null)
|
||||
{
|
||||
_dissolveCts.Cancel();
|
||||
_dissolveCts.Dispose();
|
||||
_dissolveCts = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async UniTask DestroyAfterDissolve()
|
||||
{
|
||||
if (_destructionService != null)
|
||||
{
|
||||
await _destructionService.Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.LogWarning($"[{nameof(UIDissolveImage)}] Missing {nameof(DestructionService)}. Destroying directly.");
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
private void ResolveReferences()
|
||||
{
|
||||
if (_image == null)
|
||||
{
|
||||
_image = GetComponent<Image>();
|
||||
}
|
||||
|
||||
if (_canvasGroup == null)
|
||||
{
|
||||
_canvasGroup = GetComponent<CanvasGroup>();
|
||||
}
|
||||
}
|
||||
|
||||
private void ResolveTargetGraphics()
|
||||
{
|
||||
ResolveReferences();
|
||||
_targetGraphics.Clear();
|
||||
|
||||
if (_dissolveChildGraphics)
|
||||
{
|
||||
var root = _canvasGroup != null ? _canvasGroup.transform : transform;
|
||||
var graphics = root.GetComponentsInChildren<Graphic>(_includeInactiveChildGraphics);
|
||||
|
||||
foreach (var graphic in graphics)
|
||||
{
|
||||
if (IsValidTargetGraphic(graphic))
|
||||
{
|
||||
_targetGraphics.Add(graphic);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_image != null)
|
||||
{
|
||||
_targetGraphics.Add(_image);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsValidTargetGraphic(Graphic graphic)
|
||||
{
|
||||
if (graphic == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// TMP needs a TMP-compatible dissolve shader; replacing its SDF material breaks text rendering.
|
||||
if (!_includeTextGraphics && graphic is TMP_Text)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void CreateRuntimeMaterial(bool isPreviewMaterial)
|
||||
{
|
||||
ResolveTargetGraphics();
|
||||
|
||||
if (_targetGraphics.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var sourceMaterial = _dissolveMaterialTemplate != null
|
||||
? _dissolveMaterialTemplate
|
||||
: _targetGraphics[0].material;
|
||||
|
||||
if (sourceMaterial == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_runtimeMaterial != null)
|
||||
{
|
||||
ApplyRuntimeMaterialToTargets();
|
||||
return;
|
||||
}
|
||||
|
||||
SaveOriginalMaterials();
|
||||
|
||||
_runtimeMaterial = Instantiate(sourceMaterial);
|
||||
_runtimeMaterial.name = isPreviewMaterial
|
||||
? $"{nameof(UIDissolveImage)} Preview Material"
|
||||
: $"{nameof(UIDissolveImage)} Runtime Material";
|
||||
_isPreviewMaterial = isPreviewMaterial;
|
||||
|
||||
if (isPreviewMaterial)
|
||||
{
|
||||
_runtimeMaterial.hideFlags = HideFlags.DontSaveInEditor;
|
||||
}
|
||||
|
||||
ApplyRuntimeMaterialToTargets();
|
||||
SetReverseDirection(_reverseDirection);
|
||||
}
|
||||
|
||||
private void ApplyRuntimeMaterialToTargets()
|
||||
{
|
||||
foreach (var graphic in _targetGraphics)
|
||||
{
|
||||
if (graphic == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
graphic.material = _runtimeMaterial;
|
||||
graphic.SetMaterialDirty();
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveOriginalMaterials()
|
||||
{
|
||||
_originalMaterials.Clear();
|
||||
|
||||
foreach (var graphic in _targetGraphics)
|
||||
{
|
||||
_originalMaterials.Add(graphic != null ? graphic.material : null);
|
||||
}
|
||||
|
||||
_hasOriginalMaterials = true;
|
||||
}
|
||||
|
||||
private void RestoreOriginalMaterials()
|
||||
{
|
||||
if (!_hasOriginalMaterials)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var count = Mathf.Min(_targetGraphics.Count, _originalMaterials.Count);
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var graphic = _targetGraphics[i];
|
||||
if (graphic == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
graphic.material = _originalMaterials[i];
|
||||
graphic.SetMaterialDirty();
|
||||
}
|
||||
|
||||
_originalMaterials.Clear();
|
||||
_hasOriginalMaterials = false;
|
||||
}
|
||||
|
||||
private void SetTargetsMaterialDirty()
|
||||
{
|
||||
foreach (var graphic in _targetGraphics)
|
||||
{
|
||||
if (graphic != null)
|
||||
{
|
||||
graphic.SetMaterialDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DestroyRuntimeMaterial(bool immediate)
|
||||
{
|
||||
if (_runtimeMaterial == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RestoreOriginalMaterials();
|
||||
|
||||
if (immediate)
|
||||
{
|
||||
DestroyImmediate(_runtimeMaterial);
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(_runtimeMaterial);
|
||||
}
|
||||
|
||||
_runtimeMaterial = null;
|
||||
_isPreviewMaterial = false;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void RefreshEditModePreview()
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_previewInEditMode)
|
||||
{
|
||||
RestoreEditModeMaterial();
|
||||
return;
|
||||
}
|
||||
|
||||
CreateRuntimeMaterial(true);
|
||||
SetReverseDirection(_reverseDirection);
|
||||
SetDissolveAmount(_previewDissolveAmount);
|
||||
}
|
||||
|
||||
private void RestoreEditModeMaterial()
|
||||
{
|
||||
if (!_isPreviewMaterial)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DestroyRuntimeMaterial(true);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Framework/Effects/UIDissolveImage.cs.meta
Normal file
2
Assets/Scripts/Framework/Effects/UIDissolveImage.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e1ca11eb5d39d4da19232afd2e808c96
|
||||
502
Assets/Scripts/Framework/Effects/UIEdgeDarken.cs
Normal file
502
Assets/Scripts/Framework/Effects/UIEdgeDarken.cs
Normal file
@@ -0,0 +1,502 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using PrimeTween;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace BriarQueen.Framework.Effects
|
||||
{
|
||||
[ExecuteAlways]
|
||||
public class UIEdgeDarken : MonoBehaviour
|
||||
{
|
||||
private static readonly int _amountId = Shader.PropertyToID("_Amount");
|
||||
private static readonly int _centerDarknessId = Shader.PropertyToID("_CenterDarkness");
|
||||
private static readonly int _colorId = Shader.PropertyToID("_Color");
|
||||
private static readonly int _edgeDarknessId = Shader.PropertyToID("_EdgeDarkness");
|
||||
private static readonly int _edgeWidthId = Shader.PropertyToID("_EdgeWidth");
|
||||
private static readonly int _rectPivotId = Shader.PropertyToID("_RectPivot");
|
||||
private static readonly int _rectSizeId = Shader.PropertyToID("_RectSize");
|
||||
private static readonly int _softnessId = Shader.PropertyToID("_Softness");
|
||||
|
||||
[Header("References")]
|
||||
[SerializeField]
|
||||
private Graphic _graphic;
|
||||
|
||||
[SerializeField]
|
||||
private CanvasGroup _canvasGroup;
|
||||
|
||||
[SerializeField]
|
||||
private Material _edgeDarkenMaterialTemplate;
|
||||
|
||||
[Header("Targeting")]
|
||||
[SerializeField]
|
||||
private bool _targetChildGraphics;
|
||||
|
||||
[SerializeField]
|
||||
private bool _includeInactiveChildGraphics = true;
|
||||
|
||||
[SerializeField]
|
||||
private bool _includeTextGraphics;
|
||||
|
||||
[Header("Darken")]
|
||||
[SerializeField]
|
||||
[Range(0f, 1f)]
|
||||
private float _amount = 1f;
|
||||
|
||||
[SerializeField]
|
||||
private Color _color = new(0f, 0f, 0f, 0.65f);
|
||||
|
||||
[SerializeField]
|
||||
[Range(0f, 1f)]
|
||||
private float _edgeDarkness = 1f;
|
||||
|
||||
[SerializeField]
|
||||
[Range(0f, 1f)]
|
||||
private float _centerDarkness;
|
||||
|
||||
[SerializeField]
|
||||
[Range(0.001f, 0.5f)]
|
||||
private float _edgeWidth = 0.22f;
|
||||
|
||||
[SerializeField]
|
||||
[Range(0.001f, 0.5f)]
|
||||
private float _softness = 0.18f;
|
||||
|
||||
[Header("Tween")]
|
||||
[SerializeField]
|
||||
private float _duration = 0.35f;
|
||||
|
||||
[SerializeField]
|
||||
private Ease _ease = Ease.InOutSine;
|
||||
|
||||
[SerializeField]
|
||||
private bool _useUnscaledTime = true;
|
||||
|
||||
[Header("Editor Preview")]
|
||||
[SerializeField]
|
||||
private bool _previewInEditMode;
|
||||
|
||||
[SerializeField]
|
||||
[Range(0f, 1f)]
|
||||
private float _previewAmount = 1f;
|
||||
|
||||
private readonly List<Graphic> _targetGraphics = new();
|
||||
private readonly List<Material> _originalMaterials = new();
|
||||
private readonly List<Material> _runtimeMaterials = new();
|
||||
|
||||
private CancellationTokenSource _darkenCts;
|
||||
private bool _hasOriginalMaterials;
|
||||
private bool _isPreviewMaterial;
|
||||
private Sequence _darkenSequence;
|
||||
|
||||
public float Amount
|
||||
{
|
||||
get => _amount;
|
||||
set => SetAmount(value);
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
ResolveReferences();
|
||||
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
CreateRuntimeMaterial(false);
|
||||
ApplyMaterialProperties(_amount);
|
||||
return;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
RefreshEditModePreview();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
RefreshEditModePreview();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
RestoreEditModeMaterial();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
CancelTween();
|
||||
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
DestroyRuntimeMaterial(false);
|
||||
return;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
RestoreEditModeMaterial();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
ResolveReferences();
|
||||
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
ApplyMaterialProperties(_amount);
|
||||
return;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
RestoreEditModeMaterial();
|
||||
RefreshEditModePreview();
|
||||
#endif
|
||||
}
|
||||
|
||||
public UniTask DarkenIn()
|
||||
{
|
||||
return TweenAmount(0f, 1f, _duration);
|
||||
}
|
||||
|
||||
public UniTask DarkenOut()
|
||||
{
|
||||
return TweenAmount(1f, 0f, _duration);
|
||||
}
|
||||
|
||||
public async UniTask TweenAmount(float from, float to, float duration)
|
||||
{
|
||||
if (_runtimeMaterials.Count == 0)
|
||||
{
|
||||
CreateRuntimeMaterial(false);
|
||||
}
|
||||
|
||||
CancelTween();
|
||||
_darkenCts = new CancellationTokenSource();
|
||||
|
||||
SetAmount(from);
|
||||
|
||||
_darkenSequence = Sequence.Create(useUnscaledTime: _useUnscaledTime)
|
||||
.Group(Tween.Custom(
|
||||
from,
|
||||
to,
|
||||
Mathf.Max(0f, duration),
|
||||
SetAmount,
|
||||
_ease,
|
||||
useUnscaledTime: _useUnscaledTime));
|
||||
|
||||
try
|
||||
{
|
||||
await _darkenSequence.ToUniTask(cancellationToken: _darkenCts.Token);
|
||||
SetAmount(to);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Interrupted by another darken request or object destruction.
|
||||
}
|
||||
finally
|
||||
{
|
||||
_darkenSequence = default;
|
||||
|
||||
if (_darkenCts != null)
|
||||
{
|
||||
_darkenCts.Dispose();
|
||||
_darkenCts = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetAmount(float amount)
|
||||
{
|
||||
_amount = Mathf.Clamp01(amount);
|
||||
ApplyMaterialProperties(_amount);
|
||||
}
|
||||
|
||||
public void CancelTween()
|
||||
{
|
||||
if (_darkenSequence.isAlive)
|
||||
{
|
||||
_darkenSequence.Stop();
|
||||
_darkenSequence = default;
|
||||
}
|
||||
|
||||
if (_darkenCts != null)
|
||||
{
|
||||
_darkenCts.Cancel();
|
||||
_darkenCts.Dispose();
|
||||
_darkenCts = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void ResolveReferences()
|
||||
{
|
||||
if (_graphic == null)
|
||||
{
|
||||
_graphic = GetComponent<Graphic>();
|
||||
}
|
||||
|
||||
if (_canvasGroup == null)
|
||||
{
|
||||
_canvasGroup = GetComponent<CanvasGroup>();
|
||||
}
|
||||
}
|
||||
|
||||
private void ResolveTargetGraphics()
|
||||
{
|
||||
ResolveReferences();
|
||||
_targetGraphics.Clear();
|
||||
|
||||
if (_targetChildGraphics)
|
||||
{
|
||||
var root = _canvasGroup != null ? _canvasGroup.transform : transform;
|
||||
var graphics = root.GetComponentsInChildren<Graphic>(_includeInactiveChildGraphics);
|
||||
|
||||
foreach (var graphic in graphics)
|
||||
{
|
||||
if (IsValidTargetGraphic(graphic))
|
||||
{
|
||||
_targetGraphics.Add(graphic);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_graphic != null)
|
||||
{
|
||||
_targetGraphics.Add(_graphic);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsValidTargetGraphic(Graphic graphic)
|
||||
{
|
||||
if (graphic == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// TMP needs a TMP-compatible shader; replacing its SDF material breaks text rendering.
|
||||
if (!_includeTextGraphics && graphic is TMP_Text)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void CreateRuntimeMaterial(bool isPreviewMaterial)
|
||||
{
|
||||
ResolveTargetGraphics();
|
||||
|
||||
if (_targetGraphics.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var sourceMaterial = _edgeDarkenMaterialTemplate != null
|
||||
? _edgeDarkenMaterialTemplate
|
||||
: _targetGraphics[0].material;
|
||||
|
||||
if (sourceMaterial == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_runtimeMaterials.Count > 0)
|
||||
{
|
||||
ApplyRuntimeMaterialToTargets();
|
||||
return;
|
||||
}
|
||||
|
||||
SaveOriginalMaterials();
|
||||
_isPreviewMaterial = isPreviewMaterial;
|
||||
|
||||
foreach (var graphic in _targetGraphics)
|
||||
{
|
||||
if (graphic == null)
|
||||
{
|
||||
_runtimeMaterials.Add(null);
|
||||
continue;
|
||||
}
|
||||
|
||||
var runtimeMaterial = Instantiate(sourceMaterial);
|
||||
runtimeMaterial.name = isPreviewMaterial
|
||||
? $"{nameof(UIEdgeDarken)} Preview Material"
|
||||
: $"{nameof(UIEdgeDarken)} Runtime Material";
|
||||
|
||||
if (isPreviewMaterial)
|
||||
{
|
||||
runtimeMaterial.hideFlags = HideFlags.DontSaveInEditor;
|
||||
}
|
||||
|
||||
_runtimeMaterials.Add(runtimeMaterial);
|
||||
}
|
||||
|
||||
ApplyRuntimeMaterialToTargets();
|
||||
}
|
||||
|
||||
private void ApplyRuntimeMaterialToTargets()
|
||||
{
|
||||
var count = Mathf.Min(_targetGraphics.Count, _runtimeMaterials.Count);
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var graphic = _targetGraphics[i];
|
||||
if (graphic == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
graphic.material = _runtimeMaterials[i];
|
||||
graphic.SetMaterialDirty();
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyMaterialProperties(float amount)
|
||||
{
|
||||
if (_runtimeMaterials.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var count = Mathf.Min(_targetGraphics.Count, _runtimeMaterials.Count);
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var material = _runtimeMaterials[i];
|
||||
var graphic = _targetGraphics[i];
|
||||
if (material == null || graphic == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var rectTransform = graphic.rectTransform;
|
||||
material.SetFloat(_amountId, Mathf.Clamp01(amount));
|
||||
material.SetColor(_colorId, _color);
|
||||
material.SetFloat(_edgeDarknessId, _edgeDarkness);
|
||||
material.SetFloat(_centerDarknessId, _centerDarkness);
|
||||
material.SetFloat(_edgeWidthId, _edgeWidth);
|
||||
material.SetFloat(_softnessId, _softness);
|
||||
material.SetVector(_rectSizeId, rectTransform.rect.size);
|
||||
material.SetVector(_rectPivotId, rectTransform.pivot);
|
||||
}
|
||||
|
||||
SetTargetsMaterialDirty();
|
||||
}
|
||||
|
||||
private void SaveOriginalMaterials()
|
||||
{
|
||||
_originalMaterials.Clear();
|
||||
|
||||
foreach (var graphic in _targetGraphics)
|
||||
{
|
||||
_originalMaterials.Add(graphic != null ? graphic.material : null);
|
||||
}
|
||||
|
||||
_hasOriginalMaterials = true;
|
||||
}
|
||||
|
||||
private void RestoreOriginalMaterials()
|
||||
{
|
||||
if (!_hasOriginalMaterials)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var count = Mathf.Min(_targetGraphics.Count, _originalMaterials.Count);
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var graphic = _targetGraphics[i];
|
||||
if (graphic == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
graphic.material = _originalMaterials[i];
|
||||
graphic.SetMaterialDirty();
|
||||
}
|
||||
|
||||
_originalMaterials.Clear();
|
||||
_hasOriginalMaterials = false;
|
||||
}
|
||||
|
||||
private void SetTargetsMaterialDirty()
|
||||
{
|
||||
foreach (var graphic in _targetGraphics)
|
||||
{
|
||||
if (graphic != null)
|
||||
{
|
||||
graphic.SetMaterialDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DestroyRuntimeMaterial(bool immediate)
|
||||
{
|
||||
if (_runtimeMaterials.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RestoreOriginalMaterials();
|
||||
|
||||
foreach (var runtimeMaterial in _runtimeMaterials)
|
||||
{
|
||||
if (runtimeMaterial == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (immediate)
|
||||
{
|
||||
DestroyImmediate(runtimeMaterial);
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(runtimeMaterial);
|
||||
}
|
||||
}
|
||||
|
||||
_runtimeMaterials.Clear();
|
||||
_isPreviewMaterial = false;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void RefreshEditModePreview()
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_previewInEditMode)
|
||||
{
|
||||
RestoreEditModeMaterial();
|
||||
return;
|
||||
}
|
||||
|
||||
CreateRuntimeMaterial(true);
|
||||
ApplyMaterialProperties(_previewAmount);
|
||||
}
|
||||
|
||||
private void RestoreEditModeMaterial()
|
||||
{
|
||||
if (!_isPreviewMaterial)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DestroyRuntimeMaterial(true);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Framework/Effects/UIEdgeDarken.cs.meta
Normal file
3
Assets/Scripts/Framework/Effects/UIEdgeDarken.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7cb0728fb51e41b2aa93ec51993d9150
|
||||
timeCreated: 1770379821
|
||||
399
Assets/Scripts/Framework/Effects/UIFogReveal.cs
Normal file
399
Assets/Scripts/Framework/Effects/UIFogReveal.cs
Normal file
@@ -0,0 +1,399 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using BriarQueen.Framework.Services.Destruction;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using PrimeTween;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using VContainer;
|
||||
|
||||
namespace BriarQueen.Game.Effects
|
||||
{
|
||||
[ExecuteAlways]
|
||||
public class UIFogReveal : MonoBehaviour
|
||||
{
|
||||
// ── Shader property IDs ───────────────────────────────────────
|
||||
private static readonly int _fogAmountId = Shader.PropertyToID("_FogAmount");
|
||||
private static readonly int _fogColorId = Shader.PropertyToID("_FogColor");
|
||||
private static readonly int _fogIntensityId = Shader.PropertyToID("_FogIntensity");
|
||||
private static readonly int _edgeSoftnessId = Shader.PropertyToID("_EdgeSoftness");
|
||||
private static readonly int _noiseScaleId = Shader.PropertyToID("_NoiseScale");
|
||||
private static readonly int _driftSpeedId = Shader.PropertyToID("_DriftSpeed");
|
||||
private static readonly int _densityVariationId = Shader.PropertyToID("_DensityVariation");
|
||||
private static readonly int _aspectRatioId = Shader.PropertyToID("_AspectRatio");
|
||||
private static readonly int _fogMotionId = Shader.PropertyToID("_FogMotion");
|
||||
private static readonly int _useSpriteId = Shader.PropertyToID("_UseSprite");
|
||||
|
||||
// ── Inspector ─────────────────────────────────────────────────
|
||||
[Header("References")]
|
||||
[SerializeField] private Image _image;
|
||||
[SerializeField] private Material _fogMaterialTemplate;
|
||||
|
||||
[Header("Fog Settings")]
|
||||
[SerializeField] private Color _fogColor = new(0.18f, 0.20f, 0.26f, 1f);
|
||||
[SerializeField][Range(0f, 1f)] private float _fogIntensity = 0.95f;
|
||||
[SerializeField][Range(0.05f, 1f)] private float _edgeSoftness = 0.55f;
|
||||
[SerializeField][Range(1f, 20f)] private float _noiseScale = 5f;
|
||||
[SerializeField][Range(0f, 0.5f)] private float _driftSpeed = 0.04f;
|
||||
[SerializeField][Range(0f, 1f)] private float _densityVariation = 0.28f;
|
||||
[SerializeField] private bool _fogMotion = true;
|
||||
[SerializeField] private bool _useSprite = false;
|
||||
|
||||
[Header("Fog Range")]
|
||||
[SerializeField][Range(0f, 1f)] private float _startFog = 0f;
|
||||
[SerializeField][Range(0f, 1f)] private float _maxFog = 0.6f;
|
||||
|
||||
[Header("Screen")]
|
||||
[SerializeField] private bool _autoAspectRatio = true;
|
||||
[SerializeField] private float _aspectRatio = 1.777f;
|
||||
|
||||
[Header("Tween")]
|
||||
[SerializeField] private float _duration = 1.5f;
|
||||
[SerializeField] private Ease _ease = Ease.InOutSine;
|
||||
[SerializeField] private bool _useUnscaledTime = true;
|
||||
|
||||
[Header("Delay")]
|
||||
[SerializeField][Range(0f, 5f)] private float _fogInDelay = 0f;
|
||||
[SerializeField][Range(0f, 5f)] private float _fogOutDelay = 0f;
|
||||
|
||||
[Header("Editor Preview")]
|
||||
[SerializeField] private bool _previewInEditMode;
|
||||
[SerializeField][Range(0f, 1f)] private float _previewFogAmount;
|
||||
|
||||
// ── Runtime state ─────────────────────────────────────────────
|
||||
private Material _runtimeMaterial;
|
||||
private bool _isPreviewMaterial;
|
||||
private Sequence _fogSequence;
|
||||
private CancellationTokenSource _fogCts;
|
||||
private DestructionService _destructionService;
|
||||
|
||||
// ── Public properties ─────────────────────────────────────────
|
||||
public float FogAmount
|
||||
{
|
||||
get => _runtimeMaterial != null ? _runtimeMaterial.GetFloat(_fogAmountId) : 0f;
|
||||
set => SetFogAmount(value);
|
||||
}
|
||||
|
||||
public float FogInDuration => _fogInDelay + _duration;
|
||||
public float FogOutDuration => _fogOutDelay + _duration;
|
||||
|
||||
public bool FogMotion
|
||||
{
|
||||
get => _fogMotion;
|
||||
set
|
||||
{
|
||||
_fogMotion = value;
|
||||
_runtimeMaterial?.SetFloat(_fogMotionId, value ? 1f : 0f);
|
||||
}
|
||||
}
|
||||
|
||||
public bool UseSprite
|
||||
{
|
||||
get => _useSprite;
|
||||
set
|
||||
{
|
||||
_useSprite = value;
|
||||
_runtimeMaterial?.SetFloat(_useSpriteId, value ? 1f : 0f);
|
||||
}
|
||||
}
|
||||
|
||||
public float MaxFog
|
||||
{
|
||||
get => _maxFog;
|
||||
set => _maxFog = value;
|
||||
}
|
||||
|
||||
// ── DI ────────────────────────────────────────────────────────
|
||||
[Inject]
|
||||
public void Construct(DestructionService destructionService)
|
||||
{
|
||||
_destructionService = destructionService;
|
||||
}
|
||||
|
||||
// ── Unity lifecycle ───────────────────────────────────────────
|
||||
private void Awake()
|
||||
{
|
||||
ResolveReferences();
|
||||
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
CreateRuntimeMaterial(false);
|
||||
SetFogAmount(_startFog);
|
||||
return;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
RefreshEditModePreview();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
RefreshEditModePreview();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
RestoreEditModeMaterial();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!Application.isPlaying) return;
|
||||
if (!_autoAspectRatio) return;
|
||||
|
||||
var canvas = GetComponentInParent<Canvas>();
|
||||
if (canvas == null) return;
|
||||
|
||||
var rt = canvas.GetComponent<RectTransform>();
|
||||
var ratio = rt.rect.width / Mathf.Max(rt.rect.height, 0.001f);
|
||||
_runtimeMaterial?.SetFloat(_aspectRatioId, ratio);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
CancelFog();
|
||||
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
DestroyRuntimeMaterial(false);
|
||||
return;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
RestoreEditModeMaterial();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
ResolveReferences();
|
||||
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
ApplyShaderSettings();
|
||||
return;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
RestoreEditModeMaterial();
|
||||
RefreshEditModePreview();
|
||||
#endif
|
||||
}
|
||||
|
||||
// ── Public API ────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Animate fog in (startFog → maxFog). Respects _fogInDelay.</summary>
|
||||
public UniTask FogIn() => TweenFogWithDelay(_startFog, _maxFog, _duration, _fogInDelay);
|
||||
|
||||
/// <summary>Animate fog in over a custom duration, no delay.</summary>
|
||||
public UniTask FogIn(float duration) => TweenFog(_startFog, _maxFog, duration);
|
||||
|
||||
/// <summary>Animate fog out (maxFog → startFog). Respects _fogOutDelay.</summary>
|
||||
public UniTask FogOut() => TweenFogWithDelay(_maxFog, _startFog, _duration, _fogOutDelay);
|
||||
|
||||
/// <summary>Animate fog out over a custom duration, no delay.</summary>
|
||||
public UniTask FogOut(float duration) => TweenFog(_maxFog, _startFog, duration);
|
||||
|
||||
/// <summary>Animate fog to an arbitrary target amount, no delay.</summary>
|
||||
public UniTask FogTo(float target, float duration) => TweenFog(FogAmount, target, duration);
|
||||
|
||||
/// <summary>Snap fog to a value immediately, cancels any running tween.</summary>
|
||||
public void FogSet(float amount)
|
||||
{
|
||||
CancelFog();
|
||||
SetFogAmount(amount);
|
||||
}
|
||||
|
||||
/// <summary>Snap fog to startFog immediately.</summary>
|
||||
public void FogReset()
|
||||
{
|
||||
CancelFog();
|
||||
SetFogAmount(_startFog);
|
||||
}
|
||||
|
||||
public void CancelFog()
|
||||
{
|
||||
if (_fogSequence.isAlive)
|
||||
{
|
||||
_fogSequence.Stop();
|
||||
_fogSequence = default;
|
||||
}
|
||||
|
||||
if (_fogCts != null)
|
||||
{
|
||||
_fogCts.Cancel();
|
||||
_fogCts.Dispose();
|
||||
_fogCts = null;
|
||||
}
|
||||
}
|
||||
|
||||
public async UniTask TweenFog(float from, float to, float duration)
|
||||
{
|
||||
await TweenFogWithDelay(from, to, duration, 0f);
|
||||
}
|
||||
|
||||
public async UniTask TweenFogWithDelay(float from, float to, float duration, float delay)
|
||||
{
|
||||
if (_runtimeMaterial == null)
|
||||
CreateRuntimeMaterial(false);
|
||||
|
||||
CancelFog();
|
||||
|
||||
_fogCts = new CancellationTokenSource();
|
||||
SetFogAmount(from);
|
||||
|
||||
try
|
||||
{
|
||||
if (delay > 0f)
|
||||
{
|
||||
await UniTask.Delay(
|
||||
TimeSpan.FromSeconds(delay),
|
||||
ignoreTimeScale: _useUnscaledTime,
|
||||
cancellationToken: _fogCts.Token);
|
||||
}
|
||||
|
||||
_fogSequence = Sequence.Create(useUnscaledTime: _useUnscaledTime)
|
||||
.Group(Tween.Custom(
|
||||
from,
|
||||
to,
|
||||
Mathf.Max(0f, duration),
|
||||
SetFogAmount,
|
||||
_ease,
|
||||
useUnscaledTime: _useUnscaledTime));
|
||||
|
||||
await _fogSequence.ToUniTask(cancellationToken: _fogCts.Token);
|
||||
SetFogAmount(to);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Interrupted — normal, swallow it.
|
||||
}
|
||||
finally
|
||||
{
|
||||
_fogSequence = default;
|
||||
|
||||
if (_fogCts != null)
|
||||
{
|
||||
_fogCts.Dispose();
|
||||
_fogCts = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Internal ──────────────────────────────────────────────────
|
||||
|
||||
private void SetFogAmount(float amount)
|
||||
{
|
||||
if (_runtimeMaterial == null) return;
|
||||
_runtimeMaterial.SetFloat(_fogAmountId, Mathf.Clamp01(amount));
|
||||
_image?.SetMaterialDirty();
|
||||
}
|
||||
|
||||
private void ApplyShaderSettings()
|
||||
{
|
||||
if (_runtimeMaterial == null) return;
|
||||
|
||||
_runtimeMaterial.SetColor(_fogColorId, _fogColor);
|
||||
_runtimeMaterial.SetFloat(_fogIntensityId, _fogIntensity);
|
||||
_runtimeMaterial.SetFloat(_edgeSoftnessId, _edgeSoftness);
|
||||
_runtimeMaterial.SetFloat(_noiseScaleId, _noiseScale);
|
||||
_runtimeMaterial.SetFloat(_driftSpeedId, _driftSpeed);
|
||||
_runtimeMaterial.SetFloat(_densityVariationId, _densityVariation);
|
||||
_runtimeMaterial.SetFloat(_fogMotionId, _fogMotion ? 1f : 0f);
|
||||
_runtimeMaterial.SetFloat(_useSpriteId, _useSprite ? 1f : 0f);
|
||||
|
||||
if (!_autoAspectRatio)
|
||||
_runtimeMaterial.SetFloat(_aspectRatioId, _aspectRatio);
|
||||
|
||||
_image?.SetMaterialDirty();
|
||||
}
|
||||
|
||||
private void ResolveReferences()
|
||||
{
|
||||
if (_image == null)
|
||||
_image = GetComponent<Image>();
|
||||
}
|
||||
|
||||
private void CreateRuntimeMaterial(bool isPreviewMaterial)
|
||||
{
|
||||
if (_image == null) return;
|
||||
|
||||
var source = _fogMaterialTemplate != null
|
||||
? _fogMaterialTemplate
|
||||
: _image.material;
|
||||
|
||||
if (source == null) return;
|
||||
|
||||
if (_runtimeMaterial != null)
|
||||
{
|
||||
_image.material = _runtimeMaterial;
|
||||
_image.SetMaterialDirty();
|
||||
return;
|
||||
}
|
||||
|
||||
_runtimeMaterial = Instantiate(source);
|
||||
_runtimeMaterial.name = isPreviewMaterial
|
||||
? $"{nameof(UIFogReveal)} Preview Material"
|
||||
: $"{nameof(UIFogReveal)} Runtime Material";
|
||||
|
||||
_isPreviewMaterial = isPreviewMaterial;
|
||||
|
||||
if (isPreviewMaterial)
|
||||
_runtimeMaterial.hideFlags = HideFlags.DontSaveInEditor;
|
||||
|
||||
_image.material = _runtimeMaterial;
|
||||
_image.SetMaterialDirty();
|
||||
|
||||
ApplyShaderSettings();
|
||||
}
|
||||
|
||||
private void DestroyRuntimeMaterial(bool immediate)
|
||||
{
|
||||
if (_runtimeMaterial == null) return;
|
||||
|
||||
if (_image != null)
|
||||
{
|
||||
_image.material = null;
|
||||
_image.SetMaterialDirty();
|
||||
}
|
||||
|
||||
if (immediate)
|
||||
DestroyImmediate(_runtimeMaterial);
|
||||
else
|
||||
Destroy(_runtimeMaterial);
|
||||
|
||||
_runtimeMaterial = null;
|
||||
_isPreviewMaterial = false;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void RefreshEditModePreview()
|
||||
{
|
||||
if (Application.isPlaying) return;
|
||||
|
||||
if (!_previewInEditMode)
|
||||
{
|
||||
RestoreEditModeMaterial();
|
||||
return;
|
||||
}
|
||||
|
||||
CreateRuntimeMaterial(true);
|
||||
ApplyShaderSettings();
|
||||
SetFogAmount(_previewFogAmount);
|
||||
}
|
||||
|
||||
private void RestoreEditModeMaterial()
|
||||
{
|
||||
if (!_isPreviewMaterial) return;
|
||||
DestroyRuntimeMaterial(true);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Framework/Effects/UIFogReveal.cs.meta
Normal file
3
Assets/Scripts/Framework/Effects/UIFogReveal.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 912c8bc1a5f84113848a078f0581c8ce
|
||||
timeCreated: 1778334335
|
||||
249
Assets/Scripts/Framework/Effects/UILightGlow.cs
Normal file
249
Assets/Scripts/Framework/Effects/UILightGlow.cs
Normal file
@@ -0,0 +1,249 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using PrimeTween;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace BriarQueen.Framework.Effects
|
||||
{
|
||||
[RequireComponent(typeof(Image))]
|
||||
public class UILightGlow : MonoBehaviour
|
||||
{
|
||||
private const string _lightShaderName = "BriarQueen/UI/Light Glow";
|
||||
|
||||
private static readonly int _lightColorId = Shader.PropertyToID("_LightColor");
|
||||
private static readonly int _intensityId = Shader.PropertyToID("_Intensity");
|
||||
private static readonly int _flickerOffsetId = Shader.PropertyToID("_FlickerOffset");
|
||||
|
||||
[Header("References")]
|
||||
[SerializeField]
|
||||
private Image _image;
|
||||
|
||||
[SerializeField]
|
||||
private Material _lightMaterialTemplate;
|
||||
|
||||
[Header("Light")]
|
||||
[SerializeField]
|
||||
private Color _startingColor = new(1f, 0.78f, 0.35f, 1f);
|
||||
|
||||
[SerializeField]
|
||||
private float _startingIntensity = 1.5f;
|
||||
|
||||
[SerializeField]
|
||||
private bool _randomizeFlickerOffset = true;
|
||||
|
||||
[Header("Tween")]
|
||||
[SerializeField]
|
||||
private float _defaultTweenDuration = 0.35f;
|
||||
|
||||
[SerializeField]
|
||||
private Ease _ease = Ease.InOutSine;
|
||||
|
||||
[SerializeField]
|
||||
private bool _useUnscaledTime = true;
|
||||
|
||||
private Material _runtimeMaterial;
|
||||
private Sequence _lightSequence;
|
||||
private CancellationTokenSource _lightCts;
|
||||
|
||||
public Color LightColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_runtimeMaterial == null)
|
||||
{
|
||||
return _startingColor;
|
||||
}
|
||||
|
||||
return _runtimeMaterial.GetColor(_lightColorId);
|
||||
}
|
||||
}
|
||||
|
||||
public float Intensity
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_runtimeMaterial == null)
|
||||
{
|
||||
return _startingIntensity;
|
||||
}
|
||||
|
||||
return _runtimeMaterial.GetFloat(_intensityId);
|
||||
}
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_image == null)
|
||||
{
|
||||
_image = GetComponent<Image>();
|
||||
}
|
||||
|
||||
CreateRuntimeMaterial();
|
||||
SetLightColor(_startingColor);
|
||||
SetIntensity(_startingIntensity);
|
||||
|
||||
if (_randomizeFlickerOffset && _runtimeMaterial != null)
|
||||
{
|
||||
_runtimeMaterial.SetFloat(_flickerOffsetId, UnityEngine.Random.Range(0f, 100f));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
CancelTween();
|
||||
|
||||
if (_runtimeMaterial != null)
|
||||
{
|
||||
Destroy(_runtimeMaterial);
|
||||
_runtimeMaterial = null;
|
||||
}
|
||||
}
|
||||
|
||||
public UniTask ChangeColor(Color targetColor)
|
||||
{
|
||||
return ChangeColor(targetColor, _defaultTweenDuration);
|
||||
}
|
||||
|
||||
public UniTask ChangeColor(Color targetColor, float duration)
|
||||
{
|
||||
return TweenTo(targetColor, Intensity, duration);
|
||||
}
|
||||
|
||||
public UniTask ChangeIntensity(float targetIntensity)
|
||||
{
|
||||
return ChangeIntensity(targetIntensity, _defaultTweenDuration);
|
||||
}
|
||||
|
||||
public UniTask ChangeIntensity(float targetIntensity, float duration)
|
||||
{
|
||||
return TweenTo(LightColor, targetIntensity, duration);
|
||||
}
|
||||
|
||||
public UniTask TurnOff()
|
||||
{
|
||||
return ChangeIntensity(0f, _defaultTweenDuration);
|
||||
}
|
||||
|
||||
public UniTask TurnOn()
|
||||
{
|
||||
return ChangeIntensity(_startingIntensity, _defaultTweenDuration);
|
||||
}
|
||||
|
||||
public async UniTask TweenTo(Color targetColor, float targetIntensity, float duration)
|
||||
{
|
||||
if (_runtimeMaterial == null)
|
||||
{
|
||||
CreateRuntimeMaterial();
|
||||
}
|
||||
|
||||
CancelTween();
|
||||
|
||||
var fromColor = LightColor;
|
||||
var fromIntensity = Intensity;
|
||||
var safeDuration = Mathf.Max(0f, duration);
|
||||
|
||||
_lightCts = new CancellationTokenSource();
|
||||
|
||||
_lightSequence = Sequence.Create(useUnscaledTime: _useUnscaledTime)
|
||||
.Group(Tween.Custom(
|
||||
0f,
|
||||
1f,
|
||||
safeDuration,
|
||||
progress =>
|
||||
{
|
||||
SetLightColor(Color.LerpUnclamped(fromColor, targetColor, progress));
|
||||
SetIntensity(Mathf.LerpUnclamped(fromIntensity, targetIntensity, progress));
|
||||
},
|
||||
_ease,
|
||||
useUnscaledTime: _useUnscaledTime));
|
||||
|
||||
try
|
||||
{
|
||||
await _lightSequence.ToUniTask(cancellationToken: _lightCts.Token);
|
||||
SetLightColor(targetColor);
|
||||
SetIntensity(targetIntensity);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Interrupted by another light tween or object destruction.
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lightSequence = default;
|
||||
|
||||
if (_lightCts != null)
|
||||
{
|
||||
_lightCts.Dispose();
|
||||
_lightCts = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetLightColor(Color color)
|
||||
{
|
||||
if (_runtimeMaterial == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_runtimeMaterial.SetColor(_lightColorId, color);
|
||||
}
|
||||
|
||||
public void SetIntensity(float intensity)
|
||||
{
|
||||
if (_runtimeMaterial == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_runtimeMaterial.SetFloat(_intensityId, Mathf.Max(0f, intensity));
|
||||
}
|
||||
|
||||
public void CancelTween()
|
||||
{
|
||||
if (_lightSequence.isAlive)
|
||||
{
|
||||
_lightSequence.Stop();
|
||||
_lightSequence = default;
|
||||
}
|
||||
|
||||
if (_lightCts != null)
|
||||
{
|
||||
_lightCts.Cancel();
|
||||
_lightCts.Dispose();
|
||||
_lightCts = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateRuntimeMaterial()
|
||||
{
|
||||
if (_image == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_lightMaterialTemplate != null)
|
||||
{
|
||||
_runtimeMaterial = Instantiate(_lightMaterialTemplate);
|
||||
_image.material = _runtimeMaterial;
|
||||
return;
|
||||
}
|
||||
|
||||
var shader = Shader.Find(_lightShaderName);
|
||||
if (shader == null)
|
||||
{
|
||||
Debug.LogWarning($"[{nameof(UILightGlow)}] Could not find shader '{_lightShaderName}'.");
|
||||
return;
|
||||
}
|
||||
|
||||
_runtimeMaterial = new Material(shader)
|
||||
{
|
||||
name = $"{nameof(UILightGlow)} Runtime Material"
|
||||
};
|
||||
|
||||
_image.material = _runtimeMaterial;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Framework/Effects/UILightGlow.cs.meta
Normal file
2
Assets/Scripts/Framework/Effects/UILightGlow.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d3e14c91f3c942fc84e800d2fb583fb0
|
||||
@@ -3,5 +3,5 @@ using BriarQueen.Framework.Events.System;
|
||||
|
||||
namespace BriarQueen.Framework.Events.UI
|
||||
{
|
||||
public record DisplayTutorialPopupEvent(TutorialPopupID TutorialID) : IEvent;
|
||||
public record DisplayTutorialPopupEvent(TutorialPopupID TutorialID, string ResolvedText) : IEvent;
|
||||
}
|
||||
@@ -8,9 +8,5 @@ namespace BriarQueen.Framework.Events.UI
|
||||
SolidColor = 0, // Left for Compat.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Publish with color != Color.clear to fade in.
|
||||
/// Publish with color == Color.clear to fade out (uses last active style).
|
||||
/// </summary>
|
||||
public record FadeEvent(bool Hidden, float Duration = 0.25f) : IEvent;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
using BriarQueen.Framework.Events.System;
|
||||
|
||||
namespace BriarQueen.Framework.Events.UI
|
||||
{
|
||||
public record UIBackRequestedEvent : IEvent;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4cf9029b5f1a4c4f8740cb7394c1b5f8
|
||||
timeCreated: 1778300000
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using BriarQueen.Framework.Assets.Components;
|
||||
using BriarQueen.Framework.Managers.Assets.Components;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AddressableAssets;
|
||||
@@ -11,7 +11,7 @@ using UnityEngine.SceneManagement;
|
||||
using VContainer;
|
||||
using VContainer.Unity;
|
||||
|
||||
namespace BriarQueen.Framework.Assets
|
||||
namespace BriarQueen.Framework.Managers.Assets
|
||||
{
|
||||
public class AddressableManager : IDisposable
|
||||
{
|
||||
@@ -31,14 +31,18 @@ namespace BriarQueen.Framework.Assets
|
||||
lock (_lock)
|
||||
{
|
||||
foreach (var handle in _instanceHandles.Values)
|
||||
{
|
||||
if (handle.IsValid())
|
||||
Addressables.ReleaseInstance(handle);
|
||||
}
|
||||
|
||||
_instanceHandles.Clear();
|
||||
|
||||
foreach (var handle in _assetHandles.Values)
|
||||
{
|
||||
if (handle.IsValid())
|
||||
Addressables.Release(handle);
|
||||
}
|
||||
|
||||
_assetHandles.Clear();
|
||||
}
|
||||
@@ -67,7 +71,8 @@ namespace BriarQueen.Framework.Assets
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
if (handle.IsValid()) Addressables.Release(handle);
|
||||
if (handle.IsValid())
|
||||
Addressables.Release(handle);
|
||||
|
||||
throw;
|
||||
}
|
||||
@@ -81,7 +86,8 @@ namespace BriarQueen.Framework.Assets
|
||||
{
|
||||
if (_assetHandles.TryGetValue(asset, out var handle))
|
||||
{
|
||||
if (handle.IsValid()) Addressables.Release(handle);
|
||||
if (handle.IsValid())
|
||||
Addressables.Release(handle);
|
||||
|
||||
_assetHandles.Remove(asset);
|
||||
}
|
||||
@@ -97,8 +103,20 @@ namespace BriarQueen.Framework.Assets
|
||||
)
|
||||
{
|
||||
var handle = Addressables.LoadSceneAsync(assetReference, loadSceneMode, autoLoad);
|
||||
await handle.ToUniTask(progress, cancellationToken: cancellationToken);
|
||||
return handle;
|
||||
|
||||
try
|
||||
{
|
||||
await handle.ToUniTask(progress, cancellationToken: cancellationToken);
|
||||
return handle;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
if (handle.IsValid())
|
||||
Addressables.Release(handle);
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async UniTask UnloadSceneAsync(AsyncOperationHandle<SceneInstance> sceneHandle)
|
||||
@@ -116,6 +134,9 @@ namespace BriarQueen.Framework.Assets
|
||||
)
|
||||
{
|
||||
var handle = Addressables.InstantiateAsync(reference, position, rotation, parent);
|
||||
GameObject go = null;
|
||||
NotifyOnDestruction notify = null;
|
||||
Action onInstanceDestroyed = null;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -127,33 +148,32 @@ namespace BriarQueen.Framework.Assets
|
||||
return null;
|
||||
}
|
||||
|
||||
var go = handle.Result;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_instanceHandles[go] = handle;
|
||||
}
|
||||
go = handle.Result;
|
||||
|
||||
var prefabScope = go.GetComponent<LifetimeScope>();
|
||||
var injectionScope = scope ?? prefabScope?.Container ?? _lifetimeContainer;
|
||||
|
||||
injectionScope.InjectGameObject(go);
|
||||
|
||||
var notify = go.GetComponent<NotifyOnDestruction>();
|
||||
notify = go.GetComponent<NotifyOnDestruction>();
|
||||
|
||||
if (!notify)
|
||||
{
|
||||
notify = go.AddComponent<NotifyOnDestruction>();
|
||||
injectionScope.Inject(notify);
|
||||
}
|
||||
|
||||
void OnInstanceDestroyed()
|
||||
onInstanceDestroyed = () =>
|
||||
{
|
||||
TryReleaseInstance(go);
|
||||
notify.OnDestroyedCalled -= OnInstanceDestroyed;
|
||||
}
|
||||
notify.OnDestroyedCalled -= onInstanceDestroyed;
|
||||
};
|
||||
|
||||
notify.OnDestroyedCalled += OnInstanceDestroyed;
|
||||
notify.OnDestroyedCalled += onInstanceDestroyed;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_instanceHandles[go] = handle;
|
||||
}
|
||||
|
||||
return go;
|
||||
}
|
||||
@@ -161,6 +181,17 @@ namespace BriarQueen.Framework.Assets
|
||||
{
|
||||
if (handle.IsValid()) Addressables.ReleaseInstance(handle);
|
||||
|
||||
throw;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
if (notify != null && onInstanceDestroyed != null)
|
||||
{
|
||||
notify.OnDestroyedCalled -= onInstanceDestroyed;
|
||||
}
|
||||
|
||||
if (handle.IsValid()) Addressables.ReleaseInstance(handle);
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -201,4 +232,4 @@ namespace BriarQueen.Framework.Assets
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,33 +2,42 @@ using System;
|
||||
using BriarQueen.Framework.Services.Destruction;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using VContainer;
|
||||
|
||||
namespace BriarQueen.Framework.Assets.Components
|
||||
namespace BriarQueen.Framework.Managers.Assets.Components
|
||||
{
|
||||
public class NotifyOnDestruction : MonoBehaviour, IDestructible
|
||||
{
|
||||
private DestructionService _destructionService;
|
||||
private bool _destroyedNotified;
|
||||
|
||||
public UniTask OnPreDestroy()
|
||||
{
|
||||
OnPreDestroyCalled?.Invoke();
|
||||
return UniTask.CompletedTask; // No async operation needed, just a notification
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
public UniTask OnDestroyed()
|
||||
{
|
||||
OnDestroyedCalled?.Invoke();
|
||||
return UniTask.CompletedTask; // No async operation needed, just a notification
|
||||
}
|
||||
|
||||
[Inject]
|
||||
public void Construct(DestructionService destructionService)
|
||||
{
|
||||
_destructionService = destructionService;
|
||||
RaiseDestroyedOnce();
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
public event Action OnPreDestroyCalled;
|
||||
public event Action OnDestroyedCalled;
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
RaiseDestroyedOnce();
|
||||
}
|
||||
|
||||
private void RaiseDestroyedOnce()
|
||||
{
|
||||
if (_destroyedNotified)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_destroyedNotified = true;
|
||||
OnDestroyedCalled?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace BriarQueen.Framework.Assets
|
||||
namespace BriarQueen.Framework.Managers.Assets
|
||||
{
|
||||
public interface IAssetProvider
|
||||
{
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using BriarQueen.Data.Identifiers;
|
||||
using BriarQueen.Framework.Coordinators.Events;
|
||||
@@ -23,41 +22,38 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
/// - Settings set "base" volumes (in dB) per mixer parameter.
|
||||
/// - Runtime states (Pause duck, Voice duck) apply "modifiers" (extra dB offsets).
|
||||
/// - Effective mixer value is always: effectiveDb = baseDb + modifiersDb
|
||||
/// - UI and Ambience route through SFX channel/group.
|
||||
/// - SFX pool is transient — new channels are spawned on demand and
|
||||
/// finished channels are reaped before each play.
|
||||
/// </summary>
|
||||
public class AudioManager : IDisposable, IManager
|
||||
{
|
||||
private const int INITIAL_AMBIENCE_SOURCES = 3;
|
||||
private const int INITIAL_SFX_SOURCES = 6;
|
||||
|
||||
private const float PAUSE_DUCK_TARGET_DB = -18f;
|
||||
private const float PAUSE_DUCK_FADE_SECONDS = 0.25f;
|
||||
private const int INITIAL_SFX_SOURCES = 8;
|
||||
private const float PAUSE_DUCK_TARGET_DB = -18f;
|
||||
private const float PAUSE_DUCK_FADE_SECONDS = 0.25f;
|
||||
private const float DEFAULT_VOICE_DUCK_TARGET_DB = -20f;
|
||||
|
||||
private readonly AudioMixer _audioMixer;
|
||||
private readonly AudioRegistry _audioRegistry;
|
||||
private readonly AudioMixer _audioMixer;
|
||||
private readonly AudioRegistry _audioRegistry;
|
||||
private readonly EventCoordinator _eventCoordinator;
|
||||
|
||||
private readonly Dictionary<string, float> _baseDb = new();
|
||||
private readonly List<GameObject> _createdAudioObjects = new();
|
||||
private readonly List<AudioSource> _ambienceSources = new();
|
||||
private readonly List<AudioFileSo> _currentAmbienceTracks = new();
|
||||
private readonly List<SfxChannel> _sfxChannels = new();
|
||||
private readonly Dictionary<string, float> _baseDb = new();
|
||||
private readonly List<GameObject> _createdAudioObjects = new();
|
||||
private readonly List<SfxChannel> _sfxChannels = new();
|
||||
|
||||
private AudioSource _musicSourceA;
|
||||
private AudioSource _musicSourceB;
|
||||
private AudioSource _voiceSource;
|
||||
private AudioSource _uiSource;
|
||||
|
||||
private string _activeVoiceSubtitleId;
|
||||
private string _activeVoiceSubtitleId;
|
||||
private AudioFileSo _currentMusicTrack;
|
||||
|
||||
private CancellationTokenSource _musicDuckCts;
|
||||
private CancellationTokenSource _musicFadeCts;
|
||||
private CancellationTokenSource _voiceCts;
|
||||
|
||||
private float _musicDuckDbCurrent;
|
||||
private float _pauseDuckDbCurrent;
|
||||
|
||||
private float _musicDuckDbCurrent;
|
||||
private float _pauseDuckDbCurrent;
|
||||
private Sequence _musicDuckSequence;
|
||||
private Sequence _pauseDuckSequence;
|
||||
|
||||
@@ -69,8 +65,8 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
[Inject]
|
||||
public AudioManager(AudioMixer mainMixer, AudioRegistry audioRegistry, EventCoordinator eventCoordinator)
|
||||
{
|
||||
_audioMixer = mainMixer;
|
||||
_audioRegistry = audioRegistry;
|
||||
_audioMixer = mainMixer;
|
||||
_audioRegistry = audioRegistry;
|
||||
_eventCoordinator = eventCoordinator;
|
||||
}
|
||||
|
||||
@@ -137,53 +133,41 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
}
|
||||
|
||||
_createdAudioObjects.Clear();
|
||||
_ambienceSources.Clear();
|
||||
_sfxChannels.Clear();
|
||||
_currentAmbienceTracks.Clear();
|
||||
_baseDb.Clear();
|
||||
|
||||
_musicSourceA = null;
|
||||
_musicSourceB = null;
|
||||
_voiceSource = null;
|
||||
_uiSource = null;
|
||||
_currentMusicTrack = null;
|
||||
_activeVoiceSubtitleId = null;
|
||||
_musicSourceA = null;
|
||||
_musicSourceB = null;
|
||||
_voiceSource = null;
|
||||
_currentMusicTrack = null;
|
||||
_activeVoiceSubtitleId = null;
|
||||
_voiceFinishedPublished = false;
|
||||
Initialized = false;
|
||||
Initialized = false;
|
||||
}
|
||||
|
||||
// ── Source creation ───────────────────────────────────────────
|
||||
|
||||
private void CreateSources()
|
||||
{
|
||||
_musicSourceA = CreateAudioSource("Music_Source_A", AudioMixerGroups.MUSIC_GROUP);
|
||||
_musicSourceB = CreateAudioSource("Music_Source_B", AudioMixerGroups.MUSIC_GROUP);
|
||||
|
||||
_voiceSource = CreateAudioSource("Voice_Source", AudioMixerGroups.VOICE_GROUP);
|
||||
_uiSource = CreateAudioSource("UI_Source", AudioMixerGroups.UI_GROUP);
|
||||
_voiceSource = CreateAudioSource("Voice_Source", AudioMixerGroups.VOICE_GROUP);
|
||||
|
||||
for (var i = 0; i < INITIAL_SFX_SOURCES; i++)
|
||||
{
|
||||
var src = CreateAudioSource($"SFX_Source_{i}", AudioMixerGroups.SFX_GROUP);
|
||||
_sfxChannels.Add(new SfxChannel
|
||||
{
|
||||
Source = src,
|
||||
StartedAtUnscaled = -999f
|
||||
});
|
||||
}
|
||||
|
||||
for (var i = 0; i < INITIAL_AMBIENCE_SOURCES; i++)
|
||||
{
|
||||
_ambienceSources.Add(CreateAudioSource($"Ambience_Source_{i}", AudioMixerGroups.AMBIENCE_GROUP));
|
||||
_sfxChannels.Add(new SfxChannel { Source = src, StartedAtUnscaled = -999f });
|
||||
}
|
||||
}
|
||||
|
||||
// ── Volume ────────────────────────────────────────────────────
|
||||
|
||||
private void PrimeMixerBaseValues()
|
||||
{
|
||||
PrimeBaseFromMixer(AudioMixerParameters.MASTER_VOLUME);
|
||||
PrimeBaseFromMixer(AudioMixerParameters.MUSIC_VOLUME);
|
||||
PrimeBaseFromMixer(AudioMixerParameters.SFX_VOLUME);
|
||||
PrimeBaseFromMixer(AudioMixerParameters.AMBIENCE_VOLUME);
|
||||
PrimeBaseFromMixer(AudioMixerParameters.VOICE_VOLUME);
|
||||
PrimeBaseFromMixer(AudioMixerParameters.UI_VOLUME);
|
||||
}
|
||||
|
||||
public void SetVolume(string parameter, float value01)
|
||||
@@ -194,25 +178,18 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
return;
|
||||
}
|
||||
|
||||
var linear = Mathf.Clamp01(value01);
|
||||
var db = Linear01ToDb(linear);
|
||||
|
||||
_baseDb[parameter] = db;
|
||||
_baseDb[parameter] = Linear01ToDb(Mathf.Clamp01(value01));
|
||||
ApplyEffectiveVolume(parameter);
|
||||
}
|
||||
|
||||
private static float Linear01ToDb(float linear01)
|
||||
{
|
||||
var lin = Mathf.Max(linear01, 0.0001f);
|
||||
return Mathf.Log10(lin) * 20f;
|
||||
return Mathf.Log10(Mathf.Max(linear01, 0.0001f)) * 20f;
|
||||
}
|
||||
|
||||
private void PrimeBaseFromMixer(string parameter)
|
||||
{
|
||||
if (_audioMixer != null && _audioMixer.GetFloat(parameter, out var db))
|
||||
_baseDb[parameter] = db;
|
||||
else
|
||||
_baseDb[parameter] = 0f;
|
||||
_baseDb[parameter] = _audioMixer != null && _audioMixer.GetFloat(parameter, out var db) ? db : 0f;
|
||||
}
|
||||
|
||||
private void ApplyAllEffectiveVolumes()
|
||||
@@ -220,9 +197,7 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
ApplyEffectiveVolume(AudioMixerParameters.MASTER_VOLUME);
|
||||
ApplyEffectiveVolume(AudioMixerParameters.MUSIC_VOLUME);
|
||||
ApplyEffectiveVolume(AudioMixerParameters.SFX_VOLUME);
|
||||
ApplyEffectiveVolume(AudioMixerParameters.AMBIENCE_VOLUME);
|
||||
ApplyEffectiveVolume(AudioMixerParameters.VOICE_VOLUME);
|
||||
ApplyEffectiveVolume(AudioMixerParameters.UI_VOLUME);
|
||||
}
|
||||
|
||||
private void ApplyEffectiveVolume(string parameter)
|
||||
@@ -236,8 +211,7 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
var effective = baseDb;
|
||||
|
||||
if (parameter == AudioMixerParameters.MUSIC_VOLUME ||
|
||||
parameter == AudioMixerParameters.SFX_VOLUME ||
|
||||
parameter == AudioMixerParameters.AMBIENCE_VOLUME)
|
||||
parameter == AudioMixerParameters.SFX_VOLUME)
|
||||
{
|
||||
effective += _pauseDuckDbCurrent;
|
||||
}
|
||||
@@ -248,10 +222,11 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
_audioMixer.SetFloat(parameter, effective);
|
||||
}
|
||||
|
||||
// ── UI stack / pause duck ─────────────────────────────────────
|
||||
|
||||
private void OnUIStackChanged(UIStackChangedEvent e)
|
||||
{
|
||||
if (!Initialized)
|
||||
return;
|
||||
if (!Initialized) return;
|
||||
|
||||
if (e.AnyUIOpen)
|
||||
OnGamePausedInternal().Forget();
|
||||
@@ -279,20 +254,18 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
_pauseDuckSequence = default;
|
||||
}
|
||||
|
||||
seconds = Mathf.Max(0f, seconds);
|
||||
|
||||
var from = _pauseDuckDbCurrent;
|
||||
|
||||
_pauseDuckSequence = Sequence.Create(useUnscaledTime: true)
|
||||
.Group(Tween.Custom(
|
||||
from,
|
||||
targetDb,
|
||||
seconds,
|
||||
Mathf.Max(0f, seconds),
|
||||
v =>
|
||||
{
|
||||
_pauseDuckDbCurrent = v;
|
||||
ApplyEffectiveVolume(AudioMixerParameters.MUSIC_VOLUME);
|
||||
ApplyEffectiveVolume(AudioMixerParameters.SFX_VOLUME);
|
||||
ApplyEffectiveVolume(AudioMixerParameters.AMBIENCE_VOLUME);
|
||||
},
|
||||
Ease.OutCubic,
|
||||
useUnscaledTime: true));
|
||||
@@ -303,15 +276,13 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
|
||||
public void PauseVoiceSource(bool paused)
|
||||
{
|
||||
if (!Initialized || _voiceSource == null)
|
||||
return;
|
||||
|
||||
if (paused)
|
||||
_voiceSource.Pause();
|
||||
else
|
||||
_voiceSource.UnPause();
|
||||
if (!Initialized || _voiceSource == null) return;
|
||||
if (paused) _voiceSource.Pause();
|
||||
else _voiceSource.UnPause();
|
||||
}
|
||||
|
||||
// ── Play ──────────────────────────────────────────────────────
|
||||
|
||||
public void Play(string audioName)
|
||||
{
|
||||
if (!Initialized)
|
||||
@@ -344,21 +315,11 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
break;
|
||||
|
||||
case TrackType.Ambience:
|
||||
if (!_currentAmbienceTracks.Contains(audioData))
|
||||
{
|
||||
_currentAmbienceTracks.Add(audioData);
|
||||
PlayOnAvailableAmbienceSource(audioData);
|
||||
}
|
||||
break;
|
||||
|
||||
case TrackType.UIFX:
|
||||
case TrackType.Sfx:
|
||||
PlaySfx(audioData);
|
||||
break;
|
||||
|
||||
case TrackType.UIFX:
|
||||
PlayOneShotAsync(_uiSource, audioData).Forget();
|
||||
break;
|
||||
|
||||
case TrackType.Voice:
|
||||
PlayVoiceLine(audioData).Forget();
|
||||
break;
|
||||
@@ -368,6 +329,8 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
DuckMusicAsync(audioData.Clip.length, audioData.FadeTime).Forget();
|
||||
}
|
||||
|
||||
// ── Voice ─────────────────────────────────────────────────────
|
||||
|
||||
private async UniTaskVoid PlayVoiceLine(AudioFileSo audioData)
|
||||
{
|
||||
if (!Initialized || _voiceSource == null || audioData?.Clip == null)
|
||||
@@ -377,15 +340,15 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
_voiceCts = new CancellationTokenSource();
|
||||
var token = _voiceCts.Token;
|
||||
|
||||
_activeVoiceSubtitleId = SubtitleIdentifiers.Get(audioData.MatchingSubtitleID);
|
||||
_activeVoiceSubtitleId = SubtitleIdentifiers.Get(audioData.MatchingSubtitleID);
|
||||
_voiceFinishedPublished = false;
|
||||
|
||||
_eventCoordinator.Publish(new VoiceLineStartedEvent(_activeVoiceSubtitleId));
|
||||
|
||||
_voiceSource.clip = audioData.Clip;
|
||||
_voiceSource.pitch = audioData.Pitch;
|
||||
_voiceSource.volume = audioData.Volume;
|
||||
_voiceSource.loop = false;
|
||||
_voiceSource.clip = audioData.Clip;
|
||||
_voiceSource.pitch = audioData.Pitch;
|
||||
_voiceSource.volume = audioData.Volume;
|
||||
_voiceSource.loop = false;
|
||||
_voiceSource.priority = audioData.Priority;
|
||||
_voiceSource.Play();
|
||||
|
||||
@@ -404,31 +367,210 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
|
||||
private void PublishVoiceFinishedIfNeeded()
|
||||
{
|
||||
if (_voiceFinishedPublished)
|
||||
return;
|
||||
if (_voiceFinishedPublished) return;
|
||||
|
||||
if (!string.IsNullOrEmpty(_activeVoiceSubtitleId))
|
||||
_eventCoordinator.Publish(new VoiceLineFinishedEvent(_activeVoiceSubtitleId));
|
||||
|
||||
_voiceFinishedPublished = true;
|
||||
_activeVoiceSubtitleId = null;
|
||||
_activeVoiceSubtitleId = null;
|
||||
}
|
||||
|
||||
public void StopVoice()
|
||||
{
|
||||
if (!Initialized) return;
|
||||
|
||||
StopAndDispose(ref _voiceCts);
|
||||
|
||||
if (_voiceSource != null && _voiceSource.isPlaying)
|
||||
_voiceSource.Stop();
|
||||
|
||||
PublishVoiceFinishedIfNeeded();
|
||||
}
|
||||
|
||||
// ── SFX (transient pool) ──────────────────────────────────────
|
||||
|
||||
private void PlaySfx(AudioFileSo audioData)
|
||||
{
|
||||
if (!Initialized || audioData == null || audioData.Clip == null)
|
||||
return;
|
||||
|
||||
// Reap finished channels first so we don't accumulate stale entries
|
||||
ReapFinishedSfxChannels();
|
||||
|
||||
// Try to find a free channel from the existing pool
|
||||
AudioSource src = null;
|
||||
var channelIndex = -1;
|
||||
|
||||
for (var i = 0; i < _sfxChannels.Count; i++)
|
||||
{
|
||||
var s = _sfxChannels[i].Source;
|
||||
if (s != null && !s.isPlaying)
|
||||
{
|
||||
src = s;
|
||||
channelIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// No free channel — spawn a transient one
|
||||
if (src == null)
|
||||
{
|
||||
src = CreateAudioSource($"SFX_Source_Transient_{_sfxChannels.Count}", AudioMixerGroups.SFX_GROUP);
|
||||
_sfxChannels.Add(new SfxChannel { Source = src, StartedAtUnscaled = -999f });
|
||||
channelIndex = _sfxChannels.Count - 1;
|
||||
|
||||
Debug.Log($"[AudioManager] SFX pool expanded to {_sfxChannels.Count} channels.");
|
||||
}
|
||||
|
||||
src.priority = audioData.Priority;
|
||||
src.pitch = audioData.Pitch;
|
||||
src.loop = audioData.Loopable;
|
||||
src.PlayOneShot(audioData.Clip, audioData.Volume);
|
||||
|
||||
_sfxChannels[channelIndex] = new SfxChannel
|
||||
{
|
||||
Source = src,
|
||||
StartedAtUnscaled = Time.unscaledTime
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes finished transient channels from the pool to prevent unbounded growth.
|
||||
/// Preserves the initial pool channels even when idle.
|
||||
/// </summary>
|
||||
private void ReapFinishedSfxChannels()
|
||||
{
|
||||
for (var i = _sfxChannels.Count - 1; i >= INITIAL_SFX_SOURCES; i--)
|
||||
{
|
||||
var src = _sfxChannels[i].Source;
|
||||
if (src == null || src.isPlaying)
|
||||
continue;
|
||||
|
||||
// Destroy the transient GameObject and remove from pool
|
||||
if (src.gameObject != null)
|
||||
{
|
||||
_createdAudioObjects.Remove(src.gameObject);
|
||||
Object.Destroy(src.gameObject);
|
||||
}
|
||||
|
||||
_sfxChannels.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
public void StopAllSfx()
|
||||
{
|
||||
if (!Initialized) return;
|
||||
|
||||
for (var i = _sfxChannels.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var src = _sfxChannels[i].Source;
|
||||
if (src == null) continue;
|
||||
|
||||
src.Stop();
|
||||
|
||||
// Destroy transient channels, reset initial ones
|
||||
if (i >= INITIAL_SFX_SOURCES)
|
||||
{
|
||||
if (src.gameObject != null)
|
||||
{
|
||||
_createdAudioObjects.Remove(src.gameObject);
|
||||
Object.Destroy(src.gameObject);
|
||||
}
|
||||
|
||||
_sfxChannels.RemoveAt(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
_sfxChannels[i] = new SfxChannel { Source = src, StartedAtUnscaled = -999f };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Music ─────────────────────────────────────────────────────
|
||||
|
||||
public async UniTask CrossfadeMusic(AudioFileSo newTrack, float duration)
|
||||
{
|
||||
if (!Initialized || !newTrack || !newTrack.Clip) return;
|
||||
if (_currentMusicTrack == newTrack) return;
|
||||
|
||||
StopAndDispose(ref _musicFadeCts);
|
||||
_musicFadeCts = new CancellationTokenSource();
|
||||
var token = _musicFadeCts.Token;
|
||||
|
||||
var activeSource = _musicSourceA.isPlaying ? _musicSourceA
|
||||
: _musicSourceB.isPlaying ? _musicSourceB
|
||||
: null;
|
||||
|
||||
var inactiveSource = activeSource == _musicSourceA ? _musicSourceB : _musicSourceA;
|
||||
|
||||
PlayOnSource(inactiveSource, newTrack);
|
||||
|
||||
if (activeSource == null)
|
||||
{
|
||||
inactiveSource.volume = newTrack.Volume;
|
||||
_currentMusicTrack = newTrack;
|
||||
_eventCoordinator.Publish(new MusicTrackChangedEvent(newTrack));
|
||||
return;
|
||||
}
|
||||
|
||||
duration = Mathf.Max(0.0001f, duration);
|
||||
|
||||
var elapsed = 0f;
|
||||
var startVolume = activeSource.volume;
|
||||
|
||||
try
|
||||
{
|
||||
while (elapsed < duration)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
var t = elapsed / duration;
|
||||
activeSource.volume = Mathf.Lerp(startVolume, 0f, t);
|
||||
inactiveSource.volume = Mathf.Lerp(0f, newTrack.Volume, t);
|
||||
elapsed += Time.unscaledDeltaTime;
|
||||
await UniTask.Yield(PlayerLoopTiming.Update, token);
|
||||
}
|
||||
|
||||
activeSource.volume = 0f;
|
||||
inactiveSource.volume = newTrack.Volume;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
activeSource.Stop();
|
||||
activeSource.volume = startVolume;
|
||||
|
||||
_currentMusicTrack = newTrack;
|
||||
_eventCoordinator.Publish(new MusicTrackChangedEvent(newTrack));
|
||||
}
|
||||
|
||||
public void StopMusic()
|
||||
{
|
||||
if (!Initialized) return;
|
||||
|
||||
StopAndDispose(ref _musicFadeCts);
|
||||
|
||||
if (_musicSourceA != null) { _musicSourceA.Stop(); _musicSourceA.clip = null; _musicSourceA.volume = 0f; }
|
||||
if (_musicSourceB != null) { _musicSourceB.Stop(); _musicSourceB.clip = null; _musicSourceB.volume = 0f; }
|
||||
|
||||
_currentMusicTrack = null;
|
||||
}
|
||||
|
||||
private async UniTask DuckMusicAsync(float clipLengthSeconds, float fadeTimeSeconds)
|
||||
{
|
||||
if (!Initialized)
|
||||
return;
|
||||
if (!Initialized) return;
|
||||
|
||||
StopAndDispose(ref _musicDuckCts);
|
||||
_musicDuckCts = new CancellationTokenSource();
|
||||
var token = _musicDuckCts.Token;
|
||||
|
||||
fadeTimeSeconds = Mathf.Max(0.0001f, fadeTimeSeconds);
|
||||
var duckTarget = DEFAULT_VOICE_DUCK_TARGET_DB;
|
||||
|
||||
try
|
||||
{
|
||||
await TweenMusicDuckTo(duckTarget, fadeTimeSeconds, token);
|
||||
await TweenMusicDuckTo(DEFAULT_VOICE_DUCK_TARGET_DB, fadeTimeSeconds, token);
|
||||
|
||||
var hold = clipLengthSeconds - fadeTimeSeconds * 2f;
|
||||
if (hold > 0.01f)
|
||||
@@ -450,15 +592,13 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
_musicDuckSequence = default;
|
||||
}
|
||||
|
||||
seconds = Mathf.Max(0f, seconds);
|
||||
|
||||
var from = _musicDuckDbCurrent;
|
||||
|
||||
_musicDuckSequence = Sequence.Create(useUnscaledTime: true)
|
||||
.Group(Tween.Custom(
|
||||
from,
|
||||
targetDb,
|
||||
seconds,
|
||||
Mathf.Max(0f, seconds),
|
||||
v =>
|
||||
{
|
||||
_musicDuckDbCurrent = v;
|
||||
@@ -471,258 +611,22 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
_musicDuckSequence = default;
|
||||
}
|
||||
|
||||
public async UniTask CrossfadeMusic(AudioFileSo newTrack, float duration)
|
||||
{
|
||||
if (!Initialized || !newTrack || !newTrack.Clip)
|
||||
return;
|
||||
|
||||
if (_currentMusicTrack == newTrack)
|
||||
return;
|
||||
|
||||
StopAndDispose(ref _musicFadeCts);
|
||||
_musicFadeCts = new CancellationTokenSource();
|
||||
var token = _musicFadeCts.Token;
|
||||
|
||||
var activeSource = _musicSourceA.isPlaying
|
||||
? _musicSourceA
|
||||
: _musicSourceB.isPlaying
|
||||
? _musicSourceB
|
||||
: null;
|
||||
|
||||
var inactiveSource = activeSource == _musicSourceA ? _musicSourceB : _musicSourceA;
|
||||
|
||||
PlayOnSource(inactiveSource, newTrack);
|
||||
|
||||
if (activeSource == null)
|
||||
{
|
||||
inactiveSource.volume = newTrack.Volume;
|
||||
_currentMusicTrack = newTrack;
|
||||
_eventCoordinator.Publish(new MusicTrackChangedEvent(newTrack));
|
||||
return;
|
||||
}
|
||||
|
||||
duration = Mathf.Max(0.0001f, duration);
|
||||
|
||||
var elapsed = 0f;
|
||||
var startVolume = activeSource.volume;
|
||||
|
||||
try
|
||||
{
|
||||
while (elapsed < duration)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var t = elapsed / duration;
|
||||
activeSource.volume = Mathf.Lerp(startVolume, 0f, t);
|
||||
inactiveSource.volume = Mathf.Lerp(0f, newTrack.Volume, t);
|
||||
|
||||
elapsed += Time.unscaledDeltaTime;
|
||||
await UniTask.Yield(PlayerLoopTiming.Update, token);
|
||||
}
|
||||
|
||||
activeSource.volume = 0f;
|
||||
inactiveSource.volume = newTrack.Volume;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
activeSource.Stop();
|
||||
activeSource.volume = startVolume;
|
||||
|
||||
_currentMusicTrack = newTrack;
|
||||
_eventCoordinator.Publish(new MusicTrackChangedEvent(newTrack));
|
||||
}
|
||||
|
||||
private void PlaySfx(AudioFileSo audioData)
|
||||
{
|
||||
if (!Initialized || audioData == null || audioData.Clip == null)
|
||||
return;
|
||||
|
||||
var channelIndex = GetBestSfxChannelIndex(audioData.Priority);
|
||||
if (channelIndex < 0 || channelIndex >= _sfxChannels.Count)
|
||||
return;
|
||||
|
||||
var src = _sfxChannels[channelIndex].Source;
|
||||
if (src == null)
|
||||
return;
|
||||
|
||||
if (src.isPlaying)
|
||||
src.Stop();
|
||||
|
||||
src.priority = audioData.Priority;
|
||||
src.pitch = audioData.Pitch;
|
||||
src.PlayOneShot(audioData.Clip, audioData.Volume);
|
||||
|
||||
_sfxChannels[channelIndex] = new SfxChannel
|
||||
{
|
||||
Source = src,
|
||||
StartedAtUnscaled = Time.unscaledTime
|
||||
};
|
||||
}
|
||||
|
||||
private int GetBestSfxChannelIndex(int incomingPriority)
|
||||
{
|
||||
for (var i = 0; i < _sfxChannels.Count; i++)
|
||||
{
|
||||
var src = _sfxChannels[i].Source;
|
||||
if (src == null)
|
||||
continue;
|
||||
|
||||
if (!src.isPlaying)
|
||||
return i;
|
||||
}
|
||||
|
||||
var bestIndex = -1;
|
||||
var worstPriority = int.MinValue;
|
||||
var oldestStart = float.MaxValue;
|
||||
|
||||
for (var i = 0; i < _sfxChannels.Count; i++)
|
||||
{
|
||||
var src = _sfxChannels[i].Source;
|
||||
if (src == null)
|
||||
continue;
|
||||
|
||||
var p = src.priority;
|
||||
var started = _sfxChannels[i].StartedAtUnscaled;
|
||||
|
||||
if (p > worstPriority || (p == worstPriority && started < oldestStart))
|
||||
{
|
||||
worstPriority = p;
|
||||
oldestStart = started;
|
||||
bestIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
return bestIndex;
|
||||
}
|
||||
|
||||
public void StopAmbience(AudioFileSo audioData)
|
||||
{
|
||||
if (!Initialized || !audioData || !audioData.Clip || audioData.Type != TrackType.Ambience)
|
||||
return;
|
||||
|
||||
if (_currentAmbienceTracks.Remove(audioData))
|
||||
{
|
||||
foreach (var source in _ambienceSources.Where(s => s != null && s.clip == audioData.Clip))
|
||||
source.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
public void StopAllAmbience()
|
||||
{
|
||||
if (!Initialized)
|
||||
return;
|
||||
|
||||
foreach (var s in _ambienceSources)
|
||||
{
|
||||
if (s != null)
|
||||
s.Stop();
|
||||
}
|
||||
|
||||
_currentAmbienceTracks.Clear();
|
||||
}
|
||||
|
||||
private void PlayOnAvailableAmbienceSource(AudioFileSo audioData)
|
||||
{
|
||||
var source = _ambienceSources.FirstOrDefault(s => s != null && !s.isPlaying);
|
||||
if (source == null)
|
||||
{
|
||||
source = CreateAudioSource(
|
||||
$"Ambience_Source_{_ambienceSources.Count}",
|
||||
AudioMixerGroups.AMBIENCE_GROUP);
|
||||
|
||||
_ambienceSources.Add(source);
|
||||
}
|
||||
|
||||
PlayOnSource(source, audioData);
|
||||
}
|
||||
|
||||
public void StopMusic()
|
||||
{
|
||||
if (!Initialized)
|
||||
return;
|
||||
|
||||
StopAndDispose(ref _musicFadeCts);
|
||||
|
||||
if (_musicSourceA != null)
|
||||
{
|
||||
_musicSourceA.Stop();
|
||||
_musicSourceA.clip = null;
|
||||
_musicSourceA.volume = 0f;
|
||||
}
|
||||
|
||||
if (_musicSourceB != null)
|
||||
{
|
||||
_musicSourceB.Stop();
|
||||
_musicSourceB.clip = null;
|
||||
_musicSourceB.volume = 0f;
|
||||
}
|
||||
|
||||
_currentMusicTrack = null;
|
||||
}
|
||||
|
||||
public void StopVoice()
|
||||
{
|
||||
if (!Initialized)
|
||||
return;
|
||||
|
||||
StopAndDispose(ref _voiceCts);
|
||||
|
||||
if (_voiceSource != null && _voiceSource.isPlaying)
|
||||
_voiceSource.Stop();
|
||||
|
||||
PublishVoiceFinishedIfNeeded();
|
||||
}
|
||||
|
||||
public void StopAllSfx()
|
||||
{
|
||||
if (!Initialized)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < _sfxChannels.Count; i++)
|
||||
{
|
||||
var src = _sfxChannels[i].Source;
|
||||
if (src == null)
|
||||
continue;
|
||||
|
||||
src.Stop();
|
||||
_sfxChannels[i] = new SfxChannel
|
||||
{
|
||||
Source = src,
|
||||
StartedAtUnscaled = -999f
|
||||
};
|
||||
}
|
||||
}
|
||||
// ── Stop all ──────────────────────────────────────────────────
|
||||
|
||||
public void StopAllAudio()
|
||||
{
|
||||
if (!Initialized)
|
||||
return;
|
||||
|
||||
if (!Initialized) return;
|
||||
StopMusic();
|
||||
StopVoice();
|
||||
StopAllSfx();
|
||||
StopAllAmbience();
|
||||
|
||||
if (_uiSource != null)
|
||||
_uiSource.Stop();
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────
|
||||
|
||||
private static void StopAndDispose(ref CancellationTokenSource cts)
|
||||
{
|
||||
if (cts == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
cts.Cancel();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
if (cts == null) return;
|
||||
try { cts.Cancel(); } catch { }
|
||||
cts.Dispose();
|
||||
cts = null;
|
||||
}
|
||||
@@ -733,7 +637,7 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
Object.DontDestroyOnLoad(obj);
|
||||
_createdAudioObjects.Add(obj);
|
||||
|
||||
var src = obj.AddComponent<AudioSource>();
|
||||
var src = obj.AddComponent<AudioSource>();
|
||||
var group = _audioMixer.FindMatchingGroups(groupName);
|
||||
|
||||
if (group != null && group.Length > 0)
|
||||
@@ -744,28 +648,14 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
return src;
|
||||
}
|
||||
|
||||
private async UniTaskVoid PlayOneShotAsync(AudioSource source, AudioFileSo audioData)
|
||||
{
|
||||
if (!Initialized || source == null || audioData == null || audioData.Clip == null)
|
||||
return;
|
||||
|
||||
source.priority = audioData.Priority;
|
||||
source.pitch = audioData.Pitch;
|
||||
source.PlayOneShot(audioData.Clip, audioData.Volume);
|
||||
|
||||
var seconds = audioData.Clip.length / Mathf.Max(audioData.Pitch, 0.0001f);
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(seconds));
|
||||
}
|
||||
|
||||
private void PlayOnSource(AudioSource source, AudioFileSo audioData)
|
||||
{
|
||||
if (!Initialized || source == null || audioData == null)
|
||||
return;
|
||||
if (!Initialized || source == null || audioData == null) return;
|
||||
|
||||
source.clip = audioData.Clip;
|
||||
source.loop = audioData.Loopable;
|
||||
source.volume = audioData.Volume;
|
||||
source.pitch = audioData.Pitch;
|
||||
source.clip = audioData.Clip;
|
||||
source.loop = audioData.Loopable;
|
||||
source.volume = audioData.Volume;
|
||||
source.pitch = audioData.Pitch;
|
||||
source.priority = audioData.Priority;
|
||||
source.Play();
|
||||
}
|
||||
@@ -773,7 +663,7 @@ namespace BriarQueen.Framework.Managers.Audio
|
||||
private struct SfxChannel
|
||||
{
|
||||
public AudioSource Source;
|
||||
public float StartedAtUnscaled;
|
||||
public float StartedAtUnscaled;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BriarQueen.Data.Identifiers;
|
||||
using BriarQueen.Data.IO.Saves;
|
||||
using BriarQueen.Framework.Managers.Input;
|
||||
using BriarQueen.Framework.Managers.IO;
|
||||
using BriarQueen.Framework.Managers.Player;
|
||||
using NaughtyAttributes;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using VContainer;
|
||||
|
||||
namespace BriarQueen.Framework.Managers
|
||||
@@ -13,6 +16,7 @@ namespace BriarQueen.Framework.Managers
|
||||
{
|
||||
private SaveManager _saveManager;
|
||||
private PlayerManager _playerManager;
|
||||
private InputManager _inputManager;
|
||||
|
||||
[Header("Current Loaded Save")]
|
||||
[SerializeField, ReadOnly]
|
||||
@@ -23,10 +27,11 @@ namespace BriarQueen.Framework.Managers
|
||||
private ItemKey _itemToGive;
|
||||
|
||||
[Inject]
|
||||
public void Construct(SaveManager saveManager, PlayerManager playerManager)
|
||||
public void Construct(SaveManager saveManager, PlayerManager playerManager, InputManager inputManager)
|
||||
{
|
||||
_saveManager = saveManager;
|
||||
_playerManager = playerManager;
|
||||
_inputManager = inputManager;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
@@ -38,6 +43,7 @@ namespace BriarQueen.Framework.Managers
|
||||
{
|
||||
_currentSave = save;
|
||||
}
|
||||
|
||||
|
||||
[Button]
|
||||
private void GiveItem()
|
||||
|
||||
@@ -26,7 +26,6 @@ namespace BriarQueen.Framework.Managers.IO
|
||||
private readonly object _saveLock = new();
|
||||
|
||||
private CancellationTokenSource _currentSaveCts;
|
||||
private DateTime _lastSaveTime;
|
||||
|
||||
[Inject]
|
||||
public SaveManager(EventCoordinator eventCoordinator)
|
||||
@@ -112,12 +111,6 @@ namespace BriarQueen.Framework.Managers.IO
|
||||
|
||||
private async UniTask SaveGameDataInternal(CancellationToken ct)
|
||||
{
|
||||
if ((DateTime.UtcNow - _lastSaveTime).TotalMilliseconds < 250)
|
||||
{
|
||||
Debug.Log("[SaveManager] Last save within 250ms, skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (CurrentSave == null)
|
||||
CurrentSave = new SaveGame { SaveFileName = "NewGame" };
|
||||
|
||||
@@ -186,7 +179,6 @@ namespace BriarQueen.Framework.Managers.IO
|
||||
|
||||
CurrentSave = saveClone;
|
||||
IsGameLoaded = true;
|
||||
_lastSaveTime = DateTime.UtcNow;
|
||||
|
||||
OnSaveGameSaved?.Invoke();
|
||||
Debug.Log($"[SaveManager] Save complete: {CurrentSave.SaveFileName}");
|
||||
@@ -272,8 +264,7 @@ namespace BriarQueen.Framework.Managers.IO
|
||||
|
||||
if (loadedSave != null)
|
||||
{
|
||||
CurrentSave = loadedSave;
|
||||
await SaveGameDataLatest();
|
||||
RestoreBackupToMain(mainPath, backupPath);
|
||||
Debug.Log("[SaveManager] Restored save from backup.");
|
||||
}
|
||||
}
|
||||
@@ -285,6 +276,41 @@ namespace BriarQueen.Framework.Managers.IO
|
||||
OnSaveGameLoaded?.Invoke(CurrentSave);
|
||||
}
|
||||
|
||||
private void RestoreBackupToMain(string mainPath, string backupPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(mainPath) || string.IsNullOrWhiteSpace(backupPath))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var mainDirectory = Path.GetDirectoryName(mainPath);
|
||||
if (!string.IsNullOrWhiteSpace(mainDirectory))
|
||||
Directory.CreateDirectory(mainDirectory);
|
||||
|
||||
var tempRestorePath = mainPath + ".restoretmp";
|
||||
|
||||
if (File.Exists(tempRestorePath))
|
||||
File.Delete(tempRestorePath);
|
||||
|
||||
File.Copy(backupPath, tempRestorePath, overwrite: true);
|
||||
|
||||
if (File.Exists(mainPath))
|
||||
File.Replace(tempRestorePath, mainPath, null, ignoreMetadataErrors: true);
|
||||
else
|
||||
File.Move(tempRestorePath, mainPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"[SaveManager] Failed to restore backup '{backupPath}' to '{mainPath}': {ex}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
var tempRestorePath = mainPath + ".restoretmp";
|
||||
if (File.Exists(tempRestorePath))
|
||||
File.Delete(tempRestorePath);
|
||||
}
|
||||
}
|
||||
|
||||
private async UniTask<SaveGame> LoadFromFileAsync(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path) || !File.Exists(path)) return null;
|
||||
@@ -432,4 +458,4 @@ namespace BriarQueen.Framework.Managers.IO
|
||||
return collected.Any(x => x.UniqueIdentifier == uniqueIdentifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ using BriarQueen.Framework.Managers.UI;
|
||||
using BriarQueen.Framework.Services.Game;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.InputSystem.UI;
|
||||
using VContainer;
|
||||
|
||||
namespace BriarQueen.Framework.Managers.Input
|
||||
@@ -48,6 +47,7 @@ namespace BriarQueen.Framework.Managers.Input
|
||||
|
||||
private bool _initialized;
|
||||
private bool _isPaused;
|
||||
private bool _isAnyUIOpen;
|
||||
private InputAction _pauseAction;
|
||||
|
||||
private InputAction _pointAction;
|
||||
@@ -58,7 +58,7 @@ namespace BriarQueen.Framework.Managers.Input
|
||||
private InputAction _nextItemAction;
|
||||
private InputAction _previousItemAction;
|
||||
private InputAction _virtualMouseAction;
|
||||
private InputAction _submitAction;
|
||||
private InputAction _submitAction;
|
||||
|
||||
private UICursorService _uiCursorService;
|
||||
|
||||
@@ -77,6 +77,8 @@ namespace BriarQueen.Framework.Managers.Input
|
||||
public bool IsPaused => _isPaused;
|
||||
|
||||
public bool UsingControllerCursor => DeviceInputType != DeviceInputType.KeyboardAndMouse;
|
||||
|
||||
public string CurrentControlScheme => _playerInput?.currentControlScheme ?? string.Empty;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
@@ -109,6 +111,7 @@ namespace BriarQueen.Framework.Managers.Input
|
||||
_eventCoordinator.Unsubscribe<UIToggleHudEvent>(OnHudStateChanged);
|
||||
_eventCoordinator.Unsubscribe<ToggleCodexEvent>(OnCodexStateChanged);
|
||||
_eventCoordinator.Unsubscribe<ToggleToolScreenEvent>(OnToolScreenStateChanged);
|
||||
_eventCoordinator.Unsubscribe<UIStackChangedEvent>(OnUIStackChanged);
|
||||
}
|
||||
|
||||
UnbindCoreInputs();
|
||||
@@ -148,28 +151,18 @@ namespace BriarQueen.Framework.Managers.Input
|
||||
return;
|
||||
}
|
||||
|
||||
if (_playerInput.actions == null)
|
||||
{
|
||||
Debug.LogWarning("[InputManager] PlayerInput.actions is null");
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log($"[InputManager] Current map before cache: {ActiveActionMap}");
|
||||
|
||||
CacheActions();
|
||||
|
||||
Debug.Log($"[InputManager] Point action: {_pointAction}");
|
||||
Debug.Log($"[InputManager] Click action: {_clickAction}");
|
||||
Debug.Log($"[InputManager] Virtual_Mouse action: {_virtualMouseAction}");
|
||||
|
||||
BindCoreInputs();
|
||||
|
||||
DeviceInputType = GetDeviceInputType(_playerInput);
|
||||
ApplyCursorModeForCurrentScheme();
|
||||
|
||||
_initialized = true;
|
||||
_eventCoordinator.Subscribe<UIToggleHudEvent>(OnHudStateChanged);
|
||||
_eventCoordinator.Subscribe<ToggleCodexEvent>(OnCodexStateChanged);
|
||||
_eventCoordinator.Subscribe<ToggleToolScreenEvent>(OnToolScreenStateChanged);
|
||||
_eventCoordinator.Subscribe<UIStackChangedEvent>(OnUIStackChanged);
|
||||
|
||||
Debug.Log("[InputManager] Initialization complete");
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
private void CacheActions()
|
||||
@@ -205,14 +198,12 @@ namespace BriarQueen.Framework.Managers.Input
|
||||
{
|
||||
if (_pointAction != null)
|
||||
{
|
||||
Debug.Log("[InputManager] Binding Point");
|
||||
|
||||
|
||||
_pointAction.performed += OnPoint;
|
||||
_pointAction.canceled += OnPoint;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[InputManager] Required action 'Point' not found.");
|
||||
}
|
||||
|
||||
if (_virtualMouseAction != null)
|
||||
@@ -223,53 +214,33 @@ namespace BriarQueen.Framework.Managers.Input
|
||||
|
||||
if (_pauseAction != null)
|
||||
_pauseAction.performed += OnPause;
|
||||
else
|
||||
Debug.LogWarning("[InputManager] Action 'Pause' not found.");
|
||||
|
||||
if (_clickAction != null)
|
||||
_clickAction.performed += OnClick;
|
||||
else
|
||||
Debug.LogWarning("[InputManager] Action 'Click' not found.");
|
||||
|
||||
if (_rightClickAction != null)
|
||||
_rightClickAction.performed += OnRightClick;
|
||||
else
|
||||
Debug.LogWarning("[InputManager] Action 'Right_Click' not found.");
|
||||
|
||||
if (_hideHudAction != null)
|
||||
_hideHudAction.performed += OnHideHUD;
|
||||
else
|
||||
Debug.LogWarning("[InputManager] Action 'Hide_HUD' not found.");
|
||||
|
||||
if (_codexAction != null)
|
||||
_codexAction.performed += OnCodex;
|
||||
else
|
||||
Debug.LogWarning("[InputManager] Action 'Codex' not found.");
|
||||
|
||||
if (_openToolsAction != null)
|
||||
_openToolsAction.performed += OnOpenTools;
|
||||
else
|
||||
Debug.LogWarning("[InputManager] Action 'Show_Tools' not found.");
|
||||
|
||||
if (_nextToolAction != null)
|
||||
_nextToolAction.performed += OnNextToolClicked;
|
||||
else
|
||||
Debug.LogWarning("[InputManager] Action 'Next_Tool' not found.");
|
||||
|
||||
if (_previousToolAction != null)
|
||||
_previousToolAction.performed += OnPreviousToolClicked;
|
||||
else
|
||||
Debug.LogWarning("[InputManager] Action 'Previous_Tool' not found.");
|
||||
|
||||
if (_nextItemAction != null)
|
||||
_nextItemAction.performed += OnNextItemClicked;
|
||||
else
|
||||
Debug.LogWarning("[InputManager] Action 'Next_Item' not found.");
|
||||
|
||||
if (_previousItemAction != null)
|
||||
_previousItemAction.performed += OnPreviousItemClicked;
|
||||
else
|
||||
Debug.LogWarning("[InputManager] Action 'Previous_Item' not found.");
|
||||
|
||||
if (_playerInput != null)
|
||||
_playerInput.onControlsChanged += OnControlsChanged;
|
||||
@@ -343,7 +314,7 @@ namespace BriarQueen.Framework.Managers.Input
|
||||
}
|
||||
|
||||
private void OnControlsChanged(PlayerInput playerInput)
|
||||
{ Debug.Log($"Controls changed. Scheme: {playerInput.currentControlScheme}");
|
||||
{
|
||||
DeviceInputType = GetDeviceInputType(playerInput);
|
||||
ApplyCursorModeForCurrentScheme();
|
||||
}
|
||||
@@ -391,7 +362,7 @@ namespace BriarQueen.Framework.Managers.Input
|
||||
{
|
||||
if (_submitAction == null || callback == null)
|
||||
return;
|
||||
|
||||
|
||||
_submitAction.performed -= callback;
|
||||
}
|
||||
|
||||
@@ -401,11 +372,6 @@ namespace BriarQueen.Framework.Managers.Input
|
||||
return;
|
||||
|
||||
var action = GetCachedAction(actionName);
|
||||
if (action == null)
|
||||
{
|
||||
Debug.LogWarning($"[InputManager] Action '{actionName}' not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
action.performed -= callback;
|
||||
action.performed += callback;
|
||||
@@ -417,11 +383,6 @@ namespace BriarQueen.Framework.Managers.Input
|
||||
return;
|
||||
|
||||
var action = GetCachedAction(actionName);
|
||||
if (action == null)
|
||||
{
|
||||
Debug.LogWarning($"[InputManager] Action '{actionName}' not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
action.performed -= callback;
|
||||
}
|
||||
@@ -471,18 +432,28 @@ namespace BriarQueen.Framework.Managers.Input
|
||||
_toolScreenShown = evt.Shown;
|
||||
}
|
||||
|
||||
private void OnUIStackChanged(UIStackChangedEvent evt)
|
||||
{
|
||||
_isAnyUIOpen = evt.AnyUIOpen;
|
||||
_isPaused = evt.AnyUIOpen && _gameService != null && !_gameService.IsMainMenuSceneLoaded;
|
||||
}
|
||||
|
||||
private void OnHideHUD(InputAction.CallbackContext ctx)
|
||||
{
|
||||
_hudHidden = !_hudHidden;
|
||||
_eventCoordinator?.PublishImmediate(new UIToggleHudEvent(_hudHidden));
|
||||
_eventCoordinator?.PublishImmediate(new UIToggleHudEvent(!_hudHidden));
|
||||
}
|
||||
|
||||
private void OnPause(InputAction.CallbackContext ctx)
|
||||
{
|
||||
if(_gameService.IsMainMenuSceneLoaded)
|
||||
var isMainMenu = _gameService != null && _gameService.IsMainMenuSceneLoaded;
|
||||
if (isMainMenu || _isAnyUIOpen)
|
||||
{
|
||||
_eventCoordinator?.PublishImmediate(new UIBackRequestedEvent());
|
||||
return;
|
||||
|
||||
_isPaused = !_isPaused;
|
||||
}
|
||||
|
||||
_isPaused = true;
|
||||
_eventCoordinator?.Publish(new PauseButtonClickedEvent());
|
||||
}
|
||||
|
||||
@@ -556,5 +527,6 @@ namespace BriarQueen.Framework.Managers.Input
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using BriarQueen.Data.Identifiers;
|
||||
using BriarQueen.Data.IO.Saves;
|
||||
using BriarQueen.Framework.Assets;
|
||||
using BriarQueen.Framework.Coordinators.Events;
|
||||
using BriarQueen.Framework.Events.Save;
|
||||
using BriarQueen.Framework.Events.UI;
|
||||
using BriarQueen.Framework.Managers.Assets;
|
||||
using BriarQueen.Framework.Managers.Audio;
|
||||
using BriarQueen.Framework.Managers.Interaction.Data;
|
||||
using BriarQueen.Framework.Managers.IO;
|
||||
@@ -31,11 +32,16 @@ namespace BriarQueen.Framework.Managers.Levels.Data
|
||||
|
||||
[Tooltip("Used for custom tooltip. Defaults to Item Name")]
|
||||
[SerializeField]
|
||||
private string _interactableTooltip = string.Empty;
|
||||
protected string _interactableTooltip = string.Empty;
|
||||
[Tooltip("Optional. Used for custom interaction.")]
|
||||
[SerializeField]
|
||||
private string _pickupText = string.Empty;
|
||||
|
||||
[Header("Object Setup")]
|
||||
[SerializeField]
|
||||
protected CanvasGroup _canvasGroup;
|
||||
|
||||
protected bool _isLocked;
|
||||
|
||||
protected AddressableManager AddressableManager;
|
||||
protected AssetRegistry AssetRegistry;
|
||||
@@ -68,8 +74,23 @@ namespace BriarQueen.Framework.Managers.Levels.Data
|
||||
|
||||
public virtual UICursorService.CursorStyle ApplicableCursorStyle => UICursorService.CursorStyle.Pickup;
|
||||
|
||||
public virtual string InteractableName =>
|
||||
!string.IsNullOrWhiteSpace(_interactableTooltip) ? _interactableTooltip : _itemData.ItemName;
|
||||
public virtual string InteractableName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(_interactableTooltip))
|
||||
{
|
||||
return _interactableTooltip;
|
||||
}
|
||||
|
||||
if (_itemData != null && !string.IsNullOrWhiteSpace(_itemData.ItemName))
|
||||
{
|
||||
return _itemData.ItemName;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
@@ -91,6 +112,15 @@ namespace BriarQueen.Framework.Managers.Levels.Data
|
||||
|
||||
await Pickup();
|
||||
await OnInteracted();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_pickupText))
|
||||
{
|
||||
EventCoordinator.Publish(new DisplayInteractEvent(_pickupText));
|
||||
}
|
||||
else
|
||||
{
|
||||
EventCoordinator.Publish(new DisplayInteractEvent(InteractEventIDs.Get(ItemInteractKey.LooksImportant)));
|
||||
}
|
||||
}
|
||||
|
||||
public virtual UniTask EnterHover()
|
||||
@@ -147,8 +177,8 @@ namespace BriarQueen.Framework.Managers.Levels.Data
|
||||
|
||||
protected virtual async UniTask Remove()
|
||||
{
|
||||
// TODO - Play Cut Vines SFX
|
||||
if (_canvasGroup == null) _canvasGroup = GetComponent<CanvasGroup>();
|
||||
if (_canvasGroup == null)
|
||||
_canvasGroup = GetComponent<CanvasGroup>();
|
||||
|
||||
if (PickupSequence.isAlive)
|
||||
{
|
||||
@@ -184,7 +214,7 @@ namespace BriarQueen.Framework.Managers.Levels.Data
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSaveGameOnRemoval()
|
||||
protected virtual void UpdateSaveGameOnRemoval()
|
||||
{
|
||||
var save = SaveManager.CurrentSave;
|
||||
Debug.Log($"[Base Item] Found save - {save.SaveFileName}");
|
||||
@@ -240,5 +270,35 @@ namespace BriarQueen.Framework.Managers.Levels.Data
|
||||
await DestructionService.Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
public void Lock()
|
||||
{
|
||||
_isLocked = true;
|
||||
|
||||
_canvasGroup.blocksRaycasts = false;
|
||||
_canvasGroup.interactable = false;
|
||||
}
|
||||
|
||||
public void Unlock()
|
||||
{
|
||||
_isLocked = false;
|
||||
|
||||
_canvasGroup.blocksRaycasts = true;
|
||||
_canvasGroup.interactable = true;
|
||||
}
|
||||
|
||||
public void OnValidate()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
|
||||
var canvasGroup = GetComponent<CanvasGroup>();
|
||||
|
||||
if (!canvasGroup)
|
||||
{
|
||||
canvasGroup = gameObject.AddComponent<CanvasGroup>();
|
||||
_canvasGroup = canvasGroup;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BriarQueen.Data.Identifiers;
|
||||
using BriarQueen.Framework.Coordinators.Events;
|
||||
using BriarQueen.Framework.Events.UI;
|
||||
@@ -8,12 +10,19 @@ using BriarQueen.Framework.Managers.IO;
|
||||
using BriarQueen.Framework.Managers.Player;
|
||||
using BriarQueen.Framework.Managers.Player.Data.Codex;
|
||||
using BriarQueen.Framework.Services.Destruction;
|
||||
using BriarQueen.Framework.Services.Puzzles.Base;
|
||||
using BriarQueen.Framework.Services.Settings;
|
||||
using BriarQueen.Framework.Services.Tutorials;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using NaughtyAttributes;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using VContainer;
|
||||
using SettingsService = BriarQueen.Framework.Services.Settings.SettingsService;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace BriarQueen.Framework.Managers.Levels.Data
|
||||
{
|
||||
@@ -30,6 +39,10 @@ namespace BriarQueen.Framework.Managers.Levels.Data
|
||||
|
||||
public List<CodexTrigger> CodexTriggers;
|
||||
|
||||
[Header("Puzzles")]
|
||||
[SerializeField]
|
||||
public List<BasePuzzle> Puzzles;
|
||||
|
||||
[Header("Setup")]
|
||||
[SerializeField]
|
||||
protected GraphicRaycaster _raycaster;
|
||||
@@ -47,8 +60,6 @@ namespace BriarQueen.Framework.Managers.Levels.Data
|
||||
|
||||
public virtual string LevelName => _levelName;
|
||||
|
||||
public virtual bool IsPuzzleLevel { get; }
|
||||
|
||||
public virtual int CurrentLevelHintStage { get; set; }
|
||||
|
||||
public virtual Dictionary<int, BaseHint> Hints { get; }
|
||||
@@ -112,5 +123,72 @@ namespace BriarQueen.Framework.Managers.Levels.Data
|
||||
{
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[Button("Discover Level References")]
|
||||
private void DiscoverLevelReferences()
|
||||
{
|
||||
Undo.RecordObject(this, "Discover Level References");
|
||||
|
||||
var discoveredCodexTriggers = GetComponentsInChildren<CodexTrigger>(true)
|
||||
.Where(trigger => trigger != null)
|
||||
.OrderBy(GetHierarchyPath, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
|
||||
var discoveredPickups = GetComponentsInChildren<BaseItem>(true)
|
||||
.Where(item => item != null && item is not CodexTrigger)
|
||||
.OrderBy(GetHierarchyPath, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
|
||||
var discoveredPuzzles = GetComponentsInChildren<BasePuzzle>(true)
|
||||
.Where(puzzle => puzzle != null)
|
||||
.OrderBy(GetHierarchyPath, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
|
||||
Pickups = discoveredPickups;
|
||||
CodexTriggers = discoveredCodexTriggers;
|
||||
Puzzles = discoveredPuzzles;
|
||||
|
||||
EditorUtility.SetDirty(this);
|
||||
PrefabUtility.RecordPrefabInstancePropertyModifications(this);
|
||||
|
||||
Debug.Log(
|
||||
$"[BaseLevel] Discovery complete for '{name}'. Pickups: {Pickups.Count}, CodexTriggers: {CodexTriggers.Count}, Puzzles: {Puzzles.Count}",
|
||||
this);
|
||||
}
|
||||
|
||||
private static string GetHierarchyPath(Component component)
|
||||
{
|
||||
if (component == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var names = new Stack<string>();
|
||||
var current = component.transform;
|
||||
|
||||
while (current != null)
|
||||
{
|
||||
names.Push(current.name);
|
||||
current = current.parent;
|
||||
}
|
||||
|
||||
return string.Join("/", names);
|
||||
}
|
||||
#endif
|
||||
|
||||
public void OnValidate()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
|
||||
CanvasScaler scaler = GetComponent<CanvasScaler>();
|
||||
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
||||
scaler.matchWidthOrHeight = 0.5f;
|
||||
scaler.referenceResolution = new Vector2(1920, 1200);
|
||||
|
||||
GraphicRaycaster raycaster = GetComponent<GraphicRaycaster>();
|
||||
_raycaster = raycaster;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,18 +2,17 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BriarQueen.Data.IO.Saves;
|
||||
using BriarQueen.Framework.Assets;
|
||||
using BriarQueen.Framework.Coordinators.Events;
|
||||
using BriarQueen.Framework.Events.Gameplay;
|
||||
using BriarQueen.Framework.Events.Progression;
|
||||
using BriarQueen.Framework.Events.Save;
|
||||
using BriarQueen.Framework.Events.UI;
|
||||
using BriarQueen.Framework.Managers.Assets;
|
||||
using BriarQueen.Framework.Managers.IO;
|
||||
using BriarQueen.Framework.Managers.Levels.Data;
|
||||
using BriarQueen.Framework.Registries;
|
||||
using BriarQueen.Framework.Services.Destruction;
|
||||
using BriarQueen.Framework.Services.Puzzles;
|
||||
using BriarQueen.Framework.Services.Puzzles.Base;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using VContainer;
|
||||
@@ -33,6 +32,7 @@ namespace BriarQueen.Framework.Managers.Levels
|
||||
private readonly SaveManager _saveManager;
|
||||
|
||||
private UniTask<bool> _activeLoadTask = UniTask.FromResult(false);
|
||||
private bool _isLoadInProgress;
|
||||
private BaseLevel _currentLevel;
|
||||
|
||||
public bool Initialized { get; private set; }
|
||||
@@ -57,7 +57,9 @@ namespace BriarQueen.Framework.Managers.Levels
|
||||
public void Initialize()
|
||||
{
|
||||
if (Initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log($"[{nameof(LevelManager)}] Initializing...");
|
||||
_saveManager.OnSaveRequested += OnSaveGameRequested;
|
||||
@@ -70,7 +72,9 @@ namespace BriarQueen.Framework.Managers.Levels
|
||||
public void Dispose()
|
||||
{
|
||||
if (!Initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_saveManager.OnSaveRequested -= OnSaveGameRequested;
|
||||
_eventCoordinator.Unsubscribe<UpdateHintProgressEvent>(OnHintStageUpdated);
|
||||
@@ -81,10 +85,14 @@ namespace BriarQueen.Framework.Managers.Levels
|
||||
private void OnHintStageUpdated(UpdateHintProgressEvent evt)
|
||||
{
|
||||
if (_currentLevel == null || evt == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.Equals(evt.LevelID, _currentLevel.LevelID, StringComparison.Ordinal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var incoming = Mathf.Max(0, evt.Stage);
|
||||
|
||||
@@ -101,7 +109,9 @@ namespace BriarQueen.Framework.Managers.Levels
|
||||
private void OnSaveGameRequested(SaveGame saveGame)
|
||||
{
|
||||
if (saveGame == null || _currentLevel == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
saveGame.CurrentLevelID = _currentLevel.LevelID;
|
||||
saveGame.CurrentSceneID = _currentLevel.SceneID;
|
||||
@@ -120,12 +130,73 @@ namespace BriarQueen.Framework.Managers.Levels
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_activeLoadTask = LoadLevelInternal(levelAssetID);
|
||||
if (_isLoadInProgress)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"[LevelManager] LoadLevel('{levelAssetID}') requested while another level load is already in progress. Returning the active load task.");
|
||||
return _activeLoadTask;
|
||||
}
|
||||
|
||||
_isLoadInProgress = true;
|
||||
_activeLoadTask = LoadLevelTracked(levelAssetID);
|
||||
return _activeLoadTask;
|
||||
}
|
||||
}
|
||||
|
||||
private async UniTask<bool> LoadLevelTracked(string levelAssetID)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await LoadLevelInternal(levelAssetID);
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_isLoadInProgress = false;
|
||||
_activeLoadTask = UniTask.FromResult(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async UniTask<bool> LoadLevelInternal(string levelAssetID)
|
||||
{
|
||||
var previousLevelId = _currentLevel != null ? _currentLevel.LevelID : null;
|
||||
|
||||
_eventCoordinator.PublishImmediate(new FadeEvent(false, LEVEL_FADE_DURATION));
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(LEVEL_FADE_DURATION));
|
||||
|
||||
if (_currentLevel != null)
|
||||
{
|
||||
await UnloadLevelInternal();
|
||||
}
|
||||
|
||||
if (await TryLoadLevelCore(levelAssetID))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(previousLevelId) &&
|
||||
!string.Equals(previousLevelId, levelAssetID, StringComparison.Ordinal))
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"[LevelManager] Failed to load '{levelAssetID}'. Attempting recovery by reloading previous level '{previousLevelId}'.");
|
||||
|
||||
if (await TryLoadLevelCore(previousLevelId))
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"[LevelManager] Recovery succeeded by reloading '{previousLevelId}'.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_eventCoordinator.PublishImmediate(new FadeEvent(true, LEVEL_FADE_DURATION));
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(LEVEL_FADE_DURATION));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async UniTask<bool> TryLoadLevelCore(string levelAssetID)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -141,12 +212,6 @@ namespace BriarQueen.Framework.Managers.Levels
|
||||
return false;
|
||||
}
|
||||
|
||||
_eventCoordinator.PublishImmediate(new FadeEvent(false, LEVEL_FADE_DURATION));
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(LEVEL_FADE_DURATION));
|
||||
|
||||
if (_currentLevel != null)
|
||||
await UnloadLevelInternal();
|
||||
|
||||
var levelObj = await _addressableManager.InstantiateAsync(levelRef);
|
||||
if (levelObj == null)
|
||||
{
|
||||
@@ -157,7 +222,8 @@ namespace BriarQueen.Framework.Managers.Levels
|
||||
var level = levelObj.GetComponent<BaseLevel>();
|
||||
if (level == null)
|
||||
{
|
||||
Debug.LogError($"[LevelManager] Instantiated level '{levelAssetID}' has no BaseLevel component. Destroying instance.");
|
||||
Debug.LogError(
|
||||
$"[LevelManager] Instantiated level '{levelAssetID}' has no BaseLevel component. Destroying instance.");
|
||||
await _destructionService.Destroy(levelObj);
|
||||
return false;
|
||||
}
|
||||
@@ -169,8 +235,7 @@ namespace BriarQueen.Framework.Managers.Levels
|
||||
|
||||
await _currentLevel.PostLoad();
|
||||
|
||||
if (_currentLevel is BasePuzzle puzzle)
|
||||
await _puzzleService.LoadPuzzle(puzzle);
|
||||
await _puzzleService.LoadPuzzles(_currentLevel.Puzzles);
|
||||
|
||||
_eventCoordinator.Publish(new LevelChangedEvent(_currentLevel));
|
||||
|
||||
@@ -178,7 +243,9 @@ namespace BriarQueen.Framework.Managers.Levels
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(LEVEL_FADE_DURATION));
|
||||
|
||||
if (_currentLevel != null)
|
||||
{
|
||||
await _currentLevel.PostActivate();
|
||||
}
|
||||
|
||||
_eventCoordinator.Publish(new RequestGameSaveEvent());
|
||||
return true;
|
||||
@@ -186,29 +253,38 @@ namespace BriarQueen.Framework.Managers.Levels
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"[LevelManager] Exception while loading '{levelAssetID}': {ex}");
|
||||
|
||||
if (_currentLevel != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _destructionService.Destroy(_currentLevel.gameObject);
|
||||
}
|
||||
catch (Exception destroyEx)
|
||||
{
|
||||
Debug.LogWarning($"[LevelManager] Failed to destroy broken level instance: {destroyEx}");
|
||||
}
|
||||
|
||||
_currentLevel = null;
|
||||
}
|
||||
|
||||
await CleanupFailedCurrentLevel();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async UniTask CleanupFailedCurrentLevel()
|
||||
{
|
||||
if (_currentLevel == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _destructionService.Destroy(_currentLevel.gameObject);
|
||||
}
|
||||
catch (Exception destroyEx)
|
||||
{
|
||||
Debug.LogWarning($"[LevelManager] Failed to destroy broken level instance: {destroyEx}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_currentLevel = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void RestoreHintStageForCurrentLevel()
|
||||
{
|
||||
if (_currentLevel == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var save = _saveManager.CurrentSave;
|
||||
if (save?.LevelHintStages == null)
|
||||
@@ -218,23 +294,33 @@ namespace BriarQueen.Framework.Managers.Levels
|
||||
}
|
||||
|
||||
if (save.LevelHintStages.TryGetValue(_currentLevel.LevelID, out var stage))
|
||||
{
|
||||
_currentLevel.CurrentLevelHintStage = Mathf.Max(0, stage);
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentLevel.CurrentLevelHintStage = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private async UniTask RestoreItemStateForCurrentLevel()
|
||||
{
|
||||
if (_currentLevel == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var save = _saveManager.CurrentSave;
|
||||
if (save == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var interactables = _currentLevel.Pickups;
|
||||
if (interactables == null || interactables.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var interactable in interactables)
|
||||
{
|
||||
@@ -245,10 +331,14 @@ namespace BriarQueen.Framework.Managers.Levels
|
||||
}
|
||||
|
||||
if (save.CollectedItems.Any(x => x.UniqueIdentifier == interactable.ItemData.UniqueID))
|
||||
{
|
||||
await _destructionService.Destroy(interactable.gameObject);
|
||||
}
|
||||
|
||||
if (save.RemovedItems.Any(x => x.UniqueIdentifier == interactable.ItemData.UniqueID))
|
||||
{
|
||||
await _destructionService.Destroy(interactable.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
var codexTriggers = _currentLevel.CodexTriggers;
|
||||
@@ -258,7 +348,9 @@ namespace BriarQueen.Framework.Managers.Levels
|
||||
if (save.DiscoveredCodexEntries.Any(x => x.UniqueIdentifier == trigger.Entry.UniqueID))
|
||||
{
|
||||
if (trigger.RemoveTrigger)
|
||||
{
|
||||
await _destructionService.Destroy(trigger.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -268,7 +360,9 @@ namespace BriarQueen.Framework.Managers.Levels
|
||||
lock (_lock)
|
||||
{
|
||||
if (_activeLoadTask.Status == UniTaskStatus.Pending)
|
||||
{
|
||||
return _activeLoadTask.ContinueWith(_ => UnloadLevelInternal());
|
||||
}
|
||||
|
||||
return UnloadLevelInternal();
|
||||
}
|
||||
@@ -277,15 +371,16 @@ namespace BriarQueen.Framework.Managers.Levels
|
||||
private async UniTask UnloadLevelInternal()
|
||||
{
|
||||
if (_currentLevel == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var level = _currentLevel;
|
||||
_currentLevel = null;
|
||||
|
||||
try
|
||||
{
|
||||
if (level is BasePuzzle puzzle)
|
||||
await _puzzleService.SavePuzzle(puzzle);
|
||||
await _puzzleService.SavePuzzles(level.Puzzles);
|
||||
|
||||
_eventCoordinator.Publish(new RequestGameSaveEvent());
|
||||
await level.PreUnload();
|
||||
@@ -298,4 +393,4 @@ namespace BriarQueen.Framework.Managers.Levels
|
||||
await _destructionService.Destroy(level.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,22 @@ namespace BriarQueen.Framework.Managers.Player.Data.Codex
|
||||
{
|
||||
public class Codex
|
||||
{
|
||||
public Codex(bool unlocked = false)
|
||||
{
|
||||
CodexUnlocked = unlocked;
|
||||
}
|
||||
|
||||
public bool CodexUnlocked { get; private set; }
|
||||
|
||||
private readonly List<CodexEntrySo> _entries = new();
|
||||
|
||||
public IReadOnlyList<CodexEntrySo> Entries => _entries;
|
||||
|
||||
public void UnlockCodex()
|
||||
{
|
||||
CodexUnlocked = true;
|
||||
}
|
||||
|
||||
public void AddEntry(CodexEntrySo entry)
|
||||
{
|
||||
if (entry == null)
|
||||
@@ -66,7 +78,7 @@ namespace BriarQueen.Framework.Managers.Player.Data.Codex
|
||||
|
||||
public IEnumerable<CodexEntrySo> GetBookEntries()
|
||||
{
|
||||
return GetEntriesByType(CodexType.BookEntry);
|
||||
return GetEntriesByType(CodexType.DocumentEntry);
|
||||
}
|
||||
|
||||
public IEnumerable<CodexEntrySo> GetPuzzleClues()
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using BriarQueen.Data.Identifiers;
|
||||
using BriarQueen.Data.IO.Saves;
|
||||
using BriarQueen.Framework.Events.Save;
|
||||
using BriarQueen.Framework.Events.UI;
|
||||
using BriarQueen.Framework.Managers.Levels.Data;
|
||||
using BriarQueen.Framework.Managers.UI;
|
||||
using Cysharp.Threading.Tasks;
|
||||
@@ -28,6 +33,12 @@ namespace BriarQueen.Framework.Managers.Player.Data.Codex
|
||||
{
|
||||
if (!CheckEmptyHands())
|
||||
return;
|
||||
|
||||
if (!PlayerManager.CodexUnlocked())
|
||||
{
|
||||
EventCoordinator.PublishImmediate(new DisplayInteractEvent(InteractEventIDs.Get(ItemInteractKey.CodexLocked)));
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerManager.UnlockCodexEntry(_codexEntry);
|
||||
|
||||
@@ -36,5 +47,20 @@ namespace BriarQueen.Framework.Managers.Player.Data.Codex
|
||||
await Remove();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void UpdateSaveGameOnRemoval()
|
||||
{
|
||||
var save = SaveManager.CurrentSave;
|
||||
Debug.Log($"[Base Item] Found save - {save.SaveFileName}");
|
||||
|
||||
save.RemovedItems ??= new List<ItemSaveData>();
|
||||
|
||||
save.RemovedItems.Add(new ItemSaveData
|
||||
{
|
||||
UniqueIdentifier = _codexEntry.UniqueID
|
||||
});
|
||||
|
||||
EventCoordinator.PublishImmediate(new RequestGameSaveEvent());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace BriarQueen.Framework.Managers.Player.Data
|
||||
[Header("Codex ID")]
|
||||
[SerializeField]
|
||||
[ShowIf(nameof(IsBookEntry))]
|
||||
private BookEntryID _bookEntryID;
|
||||
private DocumentEntryID _documentEntryID;
|
||||
|
||||
[SerializeField]
|
||||
[ShowIf(nameof(IsPuzzleClue))]
|
||||
@@ -66,11 +66,11 @@ namespace BriarQueen.Framework.Managers.Player.Data
|
||||
public CodexType EntryType => _codexType;
|
||||
public Location Location => _location;
|
||||
|
||||
public bool IsBookEntry => _codexType == CodexType.BookEntry;
|
||||
public bool IsBookEntry => _codexType == CodexType.DocumentEntry;
|
||||
public bool IsPuzzleClue => _codexType == CodexType.PuzzleClue;
|
||||
public bool IsPhoto => _codexType == CodexType.Photo;
|
||||
|
||||
public BookEntryID BookEntryID => _bookEntryID;
|
||||
public DocumentEntryID DocumentEntryID => _documentEntryID;
|
||||
public ClueEntryID ClueEntryID => _clueEntryID;
|
||||
public PhotoEntryID PhotoEntryID => _photoEntryID;
|
||||
|
||||
@@ -92,8 +92,8 @@ namespace BriarQueen.Framework.Managers.Player.Data
|
||||
{
|
||||
return _codexType switch
|
||||
{
|
||||
CodexType.BookEntry when _bookEntryID != BookEntryID.None =>
|
||||
CodexEntryIDs.Get(_bookEntryID),
|
||||
CodexType.DocumentEntry when _documentEntryID != DocumentEntryID.None =>
|
||||
CodexEntryIDs.Get(_documentEntryID),
|
||||
|
||||
CodexType.PuzzleClue when _clueEntryID != ClueEntryID.None =>
|
||||
CodexEntryIDs.Get(_clueEntryID),
|
||||
|
||||
@@ -7,7 +7,6 @@ using BriarQueen.Framework.Events.Gameplay;
|
||||
using BriarQueen.Framework.Events.UI;
|
||||
using BriarQueen.Framework.Managers.UI;
|
||||
using BriarQueen.Framework.Services.Tutorials;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BriarQueen.Framework.Managers.Player.Data.Tools
|
||||
|
||||
@@ -179,7 +179,7 @@ namespace BriarQueen.Framework.Managers.Player
|
||||
}
|
||||
}
|
||||
|
||||
_audioManager.Play(AudioNameIdentifiers.Get(SFXKey.ItemCollected));
|
||||
_audioManager.Play(AudioNameIdentifiers.Get(SFXKey.ItemPickup));
|
||||
_eventCoordinator.PublishImmediate(new RequestGameSaveEvent());
|
||||
_eventCoordinator.Publish(new InventoryChangedEvent());
|
||||
}
|
||||
@@ -208,11 +208,21 @@ namespace BriarQueen.Framework.Managers.Player
|
||||
|
||||
#region Codex
|
||||
|
||||
public void UnlockCodex() => _codex.UnlockCodex();
|
||||
|
||||
public bool CodexUnlocked()
|
||||
{
|
||||
return _codex is { CodexUnlocked: true };
|
||||
}
|
||||
|
||||
public void UnlockCodexEntry(string uniqueIdentifier)
|
||||
{
|
||||
var entry = _codexRegistry.FindEntryByID(uniqueIdentifier);
|
||||
if (entry == null)
|
||||
{
|
||||
Debug.LogWarning($"[PlayerManager] Could not unlock codex entry '{uniqueIdentifier}'.");
|
||||
return;
|
||||
}
|
||||
|
||||
UnlockCodexEntry(entry);
|
||||
}
|
||||
@@ -243,7 +253,7 @@ namespace BriarQueen.Framework.Managers.Player
|
||||
}
|
||||
}
|
||||
|
||||
_tutorialService.DisplayTutorial(TutorialPopupID.Codex);
|
||||
_tutorialService.DisplayTutorial(TutorialPopupID.CodexKeyboard);
|
||||
|
||||
_eventCoordinator.PublishImmediate(new RequestGameSaveEvent());
|
||||
_eventCoordinator.Publish(new CodexChangedEvent(entry.EntryType));
|
||||
@@ -382,7 +392,7 @@ namespace BriarQueen.Framework.Managers.Player
|
||||
|
||||
private void LoadCodexFromSave(SaveGame save)
|
||||
{
|
||||
_codex = new Codex();
|
||||
_codex = new Codex(save.CodexUnlocked);
|
||||
|
||||
if (save.DiscoveredCodexEntries != null)
|
||||
{
|
||||
@@ -458,4 +468,4 @@ namespace BriarQueen.Framework.Managers.Player
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace BriarQueen.Framework.Managers.UI.Base
|
||||
{
|
||||
public interface IUIBackHandler
|
||||
{
|
||||
bool HandleBackRequest();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 18b01f6ab9d3468ba9a99360acbe4e5c
|
||||
timeCreated: 1778300000
|
||||
@@ -57,7 +57,18 @@ namespace BriarQueen.Framework.Managers.UI
|
||||
|
||||
private bool _useVirtualCursor;
|
||||
|
||||
public CursorStyleEntry CurrentStyleEntry => _styleMap[_currentStyle];
|
||||
public CursorStyleEntry CurrentStyleEntry
|
||||
{
|
||||
get
|
||||
{
|
||||
if (TryGetStyleEntry(GetEffectiveStyle(), out var entry))
|
||||
{
|
||||
return entry;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
[Inject]
|
||||
private void Construct(EventCoordinator eventCoordinator)
|
||||
@@ -192,6 +203,22 @@ namespace BriarQueen.Framework.Managers.UI
|
||||
return _isStyleOverridden ? _currentStyleOverride : _currentStyle;
|
||||
}
|
||||
|
||||
private bool TryGetStyleEntry(CursorStyle style, out CursorStyleEntry entry)
|
||||
{
|
||||
if (_styleMap.TryGetValue(style, out entry))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_styleMap.TryGetValue(CursorStyle.Default, out entry))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
entry = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ApplyVirtualCursorStyle(CursorStyle style)
|
||||
{
|
||||
if (_virtualCursorImage == null)
|
||||
@@ -282,4 +309,4 @@ namespace BriarQueen.Framework.Managers.UI
|
||||
public Vector2 TooltipOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using BriarQueen.Framework.Events.UI;
|
||||
using BriarQueen.Framework.Extensions;
|
||||
using BriarQueen.Framework.Managers.Interaction;
|
||||
using BriarQueen.Framework.Managers.IO;
|
||||
using BriarQueen.Framework.Managers.Player;
|
||||
using BriarQueen.Framework.Managers.UI.Base;
|
||||
using BriarQueen.Framework.Managers.UI.Events;
|
||||
using BriarQueen.Framework.Services.Settings;
|
||||
@@ -28,6 +29,7 @@ namespace BriarQueen.Framework.Managers.UI
|
||||
private readonly InteractManager _interactManager;
|
||||
private readonly SaveManager _saveManager;
|
||||
private readonly SettingsService _settingsService;
|
||||
private readonly PlayerManager _playerManager;
|
||||
|
||||
private readonly Dictionary<WindowType, IUIWindow> _windows = new();
|
||||
private readonly Stack<IUIWindow> _windowStack = new();
|
||||
@@ -46,12 +48,14 @@ namespace BriarQueen.Framework.Managers.UI
|
||||
EventCoordinator eventCoordinator,
|
||||
InteractManager interactManager,
|
||||
SettingsService settingsService,
|
||||
SaveManager saveManager)
|
||||
SaveManager saveManager,
|
||||
PlayerManager playerManager)
|
||||
{
|
||||
_eventCoordinator = eventCoordinator;
|
||||
_interactManager = interactManager;
|
||||
_settingsService = settingsService;
|
||||
_saveManager = saveManager;
|
||||
_playerManager = playerManager;
|
||||
}
|
||||
|
||||
private IUIWindow ActiveWindow => _windowStack.Count > 0 ? _windowStack.Peek() : null;
|
||||
@@ -84,6 +88,7 @@ namespace BriarQueen.Framework.Managers.UI
|
||||
private void SubscribeToEvents()
|
||||
{
|
||||
_eventCoordinator.Subscribe<PauseButtonClickedEvent>(OnPauseClickReceived);
|
||||
_eventCoordinator.Subscribe<UIBackRequestedEvent>(OnBackRequested);
|
||||
_eventCoordinator.Subscribe<ToggleCodexEvent>(ToggleCodexWindow);
|
||||
_eventCoordinator.Subscribe<UIToggleSettingsWindow>(ToggleSettingsWindow);
|
||||
_eventCoordinator.Subscribe<FadeEvent>(OnFadeEvent);
|
||||
@@ -97,6 +102,7 @@ namespace BriarQueen.Framework.Managers.UI
|
||||
private void UnsubscribeFromEvents()
|
||||
{
|
||||
_eventCoordinator.Unsubscribe<PauseButtonClickedEvent>(OnPauseClickReceived);
|
||||
_eventCoordinator.Unsubscribe<UIBackRequestedEvent>(OnBackRequested);
|
||||
_eventCoordinator.Unsubscribe<ToggleCodexEvent>(ToggleCodexWindow);
|
||||
_eventCoordinator.Unsubscribe<UIToggleSettingsWindow>(ToggleSettingsWindow);
|
||||
_eventCoordinator.Unsubscribe<FadeEvent>(OnFadeEvent);
|
||||
@@ -175,13 +181,18 @@ namespace BriarQueen.Framework.Managers.UI
|
||||
{
|
||||
if (_windowStack.Count > 0)
|
||||
{
|
||||
CloseTopWindow();
|
||||
TryHandleBackRequest();
|
||||
return;
|
||||
}
|
||||
|
||||
OpenWindow(WindowType.PauseMenuWindow);
|
||||
}
|
||||
|
||||
private void OnBackRequested(UIBackRequestedEvent _)
|
||||
{
|
||||
TryHandleBackRequest();
|
||||
}
|
||||
|
||||
private void ToggleSettingsWindow(UIToggleSettingsWindow eventData)
|
||||
{
|
||||
if (eventData.Show)
|
||||
@@ -192,6 +203,9 @@ namespace BriarQueen.Framework.Managers.UI
|
||||
|
||||
private void ToggleCodexWindow(ToggleCodexEvent eventData)
|
||||
{
|
||||
if(!_playerManager.CodexUnlocked())
|
||||
return;
|
||||
|
||||
if (eventData.Shown)
|
||||
OpenWindow(WindowType.CodexWindow);
|
||||
else
|
||||
@@ -231,7 +245,7 @@ namespace BriarQueen.Framework.Managers.UI
|
||||
{
|
||||
return codexType switch
|
||||
{
|
||||
CodexType.BookEntry => "You've acquired a new document.",
|
||||
CodexType.DocumentEntry => "You've acquired a new document.",
|
||||
CodexType.PuzzleClue => "You've acquired a new puzzle clue.",
|
||||
CodexType.Photo => "You've acquired a new photo.",
|
||||
_ => string.Empty
|
||||
@@ -246,10 +260,14 @@ namespace BriarQueen.Framework.Managers.UI
|
||||
if (!_settingsService.AreTutorialsEnabled())
|
||||
return;
|
||||
|
||||
var duration = 3f;
|
||||
var tutorialText = TutorialPopupTexts.AllPopups[eventData.TutorialID];
|
||||
if (string.IsNullOrWhiteSpace(eventData.ResolvedText))
|
||||
{
|
||||
Debug.LogWarning($"[UIManager] Empty resolved text for tutorial '{eventData.TutorialID}'.");
|
||||
return;
|
||||
}
|
||||
|
||||
_tutorialPopup.Play(tutorialText, duration).Forget();
|
||||
var duration = _settingsService?.Game?.PopupDisplayDuration ?? 3f;
|
||||
_tutorialPopup.Play(eventData.ResolvedText, duration).Forget();
|
||||
}
|
||||
|
||||
private void OnDisplayInteractText(DisplayInteractEvent eventData)
|
||||
@@ -340,6 +358,21 @@ namespace BriarQueen.Framework.Managers.UI
|
||||
CloseTopWindowInternal().Forget();
|
||||
}
|
||||
|
||||
private void TryHandleBackRequest()
|
||||
{
|
||||
if (_disposed || _windowStack.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ActiveWindow is IUIBackHandler backHandler && backHandler.HandleBackRequest())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CloseTopWindow();
|
||||
}
|
||||
|
||||
private async UniTask CloseTopWindowInternal()
|
||||
{
|
||||
if (_disposed || _windowStack.Count == 0)
|
||||
@@ -431,4 +464,4 @@ namespace BriarQueen.Framework.Managers.UI
|
||||
_eventCoordinator.Publish(new UIStackChangedEvent(_windowStack.Count > 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BriarQueen.Data.Identifiers;
|
||||
using BriarQueen.Framework.Managers.Achievements.Data;
|
||||
using UnityEngine;
|
||||
@@ -19,8 +18,22 @@ namespace BriarQueen.Framework.Registries
|
||||
if (_achievementDictionary != null)
|
||||
return;
|
||||
|
||||
_achievementDictionary = _achievementSos.ToDictionary(achievement => achievement.Achievement,
|
||||
achievement => achievement);
|
||||
RebuildLookup();
|
||||
}
|
||||
|
||||
private void RebuildLookup()
|
||||
{
|
||||
_achievementDictionary = new Dictionary<AchievementID, AchievementSo>();
|
||||
|
||||
RegistryLookupBuilder.AddEntries(
|
||||
_achievementDictionary,
|
||||
_achievementSos,
|
||||
this,
|
||||
nameof(AchievementRegistry),
|
||||
"Achievements",
|
||||
nameof(AchievementSo.Achievement),
|
||||
entry => entry.Achievement,
|
||||
entry => entry);
|
||||
}
|
||||
|
||||
public bool TryGetAchievement(AchievementID identifier, out AchievementSo achievement)
|
||||
@@ -35,4 +48,4 @@ namespace BriarQueen.Framework.Registries
|
||||
return _achievementDictionary.Values;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,47 +45,57 @@ namespace BriarQueen.Framework.Registries
|
||||
{
|
||||
_assetDictionary = new Dictionary<string, AssetReference>();
|
||||
|
||||
AddEntries(_sceneReferences, "Scenes");
|
||||
AddEntries(_levelReferences, "Levels");
|
||||
AddEntries(_itemReferences, "Items");
|
||||
AddEntries(_uiReferences, "UI");
|
||||
}
|
||||
RegistryLookupBuilder.AddEntries(
|
||||
_assetDictionary,
|
||||
_sceneReferences,
|
||||
this,
|
||||
nameof(AssetRegistry),
|
||||
"Scenes",
|
||||
"AssetKey",
|
||||
entry => entry.AssetKey,
|
||||
entry => entry.Asset,
|
||||
entry => RegistryLookupBuilder.HasNonEmptyKey(entry.AssetKey),
|
||||
entry => entry.Asset != null,
|
||||
"AssetReference is null");
|
||||
|
||||
private void AddEntries(List<AssetEntry> entries, string category)
|
||||
{
|
||||
if (entries == null)
|
||||
return;
|
||||
RegistryLookupBuilder.AddEntries(
|
||||
_assetDictionary,
|
||||
_levelReferences,
|
||||
this,
|
||||
nameof(AssetRegistry),
|
||||
"Levels",
|
||||
"AssetKey",
|
||||
entry => entry.AssetKey,
|
||||
entry => entry.Asset,
|
||||
entry => RegistryLookupBuilder.HasNonEmptyKey(entry.AssetKey),
|
||||
entry => entry.Asset != null,
|
||||
"AssetReference is null");
|
||||
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
if (entry == null)
|
||||
continue;
|
||||
RegistryLookupBuilder.AddEntries(
|
||||
_assetDictionary,
|
||||
_itemReferences,
|
||||
this,
|
||||
nameof(AssetRegistry),
|
||||
"Items",
|
||||
"AssetKey",
|
||||
entry => entry.AssetKey,
|
||||
entry => entry.Asset,
|
||||
entry => RegistryLookupBuilder.HasNonEmptyKey(entry.AssetKey),
|
||||
entry => entry.Asset != null,
|
||||
"AssetReference is null");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(entry.AssetKey))
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"[AssetRegistry] Skipping {category} entry '{entry.name}' because AssetKey is empty.", this);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.Asset == null)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"[AssetRegistry] Skipping {category} entry '{entry.name}' because AssetReference is null.",
|
||||
this);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_assetDictionary.ContainsKey(entry.AssetKey))
|
||||
{
|
||||
Debug.LogError(
|
||||
$"[AssetRegistry] Duplicate AssetKey detected: '{entry.AssetKey}' from entry '{entry.name}'.",
|
||||
this);
|
||||
continue;
|
||||
}
|
||||
|
||||
_assetDictionary.Add(entry.AssetKey, entry.Asset);
|
||||
}
|
||||
RegistryLookupBuilder.AddEntries(
|
||||
_assetDictionary,
|
||||
_uiReferences,
|
||||
this,
|
||||
nameof(AssetRegistry),
|
||||
"UI",
|
||||
"AssetKey",
|
||||
entry => entry.AssetKey,
|
||||
entry => entry.Asset,
|
||||
entry => RegistryLookupBuilder.HasNonEmptyKey(entry.AssetKey),
|
||||
entry => entry.Asset != null,
|
||||
"AssetReference is null");
|
||||
}
|
||||
|
||||
public bool TryGetReference(string key, out AssetReference reference)
|
||||
@@ -116,4 +126,4 @@ namespace BriarQueen.Framework.Registries
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BriarQueen.Framework.Managers.Audio.Data;
|
||||
using NaughtyAttributes;
|
||||
using UnityEngine;
|
||||
@@ -17,15 +16,32 @@ namespace BriarQueen.Framework.Registries
|
||||
|
||||
private void EnsureInitialized()
|
||||
{
|
||||
if (_audioFileDict == null)
|
||||
_audioFileDict = _audioFiles.ToDictionary(entry => entry.UniqueID, entry => entry);
|
||||
if (_audioFileDict != null)
|
||||
return;
|
||||
|
||||
RebuildLookup();
|
||||
}
|
||||
|
||||
private void RebuildLookup()
|
||||
{
|
||||
_audioFileDict = new Dictionary<string, AudioFileSo>();
|
||||
|
||||
RegistryLookupBuilder.AddEntries(
|
||||
_audioFileDict,
|
||||
_audioFiles,
|
||||
this,
|
||||
nameof(AudioRegistry),
|
||||
"Audio Files",
|
||||
"UniqueID",
|
||||
entry => entry.UniqueID,
|
||||
entry => entry,
|
||||
entry => RegistryLookupBuilder.HasNonEmptyKey(entry.UniqueID));
|
||||
}
|
||||
|
||||
public bool TryGetAudio(string audioName, out AudioFileSo audioFile)
|
||||
{
|
||||
if (_audioFileDict == null) EnsureInitialized();
|
||||
|
||||
return _audioFileDict!.TryGetValue(audioName, out audioFile);
|
||||
EnsureInitialized();
|
||||
return _audioFileDict.TryGetValue(audioName, out audioFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,39 +35,38 @@ namespace BriarQueen.Framework.Registries
|
||||
{
|
||||
_entryLookup = new Dictionary<string, CodexEntrySo>();
|
||||
|
||||
AddEntries(_bookEntries, "Book Entries");
|
||||
AddEntries(_puzzleClues, "Puzzle Clues");
|
||||
AddEntries(_photoEntries, "Photo Entries");
|
||||
}
|
||||
RegistryLookupBuilder.AddEntries(
|
||||
_entryLookup,
|
||||
_bookEntries,
|
||||
this,
|
||||
nameof(CodexRegistry),
|
||||
"Book Entries",
|
||||
"UniqueID",
|
||||
entry => entry.UniqueID,
|
||||
entry => entry,
|
||||
entry => RegistryLookupBuilder.HasNonEmptyKey(entry.UniqueID));
|
||||
|
||||
private void AddEntries(List<CodexEntrySo> entries, string category)
|
||||
{
|
||||
if (entries == null)
|
||||
return;
|
||||
RegistryLookupBuilder.AddEntries(
|
||||
_entryLookup,
|
||||
_puzzleClues,
|
||||
this,
|
||||
nameof(CodexRegistry),
|
||||
"Puzzle Clues",
|
||||
"UniqueID",
|
||||
entry => entry.UniqueID,
|
||||
entry => entry,
|
||||
entry => RegistryLookupBuilder.HasNonEmptyKey(entry.UniqueID));
|
||||
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
if (entry == null)
|
||||
continue;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(entry.UniqueID))
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"[CodexRegistry] Skipping {category} entry '{entry.name}' because UniqueID is empty.",
|
||||
this);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_entryLookup.ContainsKey(entry.UniqueID))
|
||||
{
|
||||
Debug.LogError(
|
||||
$"[CodexRegistry] Duplicate UniqueID detected: '{entry.UniqueID}' from entry '{entry.name}'.",
|
||||
this);
|
||||
continue;
|
||||
}
|
||||
|
||||
_entryLookup.Add(entry.UniqueID, entry);
|
||||
}
|
||||
RegistryLookupBuilder.AddEntries(
|
||||
_entryLookup,
|
||||
_photoEntries,
|
||||
this,
|
||||
nameof(CodexRegistry),
|
||||
"Photo Entries",
|
||||
"UniqueID",
|
||||
entry => entry.UniqueID,
|
||||
entry => entry,
|
||||
entry => RegistryLookupBuilder.HasNonEmptyKey(entry.UniqueID));
|
||||
}
|
||||
|
||||
public bool TryGetEntry(string entryID, out CodexEntrySo entry)
|
||||
@@ -130,4 +129,4 @@ namespace BriarQueen.Framework.Registries
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,39 +35,38 @@ namespace BriarQueen.Framework.Registries
|
||||
{
|
||||
_entryLookup = new Dictionary<string, ItemDataSo>();
|
||||
|
||||
AddEntries(_puzzleSlots, "Puzzle Slots");
|
||||
AddEntries(_pickupItems, "Pickup Items");
|
||||
AddEntries(_environmentInteractables, "Environment Interactables");
|
||||
}
|
||||
RegistryLookupBuilder.AddEntries(
|
||||
_entryLookup,
|
||||
_puzzleSlots,
|
||||
this,
|
||||
nameof(ItemRegistry),
|
||||
"Puzzle Slots",
|
||||
"UniqueID",
|
||||
entry => entry.UniqueID,
|
||||
entry => entry,
|
||||
entry => RegistryLookupBuilder.HasNonEmptyKey(entry.UniqueID));
|
||||
|
||||
private void AddEntries(List<ItemDataSo> entries, string category)
|
||||
{
|
||||
if (entries == null)
|
||||
return;
|
||||
RegistryLookupBuilder.AddEntries(
|
||||
_entryLookup,
|
||||
_pickupItems,
|
||||
this,
|
||||
nameof(ItemRegistry),
|
||||
"Pickup Items",
|
||||
"UniqueID",
|
||||
entry => entry.UniqueID,
|
||||
entry => entry,
|
||||
entry => RegistryLookupBuilder.HasNonEmptyKey(entry.UniqueID));
|
||||
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
if (entry == null)
|
||||
continue;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(entry.UniqueID))
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"[ItemRegistry] Skipping {category} entry '{entry.name}' because UniqueID is empty.",
|
||||
this);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_entryLookup.ContainsKey(entry.UniqueID))
|
||||
{
|
||||
Debug.LogError(
|
||||
$"[ItemRegistry] Duplicate UniqueID detected: '{entry.UniqueID}' from entry '{entry.name}'.",
|
||||
this);
|
||||
continue;
|
||||
}
|
||||
|
||||
_entryLookup.Add(entry.UniqueID, entry);
|
||||
}
|
||||
RegistryLookupBuilder.AddEntries(
|
||||
_entryLookup,
|
||||
_environmentInteractables,
|
||||
this,
|
||||
nameof(ItemRegistry),
|
||||
"Environment Interactables",
|
||||
"UniqueID",
|
||||
entry => entry.UniqueID,
|
||||
entry => entry,
|
||||
entry => RegistryLookupBuilder.HasNonEmptyKey(entry.UniqueID));
|
||||
}
|
||||
|
||||
public bool TryGetEntry(string itemID, out ItemDataSo entry)
|
||||
@@ -130,4 +129,4 @@ namespace BriarQueen.Framework.Registries
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
69
Assets/Scripts/Framework/Registries/RegistryLookupBuilder.cs
Normal file
69
Assets/Scripts/Framework/Registries/RegistryLookupBuilder.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BriarQueen.Framework.Registries
|
||||
{
|
||||
internal static class RegistryLookupBuilder
|
||||
{
|
||||
public static void AddEntries<TKey, TEntry, TValue>(
|
||||
Dictionary<TKey, TValue> lookup,
|
||||
IEnumerable<TEntry> entries,
|
||||
UnityEngine.Object context,
|
||||
string registryName,
|
||||
string category,
|
||||
string keyLabel,
|
||||
Func<TEntry, TKey> keySelector,
|
||||
Func<TEntry, TValue> valueSelector,
|
||||
Func<TEntry, bool> isKeyValid = null,
|
||||
Func<TEntry, bool> isEntryValid = null,
|
||||
string invalidEntryReason = null)
|
||||
where TEntry : UnityEngine.Object
|
||||
{
|
||||
if (lookup == null)
|
||||
throw new ArgumentNullException(nameof(lookup));
|
||||
|
||||
if (entries == null)
|
||||
return;
|
||||
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
if (!entry)
|
||||
continue;
|
||||
|
||||
if (isKeyValid != null && !isKeyValid(entry))
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"[{registryName}] Skipping {category} entry '{entry.name}' because {keyLabel} is invalid.",
|
||||
context);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isEntryValid != null && !isEntryValid(entry))
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"[{registryName}] Skipping {category} entry '{entry.name}' because {invalidEntryReason}.",
|
||||
context);
|
||||
continue;
|
||||
}
|
||||
|
||||
var key = keySelector(entry);
|
||||
|
||||
if (lookup.ContainsKey(key))
|
||||
{
|
||||
Debug.LogError(
|
||||
$"[{registryName}] Duplicate {keyLabel} detected: '{key}' from entry '{entry.name}'.",
|
||||
context);
|
||||
continue;
|
||||
}
|
||||
|
||||
lookup.Add(key, valueSelector(entry));
|
||||
}
|
||||
}
|
||||
|
||||
public static bool HasNonEmptyKey(string key)
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: be2d54c81fb1f49eb974c7b2b0a91f47
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using BriarQueen.Framework.Assets;
|
||||
using BriarQueen.Framework.Managers.Assets;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using VContainer;
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
using System;
|
||||
using BriarQueen.Data.Identifiers;
|
||||
using BriarQueen.Framework.Assets;
|
||||
using BriarQueen.Framework.Coordinators.Events;
|
||||
using BriarQueen.Framework.Events.UI;
|
||||
using BriarQueen.Framework.Managers.Assets;
|
||||
using BriarQueen.Framework.Managers.IO;
|
||||
using BriarQueen.Framework.Managers.Levels;
|
||||
using BriarQueen.Framework.Registries;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.ResourceManagement.AsyncOperations;
|
||||
using UnityEngine.ResourceManagement.ResourceProviders;
|
||||
using UnityEngine.SceneManagement;
|
||||
using VContainer;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace BriarQueen.Framework.Services.Game
|
||||
{
|
||||
public class GameService
|
||||
@@ -75,7 +78,7 @@ namespace BriarQueen.Framework.Services.Game
|
||||
_eventCoordinator.PublishImmediate(new FadeEvent(false, fadeDuration));
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(fadeDuration));
|
||||
|
||||
await UnloadGameSceneIfLoaded();
|
||||
await PrepareToLeaveGameplayScene();
|
||||
|
||||
if (_assetRegistry == null ||
|
||||
!_assetRegistry.TryGetReference(
|
||||
@@ -193,6 +196,12 @@ namespace BriarQueen.Framework.Services.Game
|
||||
}
|
||||
}
|
||||
|
||||
private async UniTask PrepareToLeaveGameplayScene()
|
||||
{
|
||||
await _levelManager.UnloadLevel();
|
||||
await UnloadGameSceneIfLoaded();
|
||||
}
|
||||
|
||||
public async UniTask SwapGameSceneHandle(AsyncOperationHandle<SceneInstance> nextSceneHandle)
|
||||
{
|
||||
if (!nextSceneHandle.IsValid())
|
||||
@@ -208,7 +217,10 @@ namespace BriarQueen.Framework.Services.Game
|
||||
}
|
||||
|
||||
if (_gameSceneHandle.IsValid())
|
||||
{
|
||||
await _levelManager.UnloadLevel();
|
||||
await _addressableManager.UnloadSceneAsync(_gameSceneHandle);
|
||||
}
|
||||
|
||||
_gameSceneHandle = nextSceneHandle;
|
||||
SceneManager.SetActiveScene(nextSceneHandle.Result.Scene);
|
||||
@@ -223,4 +235,4 @@ namespace BriarQueen.Framework.Services.Game
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +1,44 @@
|
||||
// ==============================
|
||||
// PuzzleBase.cs (updated)
|
||||
// ==============================
|
||||
|
||||
using System.Collections.Generic;
|
||||
using BriarQueen.Framework.Assets;
|
||||
using BriarQueen.Framework.Coordinators.Events;
|
||||
using BriarQueen.Framework.Managers.Assets;
|
||||
using BriarQueen.Framework.Managers.Audio;
|
||||
using BriarQueen.Framework.Managers.Hints.Data;
|
||||
using BriarQueen.Framework.Managers.IO;
|
||||
using BriarQueen.Framework.Managers.Levels.Data;
|
||||
using BriarQueen.Framework.Registries;
|
||||
using BriarQueen.Framework.Services.Destruction;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using VContainer;
|
||||
|
||||
namespace BriarQueen.Framework.Services.Puzzles.Base
|
||||
{
|
||||
public abstract class BasePuzzle : BaseLevel
|
||||
public abstract class BasePuzzle : MonoBehaviour
|
||||
{
|
||||
protected AddressableManager AddressableManager;
|
||||
protected AssetRegistry AssetRegistry;
|
||||
protected AudioManager AudioManager;
|
||||
protected DestructionService DestructionService;
|
||||
protected EventCoordinator EventCoordinator;
|
||||
protected ItemRegistry ItemRegistry;
|
||||
protected PuzzleService PuzzleService;
|
||||
protected SaveManager SaveManager;
|
||||
|
||||
public abstract string PuzzleID { get; }
|
||||
|
||||
public override bool IsPuzzleLevel => true;
|
||||
|
||||
// BaseLevel still requires these.
|
||||
public abstract override string LevelName { get; }
|
||||
public abstract override Dictionary<int, BaseHint> Hints { get; }
|
||||
|
||||
public abstract UniTask CompletePuzzle();
|
||||
|
||||
public virtual UniTask PostLoad()
|
||||
{
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
public virtual UniTask PreUnload()
|
||||
{
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
[Inject]
|
||||
public void Construct(EventCoordinator eventCoordinator, AudioManager audioManager,
|
||||
SaveManager saveManager, ItemRegistry itemRegistry, AddressableManager addressableManager,
|
||||
AssetRegistry assetRegistry, PuzzleService puzzleService)
|
||||
AssetRegistry assetRegistry, PuzzleService puzzleService, DestructionService destructionService)
|
||||
{
|
||||
EventCoordinator = eventCoordinator;
|
||||
AudioManager = audioManager;
|
||||
@@ -45,6 +47,7 @@ namespace BriarQueen.Framework.Services.Puzzles.Base
|
||||
AddressableManager = addressableManager;
|
||||
AssetRegistry = assetRegistry;
|
||||
PuzzleService = puzzleService;
|
||||
DestructionService = destructionService;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace BriarQueen.Framework.Services.Puzzles
|
||||
{
|
||||
private readonly SaveManager _saveManager;
|
||||
|
||||
private BasePuzzle _currentBasePuzzle;
|
||||
private readonly Dictionary<string, BasePuzzle> _activePuzzles = new();
|
||||
private bool _isWritingState;
|
||||
|
||||
[Inject]
|
||||
@@ -31,40 +31,98 @@ namespace BriarQueen.Framework.Services.Puzzles
|
||||
|
||||
public async UniTask LoadPuzzle(BasePuzzle basePuzzle)
|
||||
{
|
||||
_currentBasePuzzle = basePuzzle;
|
||||
if (_currentBasePuzzle == null)
|
||||
if (basePuzzle == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await TryRestorePuzzleState(_currentBasePuzzle);
|
||||
if (string.IsNullOrWhiteSpace(basePuzzle.PuzzleID))
|
||||
{
|
||||
Debug.LogWarning($"[PuzzleService] Cannot load puzzle '{basePuzzle.name}' with null/empty PuzzleID.");
|
||||
return;
|
||||
}
|
||||
|
||||
_activePuzzles[basePuzzle.PuzzleID] = basePuzzle;
|
||||
|
||||
await basePuzzle.PostLoad();
|
||||
await TryRestorePuzzleState(basePuzzle);
|
||||
}
|
||||
|
||||
public async UniTask LoadPuzzles(IEnumerable<BasePuzzle> basePuzzles)
|
||||
{
|
||||
_activePuzzles.Clear();
|
||||
|
||||
if (basePuzzles == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var basePuzzle in basePuzzles)
|
||||
{
|
||||
await LoadPuzzle(basePuzzle);
|
||||
}
|
||||
}
|
||||
|
||||
public async UniTask SavePuzzle(BasePuzzle basePuzzle)
|
||||
{
|
||||
if (basePuzzle == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_currentBasePuzzle != null && basePuzzle != _currentBasePuzzle)
|
||||
if (!string.IsNullOrWhiteSpace(basePuzzle.PuzzleID) &&
|
||||
_activePuzzles.TryGetValue(basePuzzle.PuzzleID, out var activePuzzle) &&
|
||||
activePuzzle != basePuzzle)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await SavePuzzleState(basePuzzle, flushToDisk: true);
|
||||
_currentBasePuzzle = null;
|
||||
await basePuzzle.PreUnload();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(basePuzzle.PuzzleID))
|
||||
{
|
||||
_activePuzzles.Remove(basePuzzle.PuzzleID);
|
||||
}
|
||||
}
|
||||
|
||||
public async UniTask SavePuzzles(IEnumerable<BasePuzzle> basePuzzles)
|
||||
{
|
||||
if (basePuzzles != null)
|
||||
{
|
||||
foreach (var basePuzzle in basePuzzles)
|
||||
{
|
||||
await SavePuzzle(basePuzzle);
|
||||
}
|
||||
}
|
||||
|
||||
_activePuzzles.Clear();
|
||||
}
|
||||
|
||||
private async UniTask OnBeforeSaveRequestedAsync()
|
||||
{
|
||||
if (_currentBasePuzzle == null)
|
||||
if (_activePuzzles.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await SavePuzzleState(_currentBasePuzzle, flushToDisk: false);
|
||||
foreach (var basePuzzle in _activePuzzles.Values.ToList())
|
||||
{
|
||||
await SavePuzzleState(basePuzzle, flushToDisk: false);
|
||||
}
|
||||
}
|
||||
|
||||
private async UniTask TryRestorePuzzleState(BasePuzzle basePuzzle)
|
||||
{
|
||||
if (basePuzzle == null || _saveManager.CurrentSave == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (basePuzzle is not IPuzzleStateful stateful)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var save = _saveManager.CurrentSave;
|
||||
var entry = save.PuzzleStates?.FirstOrDefault(x => x != null && x.PuzzleID == basePuzzle.PuzzleID);
|
||||
@@ -83,20 +141,28 @@ namespace BriarQueen.Framework.Services.Puzzles
|
||||
private async UniTask SavePuzzleState(BasePuzzle basePuzzle, bool flushToDisk)
|
||||
{
|
||||
if (basePuzzle == null || _saveManager.CurrentSave == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (basePuzzle is not IPuzzleStateful stateful)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_isWritingState)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isWritingState = true;
|
||||
|
||||
try
|
||||
{
|
||||
if (basePuzzle is IPuzzleWorldStateSync worldStateSync)
|
||||
{
|
||||
worldStateSync.SyncWorldStateToSave();
|
||||
}
|
||||
|
||||
var save = _saveManager.CurrentSave;
|
||||
save.PuzzleStates ??= new List<PuzzleStateSaveData>();
|
||||
@@ -123,7 +189,9 @@ namespace BriarQueen.Framework.Services.Puzzles
|
||||
existing.Completed = stateful.IsCompleted;
|
||||
|
||||
if (flushToDisk)
|
||||
{
|
||||
await _saveManager.SaveGameDataLatest();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -131,4 +199,4 @@ namespace BriarQueen.Framework.Services.Puzzles
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,6 @@ namespace BriarQueen.Framework.Services.Settings.Data
|
||||
public float MusicVolume;
|
||||
public float SfxVolume;
|
||||
public float VoiceVolume;
|
||||
public float AmbienceVolume;
|
||||
public float UIVolume;
|
||||
public bool MuteWhenUnfocused;
|
||||
|
||||
public AudioSettings()
|
||||
{
|
||||
@@ -19,9 +16,6 @@ namespace BriarQueen.Framework.Services.Settings.Data
|
||||
MusicVolume = 0.75f; // 75%
|
||||
SfxVolume = 0.75f; // 75%
|
||||
VoiceVolume = 1.0f; // 100%
|
||||
AmbienceVolume = 0.75f; // 75%
|
||||
UIVolume = 0.5f; // 50%
|
||||
MuteWhenUnfocused = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,7 +89,6 @@ namespace BriarQueen.Framework.Services.Settings
|
||||
AudioMixerGroups.MASTER_GROUP => Audio.MasterVolume,
|
||||
AudioMixerGroups.MUSIC_GROUP => Audio.MusicVolume,
|
||||
AudioMixerGroups.SFX_GROUP => Audio.SfxVolume,
|
||||
AudioMixerGroups.UI_GROUP => Audio.UIVolume,
|
||||
AudioMixerGroups.VOICE_GROUP => Audio.VoiceVolume,
|
||||
_ => Audio.MasterVolume
|
||||
};
|
||||
@@ -111,10 +110,6 @@ namespace BriarQueen.Framework.Services.Settings
|
||||
_audioManager.SetVolume(AudioMixerParameters.MUSIC_VOLUME, a.MusicVolume);
|
||||
_audioManager.SetVolume(AudioMixerParameters.SFX_VOLUME, a.SfxVolume);
|
||||
_audioManager.SetVolume(AudioMixerParameters.VOICE_VOLUME, a.VoiceVolume);
|
||||
_audioManager.SetVolume(AudioMixerParameters.AMBIENCE_VOLUME, a.AmbienceVolume);
|
||||
_audioManager.SetVolume(AudioMixerParameters.UI_VOLUME, a.UIVolume);
|
||||
|
||||
Application.runInBackground = !a.MuteWhenUnfocused;
|
||||
}
|
||||
|
||||
private void ApplyVisual(VisualSettings v)
|
||||
|
||||
@@ -2,8 +2,11 @@ using BriarQueen.Data.Identifiers;
|
||||
using BriarQueen.Framework.Coordinators.Events;
|
||||
using BriarQueen.Framework.Events.Save;
|
||||
using BriarQueen.Framework.Events.UI;
|
||||
using BriarQueen.Framework.Managers.Input;
|
||||
using BriarQueen.Framework.Managers.IO;
|
||||
using BriarQueen.Framework.Services.Settings;
|
||||
using System.Text.RegularExpressions;
|
||||
using UnityEngine.InputSystem;
|
||||
using VContainer;
|
||||
|
||||
namespace BriarQueen.Framework.Services.Tutorials
|
||||
@@ -11,23 +14,26 @@ namespace BriarQueen.Framework.Services.Tutorials
|
||||
public class TutorialService
|
||||
{
|
||||
private readonly EventCoordinator _eventCoordinator;
|
||||
private readonly SettingsService _settingsService;
|
||||
private readonly SaveManager _saveManager;
|
||||
private readonly SettingsService _settingsService;
|
||||
private readonly SaveManager _saveManager;
|
||||
private readonly InputManager _inputManager;
|
||||
|
||||
[Inject]
|
||||
public TutorialService(
|
||||
EventCoordinator eventCoordinator,
|
||||
SettingsService settingsService,
|
||||
SaveManager saveManager)
|
||||
SettingsService settingsService,
|
||||
SaveManager saveManager,
|
||||
InputManager inputManager)
|
||||
{
|
||||
_eventCoordinator = eventCoordinator;
|
||||
_settingsService = settingsService;
|
||||
_saveManager = saveManager;
|
||||
_settingsService = settingsService;
|
||||
_saveManager = saveManager;
|
||||
_inputManager = inputManager;
|
||||
}
|
||||
|
||||
public void DisplayTutorial(TutorialPopupID tutorialPopupID)
|
||||
{
|
||||
var save = _saveManager.CurrentSave;
|
||||
var save = _saveManager.CurrentSave;
|
||||
var tutorialVars = save?.PersistentVariables?.TutorialPopupVariables;
|
||||
|
||||
if (tutorialVars == null)
|
||||
@@ -39,9 +45,60 @@ namespace BriarQueen.Framework.Services.Tutorials
|
||||
tutorialVars.MarkDisplayed(tutorialPopupID);
|
||||
|
||||
if (_settingsService.AreTutorialsEnabled())
|
||||
_eventCoordinator.Publish(new DisplayTutorialPopupEvent(tutorialPopupID));
|
||||
{
|
||||
var resolvedText = ResolveText(tutorialPopupID);
|
||||
_eventCoordinator.Publish(new DisplayTutorialPopupEvent(tutorialPopupID, resolvedText));
|
||||
}
|
||||
|
||||
_eventCoordinator.PublishImmediate(new RequestGameSaveEvent());
|
||||
}
|
||||
|
||||
// ── Text resolution ───────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Resolves {ActionName} tokens in the tutorial text for the given ID
|
||||
/// to the current binding display string for that action.
|
||||
/// Hotswap-safe — reads the current control scheme at call time.
|
||||
/// </summary>
|
||||
public string ResolveText(TutorialPopupID id)
|
||||
{
|
||||
if (!TutorialPopupTexts.AllPopups.TryGetValue(id, out var template))
|
||||
return string.Empty;
|
||||
|
||||
return ResolveText(template);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves {ActionName} tokens in an arbitrary string to the current
|
||||
/// binding display string for that action.
|
||||
/// </summary>
|
||||
public string ResolveText(string template)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(template))
|
||||
return template;
|
||||
|
||||
return Regex.Replace(template, @"\{(\w+)\}", match =>
|
||||
{
|
||||
var actionName = match.Groups[1].Value;
|
||||
var binding = GetBindingDisplayString(actionName);
|
||||
return string.IsNullOrWhiteSpace(binding) ? match.Value : binding;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the display string for a named action's current binding,
|
||||
/// matched to the active control scheme.
|
||||
/// </summary>
|
||||
public string GetBindingDisplayString(string actionName)
|
||||
{
|
||||
if (_inputManager == null) return string.Empty;
|
||||
|
||||
var action = _inputManager.GetAction(actionName);
|
||||
if (action == null) return string.Empty;
|
||||
|
||||
var displayString = action.GetBindingDisplayString(group: _inputManager.CurrentControlScheme);
|
||||
|
||||
return string.IsNullOrWhiteSpace(displayString) ? string.Empty : displayString;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -148,6 +148,7 @@ namespace BriarQueen.Game.Cinematics
|
||||
if (_cinematicCanvasGroup == null || _imageA == null || _imageB == null)
|
||||
{
|
||||
Debug.LogWarning("[BaseCinematic] Missing CanvasGroup or Images.");
|
||||
await EndCinematicFlow();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using BriarQueen.Data.Identifiers;
|
||||
using BriarQueen.Framework.Assets;
|
||||
using BriarQueen.Framework.Coordinators.Events;
|
||||
using BriarQueen.Framework.Managers.Assets;
|
||||
using BriarQueen.Framework.Managers.Input;
|
||||
using BriarQueen.Framework.Managers.IO;
|
||||
using BriarQueen.Framework.Managers.Levels;
|
||||
@@ -83,12 +83,12 @@ namespace BriarQueen.Game.Cinematics
|
||||
_saveManager.CurrentSave.OpeningCinematicPlayed = true;
|
||||
|
||||
var levelLoaded = await _levelManager.LoadLevel(
|
||||
AssetKeyIdentifiers.Get(LevelKey.ChapterOneVillageEdge));
|
||||
AssetKeyIdentifiers.Get(LevelKey.ChapterOneArrivalRoad));
|
||||
|
||||
if (!levelLoaded)
|
||||
{
|
||||
Debug.LogError(
|
||||
"[OpeningCinematic] Failed to load ChapterOneVillageEdge after cinematic. Returning to main menu.");
|
||||
"[OpeningCinematic] Failed to load Chapter One Arrival Road after cinematic. Returning to main menu.");
|
||||
await _gameService.LoadMainMenu();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: be01b5741bc94861a9b41d8c8ce3b5ee
|
||||
timeCreated: 1773590011
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ae64d437e6594bf58b5512ee5ffca402
|
||||
timeCreated: 1773590011
|
||||
@@ -1,45 +0,0 @@
|
||||
using BriarQueen.Data.Identifiers;
|
||||
using BriarQueen.Data.IO.Saves;
|
||||
using BriarQueen.Framework.Events.UI;
|
||||
using BriarQueen.Framework.Managers.Levels.Data;
|
||||
using BriarQueen.Framework.Managers.Player.Data;
|
||||
using BriarQueen.Framework.Managers.UI;
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace BriarQueen.Game.Items.Environment.ChapterOne.Pumphouse
|
||||
{
|
||||
public class PumpHouseWaterValve : BaseItem
|
||||
{
|
||||
public override string InteractableName => "Water Valve";
|
||||
public override UICursorService.CursorStyle ApplicableCursorStyle => UICursorService.CursorStyle.Interact;
|
||||
|
||||
public override UniTask OnInteract(ItemDataSo item = null)
|
||||
{
|
||||
if (!CheckEmptyHands())
|
||||
return UniTask.CompletedTask;
|
||||
|
||||
if (item != null)
|
||||
{
|
||||
EventCoordinator.Publish(new DisplayInteractEvent(InteractEventIDs.Get(ItemInteractKey.CantUseItem)));
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
if (SaveManager.GetLevelFlag(LevelFlag.PumpWaterRestored))
|
||||
{
|
||||
EventCoordinator.Publish(new DisplayInteractEvent(InteractEventIDs.Get(LevelInteractKey.WaterValve)));
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
if (!SaveManager.GetLevelFlag(LevelFlag.FountainVinesCut))
|
||||
{
|
||||
EventCoordinator.Publish(new DisplayInteractEvent(InteractEventIDs.Get(LevelInteractKey.ClearVinesOutside)));
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
// TODO : Play Water SFX
|
||||
SaveManager.SetLevelFlag(LevelFlag.PumpWaterRestored, true);
|
||||
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7ffe97b80fed45bbb7152752c4f10685
|
||||
timeCreated: 1770991451
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3825afbb1ee941b1af18781d8f1311fa
|
||||
timeCreated: 1773590034
|
||||
@@ -1,44 +0,0 @@
|
||||
using BriarQueen.Data.Identifiers;
|
||||
using BriarQueen.Data.IO.Saves;
|
||||
using BriarQueen.Framework.Events.UI;
|
||||
using BriarQueen.Framework.Managers.Levels.Data;
|
||||
using BriarQueen.Framework.Managers.Player.Data;
|
||||
using BriarQueen.Framework.Managers.UI;
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace BriarQueen.Game.Items.Environment.ChapterOne.Village
|
||||
{
|
||||
public class ChainLock : BaseItem
|
||||
{
|
||||
public override string InteractableName => "Locked Chain";
|
||||
public override UICursorService.CursorStyle ApplicableCursorStyle => UICursorService.CursorStyle.Inspect;
|
||||
|
||||
public override async UniTask OnInteract(ItemDataSo item = null)
|
||||
{
|
||||
if (!CheckEmptyHands())
|
||||
return;
|
||||
|
||||
if (item == null)
|
||||
{
|
||||
EventCoordinator.Publish(new DisplayInteractEvent(InteractEventIDs.Get(LevelInteractKey.PumphouseChain)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.UniqueID != ItemIDs.Pickups[ItemKey.PumphouseKey])
|
||||
{
|
||||
EventCoordinator.Publish(new DisplayInteractEvent(InteractEventIDs.Get(ItemInteractKey.CantUseItem)));
|
||||
return;
|
||||
}
|
||||
|
||||
EventCoordinator.Publish(new DisplayInteractEvent(InteractEventIDs.Get(LevelInteractKey.UnlockedPumphouse)));
|
||||
await Remove();
|
||||
}
|
||||
|
||||
protected override UniTask OnRemoved()
|
||||
{
|
||||
SaveManager.SetLevelFlag(LevelFlag.PumpHouseOpened, true);
|
||||
PlayerManager.RemoveItem(ItemIDs.Pickups[ItemKey.PumphouseKey]);
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bf2e55b12a3040deb4e54465336fc989
|
||||
timeCreated: 1770985414
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 45144a83888f401e8d282181328521fb
|
||||
timeCreated: 1773953887
|
||||
@@ -1,76 +0,0 @@
|
||||
using BriarQueen.Data.Identifiers;
|
||||
using BriarQueen.Framework.Events.UI;
|
||||
using BriarQueen.Framework.Managers.Levels.Data;
|
||||
using BriarQueen.Framework.Managers.Player.Data;
|
||||
using BriarQueen.Framework.Managers.UI;
|
||||
using BriarQueen.Game.Levels.ChapterOne.VillageStreet;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BriarQueen.Game.Items.Environment.ChapterOne.VillageStreet
|
||||
{
|
||||
public class StreetVines : BaseItem
|
||||
{
|
||||
[SerializeField]
|
||||
private Street _owner;
|
||||
|
||||
public override UICursorService.CursorStyle ApplicableCursorStyle => UICursorService.CursorStyle.Inspect;
|
||||
|
||||
public override async UniTask OnInteract(ItemDataSo item = null)
|
||||
{
|
||||
if (_owner == null)
|
||||
{
|
||||
Debug.LogWarning("StreetVines is missing its Street owner.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CanUseKnife())
|
||||
{
|
||||
PublishFailureMessage();
|
||||
return;
|
||||
}
|
||||
|
||||
EventCoordinator.Publish(new DisplayInteractEvent(
|
||||
InteractEventIDs.Get(EnvironmentInteractKey.UsingKnife)));
|
||||
|
||||
await _owner.CutVines();
|
||||
}
|
||||
|
||||
private bool CanUseKnife()
|
||||
{
|
||||
if (SettingsService.Game.AutoUseTools)
|
||||
return PlayerManager.HasAccessToTool(ToolID.Knife);
|
||||
|
||||
return PlayerManager.GetEquippedTool() == ToolID.Knife;
|
||||
}
|
||||
|
||||
private void PublishFailureMessage()
|
||||
{
|
||||
var autoUseTools = SettingsService != null &&
|
||||
SettingsService.Game != null &&
|
||||
SettingsService.Game.AutoUseTools;
|
||||
|
||||
var equippedTool = PlayerManager.GetEquippedTool();
|
||||
|
||||
string message;
|
||||
|
||||
if (equippedTool == ToolID.None)
|
||||
{
|
||||
message = InteractEventIDs.Get(LevelInteractKey.CutVines);
|
||||
}
|
||||
else if (autoUseTools)
|
||||
{
|
||||
// In auto-use mode, reaching this point means the player does not have access
|
||||
// to the required tool at all, so this should still read like a generic failure.
|
||||
message = InteractEventIDs.Get(LevelInteractKey.CutVines);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Auto-use is disabled, and the player has some tool equipped that is not valid.
|
||||
message = InteractEventIDs.Get(ItemInteractKey.WrongTool);
|
||||
}
|
||||
|
||||
EventCoordinator.Publish(new DisplayInteractEvent(message));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 32a79fbd751c4216b83680bbc425cfa7
|
||||
timeCreated: 1773954077
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d5cf35e464ec407c81e5d29b6bf5c713
|
||||
timeCreated: 1773590078
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8c3efb5408364c23bc9f0256bb727fb5
|
||||
timeCreated: 1773609093
|
||||
@@ -1,20 +0,0 @@
|
||||
using BriarQueen.Data.Identifiers;
|
||||
using BriarQueen.Framework.Events.UI;
|
||||
using BriarQueen.Framework.Managers.Levels.Data;
|
||||
using BriarQueen.Framework.Managers.Player.Data;
|
||||
using BriarQueen.Framework.Managers.UI;
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace BriarQueen.Game.Items.Environment.ChapterOne.Workshop.Downstairs
|
||||
{
|
||||
public class WorkshopBrokenLantern : BaseItem
|
||||
{
|
||||
public override UICursorService.CursorStyle ApplicableCursorStyle => UICursorService.CursorStyle.Inspect;
|
||||
|
||||
public override UniTask OnInteract(ItemDataSo item = null)
|
||||
{
|
||||
EventCoordinator.Publish(new DisplayInteractEvent(InteractEventIDs.Get(EnvironmentInteractKey.BrokenLantern)));
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1004a82a9b7f487180b0fcf4978a4446
|
||||
timeCreated: 1773609258
|
||||
@@ -1,45 +0,0 @@
|
||||
using BriarQueen.Data.Identifiers;
|
||||
using BriarQueen.Framework.Events.UI;
|
||||
using BriarQueen.Framework.Managers.Levels.Data;
|
||||
using BriarQueen.Framework.Managers.Player.Data;
|
||||
using BriarQueen.Framework.Managers.UI;
|
||||
using BriarQueen.Game.Levels.ChapterOne.Workshop;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BriarQueen.Game.Items.Environment.ChapterOne.Workshop.Downstairs
|
||||
{
|
||||
public class WorkshopDownstairsLight : BaseItem
|
||||
{
|
||||
[SerializeField]
|
||||
private WorkshopDownstairs _workshopDownstairs;
|
||||
|
||||
public override UICursorService.CursorStyle ApplicableCursorStyle => UICursorService.CursorStyle.Interact;
|
||||
|
||||
public override string InteractableName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_workshopDownstairs.LightOn)
|
||||
return "Turn off Light";
|
||||
|
||||
return "Turn on Light";
|
||||
}
|
||||
}
|
||||
|
||||
public override UniTask OnInteract(ItemDataSo item = null)
|
||||
{
|
||||
if(!CheckEmptyHands())
|
||||
return UniTask.CompletedTask;
|
||||
|
||||
if (item != null)
|
||||
{
|
||||
EventCoordinator.Publish(new DisplayInteractEvent(InteractEventIDs.Get(ItemInteractKey.CantUseItem)));
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
_workshopDownstairs.ToggleLightSwitch();
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c5889af77b0491bb18818491222520b
|
||||
timeCreated: 1773609093
|
||||
@@ -1,20 +0,0 @@
|
||||
using BriarQueen.Data.Identifiers;
|
||||
using BriarQueen.Framework.Events.UI;
|
||||
using BriarQueen.Framework.Managers.Levels.Data;
|
||||
using BriarQueen.Framework.Managers.Player.Data;
|
||||
using BriarQueen.Framework.Managers.UI;
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace BriarQueen.Game.Items.Environment.ChapterOne.Workshop.Downstairs
|
||||
{
|
||||
public class WorkshopWriting : BaseItem
|
||||
{
|
||||
public override UICursorService.CursorStyle ApplicableCursorStyle => UICursorService.CursorStyle.Inspect;
|
||||
|
||||
public override UniTask OnInteract(ItemDataSo item = null)
|
||||
{
|
||||
EventCoordinator.Publish(new DisplayInteractEvent(InteractEventIDs.Get(EnvironmentInteractKey.WorkshopWriting)));
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f8ae950ec2f6412f9a05f9eb22a81004
|
||||
timeCreated: 1773681035
|
||||
@@ -1,74 +0,0 @@
|
||||
using System;
|
||||
using BriarQueen.Data.Identifiers;
|
||||
using BriarQueen.Data.IO.Saves;
|
||||
using BriarQueen.Framework.Events.Gameplay;
|
||||
using BriarQueen.Framework.Events.UI;
|
||||
using BriarQueen.Framework.Managers.Levels.Data;
|
||||
using BriarQueen.Framework.Managers.Player.Data;
|
||||
using BriarQueen.Framework.Managers.UI;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BriarQueen.Game.Items.Environment.ChapterOne.Workshop
|
||||
{
|
||||
public class GrindingStone : BaseItem
|
||||
{
|
||||
[SerializeField]
|
||||
private Levels.ChapterOne.Workshop.Workshop _workshop;
|
||||
|
||||
public override string InteractableName => "Grinding Stone";
|
||||
|
||||
public override UICursorService.CursorStyle ApplicableCursorStyle => UICursorService.CursorStyle.Interact;
|
||||
|
||||
public override async UniTask OnInteract(ItemDataSo item = null)
|
||||
{
|
||||
if (!CheckEmptyHands())
|
||||
return;
|
||||
|
||||
if (!SaveManager.GetLevelFlag(LevelFlag.WorkshopGrindstoneRepaired))
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
EventCoordinator.Publish(
|
||||
new DisplayInteractEvent(InteractEventIDs.Get(ItemInteractKey.SomethingMissing)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.UniqueID != ItemIDs.Get(ItemKey.GrindstoneAxlePin))
|
||||
{
|
||||
EventCoordinator.Publish(new DisplayInteractEvent(InteractEventIDs.Get(ItemInteractKey.CantUseItem)));
|
||||
return;
|
||||
}
|
||||
|
||||
await _workshop.SetWoodenPin();
|
||||
return;
|
||||
}
|
||||
|
||||
if (item == null)
|
||||
{
|
||||
EventCoordinator.Publish(new DisplayInteractEvent(InteractEventIDs.Get(EnvironmentInteractKey.UseGrindstone)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.UniqueID != ItemIDs.Get(ItemKey.RustedKnife))
|
||||
{
|
||||
EventCoordinator.Publish(new DisplayInteractEvent(InteractEventIDs.Get(ItemInteractKey.CantUseItem)));
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO - Animations, SFX, etc
|
||||
|
||||
EventCoordinator.Publish(new FadeEvent(false, 0.6f));
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(1.2f));
|
||||
AudioManager.Play(AudioNameIdentifiers.Get(SFXKey.SharpenKnife));
|
||||
EventCoordinator.Publish(new FadeEvent(true, 0.6f));
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(0.8f));
|
||||
|
||||
PlayerManager.UnlockTool(ToolID.Knife);
|
||||
TutorialService.DisplayTutorial(TutorialPopupID.Tools);
|
||||
PlayerManager.RemoveItem(ItemIDs.Get(ItemKey.RustedKnife));
|
||||
|
||||
EventCoordinator.Publish(new SelectedItemChangedEvent(null));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7af3243d1b3240a5bb86ba7cd0b2e9b8
|
||||
timeCreated: 1771003747
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 09d6091451f64822a55575f3ab2b6b68
|
||||
timeCreated: 1773590204
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b57d4daa04964b28b6c24a1e41fb4d6a
|
||||
timeCreated: 1773590204
|
||||
@@ -1,40 +0,0 @@
|
||||
using BriarQueen.Data.Identifiers;
|
||||
using BriarQueen.Framework.Events.UI;
|
||||
using BriarQueen.Framework.Managers.Levels.Data;
|
||||
using BriarQueen.Framework.Managers.Player.Data;
|
||||
using BriarQueen.Game.Levels.ChapterOne.Workshop.Upstairs;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BriarQueen.Game.Items.Environment.ChapterOne.Workshop.Upstairs.Bag
|
||||
{
|
||||
public class WorkshopBagBook : BaseItem
|
||||
{
|
||||
[Header("Internal")]
|
||||
[SerializeField]
|
||||
private WorkshopBag _bag;
|
||||
|
||||
public override string InteractableName => "Damaged Book";
|
||||
|
||||
public override async UniTask OnInteract(ItemDataSo item = null)
|
||||
{
|
||||
if(!CheckEmptyHands())
|
||||
return;
|
||||
|
||||
if (item != null)
|
||||
{
|
||||
EventCoordinator.Publish(new DisplayInteractEvent(InteractEventIDs.Get(ItemInteractKey.CantUseItem)));
|
||||
return;
|
||||
}
|
||||
|
||||
EventCoordinator.Publish(new DisplayInteractEvent(InteractEventIDs.Get(EnvironmentInteractKey.WorkshopBookDisintegrating)));
|
||||
await Remove();
|
||||
}
|
||||
|
||||
protected override UniTask OnRemoved()
|
||||
{
|
||||
_bag.RemoveBook();
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a482fd71d48d4edaaceec7a7038a72a2
|
||||
timeCreated: 1773496524
|
||||
@@ -1,48 +0,0 @@
|
||||
using System;
|
||||
using BriarQueen.Data.Identifiers;
|
||||
using BriarQueen.Data.IO.Saves;
|
||||
using BriarQueen.Framework.Events.UI;
|
||||
using BriarQueen.Framework.Managers.Levels.Data;
|
||||
using BriarQueen.Framework.Managers.Player.Data;
|
||||
using BriarQueen.Game.Levels.ChapterOne.Workshop.Upstairs;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BriarQueen.Game.Items.Environment.ChapterOne.Workshop.Upstairs.Bag
|
||||
{
|
||||
public class WorkshopBagHole : BaseItem
|
||||
{
|
||||
[Header("Workshop Bag")]
|
||||
[SerializeField]
|
||||
private WorkshopBag _workshopBag;
|
||||
|
||||
public override string InteractableName => "Dig";
|
||||
|
||||
public override async UniTask OnInteract(ItemDataSo item = null)
|
||||
{
|
||||
if(!CheckEmptyHands())
|
||||
return;
|
||||
|
||||
if (SaveManager.GetLevelFlag(LevelFlag.WorkshopBagHoleDug))
|
||||
{
|
||||
EventCoordinator.Publish(new DisplayInteractEvent(
|
||||
InteractEventIDs.Get(EnvironmentInteractKey.WorkshopBagNoItems)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (item != null)
|
||||
{
|
||||
EventCoordinator.Publish(new DisplayInteractEvent(InteractEventIDs.Get(ItemInteractKey.CantUseItem)));
|
||||
return;
|
||||
}
|
||||
|
||||
EventCoordinator.Publish(new FadeEvent(false, 1f));
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(1));
|
||||
|
||||
await _workshopBag.DigHole();
|
||||
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(1));
|
||||
EventCoordinator.Publish(new FadeEvent(true, 1f));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d8e8a954a6d04904b15e4e1b50db8615
|
||||
timeCreated: 1773494920
|
||||
@@ -19,7 +19,7 @@ namespace BriarQueen.Game.Items.Environment.General.Book
|
||||
private AssetItemKey _bookAssetID;
|
||||
|
||||
[SerializeField]
|
||||
private BookEntryID _bookEntryID;
|
||||
private DocumentEntryID _documentEntryID;
|
||||
|
||||
[Header("Book Interface")]
|
||||
[SerializeField]
|
||||
@@ -58,7 +58,11 @@ namespace BriarQueen.Game.Items.Environment.General.Book
|
||||
if (bookObj == null) return;
|
||||
|
||||
_bookInterface = bookObj.GetComponent<BookInterface>();
|
||||
if (_bookInterface == null) return;
|
||||
if (_bookInterface == null)
|
||||
{
|
||||
await DestructionService.Destroy(bookObj);
|
||||
return;
|
||||
}
|
||||
|
||||
_bookInterface.CanvasGroup.alpha = 0f;
|
||||
_bookInterface.CanvasGroup.blocksRaycasts = false;
|
||||
@@ -99,7 +103,7 @@ namespace BriarQueen.Game.Items.Environment.General.Book
|
||||
|
||||
private void UnlockCodexEntry()
|
||||
{
|
||||
PlayerManager.UnlockCodexEntry(CodexEntryIDs.Get(_bookEntryID));
|
||||
PlayerManager.UnlockCodexEntry(CodexEntryIDs.Get(_documentEntryID));
|
||||
}
|
||||
|
||||
public async UniTask CloseBookInterface()
|
||||
@@ -144,4 +148,4 @@ namespace BriarQueen.Game.Items.Environment.General.Book
|
||||
_displayCts = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user