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.
Related
hey guys , So as you can see i made a robot arm grab a slingshot's objectholder with a ball in it. My arm pulls it any direction I want it but I wanted the user to know which box is going to be shot at.
If you're applying an impulse force (or velocity) to your ball and there is gravity in your world, your item will follow the Projectile motion;
Here you can find details about it:
https://en.wikipedia.org/wiki/Projectile_motion
There are basically two main options
calculating it yourself
This is probably way better for performance especially if you want a really simple trajectory preview without accounting for any collision etc
refer to linked article. But it basically comes down to
and would need slightly rework from 2D to 3D physics, should be trivial though since the important part about the Y axis basically stays the same.
You would
Call this simulation with according supposed shoot direction and velocity
Visualize the tracked positions e.g. in a LineRenderer
Physics.Simulate
This allows you to run physics updates manually all within a single frame and actually let the Physics engine handle it all for you
This costs of course a lot of performance but you get all collisions etc accounted for automatically without getting a headache
You would
Make a snapshot of all Rigid bodies in your scene - in order to reset after the simulation
Simulate the desired amount of physics steps (XY seconds ahead) while keeping track of the simulated data
reset everything to the state tracked in step 1
use the simulated data from step 2 to visualize e.g. with a LineRenderer
This might look somewhat like e.g.
public class Prediction : MonoBehaviour
{
public LineRenderer line;
public Rigidbody tracked;
private Rigidbody[] allRigidbodies;
private void Awake()
{
allRigidbodies = FindObjectsOfType<Rigidbody>();
}
private void LateUpdate()
{
// Wherever you would get this from
Vector3 wouldApplyForce;
// Step 1 - snapshot
// For simplicity reasons for now just the positions
// using some Linq magic
var originalPositions = allRigidbodies.ToDictionary(item => item, item => item.position);
// Step 2 - Simulate e.g. 2 seconds ahead
var trackedPositions = new Vector3 [(int) (2 / Time.fixedDeltaTime)];
Physics.autoSimulation = false;
tracked.AddForce(wouldApplyForce);
for(var i = 0; i < trackedPositions.Length; i++)
{
Physics.Simulate(Time.fixedDeltaTime);
trackedPositions[i] = tracked.position;
}
// Step 3 - reset
foreach (var kvp in originalPositions)
{
kvp.Key.position = kvp.Value;
}
Physics.autoSimulate = true;
// Step 4 - Visualize
line.positionCount = trackedPositions.Length;
line.SetPositions(trackedPositions);
}
}
Of course we won't talk about performance here ^^
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.
I have lots of same simple objects that are affecting gameplay, thousands of them! Well, not thousands, but really many. So if I make them GameObjects, the FPS decreases, especially when spawning them. Even with pooling. I should try a different approach.
You know that particle system in Unity3D can render many particles very fast. It also automatically controls particles, emits and removes them. But in my case, positions and lifetimes of objects are managed by game logic, and particle system is not allowed to do anything without my command, even reorder particles.
I am trying to use SetParticles method to control particles. It works in test project, where I use GetParticles first. I can even remove particles setting lifetime to -1, but can't spawn new ones. Also it does not prevent particle system from controlling particles.
I can disable emission so no particles will be created automatically.
I can set particles speed to 0 so they will not move.
I can set lifetime to huge number so they will not be removed.
I have an pool of Particle instances, to avoid unnessessary GC allocations. Objects receive a reference to particle when they are spawned, change it when updated, and set lifetime -1 and return it to pool when deleted. The pool stores this:
private ParticleSystem.Particle [] _unusedParticles;
private int _unusedCount;
private ParticleSystem.Particle [] _array;
_unused array and counter are needed for pooling, and _array stores all particles, used and unused alike, and is used in SetParticles call.
The main disadvantage of this method is that it doesn't work, probably because SetParticles does not create new particles. Also I guess it doesn't do anything with particles draw order, that's why it's poorly suited for bullet hell games where bullet patterns should look nice.
What should I do to properly disable automatic control of particles and properly setup direct control, with spawning and removing?
What you are looking for might be
List<Matrix4x4> matrixes=new List<Matrix4x4>();
for (...)
{
matrixes.Add(Matrix4x4.TRS( position,rotation,scale));
}
Graphics.DrawMeshInstanced(mesh,0,material, matrixes);
On each frame you can just update positions, rotations and scales, and get all the instances rendered on the GPU in one drawcall (pretty darn fast compared to seperate gameobjects). You can render up to 1000 instances in one call using this way
Create an empty GameObject, then add the ParticleSystem as child. Set playOnAwake to true.
Now when you need it, set GameObject.SetActive to true else false.
To get each of them use ParticleSystem.GetParticles, modify them and ParticleSystem.GetParticles.
I hope this is what you are looking for.
ParticleSystem m_System;
ParticleSystem.Particle[] m_Particles;
public float m_Drift = 0.01f;
private void LateUpdate()
{
InitializeIfNeeded();
// GetParticles is allocation free because we reuse the m_Particles buffer between updates
int numParticlesAlive = m_System.GetParticles(m_Particles);
// Change only the particles that are alive
for (int i = 0; i < numParticlesAlive; i++)
{
m_Particles[i].velocity += Vector3.up * m_Drift;
}
// Apply the particle changes to the Particle System
m_System.SetParticles(m_Particles, numParticlesAlive);
}
void InitializeIfNeeded()
{
if (m_System == null)
m_System = GetComponent<ParticleSystem>();
if (m_Particles == null || m_Particles.Length < m_System.main.maxParticles)
m_Particles = new ParticleSystem.Particle[m_System.main.maxParticles];
}
After we created a particle system in editor, we should disable emission and shape, so only core part and renderer stay active.
The most important part is that Simulation Speed must be zero. A particle system will no longer emit, remove or process particles automatically. Only your code now manages them.
I use this class to control particles. Instead of binding a particle to an object, it has API for registering objects, smoke in this case. Also, it stores a temporary array for particles to avoid GC allocations, and particle count, to avoid using particleCount property of the particle system.
In Update, which is called by my game logic, the following happens:
All objects that were despawned by game logic, are removed from list.
If object count is greater than array length, the array is resized.
If object count is greater than live particle count, _particleSystem.Emit call is made. If we call SetParticles without that, no new particles will appear. We must emit them first.
GetParticles is called. Simple, but probably not the most efficient solution, but it preserves particles data. It may be optimized by setting all particle data on array creation and resizing. If you do so, remove GetParticles call and uncomment the line above. Also, your game logic should manage particles even more carefully.
For each object, we let that object change a particle.
Each particle without object should be removed, so their lifetime is set to negative number.
SetParticles updates particles in system.
public class SmokeSystem {
private ParticleSystem _particleSystem;
private List <Smoke> _smoke = new List <Smoke> ();
private ParticleSystem.Particle [] _particles = new ParticleSystem.Particle[256];
private int _particleCount;
public SmokeSystem (ParticleSystem particleSystem) {
_particleSystem = particleSystem;
}
public void AddSmoke (Smoke smoke) => _smoke.Add (smoke);
public void Update () {
_smoke.RemoveAll (e => e.Despawned);
if (_smoke.Count > _particles.Length) {
int newSize = Max (_smoke.Count, 2 * _particles.Length);
Array.Resize (ref _particles, newSize);
}
int count = _smoke.Count;
if (count > _particleCount) {
_particleSystem.Emit (count - _particleCount);
// _particleCount = count;
}
_particleCount = _particleSystem.GetParticles (_particles);
for (int i = 0; i < count; i++) {
_smoke [i].UpdateParticle (ref _particles [i]);
}
for (int i = count; i < _particleCount; i++) {
_particles [i].remainingLifetime = -1;
}
_particleSystem.SetParticles (_particles, _particleCount);
_particleCount = count;
}
}
It does not depend on GPU instancing support so it will work on WebGL.
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.
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 )