I am making an AR experience where I would like to use a phone's compass to direct a player toward true north, but I am having problems with smoothing the compass readings on Android devices. The code I have experimented with so far logs a certain number of readings into a queue, copies the queue into a list and then once the list is full it takes the average of all the readings. From here I would like to scale the reading between -1 & 1, where 0 represents South. Then I want this data to be used to rotate a compass image on the GUI layer.
The data is nowhere near as smooth as I'd like it to be, but here is the code I have so far:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class CompassSlider : MonoBehaviour
{
public float reading;
public Queue<float> data;
private float[] dataList;
public int maxData = 100;
public int count;
public float sum, average;
public float dampening = 0.1f;
public float rotationToNorth;
// Use this for initialization
void Start()
{
data = new Queue<float>();
dataList = new float[maxData];
}
// Update is called once per frame
void Update()
{
Input.location.Start();
Input.compass.enabled = true;
count = data.Count;
if (data.Count > 0)
{
data.CopyTo(dataList, 0);
}
if (data.Count == maxData)
{
for (int i = 0; i < data.Count; i++)
{
sum += dataList[i];
}
if (Mathf.Abs(dataList[maxData - 1]) > 0)
{
average = sum / maxData;
sum = 0;
data.Clear();
dataList = new float[maxData];
}
}
if (data.Count >= maxData)
{
data.Dequeue();
}
reading = Mathf.Round(Input.compass.trueHeading);
data.Enqueue(reading);
if (count == maxData) {
rotationToNorth = average / 180;
rotationToNorth = (Mathf.Round(rotationToNorth * 10)) / 10;
rotationToNorth = rotationToNorth - 1;
}
}
}
Related
I have a method that is supposed to generate a certain number of Vector3 at a distance not less than specified.
// Generate random point based on plane area
public List<Vector3> GeneratePositions(int numberOfPositions, float minDistanceBetweenPositions)
{
float entireArea = 0f;
List<AreasWeight> areasWeights = new List<AreasWeight>();
List<Vector3> positions = new List<Vector3>();
foreach (GeneratorPlane plane in GeneratorPlanes.GetCollectionAsList())
{
entireArea += plane.GetArea();
}
foreach (GeneratorPlane plane in GeneratorPlanes.GetCollectionAsList())
{
float weight = plane.GetArea() / entireArea;
int numOfPositionsInArea = Mathf.RoundToInt(numberOfPositions * weight);
areasWeights.Add(new(plane, weight, numOfPositionsInArea));
}
foreach (AreasWeight areaWeight in areasWeights)
{
for (int i = 0; i < areaWeight.NumOfPointsInArea; i++)
{
Vector3 generatedPoint = areaWeight.Plane.GetRandomPointOnPlane();
foreach (Vector3 position in positions)
{
int attempts = 1;
while ((position - generatedPoint).magnitude < minDistanceBetweenPositions)
{
generatedPoint = areaWeight.Plane.GetRandomPointOnPlane();
attempts++;
if (attempts > 2000)
{
Debug.Log("Can't generate all positions.");
break;
}
}
}
positions.Add(generatedPoint);
}
}
return positions;
}
Get random point method:
public Vector3 GetRandomPointOnPlane()
{
float xPosition = Random.Range(Mathf.Min(DownPoint.x, DownPointHelper.x), Mathf.Max(DownPoint.x, DownPointHelper.x));
float zPosition = Random.Range(Mathf.Min(DownPoint.z, UpPointHelper.z), Mathf.Max(DownPoint.z, UpPointHelper.z));
return new(xPosition, DownPoint.y + 0.002f, zPosition);
}
But when i Instantiate objects based on these Vector3. Objects still have a distance less than the specified. What am i doing wrong?
I found a solution. The problem was a bad loop structure. When the algorithm confirmed that the distance was too small and generated a new one, it did not check whether the generated position had a gap from the previous positions on the list. It only confirmed that the gap was preserved and the program continued to execute.
I moved the code that makes sure that the distances are saved to the public List<Vector3> GeneratePositions(int numberOfPositions, float minDistanceBetweenPositions) method in the GeneratorPlane class. I also added a private Vector3 PickRandomPos() method to it, just to return the generated position.
Methods in the public class GeneratorPlane:
public Vector3 GetRandomPointOnPlane(List<Vector3> alreadyGeneratedPoints, float minDistnaceBetweenPositions)
{
if (alreadyGeneratedPoints.Count != 0)
{
int attemps = 1;
bool pointFound = false;
Vector3 posToReturn = new();
while (!pointFound)
{
pointFound = true;
posToReturn = PickRandomPos();
foreach (Vector3 position in alreadyGeneratedPoints)
{
if (Vector3.Distance(position, posToReturn) < minDistnaceBetweenPositions)
{
pointFound = false;
attemps++;
if (attemps > 2000)
{
Debug.LogError("Points cannot be generated. Too little available space");
return Vector3.zero;
}
break;
}
}
}
return posToReturn;
}
else
{
Debug.Log("First point generated");
return PickRandomPos();
}
}
private Vector3 PickRandomPos()
{
float xPosition = Random.Range(Mathf.Min(DownPoint.x, DownPointHelper.x), Mathf.Max(DownPoint.x, DownPointHelper.x));
float zPosition = Random.Range(Mathf.Min(DownPoint.z, UpPointHelper.z), Mathf.Max(DownPoint.z, UpPointHelper.z));
return new(xPosition, DownPoint.y + 0.002f, zPosition);
}
Method to generate and return a certain number of items:
public List<Vector3> GeneratePositions(int numberOfPositions, float minDistanceBetweenPositions)
{
float entireArea = 0f;
List<AreasWeight> areasWeights = new();
List<Vector3> positions = new();
foreach (GeneratorPlane plane in PlanesGenerator.GetCollectionAsList())
{
entireArea += plane.GetArea();
}
foreach (GeneratorPlane plane in PlanesGenerator.GetCollectionAsList())
{
float weight = plane.GetArea() / entireArea;
int numOfPositionsInArea = Mathf.RoundToInt(numberOfPositions * weight);
areasWeights.Add(new(plane, weight, numOfPositionsInArea));
}
foreach (AreasWeight areaWeight in areasWeights)
{
for (int i = 0; i < areaWeight.NumOfPointsInArea; i++)
{
Vector3 generatedPoint = areaWeight.Plane.GetRandomPointOnPlane(positions, minDistanceBetweenPositions);
positions.Add(generatedPoint);
}
}
return positions;
}
On the original code if you generate a point 2000 times you actually keep the last generatedPoint, and as you mentioned you don't actually cross check the whole list of positions, only the remaining positions.
Although you have solved your problem and posted a solution, I took the liberty of doing a simple script with the same end, I will share it here in hopes its useful for you or others.
This solution will not fill any area, its only making sure no two objects are at shorter distance than specified.
In my tests, with 50 nPoints only 10/20 points are instantiated before a point takes over 2000 attempts and consequently conclude the search for points. Although this will depend on the ratio between spawnLimits and nPoints.
[SerializeField]
GameObject trunkPrefab;
List<Vector3> positions;
//input variables
int nPoints = 50;
float minDistance = 2.5f;
int spawnLimits = 20;
void Start()
{
positions = new();
for (int i = 0; i < nPoints; i++)
{
Vector3 position = Vector3.zero;
bool newPosition = true;
int attempts = 0;
do
{
//first generation will be automatically added to the list
position = new(Random.Range(-spawnLimits, spawnLimits), .5f, Random.Range(-spawnLimits, spawnLimits));
if (positions.Count < 1)
{
break;
}
//every position will be compared here,
//if any position is too close from then new position
//"newPosition" is set to false and we try again from the start.
for (int p = 0; p < positions.Count; p++)
{
if (Vector3.Distance(position, positions[p]) < minDistance)
{
newPosition = false;
attempts++;
if (attempts > 2000)
{
Debug.Log("Max attempts reached.");
return;
}
break;
}
}
} while (!newPosition);
//adding a random rotation
Vector3 rotation = new(Random.Range(80, 100), Random.Range(0, 179), 0);
Instantiate(trunkPrefab, position, Quaternion.Euler(rotation));
positions.Add(position);
}
}
In my unity project i have 3 scenes.
Title
Play
Over
For achieving the flow Title -> Play -> Over. I had to start the game from Over scene. Over -> Title -> Play -> Over .. I don't want this. I want it to work when i start the game from Title scene. On doing so i am unable to change from Play -> Over.
for Title scene
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class LoadSceneOnInput : MonoBehaviour {
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
if (Input.GetAxis("Submit") == 1) {
SceneManager.LoadScene("Play");
}
}
}
for Over Scene
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class GameOverInput : MonoBehaviour {
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
if (Input.GetAxis("Submit") == 1) {
SceneManager.LoadScene("Title");
}
}
}
Play's script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class LevelGenerator : MonoBehaviour {
public GameObject floorPrefab;
public GameObject wallPrefab;
public GameObject ceilingPrefab;
public GameObject characterController;
public GameObject floorParent;
public GameObject wallsParent;
// allows us to see the maze generation from the scene view
public bool generateRoof = true;
// number of times we want to "dig" in our maze
public int tilesToRemove = 50;
public int mazeSize;
// spawns at the end of the maze generation
public GameObject pickup;
// this will determine whether we've placed the character controller
private bool characterPlaced = false;
// 2D array representing the map
private bool[,] mapData;
// we use these to dig through our maze and to spawn the pickup at the end
private int mazeX = 4, mazeY = 1;
// Use this for initialization
void Start () {
// initialize map 2D array
mapData = GenerateMazeData();
// create actual maze blocks from maze boolean data
for (int z = 0; z < mazeSize; z++) {
for (int x = 0; x < mazeSize; x++) {
if (mapData[z, x]) {
CreateChildPrefab(wallPrefab, wallsParent, x, 1, z);
CreateChildPrefab(wallPrefab, wallsParent, x, 2, z);
CreateChildPrefab(wallPrefab, wallsParent, x, 3, z);
} else if (!characterPlaced) {
// place the character controller on the first empty wall we generate
characterController.transform.SetPositionAndRotation(
new Vector3(x, 1, z), Quaternion.identity
);
// flag as placed so we never consider placing again
characterPlaced = true;
CreateChildPrefab(floorPrefab, floorParent, x, 0, z);
}
//create floor and ceiling
if(mapData[z, x]){
CreateChildPrefab(floorPrefab, floorParent, x, 0, z);
}else{
if((z > 0 && z < mazeSize - 1) && (x > 0 && x < mazeSize - 1)){
if(!(Random.value > 0.8)){
CreateChildPrefab(floorPrefab, floorParent, x, 0, z);
}
}
}
if (generateRoof) {
CreateChildPrefab(ceilingPrefab, wallsParent, x, 4, z);
}
}
}
// spawn the pickup at the end
var myPickup = Instantiate(pickup, new Vector3(mazeX, 1, mazeY), Quaternion.identity);
myPickup.transform.localScale = new Vector3(0.25f, 0.25f, 0.25f);
}
void Update() {
if(characterController.transform.position.y < -5){
SceneManager.LoadScene("Over");
}
}
// generates the booleans determining the maze, which will be used to construct the cubes
// actually making up the maze
bool[,] GenerateMazeData() {
bool[,] data = new bool[mazeSize, mazeSize];
// initialize all walls to true
for (int y = 0; y < mazeSize; y++) {
for (int x = 0; x < mazeSize; x++) {
data[y, x] = true;
}
}
// counter to ensure we consume a minimum number of tiles
int tilesConsumed = 0;
// iterate our random crawler, clearing out walls and straying from edges
while (tilesConsumed < tilesToRemove) {
// directions we will be moving along each axis; one must always be 0
// to avoid diagonal lines
int xDirection = 0, yDirection = 0;
if (Random.value < 0.5) {
xDirection = Random.value < 0.5 ? 1 : -1;
} else {
yDirection = Random.value < 0.5 ? 1 : -1;
}
// random number of spaces to move in this line
int numSpacesMove = (int)(Random.Range(1, mazeSize - 1));
// move the number of spaces we just calculated, clearing tiles along the way
for (int i = 0; i < numSpacesMove; i++) {
mazeX = Mathf.Clamp(mazeX + xDirection, 1, mazeSize - 2);
mazeY = Mathf.Clamp(mazeY + yDirection, 1, mazeSize - 2);
if (data[mazeY, mazeX]) {
data[mazeY, mazeX] = false;
tilesConsumed++;
}
}
}
return data;
}
// allow us to instantiate something and immediately make it the child of this game object's
// transform, so we can containerize everything. also allows us to avoid writing Quaternion.
// identity all over the place, since we never spawn anything with rotation
void CreateChildPrefab(GameObject prefab, GameObject parent, int x, int y, int z) {
var myPrefab = Instantiate(prefab, new Vector3(x, y, z), Quaternion.identity);
myPrefab.transform.parent = parent.transform;
}
}
The problem is in the LightsEffectCore when I make the array reverse.
The lights change direction but not from the current light it is on.
It's jumping to some other light index and change the direction from there.
And I want it to change the direction from the current light index.
If the light is now on index 5 and I change the direction then move from 5 to 4 and if I changed the direction again move from 4 to 5.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LightsEffect : MonoBehaviour
{
public List<UnityEngine.GameObject> waypoints = new List<UnityEngine.GameObject>();
public int howmanylight = 5;
public Generatenumbers gn;
public bool changeLightsDirection = false;
public float delay = 0.1f;
private List<UnityEngine.GameObject> objects;
private Renderer[] renderers;
private int greenIndex = 0;
private float lastChangeTime;
private void Start()
{
objects = new List<UnityEngine.GameObject>();
if (howmanylight > 0)
{
UnityEngine.GameObject go1 = UnityEngine.GameObject.CreatePrimitive(PrimitiveType.Sphere);
duplicateObject(go1, howmanylight);
LightsEffects();
}
}
private void Update()
{
LightsEffectCore();
}
public void duplicateObject(UnityEngine.GameObject original, int howmany)
{
howmany++;
for (int i = 0; i < waypoints.Count - 1; i++)
{
for (int j = 1; j < howmany; j++)
{
Vector3 position = waypoints[i].transform.position + j * (waypoints[i + 1].transform.position - waypoints[i].transform.position) / howmany;
UnityEngine.GameObject go = Instantiate(original, new Vector3(position.x, 0, position.z), Quaternion.identity);
go.transform.localScale = new Vector3(0.3f, 0.1f, 0.3f);
objects.Add(go);
}
}
}
private void LightsEffects()
{
renderers = new Renderer[objects.Count];
for (int i = 0; i < renderers.Length; i++)
{
renderers[i] = objects[i].GetComponent<Renderer>();
renderers[i].material.color = Color.red;
}
// Set green color to the first one
greenIndex = 0;
renderers[greenIndex].material.color = Color.green;
}
private void LightsEffectCore()
{
// Change color each `delay` seconds
if (Time.time > lastChangeTime + delay)
{
lastChangeTime = Time.time;
// Set color of the last renderer to red
// and the color of the current one to green
renderers[greenIndex].material.color = Color.red;
if (changeLightsDirection == true)
{
Array.Reverse(renderers);
changeLightsDirection = false;
}
greenIndex = (greenIndex + 1) % renderers.Length;
renderers[greenIndex].material.color = Color.green;
}
}
}
You have to change your greenIndex as well, when you reversing the array. If you do not, then the new value at that index is going to be the one after the reverse, and that is why, it looks like it is jumping away.
If you want to avoid it jumping about, set the index to greenIndex=renderers.Lenght-greenIndex.
I could not test it out, please correct me if I am wrong.
This is code for Unity 3D Game Engine...
I try to monitor sound decibels using this source scripts but why i am getting Negative Decibel ?
In theory, sound from Normal Conversation will result 60db and even silent room will make 10-20dB...
any idea ?
Result of Decibel Calculation :
and below are source code :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class VR_SoundMonitor : MonoBehaviour
{
public float DecibleValue;
public float RmsValue;
public float PitchValue;
private AudioSource audioSourceObject;
private float[] _spectrum;
private float[] _samples;
private float _fSample;
const int QSamples = 1024;
const float RefValue = 0.1f;
const float Threshold = 0.02f;
void Start ()
{
audioSourceObject = GetComponent<AudioSource> ();
InitVariable ();
InitMicrophone ();
}
//Nyalakan Microphone
private void InitMicrophone()
{
audioSourceObject.clip = Microphone.Start ("SoundMonitoring", false, 60, 44100);
while (!(Microphone.GetPosition(null) > 0)) { }
audioSourceObject.Play();
}
private void InitVariable()
{
_samples = new float[QSamples]; //use constanta (3 below)
_spectrum = new float[QSamples];
_fSample = AudioSettings.outputSampleRate;
}
void Update ()
{
CalculateAnalyzeSound (audioSourceObject);
Debug.Log ("Decibels="+DecibleValue+" ## RmsVal="+RmsValue);
}
public void CalculateAnalyzeSound(AudioSource audio)
{
float _RmsValue;
float _DbValue;
float _PitchValue;
audio.GetOutputData(_samples, 0);
int i = 0;
float sum = 0;
for (i = 0; i < QSamples; i++)
{
sum += _samples[i] * _samples[i]; // sum squared samples
}
_RmsValue = Mathf.Sqrt(sum / QSamples); // rms = square root of average
_DbValue = 20 * Mathf.Log10(_RmsValue / RefValue); // calculate dB
if (_DbValue < -160) _DbValue = -160; // clamp it to -160dB min
RmsValue = _RmsValue;
DecibleValue = _DbValue;
}
}
What's Wrong ? Any idea, please let me know....
I am creating a game and need some help handling a bunch of objects, like about 10000, in my game i am generating a random amount of rocks, in random positions around a 1mil by 1mil map, I am adding the objects to a list and updating and drawing them like that but it is running so slow. I think some help in this matter would really help a lot of learners wanting to handle many objects.
Here is my generation code:
public void WorldGeneration()
{
//Random Compatibility
Random rdm = new Random();
//Tile Variables
int tileType;
int tileCount = 0;
Rock nearestRock;
//Initialize Coordinates
Vector2 tileSize = new Vector2(48f, 48f);
Vector2 currentGenVector = new Vector2(48f, 48f);
int worldTiles = 1000000;
//Do tile generation
for(int tile = 1; tile <= worldTiles; tile += 1)
{
//Generate Classes
tileType = rdm.Next(0, 42);
if (tileType == 1)
{
if (rocks.Count != 0)
{
//Check Rock Distance
nearestRock = rocks.FirstOrDefault(x => Vector2.Distance(x.Location, currentGenVector) < 128);
if (nearestRock == null)
{
Rock rock = new Rock(rockSprite, currentGenVector);
rocks.Add(rock);
}
}
if (rocks.Count == 0)
{
Rock rock = new Rock(rockSprite, currentGenVector);
rocks.Add(rock);
}
}
//Move Generation Tile
if (tileCount == worldTiles / 1000)
{
currentGenVector.X = tileSize.X;
currentGenVector.Y += tileSize.Y;
tileCount = 0;
}
else
{
currentGenVector.X += tileSize.X;
}
//Keep Count of Tiles per layer.
tileCount += 1;
}
And here is my rock code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
namespace Game2
{
class Rock
{
//Draw Support
Texture2D sprite;
Rectangle drawRectangle;
//Support
Vector2 location;
bool updating = false;
//Active
bool active = true;
public Rock(Texture2D sprite, Vector2 location)
{
//Initialize Location/Drawing
this.sprite = sprite;
this.location = location;
drawRectangle.Width = sprite.Width;
drawRectangle.Height = sprite.Height;
drawRectangle.X = (int)location.X - sprite.Width / 2;
drawRectangle.Y = (int)location.Y - sprite.Height / 2;
}
public void Update(GameTime gameTime, MouseState mouse)
{
//Mining
if (drawRectangle.Contains(mouse.X, mouse.Y))
{
if (mouse.LeftButton == ButtonState.Pressed)
{
location.X = -800;
location.Y = -800;
}
}
drawRectangle.X = (int)location.X;
drawRectangle.Y = (int)location.Y;
}
public void Draw(SpriteBatch spriteBatch)
{
//Draws The Sprite
spriteBatch.Draw(sprite, drawRectangle, Color.White);
}
//Get Location
public Vector2 Location
{
get { return location; }
}
public bool Updating
{
get { return updating; }
}
public void setUpdating(bool updating)
{
this.updating = updating;
}
public Rectangle DrawRectangle
{
get { return drawRectangle; }
}
}
}
I'm just asking for some tips on how to handle all these objects,
pls help is appreciated
The way I handle many objects is that I create a function which calculates the distance between two vectors and then use it to check if the current object is close enough to be drawn.
//Distance checking code
public float GetDistance(Vector2 v1, Vector2 v2){
float d = Math.Sqrt(Math.Abs((v1.X * v1.X) - (v2.X * v2.X))
+ Math.Abs((v1.Y * v1.Y) - (v2.Y * v2.Y)));
return d;
}
//example of using the Distance Check
if(GetDistance(player.position, rock.position) < 1280){
rock.Update();
}
This is just from the top of my head though, so the code may not work properly, but I think it's enough to get you started. Good luck!