How do I change the pitch of audio when slowing down time? - unity3d

I've been making a game using Unity and I added a slow motion function. Afterwards, when I added audio, I wanted to change the pitch of all audio whenever the slow motion function ocurred, but I can't seem to do it. I've been using Brackey's audio tutorial (here if you wanna see it) to guide me into using audio in Unity
Here is my audio manager:
using UnityEngine.Audio;
using System;
public class soundManager : MonoBehaviour
{
public Sound[] sounds;
void Awake()
{
foreach(Sound s in sounds)
{
s.source = gameObject.AddComponent<AudioSource>();
s.source.clip = s.clip;
s.source.volume = s.volume;
s.source.pitch = s.pitch;
}
}
public void Play(string name)
{
Sound s = Array.Find(sounds, sound => sound.name == name);
s.source.Play();
}
}
here is my slow motion script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TimeManager : MonoBehaviour
{
public float slowdownFactor = 0.05f;
public float slowdownLength = 2.0f;
private void Update()
{
Time.timeScale += (1f / slowdownLength) * Time.unscaledDeltaTime;
Time.timeScale = Mathf.Clamp(Time.timeScale, 0f, 1f);
}
public void DoSlowMotion()
{
Time.timeScale = slowdownFactor;
Time.fixedDeltaTime = Time.timeScale * 0.02f;
}
}

I wanted to change the pitch of all audio
If you want to change the pitch of any song at runtime you can simply use the source of type AudioSource that is saved in the sound class and edit it's values directly.
If you then do this as a foreach loop, in your soundManager class, with each song in your array, you can pitch down all of them.
Change All Pitch Values:
public void ChangeAllPitchValues(float pitchDifference) {
// Loop through our whole sound array.
foreach (Sound s in sounds) {
// Adjust pitch value equal to the given difference.
s.source.pitch += pitchDifference;
}
}
Additionally it seems like you are missing the singleton pattern that Brackeys used. You should probably add that as well to easily call the soundManager from any other script including your TimeManager.
#region Singelton
public static soundManager instance;
private void Awake() {
// Check if instance is already defined
// and if this gameObject is not the current instance.
if (instance != null) {
Debug.LogWarning("Multiple instances found. Current instance was destroyed.");
Destroy (gameObject);
return;
}
instance = this;
DontDestroyOnLoad(gameObject);
foreach (Sound s in sounds) {
s.source = gameObject.AddComponent<AudioSource>();
s.source.clip = s.clip;
s.source.volume = s.volume;
s.source.pitch = s.pitch;
s.source.loop = s.loop;
}
}
#endregion
Once you've added the singleton pattern and the new function in the soundManager, then you can simply call that function from your DoSlowMotion() method.
Call the Method:
public void DoSlowMotion() {
...
// Pitch all songs down to 0.95f instead of 1f.
soundManager.instance.ChangeAllPitchValues(-slowdownFactor);
}

Related

Change Bullets as power

