I don't know how to get a certainly value from the opacity of my gameobject. I wanna do a laser, so I did an animation of my laser going from 0 to 230, and then back to 0. I want it to make damage only if it's in 230 of opacity but I don't know how ;(. Can somebody help me? this is a part of my animation
Color internally is stored as float values 0 to 1.
So assuming you have access to your object it would probably be something like e.g.
public class DamageController : MonoBehaviour
{
// Here you drag in the according object
[SerializeField] private Renderer renderer;
// Due to imprecision I would go one lower just to be sure
private float targetAlpha = 229f/255f;
private void Update ()
{
if(renderer.material.color.a >= targetAlpha)
{
// However you deal damage
DealDamage (amountPerSecond * Time.deltaTime);
}
}
}
However, checking the alpha isn't actually necessary!
You have an animation anyway so you can also simply have the component reduced like
public class DamageController : MonoBehaviour
{
private void Update ()
{
// However you deal damage
DealDamage (amountPerSecond * Time.deltaTime);
}
}
And now simply additionally animate the enabled state of this component!
Make it disabled by default and only enable it within the animation during the desired period.
Related
I want to add doors to my Unity project and so far I have gotten the code to work with my door but I can open it from any distance in the scene. I want to be able to only open it from a small distance away but I cannot figure out how.
public class Door : MonoBehaviour
{
public bool doorIsOpen;
public Transform closedPos;
public Transform openPos;
public float openSpeed;
public float openRadius;
void Update()
{
if(Physics.CheckSphere(gameObject.transform.position, openRadius))
{
if(Input.GetKeyDown(KeyCode.E))
{
doorIsOpen = !doorIsOpen;
}
}
if(doorIsOpen == true)
{
OpenDoor();
}
if(doorIsOpen == false)
{
CloseDoor();
}
}
void OpenDoor()
{
doorIsOpen = true;
gameObject.transform.position = openPos.position;
gameObject.GetComponent<Animator>().SetTrigger("OpenDoor");
gameObject.GetComponent<Animator>().SetBool("DoorIsClosed", false);
gameObject.GetComponent<Animator>().SetBool("DoorIsOpen", true);
}
void CloseDoor()
{
doorIsOpen = false;
gameObject.transform.position = closedPos.position;
gameObject.GetComponent<Animator>().SetTrigger("CloseDoor");
gameObject.GetComponent<Animator>().SetBool("DoorIsOpen", false);
gameObject.GetComponent<Animator>().SetBool("DoorIsClosed", true);
}
}
Prerequisites
Add a sphere with a collision body around the door instance in Unity. This sphere functions as the radius which will trigger the ChangeDoorState method.
Change the update method
The update will look at any collisions happening in the specified sphere. If there is at least one object in range (the collision sphere) of the door, then it opens or closes the door instance. Source: https://docs.unity3d.com/ScriptReference/Physics.OverlapSphere.html
void Update()
{
Collider[] hitColliders = Physics.OverlapSphere(center, radius);
if (hitColliders.Length > 0)
{
ChangeDoorState();
}
}
Merged the OpenDoor and CloseDoor methods
void ChangeDoorState()
{
doorClosureState = doorClosure ? true : false;
gameObject.transform.position = doorClosureState ? closedPos.position : openPos.postition;
gameObject.GetComponent<Animator>().SetTrigger("DoorClosure");
gameObject.GetComponent<Animator>().SetBool("DoorIsOpen", doorClosureState);
gameObject.GetComponent<Animator>().SetBool("DoorIsClosed", !doorClosureState);
}
You can increase the value of 'openRadius', the game is creating a sphere at gameObject.transform.position with a radius of 'openRadius' and checking if there is any colliders overlapping the sphere.
//...
if(Physics.CheckSphere(gameObject.transform.position, openRadius))
//...
One issue is that you permanently set the triggers in every frame. You would only want to do so when you hit the key.
Then also Physics.CheckSphere checks whether there is any collider within the range. This could be any collider, not only the player. To make sure it only works if the player is the one in range you should definitely use Layers and give the player its own layer e.g. "Player". Then you can pass the layerMask parameter to make sure the doors only check for the player's layer.
so I would simply use
// if possible already reference this in the Inspector
[SerializeField] private Animator _animator;
[Tooltip("Here select only the Layer(s) which you assigned to your Player object(s)")]
[SerializeField] LayerMask _playerLayer;
private void Awake()
{
// as fallback get it ONCE on runtime
if(!_animator) _animator = GetComponent<Animator>();
}
private void Update()
{
// Afaik the Key is way cheaper to check so do it first
// and use physics only if actually necessary
if(Input.GetKeyDown(KeyCode.E))
{
// Only check for the Player's Layer and ignore other colliders
if(Physics.CheckSphere(transform.position, openRadius, _playerLayer.value))
{
ToggleDoorState();
}
}
}
// Since manually changing the flag in the Inspector will not have
// any effect anymore in order to be able to test it you can use
// the context menu of this component in the Inspector
[ContextMenu(nameof(ToggleDoorState))]
private void ToggleDoorState()
{
// invert the flag
doorIsOpen = !doorIsOpen;
// use the flag in ternary expressions
transform.position = doorIsOpen ? openPos.position : closedPos.position;
_animator.SetTrigger(doorIsOpen ? "OpenDoor" : "CloseDoor");
// use the value of the flag diectly
_animator.SetBool("DoorIsClosed", !doorIsOpen);
_animator.SetBool("DoorIsOpen", doorIsOpen);
}
It is seems a bit though as if you have a bit of redundancy in your animator. I would either use the Bools in order to trigger a transition or use the Triggers, to have both seems odd.
I'm trying to make the game Snake, and I'm trying to get the apple functionality working. What this script is meant to do, is whenever my Snake goes over the Apple, the apple disappears and reappears at a random location on the screen. But instead it does nothing, any idea as to why?
P.S: Camera is Size 10 and Aspect Ratio is 16:9 which is why I have some weird Random.Range values. Also I used Debug.Log in Update to make sure that the variable worked, and yes it does, whenever my snake moves its coordinates are displayed.
public class Apple_RandomSpawn : MonoBehaviour
{
private Vector2 foodPos;
private Snake_Move snakeMove;
void Start()
{
SpawnApple();
}
public void Update()
{
transform.position = new Vector2(foodPos.x, foodPos.y);
SnakeAte();
}
public void SpawnApple()
{
foodPos = new Vector2(Random.Range(-17, 17), Random.Range(-9, 9));
}
public void SnakeAte()
{
if (Mathf.Approximately(foodPos.x, snakeMove.pos.x) && Mathf.Approximately(foodPos.y, snakeMove.pos.y))
{
SpawnApple();
}
}
}
First of all, it does not directly have something to do with your problem, but DO NOT put GetComponent() or GameObject.Find() in the Update() function. These two, especially GameObject.Find() function is super heavy, so It's recommended that you should call these kinds of function inside Start() or Awake(), or at the initiallization of a class. It can directly, and heavily impact the performance of your game, so here's my suggestion:
[SerializeField]
private Snake_Move snakeMove;
And drag your gameobject (which Snake_Head component is attatched) via Inspector. You should always consider this way first, rather than using GameObject.Find() and GetComponent().
Second, Float is not recommend to compare equality directly through =, since there must be rounding error. There is a help function with regard to comparing two float value in Unity, like Mathf.Approximately(float a, float b). Directly comparing two float value via = would almost always not work as you might think.
Third, It doesn't seem to be that there are no Instantiate() function in your code, but you are try to use one apple object, and every time you consume it, just change it's position. Then why does Object.Destroy(gameObject) exists? What you're doing is, just destroying the apple when you get it first time. I think you have to remove the Destroy() function, and SpawnApple() changes the target coordinate of apple, and the position will be updated in Update() function.
And it's no need to indirectly set the target position, and update to it in Update() function. You can directly set the position of apple, like:
// assuming you followed my suggestio aboven about snakeMove.
public void Update()
{
SnakeAte();
Debug.Log(snakeMove.pos);
}
public void SpawnApple()
{
transform.position = new Vector2(Random.Range(-17, 17), Random.Range(-9, 9));
}
public void SnakeAte()
{
if (foodPos == snakeMove.pos)
{
SpawnApple();
}
}
First of all your null ref in your last comment comes from:
private Snake_Move snakeMove;
Its a private variable and its never assigned. You either need to make it public/[SerialistField] and assign it in inspect or have some sort of initialize function that give it a value.
For the hit detection Mathf.Approximately is good if you wan to check if 2 floats are exactly the same. If you'r checking 2 positions of moving objects the chance of them being exactly the same is very low and may rely on frame rate and such. Keeping your implementation you can check instead for a minimum distance between the 2 positions. You can tweak DISTANCE_THRESHOLD for a value that suits your better.
public class Apple_RandomSpawn : MonoBehaviour
{
private const float DISTANCE_THRESHOLD = 0.1f;
private Vector2 foodPos;
private Snake_Move snakeMove;
void Start()
{
SpawnApple();
}
public void Update()
{
SnakeAte();
}
public void SpawnApple()
{
foodPos = new Vector2(Random.Range(-17, 17), Random.Range(-9, 9));
transform.position = new Vector2(foodPos.x, foodPos.y);
}
public void SnakeAte()
{
if (Vector3.Distance(foodPos, snakeMove) < DISTANCE_THRESHOLD)
{
SpawnApple();
}
}
}
Now remember that since your teleporting the apple to a purely random location it might teleport right back on top of your snake :).
I need little help with function WorldToScreenPoint(position), Could somebody guide me little bit ? I am using this function to display name of the city:
public class LabelsTest : MonoBehaviour
{
[SerializeField]
private Text nameLabel;
// Update is called once per frame
void Update()
{
Vector3 cameraPos = Camera.main.WorldToScreenPoint(transform.position);
nameLabel.transform.position = cameraPos;
}
}
But problem is that I see UI with text two times, one above the plane which is perfect:
but when I face away from the plane, I can see the label there, too.:
I don't know if I am doing something wrong or it just not working as it should.
Thanks for help.
You need to prevent the Text from rendering while it is behind the camera. Luckily, WorldToScreenPoint gives you a z component that tells you how far in front of the camera the point is. So, just set the Text to be enabled when z>0 and disabled when z<=0:
public class LabelsTest : MonoBehaviour
{
[SerializeField]
private Text nameLabel;
// Update is called once per frame
void Update()
{
Vector3 cameraPos = Camera.main.WorldToScreenPoint(transform.position);
nameLabel.transform.position = cameraPos;
nameLabel.enabled = cameraPos.z>0;
}
}
That's because your 'Plane' is culling the backface, meaning when the Plane is up at a certain point higher than the Camera's Y position it goes invisible.
Here's a GIF of the problem.
https://gyazo.com/d22c51951d1e8abd07a354af7f48ffef
Is there some way to have dynamic skybox? I want to make day night cycle but I need to change skyboxes - ideally with fading out and in animation
Skyboxes are really just materials. You can see its properties in the inspector by going to Window > Lighting > Settings and clicking the Material.
You can modify these properties with the SetFloat function. For example, if I can use the following script to pulse the exposure:
using UnityEngine;
public class SkyboxPulse : MonoBehaviour
{
public float pulseRate = 0.2f;
void Update()
{
float exposure = RenderSettings.skybox.GetFloat("_Exposure");
if (exposure < 0.1 || exposure > 1.9)
pulseRate = -pulseRate;
RenderSettings.skybox.SetFloat("_Exposure", exposure + pulseRate * Time.deltaTime);
print(RenderSettings.skybox.GetFloat("_Exposure"));
}
}
This script accesses and modifies the variables set in the shader "Skybox/Procedural". Warning: Whenever you terminate the skybox, it will remain the same exposure--it won't reset like other objects do after play mode.
What breaks is the scrubbing slider, it recognises a click, but it does not move. I have interactable enable.
I'm having to reimport all assets to get it to work again but even now this isn't a sure bet it'll fix it.
This code was working perfectly fine about a week ago, I've made little changes to it since.
A lot of it is commented so I can learn that different parts of the code. Feel free to delete it if it bothers you.
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class NewAnimController : MonoBehaviour {
public Slider scrubSlider; //This is the slider that controls the current position of the animation. Values have to be 0 to 1 for normalized time of the animation, called later in the script
public Slider speedSlider; //This is the slider that controls the playback speed of the animation
public Animator animator; //Animator that is attached to the model that we are looking to control the animation of
public float playbackSpeedAdjustment = 0.5f; //This is a variable that can be easily adjusted to change the total playback speed of the animation. If it's too fast make smaller and vice versa
public Text currentDateText;
public int monthsInProject;
private float rememberTheSpeedBecauseWeMightNeedIt; //Float needed to keep the speed of the animation between pausing and playing
public void Update()
{
float animationTime = animator.GetCurrentAnimatorStateInfo(0).normalizedTime; //float a new value animationTime that is equal to the animators animation state then converted to normalized time
//Debug.Log("animationTime (normalized) is " + animationTime); //logging the normalized time to be able to store it for the scrubbing slider. Doesn't need to be logged for this to work, good to log it to make sure that it's working
scrubSlider.value = animationTime; //making the slider value equal to the now logged normalized time of the animation state
}
public void ScrubSliderChanged(float ScrubSliderchangedValue) // this value has to be floated so that the scrubbing slider can be attached to in the inspector to be able to change the current frame of the animation
{
animator.Play("Take 001", -1, scrubSlider.normalizedValue);
}
public void SpeedSliderChanged(float SpeedSliderchangedValue) //value is floated to be able to change the speed of the animation playback
{
animator.speed = speedSlider.normalizedValue * playbackSpeedAdjustment; // here the speed is multiplied by the adjustment value set in the editor just in case the playback speed needs to be changed outside of normalized values
}
public void UserClickedPauseButton()
{
if (animator.speed > 0f)
{
// we need to pause animator.speed
rememberTheSpeedBecauseWeMightNeedIt = animator.speed;
animator.speed = 0f;
}
else
{
// we need to "unpause"
animator.speed = rememberTheSpeedBecauseWeMightNeedIt;
}
}
public void UserClickedBackButton()
{
scrubSlider.value = scrubSlider.value - (1f / monthsInProject);
}
public void UserClickedForwardButton()
{
scrubSlider.value = scrubSlider.value + (1f / monthsInProject);
}
public void UserClickedStartButton()
{
scrubSlider.value = 0;
}
public void UserClickedEndButton()
{
scrubSlider.value = 1;
}
}
Many thanks for all your help.
I'm pretty sure the problem is this. In Update you are doing something like this:
scrubSlider.value = animationTime
that means that every frame, "NO MATTER WHAT", YOU ARE SETTING THE SLIDER POSITION, to, where the animation is. If the user is trying to move the slider - you are moving it right back, that same frame!
It's not so easy to solve this problem. Unity did not include a trivial built-in function for this. You need a separate script which sits on the slider which determines whether or not the handle is being held down. You have to use the OnPointerDown and OnPointerUp handlers.
How to use pointer handlers in modern Unity:
Step one, make this small script called Teste.cs
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections;
public class Teste:MonoBehaviour, IPointerDownHandler, IPointerUpHandler
{
public Slider theSlider;
public bool sliderIsBeingHeldDown;
public void OnPointerDown(PointerEventData data)
{
sliderIsBeingHeldDown = true;
Debug.Log("holding down!");
}
public void OnPointerUp(PointerEventData data)
{
sliderIsBeingHeldDown = false;
Debug.Log("holding up!");
}
}
Don't forget the declaration...
Teste:MonoBehaviour, IPointerDownHandler, IPointerUpHandler
rather than the usual MonoBehavior.
Drag that script on to your UI.Slider.
Note that in general these only work when they are "on" the thing in question.
Run, and try clicking up and down on the slider button - check the console. It works right? Now you have to use that variable inside NewAnimController
(1) Add a public variable to NewAnimController,
public Teste teste;
(2) Be sure to drag to connect that variable in Inspector
(3) Now you can refer to teste.sliderIsBeingHeldDown to see if that slider is being held down. So instead of doing this every frame...
scrubSlider.value = animationTime;
you will have to do this every frame...
if (teste.sliderIsBeingHeldDown)
Debug.Log("don't try to work against the user!");
else
scrubSlider.value = animationTime;
I hope that works! I think that's the simplest way to know when the slider is being held-down.
You really chose a difficult project for your first project! Hell!
It's worth noting that you could also put the slider-adjusting code (I mean to say your actual code for the scrubber) "inside" the script that is actually on the slider -- but I wouldn't worry about that for now. Your solution as stands is good.
Note depending on how you implement it, you may need to make it pause more elegantly when you hold down the slider handle, but before moving the slider handle. To achieve this, one approach is arrange to call UserClickedPauseButton() or a similar concept, when, the Down/Up routines shown here in "Teste", are called. Conversely you could just not bother with ScrubSliderChanged, and instead do the whole job inside code that runs whenever the handle is "down".
(In general, you'd almost certainly use UnityEvent here to make a solid reusable solution for a slider such as this for use with something like a scrubber.)