WPF animated object collision detection, objects not displaying in position of intersection - c#

So I have rectangle shaped custom controls being animated on a path, 35 in total.
When a control stops, for what ever reason, I detect if any other controls overlap/intersect with it. If they do, I stop them.
To detect a collision, when a controls stops it triggers a DispatcherTimer to start. When the tick even triggers it does this.
Pause all the storyboards that are currently running.
Loop through every control and see if they intersect with the stopped control.
If they do, then that control is also marked as stopped.
Once loop is complete, any control that is not marked as stopped, I continue the storyboard.
Hopefully that logic sounds ok for the basics. This is the code for the detection part.
foreach (var ucPallet in Paellets)
{
if(ucPallet.IsMoving)
ucPallet.Storyboard.Pause(this);
}
for (var j = 0; j < Paellets.Count; j++)
{
var movingBounds = Paellets[j].BoundsRelativeTo(AnimationCanvas);
var stoppedBounds = stoppedPallet.BoundsRelativeTo(AnimationCanvas);
if (stoppedBounds.IntersectsWith(movingBounds))
{
var pallet = movingPaellet[j]; // Breakpoint Here
pallet.IsMoving = false;
}
}
foreach (var ucPallet in Paellets)
{
if(ucPallet.IsMoving) // Only continue if not set to stop
ucPallet.Storyboard.Resume(this);
}
For test purposes, I just made a simple path between 2 points, so there is a start and end. I create the controls in a loop, with a gap of about 250ms.
What should happen is that when the first control stops (Storyboard Complete), the others should stack up behind it. A Bit like cars coming to a stop at a traffic light.
The problem.
Visually, they are not stacking behind eachother they are all going to the end.
If I put a break point on the detection of an intersection (see Breakpoint Here in the above code), I can see the details of the bounds and it looks like the intersect is about 5 pixels.
With the assumption that the storyboard has paused, and I do not continue it, why does it visually not look the same as what the bounds say? i.e. I expect to see 1 control overlap the other by about 5 pixels, instead I see it is the same location and 1 control covers the other.
UPDATE 1:
Just a thought after submitting the question. I put a Console.WriteLine in every Storyboard.Complete EventHandler. In theory I should only see 1 (the first control) as the others are paused when they intersect. When I run I see they all complete. How can this happen if I have Paused them?
UPDATE 2:
Simple Project link to show the issue.
PalletTest.Zip

To Pause a StoryBoard, you have to Begin it like this:
myStoryboard.Begin(this, true);
Did you? See How to: Control a Storyboard After It Starts
Update after looking at the project:
The pallets are moving too fast. I've played around with your animations parms Setting the animation's Duration to 15 seconds results in a "perfect stack" as you expected and only one completed animation. Looks like with 2 seconds one animated step is too big, so the last step executed reahces the end of the path.
So there's nothing wrong with your code. So I just have a tiny suggestion for improvement: You can reduce the three for/foreach-loops to a single, where you set IsMoving and pause the animation. Here is a sample, that uses LINQ:
var stoppedPallets = this.Pallets.Where(p => !p.IsMoving).ToList();
var movingPallets = this.Pallets.Where(p => p.IsMoving).ToList();
foreach (var movingPallet in movingPallets)
{
var movingBounds = movingPallet.BoundsRelativeTo(this.AnimationGrid);
if (stoppedPallets.Any(
p => p.BoundsRelativeTo(this.AnimationGrid).IntersectsWith(movingBounds)))
{
movingPallet.Storyboard.Pause(this);
movingPallet.IsMoving = false;
}
}
There is still some room for optimization, as the bounds of the stopped pallets are calculated for every moving pallet (this is true for the above and for your code).
Additionally, I would recommend moving as much of the pallet-initializing as possible to the ucPallet class.

Related

Why is TextMeshPro not Updating textBounds After Setting text?

