- Home /
How to enable UI collision while timeScale is 0?
I'm currently working on an inventory menu for my game where you use the directional keys to move a cursor and select objects (like in pretty much all of the Resident Evil games, for example). In particular I'm making a grid/"tetris" inventory system. In order to determine whether a given grid slot is available (and therefore selectable), I basically gave both my slot objects and my item objects rigidbodies and box colliders, and check for OnTriggerStay. For hours I couldn't figure out why it wasn't working until I realized that I pause the game while the inventory menu is open by setting Time.timeScale to 0, and collisions don't calculate while the time is 0.
I've done some internet research and I keep seeing people sing the praises of using timeScale to pause your game, only to ignore people pointing out the physics problem, which in my view is a pretty serious oversight unless there's some obvious better way to do this than what I've come up with. Is there a way to enable particular colliders while timeScale is 0, or is timeScale not actually a very good way to handle pausing the game, contrary to what I've seen so many people claim? Or, alternatively, am I overlooking a better method for checking for overlap between two UI objects that doesn't involve using Unity's physics system?
Thanks in advance for any assistance. I don't think my question should require sharing any of my code but if there's something you need to see to answer the question please let me know!
Answer by lgarczyn · Jan 15, 2020 at 11:44 PM
I'm sorry, but this is a terrible way to handle this. Physics are made for physics, not UI.
(If you really wanted to do this, you could create a Physics Scene for your UI, that is independent from the rest of the game. But please, be sensible.)
Here's an idea of a script that would work for you:
You have an array that represents your whole inventory.
Every UI element representing a slot has an index matching that array.
You then implement a dragging script using the standard unity UI tools, and get on which object/slot the item is dropped, for example using a graphics raycaster (a raycast for UI elements).
If your raycast got a slot object, get the script, get the index, check if the array position is already occupied, and if it is not, update the array. Otherwise, put the item back in its original position
An example of such a script: https://www.youtube.com/watch?v=Pc8K_DVPgVM
Hey, no need to apologize. I kind of assumed it wasn't a great method. I'm pretty new to program$$anonymous$$g and I got the idea from various tutorials I've found, but evidently it's a bad method.
Your suggestion would work for a simple drag-and-drop inventory system with discrete slots and using the mouse pointer, but as I stated in the OP that isn't quite what I'm after. As I stated in the OP, I want to do something like the Resident Evil games where the currently selected item is highlighted and you can move that highlight "cursor" with the directional keys. In particular I want to do something like in Resident Evil 4, where there is a single item grid with a given amount of grid slots and each item occupies a set amount of those slots. Items can be picked up, moved, and rotated by selecting them with the highlight "cursor" and moving them around. There aren't really any good tutorials I've found explaining how to do this. There is a single asset in the asset store that seems to have this functionality, but it still uses the mouse to drag and drop, and the code for the free version isn't very well commented so I don't know if I would be able to adapt it to my needs.
In case you've never played RE4, this video briefly shows off the inventory system: https://www.youtube.com/watch?v=xhRm_L4gOi4
Essentially I would be happy to use your idea if I could easily replace the mouse pointer drag and drop functionality with a virtual highlight cursor controlled by directional buttons.
Also this is a bit of a separate issue that might merit its own post but I had some problems with the Standard Input $$anonymous$$odule for the Event System. I would like to strip out the mouse click stuff and just have it read horizontal/vertical movement, cancel, and confirm buttons, but I have no idea how to go about doing that. You can't seem to modify the script either.
It appears that every item is RE4 is a rectangle. This would be pretty easy to implement.
Basically have a function to convert mouse position to inventory indexes, a function to check if a rectangle is occupied, and a function to fill or free a rectangle.
Your inventory would be a 2d array, with every slot being something like
class InventoryData
{
Vector2Int itemPosition;
Vector2int itemSize;
Item item;
}
An array position would be null if empty. When you place an item, you first check if every square under the rectangle is empty, then you set all the square to the same InventoryData instance.
It's actually easier to do without the mouse, as you only have to store the current index of the item you're moving, and don't have to convert mouse position to the array.
If you don't like a unity feature, don't use it. Trying to remove functionalities from the EventSystem is basically impossible. You'd have to find the source of the script, rename it to avoid conflicts, modify it, and then basically give up on any future updates the script might receive.
Thank you for this suggestion, I think I'm going to give it a try.
Well you see, it's not that I don't like the Event System. I like the Selectable feature, as it's made making UI menu navigation pretty easy and convenient. However, whenever you click the left mouse button, it see$$anonymous$$gly causes the Event System to deselect, which I don't want. I'm not sure what the alternative is except to painstakingly create my own selection system by similarly using a rectangle function and totally rewriting my inventory menu code. I find it a little hard to believe that the Event System was designed with such a narrow use-case in $$anonymous$$d.If you don't like a unity feature, don't use it. Trying to remove functionalities from the EventSystem is basically impossible. You'd have to find the source of the script, rename it to avoid conflicts, modify it, and then basically give up on any future updates the script might receive.
Answer by Esteem · Jan 16, 2020 at 05:26 PM
Consider you have the following:
public class Inventory {
public InventorySlot[,] slots = new InventorySlot[7, 5];
}
public class InventorySlot {
InventoryItem currentOccupier;
public bool IsFreeFor(InventoryItem item) {
return currentOccupier == null || currentOccupier == item;
}
}
public class InventoryItem {
public Vector3[] layoutPositions; // filled with yellow boxes (and the blue one)
}
So, your item is centered in the green box with the yellow and blue boxes being its layout. It also has a Vector3[] layoutPositions array containing all the yellow (and blue) boxes (they're all local positions which means they're related to the center. that's why the blue box has the values it does. It's all relative to the center of the item)
it is currently located at position [3, 2] in your inventory.
If I want to move it up I have to check the red cross positions. You can get those by moving all your points up one step resulting in:
Inventory perspective:
green [3, 2] -> [3, 3]
blue [1, 1] -> [1, 2]
and you can get those coordinates by calculating
Vector3 newPosition = item.transform.position + Vector3.up;
itemCoordX = Mathf.RoundToInt(newPosition.x);
itemCoordY = Mathf.RoundToInt(newPosition.y);
Vector3 blueBoxPosition = newPosition + layoutPositions[blueBoxIndex];
blueBoxCoordX = Mathf.RoundToInt(blueBoxPosition.x);
blueBoxCoordY = Mathf.RoundToInt(blueBoxPosition.y);
// and do this for all the yellow boxes as well
now all you have to do is to do is check whether or not those positions in the inventory are already taken up by something:
bool inventorySlotEmpty =
slots[itemCoordX, itemCoordY].IsFreeFor(item) &&
slots[blueBoxCoordX,blueBoxCoordY].IsFreeFor(item) ;
NOTE: you also calculate the array positions of all your yellow boxes and check if their new positions are empty as well. that's easy to do using a for loop or whatnot.
Now if inventorySlotEmpty is true, you can move the item saying item.transform.position = item.transform.position + Vector3.up;
when it comes down to rotating those positions, there are several ways but the easiest way I can think of that doesnt require too much math is doing this for each layoutPositions point:
layoutPositions[i] = new Vector3(
item.transform.right * layoutPositions[i].x,
item.transform.forward * layoutPositions[i].y
);
Thank you very much for this reply. As a relatively new programmer, I do have some questions though: first, what is the significance of the blue square? Is it just for the sake of demonstrating how to move all the squares? And how would I go about defining the initial layoutPositions occupied by the item? Is that something I should do manually in Unity on a per-item basis?
Doesn't this presume that each slot is 1 unit apart in terms of world position? I apologize if the answers to any of these are obvious, I'm still trying to get into the swing of things. Thanks again!Vector3 newPosition = item.transform.position + Vector3.up;
The blue and green squares are I believe simply an example of coordinates inside the object structure. Green has the coordinates 0,0 and Blue -2,-1.
Unity is also telling me that $$anonymous$$athf.ClampToInt does not exist.
as ceandros said.
you can painstakingly set all the layoutPositions in the array by hand or create some sort of a tool but if you're new to all this I'd suggest setting it by hand for now. If you're being realistic you can expect you'll refactor your code many times over before you're done (thinking up a way to visualize the positions) and if you won't then setting it by hand will suffice.
yes it does presume items being 1 unit apart.
good luck
I wonder if there's an easier, more visual, way to do it. If all objects are rectangles, it's pretty trivial, but the idea of using colliders is not bad in itself.
Your answer
Follow this Question
Related Questions
Time.timescale = 0 causes no display of Pausemenu 1 Answer
I need to prevent my scroll bar from moving the camera behind the panel 2 Answers
Timescale wont work 0 Answers
Pause, Unpause 1 Answer