I am doing a 2d game and I want my player to be able to change bullets when he collides with an object(power) and destroy that object. I have a script and I was thinking that I need to implement 2 variables prefab ON/Off but now thinking much more I want to change with the help of a tag ( My player has in his script a public Rigidbody2D bullet) and this function
void Fire()
{
if (photonView.IsMine)
{
var firedBullet = Instantiate(bullet, barrel.position, barrel.rotation);
firedBullet.AddForce(barrel.up * bulletSpeed);
}
}
this is the script that I was working on for switching bullets but I think it will not work to change a bullet that I add in the inspector for the Character script , to disable from this script and add other bullet . How I can make it by tag?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class WeaponSwitching : MonoBehaviour
{
[SerializeField] private GameObject pickupEffect;
public GameObject[] DisablePrefab;
public GameObject[] EnablePrefab;
public int selectBullet = 0;
// Start is called before the first frame update
// Update is called once per frame
public void Bullet(Character bullet)
{
var effect = Instantiate(pickupEffect, transform.position, transform.rotation);
foreach (GameObject disable in DisablePrefab)
{
disable.SetActive(false);
}
foreach (GameObject enable in EnablePrefab)
{
enable.SetActive(true);
}
Destroy(gameObject);
Destroy(effect, 3.0f);
}
}
and I try this think with a BulletSwitch script to call the function from Weapon Switching script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BulletSwitch : MonoBehaviour
{
[SerializeField] private Character bullet;
// Start is called before the first frame update
private void Awake()
{
if (!bullet) bullet = GetComponent<Character>();
}
private void OnTriggerEnter2D(Collider2D other)
{
// or whatever tag your powerups have
if (!other.CompareTag("Bullet"))
{
Debug.LogWarning($"Registered a collision but with wrong tag: {other.tag}", this);
return;
}
var Bullet = other.GetComponent<WeaponSwitching>();
if (!Bullet)
{
Debug.LogError($"Object {other.name} is tagged PowerUp but has no PowerUp component attached", this);
return;
}
Debug.Log("Found powerup, pick it up!", this);
Bullet.Bullet(bullet);
}
}
inspector character
after my player collides with an object the bullets disappear.
You could make an array of prefabs and change to the correct bullet type when you hit a powerup by setting a CurrentBulletType variable to one of the types from the array. So when you hit powerup X, change bullet type to one of the types from the array.
public GameObject currentPrefab;
public GameObject[] bulletPrefabs;
private void OnTriggerEnter2D(Collider2D other)
{
string tagString = other.tag;
bool foundObject = true;
switch (tagString)
{
case "standard bullet":
currentPrefab = bulletPrefabs[0];
break;
case "fancy bullet":
currentPrefab = bulletPrefabs[1];
break;
case "even fancier bullet":
currentPrefab = bulletPrefabs[2];
break;
default:
foundObject = false;
break;
}
if (foundObject) other.gameObject.SetActive(false);
}
You can instead use a dictionary to directly reference the correct prefab by string name (tag) to make it easy to add more bullet types.
//dictionairy to reference bullet types quickly
public Dictionary<string, BulletTypes> bulletLibrary = new Dictionary<string, BulletTypes>();
//set these in the inspector
public BulletTypes[] bulletTypes;
[System.Serializable]
public struct BulletTypes
{
public string bulletName;
public GameObject prefab;
//public int bulletPower; //more data if you wish
}
private void Start()
{
//fill the dictionary based on the filled bullet type array in inspector
for (int i = 0; i < bulletTypes.Length; i++)
{
if (bulletTypes[i].bulletName != "")
bulletLibrary.Add(bulletTypes[i].bulletName, bulletTypes[i]);
else
print("bullet added, but name empty");
}
}
private void OnTriggerEnter2D(Collider2D other)
{
string tagString = other.tag;
//if the collided tag is in the dictionary, we can reference the bullet type from that dictionairy
if (bulletLibrary.ContainsKey(tagString))
{
print("spawn: " + bulletLibrary[tagString].bulletName);
print("prefab: " + bulletLibrary[tagString].prefab);
//all the data you need is in: bulletLibrary[tagString];
//Bullet.Bullet(bulletLibrary[tagString]);
other.gameObject.SetActive(false);
}
}
Sidenote: If you spawn a lot of bullets, you can use pooling. SimplePool is nice and easy for this. Instead of calling: instantiate and destroy, you call: SimplePool.Spawn() and SimplePool.DeSpawn().
If you have any more questions on this feel free to ask ;)

Using a timer in conjunction with 2 push buttons from arduino

