Calibrating a gyroscope for different "flat" positions, then extracting x and y tilt - unity3d

I have a top down 2D ball game like the old Labyrinth ball game. My game is played on a mobile phone. The user controls it by tilting the phone. The idea is that when you set the screen at a slant, the ball realistically rolls to the "bottom" of the slant. For example, if the phone is sitting flat on a table, the ball shouldn't be going anywhere. But if I tilt the device up towards me, like a book, then the ball should fall down to the bottom of the screen. This is easy to do with the accelerometer, BUT...
My question is how to calibrate the gyroscope so the game can easily be played from any starting "flat" 3d rotation of the device. I'd like the user to be able to "calibrate" the game so that however they want to hold the device, they can control the ball properly. I'm thinking:
If the phone is flat on the table, then tilting it will work "normally", as described in the first paragraph.
If the phone is upside down, then it should be the opposite of #1. This is for people who want to are holding the phone over their head in bed, or something.
If the phone is, say, held parallel to a wall (like a wall-mounted TV), then it should act as though the wall were a flat table, if that makes sense.
Currently, my code is:
public void OnHitCalibrate() {
_offset = Input.gyro.attitude;
}
public void Update() {
if(!_hasGyro) return;
Quaternion reading = Input.gyro.attitude;
reading *= Quaternion.Inverse(_offset); //subtract the offset
_gyro.rotation = reading;
}
This gets the KIND of input I want, but I don't understand how the axes of the quaternion returned by Input.gyro.attitude help me find my 2d tilt values. For example, when the device is pointed north, and the attitude is the same as Quaternion.Identity, the values seem to change reasonably -- when I turn the attitude into a euler angle, changes in the x value correspond to my y tilt, and changes in the y value correspond to changes in my x tilt. But when I spin around, all that changes, and when I turn the phone more parallel to the wall, the numbers don't seem directly correlated to the axes in the euler angle, even when I use my OnHitCalibrate function and subtract the offset. Is there any way to interpret the attitude so I can always know which way the device is being turned?
My end goal is to distill the attitude into a Vector2 representing force -- where both x and y are between -1 and 1, where 1 represents maximum force up or too the right, -1 is maximum force down or to the left, and 0 means no change in force for the ball.

UPDATE: Solved it! I've attached the class that handles my gyroscope offsets. Hopefully it helps you! Note that it's coded for Unity C#, and has a few of my custom classes in it already.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum TiltModule { GYROSCOPE, ACCELEROMETER, KEYBOARD_ONLY, NONE }
public class CalibratedGyroscope : SingletonKTBehavior<CalibratedGyroscope>
{
public bool _supported;
private Quaternion _off;
private Vector3 _offEuler;
int _activeSemaphore = 0;
private float _degreesForFullTilt = 10;
public Vector2 _lastTilt;
public void Init() {
_off = Quaternion.identity;
_supported = SystemInfo.supportsGyroscope;
}
public bool Activate(bool isActivated) {
if(isActivated) _activeSemaphore++;
else _activeSemaphore--;
_activeSemaphore = Mathf.Max(_activeSemaphore, 0);
if(_activeSemaphore > 0) {
if(_supported) {
Input.gyro.enabled = true;
} else {
return false; //everything not ok; you requested gyro but can't have it!
}
} else {
if(_supported) {
Input.gyro.enabled = false;
}
}
return true; //everything ok;
}
public void Deactivate() {
_activeSemaphore = 0;
}
public void SetCurrentReadingAsFlat() {
_off = Input.gyro.attitude;
_offEuler = _off.eulerAngles;
}
public Vector3 GetReading() {
if(_supported) {
return (Quaternion.Inverse(_off) * Input.gyro.attitude).eulerAngles;
} else {
Debug.LogError("Tried to get gyroscope reading on a device which didn't have one.");
return Vector3.zero;
}
}
public Vector2 Get2DTilt() {
Vector3 reading = GetReading();
Vector2 tilt = new Vector2(
-Mathf.DeltaAngle(reading.y, 0),
Mathf.DeltaAngle(reading.x, 0)
);
//can't go over max
tilt.x = Mathf.InverseLerp( -_degreesForFullTilt, _degreesForFullTilt, tilt.x) * 2 - 1;
tilt.y = Mathf.InverseLerp( -_degreesForFullTilt, _degreesForFullTilt, tilt.y) * 2 - 1;
//get phase
tilt.x = Mathf.Clamp(tilt.x, -1, 1);
tilt.y = Mathf.Clamp(tilt.y, -1, 1);
_lastTilt = tilt;
return tilt;
}
public string GetExplanation() {
Vector3 reading = GetReading();
string msg = "";
msg += "OFF: " + _offEuler + "\n";
Vector2 tilt = new Vector2(
-Mathf.DeltaAngle(reading.y, 0),
Mathf.DeltaAngle(reading.x, 0)
);
msg += "DELTA: " + tilt + "\n";
//can't go over max
tilt.x = Mathf.InverseLerp( -_degreesForFullTilt, _degreesForFullTilt, tilt.x) * 2 - 1;
tilt.y = Mathf.InverseLerp( -_degreesForFullTilt, _degreesForFullTilt, tilt.y) * 2 - 1;
msg += "LERPED: " + tilt + "\n";
//get phase
tilt.x = Mathf.Clamp(tilt.x, -1, 1);
tilt.y = Mathf.Clamp(tilt.y, -1, 1);
msg += "CLAMPED: " + tilt + "\n";
return msg;
}
public void SetDegreesForFullTilt(float degrees) {
_degreesForFullTilt = degrees;
}
}

Related

AI returning to original position

I want my enemy to move back to starting position. He follows me until I get out of his range and then he just stops.
Also i want my skeleton to stop for like 5 sec, and then go back to starting point, any ideas ? I never did anything involving time, exept stopping it.
Here is my script for enemy:
Also here is a screenshoot of inspector on the skeleton: enemy
Here is my script for enemy:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class chase : MonoBehaviour
{
public Transform player;
private Animator anim;
public float LookRadius = 15f;
public Transform enemyStartPos;
// Use this for initialization
void Start()
{
anim = GetComponent<Animator>();
this.enemyStartPos.position = this.transform.position;
}
// Update is called once per frame
void Update()
{
if (!PauseMenu.GameIsPaused)
{
if (Vector3.Distance(player.position, this.transform.position) < 15)
{
Vector3 direction = player.position - this.transform.position;
direction.y = 0;
this.transform.rotation = Quaternion.Slerp(this.transform.rotation, Quaternion.LookRotation(direction), 0.1f);
anim.SetBool("isIdle", false);
if (direction.magnitude > 3)
{
this.transform.Translate(0, 0, 0.05f);
anim.SetBool("isWalking", true);
anim.SetBool("isAttacking", false);
}
else
{
anim.SetBool("isAttacking", true);
anim.SetBool("isWalking", false);
}
}
else
{
if (Vector3.Distance(this.enemyStartPos.position, this.transform.position) >1)
{
Vector3 direction = this.enemyStartPos.position - this.transform.position;
direction.y = 0;
this.transform.rotation = Quaternion.Slerp(this.transform.rotation, Quaternion.LookRotation(direction), 0.1f);
anim.SetBool("isIdle", false);
if (direction.magnitude > 1)
{
this.transform.Translate(0, 0, 0.05f);
anim.SetBool("isWalking", true);
anim.SetBool("isAttacking", false);
}
}
else
anim.SetBool("isIdle", true);
anim.SetBool("isAttacking", false);
anim.SetBool("isWalking", false);
}
}
}
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, LookRadius);
}
}
Before you try to "code" this, think about it in terms of your program design. You have designed and implemented behavior that says "if the bot's position is within X units of the player's position, turn the bot and move it toward the player."
What you describe wanting to do next can be thought of in the same way (which should lead to similar code).. "else, if the bot's position is NOT within X units of the player's position, turn the bot and move it toward [origin]." Note, this means you need to define what [origin] is for the bot. Point being, it makes no difference in the code whether you are moving toward a player or some arbitrary fixed point. The code to move one transform toward another transform is the same.
For "wandering" it's essentially the same thought process: If bot is within X units of [origin] and not following player, pick a random direction and move that way. (better yet, pick a random direction and move that way for some amount of time so your bot doesn't just jitter around origin).

Finding gameObject via direction of input

I have a list of gameObjects added to a List within the area as shown. What I need is a directional input to choose a target from the origin point. I have got it work to get this origin point.
My first attempt of this was to get it via rayCast, but by doing that there were times directional inputs need to directly hit target object by the ray. This is no problem if input was done like case #1.
However, what I need it to really work is when input direction was as if case #2 or #3 the input will still get a target. My second attempt was to do this with sphereCast, but it still needed a target in sphere proximity and multiple targets hit by sphereCast still needed to result in only one and more accurate target selection by input.
Since I have all the transform.position of all the possible targets as well as the origin point I wondered there would be a more elegant way of resolving this via comparing vector3's of these coordinates(origin and targets in the general direction).
Here's my latest approach:
//
// m_targetList is the list containing all GameObjects as GameObjects in other script m_collector. All m_collector does is this.
//
using System.Collections.Generic;
using UnityEngine;
public class TargetSwitcher : MonoBehaviour
{
private TargetCollector m_collector;
private Transform m_origin;
public bool m_targetChanged = false;
public GameObject m_target;
public LayerMask m_targetMask;
private Dictionary<Vector3, float> m_targetInfo = new Dictionary<Vector3, float>();
private void Awake()
{
m_collector = GetComponent<TargetCollector>();
m_origin = GameObject.Find("TargetOrigin").transform;
m_tracker = GameObject.Find("TargetTracker").transform;
m_bound = GetComponent<BoxCollider>();
}
public void UpdateBearing(GameObject origin)
{
m_origin = origin.transform;
foreach (GameObject target in m_collector.m_targetList)
{
Vector3 dir = (target.transform.position - origin.transform.position).normalized
float dist = Vector3.Distance(origin.transform.position, target.transform.position);
m_targetInfo.Add(dir, dist);
}
}
public void SwitchTarget()
{
if (!m_targetChanged)
{
Vector2 dir = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical")).normalized;
// Find closest direction value from Dictionary to dir of here
// Compare distance from origin if multiple targets and choose the nearest
}
}
public void ReturnToIdle()
{
m_origin.position = m_target.transform.position;
m_targetChanged = false;
m_targetInfo.Clear();
}
public struct TargetInfo
{
public Vector3 bearing;
public float distance;
public TargetInfo(Vector3 bearing, float distance)
{
this.bearing = bearing;
this.distance = distance;
}
}
}
Generally, I'm trying to compare the normalized vector of directional input to the normalized vector from the origin to each target before SwitchTarget(). The input method here is Gamepad axis x and y as Horizontal and Vertical.
Reposting this question since a provided answer was very far from the question and marked as duplicate(Given answer was about finding gameObject by distance only, this question is about direction and distance portion is to compare second-handedly when multiple items were found in the direction)
Edit
After some trials with dot product now I'm sure this is much likely where I want to head. There are much inconsistency I need to get on with, though.
Here's my most recent attempt:
private void Update()
{
UpdateBearing();
Vector3 input = new Vector3(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"), 0);
if (input != Vector3.zero)
{
SwitchTarget();
}
}
public void UpdateBearing(GameObject origin)
{
m_origin.position = origin.transform.position;
foreach (GameObject target in m_collector.m_targetList)
{
Vector3 dir = (target.transform.position - origin.transform.position).normalized;
if (!m_targetInfo.ContainsKey(target))
{
m_targetInfo.Add(target, dir);
}
}
}
public void SwitchTarget()
{
GameObject oldTarget = m_collector.m_target;
if (!m_targetChanged)
{
Vector3 dir = new Vector3(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"), 0).normalized;
Debug.DrawRay(m_origin.position, dir * 100, Color.yellow, 0.5f);
foreach (KeyValuePair<GameObject, Vector3> possibleTarget in m_targetInfo)
{
float dot = Vector3.Dot(dir, possibleTarget.Value);
if (dot > 0.5f) // Compare DP difference of added dot and input dot
{
GameObject newTarget = possibleTarget.Key;
if (oldTarget != newTarget)
{
Debug.Log(possibleTarget.Value + " // " + dot);
m_target = newTarget;
m_collector.m_target = newTarget;
m_targetChanged = true;
}
}
}
}
}
With this, I'm kind of getting gameObject selection without raycasting and missing any targets. However, I'm sure I need better case comparison than if(dot > 0.5f). Also, my rough assumption is that if I don't update the value of the dictionary m_targetInfo for each Key I'd have another inconsistency if those targets ever move. Anyways, I'm still confused how properly utilize this to achieve my end goal.
Since you have all the desired game objects in the area you can create a for loop and check the angle between your look direction and their position, if it is lower than some value (you can make it super low so it's precise or a little bit higher to allow for some margin of error) put it in a list of gameobjects and if there's more than one object there get the closest one.
The code for getting closest object in angle would look something like this:
GameObject CheckObjects()
{
List<GameObject> InAngle = new List<GameObject>();
for(int i = 0; i < YourObjectsList.Count; i++)
{
GameObject tested = YourObjectsList[i];
Vector3 dir = tested.transform.position - origin.transform.forward;
// I'm assuming here that youre rotating your origin with the
directional input, not then instead of origin.transform.forward
place there your directional vector3
float angle = Vector3.Angle(dir, tested.transform.position);
if(angle <= desiredAngle)
{
InAngle.Add(tested);
}
}
GameObject closest = null;
for(int j = 0; j < InAngle.Count; i++)
{
GameObject tested = InAngle[i];
Vector3 dir1 = tested.transform.position - origin.transform.position;
Vector3 dir2 = closest.transform.position - origin.transform.position;
if(!closest)
{
closest = tested;
}
else
{
if(dir2.sqrMagnitude > dir1.sqrMagnitude)
{
closest = tested;
}
}
}
return closest;
}

Restricting movement of a 2d object to backward only in UNITY

I'm making a simple project in Unity where there is a Ball attached to a SpringJoint2d component the ball is on an angled slope, like in the image below:
I simply want the user to be able to drag the ball backward along the edge of the slope only,in other words I don't want the user to be able to move the ball away from the slope or into it.
I'v been trying several ways I thought could do the job hers the script of the dragging with what I tried:
(This Is the updated version)
public class ball : MonoBehaviour
{
public Rigidbody2D rb;
public Transform spring;
public Transform calcpoint;
private Vector3 start;
private Vector3 end;
private bool isPressed = false;
RaycastHit2D[] hits = new RaycastHit2D[2];
RaycastHit2D[] hits2 = new RaycastHit2D[2];
float factor = 0;
private void OnMouseDown()
{
if (!isPressed)
{
isPressed = true;
rb.isKinematic = true;
}
}
private void OnMouseUp()
{
isPressed = false;
rb.isKinematic = false;
StartCoroutine(release());
}
/// <summary>
/// release the ball from the spring joint after a small amount of time
/// </summary>
/// <returns></returns>
IEnumerator release()
{
yield return new WaitForSeconds(0.1f);
rb.GetComponent<SpringJoint2D>().enabled = false;
}
// Update is called once per frame
void Update()
{
if (isPressed)
{
if (Vector3.Distance(spring.position, rb.position) > 3f || spring.position.x < (rb.position.x - 1)) return;//restrict the dragging of the ball to not go beyond the spring point and not too far back
float angle = 0;
if (checkGround() > 1)//if we hit the slope with the ray cast downward from the mouse/Tap position
{
angle = Mathf.Abs(Mathf.Atan2(hits[1].normal.x, hits[1].normal.y) * Mathf.Rad2Deg); //get angle
factor = (float)(((45 - angle) * 0.02) + 1) * (angle / 45);//an inaccurate formula to offset the ball to be on top of the slope that works just fine with some glitches
rb.position = hits[1].point + new Vector2(0, factor * 1f);//position the ball at the point were the ray cast downward from the mouse hit
//(that puts the ball center on the line of the slope) so I offset it usinf the formula above
}
}
}
private int checkGround()
{
int h = Physics2D.RaycastNonAlloc(Camera.main.ScreenToWorldPoint(Input.mousePosition), -Vector2.up, hits); //cast downwards
return h;
}
}
here are the settings on the ball:
and the slope setup:
The dragging of the ball works fine ,at one point the player could drag it in the air or into the slope, I managed to fix that with the new code so now the player could only drag it on the edge, though my calculations are still a bit flawed and when the slopes angle is changed the ball would dip a bit inside the slope and that causes some problems at release.
The method used to try to solve the problem is simple, when the player start dragging the ball I cast a ray from the mouse downward and pit the ball on the point of impact with the slope ,offsetting it to sit on top of it,right ow the problem is that the offsetting part is not accurate enough.
I hope I explained myself a bit better this time Thanks:)
After lots of trial and error I did manage to come up with a perfect solution to the problem so I thought I might as well share the answer maybe it will help someone.
here is the updated code I changed my method of restricting the movement completely now I use simple linear line equation as shown below:
private void OnMouseDown()
{
if (!isPressed)
{
//cast a ray on the slope
if (checkGround() > 1)
{
angle = Mathf.Abs(Mathf.Atan2(hits[1].normal.x, hits[1].normal.y) * Mathf.Rad2Deg); //get angle
slope = Mathf.Tan(angle * Mathf.Deg2Rad);//get the slope steepiness
}
isPressed = true;
rb.isKinematic = true;
}
}
private void OnMouseUp()
{
isPressed = false;
rb.isKinematic = false;
StartCoroutine(release());
}
void Update() {
if (isPressed)
{
xMove = Camera.main.ScreenToWorldPoint(Input.mousePosition).x - spring.position.x;//get how much the mouse moved backward or forward
if (xMove < -3f ) xMove = -3f; //restrict the drag range to 3 backward
if (xMove > 0.3f) xMove = 0.3f;//restrict the drag range to 0.3 forward
xpos = spring.position.x+xMove;//since the ball and the spring start at exactly the same position the new ball's x position would be the spring x + the x movement we calculated above
ypos = (xMove * slope)- spring.position.y; //the y posistion would be y=mx+b so the the x movement * the slop steepiness - the starting y position
rb.position = new Vector2(xpos, -ypos);//set the new position of the ball
}
}
private int checkGround()
{
int h = Physics2D.RaycastNonAlloc(Camera.main.ScreenToWorldPoint(Input.mousePosition), -Vector2.up, hits); //cast downwards
return h;
}

Unity 2D Rigidbody.velocity and transform.position causes random 'ghost' frame

I am creating a 2D platform / adventure game that uses flip screen to manage changing rooms.
My character moves using RigidBody.velocity, this works as I need it to, when my character reaches one of the screen's left or right extremes I use transform.position to move (effectively teleporting) the character to the other side of the screen, I then change the room. This gives the effect of moving to the next room along.
My flip screen code works fine, the changing room effect works without a problem, in fact almost everything works just as I like it, except that I get 1 frame where the character appears at a random point in between where the character moved from and where he moved to.
For example, my room is 16 tiles across, if my character moves left and passes x position '0' (the very left of the screen) I make a transform.position to x position 16 (keeping the y position constant) and then change rooms. I have logged the movement and what happens is (pseudo code)..
x < 0 (the change room code is run)
translate.position to x=16 (the far right)
x = 16 (this is correct)
The room changes
x = some float somewhere between 0 and 16 (this is NOT correct)
x = 16 (back to the correct position again)
This is my player code:
using UnityEngine;
using System.Collections;
public class MKManager : MonoBehaviour {
public float maxSpeed = 0.10f;
bool isFacingRight = false;
Rigidbody2D myBody;
bool grounded = true;
public static int liftdirection = 0;
void Start (){
myBody = GetComponent<Rigidbody2D> ();
}
void Update () {
checkRoomBounds ();
}
void FixedUpdate(){
float h = Input.GetAxisRaw ("Horizontal");
if (grounded) {
myBody.velocity = new Vector2 (h * maxSpeed, myBody.velocity.y);
if (h < 0) { //Moving Left
if (isFacingRight) {
FlipAnimation ();
}
} else if (h > 0) { //Moving Right
if (!isFacingRight) {
FlipAnimation ();
}
}
}
}
void checkRoomBounds(){
if (transform.position.x < 0) {
transform.position = new Vector3 (16.0f, transform.position.y, 0.0f);
GameManager.instance.moveRoom (-1);
} else if (transform.position.x > 16) {
transform.position = new Vector3 (0.0f, transform.position.y, 0.0f);
GameManager.instance.moveRoom (1);
}
}
void FlipAnimation(){
isFacingRight = !isFacingRight;
Vector3 theScale = transform.localScale;
theScale.x *= -1;
transform.localScale = theScale;
}
I have tried calling checkRoomBounds() in update, fixed update and lateupdate, all give the same result. If I do not call the moveRoom() function the same happens but the room is not changed, but the moveRoom() code is...
public void moveRoom(int direction){
if (room == 0) {
room = (10*currentFloor) + 1;
} else {
room += direction;
if (room == -1) {
room = 68;
} else if (room == 69) {
room = 0;
} else if (room % 10 == 9 || room % 10 == 0) {
//room += (direction * 2);
room = 0;
}
}
boardScript.SetupScene (room);
}
I've run out of ideas and don't know where I'm going wrong. Any help would be much appreciated.
Many thanks in advance

Unity3d - Moving camera literally cuts FPS in half?

I'm having an issue with the cameras in Unity. When the camera moves through any means it seems to cut my FPS in half if not more. It's not really noticeable on PC, unless I'm looking at the from from 800fps to about 150fps, however on mobile it'll cut the smooth 60fps I'm getting to 20fps on a Nexus 4. It's absolutely devastating.
Here's the properties for the camera I'm using & the script HOWEVER this issue still happens without ANY of these components and a completely reset camera component:
public class ViewDrag : MonoBehaviour
{
public Vector3 hit_position = Vector3.zero;
public Vector3 current_position = Vector3.zero;
public Vector3 camera_position = Vector3.zero;
public Vector2 min_position;
public Vector2 max_position;
float z = 0.0f;
MouseHolder holder;
hider sidebarHide;
GameObject gameStructure;
// Use this for initialization
void Start()
{
gameStructure = GameObject.FindGameObjectWithTag("GameStructure");
holder = gameStructure.GetComponent<MouseHolder>();
sidebarHide = GameObject.FindGameObjectWithTag("SidebarBG").GetComponent<hider>();
}
void Update()
{
if (Input.GetMouseButtonDown(0))
{
hit_position = Input.mousePosition;
camera_position = transform.position;
}
if (Input.GetMouseButton(0))
{
current_position = Input.mousePosition;
LeftMouseDrag();
}
if (!sidebarHide.isHidden)
{
//GetComponent<Camera2DFollow>().enabled = true;
}
if(gameStructure.GetComponent<ExecuteMovement2>().isExecuted)
{
//GetComponent<Camera2DFollow>().enabled = true;
}
}
void LeftMouseDrag()
{
// From the Unity3D docs: "The z position is in world units from the camera." In my case I'm using the y-axis as height
// with my camera facing back down the y-axis. You can ignore this when the camera is orthograhic.
//current_position.z = hit_position.z = camera_position.y;
// Get direction of movement. (Note: Don't normalize, the magnitude of change is going to be Vector3.Distance(current_position-hit_position)
// anyways.
Vector3 direction = Camera.main.ScreenToWorldPoint(current_position) - Camera.main.ScreenToWorldPoint(hit_position);
// Invert direction to that terrain appears to move with the mouse.
direction = direction * -1;
Vector3 position = camera_position + direction;
if (position.x < max_position.x && position.x > min_position.x && position.y < max_position.y && position.y > min_position.y)
{
if (!EventSystem.current.IsPointerOverGameObject() && holder.fromSlot == null )
{
if (sidebarHide.isHidden)
{
if (!gameStructure.GetComponent<ExecuteMovement2>().isExecuted)
{
//GetComponent<Camera2DFollow>().enabled = false;
transform.position = position;
}
}
}
}
}
}
Has anyone got an idea why this happens and how I can work around it if not fix it?
Through closer inspection I think it has something to do with the Canvas being screen space.. But it's kind of needed that way. Again, any workaround?
Check comments for profiler screenshot.
Problem Solved:
In the profiler I found that Canvas.SendWillRenderCanvases() was causing huge spikes. I solved the issue completely by turning off Pixel Perfect in the Canvas. Smooth as butter now.