I want to use the volume of the audio that the user inputs with his/her microphone in Unity3d in a visual representation. So I'd like to get a value between 0 and 1 that tell how loud the user is. I went looking for a script, but the part that handles the volume doesn't work properly, that part is the method LevelMax(). For some reason micPosiotion never becomes higher than 0. I don't know what Microphone.GetPosition really does except for this:
http://docs.unity3d.com/ScriptReference/Microphone.GetPosition.html
Does anyone know what goes wrong in the method LevelMax()? I am getting no errors or anything. And it finds my microphone properly, and it is working. I tested it.
This is the code:
using UnityEngine;
using System.Collections;
public class MicInput : MonoBehaviour{
public float testSound;
public static float MicLoudness;
private string _device;
private AudioClip _clipRecord = new AudioClip();
private int _sampleWindow = 128;
private bool _isInitialized;
void InitMic()
{
if (_device == null) {
_device = Microphone.devices [0];
_clipRecord = Microphone.Start (_device, true, 999, 44100);
Debug.Log (_clipRecord);
}
}
void StopMicrophone()
{
Microphone.End (_device);
}
float LevelMax()
{
float levelMax = 0;
float[] waveData = new float[_sampleWindow];
int micPosition = Microphone.GetPosition (null) - (_sampleWindow + 1);
if (micPosition < 0) {
return 0;
}
_clipRecord.GetData (waveData, micPosition);
for (int i = 0; i < _sampleWindow; ++i) {
float wavePeak = waveData [i] * waveData [i];
if (levelMax < wavePeak) {
levelMax = wavePeak;
}
}
return levelMax;
}
void Update()
{
MicLoudness = LevelMax ();
testSound = MicLoudness;
}
void OnEnable()
{
InitMic ();
_isInitialized = true;
}
void OnDisable()
{
StopMicrophone ();
}
void OnDestory()
{
StopMicrophone ();
}
void OnApplicationFocus(bool focus)
{
if (focus) {
if (!_isInitialized) {
InitMic ();
_isInitialized = true;
}
}
if (!focus) {
StopMicrophone ();
_isInitialized = false;
}
}
}
This script works. I have just tested it and it shows the peak level of the mic in the inspector as the variable testSound. There is something going wrong on your end that is causing it to not begin recording into the audioclip. That is why it is always returning that the micPosition is less than zero.
The only thing that I can see that is slightly off is Microphone.GetPosition(null) inside the LevelMax method. Try changing this to Microphone.GetPosition(_device)
You might also want to try going through your different audio devices by changing the index passed in the line (in the InitMic method):
_device = Microphone.devices [0];
Try changing this to 1,2,3 etc and see if you are just finding the wrong audio device. If you have more than one mic or are not using the default mic then this could be the problem.
Also, I think you are misunderstanding how digital audio works. GetPosition gets the current sample that the microphone is recording into the audioclip (i.e the latest sample/current sample). This basically means that it gets the amount of samples that have been recorded. You are recording at 44.1Khz samples. That means that every second the audio is being checked 441000 times and a level is assigned to that individual sample. This is called the sample rate and it can be changed. For example CD's use the sample rate 44.1kHz where as digital video tends to use 48kHz. The accuracy of the the sample being recorded is defined by the bit-depth (but you don't have to worry about this). For example CD's use 16bit (which needs to be dithered) whereas digital media uses 24bit(generally). The line:
int micPosition = Microphone.GetPosition(null)-(_sampleWindow+1);
Says "find the amount samples we have recorded 129 samples ago". It then iterates through the values of the next 128 samples and finds the 'loudest' sample and returns it. This is then shown in the inspector. If you don't understand anything I've just said then look into how digital audio is recorded. It's not too complicated to understand the basics of it.
You should check out this thread, but here is the code that may help you:
public class MicInput : MonoBehaviour {
public static float MicLoudness;
private string _device;
//mic initialization
void InitMic(){
if(_device == null) _device = Microphone.devices[0];
_clipRecord = Microphone.Start(_device, true, 999, 44100);
}
void StopMicrophone()
{
Microphone.End(_device);
}
AudioClip _clipRecord = new AudioClip();
int _sampleWindow = 128;
//get data from microphone into audioclip
float LevelMax()
{
float levelMax = 0;
float[] waveData = new float[_sampleWindow];
int micPosition = Microphone.GetPosition(null)-(_sampleWindow+1); // null means the first microphone
if (micPosition < 0) return 0;
_clipRecord.GetData(waveData, micPosition);
// Getting a peak on the last 128 samples
for (int i = 0; i < _sampleWindow; i++) {
float wavePeak = waveData[i] * waveData[i];
if (levelMax < wavePeak) {
levelMax = wavePeak;
}
}
return levelMax;
}
void Update()
{
// levelMax equals to the highest normalized value power 2, a small number because < 1
// pass the value to a static var so we can access it from anywhere
MicLoudness = LevelMax ();
}
bool _isInitialized;
// start mic when scene starts
void OnEnable()
{
InitMic();
_isInitialized=true;
}
//stop mic when loading a new level or quit application
void OnDisable()
{
StopMicrophone();
}
void OnDestroy()
{
StopMicrophone();
}
// make sure the mic gets started & stopped when application gets focused
void OnApplicationFocus(bool focus) {
if (focus)
{
//Debug.Log("Focus");
if(!_isInitialized){
//Debug.Log("Init Mic");
InitMic();
_isInitialized=true;
}
}
if (!focus)
{
//Debug.Log("Pause");
StopMicrophone();
//Debug.Log("Stop Mic");
_isInitialized=false;
}
}
}
Related
I want GameTipUI with a blank space for a certain amount of time and certain message(==NormalGuideTip) for a certain amount of time using CoRoutine.
I was thinking of putting an 'if' in the CoRoutine to check the time,so make it easy to empty the message, and display the message again.
However, the message just keeps getting blank.
What could be the problem?
I wanted to repeat the randomly selected game tip message for 5 seconds and the blank message for 7 seconds.
And I wanted to use realtimeForGameTip to immediately display a special message(==UrgentGameTip ) according to a specific game action later and repeat the above process again.
bool isGameTipOn= true;
public GameObject gameTipTitle;
public GameObject gameTipMsg;
Color normalTipColor = new Color(255f, 255f, 255f, 220f);
Color UrgentTipColor = Color.green;
float timeBetGameTip = 7f; //
float timeOfGameMsg = 5f; //
public readonly WaitForSeconds m_waitForSecondsForGameTip = new WaitForSeconds(7f);
float realtimeForGameTip= 0f;
public string[] NormalGuideTips;
public string[] UrgentGuideTips;
private void Start()
{
#region GameTipSetUp
OnOffGameTip(true);
StartCoroutine("GameTipUI");
#endregion GameTipSetUp
}
public void OnOffGameTip(bool OnOff)
{
isGameTipOn = OnOff;
gameTipTitle.gameObject.SetActive(isGameTipOn);
gameTipMsg.gameObject.SetActive(isGameTipOn);
}
private void ResetGameTip()
{
if (!isGameTipOn) return;
realtimeForGameTip = 0f ;
int index = Random.Range(0, NormalGuideTips.Length);
string selectedMsg = NormalGuideTips[index];
Debug.Log(selectedMsg);
gameTipMsg.GetComponent<Text>().text = selectedMsg;
gameTipMsg.GetComponent<Text>().color = normalTipColor;
}
public void UrgentGameTip(string id)
{
if (!isGameTipOn) return;
=
int index = Random.Range(0, NormalGuideTips.Length);
string selectedMsg = UrgentGuideTips[index];
Debug.Log(selectedMsg);
gameTipMsg.GetComponent<Text>().text = selectedMsg;
gameTipMsg.GetComponent<Text>().color = UrgentTipColor;
realtimeForGameTip = timeOfGameMsg;
}
public void MakeTermBetGameTip()
{
gameTipMsg.GetComponent<Text>().text = " blank ";
}
IEnumerator GameTipUI()
{
while (isGameTipOn)
{
if(realtimeForGameTip < timeOfGameMsg)
{
Debug.Log(realtimeForGameTip);
realtimeForGameTip += Time.deltaTime;
continue;
}
MakeTermBetGameTip();
yield return new WaitForSeconds(m_waitForSecondsForGameTip);
ResetGameTip();
}
}
I got a quick question for you. I have already everything prepared. I am making a simple launching game, where you need to kill all enemies (not time-based) to pass to the next level. I have 2 methods GoToNextLevel() and AllMonsterDied(); .
Need to make something like player have 3 attempts (3 launchings whenever he wants not based on time).
Then every launch checks if any monster left. If does just show 1 attemps less. After 3 times just restart scene.
Thanks a lot since I am new to both c# and unity that would mean the world to me.
public class LevelController: MonoBehaviour
{
[SerializeField] string _nextLevelName;
Monster[] _monsters;
void OnEnable()
{
_monsters = FindObjectsOfType<Monster>();
}
void Update()
{
int currentLevel = SceneManager.GetActiveScene().buildIndex;
if (currentLevel >= PlayerPrefs.GetInt("levelsUnlocked"))
{
PlayerPrefs.SetInt("levelsUnlocked", currentLevel + 1);
}
if (MonsterAreAllDead() )
{
GoToNextLevel();
}
Debug.Log("Level" + PlayerPrefs.GetInt("levelsUnlocked") + "UNLOCKED");
}
void GoToNextLevel()
{
Debug.Log("Go to next level" + _nextLevelName);
SceneManager.LoadScene(_nextLevelName);
}
bool MonsterAreAllDead()
{
foreach (var monster in _monsters)
{
if (monster.gameObject.activeSelf)
return false;
}
return true;
}
}
Line from Monster.cs
private void OnCollisionEnter2D(Collision2D collision)
{
if (SouldDieFromCollision(collision))
{
tickSource.Play();
StartCoroutine(Die());
}
}
IEnumerator Die()
{
_hasDied =true;
GetComponent<SpriteRenderer>().sprite=_deadsprite;
_particleSystem.Play();
yield return new WaitForSeconds(1);
gameObject.SetActive(false);
}
Let's say you want the next level launch attempt happen when the player hits N key on the keyboard.
When the player hits the key, check if all monsters are dead. If so, call GoToNextLeve(), otherwise take off 1 attempt from the attempts available and restart the current scene.
int attempts;
void OnEnable()
{
attempts = 3;
}
void Update()
{
if (Input.GetKeyUp(KeyCode.H)
{
TryGoToNextLevel();
}
}
void TryGoToNextLevel()
{
if (MonsterAreAllDead() )
{
GoToNextLevel();
}
else
{
attempts--;
}
if (attempts <= 0)
{
SceneManager.LoadScene(
SceneManager.GetActiveScene().name);
}
}
I am working on small game similar to angry birds since I am new to both unity
and C#. I want to make load and unlock next level if all enemies are dead and check if new level exist
(if not) return back to Level selection scene.
This is what I tried:
LevelScript
using UnityEngine;
using UnityEngine.SceneManagement;
public class LevelScript : MonoBehaviour
{
[SerializeField] string _nextLevelName;
Monster[] _monsters;
void OnEnable()
{
_monsters = FindObjectsOfType<Monster>();
}
private void Update()
{
int currentLevel = SceneManager.GetActiveScene().buildIndex ;
if (currentLevel >= PlayerPrefs.GetInt("levelsUnlocked"))
{
PlayerPrefs.SetInt("levelsUnlocked", currentLevel );
}
if (MonsterAreAllDead())
{
GoToNextLevel();
}
Debug.Log("Level" + PlayerPrefs.GetInt("levelsUnlocked") + "UNLOCKED");
}
public void Pass()
{
int currentLevel = SceneManager.GetActiveScene().buildIndex;
if (currentLevel >= PlayerPrefs.GetInt("levelsUnlocked") )
{
PlayerPrefs.SetInt("levelsUnlocked", currentLevel + 1);
};
}
bool MonsterAreAllDead()
{
foreach (var monster in _monsters)
{
if (monster.gameObject.activeSelf)
return false;
}
return true;
}
void GoToNextLevel()
{
Debug.Log("Go to next level" + _nextLevelName);
SceneManager.LoadScene(_nextLevelName);
}
}
and Level Manager
public class LevelManager : MonoBehaviour
{
int levelsUnlocked;
public Button[] buttons;
void Start()
{
levelsUnlocked = PlayerPrefs.GetInt("levelsUnlocked", 1);
for (int i = 0; i < buttons.Length; i++)
{
buttons[i].interactable = false;
}
for (int i = 0; i < levelsUnlocked; i++)
{
buttons[i].interactable = true;
}
}
public void LoadLevel(int levelIndex)
{
SceneManager.LoadScene(levelIndex);
}
I got these 2 scripts and I attached both canvas and my buttons and Level script to my levels.
Problem is that every level gets unlocked at begin and after completeing levels automatically
it want to go to next level whic is not exist yet.
Pls help me. Sorry if my question is stupid and for bad english.
You should make an array of scene in your LevelManager that know all your levels (in order)
and for getting next level you can get the position of your actual scene in the array and check the next one.
somethink like
pseudocode :
[Serialized]
Scene[] AllScenes
void GoToNextLevel()
{
int currentLevelPos = AllScenes.IndexOf(currentScene);
if (AllScenes[currentLevelPos + 1] != null)
Load Next Level
else
Go To Level Menu
}
I need my script to go to the next camera every time I press a button on my Arduino, I just can't get it working can somebody please help me?
I got the Arduino communicating with Anity but i just can't get my head around the camera switching part.
Could someone tell me how to make a button press hop to the next camera, so I only need 1 button to show all the camera's
using UnityEngine;
using System.Collections;
using Uniduino;
#if (UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5)
public class DigitalRead : Uniduino.Examples.DigitalRead { } // for unity 3.x
#endif
namespace Uniduino.Examples
{
public class digitalread2 : MonoBehaviour {
public Arduino arduino;
public int pin = 2;
public int pinValue;
public int testLed = 11;
public int licht;
public int enable;
public int val = 0;
public GameObject cam1;
public GameObject cam2;
void Start ()
{
arduino = Arduino.global;
arduino.Log = (s) => Debug.Log("Arduino: " +s);
arduino.Setup(ConfigurePins);
cam1 = GameObject.Find ("cam1");
}
void ConfigurePins ()
{
arduino.pinMode(pin, PinMode.INPUT);
arduino.reportDigital((byte)(pin/8), 1);
// set the pin mode for the test LED on your board, pin 13 on an Arduino Uno
arduino.pinMode(testLed, PinMode.OUTPUT);
}
void Update ()
{
// read the value from the digital input
enable = arduino.digitalRead (pin);
licht = arduino.digitalRead (pin);
pinValue = arduino.digitalRead(pin);
// apply that value to the test LED
arduino.digitalWrite(testLed,pinValue);
Debug.Log(pinValue);
val = arduino.digitalRead (pin);
arduino.digitalWrite (testLed, val);
val = cam1.active = false;
val = cam2.active = true;
}
}
}
GameObject cam1, cam2;
void Start(){
cam1 = Find("camera_name");
cam2 = Find("camera2_name");
cam1.enabled = True;
cam2.enabled = False;
void Update(){
if(Input.GetButtonDown... // you need to implement ur arduino && cam1,isActive())
cam1.enabled = false;
cam2.enabled = true;
if(Input.GetButtonDown... // you need to implement ur arduino && cam2.isActive()) {
cam2.enabled = false;
cam1.enabled = true;
}
There might be something wrong. I am not able to use Unity Tool now. I am also not sure if you can you "cam1.enabled" or not.
I got some trouble with my unity cardboard app. May some of you guys can help me.
I have build a little Island with Animations and A second island as a main menu.
So when the apps starts, you see the Island from above and the Logo of the App.
When the user pull down the magnet button on side the app will starts another level.
I used this scripts:
http://www.andrewnoske.com/wiki/Unity_-_Detecting_Google_Cardboard_Click
Detecting Google Cardboard Magnetic Button Click - Singleton Implementation
CardboardMagnetSensor.cs and CardboardTriggerControlMono.cs
I created a script in my asset folder(CardboardMagnetSensor.cs) like in the description from Link. Than I created a second script(CardboardTriggerControlMono.cs) like in the discription an dragged it onto my CardboardMain in may Projekt.
The CardboardTriggerControlMono.cs looks like:
using UnityEngine;
using System.Collections;
public class CardboardTriggerControlMono : MonoBehaviour {
public bool magnetDetectionEnabled = true;
void Start() {
CardboardMagnetSensor.SetEnabled(magnetDetectionEnabled);
// Disable screen dimming:
Screen.sleepTimeout = SleepTimeout.NeverSleep;
}
void Update () {
if (!magnetDetectionEnabled) return;
if (CardboardMagnetSensor.CheckIfWasClicked()) {
Debug.Log("Cardboard trigger was just clicked");
Application.LoadLevel(1);
CardboardMagnetSensor.ResetClick();
}
}
}
The CarboardMagnetSensor:
using UnityEngine;
using System.Collections.Generic;
public class CardboardMagnetSensor {
// Constants:
private const int WINDOW_SIZE = 40;
private const int NUM_SEGMENTS = 2;
private const int SEGMENT_SIZE = WINDOW_SIZE / NUM_SEGMENTS;
private const int T1 = 30, T2 = 130;
// Variables:
private static bool wasClicked; // Flips to true once set off.
private static bool sensorEnabled; // Is sensor active.
private static List<Vector3> sensorData; // Keeps magnetic sensor data.
private static float[] offsets; // Offsets used to detect click.
// Call this once at beginning to enable detection.
public static void SetEnabled(bool enabled) {
Reset();
sensorEnabled = enabled;
Input.compass.enabled = sensorEnabled;
}
// Reset variables.
public static void Reset() {
sensorData = new List<Vector3>(WINDOW_SIZE);
offsets = new float[SEGMENT_SIZE];
wasClicked = false;
sensorEnabled = false;
}
// Poll this once every frame to detect when the magnet button was clicked
// and if it was clicked make sure to call "ResetClick()"
// after you've dealt with the action, or it will continue to return true.
public static bool CheckIfWasClicked() {
UpdateData();
return wasClicked;
}
// Call this after you've dealt with a click operation.
public static void ResetClick() {
wasClicked = false;
}
// Updates 'sensorData' and determines if magnet was clicked.
private static void UpdateData() {
Vector3 currentVector = Input.compass.rawVector;
if (currentVector.x == 0 && currentVector.y == 0 && currentVector.z == 0) {
return;
}
if(sensorData.Count >= WINDOW_SIZE) sensorData.RemoveAt(0);
sensorData.Add(currentVector);
// Evaluate model:
if(sensorData.Count < WINDOW_SIZE) return;
float[] means = new float[2];
float[] maximums = new float[2];
float[] minimums = new float[2];
Vector3 baseline = sensorData[sensorData.Count - 1];
for(int i = 0; i < NUM_SEGMENTS; i++) {
int segmentStart = 20 * i;
offsets = ComputeOffsets(segmentStart, baseline);
means[i] = ComputeMean(offsets);
maximums[i] = ComputeMaximum(offsets);
minimums[i] = ComputeMinimum(offsets);
}
float min1 = minimums[0];
float max2 = maximums[1];
// Determine if button was clicked.
if(min1 < T1 && max2 > T2) {
sensorData.Clear();
wasClicked = true; // Set button clicked to true.
// NOTE: 'wasClicked' will now remain true until "ResetClick()" is called.
}
}
private static float[] ComputeOffsets(int start, Vector3 baseline) {
for(int i = 0; i < SEGMENT_SIZE; i++) {
Vector3 point = sensorData[start + i];
Vector3 o = new Vector3(point.x - baseline.x, point.y - baseline.y, point.z - baseline.z);
offsets[i] = o.magnitude;
}
return offsets;
}
private static float ComputeMean(float[] offsets) {
float sum = 0;
foreach(float o in offsets) {
sum += o;
}
return sum / offsets.Length;
}
private static float ComputeMaximum(float[] offsets) {
float max = float.MinValue;
foreach(float o in offsets) {
max = Mathf.Max(o, max);
}
return max;
}
private static float ComputeMinimum(float[] offsets) {
float min = float.MaxValue;
foreach(float o in offsets) {
min = Mathf.Min(o, min);
}
return min;
}
}
And my steps:
http://www.directupload.net/file/d/3887/mtjygjan_jpg.htm
(sorry I´m not able to upload pictures here)
How ever, it wont work. When I start the app and pull down the magnet, nothing happens. May I did something wrong with switching the level over level index?
I use a nexus 4 and 5 for testing the app
Thanks allot and greetz to you!
Phillip
If you are using the Google Cardboard SDK for Unity, it currently has a bug that prevents Unity from seeing the magnet (and gyro, and accelerometer). That is probably why your script is not working. Until the bug is fixed, there is no good workaround, but you can instead use the property Cardboard.CardboardTriggered to detect if the magnet was pulled.
Update for Unity 5: The sensor bug is gone. Cardboard SDK does not block the sensors.