So I am using two push buttons (connected to an Arduino Uno) as an input to my game. The player has to push down both buttons at the same time for the character to move in the game. I want the player to hold down the buttons for a different amount of time in each level. I have a working Arduino and a working Unity timer and player script, but am not able to get the code to do what I want. What I basically want is that only when the player presses the buttons down, does the timer start counting down. Right now, the timer starts as soon as the scene begins. I know that I somehow have to reference the timer script to the button object, I have tried this but it still doesn't work. Note that the timer UI does have a Timer tag on it. I have also referenced the Player Controller script in the Timer script. Right now, Its giving me a range of errors. I have attached an image depicting these errors.error image
The Timer script:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class Timer : MonoBehaviour
{
//int startTime = 0;
public bool buttonPressed = false;
public int timeLeft;
public Text countdownText;
GameObject Character;
void Awake()
{
Character = GameObject.FindWithTag("Player");
}
public void Start()
{
//StartCoroutine("LoseTime");
BeginTimer();
}
void Update()
{
countdownText.text = ("Time Left = " + timeLeft);
if (timeLeft <= 0)
{
//StopCoroutine("LoseTime");
//countdownText.text = "Times Up!";
Invoke("ChangeLevel", 0.1f);
}
}
public void BeginTimer()
{
Character.GetComponent<PlayerController>().Update();
//gameObject.GetComponent<MyScript2>().MyFunction();
if (buttonPressed == true )
{
StartCoroutine("LoseTime");
}
else if (buttonPressed == false)
{
StopCoroutine("LoseTime");
}
}
IEnumerator LoseTime()
{
while (true)
{
yield return new WaitForSeconds(1);
timeLeft--;
}
}
void ChangeLevel()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
}
The Player Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO.Ports;
public class PlayerController : MonoBehaviour
{
SerialPort sp = new SerialPort("\\\\.\\COM4", 9600);
//player == GameObject.FindWithTag("Player").GetComponent<>();
public float Speed;
public Vector2 height;
public float xMin, xMax, yMin, yMax;
public bool buttonPressed = false;
GameObject Character;
public void Awake()
{
Character = GameObject.FindWithTag("Player");
}
public void Start()
{
if (!sp.IsOpen)
{ // If the erial port is not open
sp.Open(); // Open
}
sp.ReadTimeout = 1; // Timeout for reading
}
public void Update()
{
if (sp.IsOpen)
{ // Check to see if the serial port is open
try
{
string value = sp.ReadLine();//To("Button"); //Read the information
int button = int.Parse(value);
//float amount = float.Parse(value);
//transform.Translate(Speed * Time.deltaTime, 0f, 0f); //walk
if (button == 0) //*Input.GetKeyDown(KeyCode.Space*/) //jump
{
buttonPressed = true;
Character.GetComponent<Rigidbody2D>().AddForce(height, ForceMode2D.Impulse);
Character.GetComponent<Rigidbody2D>().position = new Vector3
(
Mathf.Clamp(GetComponent<Rigidbody2D>().position.x, xMin, xMax),
Mathf.Clamp(GetComponent<Rigidbody2D>().position.y, yMin, yMax)
);
Timer tmr = GameObject.Find("Timer").GetComponent<Timer>();
tmr.BeginTimer();
}
}
catch (System.Exception)
{
}
}
void ApplicationQuit()
{
if (sp != null)
{
{
sp.Close();
}
}
}
}
}
I think the problem may be with how I am referencing the scripts in each other.
In your timer you have a quite strange mixup of Update and Coroutine. Also note that BeginTimer is called exactly once! You also shouldn't "manually" call Update of another component.
I wouldn't use Update at all here. Simply start and stop a Coroutine.
The Timer script should only do the countdown. It doesn't have to know more:
public class Timer : MonoBehaviour
{
public int timeLeft;
public Text countdownText;
private bool timerStarted;
public void BeginTimer(int seconds)
{
// Here you have to decide whether you want to restart a timer
timeLeft = seconds;
// or if you rather want to continue counting down
//if(!timerStarted) timeLeft = seconds;
StartCoroutine(LoseTime());
}
public void StopTimer()
{
StopAllCoroutines();
}
private IEnumerator LoseTime()
{
timerStarted = true;
while (timeLeft > 0)
{
yield return new WaitForSeconds(1);
timeLeft --;
countdownText.text = $"Time Left = {timeLeft}";
}
// Only reached after the timer finished and wasn't interrupted meanwhile
// Using Invoke here is a very good idea since we don't want to interrupt anymore
// if the user lets go of the button(s) now
Invoke(nameof(ChangeLevel), 0.1f);
}
void ChangeLevel()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
}
In general avoid to use Find at all. If anyhow possible already reference things in the Inspector! If needed you can use Find but only once! What you never want to do is use any of the Find and GetComponent variants repeatedly - rather store the reference the first time and re-use it - and especially not in Update no a per frame basis. They are very expensive calls!
public class PlayerController : MonoBehaviour
{
public float Speed;
public Vector2 height;
// I prefer to use Vector2 for such things
public Vector2 Min;
public Vector2 Max;
public bool buttonPressed = false;
// Already reference these via the Inspector if possible!
public Rigidbody2D Character;
public Timer timer;
public Rigidbody2D _rigidbody;
private SerialPort sp = new SerialPort("\\\\.\\COM4", 9600);
private void Awake()
{
FetchReferences();
}
// This is my "secret" tip for you! Go to the component in the Inspector
// open the ContextMenu and hit FetchReferences
// This already stores the references in the according fields ;)
[ContextMenu("FetchReferences")]
private void FetchReferences()
{
if(!Character)Character = GameObject.FindWithTag("Player"). GetComponent<Rigidbody2D>();
if(!timer) timer = GameObject.Find("Timer").GetComponent<Timer>();
}
private void Start()
{
if (!sp.IsOpen)
{
sp.Open(); // Open
}
sp.ReadTimeout = 1;
}
private void Update()
{
// I wouldn't do the serialport open check here
// your if block simply silently hides the fact that your application
// doesn't work correctly! Rather throw an error!
try
{
string value = sp.ReadLine(); //Read the information
int button = int.Parse(value);
//TODO: Since it isn't clear in your question how you get TWO buttons
//TODO: You will have to change this condition in order to only fire if both
//TODO: buttons are currently pressed!
buttonPressed = button == 0;
if (buttonPressed)
{
Character.AddForce(height, ForceMode2D.Impulse);
// The clamping of a rigidbody should always be done ine FixedUpdate!
// Pass in how many seconds as parameter or make the method
// parameterless and configure a fixed duration via the Inspector of the Timer
timer.BeginTimer(3.0f);
}
else
{
// Maybe stop the timer if condition is not fulfilled ?
timer.StopTimer();
}
}
catch (System.Exception)
{
// You should do something here! At least a Log ...
}
}
private void FixedUpdate()
{
// Here I wasn't sure: Are there actually two different
// Rigidbody2D involved? I would assume you rather wanted to use the Character rigidbody again!
Character.position = new Vector3(Mathf.Clamp(Character.position.x, Min.x, Max.x), Mathf.Clamp(Character.position.y, Min.y, Max.y));
}
// Did you mean OnApplicationQuit here?
private void ApplicationQuit()
{
if (sp != null)
{
{
sp.Close();
}
}
}
}
Typed on smartphone but I hope the idea gets clear

Control object rotation by mouse click in unity

I need help with my project in unity
I want to stop each object by clicking on it.
What I did so far:
all my objects rotate but when I click anywhere they all stop, I need them to stop only if I click on each one.
This is my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EarthScript : MonoBehaviour
{
public bool rotateObject = true;
public GameObject MyCenter;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
if(rotateObject == true)
{
rotateObject = false;
}
else
{
rotateObject = true;
}
}
if(rotateObject == true)
{
Vector3 axisofRotation = new Vector3(0,1,0);
transform.RotateAround(MyCenter.transform.position,axisofRotation, 30*Time.deltaTime);
transform.Rotate(0,Time.deltaTime*30,0,Space.Self);
}
}
}
Theres two good ways to achieve this. Both ways require you to have a collider attatched to your object.
One is to raycast from the camera, through the cursor, into the scene, to check which object is currently under the cursor.
The second way is using unity's EventSystem. You will need to attach a PhysicsRaycaster on your camera, but then you get callbacks from the event system which simplifies detection (it is handled by Unity so there is less to write)
using UnityEngine;
using UnityEngine.EventSystems;
public class myClass: MonoBehaviour, IPointerClickHandler
{
public GameObject MyCenter;
public void OnPointerClick (PointerEventData e)
{
rotateObject=!rotateObject;
}
void Update()
{
if(rotateObject == true)
{
Vector3 axisofRotation = new Vector3(0,1,0);
transform.RotateAround(MyCenter.transform.position,axisofRotation, 30*Time.deltaTime);
transform.Rotate(0,Time.deltaTime*30,0,Space.Self);
}
}

Unity - NullReferenceException in my GameOverManager

Iam trying to load my "PlayerHealth" Script to my "GameOverManager" to check my currentHealth from the PlayerHealth-Script.
If the current health is "0" - I want to trigger an animation.
The problem is, that Unity gives me an error with following message:
"NullReferenceException: Object reference not set to an instance of an object
GameOverManager.Update () (at Assets/GameOverManager.cs:32)"
Here is the piece of Code of my GameOverManager:
public class GameOverManager : MonoBehaviour {
public PlayerHealth playerHealthScript;
public float restartDelay = 5f;
Animator anim;
float restartTimer;
private void Awake()
{
anim = GetComponent<Animator>();
}
private void Update()
{
playerHealthScript = GetComponent<PlayerHealth>();
if (playerHealthScript.currentHealth <= 0) {
anim.SetTrigger("GamerOver");
restartTimer += Time.deltaTime;
if (restartTimer >= restartDelay) {
SceneManager.LoadScene(2);
}
}
}
}
The error is triggered on the following line:
if (playerHealthScript.currentHealth <= 0)
Here is the hierarchy - FPSController holds "PlayerHealth" - HUDCanvas holds "GameOverManager:
Here are the inspectors:
Here is the code of "PlayerHealth":
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using UnityEngine.SceneManagement;
public class PlayerHealth : MonoBehaviour
{
public int startingHealth = 100; // The amount of health the player starts the game with.
public int currentHealth; // The current health the player has.
public Slider healthSlider; // Reference to the UI's health bar.
public Image damageImage; // Reference to an image to flash on the screen on being hurt.
public AudioClip deathClip; // The audio clip to play when the player dies.
public float flashSpeed = 5f; // The speed the damageImage will fade at.
public Color flashColour = new Color(1f, 0f, 0f, 0.1f); // The colour the damageImage is set to, to flash.
public float restartDelay = 5f;
//Animator anim; // Reference to the Animator component.
public AudioSource playerAudio; // Reference to the AudioSource component.
// PlayerMovement playerMovement; // Reference to the player's movement.
// PlayerShooting playerShooting; // Reference to the PlayerShooting script.
bool isDead; // Whether the player is dead.
bool damaged; // True when the player gets damaged.
void Awake()
{
// Setting up the references.
// anim = GetComponent<Animator>();
// playerAudio = GetComponent<AudioSource>();
// playerMovement = GetComponent<PlayerMovement>();
// playerShooting = GetComponentInChildren<PlayerShooting>();
// Set the initial health of the player.
currentHealth = startingHealth;
}
void Update()
{
// If the player has just been damaged...
if (damaged)
{
// ... set the colour of the damageImage to the flash colour.
damageImage.color = flashColour;
}
// Otherwise...
else
{
// ... transition the colour back to clear.
damageImage.color = Color.Lerp(damageImage.color, Color.clear, flashSpeed * Time.deltaTime);
}
// Reset the damaged flag.
damaged = false;
}
public void TakeDamage(int amount)
{
// Set the damaged flag so the screen will flash.
damaged = true;
// Reduce the current health by the damage amount.
currentHealth -= amount;
playerAudio.Play();
Debug.Log("PLayer Health: " + currentHealth);
// Set the health bar's value to the current health.
healthSlider.value = currentHealth;
// Play the hurt sound
playerAudio.Play();
// If the player has lost all it's health and the death flag hasn't been set yet...
if (currentHealth <= 0 && !isDead)
{
// ... it should die.
Death();
}
}
void Death()
{
// Set the death flag so this function won't be called again.
isDead = true;
Debug.Log("In der Death Funktion");
First of all, you don't need, or better, you SHOULD NOT, use GetComponent inside Update, it's a very slow method and it impacts a lot the performance.
So, change your code to this:
public class GameOverManager : MonoBehaviour {
public PlayerHealth playerHealthScript;
public float restartDelay = 5f;
private Animator anim;
private float restartTimer;
private void Awake() {
anim = GetComponent<Animator>();
//No need for GetComponent<PlayerHealth>() if you assign it in the Inspector
//playerHealthScript = GetComponent<PlayerHealth>();
}
private void Update() {
if (playerHealthScript.currentHealth <= 0) {
anim.SetTrigger("GamerOver");
restartTimer += Time.deltaTime;
if (restartTimer >= restartDelay) {
SceneManager.LoadScene(2);
}
}
}
}
Moreover, your bug happens most probably because in the Inspector you assigned to the playerHealthScript variable the game object containing the PlayerHealth script. But, since you try in Update to get the script component again but this time from the game object that has the GameOverManager script (and I assume it doesn't have the PlayerHealth script), you get the NullReference since that script can't be found on this game object.
So, as you can see from the two lines commented out in my code, you actually don't need to get that component from script, just assign it via Inspector and you're good to go.

Unity Video script not playing sound

Im using a plugin from the unity asset store and trying to get video playing with sound - currently its not. Here is the script that plays the video --
using UnityEngine;
using UnityEngine.Video;
namespace Complete360Tour {
[AddComponentMenu("Complete360Tour/Media/VideoMediaReactor")] public class VideoMediaReactor : MonoBehaviour, IMediaSwitchReactor<VideoMediaNodeData> {
//-----------------------------------------------------------------------------------------
// Inspector Variables:
//-----------------------------------------------------------------------------------------
[Header("Assignment")] [SerializeField] protected MediaView mediaView;
[SerializeField] protected VideoPlayer videoPlayer;
//-----------------------------------------------------------------------------------------
// Private Fields:
//-----------------------------------------------------------------------------------------
private RenderTexture renderTexture;
//-----------------------------------------------------------------------------------------
// Unity Lifecycle:
//-----------------------------------------------------------------------------------------
protected void Awake() {
if (mediaView == null) Debug.LogWarning("No MediaView assigned. Please assign a MediaView.");
}
//-----------------------------------------------------------------------------------------
// Public Methods:
//-----------------------------------------------------------------------------------------
public void SwitchMedia(VideoMediaNodeData data, MediaSwitchStates state) {
if (data == null) {
InvalidSwitchData();
return;
}
switch (state) {
case MediaSwitchStates.BeforeSwitch: break;
case MediaSwitchStates.Switch:
BeginVideo(data.VideoClip);
mediaView.SetStereoscopic(data.IsStereo);
break;
case MediaSwitchStates.AfterSwitch: break;
}
}
public void ExitMedia() { InvalidSwitchData(); }
//-----------------------------------------------------------------------------------------
// Private Methods:
//-----------------------------------------------------------------------------------------
private void InvalidSwitchData() {
videoPlayer.Stop();
videoPlayer.targetTexture = null;
mediaView.SetMedia(null);
mediaView.SetStereoscopic(false);
}
private void BeginVideo(VideoClip videoClip) {
renderTexture = new RenderTexture((int) videoClip.width, (int) videoClip.height, 0);
videoPlayer.clip = videoClip;
videoPlayer.targetTexture = renderTexture;
mediaView.SetMedia(renderTexture);
videoPlayer.Play();
}
}
}
I tried adding the audio stuff following
http://justcode.me/unity2d/how-to-play-videos-on-unity-using-new-videoplayer/
but no luck. Audio still wouldn't play.
In case you are using the Unity Video Player:
Try adding Audio Source Component to the GameObject holding the video player
Drag and drop the Audio source to the Audio Source in Video Player component