I have a shake tweener initilialized like this:
mainCamera.DOShakePosition (100f, 0.05f, shakeVibrato, 90, false, false);
The duration 100 is just put there so I can have some big value. The idea is to change the vibrato/strength of the shake while it is active.
Imagine some vibration source is coming closer to the player. As it approaches, the vibrato increases, as it passes by the vibrato decreases. How can I manipulate these values while tweener is active? I saw some ChangeValues() methods but I'm not sure what they do and the documentation is not very clear about them.
As far as I know, there's no proper way to do this for DOTween's DOShake methods (though I would love to be corrected). One really hacky way to do this is by setting the duration to a low-ish value, and re-initializing the tween with different values in it's OnComplete callback. It's pretty far from ideal, since you're not changing the values during the tween, but rather at an interval - resulting in a stepped or sudden change. Resource-wise, I don't imagine this being very efficient either.
Tweener shakeTween;
TweenCallback shakeTweenComplete;
void Start()
{
shakeTweenComplete = () =>
{
shakeTween = transform.DOShakeRotation(0.1f, strength: shakeStrength, vibrato: shakeVibrato, fadeOut: false).SetRelative();
shakeTween.OnComplete(shakeTweenComplete);
};
// Invoke the callback instead of having duplicated code
shakeTweenComplete.Invoke()
}
I have shakeVibrato modified elsewhere. I also tried to do this with SetLoop and OnStepComplete, with no luck.
EDIT - For future reference, this is my take on how OP ended up solving this issue. I substituted changing the vibrato with Tweener.timeScale, since it ends up looking the same anyway.
public float shakeMultiplier = 1.0f;
public float shakeTimeScale = 1.0f;
// These values won't be changed
public float baseShakeDuration = 1.0f;
public float baseShakeStrength = 0.1f;
public int baseShakeVibrato = 10;
Vector3 shakePosition;
Tweener shakeTween;
void Start()
{
shakeTween = DOTween.Shake(() => shakePosition, x => shakePosition = x, baseShakeDuration, baseShakeStrength, baseShakeVibrato, fadeOut: true)
.SetLoops(-1, LoopType.Restart);
}
void Update()
{
transform.localPosition = shakePosition * shakeMultiplier;
shakeTween.timeScale = shakeTimeScale;
}
Related
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 have this piece of code right here that should make the block object move between the startPos and endPos objects, but something is wrong about it and I don't know what.
void FixedUpdate()
{
if (block.transform.position == startPos.transform.position)
{
check = false;
}
if(block.transform.position == endPos.transform.position)
{
check = true;
}
if (check == false)
{
block.transform.position = Vector3.Lerp(block.transform.position, endPos.transform.position, .03f);
}
if (check == true)
{
block.transform.position = Vector3.Lerp(block.transform.position, startPos.transform.position, .03f);
}
}
At some point the block will reach endPos, and then on its way back to startPos it will stop, because the functions will be executed simultaneously. But how is this possible because my if's right there should not allow this to happen?
In general you should always be using
Update → called every frame
instead of
FixedUpdate → called in certain realtime intervals
except you are dealing with Physics somehow (which doesn't seem to be the case here). Also see the Update and FixedUpdate Tutorial
The issue with Vector3.Lerp is that it doesn't behave as you expect.
I guess you like that it starts fast and then becomes "smooth" ... but actually this might be your problem.
It never reaches the target position really. It just gets closer and closer and slower and slower ...
... until at some moment the == with a precision of 0.00001f eventually becomes true.
So it might seem that it stopped but actually it might still be moving just really really slow.
For the following two alternatives you have to decide a bit what you want to control:
Option: The speed
If you want to have a linear velocity for the object you should rather use
// adjust via the Inspector
[SerializeField] private float moveSpeedInUnityUnitPerSecond = 1f;
// you should use Update here in general
void Update()
{
if (block.transform.position == startPos.transform.position)
{
check = false;
}
// always use else in cases where only on condition can be
// true at the same time anyway
else if(block.transform.position == endPos.transform.position)
{
check = true;
}
block.transform.position = Vector3.MoveTowards(block.transform.position, check ? startPos.transform.position : endPos.transform.position, Time.deltaTime * moveSpeed);
}
Option: The duration
If you rather want a smooth movement but control the duration it takes to reach the target you should use a Lerp but with a factor depending on the time like
// adjust via the Inspector
[SerializeField] private float moveDurationInSeconds = 1f;
private float passedTime;
// you should use Update here in general
void Update()
{
// prevent overshooting
passedTime += Mathf.Min(moveDurationInSeconds - passedTime, Time.deltaTime);
if(passedTime >= moveDurationInSeconds)
{
check = !check;
passedTime = 0;
}
var lerpFactor = passedTime / moveDurationInSeconds;
// and now add ease-in and ease-out
var smoothedLerpFactor = Mathf.SmoothStep(0, 1, lerpFactor);
var fromPosition = check ? endPos.transform.position : startPos.transform.position;
var toPosition = check ? startPos.transform.position : endPos.transform.position;
block.transform.position = Vector3.Lerp(fromPosition, toPosition, smoothedLerpFactor);
}
For this you could also use a Coroutine which usually is a bit easier to interpret and maintain:
// adjust via the Inspector
[SerializeField] private float moveDurationInSeconds = 1f;
// yes you see correctly one can directly use the Start
// as a Coroutine
private IEnumerator Start()
{
var fromPosition = startPos.transform.position;
var toPosition = endPos.transform.position;
// looks strange but as long as you yield somewhere inside
// the loop it simply means repeat the sequence forever
// just like the Update method
while(true)
{
var passedTime = 0f;
while(passedTime < moveDurationInSeconds)
{
var lerpFactor = passedTime / moveDurationInSeconds;
// and now add ease-in and ease-out
var smoothedLerpFactor = Mathf.SmoothStep(0, 1, lerpFactor);
block.transform.position = Vector3.Lerp(fromPosition, toPosition, smoothedLerpFactor);
passedTime += Mathf.Min(moveDurationInSeconds - passedTime, Time.deltaTime);
// reads like: "pause" here, render this frame and continue
// from here in the next frame
yield return null;
}
// once reached flip the positions
var temp = fromPosition;
fromPosition = toPosition;
toPosition = temp;
}
}
in both cases you could still add more flexibility and instead of simply using the moveDurationInSeconds use
var fixedDuration = moveDurationInSeconds * Vector3.Distance(fromPosition, toPosition);
this way the movement takes shorter if the positions are closer together and longer if they are further apart. This comes pretty close to the Lerp you used before regarding to the smoothness of motion but you can control very good how long the movement will take.
The third argument of Vector3.Lerp is a percentage of the distance between the first two arguments. When you pass in 0.03f, you're getting 3% closer to your end position but never actually getting exactly there (You can prove this by logging the block's position and the target's position and you'll see they're never perfectly equal).
Instead of checking if your block's position with the == operator (which works to an accuracy of 0.00001f) to the target position, you could simply check if it's close enough with Vector3.Distance:
if(Vector3.Distance(block.transform.position, endPos.transform.position) < 0.1f) {
You can make your "close enough threshold" (0.1f in the example) a named variable for easy adjustment until it feels right.
For starters, Update() loops every frame. FixedUpdate() depends on the number of frames per second set in the time setting. So, put your code into Void Update() instead.
If you refer to Vector3.MoveTowards documentation, you may find a much better approach to your problem.
The script below should do the trick. The target variable should be set to the position of your end point. Speed is how fast your object should move.
Inside Update(), the step variable is used to determine how far the object should move based off its speed and the time elapsed since it last moved. Finally, the last line changes the position of the object and records the new position.
public Transform target;
public float speed;
void Update() {
float step = speed * Time.deltaTime;
transform.position = Vector3.MoveTowards(transform.position, target.position, step);
}
I am trying to recreate the full range of a guitar only using 6 audio clips.
I was thinking there would be a way to set frequency of an audio clip but audio.frequency only returns the frequency of the audio based on compression format and not the actual tone.
I know I can read GetSpectrumData, but that solution is fairly complex and would require some Fourier Transform analysis or something of the kind.
Affecting the pitch, it is easy to alter the tone so I can go up and down but is there a way to figure out what are the steps to use.
void Update ()
{
CheckAudio(KeyCode.Q, 1.0f);
CheckAudio(KeyCode.W, 1.1f);
CheckAudio(KeyCode.E, 1.2f);
CheckAudio(KeyCode.R, 1.3f);
CheckAudio(KeyCode.T, 1.4f);
}
void CheckAudio(KeyCode key, float pitch)
{
if (Input.GetKeyDown (key))
{
audio.pitch = pitch;
audio.Play ();
}
}
I can hear it does not sound right.
Knowing the initial tone E4 329.63Hz with pitch at 1 is there any equation that affecting the pitch, I would get the next key F4 349.23Hz (or close enough)?
It has to be considered also that Unity AudioSource limits the pitch within -3/3 range (which I think is more than needed).
EDIT: Adding some personal research. It seems pitch 1 is initial note and setting to 2 give the same key one octave higher.
Since a chromatic scale (all black and white notes on the piano) is 12 keys, I assume that using 1/12 for each step should do it.
It sounds close but I fell it is not quite right. Here is the new code:
[SerializeField] private AudioSource audio;
float step = 1f/12f;
KeyCode[]keys = new KeyCode[]{
KeyCode.Q, KeyCode.W,KeyCode.E,KeyCode.R,KeyCode.T,
KeyCode.Y, KeyCode.U, KeyCode.I, KeyCode.O, KeyCode.P,
KeyCode.A, KeyCode.S, KeyCode.D
};
void Update ()
{
float f = 0.0f;
foreach (KeyCode key in keys)
{
CheckAudio(key, f);
f += 1f;
}
}
void CheckAudio(KeyCode key, float pitch)
{
if (Input.GetKeyDown (key))
{
audio.pitch = 1f + pitch * step;
audio.Play ();
}
}
What you are trying to do will not work well by simply changing the pitch of the audio. By changing the pitch, you will run into other problems such as sound finishing too fast or taking more time to finish and the sound will not be good either.
The first solution is to make a plugin(Synthesizer) in C++ that reads the audio file from Unity and change the frequency. It should also perform other actions to fix speed issues. This is very complicated unless you are an audio engineer with some great math skills. And trying this on a mobile device is whole different story. OnAudioFilterRead is a function you should use if you decide to go with this method.
The second and the recommended solution is to make an audio file for each guitar key then put them into array of audioClip. This solves every other problems.The down side is that you will have more files.
EDIT:
If you don't care about it being perfect, you can use something below from this nice guy on the internet.
void playSound(){
float transpose = -4;
float note = -1;
if (Input.GetKeyDown("a")) note = 0; // C
if (Input.GetKeyDown("s")) note = 2; // D
if (Input.GetKeyDown("d")) note = 4; // E
if (Input.GetKeyDown("f")) note = 5; // F
if (Input.GetKeyDown("g")) note = 7; // G
if (Input.GetKeyDown("h")) note = 9; // A
if (Input.GetKeyDown("j")) note = 11; // B
if (Input.GetKeyDown("k")) note = 12; // C
if (Input.GetKeyDown("l")) note = 14; // D
if (note>=0){ // if some key pressed...
audio.pitch = Mathf.Pow(2, (note+transpose)/12.0);
audio.Play();
}
EDIT: For those of you interested in why the Mathf.Pow equation is used and working, read the following: https://en.wikipedia.org/wiki/Twelfth_root_of_two
I tried to make a script that moves an object back and forth between two points. But it just flies in the ifinity. I tried to find the problem whole evening but idk.
here is the code:
using UnityEngine;
public class MovementBetweenPoints : MonoBehaviour {
public Transform[] keyPoints;
public float speed;
private int currentKeyPoint;
// Use this for initialization
void Start ()
{
transform.position = keyPoints[0].position;
currentKeyPoint = 1;
}
// Update is called once per frame
void Update ()
{
if (transform.position == keyPoints[currentKeyPoint].position)
{
currentKeyPoint++;
}
if (currentKeyPoint >= keyPoints.Length)
{
currentKeyPoint = 0;
}
transform.position = Vector3.MoveTowards(transform.position, keyPoints[currentKeyPoint].position, speed * Time.deltaTime);
}
}
Your script works fine as it is. you need to make sure that speed is set to a value greater than 0 in the inspector, and that the keypoints array contains some gameobjects in the inspector too, and you are good to go
I'm sure the problem comes with this part of the code where you check if the position of the object is equal at some waypoint. Instead of:
if (transform.position == keyPoints[currentKeyPoint].position)
{
currentKeyPoint++;
}
try to do something less agressive, and give a bit of margin like:
if (Vector3.Distance(transform.position - keyPoints[currentKeyPoint].position) <= min_Distance)
{
currentKeyPoint++;
}
because it's almost impossible that two objects with different speeds match at the same point. Instead of this, you'll use min_Distance to check it.
I've created a simple GUI progress bar that starts at 0px and expands to 640px. It works great, but appears a little jumpy. I can live with it, but if I can make it appear smoother, that would be ideal.
Any suggestions?
IEnumerator Timer() {
DateTime start = DateTime.Now;
while (true) {
float tick = (float)DateTime.Now.Subtract(start).TotalSeconds;
Debug.Log("TimeInSeconds: " + (int)tick);
rawImageRectTransform.sizeDelta = new Vector2(tick / 60f * 640f, 10f);
if (tick > 60f) {
break;
}
yield return null;
}
}
UPDATE:
As per suggestions, I've tried the following and the effect is identical.
rawImageRectTransform.sizeDelta = Vector2.Lerp(new Vector2(0, 10f), new Vector2(640f, 10f), tick/60f);
I've also tried it this way:
rawImageRectTransform.sizeDelta = Vector2.Lerp(new Vector2(0, 10f), new Vector2(640f, 10f), Time.fixedTime/60f);
And the effect is the same, however the progress bar is no loger sync'd to the DateTime.TotalSeconds (ie. tick) value, so this isn't going to work.
Try using Vector3.lerp() or Vector2.lerp() in a FixedUpdate() it will make sure your requirement gets called fixed number of times per second still it might also work with co-routine without much visual irregulaties.
// for a 3d system
public static Vector3 Lerp(Vector3 from, Vector3 to, float t);
// for a 2d system
public static Vector2 Lerp(Vector2 from, Vector2 to, float t);