I have implemented a tooltip system on hover. For this I use a textmeshpro text and an image. This image should be a simple background depending on the length of the text, which is always different.
If I hover over a certain element, the textmeshpro text is replaced. The background image is moved to the element and the size is shall change based on the length of the text:
// Hover over slot in
public void OnPointerEnter(PointerEventData eventData) {
if (currentItem != null) {
Debug.Log("hover over slot item in");
GamePlay.EnableCanvas(GamePlay.GetHoverCanvas());
TextMeshProUGUI txtMeshPro = GamePlay.GetHoverCanvas().GetComponentInChildren<TextMeshProUGUI>();
Debug.Log(currentItem.GetItemDescription(currentItem));
txtMeshPro.text = currentItem.GetItemDescription(currentItem);
var hoverCanvasText = txtMeshPro.GetComponent<RectTransform>();
var hoverCanvasTextBG = GamePlay.GetHoverCanvas().GetComponentInChildren<Image>().GetComponent<RectTransform>();
hoverCanvasText.position = new Vector3(this.GetComponent<RectTransform>().position.x, this.GetComponent<RectTransform>().position.y + 50, 0);
hoverCanvasTextBG.position = new Vector3(this.GetComponent<RectTransform>().position.x, this.GetComponent<RectTransform>().position.y + 50, 0);
hoverCanvasTextBG.sizeDelta = new Vector2(txtMeshPro.textBounds.size.x + 15, txtMeshPro.textBounds.size.y + 15);
// No clue why image is not resized properly on first hover
}
}
The weird issue I have is that the first time I hover over the element, the image size wont change.
Replacing the text and changing the position always works without issues.
Issue description:
First hover over element A: Image background size wont change at all.
Second hover over element B: Image background size changes, but has the correct size of element A
Third hover over element A: Image background size changes, but has the correct size of element B
How is this possible? I dont get whats happening here. The code works without any issues for the position, why the same logic does not work for the size.
Note: I am very new to unity, tinkering arround for a few days now, so I could be a very simple issue, but after researching for a few hours now, I haven't found anything useful.
After setting the text of a TextMeshProUGUI, the textBounds and its size are not updated until the following frame. This is caused by Unity's deferred layout rebuilding. In their UI Layout Documentation, it reads:
The rebuild will not happen immediately, but at the end of the current
frame, just before rendering happens. The reason it is not immediate
is that this would cause layouts to be potentially rebuild many times
during the same frame, which would be bad for performance.
I'm gonna go ahead and guess GamePlay.GetHoverCanvas() is maintaining an object that's never destroyed and, therefore, your code is working in all cases except its very first run.
There are probably a few ways you can go about fixing this but the easiest would be to call Canvas.ForceUpdateCanvases() between setting the text and reading the size of the textbounds. It's not the fastest function but it should do the trick.
Another option would be to, instead of a synchronous function, kick off a coroutine that sets the text one one frame, yields the next frame, then reads the textbounds on its second frame of execution. (This would likely lead to the size popping in and might look undesirable).
Instead of refreshing all your Canvases, you can use TMP_Text.ForceMeshUpdate(); right after you assign your string. This makes sure your text-information are correct within the same frame.
If the parents of your text uses Layout-Groups, there is a fair chance that you need to update those as well. You do that with LayoutRebuilder.ForceRebuildLayoutImmediate( [Layout-Group.RectTransform] ); (read more here)
Please note that the order is important.
Assign your text
Rebuild Layout-Group
Force Mesh Update
To prevent the need to force update the Canvas, you can use TextMeshProUGUI.GetPreferredValues() to ascertain ahead of time what the width and height would be for the text currently assigned to it. It'll return a Vector2. You can then use that Vector2 instead of TextMeshProUGUI.textBounds.
Or if you need the size for a certain text without actually assigning it, use TextMeshProUGUI.GetPreferredValues(string text).
There are other overloads of that method to get the size given constraints in either width or height.

Unity 2D Setting the value of a Scrollbar to 0

