(Feat) add crafting discovery + notification + inventory tool tips
@@ -430,6 +430,7 @@ GameObject:
|
|||||||
- component: {fileID: 4782095384616423459}
|
- component: {fileID: 4782095384616423459}
|
||||||
- component: {fileID: 1509526206074017255}
|
- component: {fileID: 1509526206074017255}
|
||||||
- component: {fileID: 1890885114087150018}
|
- component: {fileID: 1890885114087150018}
|
||||||
|
- component: {fileID: -4423719270533760289}
|
||||||
- component: {fileID: 656527942023048284}
|
- component: {fileID: 656527942023048284}
|
||||||
- component: {fileID: 4969709554801488359}
|
- component: {fileID: 4969709554801488359}
|
||||||
m_Layer: 10
|
m_Layer: 10
|
||||||
@@ -919,6 +920,7 @@ MonoBehaviour:
|
|||||||
- {fileID: 4232943699575871128}
|
- {fileID: 4232943699575871128}
|
||||||
- {fileID: 7331404443355966726}
|
- {fileID: 7331404443355966726}
|
||||||
- {fileID: 3026700730577618004}
|
- {fileID: 3026700730577618004}
|
||||||
|
- {fileID: -4423719270533760289}
|
||||||
ownerOnlyObjects:
|
ownerOnlyObjects:
|
||||||
- {fileID: 3016760143010852029}
|
- {fileID: 3016760143010852029}
|
||||||
- {fileID: 5457574158965716017}
|
- {fileID: 5457574158965716017}
|
||||||
@@ -1040,6 +1042,21 @@ MonoBehaviour:
|
|||||||
nameLabel: {fileID: 7210073718532933207}
|
nameLabel: {fileID: 7210073718532933207}
|
||||||
faceCamera: 1
|
faceCamera: 1
|
||||||
flipHorizontal: 0
|
flipHorizontal: 0
|
||||||
|
--- !u!114 &-4423719270533760289
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 5167716622245296089}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: fabb912832c44b945a26c8ef03db222a, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Assembly-CSharp::Ashwild.Crafting.CraftDiscovery
|
||||||
|
_componentIndexCache: 255
|
||||||
|
_addedNetworkObject: {fileID: -5889822588553870904}
|
||||||
|
_networkObjectCache: {fileID: 0}
|
||||||
--- !u!114 &656527942023048284
|
--- !u!114 &656527942023048284
|
||||||
MonoBehaviour:
|
MonoBehaviour:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
|
|||||||
@@ -1,5 +1,69 @@
|
|||||||
%YAML 1.1
|
%YAML 1.1
|
||||||
%TAG !u! tag:unity3d.com,2011:
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!1 &4431250198828445795
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 4036155841356705039}
|
||||||
|
- component: {fileID: 6008748456738279482}
|
||||||
|
m_Layer: 5
|
||||||
|
m_Name: GameObject
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!224 &4036155841356705039
|
||||||
|
RectTransform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 4431250198828445795}
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children:
|
||||||
|
- {fileID: 7818010301368158588}
|
||||||
|
- {fileID: 3950729247332657127}
|
||||||
|
m_Father: {fileID: 2991053326735533102}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
m_AnchorMin: {x: 0, y: 0}
|
||||||
|
m_AnchorMax: {x: 1, y: 1}
|
||||||
|
m_AnchoredPosition: {x: 0, y: 0}
|
||||||
|
m_SizeDelta: {x: -20.8044, y: -20.8044}
|
||||||
|
m_Pivot: {x: 0.5, y: 0.5}
|
||||||
|
--- !u!114 &6008748456738279482
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 4431250198828445795}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 59f8146938fff824cb5fd77236b75775, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.VerticalLayoutGroup
|
||||||
|
m_Padding:
|
||||||
|
m_Left: 0
|
||||||
|
m_Right: 0
|
||||||
|
m_Top: 0
|
||||||
|
m_Bottom: 0
|
||||||
|
m_ChildAlignment: 4
|
||||||
|
m_Spacing: 0
|
||||||
|
m_ChildForceExpandWidth: 1
|
||||||
|
m_ChildForceExpandHeight: 1
|
||||||
|
m_ChildControlWidth: 1
|
||||||
|
m_ChildControlHeight: 0
|
||||||
|
m_ChildScaleWidth: 1
|
||||||
|
m_ChildScaleHeight: 1
|
||||||
|
m_ReverseArrangement: 0
|
||||||
--- !u!1 &4933178774466085049
|
--- !u!1 &4933178774466085049
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -25,17 +89,17 @@ RectTransform:
|
|||||||
m_PrefabInstance: {fileID: 0}
|
m_PrefabInstance: {fileID: 0}
|
||||||
m_PrefabAsset: {fileID: 0}
|
m_PrefabAsset: {fileID: 0}
|
||||||
m_GameObject: {fileID: 4933178774466085049}
|
m_GameObject: {fileID: 4933178774466085049}
|
||||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
m_ConstrainProportionsScale: 0
|
m_ConstrainProportionsScale: 0
|
||||||
m_Children: []
|
m_Children: []
|
||||||
m_Father: {fileID: 2991053326735533102}
|
m_Father: {fileID: 4036155841356705039}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
m_AnchorMin: {x: 0.5, y: 1}
|
m_AnchorMin: {x: 0, y: 0}
|
||||||
m_AnchorMax: {x: 0.5, y: 1}
|
m_AnchorMax: {x: 0, y: 0}
|
||||||
m_AnchoredPosition: {x: 0, y: -10.6}
|
m_AnchoredPosition: {x: 0, y: 0}
|
||||||
m_SizeDelta: {x: 126.0229, y: 126.0229}
|
m_SizeDelta: {x: 0, y: 128}
|
||||||
m_Pivot: {x: 0.5, y: 1}
|
m_Pivot: {x: 0.5, y: 1}
|
||||||
--- !u!222 &8806915406871817360
|
--- !u!222 &8806915406871817360
|
||||||
CanvasRenderer:
|
CanvasRenderer:
|
||||||
@@ -101,17 +165,17 @@ RectTransform:
|
|||||||
m_PrefabInstance: {fileID: 0}
|
m_PrefabInstance: {fileID: 0}
|
||||||
m_PrefabAsset: {fileID: 0}
|
m_PrefabAsset: {fileID: 0}
|
||||||
m_GameObject: {fileID: 5498742472193566416}
|
m_GameObject: {fileID: 5498742472193566416}
|
||||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
m_ConstrainProportionsScale: 0
|
m_ConstrainProportionsScale: 0
|
||||||
m_Children: []
|
m_Children: []
|
||||||
m_Father: {fileID: 2991053326735533102}
|
m_Father: {fileID: 4036155841356705039}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
m_AnchorMin: {x: 0.5, y: 0}
|
m_AnchorMin: {x: 0, y: 1}
|
||||||
m_AnchorMax: {x: 0.5, y: 0}
|
m_AnchorMax: {x: 0, y: 1}
|
||||||
m_AnchoredPosition: {x: 0, y: 13.2}
|
m_AnchoredPosition: {x: 89.5978, y: -180.48}
|
||||||
m_SizeDelta: {x: 0, y: 0}
|
m_SizeDelta: {x: 66.59, y: 0}
|
||||||
m_Pivot: {x: 0.5, y: 0}
|
m_Pivot: {x: 0.5, y: 0}
|
||||||
--- !u!222 &4058813637582350659
|
--- !u!222 &4058813637582350659
|
||||||
CanvasRenderer:
|
CanvasRenderer:
|
||||||
@@ -258,8 +322,7 @@ RectTransform:
|
|||||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
m_ConstrainProportionsScale: 0
|
m_ConstrainProportionsScale: 0
|
||||||
m_Children:
|
m_Children:
|
||||||
- {fileID: 7818010301368158588}
|
- {fileID: 4036155841356705039}
|
||||||
- {fileID: 3950729247332657127}
|
|
||||||
m_Father: {fileID: 0}
|
m_Father: {fileID: 0}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
m_AnchorMin: {x: 0, y: 0}
|
m_AnchorMin: {x: 0, y: 0}
|
||||||
@@ -319,5 +382,6 @@ MonoBehaviour:
|
|||||||
m_EditorClassIdentifier: Assembly-CSharp::IngredientSlotUI
|
m_EditorClassIdentifier: Assembly-CSharp::IngredientSlotUI
|
||||||
iconImage: {fileID: 6656192589770360378}
|
iconImage: {fileID: 6656192589770360378}
|
||||||
quantityText: {fileID: 2097148993551982845}
|
quantityText: {fileID: 2097148993551982845}
|
||||||
|
mysteryIcon: {fileID: 21300000, guid: 674a34f9888b21f4f9e8fe295f753065, type: 3}
|
||||||
enoughColor: {r: 1, g: 1, b: 1, a: 1}
|
enoughColor: {r: 1, g: 1, b: 1, a: 1}
|
||||||
missingColor: {r: 1, g: 0.3, b: 0.3, a: 1}
|
missingColor: {r: 1, g: 0.3, b: 0.3, a: 1}
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ RectTransform:
|
|||||||
m_AnchorMin: {x: 0, y: 1}
|
m_AnchorMin: {x: 0, y: 1}
|
||||||
m_AnchorMax: {x: 0, y: 1}
|
m_AnchorMax: {x: 0, y: 1}
|
||||||
m_AnchoredPosition: {x: 26.81, y: -30.848938}
|
m_AnchoredPosition: {x: 26.81, y: -30.848938}
|
||||||
m_SizeDelta: {x: 0, y: 0}
|
m_SizeDelta: {x: 26.81, y: 0}
|
||||||
m_Pivot: {x: 1, y: 0.5}
|
m_Pivot: {x: 1, y: 0.5}
|
||||||
--- !u!222 &4956121116561260591
|
--- !u!222 &4956121116561260591
|
||||||
CanvasRenderer:
|
CanvasRenderer:
|
||||||
|
|||||||
@@ -3613,22 +3613,6 @@ PrefabInstance:
|
|||||||
serializedVersion: 3
|
serializedVersion: 3
|
||||||
m_TransformParent: {fileID: 0}
|
m_TransformParent: {fileID: 0}
|
||||||
m_Modifications:
|
m_Modifications:
|
||||||
- target: {fileID: 268644269685709644, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
|
||||||
propertyPath: m_AnchorMax.y
|
|
||||||
value: 0
|
|
||||||
objectReference: {fileID: 0}
|
|
||||||
- target: {fileID: 268644269685709644, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
|
||||||
propertyPath: m_AnchorMin.y
|
|
||||||
value: 0
|
|
||||||
objectReference: {fileID: 0}
|
|
||||||
- target: {fileID: 268644269685709644, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
|
||||||
propertyPath: m_SizeDelta.x
|
|
||||||
value: 0
|
|
||||||
objectReference: {fileID: 0}
|
|
||||||
- target: {fileID: 268644269685709644, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
|
||||||
propertyPath: m_AnchoredPosition.x
|
|
||||||
value: 0
|
|
||||||
objectReference: {fileID: 0}
|
|
||||||
- target: {fileID: 297657202941871783, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
- target: {fileID: 297657202941871783, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
||||||
propertyPath: m_AnchorMax.y
|
propertyPath: m_AnchorMax.y
|
||||||
value: 0
|
value: 0
|
||||||
@@ -3773,14 +3757,6 @@ PrefabInstance:
|
|||||||
propertyPath: m_AnchoredPosition.y
|
propertyPath: m_AnchoredPosition.y
|
||||||
value: 0
|
value: 0
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 1798594070215275997, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
|
||||||
propertyPath: m_SizeDelta.x
|
|
||||||
value: 0
|
|
||||||
objectReference: {fileID: 0}
|
|
||||||
- target: {fileID: 1798594070215275997, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
|
||||||
propertyPath: m_SizeDelta.y
|
|
||||||
value: 0
|
|
||||||
objectReference: {fileID: 0}
|
|
||||||
- target: {fileID: 1941997771569907896, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
- target: {fileID: 1941997771569907896, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
||||||
propertyPath: m_AnchorMax.y
|
propertyPath: m_AnchorMax.y
|
||||||
value: 0
|
value: 0
|
||||||
@@ -3817,34 +3793,6 @@ PrefabInstance:
|
|||||||
propertyPath: m_AnchoredPosition.y
|
propertyPath: m_AnchoredPosition.y
|
||||||
value: 0
|
value: 0
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 2645420000368753197, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
|
||||||
propertyPath: m_AnchorMax.y
|
|
||||||
value: 0
|
|
||||||
objectReference: {fileID: 0}
|
|
||||||
- target: {fileID: 2645420000368753197, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
|
||||||
propertyPath: m_AnchorMin.y
|
|
||||||
value: 0
|
|
||||||
objectReference: {fileID: 0}
|
|
||||||
- target: {fileID: 2645420000368753197, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
|
||||||
propertyPath: m_SizeDelta.x
|
|
||||||
value: 0
|
|
||||||
objectReference: {fileID: 0}
|
|
||||||
- target: {fileID: 2645420000368753197, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
|
||||||
propertyPath: m_AnchoredPosition.x
|
|
||||||
value: 0
|
|
||||||
objectReference: {fileID: 0}
|
|
||||||
- target: {fileID: 2645420000368753197, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
|
||||||
propertyPath: m_AnchoredPosition.y
|
|
||||||
value: 0
|
|
||||||
objectReference: {fileID: 0}
|
|
||||||
- target: {fileID: 3137753552549908057, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
|
||||||
propertyPath: m_SizeDelta.x
|
|
||||||
value: 0
|
|
||||||
objectReference: {fileID: 0}
|
|
||||||
- target: {fileID: 3137753552549908057, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
|
||||||
propertyPath: m_SizeDelta.y
|
|
||||||
value: 0
|
|
||||||
objectReference: {fileID: 0}
|
|
||||||
- target: {fileID: 3377263676762350218, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
- target: {fileID: 3377263676762350218, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
||||||
propertyPath: m_AnchorMax.y
|
propertyPath: m_AnchorMax.y
|
||||||
value: 0
|
value: 0
|
||||||
@@ -4041,26 +3989,6 @@ PrefabInstance:
|
|||||||
propertyPath: m_SizeDelta.x
|
propertyPath: m_SizeDelta.x
|
||||||
value: 0
|
value: 0
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 5483805172319020686, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
|
||||||
propertyPath: m_AnchorMax.y
|
|
||||||
value: 0
|
|
||||||
objectReference: {fileID: 0}
|
|
||||||
- target: {fileID: 5483805172319020686, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
|
||||||
propertyPath: m_AnchorMin.y
|
|
||||||
value: 0
|
|
||||||
objectReference: {fileID: 0}
|
|
||||||
- target: {fileID: 5483805172319020686, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
|
||||||
propertyPath: m_SizeDelta.x
|
|
||||||
value: 0
|
|
||||||
objectReference: {fileID: 0}
|
|
||||||
- target: {fileID: 5483805172319020686, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
|
||||||
propertyPath: m_AnchoredPosition.x
|
|
||||||
value: 0
|
|
||||||
objectReference: {fileID: 0}
|
|
||||||
- target: {fileID: 5483805172319020686, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
|
||||||
propertyPath: m_AnchoredPosition.y
|
|
||||||
value: 0
|
|
||||||
objectReference: {fileID: 0}
|
|
||||||
- target: {fileID: 5516694424821111620, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
- target: {fileID: 5516694424821111620, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
||||||
propertyPath: m_AnchorMax.y
|
propertyPath: m_AnchorMax.y
|
||||||
value: 0
|
value: 0
|
||||||
@@ -4245,22 +4173,6 @@ PrefabInstance:
|
|||||||
propertyPath: m_AnchoredPosition.y
|
propertyPath: m_AnchoredPosition.y
|
||||||
value: 0
|
value: 0
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 7716463092849739759, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
|
||||||
propertyPath: m_AnchorMax.y
|
|
||||||
value: 0
|
|
||||||
objectReference: {fileID: 0}
|
|
||||||
- target: {fileID: 7716463092849739759, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
|
||||||
propertyPath: m_AnchorMin.y
|
|
||||||
value: 0
|
|
||||||
objectReference: {fileID: 0}
|
|
||||||
- target: {fileID: 7716463092849739759, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
|
||||||
propertyPath: m_SizeDelta.x
|
|
||||||
value: 0
|
|
||||||
objectReference: {fileID: 0}
|
|
||||||
- target: {fileID: 7716463092849739759, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
|
||||||
propertyPath: m_AnchoredPosition.x
|
|
||||||
value: 0
|
|
||||||
objectReference: {fileID: 0}
|
|
||||||
- target: {fileID: 7749384703976975436, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
- target: {fileID: 7749384703976975436, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
||||||
propertyPath: m_AnchorMax.y
|
propertyPath: m_AnchorMax.y
|
||||||
value: 0
|
value: 0
|
||||||
@@ -4317,18 +4229,6 @@ PrefabInstance:
|
|||||||
propertyPath: m_SizeDelta.y
|
propertyPath: m_SizeDelta.y
|
||||||
value: 0
|
value: 0
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 7924365604093564939, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
|
||||||
propertyPath: m_SizeDelta.y
|
|
||||||
value: 0
|
|
||||||
objectReference: {fileID: 0}
|
|
||||||
- target: {fileID: 8095760197126531454, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
|
||||||
propertyPath: m_SizeDelta.x
|
|
||||||
value: 0
|
|
||||||
objectReference: {fileID: 0}
|
|
||||||
- target: {fileID: 8095760197126531454, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
|
||||||
propertyPath: m_SizeDelta.y
|
|
||||||
value: 0
|
|
||||||
objectReference: {fileID: 0}
|
|
||||||
- target: {fileID: 8331890770765351021, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
- target: {fileID: 8331890770765351021, guid: fc052529108a2854aa09a1d295778ef4, type: 3}
|
||||||
propertyPath: m_Name
|
propertyPath: m_Name
|
||||||
value: Canvas
|
value: Canvas
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
using FishNet.Object;
|
||||||
|
using Ashwild.Inventory;
|
||||||
|
using Ashwild.Player;
|
||||||
|
|
||||||
|
namespace Ashwild.Crafting
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The local player's craft-discovery knowledge: the set of items it has ever acquired.
|
||||||
|
/// A recipe is only revealed (and only craftable) once its ingredients have been discovered,
|
||||||
|
/// so the player uncovers crafts by gathering resources rather than seeing the whole catalog
|
||||||
|
/// at once. Discovery is triggered the first time an item enters the inventory — pickups,
|
||||||
|
/// harvest drops AND craft results all count, which lets crafting chains reveal step by step.
|
||||||
|
///
|
||||||
|
/// This is owner-only local state: each player keeps its own knowledge, and it is never
|
||||||
|
/// replicated over the wire (no one else needs to read it). It mirrors PlayerInventory's
|
||||||
|
/// singleton pattern purely for reliable owner gating — set the Instance for the owning
|
||||||
|
/// client in OnStartNetwork, and gate the component off on remotes via ownerOnlyBehaviours.
|
||||||
|
/// </summary>
|
||||||
|
[DisallowMultipleComponent]
|
||||||
|
public class CraftDiscovery : NetworkBehaviour
|
||||||
|
{
|
||||||
|
#region Singleton
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The local player's discovery knowledge (set only on the owning client).
|
||||||
|
/// </summary>
|
||||||
|
public static CraftDiscovery Instance { get; private set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region State
|
||||||
|
|
||||||
|
private readonly HashSet<ItemData> discovered = new HashSet<ItemData>();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Network Lifecycle
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers the singleton on the owning client only (before LocalPlayerSpawned fires).
|
||||||
|
/// </summary>
|
||||||
|
public override void OnStartNetwork()
|
||||||
|
{
|
||||||
|
base.OnStartNetwork();
|
||||||
|
if (base.Owner.IsLocalClient) Instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears the singleton when this player leaves the network.
|
||||||
|
/// </summary>
|
||||||
|
public override void OnStopNetwork()
|
||||||
|
{
|
||||||
|
base.OnStopNetwork();
|
||||||
|
if (Instance == this) Instance = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Unity Lifecycle
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts tracking every item the local player acquires.
|
||||||
|
/// </summary>
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
PlayerEvents.ItemAdded += HandleItemAdded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops tracking — mirrors OnEnable exactly.
|
||||||
|
/// </summary>
|
||||||
|
private void OnDisable()
|
||||||
|
{
|
||||||
|
PlayerEvents.ItemAdded -= HandleItemAdded;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Event Handlers
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marks an acquired item as discovered. Guarded to the owning instance so a remote puppet
|
||||||
|
/// that briefly subscribed before being gated off can never drive discovery on the wrong copy.
|
||||||
|
/// </summary>
|
||||||
|
private void HandleItemAdded(ItemData item, int quantity)
|
||||||
|
{
|
||||||
|
if (Instance != this) return;
|
||||||
|
Discover(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public API
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the local player has ever acquired this item (and therefore knows it).
|
||||||
|
/// </summary>
|
||||||
|
public bool IsDiscovered(ItemData item) => item != null && discovered.Contains(item);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Internal Helpers
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds an item to the discovered set the first time it is seen and announces it on the bus
|
||||||
|
/// so the crafting view can reveal any recipe the item unlocks.
|
||||||
|
/// </summary>
|
||||||
|
private void Discover(ItemData item)
|
||||||
|
{
|
||||||
|
if (item == null) return;
|
||||||
|
if (discovered.Add(item))
|
||||||
|
PlayerEvents.RaiseItemDiscovered(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: fabb912832c44b945a26c8ef03db222a
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Events;
|
using UnityEngine.Events;
|
||||||
using Ashwild.Inventory;
|
using Ashwild.Inventory;
|
||||||
@@ -31,6 +32,7 @@ namespace Ashwild.Crafting
|
|||||||
|
|
||||||
private PlayerInventory inventory;
|
private PlayerInventory inventory;
|
||||||
private bool bound;
|
private bool bound;
|
||||||
|
private readonly HashSet<CraftingRecipe> unlockedRecipes = new HashSet<CraftingRecipe>();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -43,19 +45,25 @@ namespace Ashwild.Crafting
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns whether the local player can currently craft the recipe the given number of times.
|
/// Returns whether the local player can currently craft the recipe the given number of times.
|
||||||
|
/// A recipe is only craftable once every ingredient has been discovered, on top of the usual
|
||||||
|
/// inventory check — an ingredient still shown as a mystery ("?") blocks the craft.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool CanCraft(CraftingRecipe recipe, int count = 1)
|
public bool CanCraft(CraftingRecipe recipe, int count = 1)
|
||||||
{
|
{
|
||||||
return PlayerInventory.Instance != null && recipe.CanCraft(PlayerInventory.Instance, count);
|
return PlayerInventory.Instance != null
|
||||||
|
&& IsFullyDiscovered(recipe)
|
||||||
|
&& recipe.CanCraft(PlayerInventory.Instance, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Consumes the ingredients and produces the result if the local player can afford it.
|
/// Consumes the ingredients and produces the result if the local player can afford it and has
|
||||||
|
/// discovered every ingredient. The result item is added through the inventory, which in turn
|
||||||
|
/// discovers it — so crafting an intermediate result reveals the recipes further down the chain.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool TryCraft(CraftingRecipe recipe, int count = 1)
|
public bool TryCraft(CraftingRecipe recipe, int count = 1)
|
||||||
{
|
{
|
||||||
PlayerInventory inv = PlayerInventory.Instance;
|
PlayerInventory inv = PlayerInventory.Instance;
|
||||||
if (inv == null || !recipe.CanCraft(inv, count))
|
if (inv == null || !IsFullyDiscovered(recipe) || !recipe.CanCraft(inv, count))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Consume ingredients
|
// Consume ingredients
|
||||||
@@ -82,6 +90,7 @@ namespace Ashwild.Crafting
|
|||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
{
|
{
|
||||||
PlayerEvents.LocalPlayerSpawned += HandleLocalPlayerSpawned;
|
PlayerEvents.LocalPlayerSpawned += HandleLocalPlayerSpawned;
|
||||||
|
PlayerEvents.ItemDiscovered += HandleItemDiscovered;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -90,6 +99,7 @@ namespace Ashwild.Crafting
|
|||||||
private void OnDisable()
|
private void OnDisable()
|
||||||
{
|
{
|
||||||
PlayerEvents.LocalPlayerSpawned -= HandleLocalPlayerSpawned;
|
PlayerEvents.LocalPlayerSpawned -= HandleLocalPlayerSpawned;
|
||||||
|
PlayerEvents.ItemDiscovered -= HandleItemDiscovered;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -98,7 +108,7 @@ namespace Ashwild.Crafting
|
|||||||
private void Start()
|
private void Start()
|
||||||
{
|
{
|
||||||
if (craftingUI != null)
|
if (craftingUI != null)
|
||||||
craftingUI.Build(recipes, CanCraft, CountItem, TryCraft);
|
craftingUI.Build(recipes, CanCraft, CountItem, TryCraft, IsDiscovered, IsRecipeVisible);
|
||||||
else
|
else
|
||||||
Debug.LogError("[CraftingManager] No CraftingUI assigned — the crafting panel will not populate.", this);
|
Debug.LogError("[CraftingManager] No CraftingUI assigned — the crafting panel will not populate.", this);
|
||||||
|
|
||||||
@@ -134,6 +144,19 @@ namespace Ashwild.Crafting
|
|||||||
craftingUI.Refresh();
|
craftingUI.Refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reacts to a newly discovered item: announces any recipe it just unlocked (for the "NEW"
|
||||||
|
/// craft notification), then refreshes the view so those recipes appear and their once-mystery
|
||||||
|
/// ingredients are shown.
|
||||||
|
/// </summary>
|
||||||
|
private void HandleItemDiscovered(ItemData item)
|
||||||
|
{
|
||||||
|
NotifyNewlyUnlockedRecipes();
|
||||||
|
|
||||||
|
if (craftingUI != null)
|
||||||
|
craftingUI.Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Internal Helpers
|
#region Internal Helpers
|
||||||
@@ -150,10 +173,41 @@ namespace Ashwild.Crafting
|
|||||||
|
|
||||||
inventory.onSlotChanged.AddListener(HandleInventoryChanged);
|
inventory.onSlotChanged.AddListener(HandleInventoryChanged);
|
||||||
|
|
||||||
|
SeedUnlockedRecipes();
|
||||||
|
|
||||||
if (craftingUI != null)
|
if (craftingUI != null)
|
||||||
craftingUI.Refresh();
|
craftingUI.Refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Records the recipes already revealed at bind time (e.g. from restored discoveries) so they
|
||||||
|
/// are treated as known and never fire a spurious "NEW" notification. With a fresh player this
|
||||||
|
/// is empty since discovery starts at zero.
|
||||||
|
/// </summary>
|
||||||
|
private void SeedUnlockedRecipes()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < recipes.Length; i++)
|
||||||
|
if (IsRecipeVisible(recipes[i]))
|
||||||
|
unlockedRecipes.Add(recipes[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raises a "NEW" notification for every recipe that has just become visible (its first
|
||||||
|
/// discovered ingredient), each announced once. Called after an item is discovered.
|
||||||
|
/// </summary>
|
||||||
|
private void NotifyNewlyUnlockedRecipes()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < recipes.Length; i++)
|
||||||
|
{
|
||||||
|
CraftingRecipe recipe = recipes[i];
|
||||||
|
if (unlockedRecipes.Contains(recipe)) continue;
|
||||||
|
if (!IsRecipeVisible(recipe)) continue;
|
||||||
|
|
||||||
|
unlockedRecipes.Add(recipe);
|
||||||
|
PlayerEvents.RaiseRecipeDiscovered(recipe.Icon, recipe.RecipeName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How many of an item the local player currently holds (0 when offline).
|
/// How many of an item the local player currently holds (0 when offline).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -162,6 +216,37 @@ namespace Ashwild.Crafting
|
|||||||
return PlayerInventory.Instance != null ? PlayerInventory.Instance.CountItem(item) : 0;
|
return PlayerInventory.Instance != null ? PlayerInventory.Instance.CountItem(item) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the local player has discovered the item (false when the discovery tracker isn't ready).
|
||||||
|
/// </summary>
|
||||||
|
private bool IsDiscovered(ItemData item)
|
||||||
|
{
|
||||||
|
return CraftDiscovery.Instance != null && CraftDiscovery.Instance.IsDiscovered(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the recipe should appear at all: revealed as soon as at least one of its ingredients
|
||||||
|
/// has been discovered. Undiscovered ingredients then show as a mystery until found.
|
||||||
|
/// </summary>
|
||||||
|
private bool IsRecipeVisible(CraftingRecipe recipe)
|
||||||
|
{
|
||||||
|
CraftingIngredient[] ingredients = recipe.Ingredients;
|
||||||
|
for (int i = 0; i < ingredients.Length; i++)
|
||||||
|
if (IsDiscovered(ingredients[i].item)) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether every ingredient of the recipe has been discovered — the gate for actually crafting it.
|
||||||
|
/// </summary>
|
||||||
|
private bool IsFullyDiscovered(CraftingRecipe recipe)
|
||||||
|
{
|
||||||
|
CraftingIngredient[] ingredients = recipe.Ingredients;
|
||||||
|
for (int i = 0; i < ingredients.Length; i++)
|
||||||
|
if (!IsDiscovered(ingredients[i].item)) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ namespace Ashwild.Crafting
|
|||||||
private Func<CraftingRecipe, int, bool> canCraftQuery;
|
private Func<CraftingRecipe, int, bool> canCraftQuery;
|
||||||
private Func<ItemData, int> countItemQuery;
|
private Func<ItemData, int> countItemQuery;
|
||||||
private Func<CraftingRecipe, int, bool> craftRequest;
|
private Func<CraftingRecipe, int, bool> craftRequest;
|
||||||
|
private Func<ItemData, bool> isDiscoveredQuery;
|
||||||
|
private Func<CraftingRecipe, bool> isRecipeVisibleQuery;
|
||||||
private readonly List<RecipeButtonUI> recipeButtons = new List<RecipeButtonUI>();
|
private readonly List<RecipeButtonUI> recipeButtons = new List<RecipeButtonUI>();
|
||||||
private readonly List<GameObject> ingredientInstances = new List<GameObject>();
|
private readonly List<GameObject> ingredientInstances = new List<GameObject>();
|
||||||
private RecipeButtonUI selectedButton;
|
private RecipeButtonUI selectedButton;
|
||||||
@@ -55,12 +57,15 @@ namespace Ashwild.Crafting
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Builds the recipe buttons (once) and wires the manager-owned queries/action. Safe to
|
/// Builds the recipe buttons (once) and wires the manager-owned queries/action. Safe to
|
||||||
/// call again later to re-point the delegates; it then simply refreshes the display.
|
/// call again later to re-point the delegates; it then simply refreshes the display.
|
||||||
|
/// The discovery queries decide which recipes are shown and which ingredients are still a mystery.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Build(CraftingRecipe[] recipes, Func<CraftingRecipe, int, bool> canCraft, Func<ItemData, int> countItem, Func<CraftingRecipe, int, bool> craft)
|
public void Build(CraftingRecipe[] recipes, Func<CraftingRecipe, int, bool> canCraft, Func<ItemData, int> countItem, Func<CraftingRecipe, int, bool> craft, Func<ItemData, bool> isDiscovered, Func<CraftingRecipe, bool> isRecipeVisible)
|
||||||
{
|
{
|
||||||
canCraftQuery = canCraft;
|
canCraftQuery = canCraft;
|
||||||
countItemQuery = countItem;
|
countItemQuery = countItem;
|
||||||
craftRequest = craft;
|
craftRequest = craft;
|
||||||
|
isDiscoveredQuery = isDiscovered;
|
||||||
|
isRecipeVisibleQuery = isRecipeVisible;
|
||||||
|
|
||||||
if (!built)
|
if (!built)
|
||||||
{
|
{
|
||||||
@@ -81,8 +86,9 @@ namespace Ashwild.Crafting
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Recomputes craftability for every recipe button and refreshes the open detail.
|
/// Recomputes each recipe button's visibility (revealed once an ingredient is discovered) and
|
||||||
/// Called by the manager whenever the local player's inventory changes.
|
/// its craftability, then refreshes the open detail. Called by the manager whenever the local
|
||||||
|
/// player's inventory changes or a new item is discovered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Refresh()
|
public void Refresh()
|
||||||
{
|
{
|
||||||
@@ -91,6 +97,9 @@ namespace Ashwild.Crafting
|
|||||||
for (int i = 0; i < recipeButtons.Count; i++)
|
for (int i = 0; i < recipeButtons.Count; i++)
|
||||||
{
|
{
|
||||||
RecipeButtonUI btn = recipeButtons[i];
|
RecipeButtonUI btn = recipeButtons[i];
|
||||||
|
bool visible = isRecipeVisibleQuery == null || isRecipeVisibleQuery(btn.Recipe);
|
||||||
|
btn.gameObject.SetActive(visible);
|
||||||
|
|
||||||
bool canCraft = canCraftQuery != null && canCraftQuery(btn.Recipe, btn.CraftCount);
|
bool canCraft = canCraftQuery != null && canCraftQuery(btn.Recipe, btn.CraftCount);
|
||||||
btn.UpdateCraftability(canCraft);
|
btn.UpdateCraftability(canCraft);
|
||||||
}
|
}
|
||||||
@@ -193,12 +202,21 @@ namespace Ashwild.Crafting
|
|||||||
GameObject slotGO = Instantiate(ingredientSlotPrefab, ingredientContainer);
|
GameObject slotGO = Instantiate(ingredientSlotPrefab, ingredientContainer);
|
||||||
IngredientSlotUI slotUI = slotGO.GetComponent<IngredientSlotUI>();
|
IngredientSlotUI slotUI = slotGO.GetComponent<IngredientSlotUI>();
|
||||||
|
|
||||||
|
bool discovered = isDiscoveredQuery == null || isDiscoveredQuery(ingredients[i].item);
|
||||||
|
if (!discovered)
|
||||||
|
{
|
||||||
|
slotUI.SetupMystery();
|
||||||
|
canCraft = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
int required = ingredients[i].quantity * count;
|
int required = ingredients[i].quantity * count;
|
||||||
int playerHas = countItemQuery != null ? countItemQuery(ingredients[i].item) : 0;
|
int playerHas = countItemQuery != null ? countItemQuery(ingredients[i].item) : 0;
|
||||||
slotUI.Setup(ingredients[i].item, required, playerHas);
|
slotUI.Setup(ingredients[i].item, required, playerHas);
|
||||||
|
|
||||||
if (playerHas < required)
|
if (playerHas < required)
|
||||||
canCraft = false;
|
canCraft = false;
|
||||||
|
}
|
||||||
|
|
||||||
ingredientInstances.Add(slotGO);
|
ingredientInstances.Add(slotGO);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,21 +5,45 @@ using Ashwild.Inventory;
|
|||||||
|
|
||||||
namespace Ashwild.Crafting
|
namespace Ashwild.Crafting
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A single ingredient cell in the crafting detail panel. Renders either a known ingredient
|
||||||
|
/// (icon + required quantity, tinted by whether the player has enough) or a mystery placeholder
|
||||||
|
/// ("?") for an ingredient the player has not discovered yet.
|
||||||
|
/// </summary>
|
||||||
public class IngredientSlotUI : MonoBehaviour
|
public class IngredientSlotUI : MonoBehaviour
|
||||||
{
|
{
|
||||||
[SerializeField] private Image iconImage;
|
[SerializeField] private Image iconImage;
|
||||||
[SerializeField] private TextMeshProUGUI quantityText;
|
[SerializeField] private TextMeshProUGUI quantityText;
|
||||||
|
|
||||||
|
[Header("Mystery (undiscovered ingredient)")]
|
||||||
|
[Tooltip("Icon shown for an ingredient the player hasn't discovered yet (a question mark).")]
|
||||||
|
[SerializeField] private Sprite mysteryIcon;
|
||||||
|
|
||||||
[Header("Colors")]
|
[Header("Colors")]
|
||||||
[SerializeField] private Color enoughColor = Color.white;
|
[SerializeField] private Color enoughColor = Color.white;
|
||||||
[SerializeField] private Color missingColor = new Color(1f, 0.3f, 0.3f, 1f);
|
[SerializeField] private Color missingColor = new Color(1f, 0.3f, 0.3f, 1f);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Renders a known ingredient: its icon and required amount, dimmed/red when the player lacks it.
|
||||||
|
/// </summary>
|
||||||
public void Setup(ItemData item, int required, int playerHas)
|
public void Setup(ItemData item, int required, int playerHas)
|
||||||
{
|
{
|
||||||
|
quantityText.gameObject.SetActive(true);
|
||||||
iconImage.sprite = item.Icon;
|
iconImage.sprite = item.Icon;
|
||||||
quantityText.text = "x" + required;
|
quantityText.text = "x" + required;
|
||||||
quantityText.color = playerHas >= required ? enoughColor : missingColor;
|
quantityText.color = playerHas >= required ? enoughColor : missingColor;
|
||||||
iconImage.color = playerHas >= required ? Color.white : new Color(1f, 1f, 1f, 0.5f);
|
iconImage.color = playerHas >= required ? Color.white : new Color(1f, 1f, 1f, 0.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Renders an undiscovered ingredient as a mystery: the "?" icon with no quantity text, so the
|
||||||
|
/// player knows a craft exists but must still find what it needs and how much.
|
||||||
|
/// </summary>
|
||||||
|
public void SetupMystery()
|
||||||
|
{
|
||||||
|
iconImage.sprite = mysteryIcon;
|
||||||
|
iconImage.color = new Color(1f, 1f, 1f, 0.5f);
|
||||||
|
quantityText.gameObject.SetActive(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ namespace Ashwild.Inventory
|
|||||||
[Header("Context Menu")]
|
[Header("Context Menu")]
|
||||||
[SerializeField] private SlotContextMenu contextMenu;
|
[SerializeField] private SlotContextMenu contextMenu;
|
||||||
|
|
||||||
|
[Header("Hover Description")]
|
||||||
|
[Tooltip("Same panel used by the inventory — assign the shared HoverDescriptionUI here too.")]
|
||||||
|
[SerializeField] private HoverDescriptionUI hoverDescription;
|
||||||
|
|
||||||
private PlayerInventory inventory;
|
private PlayerInventory inventory;
|
||||||
private Tweener scaleTween;
|
private Tweener scaleTween;
|
||||||
private Tweener alphaTween;
|
private Tweener alphaTween;
|
||||||
@@ -51,7 +55,7 @@ namespace Ashwild.Inventory
|
|||||||
{
|
{
|
||||||
// Visual setup that does not need the player.
|
// Visual setup that does not need the player.
|
||||||
for (int i = 0; i < hotbarSlot.Length; i++)
|
for (int i = 0; i < hotbarSlot.Length; i++)
|
||||||
hotbarSlot[i].Initialize(i, InventoryUI.OnSwapRequested, OnSlotClicked);
|
hotbarSlot[i].Initialize(i, InventoryUI.OnSwapRequested, OnSlotClicked, OnSlotHoverEnter, OnSlotHoverExit);
|
||||||
|
|
||||||
transform.localScale = Vector3.one * idleScale;
|
transform.localScale = Vector3.one * idleScale;
|
||||||
if (canvasGroup != null)
|
if (canvasGroup != null)
|
||||||
@@ -167,6 +171,40 @@ namespace Ashwild.Inventory
|
|||||||
hotbarSlot[index].UpdateVisual(inventory.GetSlot(index));
|
hotbarSlot[index].UpdateVisual(inventory.GetSlot(index));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shows the shared description panel for a hovered hotbar slot, but only while the inventory
|
||||||
|
/// is open — during normal play the hotbar must not pop a description. The duration bar
|
||||||
|
/// reflects the item's remaining uses/durability.
|
||||||
|
/// </summary>
|
||||||
|
private void OnSlotHoverEnter(int index)
|
||||||
|
{
|
||||||
|
if (hoverDescription == null || inventory == null || !PlayerEvents.IsInventoryOpen) return;
|
||||||
|
|
||||||
|
InventorySlot slot = inventory.GetSlot(index);
|
||||||
|
if (slot == null || slot.IsEmpty)
|
||||||
|
{
|
||||||
|
hoverDescription.Hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemData item = slot.ItemData;
|
||||||
|
bool hasDuration = item.HasUses;
|
||||||
|
float fill = hasDuration ? (float)slot.CurrentUses / item.MaxUses : 0f;
|
||||||
|
string durationText = hasDuration ? $"{slot.CurrentUses} / {item.MaxUses}" : string.Empty;
|
||||||
|
|
||||||
|
hoverDescription.Show(new ItemDescriptionView(
|
||||||
|
item.Icon, item.ItemName, item.Description, hasDuration, fill, durationText));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hides the description panel when the pointer leaves a hotbar slot.
|
||||||
|
/// </summary>
|
||||||
|
private void OnSlotHoverExit()
|
||||||
|
{
|
||||||
|
if (hoverDescription != null)
|
||||||
|
hoverDescription.Hide();
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateSelection(int selectedIndex)
|
private void UpdateSelection(int selectedIndex)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < hotbarSlot.Length; i++)
|
for (int i = 0; i < hotbarSlot.Length; i++)
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using TMPro;
|
||||||
|
using DG.Tweening;
|
||||||
|
|
||||||
|
namespace Ashwild.Inventory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The fixed description panel shown beside the inventory — a pure view. When the pointer
|
||||||
|
/// enters a filled slot the inventory manager hands it an <see cref="ItemDescriptionView"/> and
|
||||||
|
/// it fades its CanvasGroup in, drawing the icon, name, description and (for uses-tracked items)
|
||||||
|
/// the duration bar. It never reads ItemData or the inventory itself; it renders what it is given.
|
||||||
|
/// </summary>
|
||||||
|
[DisallowMultipleComponent]
|
||||||
|
public class HoverDescriptionUI : MonoBehaviour
|
||||||
|
{
|
||||||
|
#region Serialized Fields
|
||||||
|
|
||||||
|
[Header("References")]
|
||||||
|
[SerializeField] private CanvasGroup canvasGroup;
|
||||||
|
[SerializeField] private Image iconImage;
|
||||||
|
[SerializeField] private TextMeshProUGUI nameText;
|
||||||
|
[SerializeField] private TextMeshProUGUI descriptionText;
|
||||||
|
|
||||||
|
[Header("Duration Bar")]
|
||||||
|
[Tooltip("Root object of the duration/uses bar — shown only for items that track uses.")]
|
||||||
|
[SerializeField] private GameObject durationBarRoot;
|
||||||
|
[Tooltip("Filled image whose fillAmount maps to the remaining uses (Image Type = Filled).")]
|
||||||
|
[SerializeField] private Image durationBarFill;
|
||||||
|
[Tooltip("Label drawn over the bar, e.g. \"3 / 5\".")]
|
||||||
|
[SerializeField] private TextMeshProUGUI durationText;
|
||||||
|
|
||||||
|
[Header("Animation")]
|
||||||
|
[SerializeField] private float fadeDuration = 0.15f;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region State
|
||||||
|
|
||||||
|
private Tweener fadeTween;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Unity Lifecycle
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts hidden so the panel only appears once a slot is actually hovered.
|
||||||
|
/// </summary>
|
||||||
|
private void Awake() => SetHidden();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Kills the fade tween so it never targets the CanvasGroup after teardown.
|
||||||
|
/// </summary>
|
||||||
|
private void OnDestroy() => fadeTween?.Kill();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public API
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Draws the payload and fades the panel in. Called by the inventory manager on hover enter.
|
||||||
|
/// </summary>
|
||||||
|
public void Show(ItemDescriptionView view)
|
||||||
|
{
|
||||||
|
if (iconImage != null)
|
||||||
|
{
|
||||||
|
iconImage.sprite = view.Icon;
|
||||||
|
iconImage.enabled = view.Icon != null;
|
||||||
|
}
|
||||||
|
if (nameText != null)
|
||||||
|
nameText.text = view.Name;
|
||||||
|
if (descriptionText != null)
|
||||||
|
descriptionText.text = view.Description;
|
||||||
|
|
||||||
|
if (durationBarRoot != null)
|
||||||
|
durationBarRoot.SetActive(view.HasDuration);
|
||||||
|
if (view.HasDuration)
|
||||||
|
{
|
||||||
|
if (durationBarFill != null)
|
||||||
|
durationBarFill.fillAmount = view.DurationFill;
|
||||||
|
if (durationText != null)
|
||||||
|
durationText.text = view.DurationText;
|
||||||
|
}
|
||||||
|
|
||||||
|
fadeTween?.Kill();
|
||||||
|
fadeTween = canvasGroup.DOFade(1f, fadeDuration).SetUpdate(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fades the panel out via its CanvasGroup only — the GameObject stays active so it can be
|
||||||
|
/// shown again instantly. Called on hover exit / inventory close.
|
||||||
|
/// </summary>
|
||||||
|
public void Hide()
|
||||||
|
{
|
||||||
|
fadeTween?.Kill();
|
||||||
|
fadeTween = canvasGroup.DOFade(0f, fadeDuration).SetUpdate(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Internal Helpers
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Instantly parks the panel invisible via the CanvasGroup, without deactivating it.
|
||||||
|
/// </summary>
|
||||||
|
private void SetHidden() => canvasGroup.alpha = 0f;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 40050a905c9108244901dadab24daa28
|
||||||
@@ -33,6 +33,9 @@ namespace Ashwild.Inventory
|
|||||||
[Header("Context Menu")]
|
[Header("Context Menu")]
|
||||||
[SerializeField] private SlotContextMenu contextMenu;
|
[SerializeField] private SlotContextMenu contextMenu;
|
||||||
|
|
||||||
|
[Header("Hover Description")]
|
||||||
|
[SerializeField] private HoverDescriptionUI hoverDescription;
|
||||||
|
|
||||||
[Header("Categories")]
|
[Header("Categories")]
|
||||||
[SerializeField] private InventoryCategoryManager categoryManager;
|
[SerializeField] private InventoryCategoryManager categoryManager;
|
||||||
|
|
||||||
@@ -93,7 +96,7 @@ namespace Ashwild.Inventory
|
|||||||
int slotIndex = inventory.HotbarSize + i;
|
int slotIndex = inventory.HotbarSize + i;
|
||||||
GameObject slotGO = Instantiate(slotPrefab, slotContainer);
|
GameObject slotGO = Instantiate(slotPrefab, slotContainer);
|
||||||
SlotUI slotUI = slotGO.GetComponent<SlotUI>();
|
SlotUI slotUI = slotGO.GetComponent<SlotUI>();
|
||||||
slotUI.Initialize(slotIndex, OnSwapRequested, OnSlotClicked);
|
slotUI.Initialize(slotIndex, OnSwapRequested, OnSlotClicked, OnSlotHoverEnter, OnSlotHoverExit);
|
||||||
slotUIs[i] = slotUI;
|
slotUIs[i] = slotUI;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,6 +136,9 @@ namespace Ashwild.Inventory
|
|||||||
if (contextMenu != null)
|
if (contextMenu != null)
|
||||||
contextMenu.Hide();
|
contextMenu.Hide();
|
||||||
|
|
||||||
|
if (hoverDescription != null)
|
||||||
|
hoverDescription.Hide();
|
||||||
|
|
||||||
if (categoryManager != null)
|
if (categoryManager != null)
|
||||||
categoryManager.Close();
|
categoryManager.Close();
|
||||||
|
|
||||||
@@ -192,6 +198,39 @@ namespace Ashwild.Inventory
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds the description payload for the hovered slot and shows the side panel. Empty slots
|
||||||
|
/// keep the panel hidden. The duration bar reflects the item's remaining uses/durability.
|
||||||
|
/// </summary>
|
||||||
|
private void OnSlotHoverEnter(int index)
|
||||||
|
{
|
||||||
|
if (hoverDescription == null || inventory == null) return;
|
||||||
|
|
||||||
|
InventorySlot slot = inventory.GetSlot(index);
|
||||||
|
if (slot == null || slot.IsEmpty)
|
||||||
|
{
|
||||||
|
hoverDescription.Hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemData item = slot.ItemData;
|
||||||
|
bool hasDuration = item.HasUses;
|
||||||
|
float fill = hasDuration ? (float)slot.CurrentUses / item.MaxUses : 0f;
|
||||||
|
string durationText = hasDuration ? $"{slot.CurrentUses} / {item.MaxUses}" : string.Empty;
|
||||||
|
|
||||||
|
hoverDescription.Show(new ItemDescriptionView(
|
||||||
|
item.Icon, item.ItemName, item.Description, hasDuration, fill, durationText));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hides the description panel when the pointer leaves a slot.
|
||||||
|
/// </summary>
|
||||||
|
private void OnSlotHoverExit()
|
||||||
|
{
|
||||||
|
if (hoverDescription != null)
|
||||||
|
hoverDescription.Hide();
|
||||||
|
}
|
||||||
|
|
||||||
private void RefreshSlot(int index)
|
private void RefreshSlot(int index)
|
||||||
{
|
{
|
||||||
if (slotUIs == null) return;
|
if (slotUIs == null) return;
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Ashwild.Inventory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The view payload for the hover description panel: exactly what the panel renders (icon,
|
||||||
|
/// name, description, and an optional duration/uses bar), nothing more. The inventory manager
|
||||||
|
/// builds one of these from a hovered slot and pushes it into the view, so the panel never
|
||||||
|
/// touches ItemData or the inventory model — it just draws what it is handed.
|
||||||
|
/// </summary>
|
||||||
|
public readonly struct ItemDescriptionView
|
||||||
|
{
|
||||||
|
public readonly Sprite Icon;
|
||||||
|
public readonly string Name;
|
||||||
|
public readonly string Description;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the hovered item tracks uses/durability and should show its duration bar.
|
||||||
|
/// </summary>
|
||||||
|
public readonly bool HasDuration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remaining uses as a 0..1 fill for the bar's Filled image.
|
||||||
|
/// </summary>
|
||||||
|
public readonly float DurationFill;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The remaining/max label drawn over the bar, e.g. "3 / 5".
|
||||||
|
/// </summary>
|
||||||
|
public readonly string DurationText;
|
||||||
|
|
||||||
|
public ItemDescriptionView(Sprite icon, string name, string description,
|
||||||
|
bool hasDuration, float durationFill, string durationText)
|
||||||
|
{
|
||||||
|
Icon = icon;
|
||||||
|
Name = name;
|
||||||
|
Description = description;
|
||||||
|
HasDuration = hasDuration;
|
||||||
|
DurationFill = durationFill;
|
||||||
|
DurationText = durationText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b901ece22e48ee344837ad720d6f2e18
|
||||||
@@ -6,7 +6,7 @@ using System;
|
|||||||
|
|
||||||
namespace Ashwild.Inventory
|
namespace Ashwild.Inventory
|
||||||
{
|
{
|
||||||
public class SlotUI : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler, IDropHandler, IPointerClickHandler
|
public class SlotUI : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler, IDropHandler, IPointerClickHandler, IPointerEnterHandler, IPointerExitHandler
|
||||||
{
|
{
|
||||||
[Header("References")]
|
[Header("References")]
|
||||||
[SerializeField] private Image iconImage;
|
[SerializeField] private Image iconImage;
|
||||||
@@ -26,6 +26,8 @@ namespace Ashwild.Inventory
|
|||||||
private int slotIndex;
|
private int slotIndex;
|
||||||
private Action<int, int> onSwapRequested;
|
private Action<int, int> onSwapRequested;
|
||||||
private Action<int, bool> onClicked;
|
private Action<int, bool> onClicked;
|
||||||
|
private Action<int> onHoverEnter;
|
||||||
|
private Action onHoverExit;
|
||||||
|
|
||||||
public int SlotIndex => slotIndex;
|
public int SlotIndex => slotIndex;
|
||||||
|
|
||||||
@@ -43,11 +45,14 @@ namespace Ashwild.Inventory
|
|||||||
ghostObject.SetActive(false);
|
ghostObject.SetActive(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Initialize(int index, Action<int, int> swapCallback, Action<int, bool> clickCallback)
|
public void Initialize(int index, Action<int, int> swapCallback, Action<int, bool> clickCallback,
|
||||||
|
Action<int> hoverEnterCallback = null, Action hoverExitCallback = null)
|
||||||
{
|
{
|
||||||
slotIndex = index;
|
slotIndex = index;
|
||||||
onSwapRequested = swapCallback;
|
onSwapRequested = swapCallback;
|
||||||
onClicked = clickCallback;
|
onClicked = clickCallback;
|
||||||
|
onHoverEnter = hoverEnterCallback;
|
||||||
|
onHoverExit = hoverExitCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -170,5 +175,23 @@ namespace Ashwild.Inventory
|
|||||||
if (eventData.button == PointerEventData.InputButton.Right)
|
if (eventData.button == PointerEventData.InputButton.Right)
|
||||||
onClicked?.Invoke(slotIndex, true);
|
onClicked?.Invoke(slotIndex, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reports the hovered slot index so the manager can show its description panel. Suppressed
|
||||||
|
/// while a drag is in progress, where the panel would only get in the way.
|
||||||
|
/// </summary>
|
||||||
|
public void OnPointerEnter(PointerEventData eventData)
|
||||||
|
{
|
||||||
|
if (draggedSlot != null) return;
|
||||||
|
onHoverEnter?.Invoke(slotIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reports that the pointer left the slot so the manager can hide the description panel.
|
||||||
|
/// </summary>
|
||||||
|
public void OnPointerExit(PointerEventData eventData)
|
||||||
|
{
|
||||||
|
onHoverExit?.Invoke();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,8 @@ namespace Ashwild.Player
|
|||||||
public static event Action<ItemData, int> ItemDropped;
|
public static event Action<ItemData, int> ItemDropped;
|
||||||
public static event Action<ItemData> ItemConsumed;
|
public static event Action<ItemData> ItemConsumed;
|
||||||
public static event Action<ItemData, int> ItemPickedUp;
|
public static event Action<ItemData, int> ItemPickedUp;
|
||||||
|
public static event Action<ItemData> ItemDiscovered; // an item entered the inventory for the first time (unlocks recipes)
|
||||||
|
public static event Action<Sprite, string> RecipeDiscovered; // a recipe was just revealed (icon, name) — for the "NEW" craft notification
|
||||||
public static event Action<ItemData> HeldItemChanged;
|
public static event Action<ItemData> HeldItemChanged;
|
||||||
public static event Action<bool> InventoryOpenChanged;
|
public static event Action<bool> InventoryOpenChanged;
|
||||||
public static event Action<bool> ChestOpenChanged;
|
public static event Action<bool> ChestOpenChanged;
|
||||||
@@ -248,6 +250,8 @@ namespace Ashwild.Player
|
|||||||
public static void RaiseItemDropped(ItemData d, int q) { Log(nameof(ItemDropped)); ItemDropped?.Invoke(d, q); }
|
public static void RaiseItemDropped(ItemData d, int q) { Log(nameof(ItemDropped)); ItemDropped?.Invoke(d, q); }
|
||||||
public static void RaiseItemConsumed(ItemData d) { Log(nameof(ItemConsumed)); ItemConsumed?.Invoke(d); }
|
public static void RaiseItemConsumed(ItemData d) { Log(nameof(ItemConsumed)); ItemConsumed?.Invoke(d); }
|
||||||
public static void RaiseItemPickedUp(ItemData d, int q) { Log(nameof(ItemPickedUp)); ItemPickedUp?.Invoke(d, q); }
|
public static void RaiseItemPickedUp(ItemData d, int q) { Log(nameof(ItemPickedUp)); ItemPickedUp?.Invoke(d, q); }
|
||||||
|
public static void RaiseItemDiscovered(ItemData d) { Log(nameof(ItemDiscovered)); ItemDiscovered?.Invoke(d); }
|
||||||
|
public static void RaiseRecipeDiscovered(Sprite icon, string recipeName) { Log(nameof(RecipeDiscovered)); RecipeDiscovered?.Invoke(icon, recipeName); }
|
||||||
public static void RaiseHeldItemChanged(ItemData d) { Log(nameof(HeldItemChanged)); HeldItemChanged?.Invoke(d); }
|
public static void RaiseHeldItemChanged(ItemData d) { Log(nameof(HeldItemChanged)); HeldItemChanged?.Invoke(d); }
|
||||||
public static void RaiseInventoryOpenChanged(bool b)
|
public static void RaiseInventoryOpenChanged(bool b)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ namespace Ashwild.UI
|
|||||||
private int totalQuantity;
|
private int totalQuantity;
|
||||||
private float hideTimer;
|
private float hideTimer;
|
||||||
private bool isHiding;
|
private bool isHiding;
|
||||||
|
private bool isDiscovery;
|
||||||
|
|
||||||
private Tweener fadeTween;
|
private Tweener fadeTween;
|
||||||
private Tweener slideTween;
|
private Tweener slideTween;
|
||||||
@@ -30,6 +31,9 @@ namespace Ashwild.UI
|
|||||||
// A notification is either a gain (+) or a loss (-); merging only happens within the same direction.
|
// A notification is either a gain (+) or a loss (-); merging only happens within the same direction.
|
||||||
public bool IsGain => totalQuantity >= 0;
|
public bool IsGain => totalQuantity >= 0;
|
||||||
|
|
||||||
|
// A discovery notification (icon + name + "NEW" badge) never merges with a gain/loss one.
|
||||||
|
public bool IsDiscovery => isDiscovery;
|
||||||
|
|
||||||
public void Initialize(ItemData item, int signedQuantity, Color numberColor, float displayDuration, float slideInDuration, Ease slideInEase)
|
public void Initialize(ItemData item, int signedQuantity, Color numberColor, float displayDuration, float slideInDuration, Ease slideInEase)
|
||||||
{
|
{
|
||||||
itemData = item;
|
itemData = item;
|
||||||
@@ -49,6 +53,34 @@ namespace Ashwild.UI
|
|||||||
fadeTween = canvasGroup.DOFade(1f, slideInDuration).SetEase(Ease.OutQuad);
|
fadeTween = canvasGroup.DOFade(1f, slideInDuration).SetEase(Ease.OutQuad);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets up a discovery notification: an icon and name (of the unlocked recipe) with a fixed
|
||||||
|
/// badge (e.g. "NEW") in the number slot, tinted with the caller-chosen colour. It carries no
|
||||||
|
/// quantity and never merges, so it reads as a one-time "recipe unlocked" cue.
|
||||||
|
/// </summary>
|
||||||
|
public void InitializeDiscovery(Sprite icon, string displayName, string badgeText, Color badgeColor, float displayDuration, float slideInDuration)
|
||||||
|
{
|
||||||
|
itemData = null;
|
||||||
|
isDiscovery = true;
|
||||||
|
hideTimer = displayDuration;
|
||||||
|
isHiding = false;
|
||||||
|
|
||||||
|
if (iconImage != null)
|
||||||
|
iconImage.sprite = icon;
|
||||||
|
|
||||||
|
if (labelText != null)
|
||||||
|
labelText.text = displayName;
|
||||||
|
|
||||||
|
if (numberText != null)
|
||||||
|
{
|
||||||
|
numberText.text = badgeText;
|
||||||
|
numberText.color = badgeColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvasGroup.alpha = 0f;
|
||||||
|
fadeTween = canvasGroup.DOFade(1f, slideInDuration).SetEase(Ease.OutQuad);
|
||||||
|
}
|
||||||
|
|
||||||
public void AddQuantity(int signedAmount, float displayDuration)
|
public void AddQuantity(int signedAmount, float displayDuration)
|
||||||
{
|
{
|
||||||
totalQuantity += signedAmount;
|
totalQuantity += signedAmount;
|
||||||
|
|||||||
@@ -30,6 +30,11 @@ namespace Ashwild.UI
|
|||||||
[SerializeField] private Color gainColor = new Color(0.45f, 1f, 0.45f); // picked up / added
|
[SerializeField] private Color gainColor = new Color(0.45f, 1f, 0.45f); // picked up / added
|
||||||
[SerializeField] private Color lossColor = new Color(1f, 0.45f, 0.45f); // dropped / consumed
|
[SerializeField] private Color lossColor = new Color(1f, 0.45f, 0.45f); // dropped / consumed
|
||||||
|
|
||||||
|
[Header("Discovery")]
|
||||||
|
[Tooltip("Badge shown in the number slot when a new item/recipe is discovered.")]
|
||||||
|
[SerializeField] private string discoveryLabel = "NEW";
|
||||||
|
[SerializeField] private Color discoveryColor = new Color(1f, 0.85f, 0.3f);
|
||||||
|
|
||||||
private readonly List<NotificationItemUI> activeNotifications = new List<NotificationItemUI>();
|
private readonly List<NotificationItemUI> activeNotifications = new List<NotificationItemUI>();
|
||||||
|
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
@@ -37,6 +42,7 @@ namespace Ashwild.UI
|
|||||||
PlayerEvents.ItemAdded += OnItemAdded;
|
PlayerEvents.ItemAdded += OnItemAdded;
|
||||||
PlayerEvents.ItemDropped += OnItemDropped;
|
PlayerEvents.ItemDropped += OnItemDropped;
|
||||||
PlayerEvents.ItemConsumed += OnItemConsumed;
|
PlayerEvents.ItemConsumed += OnItemConsumed;
|
||||||
|
PlayerEvents.RecipeDiscovered += OnRecipeDiscovered;
|
||||||
PlayerEvents.FoodPlacedToCook += OnItemSpent;
|
PlayerEvents.FoodPlacedToCook += OnItemSpent;
|
||||||
PlayerEvents.FuelAdded += OnItemSpent;
|
PlayerEvents.FuelAdded += OnItemSpent;
|
||||||
}
|
}
|
||||||
@@ -46,6 +52,7 @@ namespace Ashwild.UI
|
|||||||
PlayerEvents.ItemAdded -= OnItemAdded;
|
PlayerEvents.ItemAdded -= OnItemAdded;
|
||||||
PlayerEvents.ItemDropped -= OnItemDropped;
|
PlayerEvents.ItemDropped -= OnItemDropped;
|
||||||
PlayerEvents.ItemConsumed -= OnItemConsumed;
|
PlayerEvents.ItemConsumed -= OnItemConsumed;
|
||||||
|
PlayerEvents.RecipeDiscovered -= OnRecipeDiscovered;
|
||||||
PlayerEvents.FoodPlacedToCook -= OnItemSpent;
|
PlayerEvents.FoodPlacedToCook -= OnItemSpent;
|
||||||
PlayerEvents.FuelAdded -= OnItemSpent;
|
PlayerEvents.FuelAdded -= OnItemSpent;
|
||||||
}
|
}
|
||||||
@@ -53,6 +60,7 @@ namespace Ashwild.UI
|
|||||||
private void OnItemAdded(ItemData item, int quantity) => Push(item, quantity);
|
private void OnItemAdded(ItemData item, int quantity) => Push(item, quantity);
|
||||||
private void OnItemDropped(ItemData item, int quantity) => Push(item, -quantity);
|
private void OnItemDropped(ItemData item, int quantity) => Push(item, -quantity);
|
||||||
private void OnItemConsumed(ItemData item) => Push(item, -1);
|
private void OnItemConsumed(ItemData item) => Push(item, -1);
|
||||||
|
private void OnRecipeDiscovered(Sprite icon, string recipeName) => PushDiscovery(icon, recipeName);
|
||||||
|
|
||||||
// Food placed on a cooking station or fuel fed to it both leave the inventory by one.
|
// Food placed on a cooking station or fuel fed to it both leave the inventory by one.
|
||||||
private void OnItemSpent(ItemData item) => Push(item, -1);
|
private void OnItemSpent(ItemData item) => Push(item, -1);
|
||||||
@@ -69,7 +77,8 @@ namespace Ashwild.UI
|
|||||||
// Merge only with a notification for the same item AND same direction
|
// Merge only with a notification for the same item AND same direction
|
||||||
for (int i = 0; i < activeNotifications.Count; i++)
|
for (int i = 0; i < activeNotifications.Count; i++)
|
||||||
{
|
{
|
||||||
if (activeNotifications[i] != null && activeNotifications[i].ItemData == item
|
if (activeNotifications[i] != null && !activeNotifications[i].IsDiscovery
|
||||||
|
&& activeNotifications[i].ItemData == item
|
||||||
&& activeNotifications[i].IsGain == gain)
|
&& activeNotifications[i].IsGain == gain)
|
||||||
{
|
{
|
||||||
activeNotifications[i].AddQuantity(signedQuantity, displayDuration);
|
activeNotifications[i].AddQuantity(signedQuantity, displayDuration);
|
||||||
@@ -94,6 +103,29 @@ namespace Ashwild.UI
|
|||||||
activeNotifications.Add(notification);
|
activeNotifications.Add(notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pushes a one-time "NEW" notification for a newly unlocked recipe: its result icon and name
|
||||||
|
/// with the configured badge in the discovery colour. Never merges with anything.
|
||||||
|
/// </summary>
|
||||||
|
private void PushDiscovery(Sprite icon, string recipeName)
|
||||||
|
{
|
||||||
|
activeNotifications.RemoveAll(n => n == null);
|
||||||
|
|
||||||
|
GameObject go = Instantiate(notificationPrefab, container);
|
||||||
|
NotificationItemUI notification = go.GetComponent<NotificationItemUI>();
|
||||||
|
|
||||||
|
float targetY = startOffsetY + activeNotifications.Count * (notificationHeight + spacing);
|
||||||
|
|
||||||
|
RectTransform rt = notification.RectTransform;
|
||||||
|
rt.anchoredPosition = new Vector2(0f, targetY - 30f);
|
||||||
|
|
||||||
|
notification.InitializeDiscovery(icon, recipeName, discoveryLabel, discoveryColor, displayDuration, slideInDuration);
|
||||||
|
|
||||||
|
rt.DOAnchorPosY(targetY, slideInDuration).SetEase(slideInEase);
|
||||||
|
|
||||||
|
activeNotifications.Add(notification);
|
||||||
|
}
|
||||||
|
|
||||||
private void LateUpdate()
|
private void LateUpdate()
|
||||||
{
|
{
|
||||||
// Clean up destroyed notifications and reposition remaining ones
|
// Clean up destroyed notifications and reposition remaining ones
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 497 KiB After Width: | Height: | Size: 497 KiB |
|
Before Width: | Height: | Size: 180 KiB After Width: | Height: | Size: 180 KiB |
|
Before Width: | Height: | Size: 214 KiB After Width: | Height: | Size: 214 KiB |
|
Before Width: | Height: | Size: 301 KiB After Width: | Height: | Size: 301 KiB |
|
Before Width: | Height: | Size: 166 KiB After Width: | Height: | Size: 166 KiB |
|
Before Width: | Height: | Size: 236 KiB After Width: | Height: | Size: 236 KiB |
|
Before Width: | Height: | Size: 273 KiB After Width: | Height: | Size: 273 KiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 388 KiB After Width: | Height: | Size: 388 KiB |
|
Before Width: | Height: | Size: 300 KiB After Width: | Height: | Size: 300 KiB |
|
Before Width: | Height: | Size: 301 KiB After Width: | Height: | Size: 301 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |