Getting the "border Y-values" in Unity 2D - c#

My problem is pretty simple, but yet I struggle to find an answer. Brief context, I have a sprite that moves accross the Y-axis according to microphone input. Beforehand, I prompted the user to record their lowest and highest note, calculated the frequency of these two notes and am using it as a reference for positioning the sprite on the Y-axis.
Let's say the lowest note is 100 Hz and the highest one 400 Hz. So if the player makes a tone of 100 Hz, the sprite should move down to the bottom of the Y-axis. To move back to the center (Y-position 0), the player would have to make a tone of 250 Hz (midpoint between 100 and 400).
So we know that for that player, 250 Hz equals to Y-position 0. But I need to know the Y-position equivalents of the lowest note (bottom edge) and highest note (top edge). When I move the sprite manually to the top edge and look at the Y-value in the inspector, it's apparently 4.58. But I'm not sure if hard-coding 4.58 would scale well across different screen-sized devices.
Screenshot here: https://i.ibb.co/pjrkSNV/Capture.png
I ideally want to have a method called FrequencyToY(float frequency) that converts a frequency value to the corresponding Y-value on the axis. I saved the lowest and highest frequency values in PlayerPrefs. Important note about the sprite movement, I don't want gravity. The bird should just smoothly move to the corresponding Y-position every time the player produces a tone, and stay in place otherwise.
This is my current script attached to my sprite:
public class Player : MonoBehaviour
{
public AudioSource audioPlayer;
public AudioMixer masterMixer;
private float[] _spectrum;
private float _fSample;
private Transform playerTransform;
void Start()
{
playerTransform = transform;
//Code for microphone loop
masterMixer.SetFloat("masterVolume", -80f);
_spectrum = new float[AudioAnalyzer.QSamples];
_fSample = AudioSettings.outputSampleRate;
audioPlayer.clip = Microphone.Start("", true, 10, 44100);
audioPlayer.loop = true;
while(!(Microphone.GetPosition("") > 0)) { }
audioPlayer.Play();
}
void Update()
{
//Calculate frequency of currently detected tones
audioPlayer.GetSpectrumData(_spectrum, 0, FFTWindow.BlackmanHarris);
float pitchVal = AudioAnalyzer.calculateFrequency(ref _spectrum, _fSample);
if(pitchVal != 0)
{
if (pitchVal < PlayerPrefs.GetFloat("lowestFrequency"))
pitchVal = PlayerPrefs.GetFloat("lowestFrequency");
else if (pitchVal < PlayerPrefs.GetFloat("highestFrequency"))
pitchVal = PlayerPrefs.GetFloat("highestFrequency");
//This is how I'd like to call the function
//But if someone could change this and make the sprite actually
//"move" to that point instead of just popping there it would be awesome!
transform.position = new Vector2(0, FrequencyToY(pitchVal));
}
}
//Converts frequency to position on Y-axis
public float FrequencyToY(float frequency)
{
float x = 0;
return x;
}
}

You need to define the range of your Y coordinates that you want to use (say, 0 -> 100) and then scale the pitch you're given against their scale (so what % on 0 - 100 of their scale) and then use that % as a point on Y.
Thus, if they give you a note that's exactly in the middle of their scale (50%) and your Y coordinates in game range from 0-10, you want to put them at 5.

Related

Knocking back the Player in a Unity 2D TopDown Game

