Apply script upon item consumption

SOSolacex

Member
Hey,
What I am trying to do is creating a potion that executes a specific script once upon use.
For example: Use potion -> add effect to camera.
However, I have absolutely no clue how to set this up. I've been messing around for hours with things in the UI Designer.
One of these is going to UI designer -> Item shape grid -> Item actions then set up a UseItemActionSetAttribute... but that's as far as my understanding of the documentation goes.

I tried adding a script as attribute, then setting the name of that attribute the same as the attribute name I put in the use item action.. But that didn't help.
The "use" button is generally greyed out.

I am also occasionally getting this error, however I have no clue where I am supposed to be missing a reference. I suspect that it has to do with the reason why I can't get the item to work, but I couldnt find anything missing in either the UI department on the player.

Note, I am using Opsive's first person controller and dialogue system integrations in case that changes things.

Code:
NullReferenceException: Object reference not set to an instance of an object
Opsive.UltimateInventorySystem.Core.InventoryCollections.Inventory.GetItemCollection (Opsive.UltimateInventorySystem.Core.InventoryCollections.ItemCollectionID collectionID) (at Assets/Opsive/UltimateInventorySystem/Scripts/Core/InventoryCollections/Inventory.cs:402)
Opsive.UltimateInventorySystem.UI.Panels.Hotbar.ItemSlotCollectionView.OnInventoryChanged (Opsive.UltimateInventorySystem.Core.InventoryCollections.Inventory previousInventory, Opsive.UltimateInventorySystem.Core.InventoryCollections.Inventory newInventory) (at Assets/Opsive/UltimateInventorySystem/Scripts/UI/Panels/Hotbar/ItemSlotCollectionView.cs:141)
Opsive.UltimateInventorySystem.UI.Item.ItemViewSlotsContainerBase.SetInventory (Opsive.UltimateInventorySystem.Core.InventoryCollections.Inventory inventory, System.Boolean handleChange) (at Assets/Opsive/UltimateInventorySystem/Scripts/UI/Item/ItemViewSlotsContainerBase.cs:304)
Opsive.UltimateInventorySystem.UI.Item.ItemViewSlotsContainerBase.SetInventory (Opsive.UltimateInventorySystem.Core.InventoryCollections.Inventory inventory) (at Assets/Opsive/UltimateInventorySystem/Scripts/UI/Item/ItemViewSlotsContainerBase.cs:287)
Opsive.UltimateInventorySystem.UI.Item.ItemViewSlotsContainerBase.Initialize (System.Boolean force) (at Assets/Opsive/UltimateInventorySystem/Scripts/UI/Item/ItemViewSlotsContainerBase.cs:270)
Opsive.UltimateInventorySystem.UI.Item.ItemViewSlotsContainer.Initialize (System.Boolean force) (at Assets/Opsive/UltimateInventorySystem/Scripts/UI/Item/ItemViewSlotsContainer.cs:43)
Opsive.UltimateInventorySystem.Editor.Managers.UIDesigner.EquipmentDesignerEditorOptions.Refresh () (at Assets/Opsive/UltimateInventorySystem/Editor/Managers/UIDesigner/EquipmentDesigner.cs:386)
Opsive.UltimateInventorySystem.Editor.Managers.UIDesigner.EquipmentDesignerEditor.NewValidTargetAssigned () (at Assets/Opsive/UltimateInventorySystem/Editor/Managers/UIDesigner/EquipmentDesigner.cs:221)
Opsive.UltimateInventorySystem.Editor.Managers.UIDesigner.UIDesignerEditor`1[T].TargetObjectChanged () (at Assets/Opsive/UltimateInventorySystem/Editor/Managers/UIDesigner/UIDesignerManager.cs:656)
Opsive.UltimateInventorySystem.Editor.Managers.UIDesigner.UIDesignerEditor`1[T].Refresh () (at Assets/Opsive/UltimateInventorySystem/Editor/Managers/UIDesigner/UIDesignerManager.cs:613)
Opsive.UltimateInventorySystem.Editor.Managers.UIDesigner.UIDesignerCreateEditTabContent`3[To,Tc,Te].Refresh () (at Assets/Opsive/UltimateInventorySystem/Editor/Managers/UIDesigner/UIDesignerManager.cs:469)
Opsive.UltimateInventorySystem.Editor.Managers.UIDesigner.UIDesignerManager.Refresh () (at Assets/Opsive/UltimateInventorySystem/Editor/Managers/UIDesigner/UIDesignerManager.cs:184)
Opsive.UltimateInventorySystem.Editor.Managers.UIDesigner.UIDesignerManager.OnFocus () (at Assets/Opsive/UltimateInventorySystem/Editor/Managers/UIDesigner/UIDesignerManager.cs:159)
Opsive.UltimateInventorySystem.Editor.Managers.MainManagerWindow.OnFocus () (at Assets/Opsive/UltimateInventorySystem/Editor/Managers/MainManagerWindow.cs:658)
UnityEditor.HostView.RegisterSelectedPane (System.Boolean sendEvents) (at <8f44d91b549e47c9883e180579f26ef6>:0)
UnityEditor.HostView.SetActualViewInternal (UnityEditor.EditorWindow value, System.Boolean sendEvents) (at <8f44d91b549e47c9883e180579f26ef6>:0)
UnityEditor.DockArea.SetSelectedPrivate (System.Int32 value, System.Boolean sendEvents) (at <8f44d91b549e47c9883e180579f26ef6>:0)
UnityEditor.DockArea.set_selected (System.Int32 value) (at <8f44d91b549e47c9883e180579f26ef6>:0)
UnityEditor.DockArea.DragTab (UnityEngine.Rect tabAreaRect, System.Single scrollOffset, UnityEngine.GUIStyle tabStyle, UnityEngine.GUIStyle firstTabStyle) (at <8f44d91b549e47c9883e180579f26ef6>:0)
UnityEditor.DockArea.DrawTabs (UnityEngine.Rect tabAreaRect) (at <8f44d91b549e47c9883e180579f26ef6>:0)
UnityEditor.DockArea.OldOnGUI () (at <8f44d91b549e47c9883e180579f26ef6>:0)
UnityEngine.UIElements.IMGUIContainer.DoOnGUI (UnityEngine.Event evt, UnityEngine.Matrix4x4 parentTransform, UnityEngine.Rect clippingRect, System.Boolean isComputingLayout, UnityEngine.Rect layoutSize, System.Action onGUIHandler, System.Boolean canAffectFocus) (at <93ba6ea0bbfd4de09b538244d248af02>:0)
UnityEngine.UIElements.IMGUIContainer.HandleIMGUIEvent (UnityEngine.Event e, UnityEngine.Matrix4x4 worldTransform, UnityEngine.Rect clippingRect, System.Action onGUIHandler, System.Boolean canAffectFocus) (at <93ba6ea0bbfd4de09b538244d248af02>:0)
UnityEngine.UIElements.IMGUIContainer.HandleIMGUIEvent (UnityEngine.Event e, System.Action onGUIHandler, System.Boolean canAffectFocus) (at <93ba6ea0bbfd4de09b538244d248af02>:0)
UnityEngine.UIElements.IMGUIContainer.HandleIMGUIEvent (UnityEngine.Event e, System.Boolean canAffectFocus) (at <93ba6ea0bbfd4de09b538244d248af02>:0)
UnityEngine.UIElements.IMGUIContainer.SendEventToIMGUIRaw (UnityEngine.UIElements.EventBase evt, System.Boolean canAffectFocus, System.Boolean verifyBounds) (at <93ba6ea0bbfd4de09b538244d248af02>:0)
UnityEngine.UIElements.IMGUIContainer.SendEventToIMGUI (UnityEngine.UIElements.EventBase evt, System.Boolean canAffectFocus, System.Boolean verifyBounds) (at <93ba6ea0bbfd4de09b538244d248af02>:0)
UnityEngine.UIElements.IMGUIContainer.HandleEvent (UnityEngine.UIElements.EventBase evt) (at <93ba6ea0bbfd4de09b538244d248af02>:0)
UnityEngine.UIElements.EventDispatchUtilities.PropagateEvent (UnityEngine.UIElements.EventBase evt) (at <93ba6ea0bbfd4de09b538244d248af02>:0)
UnityEngine.UIElements.MouseEventDispatchingStrategy.SendEventToRegularTarget (UnityEngine.UIElements.EventBase evt, UnityEngine.UIElements.BaseVisualElementPanel panel) (at <93ba6ea0bbfd4de09b538244d248af02>:0)
UnityEngine.UIElements.MouseEventDispatchingStrategy.SendEventToTarget (UnityEngine.UIElements.EventBase evt, UnityEngine.UIElements.BaseVisualElementPanel panel) (at <93ba6ea0bbfd4de09b538244d248af02>:0)
UnityEngine.UIElements.MouseEventDispatchingStrategy.DispatchEvent (UnityEngine.UIElements.EventBase evt, UnityEngine.UIElements.IPanel iPanel) (at <93ba6ea0bbfd4de09b538244d248af02>:0)
UnityEngine.UIElements.EventDispatcher.ApplyDispatchingStrategies (UnityEngine.UIElements.EventBase evt, UnityEngine.UIElements.IPanel panel, System.Boolean imguiEventIsInitiallyUsed) (at <93ba6ea0bbfd4de09b538244d248af02>:0)
UnityEngine.UIElements.EventDispatcher.ProcessEvent (UnityEngine.UIElements.EventBase evt, UnityEngine.UIElements.IPanel panel) (at <93ba6ea0bbfd4de09b538244d248af02>:0)
UnityEngine.UIElements.EventDispatcher.ProcessEventQueue () (at <93ba6ea0bbfd4de09b538244d248af02>:0)
UnityEngine.UIElements.EventDispatcher.OpenGate () (at <93ba6ea0bbfd4de09b538244d248af02>:0)
UnityEngine.UIElements.EventDispatcherGate.Dispose () (at <93ba6ea0bbfd4de09b538244d248af02>:0)
UnityEngine.UIElements.EventDispatcher.ProcessEvent (UnityEngine.UIElements.EventBase evt, UnityEngine.UIElements.IPanel panel) (at <93ba6ea0bbfd4de09b538244d248af02>:0)
UnityEngine.UIElements.EventDispatcher.Dispatch (UnityEngine.UIElements.EventBase evt, UnityEngine.UIElements.IPanel panel, UnityEngine.UIElements.DispatchMode dispatchMode) (at <93ba6ea0bbfd4de09b538244d248af02>:0)
UnityEngine.UIElements.BaseVisualElementPanel.SendEvent (UnityEngine.UIElements.EventBase e, UnityEngine.UIElements.DispatchMode dispatchMode) (at <93ba6ea0bbfd4de09b538244d248af02>:0)
UnityEngine.UIElements.UIElementsUtility.DoDispatch (UnityEngine.UIElements.BaseVisualElementPanel panel) (at <93ba6ea0bbfd4de09b538244d248af02>:0)
UnityEngine.UIElements.UIElementsUtility.UnityEngine.UIElements.IUIElementsUtility.ProcessEvent (System.Int32 instanceID, System.IntPtr nativeEventPtr, System.Boolean& eventHandled) (at <93ba6ea0bbfd4de09b538244d248af02>:0)
UnityEngine.UIElements.UIEventRegistration.ProcessEvent (System.Int32 instanceID, System.IntPtr nativeEventPtr) (at <93ba6ea0bbfd4de09b538244d248af02>:0)
UnityEngine.UIElements.UIEventRegistration+<>c.<.cctor>b__1_2 (System.Int32 i, System.IntPtr ptr) (at <93ba6ea0bbfd4de09b538244d248af02>:0)
UnityEngine.GUIUtility.ProcessEvent (System.Int32 instanceID, System.IntPtr nativeEventPtr, System.Boolean& result) (at <6cf4b8f7a4344e9e9df60cc4c1690d8c>:0)
 
The error seems weird, it shouldn't be happening. Could you try updating to v1.1.6 and let me know if it continues to happens.

As for the potion, there are many ways to implement functions to our items. If you haven't done so yet I would highly recommend you watch this video:
And read those pages:

From my understanding you want a "Use" Item Action that does different things depending on the item being used. For that my approach would be to create a scriptable Object and set it on the item as an attribute. Then I can have an Item Action that uses a custom method on that scriptable object that uses the item however I want. For that you could create your own item Action and scriptable object or use the "UseItemActionSetAttribute" item action.
Option 1 of the Item Skills page should be a good start, by using an "UseItemActionSetAttribute" item Action with an "ItemActionSet" attribute. Then you can create custom Item Actions to do whatever you want.

To create a custom Item Action, simply inherit the ItemAction class (the one from the inventory system not the character controller).

Make sure to fix any errors, before trying to see if you item actions work though.

I hope that helps
 
Hey, thanks for the reply!
I've updated and so far haven't ran into the error.
In regards to the documentation and your explanation, I understand it as far as adding a custom item action goes.
For example, I can make an item have the "heal" option in the item action panel and that custom item action works...
But as far as making the "use" item action different depending on the item, I am very confused.

I've created a category item set (Scriptable Object) that is connected with the category "CustomConsumable", one of the Item Actions this set has is "UseItemActionSetAttribute" with the name "Use".

Then in the Item Categories, I've added an Item Definition Attribute with the Item set assigned that has the ability I want..
The category item set's "UseItemActionSetAttribute" Attribute name has the same name as name the item set in Item Definition Attributes has..
But there are two problems with this:
1. "Use" from the CustomConsumable set is greyed out; it does nothing.
2. The "UsePotion" from the assigned set in Item Definition Attributes DOES work... but it shows up in the panel, while I only want the previous "use" to show up.

Of course I could "fix" my issue by creating a new item set for every single item with a custom effect and assigning said set in the Item Definition Attribute list.. but that seems kind of inefficient?

My apologies by the way.
I've watched the video and read the documentation multiple times and I am sure that I should've understood after your explanation too, however I am not a native English speaker and thus have issues entirely understanding explanations like these.

Honestly the "UseItemActionSetAttribute" item action is confusing to me by default; I tried making it work multiple times, with the result of the button being greyed out while the item action that I tried to reference it to worked fine.
 
If the button is greyed out that means the condition to use the item action returned false.

The condition checks that the item has the attribute, that is is the correct type and that the ItemActionSet value of the attribute is not null.

So one of these things must be off in your setup.

Here is an example of a Potion with an attriubute ItemActionSet that points to a Heal Item Action:
1618242312848.png


Your Use Item Action will get that attribute by name and invoke it
1618243491520.png

So Heal Item Action is specific to the potion item. It is used by the "Use" Item action.

The Consumable Use Item Action is the item action used for all consumables. This is the Item Action Set you'll use in the Category Item Action Set that is attached to your UI.

Hopefully it makes more sense with the images.

EDIT I tested it myself and turns out there was a bug in the code... I apologize for this you were probably doing everything correctly. Please replace this function in the UseItemActionSetAttribute script.

Code:
/// <summary>
/// Check if the action can be invoked.
/// </summary>
/// <param name="itemInfo">The item.</param>
/// <param name="itemUser">The item user (can be null).</param>
/// <returns>True if the action can be invoked.</returns>
protected override bool CanInvokeInternal(ItemInfo itemInfo, ItemUser itemUser)
{
    if (itemInfo.Item == null) { return false; }
   
    var attribute = itemInfo.Item.GetAttribute<Attribute<ItemActionSet>>(m_AttributeName);
    if (attribute == null) { return false; }
   
    var actionSet = attribute.GetValue();
    if (actionSet == null) { return false; }
   
    actionSet.ItemActionCollection.Initialize(false);
   
    if (m_ActionIndex < 0 || m_ActionIndex >= actionSet.ItemActionCollection.Count) { return false; }
   
    return true;
}

I'm really sorry about that
 
If the button is greyed out that means the condition to use the item action returned false.

The condition checks that the item has the attribute, that is is the correct type and that the ItemActionSet value of the attribute is not null.

So one of these things must be off in your setup.

Here is an example of a Potion with an attriubute ItemActionSet that points to a Heal Item Action:
View attachment 5540


Your Use Item Action will get that attribute by name and invoke it
View attachment 5543

So Heal Item Action is specific to the potion item. It is used by the "Use" Item action.

The Consumable Use Item Action is the item action used for all consumables. This is the Item Action Set you'll use in the Category Item Action Set that is attached to your UI.

Hopefully it makes more sense with the images.

EDIT I tested it myself and turns out there was a bug in the code... I apologize for this you were probably doing everything correctly. Please replace this function in the UseItemActionSetAttribute script.

Code:
/// <summary>
/// Check if the action can be invoked.
/// </summary>
/// <param name="itemInfo">The item.</param>
/// <param name="itemUser">The item user (can be null).</param>
/// <returns>True if the action can be invoked.</returns>
protected override bool CanInvokeInternal(ItemInfo itemInfo, ItemUser itemUser)
{
    if (itemInfo.Item == null) { return false; }

    var attribute = itemInfo.Item.GetAttribute<Attribute<ItemActionSet>>(m_AttributeName);
    if (attribute == null) { return false; }

    var actionSet = attribute.GetValue();
    if (actionSet == null) { return false; }

    actionSet.ItemActionCollection.Initialize(false);

    if (m_ActionIndex < 0 || m_ActionIndex >= actionSet.ItemActionCollection.Count) { return false; }

    return true;
}

I'm really sorry about that
After replacing the code it works perfectly!
And no worries, even with my skill level at scripting and such, I am proud I keep finding bugs rather than being the bug ?

However, I still have a related question.
Since the use is now tied to the item set, I can't change values etc. based on the item that uses the item set.. (For example: Potion X = Applies camera effect for 5 seconds, Potion Y applies effect for 10 seconds = 2 different item sets?)
So does that just mean I should create a different item set for every item that uses custom effects like these?
 
No, You still have reference to the item and the item user in the Item Action.

Normally you should be able to set those values on the item itself as attribute values.

For example the Consumable Item Category could define an Item Definition attribute for "screen shake", that could be an int, or anything really.

Then in your custom ItemAction you can get the item from the ItemInfo parameter. From that Item you may get the "screen shake" attribute value and use that to shake the screen.

Refer to the other Item Actions to see how this is done.

Here is quick untested code to give you an idea:
Code:
/// <summary>
/// Invoke the action.
/// </summary>
/// <param name="itemInfo">The item.</param>
/// <param name="itemUser">The item user (can be null).</param>
protected override void InvokeActionInternal(ItemInfo itemInfo, ItemUser itemUser)
{
    //Use one or multiple items at a time?
    if (m_UseOne) { itemInfo = (1, itemInfo); }
    
    var attribute = itemInfo.Item.GetAttribute<Attribute<int>>("screen shake");
    var screenShakeFactor = attribute.GetValue();
    
    //Use something to shake the screen using the screenShakeFactor value (cinemachine?)
    
    //Remove the item once it is used?
    if (m_RemoveOnUse) {
        itemInfo.ItemCollection?.RemoveItem(itemInfo);
    }
}
 
Top