I want to fade an image if I press down key "A", I found a code here, in stackoverflow and tried to overwrite it to work as I want, but it still doesn't fade out my image.
public float FadeRate;
private Image image;
private float targetAlpha;
void Start()
{
if (Input.GetKeyDown(KeyCode.A))
{
image = GetComponent<Image>();
Material instantiatedMaterial = Instantiate<Material>(image.material);
image.material = instantiatedMaterial;
targetAlpha = image.color.a;
StartCoroutine(FadeIn());
}
}
IEnumerator FadeIn()
{
targetAlpha = 1.0f;
Color curColor = image.color;
while (Mathf.Abs(curColor.a - targetAlpha) > 0.0001f)
{
Debug.Log(image.material.color.a);
curColor.a = Mathf.Lerp(curColor.a, targetAlpha, FadeRate * Time.deltaTime);
image.color = curColor;
yield return null;
}
}
So first of all you have your check which button is pressed in the wrong method. Your start method should look like this:
void Start()
{
image = GetComponent<Image>();
Material instantiatedMaterial = Instantiate<Material>(image.material);
image.material = instantiatedMaterial;
targetAlpha = image.color.a;
}
Your if statement has to go into your update method. The start method only runs once after the object is initalized. Your update method runs every frame. You want to check every frame if the A key was pressed.
private void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
StartCoroutine(FadeIn());
}
}
Your coroutine actually looks fine. It is doing what it should do: Fade your image in and not out. You can easily see that by looking at its name. It is called FadeIn. Your image is fading in because its targetAlpha is set to 1f. This has to be changed to zero. I also renamed your method accordingly.
IEnumerator FadeOut()
{
targetAlpha = 0f;
Color curColor = image.color;
while (Mathf.Abs(curColor.a - targetAlpha) > 0.0001f)
{
Debug.Log(image.material.color.a);
curColor.a = Mathf.Lerp(curColor.a, targetAlpha, FadeRate * Time.deltaTime);
image.color = curColor;
yield return null;
}
}
Just as an addition to Daniel M's answer.
You can actually make Start itself to be a Coroutine in which case you actually could do
private IEnumerator Start()
{
var image = GetComponent<Image>();
var startAlpha = image.color.a;
var targetAlpha = 1.0f;
var curColor = image.color;
yield return new WaitUntil(() => Input.GetKeyDown(KeyCode.A));
while (!Mathf.Abs(curColor.a - targetAlpha) > 0.0001f)
{
Debug.Log(image.material.color.a);
curColor.a = Mathf.Lerp(curColor.a, targetAlpha, FadeRate * Time.deltaTime);
image.color = curColor;
yield return null;
}
// In order to end with clean values
curColor.a = targetAlpha;
image.color = curColor;
}
Note though that your Lerp will always start pretty fast and get slower and slower and probably never really reach the target alpha.
If you want to rather fade in a fixed time you could use
[SerializeField] private float duration = 2f;
and then
for(var factor = 0f; factor < 1; factor += Time.deltaTime / duration)
{
// Optionally add ease-in and -out
factor = Mathf.SmoothStep(0, 1, factor);
curColor.a = Mathf.Lerp(startAlpha, targetAlpha, factor);
image.color = curColor;
yield return null;
}
// In order to end with clean values
curColor.a = targetAlpha;
image.color = curColor;
And then if you want total control of the blend curve you could also use an
[SerializeField] private AnimationCurve _fadeCurve;
configure it according to your needs in the Inspector and do
for(var factor = 0f; factor < 1; factor += Time.deltaTime / duration)
{
factor = _fadeCurve.Evaluate(factor);
curColor.a = Mathf.Lerp(startAlpha, targetAlpha, factor);
image.color = curColor;
yield return null;
}
// In order to end with clean values
curColor.a = targetAlpha;
image.color = curColor;
Related
I'm fairly new to unity, and after scraping through threads about rotations through frames, I managed to find code to help me get the look that I wanted for my game, here's the code for the movement:
public class Movement : MonoBehaviour
{
IEnumerator RotateOctagon(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.Lerp(fromAngle, toAngle, t);
yield return null;
}
transform.rotation = toAngle;
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown("left"))
{
StartCoroutine(RotateOctagon(Vector3.forward * 45, 0.1f));
}
if (Input.GetKeyDown("right"))
{
StartCoroutine(RotateOctagon(Vector3.forward * -45, 0.1f));
}
if (Input.GetKeyDown("up"))
{
StartCoroutine(RotateOctagon(Vector3.forward * 180, 0.1f));
}
}
}
And it generates desired results - putting the octagon to its next side, assuming you wait until the rotation is finished...
But The problem arises when you press left or right in the middle of the animation, in which the octagon incorrectly ends up on a point or weird angle (not a multiple of 45 degrees)...
My guess is that if the octagon is in the middle of the coroutine of rotating to the right say, at 30 degrees, any immediate key press will now rotate from that angle, ending up being 30+- 45 an unwanted angle.
I considered calculating the offset of the angle to the lower/higher 45 degree angle multiple, but I feel like there's a more correct approach and something I'm missing about coroutines that would help this.
Any help on how to get the octagon to rotate properly under the condition that multiple keys are pressed would be greatly appreciated.
Desired behavior: Say the current z axis is 0. After right is pressed and in the middle of rotating, left is pressed at 30 degrees. The octagon should rotate back to z=0
In order to simply not allow a new routine until the previous one has finished simply introduce a bool flag
private bool isRotating;
IEnumerator RotateOctagon(Vector3 byAngles, float inTime)
{
if(isRotating) yield break;
isRotating = true;
var fromAngle = transform.rotation;
var toAngle = Quaternion.Euler(transform.eulerAngles + byAngles);
for(var t = 0f; t <= 1; t+= Time.deltaTime / inTime)
{
transform.rotation = Quaternion.Lerp(fromAngle, toAngle, t);
yield return null;
}
transform.rotation = toAngle;
isRotating = false;
}
This way your input is basically ignored until the previous routine finished.
In order to be more efficient you can then also add
void Update()
{
if(isRotating) return;
...
}
Or the "fancy" alternative without additional flag: Make Start itself a Coroutine and do
IEnumerator Start ()
{
while(true)
{
if (Input.GetKeyDown("left"))
{
// This now rotates and waits at the same time until the rotation is finished
yield return RotateOctagon(Vector3.forward * 45, 0.1f));
}
else if (Input.GetKeyDown("right"))
{
yield return RotateOctagon(Vector3.forward * -45, 0.1f));
}
else if (Input.GetKeyDown("up"))
{
yield return RotateOctagon(Vector3.forward * 180, 0.1f));
}
else
{
// If none of the previous is true wait at least one frame
yield return null;
}
}
}
Alternatively since now we know you want to rather interrupt the current rotation and start a new one store it in a field like
private float rotation;
void Awake()
{
rotation = transform.localEulerAngles.z;
}
void Update()
{
if (Input.GetKeyDown("left"))
{
RotateAbout (45);
}
if (Input.GetKeyDown("right"))
{
RotateAbout (-45);
}
if (Input.GetKeyDown("up"))
{
RotateAbout (180);
}
}
private void RotateAbout(float angle)
{
StopAllCoroutines ();
rotation += angle;
StartCoroutine(RotateOctagon(rotation, 0.1f));
}
and then rather do
IEnumerator RotateOctagon(float toAngle, float inTime)
{
var fromRotation = transform.localRotation;
var toRotation = Quaternion.Euler(Vector3.forward * toAngle);
for(var t = 0f; t <= 1; t+= Time.deltaTime / inTime)
{
transform.localRotation = Quaternion.Lerp(fromRotation, toRotation, t);
yield return null;
}
transform.localRotation = toRotation;
}
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.
The story is that I have this research project in which I have to simulate a tracking experiment.
I have a ball in a box (which we hope can be implemented as a VR room in the future), and the ball moves to random coordinates.
I also plan on adding another object that the user can click on (or in the case of VR, use a joystick) to move and follow the ball.
The coordinates of each movement of the ball, and the coordinates of each movement of the object must be outputted to a file.
Right now, I am having difficulty sending the ball to random coordinates.
The ball is named "Player" and the file is named "PlayerController." The two issues are that while utilizing the Unity version of Random, I consistently attain the same coordinates on each run and the ball does not stop moving.
My code is attached below. Also, if any of you readers have an idea on how to implement the rest of my project, suggestions are definitely appreciated!
Thank you so much!
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
public class PlayerController : MonoBehaviour {
private float movementDuration = 2.0f;
private float waitBeforeMoving = 2.0f;
private bool hasArrived = false;
private Coroutine moveCoroutine = null;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
if (!hasArrived)
{
hasArrived = true;
Vector3[] v = new Vector3[20];
for (int i=0; i<v.Length; i++)
{
Random.InitState(System.DateTime.Now.Millisecond);
v[i] = new Vector3(Random.Range(-10.0f, 10.0f),
Random.Range(-10.0f, 10.0f),
Random.Range(-10.0f, 10.0f));
moveCoroutine = StartCoroutine(MoveToPoint(v[i]));
}
}
if (Input.GetMouseButtonDown(0))
StopMovement();
}
private IEnumerator MoveToPoint(Vector3 targetPos)
{
float timer = 0.0f;
Vector3 startPos = transform.position;
while (timer < movementDuration)
{
timer += Time.deltaTime;
float t = timer / movementDuration;
t = t * t * t * (t * (6f * t - 15f) + 10f);
transform.position = Vector3.Lerp(startPos, targetPos, t);
yield return null;
}
yield return new WaitForSeconds(waitBeforeMoving);
hasArrived = false;
}
private void StopMovement()
{
if (moveCoroutine != null)
StopCoroutine(moveCoroutine);
}
public void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.CompareTag("sphereTag"))
StopMovement();
}
The ball doesn't stop moving because you set hasArrived to false at the end of your coroutine so it just fires again every 2 seconds.
The coordinates were different for me every time - not sure what issue you are facing there. May be something with Random.InitState()
The way you have your loop set up, every vector in the array is getting fed into its own coroutine, but this is all happening essentially instantaneously since there is no delay before going into the next iteration of your for-loop. If you are attempting to loop through all 20 of the points you should instead call a single coroutine, with the for-loop and embedded delay inside:
bool hasArrived;
private float movementDuration = 2.0f;
private float waitBeforeMoving = 2.0f;
void Update()
{
if (!hasArrived)
{
hasArrived = true;
StartCoroutine(MoveToPoints());
}
}
private IEnumerator MoveToPoints()
{
Vector3[] v = new Vector3[20];
for (int i = 0; i < v.Length; i++)
{
float timer = 0.0f;
Vector3 startPos = transform.position;
v[i] = new Vector3(Random.Range(-10.0f, 10.0f),
Random.Range(-10.0f, 10.0f),
Random.Range(-10.0f, 10.0f));
while (timer < movementDuration)
{
timer += Time.deltaTime;
float t = timer / movementDuration;
t = t * t * t * (t * (6f * t - 15f) + 10f);
transform.position = Vector3.Lerp(startPos, v[i], t);
yield return null;
}
yield return new WaitForSeconds(waitBeforeMoving);
}
}
I'm creating a script that allows objects to be faded considering an interval of time.
The script works very well, however when an object is faded, it is still very visible in the inspector AND in the build.
Could someone explain me why and how to make an object completely invisible?
(I know I can "enable" the object, but isn't it using more resources than fading? I don't know :( )
Here is the code if I've made any mistake (code inspired from a topic in unity's forum)
// Update is called once per frame
void Update ()
{
MyTime = Time.deltaTime;
AccumulatedTime += MyTime;
if (this.name == "Cube (1)")
{
Debug.Log(AccumulatedTime);
}
if (SwitchVisibility == 1)
{
if (AccumulatedTime >= Interval && AccumulatedTime<= 2*Interval || AccumulatedTime >= 3*Interval && AccumulatedTime<= 4*Interval)
{
StartCoroutine(FadeTo(0.0f, 1.0f));
SwitchVisibility = 0;
}
}
if (SwitchVisibility == 0)
{
if (AccumulatedTime >= 0 && AccumulatedTime <= Interval || AccumulatedTime >= 2*Interval && AccumulatedTime <= 3*Interval)
{
StartCoroutine(FadeTo(1.0f, 1.0f));
SwitchVisibility = 1;
}
}
if (AccumulatedTime >= Interval * 4.5f)
{
AccumulatedTime = 0;
}
}
IEnumerator FadeTo(float aValue, float aTime)
{
float alpha = MyRenderer.material.color.a;
for (float t = 0.0f; t < 1.0f; t += Time.deltaTime / aTime)
{
OriginalColor.a = Mathf.Lerp(alpha, aValue, t);
Color newColor = OriginalColor;
MyRenderer.material.color = newColor;
yield return null;
}
}
Here is what objects look like:
Assuming your code is fine, the problem very likely is from your Material settings. This changed in Unity 5. To change the alpha of a Mesh Renderer, you must also change the Material's Rendering Mode from Opaque(default) to Fade.
Transparent Mode is also fine but will will not be completely transparent and will result to the problem in your question.
You can change the mode from script.
Property Name: _Mode
With Debug.Log(MyRenderer.material.GetFloat("_Mode"));, I got the follwing values:
0 = Opaque
1 = Cutout
2 = Fade
3 = Transparent
We can change the Rendering Mode to Fade with MyRenderer.material.SetFloat("_Mode",2);
There is a known problem when setting the Render Mode from script. You must also update all other properties as well to make the change take effect. Here is a complete way to change your Render Mode to Fade from script:
MyRenderer.material.SetFloat("_Mode", 2);
MyRenderer.material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
MyRenderer.material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
MyRenderer.material.SetInt("_ZWrite", 0);
MyRenderer.material.DisableKeyword("_ALPHATEST_ON");
MyRenderer.material.EnableKeyword("_ALPHABLEND_ON");
MyRenderer.material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
MyRenderer.material.renderQueue = 3000;
Finally, if the this is working for you then your script is not good. You can use the script below to fade in and fade out a Mesh Renderer.
public MeshRenderer MyRenderer;
bool fading = false;
void Fade(bool fadeIn, float duration)
{
if (fading)
{
return;
}
fading = true;
changeModeToFade();
StartCoroutine(FadeTo(fadeIn, duration));
}
void changeModeToFade()
{
MyRenderer.material.SetFloat("_Mode", 2);
MyRenderer.material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
MyRenderer.material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
MyRenderer.material.SetInt("_ZWrite", 0);
MyRenderer.material.DisableKeyword("_ALPHATEST_ON");
MyRenderer.material.EnableKeyword("_ALPHABLEND_ON");
MyRenderer.material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
MyRenderer.material.renderQueue = 3000;
}
IEnumerator FadeTo(bool fadeIn, float duration)
{
//MyRenderer.material.
float counter = 0f;
//Set Values depending on if fadeIn or fadeOut
float a, b;
if (fadeIn)
{
a = 0;
b = 1;
}
else
{
a = 1;
b = 0;
}
//Enable MyRenderer component
if (!MyRenderer.enabled)
MyRenderer.enabled = true;
//Get original Mesh Color
Color meshColor = MyRenderer.material.color;
//Do the actual fading
while (counter < duration)
{
counter += Time.deltaTime;
float alpha = Mathf.Lerp(a, b, counter / duration);
Debug.Log(alpha);
MyRenderer.material.color = new Color(meshColor.r, meshColor.g, meshColor.b, alpha);
yield return null;
}
if (!fadeIn)
{
//Disable Mesh Renderer
MyRenderer.enabled = false;
}
fading = false; //So that we can call this function next time
}
void Start()
{
//Fade(true, 3f); //Fade In
Fade(false, 3f);//Fade Out
}
The game character will lose hunger when he moved. I tried to use Mathf.Clamp to limit the number when the player consumed food which will add 20 hunger to the HungerClock so it doesn't go more than 100. The problem I have is the number in HUD will not display the number that's more than a hundred but the system will stored the number that goes up to 100. So when the number is below 100 the hud will start updating the number.
This is part of my code:
private float HungerClock;
private float ThirstClock;
private float MaxHunger = 100.0f;
private float MinHunger = 0.0f;
private float HungerStatus;
void Start ()
{
thePlayer = GetComponent<Player> ();
health = thePlayer.ReturnHealth ();
player = GameObject.Find("Player");
HungerClock = thePlayer.ReturnHunger();
}
void Update () {
HungerStatus = Mathf.Clamp(HungerStatus, MinHunger, MaxHunger);
HungerClock = Mathf.Clamp(HungerClock, MinHunger, MaxHunger);
PlayerHUD.updateHunger(HungerStatus);
PlayerHUD.updateThirst(ThirstStatus);
if(sceneName == ("Bunker"))
{
HungerStatus = 100.0f;
ThirstStatus = 100.0f;
}
else
{
//Hunger and thirst counter-----------------------------
HungerStatus = HungerClock - distanceTravelled * 0.5f;
ThirstStatus = ThirstClock - distanceTravelled * 0.5f;
distanceTravelled += Vector3.Distance(transform.position, lastPosition);
lastPosition = transform.position;
//------------------------------------------------------
}
}
public void ChangeHunger(float change)
{
HungerClock += change;
// I tried to put Mathf.Clamp here but it did not work either.
}
HUD Script
public void updateHunger (float Hunger)
{
//Hunger = Player.ReturnHunger();
hungerTextMesh.text = hungerText + Hunger;
It is going over 100 because you told it to.
You have written:
clamp the value
change the value
display in HUD
change the value again
So ... now the value in HUD and the value in the game are different.. And the HUD will never go above 100 because you clamped it (step 1), but the game will go above 100 because you changed it without clamping it (step 4)
I think the other responders tackled this with more thought. And #Adam above actually mentioned it, but to elaborate on his solution, try this:
void Update () {
if(sceneName == ("Bunker"))
{
HungerStatus = 100.0f;
ThirstStatus = 100.0f;
}
else
{
//Hunger and thirst counter-----------------------------
HungerStatus = HungerClock - distanceTravelled * 0.5f;
ThirstStatus = ThirstClock - distanceTravelled * 0.5f;
distanceTravelled += Vector3.Distance(transform.position, lastPosition);
lastPosition = transform.position;
//------------------------------------------------------
}
HungerStatus = Mathf.Clamp(HungerStatus, MinHunger, MaxHunger);
HungerClock = Mathf.Clamp(HungerClock, MinHunger, MaxHunger);
PlayerHUD.updateHunger(HungerStatus);
PlayerHUD.updateThirst(ThirstStatus);
}
Notice the change in order of statements. We are now using the Mathf.Clamp() after all the other processing.
I hope that helps!