I found a lot of topics saying that if I would like to remove a specific particle, I simply have to set it's LifeTime to -1.
I know my loop is working correctly, as the movement of each particle goes as planned AND I can see the "remove particle" Debug line in my log the moment it reaches it's destination. Did anything change over time, or am I missing something simple?
I'm using Unity 5,4,3f1 Personal
void Update ()
{
if(Input.GetKeyDown(KeyCode.Space)) PlayParticleEffect();
if (particleSystem != null) {
particles = new ParticleSystem.Particle[particleSystem.particleCount];
int count = particleSystem.GetParticles (particles);
for (int i = 0; i < count; i++) {
ParticleSystem.Particle particle = particles [i];
float dist = Vector3.Distance (particleTarget.transform.position, particle.position);
if (dist > 0.1f) {
particle.position = Vector3.MoveTowards (particle.position, particleTarget.transform.position, Time.deltaTime * 10);
particles [i] = particle;
} else {
particle.lifetime = -0;
Debug.Log ("remove particle");
}
}
particleSystem.SetParticles (particles, count);
}
}
You just need to set the remaining lifetime of the particle to 0 (if it's set to 0, the particle will disappear).
Your code doesn't work because you forgot to add particles [i] = particle; in the else branch of your if, you're never setting the lifetime to 0 to the actual particle:
if (dist > 0.1f) {
particle.position = Vector3.MoveTowards (particle.position, Vector3.zero, Time.deltaTime * 10);
particles [i] = particle;
} else {
particle.remainingLifetime = 0;
particles [i] = particle;
}
P.S.: I used remainingLifetime instead of lifetime since I'm on Unity 5.5
Related
I have a coroutine DefaultFixedUpdate() that functions as a replacement for FixedUpdate() and another coroutine called WallSlidingFixedUpdate(). I have two variables that store which of the both is active. activeCoroutine and prevActiveCoroutine with the starting strings nameof(DefaultFixedUpdate) and "no coroutine". In FixedUpdate() from Unity I have this code that should change coroutines if activeCoroutine and prevActiveCoroutine are not the same:
void FixedUpdate()
{
if (activeCoroutine != prevActiveCoroutine)
{
print($"changing coroutines from {prevActiveCoroutine} to {activeCoroutine}");
StopCoroutine(prevActiveCoroutine);
StartCoroutine(activeCoroutine);
prevActiveCoroutine = activeCoroutine;
print("changed coroutines");
}
}
When starting the game DefaultFixedUpdate() starts as expected and prevActiveCoroutine changes as expected to activeCoroutine and when a condition inside DefaultFixedUpdate() is met to switch to WallSlidingFixedUpdate() activeCoroutine changes to the new string as expected. But when StartCoroutine(activeCoroutine) gets called the code inside WallSlidingFixedUpdate() doesn't run for some reason. StopCoroutine(prevActiveCoroutine) does work. The end result is that none of the two corutines runs but expected was that WallSlidingCoroutine() runs.
These are the two Coroutines. The changing of activeCoroutine happens almost at the bottom of both functions.
IEnumerator DefaultFixedUpdate()
{
while (true)
{
//setup
gravity = (velocity.y < 0 || !Input.GetKey(KeyCode.Space)) ? dropGravity : normalGravity;
Vector2 pos = transform.position + col.size.y * 0.5f * Vector3.up;
Vector2 timeStepVelocity = velocity * Time.fixedDeltaTime;
Vector2 pos1 = pos;
velocity.y -= gravity * Time.fixedDeltaTime;
//checking collision
RaycastHit2D[] colChecks = Physics2D.BoxCastAll(pos, col.size, 0, timeStepVelocity, timeStepVelocity.magnitude, 0b1000000);
//remove unwanted collisions that are behind and don't make sense to collide with
List<RaycastHit2D> newColChecks = new();
foreach (RaycastHit2D colCheck in colChecks)
if (Vector2.Dot(colCheck.normal, timeStepVelocity) < 0)
newColChecks.Add(colCheck);
colChecks = newColChecks.ToArray();
//remove unwanted velocities
foreach (RaycastHit2D colCheck in colChecks)
{
if (Mathf.Abs(colCheck.normal.x / colCheck.normal.y) > 1) // left and right
{
velocity.x = Mathf.Sign(colCheck.normal.x) > 0 ? Mathf.Max(velocity.x, 0) : Mathf.Min(velocity.x, 0);
}
else // top bottom
{
velocity.y = Mathf.Sign(colCheck.normal.y) > 0 ? Mathf.Max(velocity.y, 0) : Mathf.Min(velocity.y, 0);
}
}
//calculate correct destination
if (colChecks.Length == 0)
pos += timeStepVelocity;
else
{
//create bounding box for all possible future positions
Vector4 posBox = new Vector4(pos.x, pos.x + timeStepVelocity.x, pos.y, pos.y + timeStepVelocity.y);
//move to surface
pos = colChecks[0].centroid;
//slide along surface until hitting another tangent or the bounding box
Vector2 tangent = new Vector3(colChecks[0].normal.y, -colChecks[0].normal.x);
Vector2 centroid = pos;
//slide until hitting distance bounds
if (Mathf.Abs(tangent.x) > Mathf.Abs(tangent.y)) //top and bottom
{
pos += tangent / tangent.x * (timeStepVelocity.x - pos.x + pos1.x);
tangent = Mathf.Sign(centroid.y - pos1.y) * tangent;
}
else //left and right
{
pos += tangent / tangent.y * (timeStepVelocity.y - pos.y + pos1.y);
tangent = Mathf.Sign(centroid.x - pos1.x) * tangent;
}
//save previous surface that is being slid on
Vector2 prevTangent = Tangent(colChecks[0].normal);
//check if I collided with another tangent along the way
colChecks = Physics2D.BoxCastAll(centroid, col.size, 0, pos, Vector2.Distance(pos, centroid), 0b1000000);
//remove unwanted collisions that are behind and don't make sense to collide with
newColChecks = new();
foreach (RaycastHit2D colCheck in colChecks)
if (Vector2.Dot(colCheck.normal, tangent * Mathf.Sign(Vector2.Dot(pos - centroid, tangent))) < 0 || Vector2.Dot(colCheck.normal, timeStepVelocity) < 0)
newColChecks.Add(colCheck);
colChecks = newColChecks.ToArray();
if (colChecks.Length > 1)
{
pos = centroid + (pos - centroid).normalized * colChecks[1].distance;
}
//repeat
int loopBreak = 0;
string loopBreakMessage = "";
while (true)
{
loopBreakMessage += centroid + "\n";
if (loopBreak > 4)
{
Debug.LogError("loop limit exceeded\n" + loopBreakMessage);
break;
}
loopBreak++;
//check if I collided with another tangent along the way
colChecks = Physics2D.BoxCastAll(centroid, col.size, 0, pos - centroid, Vector2.Distance(pos, centroid), 0b1000000);
//remove unwanted collisions that are behind and don't make sense to collide with
newColChecks = new();
foreach (RaycastHit2D colCheck in colChecks)
if (Tangent(colCheck.normal) != prevTangent)
newColChecks.Add(colCheck);
colChecks = newColChecks.ToArray();
if (colChecks.Length > 2)
{
prevTangent = colChecks[0].normal;
if (!VectorOnSameQuarter(colChecks[1].normal, colChecks[2].normal)) // stuck in a corner
{
pos = centroid + (pos - centroid).normalized * colChecks[2].distance;
break;
}
else //continue along new tangent
{
//corner pos
centroid += (pos - centroid).normalized * colChecks[2].distance;
//end pos if there's no surface along the way
pos = PosAlongVectorInsideRectangle(posBox, centroid, Tangent(colChecks[2].normal));
}
}
else
{
break;
}
}
AlignVelocityWithGround();
}
//check if player is grounded and save ground tangent to remove small hops when going down slopes
PlayerState.OnGround = false;
RaycastHit2D[] jumpChecks = Physics2D.BoxCastAll(pos, col.size, 0, Vector2.down, 1e-3f, 0b1000000);
if (jumpChecks.Length != 0)
{
foreach (RaycastHit2D jumpCheck in jumpChecks)
{
if (jumpCheck.normal.y > Mathf.Abs(jumpCheck.normal.x))
{
PlayerState.OnGround = true;
groundTangent = new Vector2(jumpCheck.normal.y, -jumpCheck.normal.x);
}
}
PlayerState.state = PlayerState.state == PlayerState.State.WallSliding ? PlayerState.State.Default : PlayerState.state;
//check for a wall slide and save wall tangent for smooth sliding
if (!PlayerState.OnGround && velocity.y < 0 && Mathf.Abs(jumpChecks[0].normal.x) > Mathf.Abs(jumpChecks[0].normal.y) && Mathf.Asin(jumpChecks[0].normal.y) * Mathf.Rad2Deg < 15)
{
groundTangent = new Vector2(jumpChecks[0].normal.y, -jumpChecks[0].normal.x);
transform.position = pos - col.size.y * 0.5f * Vector2.up;
activeCoroutine = nameof(WallSlidingFixedUpdate);
yield break;
}
}
transform.position = pos - col.size.y * 0.5f * Vector2.up;
yield return new WaitForFixedUpdate();
}
}
IEnumerable WallSlidingFixedUpdate()
{
print("wallsliding coroutine entered");
while (true)
{
print("wallsliding coroutine happening");
Vector2 pos = transform.position;
pos -= Time.fixedDeltaTime * wallSlideSpeed * groundTangent / groundTangent.y;
transform.position = pos;
if (InputManager.Left && groundTangent.y > 0)
{
activeCoroutine = nameof(DefaultFixedUpdate);
yield break;
}
else if (InputManager.Right)
{
activeCoroutine = nameof(DefaultFixedUpdate);
yield break;
}
yield return new WaitForFixedUpdate();
}
}
You figured your issue about the return type out already -> It needs to be IEnumerator.
Anyway my question is WHY?
Instead of going through all that trouble for switching between different Coroutine instances via a string I would rather have only a single outer scope routine running and then rather switch between which inner block to execute
private enum RoutineType
{
Default,
WallSliding
}
private RouineType activeRoutine;
private IEnumerator FixedUptadeRoutine()
{
while(true)
{
switch(activeRoutine)
{
case RoutineType.WallSliding:
yield return DefaultRoutine();
break;
case default:
yield return WallSlidingRoutine();
break;
}
}
}
private IEnumerator DefaultRoutine()
{
while(true)
{
// NOTE: Btw you want to wait for fixed update FIRST
// Before applying all the values
...
// And now this will simply make sure you change the routine type
activeRoutine = RoutineType.WallSliding;
// and exit this one so the outer routine will handle the switch in the next frame
yield break;
...
}
}
private IEnumerator WallSlidingRoutine()
{
...
}
you can simply yield return any IEnumerator so it will be executed and at the same time the outer routine waits for it to finish. There is no need to have multiple start and stop coroutines.
Holy moly, how did I miss this. The return value of WallSlidingFixedUpdate is IEnumerable and not IEnumerator. I changed it to IEnumertor and now it works.
I've got a setup right now where I have a 3D object with an empty object parented to it inside the object. When you press one button, an empty object that the camera is following rotates 90 degrees. If you press the other button, it rotates the other direction in 90 degrees. The result is that the camera can spin around the object 4 times before it makes a complete rotation.
Currently it works well, but I'm trying to figure out how I add some easing to the animation so it doesn't look so rough. I know a little about working with curves in animation but I'm not sure how I can apply that to code (or if it's even the best way to do things)
public InputMaster controls;
private bool isSpinning;
private float rotationSpeed = 0.3f;
IEnumerator RotateMe(Vector3 byangles, float intime)
{
var fromangle = transform.rotation;
var toangle = Quaternion.Euler(transform.eulerAngles + byangles);
for (var t = 0f; t < 1; t += Time.deltaTime / intime)
{
transform.rotation = Quaternion.Slerp(fromangle, toangle, t);
yield return null;
transform.rotation = toangle;
}
Debug.Log("finished rotation");
isSpinning = false;
Debug.Log("isSpinning now false");
}
code above is where I create a coroutine that says how it is going to transform. One thing that confuses me here a little is that if I don't have the line that says transform.rotation = toangle; , the rotation comes out at like 89.5 degrees or something, and if you do it multiple times it goes several degrees off. Not sure why that happens.
void Rotation(float amount)
{
Debug.Log("rotation number is " + amount);
float holdingRotate = controls.Player.Camera.ReadValue<float>();
if (holdingRotate == 1 && isSpinning == false)
{
isSpinning = true;
Debug.Log("isSpinning now true");
StartCoroutine(RotateMe(Vector3.up * 90, rotationSpeed));
}
else if (holdingRotate == -1 && isSpinning == false)
{
isSpinning = true;
Debug.Log("isSpinning now true");
StartCoroutine(RotateMe(Vector3.up * -90, rotationSpeed));
}
}
and this part is where the animation gets called up. Any help much appreciated.
Have a look at Animation Curves like #immersive said.
You can easily define your own curves in inspector
simply by adding this to your code:
public AnimationCurve curve;
You can then use AnimationCurve.Evaluate
to sample the curve.
The alternative is to use a premade library from the AssetStore/Package Manager like DOTween or LeanTween to name some.
Comments inline:
public class SmoothRotator : MonoBehaviour
{
// Animation curve holds the 'lerp factor' from starting angle to final angle over time
// (Y axis is blend factor, X axis is normalized 'time' factor)
public AnimationCurve Ease = AnimationCurve.EaseInOut(0, 0, 1, 1);
public float Duration = 1f;
private IEnumerator ActiveCoroutine = null;
public void RotateToward(Quaternion targetAngle) {
// If there's a Coroutine to stop, stop it.
if (ActiveCoroutine != null)
StopCoroutine(ActiveCoroutine);
// Start new Coroutine and cache the IEnumerator in case we need to interrupt it.
StartCoroutine(ActiveCoroutine = Rotator(targetAngle));
}
public IEnumerator Rotator(Quaternion targetAngle) {
// Store starting angle
var fromAngle = transform.rotation;
// Accumulator for Time.deltaTime
var age = 0f;
while (age < 1f) {
// normalize time (scale to "percentage complete") and clamp it
var normalisedTime = Mathf.Clamp01(age / Duration);
// Pull lerp factor from AnimationCurve
var lerpFactor = Ease.Evaluate(normalisedTime);
// Set rotation
transform.rotation = Quaternion.Slerp(fromAngle, targetAngle, lerpFactor);
// Wait for next frame
yield return null;
// Update age with new frame's deltaTime
age += Time.deltaTime;
}
// Animation complete, force true value
transform.rotation = targetAngle;
// Housekeeping, clear ActiveCoroutine
ActiveCoroutine = null;
}
}
for (var t = 0f; t < 1; t += Time.deltaTime / intime)
{
transform.rotation = Quaternion.Slerp(fromangle, toangle, t);
yield return null;
}
transform.rotation = toangle;
Because t won't equal to 1.
You just need to set the rotation to the target after the loop.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Rotate : MonoBehaviour
{
public enum Axistorotate { Back, Down, Forward, Left, Right, Up, Zero };
public Vector3[] vectorAxises = new Vector3[7];
public Axistorotate[] myAxis;
public float angle;
public float speed;
private bool stopRotation = true;
// Start is called before the first frame update
void Start()
{
vectorAxises[0] = Vector3.back;
vectorAxises[1] = Vector3.down;
vectorAxises[2] = Vector3.forward;
vectorAxises[3] = Vector3.left;
vectorAxises[4] = Vector3.right;
vectorAxises[5] = Vector3.up;
vectorAxises[6] = Vector3.zero;
StartCoroutine(RotateObject());
}
public Vector3 GetAxis(Axistorotate axis)
{
return vectorAxises[(int)axis];
}
// Update is called once per frame
void Update()
{
if(Input.GetKeyDown(KeyCode.S))
{
stopRotation = false;
}
if (Input.GetKeyDown(KeyCode.C) && stopRotation == false)
{
stopRotation = true;
StartCoroutine(RotateObject());
}
}
IEnumerator RotateObject()
{
while (stopRotation == true)
{
for (int i = 0; i < myAxis.Length; i++)
{
transform.Rotate(GetAxis(myAxis[i]), angle);
}
yield return new WaitForSeconds(speed);
}
}
}
For some reason while the game is running and I'm changing one of the enums to forward or back or down each time it's rotating another direction. If I'm starting when both enums on back states and then changing one of them to down it looks like it's rotating to the left or right and then when changing back to back it's not rotating like it was when they were both set to back.
How can I update the vectorAxises array in real time while the game is running to show in the Inspector the current axis state for example : vectorAxises[0] -> 1, 0, -1 ..... vectorAxises[7] -> 0,-1, 0 I want that when I'm changing one of the enums that it will show it on the vectorAxises.
Maybe I need to create another vectorAxises array one for each enum ?
And maybe when doing two myAxis it's changing the same one the same angle so it's not realy two enums that change individual axis ?
transform.Rotate has an optional parameter
relativeTo
Determines whether to rotate the GameObject either locally to the GameObject or relative to the Scene in world space.
which by default is Space.Self.
So your
transform.Rotate(GetAxis(myAxis[i]), angle);
is always done in the local coordinate system of the GameObject. This local system is rotated along with the GameObject so the local transform.up, transform.forward etc axis change all the time.
Instead make it rotate around world axis
transform.Rotate(GetAxis(myAxis[i]), angle, Space.World);
How can I update the vectorAxises array in real time while the game is running to show in the Inspector the current axis state
This should already be the case. Or do you mean you want to see the currently "selected" value. You should use Debugging and Breakpoints for that. Since you do
for (int i = 0; i < myAxis.Length; i++)
{
transform.Rotate(GetAxis(myAxis[i]), angle);
}
without any further yield return the object will directly "jump" into the new rotation and in the inspector you would always only see the last GetAxis(myAxis[i]).
If you are looking for a smooth rotation then checkout Dest's answer slightly modified you could e.g. let the object rotate within 1 second
while (stopRotation == true)
{
// calculate the target rotation
Quaternion rotationQuaternion = Quaternion.identity;
for (int i = 0; i < myAxis.Length; i++)
{
rotationQuaternion *= Quaternion.AngleAxis(angle, GetAxis(myAxis[i]));
}
// before starting to rotate store initial and target rotation
var initialRotation = transform.rotation;
var targetRotation = initialRotation * rotationQuaternion;
// could also get this from the Inspector e.g.
var rotationDuration = 1;
// Do a smooth rotation from the initial to target rotation
// within the defined rotationDuration
var timePassed = 0f;
do
{
// additionally ease-in and -out the rotation
var lerpFactor = Mathf.SmoothStep(0, 1, timePassed / rotationDuration);
transform.rotation = Quaternion.Slerp(initialRotation, targetRotation, lerpFactor);
timePassed += Time.deltaTime;
// let this state be rendered
yield return null;
} while(timePassed < rotationDuration);
// if you still want the pause
yield return new WaitForSeconds(speed);
}
Just out of curiosity: Why even use an enum here? Couldn't you directly iterate through the vectorAxises index instead and only add those entries you will be using?
Use Quaternions for multiple rotations at the same time:
Quaternion rotationQuaternion = Quaternion.identity;
for (int i = 0; i < myAxis.Length; i++)
{
rotationQuaternion *= Quaternion.AngleAxis(angle, GetAxis(myAxis[i]));
}
transform.rotation *= rotationQuaternion;
It should fix your problem with wrong rotations
I say that I am a beginner.
I have a question during the project.
I'm currently implementing a card-matching game.
When I start the game, The image is loaded from the external path (D: / ..).
My question is in the code below.
public void createCardList()
{
int count_x = 0;
int count_y = 0;
GameObject parent_area = GameObject.Find("GameMain");
List<int> num = createRandomIndex(createArrayIndex());
for (int i = 0; i < card_list.Count; ++i)
{
if ((i % CARD_ROWS) == 0 && i > 0)
{
count_y += 1;
count_x = 0;
}
GameObject card_prefab_load = Resources.Load("Prefabs/card") as GameObject;
GameObject card_prefab_instantiate = Instantiate(card_prefab_load, card_prefab_load.transform.position, Quaternion.identity);
float card_position_x = (CARD_WIDTH + CARD_SPACE) * (i % CARD_ROWS) - 290f;
//Debug.Log("card_position_x" + card_position_x);
float card_position_y = count_y * (CARD_HEIGHT + CARD_SPACE) - 280f;
//Debug.Log("card_position_y" + card_position_y);
card_prefab_instantiate.transform.SetParent(parent_area.transform);
card_prefab_instantiate.transform.name = "card_" + num[i];
card_prefab_instantiate.transform.localScale = new Vector3(1f, 1f, 1f);
card_prefab_instantiate.transform.localPosition = new Vector3(card_position_x, card_position_y, 1f);
StartCoroutine(firstRotateOriginalImage());
}
}
// Rotate image
private IEnumerator firstRotateOriginalImage()
{
yield return new WaitForSeconds(2f);
GameObject findCardList = GameObject.Find("GameMain");
for (int i = 0; i < findCardList.transform.childCount; ++i)
{
// I don't know what code to put in this part.
}
}
What I want to implement is below.
When the card rotation value reaches 90 degrees,How to change an externally imported image to a Sprite Image of a child GameObject?
How to rotate the child objects 360 degrees after the first task is completed?
For example, the picture below.
Arrows indicate the order in which cards are flipped.
also, Of the 12 GameObjects, Six GameObjects try to implement a common image.
I don't know much. I need your help.
There are many ways to do that...
transform.Rotate(...)
transform.RotateAround(...)
transform.localEulerAngles = transform.localEulerAngles.X|Y|Z +
amount
transform.localRotation = transform.localRotation*relativeRotation
/*= a Quaternion*/
Something else entirely...
I'm a doubtful regarding how well I understand your question...
But it seems like you want to simply flip the cards over don't you?
The approach I'd take is to have each card as the combination of the face (rotated 180 degrees in Y) and the back, both being children of an empty GameObject.
That way you could simply rotate the Card object using transform.Rotate(0, 180, 0)
To use it in a coroutine you could do
//Speed of animation (here 0.55 seconds)
float degreesPerSecond = 200f;
//The amount you want to rotate
float maxDegrees = 180f;
//Animate
for (float f = maxDegrees; f < 0;)
{
//How much to rotate
float rot = degreesPerSecond * Time.deltaTime;
//Rotate children
foreach(var child in transform)
child.Rotate(0, rot, 0);
//Record rotation
f -= rot;
//Wait next frame
yield return null;
}
//Round to desired rotation
foreach(var child in transform)
child.position = new Vector3(child.position.x, maxDegrees, child.position.z);
I have enemies that patrol to different waypoints using NavMesh Agent I want when the enemy reach the next waypoint to have the same rotation as that waypoint.
Here is the code:
void Update ()
{
if (agent.remainingDistance < 0.1)
{
// tried to stop the agent so I can override it's rotation, doesn't work
agent.Stop();
// Give him the desired rotation
transform.rotation = wayPoints[curretPoint].rotation;
if (curretPoint < wayPoints.Length -1)
{
curretPoint++;
}
else
{
curretPoint = 0;
}
// make him wait for a fixed amount of time
patrolTimer += Time.deltaTime;
if (patrolTimer >= patrolWait)
{
patrolTimer = 0;
agent.SetDestination (wayPoints[curretPoint].position);
agent.Resume ();
}
}
}
The problem is that he keeps rotating back and forth very quickly, I can't get teh desired effect that I want.
Try setting Angular Speed of NavMesh Agent to 0.
Edit:
That should work:
// make him wait for a fixed amount of time
patrolTimer += Time.deltaTime;
if (patrolTimer >= patrolWait)
{
if (curretPoint < wayPoints.Length -1)
{
curretPoint++;
}
else
{
curretPoint = 0;
}
patrolTimer = 0;
agent.SetDestination (wayPoints[curretPoint].position);
agent.Resume ();
}
This is how I handled it:
Instead of doing agent.Stop(); and agent.Resume(); I simply set its speed to 0 and used transform.Rotate to rotate the character.