When something hits the Player, maybe an enemy or spikes etc., I want him to get knocked back in the oppisite direction of the enemy.
So what I already got:
public void ChangeHealth(float healthToAdd, Vector2 objectPosition) // Change the players health, objectPosition = enemies position or something else
{
if (healthToAdd < 0) // incoming damage
{
// ... other stuff
Knockback(objectPosition);
}
// ... other stuff
}
void Knockback(Vector2 objectPosition) // Knockback routine
{
Vector2 knockbackPosition = new Vector2( , ); // calculation is missing here! Calculate the new position by the knockback direction
rigid.MovePosition(Vector2.MoveTowards(playerPos, knockbackPosition, 2 * Time.deltaTime)); // the knock back
}
private void Update() // !! TEST !!
{
if (Input.GetKeyDown(KeyCode.E)) // TEST routine
{
ChangeHealth(-7, new Vector2(10,10)); // decrease players health by 7 and knock him back
}
}
And what is missing:
Vector2 knockbackPosition = new Vector2( , );
I am looking for a calculation like this picture is showing:
Vector2 knockbackPosition =
transform.position + (transform.position - objectPosition).normalized *a
To understand why is it equal to that you have to read through.
There are three points: E,P,K (Enemy, Player, Knockback)
And one scalar number: a (the greater this value is, the more knockback you'll have)
Now from your picture:
PK = EP*a
expand vectors into distances between two points:
(K-P) = (P-E)*a
calculate the location of K:
K = P + (P-E)*a
There is one problem though. (thanks to Rotem) with this formula as it is:
you'd expect a bigger knockback from a closer opponent.
We don't want the knockback to be dependent on the distance between P and E.
To remove the dependency on the former, normalize the vector before multiplying by a
So we add .normalized to use just the direction of (P-E) instead of its original vector

Bounded rotation with Unity3D

I want to rotate an object with the left arrow key and I want to bound the rotation to 30 deg. My code is:
if (Input.GetKey(KeyCode.LeftArrow) && transform.localEulerAngles.z <= 30)
transform.Rotate(0,0,1);
Why rotation stops to 31 deg?
Obviously my problem is more complex than this, I have different bounds and I need precision. The reason of this example is to simply say that rotations are not precise if managed in this way.
I think the reason is that Unity3D internally uses quaternions and acting on degrees is just an approximation. I'm right? In this last case how can I cope to this?
For example, how can I use quaternions to bound of 30 degs a rotation on an axis?
By the way if the problem is not this, do you have other solutions?
I don't know how unity manage the rotation, but here your problem seem more simple.
In your if you use the '<=' comparison, so when your object is at 30 degree, you enter a last time in the if and rotate 1 more degree, use a '<' to stop at the right moment
Get the current rotation in the Start() function then use it to find an offset that will be used to perform the if statement. This should do it:
public GameObject gameObjectToRotate;
Vector3 defaultAngle;
float minRot = 30f;
float maxRot = 30f;
// Use this for initialization
void Start()
{
defaultAngle = gameObjectToRotate.transform.eulerAngles;
}
// Update is called once per frame
void Update()
{
if (Input.GetKey(KeyCode.LeftArrow))
{
float offset = defaultAngle.z - gameObjectToRotate.transform.eulerAngles.z;
//Check if we are within min, max rot
if (offset < minRot && offset > -maxRot)
{
gameObjectToRotate.transform.Rotate(0, 0, 1);
Debug.Log("Rotating!");
}
}
}
The precision is 30.016. This is much more better than what you are getting now.

Moving mouse in direction and rate

I'm trying to create a simple mouse emulator controlled by a joystick's right thumbstick. I was trying to have the mouse move in the direction the stick pointed with a smooth gradient of pressure values dictating speed, but I've hit a number of snags when trying to do so.
The first is how to accurately translate the angle into accurate X and Y values. I can't find a way to implement the angle correctly. The way I have it, the diagonals are likely to move considerably faster than the cardinals.
I was thinking I need something like Math.Cos(angle) for the X values, and Math.Sin(angle) for the Y values to increment the mouse, but I can't think of a way to set it up.
The second, is smooth movement of the mouse, and this is probably the more important of the two. Since the SetPosition() function only works with integers, the rate at which pixels move over time seems very limited. The code I have is very basic, and only registers whole number values of 1-10. That not only creates small 'jumps' in acceleration, but limits diagonal movement as well.
The goal would to have something like 10 pixels-per-second, with the program running at 100hz, and each cycle outputting 0.1 pixel movement.
I'd imagine I might be able to keep track of the pixel 'decimals' for the X and Y values and add them to the axes when they build to whole numbers, but I'd imagine there's a more efficient way to do so and still not anger the SetPosition() function.
I feel like Vector2 objects should get this done, but I don't know how the angle would fit in.
Sample code:
//Poll Gamepad and Mouse. Update all variables.
public void updateData(){
padOne = GamePad.GetState(PlayerIndex.One, GamePadDeadZone.None);
mouse = Mouse.GetState();
currentStickRX = padOne.ThumbSticks.Right.X;
currentStickRY = padOne.ThumbSticks.Right.Y;
currentMouseX = mouse.X;
currentMouseY = mouse.Y;
angle = Math.Atan2(currentStickRY, currentStickRX);
vectorX = (int)( currentStickRX*10 );
vectorY = (int)( -currentStickRY*10 );
mouseMoveVector.X = vectorX;
mouseMoveVector.Y = vectorY;
magnitude = Math.Sqrt( Math.Pow( (currentStickRX - 0), 2 ) + Math.Pow( (currentStickRY - 0), 2 ) );
if (magnitude > 1){
magnitude = 1;
}
//Get values not in deadzone range and re-scale them from 0-1
if(magnitude >= deadZone){
activeRange = (magnitude - deadZone)/(1 - deadZone);
}
Console.WriteLine(); //Test Code
}
//Move mouse in in direction at specific rate.
public void moveMouse(){
if (magnitude > deadZone){
Mouse.SetPosition( (currentMouseX + vectorX), (currentMouseY + vectorY));
}
previousStickRX = currentStickRX;
previousStickRY = currentStickRY;
previousActiveRange = activeRange;
}
Note: I'm using all the xna frameworks.
Anyway, apologies if I'm explaining these things incorrectly. I haven't been able to find a good resource for this, and the vector examples I searched only move in integer increments and from point A to B.
Any help with any part of this is greatly appreciated.
I haven't tried it myself but from my point of view, you should normalize the pad axis after reading them, that way diagonals would move the same speed as cardinals. And for the second part, I would keep track of the mouse in floating variables, such as a Vector2 and do the cast (maybe rounding better) when setting the mouse position.
public void Start()
{
mousePosV2 = Mouse.GetState().Position.ToVector2();
}
public void Update(float dt)
{
Vector2 stickMovement = padOne.ThumbSticks.Right;
stickMovement.Normalize();
mousePosV2 += stickMovement*dt*desiredMouseSpeed;
/// clamp here values of mousePosV2 according to Screen Size
/// ...
Point roundedPos = new Point(Math.Round(mousePosV2.X), Math.Round(mousePosV2.Y));
Mouse.SetPosition(roundedPos.X, roundedPos.Y);
}

Xna Isometric point to click movement

I'm working on an isometric game (diamond grid) and I've stumbled across a minor problem regarding a character movement.
I'm using A* to find a path between 2 points and then I want to move my character from point A to point B going through all the tiles which form the path but I can't find a way to do this , I mean a simpler and accurate method.
So far I've scrapped this piece of code but it's kinda "rusty"
public void Destination(tile destination)
{
for (int i = 0; i < 8; i++)
{
if (AdjacentTile[i] == destination)
{
characterDirection = i;
}
}
animation.changeSpriteDirection(characterDirection); //After I found which adjacent tile is the next destination I change the character direction based on it's position (1 = North , 2 = Nort Est etc) .. so the Y of the Animation_sourceRectangle it's changed//
Vector2 Position;
Position.X = current_characterTile.X - destination.X;
Position.Y = current_characterTile.Y - destination.Y;
rotation = (float)Math.Atan2(-Position.X, Position.Y);
moveVector = (Vector2.Transform(new Vector2(0, -1), Matrix.CreateRotationZ(rotation))) * characterSpeed;
movingCommand = 1; // the character is supposed to be moving..
Move(); //this function moves the sprite until the *tile.i and tile.j* of the character is the same as tile.j and tile.i of the destination
//something like this
if ( characterTile.i == destination.i && characterTile.j == destination.j)
movingCommand = 0 //stop
else
character_Position += moveVector;
}
If anyone could give me a hint on what to do or help me I'll be very grateful.
Thank You.
Possibilities:
At each tile, determine the character's speed vector and also determine how much time it will take for the character to move to next tile. When that time elapses, immediately begin moving to the next tile. (This is what I implemented below.)
At each tile, determine the character's speed vector. Then, when the character is sufficiently close to the next tile (say, the difference between their X and Y coordinates is less than 2 pixels?), snap it to the tile and begin moving to the next tile. This will causes artifacts and be in general less precise.
A solution:
Let's assume you already ran your pathfinding algorithm and found a linked list of a tiles that you must go through to arrive at target. Let's also assume those tiles cannot become blocked partway through the movement (it is simple to modify the algorithm if they can, though).
I usually do something like this to handle this problem:
Run the pathfinding algorithm, which returns a List, if a path
exists.
character.Path = theListThatAStarReturned;
character.beginMovingToTarget(character.Path[0]);
character.Path.RemoveAt(0);
The beginMovingToTarget() method will determine the velocity vector and also determine the the time needed to arrive at the tile. When the time is reached, we immediately go to the next tile, until the Path is empty. Let's call this time variable character.timeToArrival.
Update():
if (!character.Moving) return; // Or just don't execute the rest of this code.
character.position += character.speed * elapsedSeconds;
character.timeToArrival -= elapsedSeconds;
// Did the character arrive in a tile?
if (character.timeToArrival <= 0)
{
// This will ensure the character is precisely in the tile, not a few pixels veered off.
character.position = character.movingToTile.position;
if (character.Path.Count == 0)
{
character.Moving = false;
// We are at final destination.
}
else
{
character.beginMovingToTarget(character.Path[0]);
character.Path.RemoveAt(0);
}
}
And the beginMovingToTarget(targetTile) function:
this.movingToTile = targetTile;
Vector2 direction;
direction = targetTile.position - this.position;
this.timeToArrival = direction.Length() / this.speedPerSeconds;
direction.Normalize();
direction *= this.speedPerSeconds;
this.speed = direction;
// Here, you may also want to change the character's animation, if you want to, or you may do that directly in the Draw() method based on its speed vector.
Make sure the division is in floats, not integers.

Slight offset in time and distance

I am building a teaching platform for teaching basic Physics.
From my experience in Flash development, I have met similar problems before.
The game time is not the same as real world time. In which case, for example, the distance covered by a projectile can be larger or smaller if the computer lags or for whatever reasons.
Here is a screenshot of the said platform. As shown in the screenshot, I am only developing a lesson for teaching the basic s = v * t relation.
Necessary background
The red line marks the position 69.06284 m, our target distance. The time 11.56087 s is a given value.
The user is supposed to input a speed, moving a projectile from 0 m to the right in order to reach the red line within the time given.
The green line marks the position of the projectile when time is up.
I can assure you that I input an accurate speed up to 5 decimal points so there is no human error in this case.
Ignore the yellow rectangle. It is just a UI element.
The screen is 800 pixels wide, and therefore 10 pixels represent 1 meter.
Way to solve the problem
I'm not sure what to do, quite frankly. But I heard from someone that variable time step represents real world time better. However, Farseer, being a physics simulation engine, should be used with fixed time step, isn't it?
Any advice will be greatly appreciated.
EDIT: here in the screenshot, the actual distance covered by the projectile is ~66.3 m, whereas the theoretical distance is 69.1 m. I also notice that if the target distance (currently 69.1 m) is smaller (red line moves a lot more to the left), the error is smaller.
How do I fire a projectile?
public override void ShootProjectile(Vector2 start, float angle, float speed) {
GameTemplate g = Game as GameTemplate;
Arrow a = new Arrow(Game, start, this);
a.Initialize();
a.Restitution = 0f;
a.Friction = 0f;
a.LinearVelocity = new Vector2(speed, 0);
Console.WriteLine("Speed: "+a.LinearVelocity.X);
_fireTime = g.SinceGameStarts;
//Console.WriteLine("Fire time: "+_fireTime);
_projectiles.Add(a);
}
In the Arrow class, I basically set up Farseer through a CreateBody method:
protected override void CreateBody() {
GameTemplate g = Game as GameTemplate;
Vector2 positionInMeters = _initPos / g.MeterInPixels;
float width = 30f / g.MeterInPixels;
float height = 40f / g.MeterInPixels;
_body = BodyFactory.CreateRectangle(g.GameWorld.World, width, height, 1f, positionInMeters);
_body.BodyType = BodyType.Dynamic;
_origin = new Vector2(15f, 40f);
}
How do I calculate the flight time of projectile?
May be you are curious about the _fireTime = g.SinceGameStart line. SinceGameStart is a getter of the variable _currTime in a GameTemplate. The value of _currTime is updated once every Update(gameTime) call. So if I want to know how long the projectile has been flying, I do this:
time = _currTime - projectile.FireTime

Categories

Resources