Plot sometimes does not react to mouse input in windows forms - ilnumerics

I'm using an ILPlotcube with line- and point-plots inside a windows forms window. Somehow the mouse control of the PlotCube, like zooming and dragging does not work anymore. For example: i can't zoom into the plot by drawing a rectangle over the region i want to see. The mouse doesn't seem to be recognized anymore.
Basically the code looks like this:
public void init() {
Thread backgroundThread = new Thread(
new ThreadStart(() =>
{
makePlot();
}));
backgroundThread.Start();
}
makePlot() looks like this:
public void makePlot()
{
ILPlotCube plotCube = _ilPanel.Scene.First<ILPlotCube>();
if (plotCube == null) {
plotCube = new ILPlotCube {
new ILLinePlot(tosingle(_data1), "plot1", lineColor: Color.Blue),
new ILLinePlot(tosingle(_data2), "plot2", lineColor: Color.Red),
new ILLegend("data1","data2")
};
_ilPanel.Scene = new ILScene { plotCube };
}
else
{
plotCube.First<ILLinePlot>(tag: "plot1").Line.Positions.Update(tosingle(_data1);
plotCube.First<ILLinePlot>(tag: "plot2").Line.Positions.Update(tosingle(_data2);
}
_ilPanel.Scene.Configure();
_ilPanel.Invoke((MethodInvoker)delegate { _ilPanel.Refresh(); });
}
I call init() in Form1_Load() not in ilPanel1_Load()
Actually there is an other thing. The Class Reference says ILLinePlot has a Method "Update" to update the positions of the plotted line. But Visual Studio tells me ILLinePlot doesn't have such a member Function. Instead i'm using
linePlot.Line.Positions.Update
Also if i don't call Configure() on the Scene Element it won't plot the legend but if i do, the whole Plotting takes much more time.

It is hard to guess without some code. But I suppose there are multiple plot cubes in your scene. And they probably use the same screen area (probably the whole panel area?). Mouse events will fire on the topmost camera object or one of its children, if configured that way. Possibly the plot cube lays "behind" some other camera object which catches all the events. You can find out by registering an event handler on the root node and print out all mouse down events:
ilPanel1.Scene.MouseDown += (_s, _e) => { Debug.WriteLine(_e.Target); };
This will give the node below the mouse which was selected as target for the mouse event during the mouse down action. In order for the (correct) plotcube to properly receive the mouse events for zoom / rotate/ pan operations there should be either the plotcube node (plotcube derives from ILCamera) or one of its children found as target.
Another easy way to inspect the scene and detect all camera nodes contained is to create a breakpoint at the very end of your setup method (mostly this will be ilPanel1_Load(..). Once you stopped in the debugger, execute the following code in the Immediate window:
ilPanel1.Scene.Find<ILCamera>()
Count = 2
[0]: "Camera: #2 - Polar r:10 φ:0° ρ:0° - Pos X:0 Y:0 Z:10 - Lookat X:0 Y:0 Z:0 - Top X:0 Y:1 Z:0"
[1]: "ILPlotCube #30 'PlotCube' Children:[2]"
How many plot cubes do you count?

I got it to work after i switched
ilpanel.Scene = new ILScene { plotCube }
to
ilpanel.Scene.Add( plotCube )

Related

Unity3D How can I select multiple objects in 3D with a drag and select / lasso select?

I am struggling to find a good tutorial or informations that would allow me to select multiple objects in 3D in a user friendly manner.
So far, the best tutorial I found is this one : https://sharpcoderblog.com/blog/unity-3d-rts-style-unit-selection. The tutorial works by using the transform.position of the selectable objects and checking if it within the user's selection.
What I wish is to have the user be able to select a unit even if it is only partially within the user's selection such as most RTS games do ( both in 2D and 3D ).
One possibility would be to create a temporary mesh using the camera's clipping distances and the user's selection and then check for collisions but I was not able to find any tutorials using this method nor do I know if it is the best approach to the subject.
If I understand correctly you want to
somehow start a selection
collect every object that was "hit" during the collection
somehow end the collection
Couldn't you simply use raycasting? I will assume simple mouse input for now but you could basically port this to whatever input you have.
// Just a little helper class for an event in the Inspector you can add listeners to
[SerializeField]
public class SelectionEvent : UnityEvent<HashSet<GameObject>> { }
public class SelectionController : MonoBehaviour
{
// Adjust via the Inspector and select layers that shall be selectable
[SerializeField] private LayerMask includeLayers;
// Add your custom callbacks here either via code or the Inspector
public SelectionEvent OnSelectionChanged;
// Collects the current selection
private HashSet<GameObject> selection = new HashSet<GameObject>();
// Stores the current Coroutine controlling he selection process
private Coroutine selectionRoutine;
// If possible already reference via the Inspector
[SerializeField] private Camera _mainCamera;
// Otherwise get it once on runtime
private void Awake ()
{
if(!_mainCamera) _mainCamera = Camera.main;
}
// Depending on how exactly you want to start and stop the selection
private void Update()
{
if(Input.GetMouseButtonDown(0))
{
StartSelection();
}
if(Input.GetMouseButtonUp(0))
{
EndSelection();
}
}
public void StartSelection()
{
// if there is already a selection running you don't wanr to start another one
if(selectionRoutine != null) return;
selectionRoutine = StartCoroutine (SelectionRoutine ());
}
public void EndSelection()
{
// If there is no selection running then you can't end one
if(selectionRoutine == null) return;
StopCoroutine (selectionRoutine);
selectionRoutine = null;
// Inform all listeners about the new selection
OnSelectionChanged.Invoke(new HashSet<GameObject>(selection);
}
private IEnumerator SelectionRoutine()
{
// Start with an empty selection
selection.Clear();
// This is ok in a Coroutine as long as you yield somewhere within it
while(true)
{
// Get the ray shooting forward from the camera at the mouse position
// for other inputs simply replace this according to your needs
var ray = _mainCamera.ScreenPointToRay(Input.mousePosition);
// Check if you hit any object
if(Physics.Raycast(ray, out var hit, layerMask = includeLayers ))
{
// If so Add it once to your selection
if(!selection.Contains(hit.gameObject)) selection.Add(hit.gameObject);
}
// IMPORTANT: Tells Unity to "pause" here, render this frame
// and continue from here in the next frame
// (without this your app would freeze in an endless loop!)
yield return null;
}
}
}
Ofcourse you could do it directly in Update in this example but I wanted to provide it in a way where you can easily exchange the input method according to your needs ;)
From UX side you additionally might want to call a second event like OnSelectionPreviewUpdate or something like this every time you add a new object to the selection in order to be able to e.g. visualize the selection outcome.
I might have understood this wrong and it sounds like you rather wanted to get everything inside of a drawn shape.
This is slightly more complex but here would be my idea for that:
Have a dummy selection Rigidbody object that by default is disabled and does nothing
don't even have a renderer on it but a mesh filter and mesh collider
while you "draw" create a mesh based on the input
then use Rigidbody.SweepTestAll in order to check if you hit anything with it
Typed on smartphone but I hope the idea gets clear
I think I would try to create a PolygonCollider2D because it is quite simple comparing to creating a mesh. You can set its path (outline) by giving it 2D points like location of your pointer/mouse. Use the SetPath method for it. You can then use one of its methods to check if another point in space overlaps with that collider shape.
While the PolygonCollider2D interacts with 2D components you can still use its Collider2D.OverlapPoint method to check positions/bounds of your 3D objects after translating it to 2D space.
You can also use its CreateMesh method to create a mesh for drawing your selection area on the screen.
You can read more about the PolygonCollider2D here.
Hope it makes sens and hope it helps.

Gizmos.DrawCube display bug when multiple cubes drawn near each other

When I draw multiple cubes using Gizmos.DrawCubeto visualize some 3D tiles, I end up with a bunch of cubes inside out. It's called from Editor Code.
Here is the code for the call:
private void OnDrawGizmos() {
foreach (Vector3Int position in positions) {
Gizmos.DrawCube(position * Const.tileSize, Const.tileVectorSize);
}
}
Here is the display bug:
But everything is ok when _position only contains 1 cube
Edit: It also happens with few cubes drawn :
Any idea what is going on and how to correct it ?
This is not actually a bug. The gizmos don't write to the depth buffer. What that means (and what you see in the image) is gizmos being drawn on top of each other regardless of whether they are behind another gizmo. Maybe there is some way to enable depth buffer write or zWrite on gizmos, now you know what to look for.
What you could do in the meantime is try the Painter's algorithm. This is just sorting the gizmos from furthest from the camera to closest before you draw them.
private void OnDrawGizmos()
{
var sorted = positions.OrderByDescending((x) => Vector3.Distance(Camera.current.transform.position, x));
foreach (Vector3Int position in sorted)
{
Gizmos.DrawCube(position * Const.tileSize, Const.tileVectorSize);
}
}
And this is what it looks like:

Unity 5: how to zoom and translate an object from a grid layout to the center of the screen

I'm trying to create a scroll grid view in which every cell object is tapable.
When a cell object is tapped I want to scale and traslate it to the center of the screen and render it above other cells.
I was able to make it tapable and scale it in its position. Now I want to move the cell object to the center of the screen and render it above other cells.
I've tried many solutions but none of them works.
This is my hierarchy:
This is the grid in normal state:
This is the grid when a cell was tapped:
I'm populating the grid from a C# script dynamically.
void Populate()
{
GameObject cardContainerInstance, cardInstance;
foreach (var c in cardsCollection.GetAll())
{
if (c.IsOwned)
{
cardContainerInstance = Instantiate(cardContainer, transform);
cardInstance = cardContainerInstance.transform.Find("Card").gameObject;
var cardManager = cardInstance.GetComponent<CardManager>();
cardManager.card = c;
cardManager.AddListener(this);
}
else
{
Instantiate(cardSlot, transform);
}
}
}
public void OnCardClick(GameObject cardObject, Card card)
{
Debug.Log("OnCardClick " + card.name);
if (openedCard != null) {
if (openedCard.Number == card.Number)
{
CloseCard(openedCardObject);
}
else
{
CloseCard(openedCardObject);
OpenCard(cardObject, card);
}
}
else
{
OpenCard(cardObject, card);
}
}
void OpenCard(GameObject cardObject, Card card)
{
//cardObject.GetComponent<Canvas>().sortingOrder = 1;
var animator = cardObject.GetComponent<Animator>();
animator.SetTrigger("Open");
openedCard = card;
openedCardObject = cardObject;
}
void CloseCard(GameObject cardObject)
{
var animator = cardObject.GetComponent<Animator>();
animator.SetTrigger("Close");
openedCard = null;
openedCardObject = null;
}
I can't figure out how to move the cell to the center and render it above others.
Note that all is animated using an animator attached to the object itself.
Could anyone help me please? Thank you very much!
EDIT: more details
All cell object have the following hierarchy:
where:
CardContainer is an empty object added to use animator on Card child object
Card is the object itself that has a script, a canvas renderer and an animator
StatsImage is the object that slide out when the card is tapped
Image is a calssic UIImage with Image script, Shadow script and canvas renderer
Other component are simple texts.
EDIT: fix in progress
Trying to apply this suggestions I was able to manage the rendering order (as you see on the image below) but it seems that prevent touch events to be detected on the game object.
I've added a GraphicsRaycaster too and now the bottom horizontal scroll view scrolls again but only if I click and drag a card.
Moreover, with the GraphicsRaycaster, the main grid card still are not clickable and it's possible to open the card only if it is behind the bottom panel (if I click on the red spot in the image below the card behind the panel receives che click)
This is the CardContainer at runtime(note that I'm attaching new Canvas and GraphicsRaycaster on the CardContainer, which is the "root" element):
You didn't clarify whether you are using a sprite renderer or some other method but here is an answer for each.
Sprite renderer:
this the simple one. In each sprite renderer, there is a variable called "sortingOrder" in script and "Order in layer" in the inspector. sprite renderer with sorting Orders that are higher is rendered first. All you would need to do is call:
cardObject.GetComponent<SpriteRenderer>().sortingOrder = 1;
when you click the card, and
cardObject.GetComponent<SpriteRenderer>().sortingOrder = 0;
when you unclick it. I hope that makes sense!
Other Method:
this one is a bit harder and I would suggest that you switch to sprite renderers and it will be much easier and more stable down the road, but I can understand if you have already written a lot of scripts and don't want to go back and change them.
Anyway, all you will need to do Is create two layers: cardLower and cardUpper. then create a new camera and call it topCamera. now under the top camera object in the inspector, change the culling mask (it's near the top) and make sure cardUpper is selected. then change the Clear flags (first one) to "Don't Clear" finally change the depth to 0 (if that doesn't work change it to -2). Now objects in the cardUpper will always be rendered above everything else. You can change the layer through script with
cardObject.layer = "cardUpper"
or
cardObject.layer = "cardLower"
I hope that helps!
Ok, so its pretty simple. So you are going to want to add another canvas component to the game object, and check the override sorting to true. Then use
cardObject.GetComponent<Canvas>().sortingOrder = 1;
to place it in the front and
cardObject.GetComponent<Canvas>().sortingOrder = 0;
to put it in the back.
you are also going to need to put a GraphicsRaycaster on to each of the cardObjects
Ignore my other answer about sprite renderers, they are not needed here

Move a particular sprite to a target position after clicking the button in unity 4.6

I have just started unity. I have 4 Images(sprites) aligned in a grid.
As soon as i touch the particular chocolate, its texture changes[I wrote a code for that]. There is a button on screen.After pressing the button, I want to move only those chocolates whose texture has been changed.
I know the following move code but i don't know how to use it here.
void Update () {
float step=speed*Time.deltaTime;
transform.position=Vector3.MoveTowards(transform.position,target.position,step);
}
I just don't know to move that particular sprite whose texture is changed. Thanks
Do you want to be moving the sprites over the course of a duration or instantly?
If it's over the course of a duration I suggest you use Lerp. You can Lerp between two Vector.3's in a time scale. Much cleaner and once learned a very useful function.
Code examples below:
http://docs.unity3d.com/ScriptReference/Vector3.Lerp.html
http://www.blueraja.com/blog/404/how-to-use-unity-3ds-linear-interpolation-vector3-lerp-correctly
However if you want to move it instantly. This can be done very easily using the built in localPosition properties which you can set in or outside the object.
Set your changed sprites Bool moved property (create this) to true on click (if you're using Unity 4.6 UI canvas then look at the IClick interfaces available for registering mouse activity in canvas elements) and then when you press the button, loop through a list in a handler file which contains all your button texture objects and move those that the moved property is set to true for.
foreach(GameObject chocolate in chocolateList)
{
if (chocolate.moved == true)
{
gameObject.transform.localPosition.x = Insert your new x position.
gameObject.transform.localPosition.y = Insert your new y position.
}
}
However please do clarify your intentions so I can help further.
EDIT 1:
I highly suggest you make your sprites an object in the canvas for absolute work clarity. This makes a lot of sense as your canvas can handle these type of things much better. Use Image and assign your image the sprite object (your chocolate piece), define it's width and height and add a script to it called "ChocolatePiece", in this script create two public variables, bool moved and int ID, nothing else is required from this script. Save this new object as your new prefab.
Once you've done this in a handler script attached to an empty gameobject in your canvas make a list of gameobjects:
List<GameObject> chocolatePieces = new List<GameObject>();
You'll want to at the top of your handler script define GameObject chocolatePiece and attach in your inspector the prefab we defined earlier. Then in Start(), loop the size of how many chocolate pieces you want, for your example lets use 4. Instantiate 4 of the prefabs you defined earlier as gameobjects and for each define their properties just like this:
Example variables:
int x = -200;
int y = 200;
int amountToMoveInX = 200;
int amountToMoveInY = 100;
Example instantiation code:
GameObject newPiece = (GameObject)Instantiate(chocolatePiece);
chocolatePieces.Add(newPiece);
newPiece.GetComponent<ChocolatePiece>().ID = i;
newPiece.transform.SetParent(gameObject.transform, false);
newPiece.name = ("ChocolatePiece" + i);
newPiece.GetComponent<RectTransform>().localPosition = new Vector3(x, y, 0);
From this point add to your positions (x by amountToMoveInX and y by amountToMoveInY) for the next loop count;
(For the transform.position, each count of your loop add an amount on to a default x and default y value (the position of your first piece most likely))
Now because you have all your gameobjects in a list with their properties properly set you can then access these gameobjects through your handler script.

Multiple PlotCube synchronization

Multiple ILPlotCubes in the same ILScene react independent to mouse interaction. Docu example here.
Each of my PlotCubes contains one LinePlot and I need to keep the X-Axis of both PlotCubes aligned. Therefore an event is needed that notifies me when the X-Axis in one PlotCube is changed due to mouse interaction.
Couldn't find anything in the documentation or search engines. Did some limited testing with mouse events (complicated, possible ?). Found a ILAxisChangedEventArgs class, but no event.
Use ILPlotCube.Limits instead! The ILLimits class manages the axis limits for a plotcube. It provides the Changed event. You can use that in order to reflect changes to other plot cubes.
private void ilPanel1_Load_1(object sender, EventArgs e) {
// just some data
ILArray<float> A1 = new float[] { 1,4,3,2,5 };
// setup new plot cube
var pc1 = ilPanel1.Scene.Add(new ILPlotCube("pc1") {
ScreenRect = new RectangleF(0,0,1,.6f)
});
// 2nd plot cube
ILArray<float> A2 = new float[] { -1,-4,-3,-2,4 };
var pc2 = ilPanel1.Scene.Add(new ILPlotCube("pc2") {
ScreenRect = new RectangleF(0, .4f, 1, .6f)
});
// add line plots to the plot cubes
pc1.Add(new ILLinePlot(A1));
pc2.Add(new ILLinePlot(A2));
// Synchronize changes to the limits property
// NOTE: mouse interaction is fired on the SYNCHRONIZED COPY
// of the plot cube, which is maintained for each individual ILPanel!
pc1 = ilPanel1.SceneSyncRoot.First<ILPlotCube>("pc1");
pc2 = ilPanel1.SceneSyncRoot.First<ILPlotCube>("pc2");
pc1.Limits.Changed += (_s, _a) => { SynchXAxis(pc1.Limits, pc2.Limits); };
pc2.Limits.Changed += (_s, _a) => { SynchXAxis(pc2.Limits, pc1.Limits); };
}
private void SynchXAxis(ILLimits lim1, ILLimits lim2) {
// synch x-axis lim1 -> lim2
Vector3 min = lim2.Min;
Vector3 max = lim2.Max;
min.X = lim1.XMin; max.X = lim1.XMax;
// disable eventing to prevent from feedback loops
lim2.EventingSuspend();
lim2.Set(min, max);
lim2.EventingStart(); // discards Changed events
}
Now, when you zoom / pan with the mouse, changes to one plot cube gets transferred to the other plot cube. This acts on the X axes only. Other limits will not be affected.
Two Hints
1) Keep in mind that mouse interaction does not affect the original plot cube object you created but instead change a synchronized copy of it. The copy is maintained by the ILPanel internally and prevents from multithreading issues as well as from changes to one instance populating back to other instances which may existing in other panels. In order to get informed about those changes, you must wire up to the event handler of the synchronized copy. ILPanel.SceneSynchRoot provides you access.
2) When transferring the changes from the Limits of one plot cube to the other one should disable the eventing on the target limits object. Otherwise, it would trigger another Changed event and the events would fire endlessly. The ILLimits.EventingStart() function reenables the eventing after your changes and discards all events accumulated so far.