I just wanted to set the value of the scrollbar of my scrollview object to 0 every time a new text object is created. This way the newest object would always remain at the bottom without needing to scroll down (think of the chat of a video game for example). My problem is that for some reason the program is setting the value to 0 and then creating the text object, so it keeps the 2nd newest object at the bottom of the scrollview.
My code:
//Creates the Text objects
private GameObject GenerateTextObject(string content)
{
//Creates the Text objects
GameObject messagePrefab = Instantiate(MessagePrefab);
Text textComponent = messagePrefab.GetComponent<Text>();
//Sets the content of the text and parent
textComponent.text = content;
messagePrefab.transform.SetParent(ContentPanel.transform, false);
ScrollbarVertical.value = 0;
return messagePrefab;
}
In the code I am setting the value at the end of the function, but still sets the value of the scrollbar to 0 (properly moving the scrollview to the bottom) before the object is created.
https://gyazo.com/897982521f13d7792ec26540490a40c0
In the Gyazo picture you can see how it doesn't scroll all the way down.
I have tried using a coroutine and waitForEndFrame aswell as waitforseconds(1), but none seem to work.
Edit: when loading up Unity and sending new messages to the scrollview, I see the scrollbar go all the way down and then really quickly move up just a bit hiding the new text object.
What you can do is create a new Scroll View and follow these steps:
The result is from calling GenerateTextObject(Time.time.ToString()); every second.
private GameObject GenerateTextObject(string content)
{
//Creates the Text objects
GameObject messagePrefab = Instantiate(MessagePrefab);
Text textComponent = messagePrefab.GetComponent<Text>();
//Sets the content of the text and parent
textComponent.text = content;
messagePrefab.transform.SetParent(ContentPanel.transform, false);
//Force reset to the bottom on each message
//ScrollbarVertical.value = 0;
return messagePrefab;
}
I have not come up with a proper fix for this problem (I honestly do not know the cause of problem), but I have put together a little work-around for this situation.
To do this I simply created a small function:
public void Testing()
{
if (testing == true)
{
ScrollbarVertical.value = 0;
testing = false;
}
}
This would be attached to the built-in functionality of scrollbars "On Value Changed (Single)". In order to allow this to work, I just change the bool "testing" to true after creating a text object on GenerateTextObject(string content).
This fix is really ugly, and probably would cause more problems in the near future, but for now, it is a quick and dirty solution for this problem.
Sorry to necro this, but I had a similar problem and googling couldn't find a good answer. So I'm posting the solution that I found.
I tried to change the scrollbar value to 1 or 0, depending on the direction, so that everytime I open a UI panel, it'll start at the top. But no matter what I did, nothing seemed to work.
So my solution is to change the Y position of the content instead of the scrollbar value.
My scrollbar direction is set at bottom to top. Then I take the sizeDelta of the content, divide it by 2, (since I need the value of 1 for the scrollbar.value). I then multiplied it by -1 (if I dont, it'll start at the bottom). Then I set that value as the Y position of the content.
So far it works like a charm everytime I open my UI panel.
Hope this helps anyone looking for a solution.

Touch method update Too fast for loop game

I'm having trouble in developing my windows phone app. It's a game and I'm using XNA.
Here one of the Navigation Page:
The problem is that in the top left the "back" button is at the same place on the page before this one. So when I tap the back button, it doesn't lead me to the previous page but to the first one. (sometimes if I click fast enought it leads me to the previous one). As it works sometimes, I think the error doesn't come from my code. I think as the back_button is at the same place on both pages, it update too fast and the "touch_event" stay for too long maybe. I don't know how to solve this.
There is how I catch the button click:
TouchPanelCapabilities touchCap = TouchPanel.GetCapabilities();
if (touchCap.IsConnected)
{
TouchCollection touches = TouchPanel.GetState();
if (touches.Count >= 1)
{
Vector2 PositionTouch = touches[0].Position;
return (Mouseclik((int)PositionTouch.X, (int)PositionTouch.Y));
}
}
return (Screen.ChooseLevelScreen);
You need something like this:
if (touches.Count >= 1)
if (touches[0].State == TouchLocationState.Released)
{
Vector2 PositionTouch = touches[0].Position;
return (Mouseclik((int)PositionTouch.X, (int)PositionTouch.Y));
}
Usually, a tap is detected when you release the touch, not while you are pressing it, in this way you are sure that you detect it only once.
Poll for when your finger is released as opposed to clicked. That way when you remove your finger from the touch button it will only be registered once and not the x amount of times your finger was still on the button and the loop was carried out.
see TouchLocationState.Released at
http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.input.touch.touchlocationstate.aspx
you need TouchLocationState.Released
http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.input.touch.touchlocationstate.aspx

C# - How to register onMouseClick in real time?

I am writing a silly little program (as we all do from time to time) to work on some basic C# coding. As I'm sure you can see, the mouse is clicked, the picture moves until it's at the place where the mouse was clicked. This bit works fine. I then want the picturebox to move randomly, which I have also managed! The next problem I have is that when I click, instead of moving to the new mouse coordinates, the picturebox continues to move randomly. Any help on this would be much appreciated! Here is the code I think is the problem.
Many Thanks!
John
protected override void OnMouseClick(MouseEventArgs e)
{
base.OnMouseClick(e);
int destX = e.X;
int destY = e.Y;
HasArrived = false;
while (HasArrived == false)
{
moveImage(destX, destY, pictureBox1);
if (pictureBox1.Left == destX && pictureBox1.Top == destY)
{
HasArrived = true;
while (HasArrived == true)
{
randomMove(pictureBox1);
hungry1 += 1;
}
}
}
}
You are stucked in your inner loop!
Try to put the loop in a backgroundworker,
so you can recognize the new mouseclick to set HasArrived to true.
And using the negative while statement in the positive while statemant seems very bad to me,
ive never seen that before.
I dont think thiss will work fine..
It's probably because HasArrived never gets set to false anywhere? But that will also mean you will be stuck in your Move loop as well.
EDIT: If you want to only move randomly once after you move to the position you clicked at, then remove the while around the random bit, else you'll need some other variable to know when to break out of the while.
Your inner loop seems to have a problem.
HasArrived = true;
while (HasArrived == true)
{
randomMove(pictureBox1);
hungry1 += 1;
}
Unless randomMove() can modify HasArrived, your loop never set HasArrived to false, so the while condition is always true, and you will never get out of the loop.
I looks like its because once the picture moves to its destination your program is stuck in a tight while loop from which it never escapes
First of all, you should never test equality of variables with boolean expressions. This means HasArrived == false is bad and it is better to write it like this:
while (!HasArrived)
{
// ...
while (HasArrived)
{
}
}
The problem is that your program does not give back control to the form, after the mouse has been clicked once.
Let me illustrate, what your program does as soon as the mouse has been clicked:
remember new x and y coords
while the picture has not arrived, move the image
in case the picture has arrived it's destination - move it around randomly
But it ends up moving the picture around until you kill the program, because your form does still handle the "OnMouseClicked"-event. And as long as the program does not return from your event handling method, the form is unable to record any other event (typically it should freeze, ending with "Application not responding").
So from what I think your code is supposed to do you should code your program like this:
1. Create a parallel thread to move around your image randomly
2. Pause the thread as long as your mouse-click event has not yet been handled
Also your question is wrong at all. Realtime means that your program is able to give any result in a predefined time-limit. Your method does not fit this at all, beacause the time your method takes for execution depends on how fast your image moves and which distance it should move.

Resizing a grid control programatically over a period of time

G'day,
I am attempting to simulate the old XBox 360 GUI with the sliding tabs (Remember, you'd press left or right and the content would slide in depending on the tab?) Anyways, at the moment, I have this working well, however I cannot get the "animation" working.
When the user presses left arrow or right arrow, my OpenWindow(int iIndex) method will be called, where iIndex is the index to the next or previous "window" to be slid in. (Not a window... each "Window" is a struct with a parent grid control containing a button and a smaller grid control that contains the windows contents.)
Now, my problem lies with resizing the parent grid control. When it is slid in, it is resized by calling mygrid.Width += 1; That works, but I don't see it happen over a determined period of time, it just lags a bit and then resizes to the required width. Whereas if I call this.Width += 1 in the same method, (this being the main program window) the window resizes how I want the grid control to resize. I've tried UpdateLayout() but to no avail. This tells me my timing is okay.
If anyone could be of assistance, it would be greatly appreciated.
Here is my OpenWindow method...
public void OpenWindow(int iIndex)
{
int iInterval = 1;
for (int i = (int)myDict[iIndex].Shell.Width; i < (int)stack_outter.Width; i += iInterval)
{
myDict[iIndex].Shell.Width += 1;
myDict[iIndex].Shell.UpdateLayout();
System.Threading.Thread.Sleep(1);
}
myDict[iIndex].Shell.Width = stack_outter.Width - (BUTTON_WIDTH * (myDict.Count - 1));
}
myDict is a Dictionary, Shell is the grid that I am attempting to animate when resizing. Sorry about the code, it's messy, my code is always hacked when I am trying to get stuff working :)
Thanks,
Ash
Neried Web Solutions
Your OpenWindow method is happening on the Dispatcher thread. That's also the thread responsible for rendering, so as long as your OpenWindow method doesn't return, nothing gets rendered.
The proper way to fix this would be to animate the Width property. I don't have any experience in starting animations from code (I've only used them in the past for things like a fade-in button highlight on mouse over, which is more easily done from WPF), but I took a quick read-through this page, Animation Overview on MSDN, and I think you'll want something like this:
DoubleAnimation myDoubleAnimation = new DoubleAnimation();
myDoubleAnimation.From = myDict[iIndex].Shell.Width;
myDoubleAnimation.To = stack_outter.Width;
myDoubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(0.5));
myDoubleAnimation.AutoReverse = false;
myDoubleAnimation.RepeatBehavior = new RepeatBehavior(1.0);
myStoryboard = new Storyboard();
myStoryboard.Children.Add(myDoubleAnimation);
Storyboard.SetTarget(myDoubleAnimation, myDict[iIndex].Shell);
Storyboard.SetTargetProperty(myDoubleAnimation, new PropertyPath(FrameworkElement.WidthProperty));
myStoryboard.Begin(myDict[iIndex].Shell);

Categories

Resources