I'm making a 3D game in unity where the object should move forward and backward as the android device moves/accelerates in the Z axes. ie. When the player moves the devise in the direction of the +ve Z axis, the object should move forward, and when the player moves the devise in the direction of the -ve Z axis, the object should move backward.
This game is a multiplayer game, and the players will move in a large football field.
My idea to do this is using the accelerometer to calculate the acceleration of the device, then integrate the data of acceleration to get the device speed in the Z axis. and use the speed to move the device.
Using this equation
V2=V1 + ΔA . ΔT
Where
V2 : final velocity.
V1 : initial velocity.
ΔA : difference between the initial and final acceleration.
ΔT : difference between the initial and final time.
At first I tried to use kinematic equations to calculate the final speed, but I realized then that it can be only used when acceleration is constant. So a friend of me who studies physics differentiated this equation for me to use it when acceleration is variable.
I know that there will be some error in calculating the accurate displacement, and that the error will increase after the integration of acceleration, but this small percentage of error is okay for my application; I thought at first in using GPS instead of accelerometer but I found that GPS accuracy will be less than the sensors.
I know also that the error will be incredibly high after some time, so I reset the values of acceleration and velocity every 10 seconds. I'm also using a low-pass filter to reduce the noise of the sensor.
public class scriptMove : MonoBehaviour
{
const float kFilteringFactor = 0.1f;
public Vector3 A1;
public Vector3 A2;
public Vector3 A2ramping; // for the low-pass filter
public Vector3 V1;
public Vector3 V2;
public int SpeedFactor=1000; //this factor is for increasing acceleration to move in unity world
void resetAll()
{
Input.gyro.enabled = true;
A2 = Vector3.zero;
V1 = Vector3.zero;
V2 = Vector3.zero;
A2ramping = Vector3.zero;
}
// Use this for initialization
void Start()
{
InvokeRepeating("resetAll", 0, 10);
}
//http://stackoverflow.com/a/1736623
Vector3 ramping(Vector3 A)
{
A2ramping = A * kFilteringFactor + A2ramping * (1.0f - kFilteringFactor);
return A - A2ramping;
}
void getAcceleration(float deltaTime)
{
Input.gyro.enabled = true;
A1 = A2;
A2 = ramping(Input.gyro.userAcceleration) * SpeedFactor;
V2 = V1 + (A2 - A1) * deltaTime;
V1 = V2;
}
//Update is called once per frame
void Update()
{
getAcceleration(Time.deltaTime);
float distance = -1f;
Vector3 newPos = transform.position;
transform.Translate(Vector3.forward * Time.deltaTime * V2.z * distance);
}
}
The problem:
My code doesn't work always as expected when I move with the device;
Sometimes when I move forward (in the +ve Z axis of the device) the object moves forward also, but sometimes it doesn't move at all.
Sometimes when I'm still in my position the object moves alone by itself.
Sometimes when I move forward and suddenly stop, the object does not stop.
My questions:
Are those strange behaviors because of the accuracy of the device, or is there something I'm missing in my code.
If I'm missing something in my code, What is it?
I searched a lot about methods to get the most accurate position of the device, and I found that I can integrate GPS with accelerometer, how can I do this with my code in unity?
I don't know if you still need it but if anyone in the future need I will post what I found:
When I first used the Unity accelerometer I was thinking that the output was simply the device's rotation, and in a way is, but more than that it give us the acceleration but in order to have this value your must filter the gravity then you have the value.
I created a plugin for Android and get the Android's Accelerometer and Linear Accelerometer, the standard accelerometer give us a similar value of Unity accelerometer, the main difference is that is raw, and unity give us some refined output, for example if your game is Landscape unity automatically inverts X and Y axis, while the Android raw information don't. And the Linear accelerometer that is a fusion of sensors including the standard accelerometer, the output is acceleration without the gravity but the speed is terrible, while both (Unity and Android) accelerometer are updated every frame, the Linear accelerometer was updated every 4 to 5 frames what is a terrible rate for user's experience.
But going for Android plugin was great because it gave the light how to solve my problem of removing gravity from Unity Accelerometer, as you can find here:
https://developer.android.com/reference/android/hardware/SensorEvent.html
Under Sensor.TYPE_ACCELEROMETER
If we tilt the device, Unity Accelerometer gives you a value, for example 6, and while you hold in that position this is the value, is not a wave, if you tilt back really fast or really slowly it will give the value from 6 to 0 (supposing you move back to zero), what I wanted and accomplished with the code I'm sharing below is, when you turn it does a wave, returns the acceleration and back to zero, so is a acceleration deceleration curve, if you turn it really slow the acceleration returned is almost zero, if you turn it fast the response reflects this speed. If this is the result you are looking for you just need to create this class:
using UnityEngine;
public class AccelerometerUtil
{
public float alpha = 0.8f;
public float[] gravity = new float[3];
public AccelerometerUtil()
{
Debug.Log("AccelerometerUtil Init");
Vector3 currentAcc = Input.acceleration;
gravity[0] = currentAcc.x;
gravity[1] = currentAcc.y;
gravity[2] = currentAcc.z;
}
public Vector3 LowPassFiltered()
{
/*
https://developer.android.com/reference/android/hardware/SensorEvent.html
gravity[0] = alpha * gravity[0] + (1 - alpha) * event.values[0];
gravity[1] = alpha * gravity[1] + (1 - alpha) * event.values[1];
gravity[2] = alpha * gravity[2] + (1 - alpha) * event.values[2];
linear_acceleration[0] = event.values[0] - gravity[0];
linear_acceleration[1] = event.values[1] - gravity[1];
linear_acceleration[2] = event.values[2] - gravity[2];
*/
Vector3 currentAcc = Input.acceleration;
gravity[0] = alpha * gravity[0] + (1 - alpha) * currentAcc.x;
gravity[1] = alpha * gravity[1] + (1 - alpha) * currentAcc.y;
gravity[2] = alpha * gravity[2] + (1 - alpha) * currentAcc.z;
Vector3 linearAcceleration =
new Vector3(currentAcc.x - gravity[0],
currentAcc.y - gravity[1],
currentAcc.z - gravity[2]);
return linearAcceleration;
}
}
Once you have this class, just create it into your MonoBehaviour:
using UnityEngine;
public class PendulumAccelerometer : MonoBehaviour
{
private AccelerometerUtil accelerometerUtil;
// Use this for initialization
void Start()
{
accelerometerUtil = new AccelerometerUtil();
}
// Update is called once per frame
void Update()
{
Vector3 currentInput = accelerometerUtil.LowPassFiltered();
//TODO: Create your logic with currentInput (Linear Acceleration)
}
}
Notice that the TODO on MonoBehaviour is to be implemented, is up to you create an algorithm how to handle this values, in my case I found really useful to create a Graphic output and analise my acceleration before write it.
Really hope it helps
The movement is based on acceleration, so it will be dependant on how quickly you rotate your device. This is also why the object does not stop when you do. Suddenly stopping your device is a lot of acceleration, which then gets added to the amount the object is translating, which causes it to move a much greater distance than you intend.
I think what may be easier for you is to use the attitude of the gyro rather than the userAcceleration. The attitude returns a quaternion of the rotation of the device.
https://docs.unity3d.com/ScriptReference/Gyroscope-attitude.html
(You'll have to do a bit of experimenting, because I don't know what (0,0,0,0) on the attitude is. It could mean the device is flat on a table, or that it is sideways being held in front of you, or it could simply be the orientation of the device when the app first starts, I don't know how Unity initialises it.)
Once you have that Quaternion, you should be able to adjust velocity directly based off of how far in either direction the user is rotating the device. So if they rotate +ve Z-axis, you move forwards, if they move more, it moves faster, if they move -ve Z-axis, it slows down or moves backwards.
Regarding the GPS coordinates, you need to use LocationService for that.
http://docs.unity3d.com/ScriptReference/LocationService.html
You'll need to start LocationServices, wait for them to initialise (this bit is important), and then you can query the different parts using LocationService.lastData
I am trying to do the same thing as you. It is not trivial to get device's linear acceleration using just one sensor. You will need to implement a solution using both the accelerometer and the gyroscope (sensor fusion). Google has an android specific solution which behaves differently according to how sophisticated your device is. It uses multiple sensors as well as low/high pass filters (see Android TYPE_LINEAR_ACCELERATION sensor - what does it show?).
Google's Tango tablet should have sensors to address such issues.
If you want to get accelerometer data in Unity, try:
public class scriptMove : MonoBehaviour{
private float accelX;
private float accelY;
private float accelZ;
void Update(){
accelX = Input.acceleration.x;
accelY = Input.acceleration.y;
accelZ = Input.acceleration.z;
//pass values to your UI
}
}
What I am currently trying is to port Google's solution to Unity using IKVM.
This link might be helpful too:
Unity3D - Get smooth speed and acceleration with GPS data
Related
I need to have a game object point north AND I want to combine this with gyro.attitude input. I have tried, unsuccessfully, to do this in one step. That is, I couldn't make any gyro script, which I found on the net, work with the additional requirement of always pointing north. Trust me, I have tried every script I could find on the subject. I deduced that it's impossible and probably was stupid to think it could be done; at least not this way (i.e. all-in-one). I guess you could say I surmised that you can't do two things at once. Then I thought possibly I could get the same effect by breaking-up the duties. That is, a game object that always points north via the Y axis. Great, got that done like this:
_parentDummyRotationObject.transform.rotation = Quaternion.Slerp(_parentDummyRotationObject.transform.rotation, Quaternion.Euler(0, 360 - Input.compass.trueHeading, 0), Time.deltaTime * 5f);
And with the game object pointing north on the Y, I wanted to add the second game-object, a camera in this case, with rotation using gyro input on the X and Z axis. The reason I have to eliminate the Y axes on the camera is because I get double rotation. With two things rotating at once (i.e. camera and game-object), a 180 degree rotation yielded 360 in the scene. Remember I need the game object to always point north (IRL) based on the device compass. If my device is pointing towards the East, then my game-object would be rotated 90 degrees in the unity scene as it points (rotation) towards the north.
I have read a lot about gyro camera controllers and one thing I see mentioned a lot is you shouldn't try to do this (limit it) on just 1 or 2 axis, when using Quaternions it's impossible when you don't know what you're doing, which I clearly do not.
I have tried all 3 solutions from this solved question: Unity - Gyroscope - Rotation Around One Axis Only and each has failed to rotate my camera on 1 axis to satisfy my rotational needs. Figured I'd try getting 1 axis working before muddying the waters with the 2nd axis. BTW, my requirements are simply that the camera should only rotate on 1 axis (in any orientation) based on the X axis of my device. If I could solve for X, then I thought it'd be great to get Z gyro input to control the camera as well. So far I cannot get the camera controlled on just 1 axis (X). Anyway, here are my findings...
The first solution, which used Input.gyro.rotationRateUnbiased, was totally inaccurate. That is, if I rotated my device around a few times and then put my phone/device down on my desk, the camera would be in a different rotation/location each time. There was no consistency. Here's my code for the first attempt/solution:
<code>
private void Update()
{
Vector3 previousEulerAngles = transform.eulerAngles;
Vector3 gyroInput = Input.gyro.rotationRateUnbiased;
Vector3 targetEulerAngles = previousEulerAngles + gyroInput * Time.deltaTime * Mathf.Rad2Deg;
targetEulerAngles.y = 0.0f;
targetEulerAngles.z = 0.0f;
transform.eulerAngles = targetEulerAngles;
}
</code>
The second solution was very consistent in that I could rotate my device around and then put it down on the desk and the unity camera always ended up in the same location/rotation/state so-to-speak. The problem I had was the camera would rotate on the one axis (X in this case), but it did so when I rotated my device on either the y or x axis. Either type of rotation/movement of my phone caused the unity camera to move on the X. I don't understand why the y rotation of my phone caused the camera to rotate on X. Here is my code for solution #2:
private void Start()
{
Input.gyro.enabled = true;
startEulerAngles = transform.eulerAngles;
startGyroAttitudeToEuler = Input.gyro.attitude.eulerAngles;
}
private void Update()
{
Vector3 deltaEulerAngles = Input.gyro.attitude.eulerAngles - startGyroAttitudeToEuler;
deltaEulerAngles.y = 0.0f;
deltaEulerAngles.z = 0.0f;
transform.eulerAngles = startEulerAngles - deltaEulerAngles;
}
The 3rd solution: I wasn't sure how to complete this last solution, so it never really worked. With the 2 axis zeroed-out, the camera just flipped from facing left to right and back, or top to bottom and back; depending on which axis were commented out. If none of the axis were commented-out (like the original solution) the camera would gyro around on all axis. Here's my code for attempt #3:
private void Start()
{
_upVec = Vector3.zero;
Input.gyro.enabled = true;
startEulerAngles = transform.eulerAngles;
}
private void Update()
{
Vector3 gyroEuler = Input.gyro.attitude.eulerAngles;
phoneDummy.transform.eulerAngles = new Vector3(-1.0f * gyroEuler.x, -1.0f * gyroEuler.y, gyroEuler.z);
_upVec = phoneDummy.transform.InverseTransformDirection(-1f * Vector3.forward);
_upVec.z = 0;
// _upVec.x = 0;
_upVec.y = 0;
transform.LookAt(_upVec);
// transform.eulerAngles = _upVec;
}
Originally I thought it was my skills, but after spending a month on this I'm beginning to think that this is impossible to do. But that just can't be. I know it's a lot to absorb, but it's such a simple concept.
Any ideas?
EDIT: Thought I'd add my hierarchy:
CameraRotator (parent with script) -> MainCamera (child)
CompassRotator (parent) -> Compass (child with script which rotates parent)
I'd do this in the following way:
Camara with default 0, 0, 0 rotation
Screenshot
Object placed at the center of the default position of the camera.
Script for the Camera:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour
{
Camera m_MainCamera;
// Start is called before the first frame update
void Start()
{
// Disable the sleep timeout during gameplay.
// You can re-enable the timeout when menu screens are displayed as necessary.
Screen.sleepTimeout = SleepTimeout.NeverSleep;
// Enable the gyroscope.
if (SystemInfo.supportsGyroscope)
{
Input.gyro.enabled = true;
}
m_MainCamera = Camera.main;
m_MainCamera.enabled = true;
}
// Update is called once per frame
void Update()
{
if (m_MainCamera.enabled)
{
// First - Grab the Gyro's orientation.
Quaternion tAttitude = Input.gyro.attitude;
// The Device uses a 'left-hand' orientation, we need to transform it to 'right-hand'
Quaternion tGyro = new Quaternion(tAttitude.x, tAttitude.y, -tAttitude.z, -tAttitude.w);
// the gyro attitude is tilted towards the floor and upside-down reletive to what we want in unity.
// First Rotate the orientation up 90deg on the X Axis, then 180Deg on the Z to flip it right-side up.
Quaternion tRotation = Quaternion.Euler(-90f, 0, 0) * tGyro;
tRotation = Quaternion.Euler(0, 0, 180f) * tRotation;
// You can now apply this rotation to any unity camera!
m_MainCamera.transform.localRotation = tRotation;
}
}
}
With this script my Object always face SOUTH no matter what.
If you want the object to face NORTH you just have to turn the view 180º on the Y axis as a last rotation:
Quaternion tRotation = Quaternion.Euler(-90f, 0, 0) * tGyro;
tRotation = Quaternion.Euler(0, 0, 180f) * tRotation;
//Face NORTH:
tRotation = Quaternion.Euler(0,180f, 0) * tRotation;
Hope this might help ;)
Lets say I'm working in a 2D plane, with a person in the bottom of my screen, and an object that moves down along the Y-axis.
The moving object has following movement code:
transform.position += new Vector3 (0, -1, 0) * speed * Time.deltaTime;
Now, as I want to be able to modify how long it takes for the object to connect with the player, I created this, where spawnTimeDistance is our key factor:
Instantiate (obstacle, player.transform.position + new Vector3(-0.50F , (spawnTimeDistance + obstacleDimensionY + pScript.playerDimensionY), -1), Quaternion.identity);
As can be seen, spawnTimeDistance is a place-holder for the objects Y-distance to the player. However, using this code I get great inconsistencies with my results, especially when comparing devices. I suspect this is caused by distance being FPS dependant, instead of actual time dependant.
To fix this I believe I should implement Time.deltaTime in an extended manner, but I'm insecure on how.
EDIT - spawnDistanceTime related:
void Start() {
spawnTimeDistance = 4F;
}
and
Vector2 sprite_size = obstacle.GetComponent<SpriteRenderer> ().sprite.rect.size;
Vector2 spriteScale = obstacle.transform.localScale;
float sizeAndScaleY = sprite_size.y * spriteScale.y;
float obstacle_local_sprite_sizeY = (sizeAndScaleY / obstacle.GetComponent<SpriteRenderer> ().sprite.pixelsPerUnit) * 0.5F;
obstacleDimensionY = obstacle_local_sprite_sizeY;
Above code, same thing for my player.
Edited the Instantiate to include the obstacleDimensions, had them removed before as I thought it would give a simpler question.
I have a very basic car model. So far I have managed to get the wheels rotating in one of two ways:
The wheel rotates on the local Z axis when it turns
The wheel rotates on the local Y axis when it drives forward or backward
They both work fine, but I just cannot get them both to work at the same time. I am absolutely stumped and its starting to piss me off! I have also been trying to follow this video (https://www.youtube.com/watch?v=0jvJ2FMvbBs) and have the following error:
Cannot modify a value type return of 'UnityEngine.Transform.rotation'. Consider storing the value in a temporary variable.
Could someone take a look at my code and show me what I'm doing wrong? Thanks in advance.
Code:
public Wheels[] wheel;
public float enginePower = 20f;
public float turnPower = 20f;
void FixedUpdate () {
float torque = Input.GetAxis("Vertical") * enginePower;
float turnSpeed = Input.GetAxis("Horizontal") * turnPower;
//4 Wheel Drive
wheel[0].Move(torque);
wheel[1].Move(torque);
wheel[2].Move(torque);
wheel[3].Move(torque);
//Front Wheel Steering
wheel[0].Turn(turnSpeed);
wheel[1].Turn(turnSpeed);
}
void Update () {
wheel[0].transform.Rotate(0, 0, wheel[0].GetComponent<WheelCollider>().rpm / 60 * 360 * Time.deltaTime);
//Only one wheel is shown for simplicity
wheel[0].transform.rotation.y = wheel[0].GetComponent<WheelCollider>().steerAngle - wheel[0].transform.rotation.z;
}
The "rotation" variable of a Transform is a Quaternion thus what you are trying to do in your second line of your code in Update will break the rotation. Instead, you'll have to use transform.Rotate again and access the z value of the rotation from the transform's local euler angles.
wheel[0].transform.Rotate(0, wheel[0].GetComponent<WheelCollider>().steerAngle - wheel[0].transform.localEulerAngles.z, 0);
I have the model representing the player's ship gradually leaning when the player strafes. For instance, here's the code that leans the ship right:
In Update() of the Game class:
if (ship.rightTurnProgress < 1 && (currentKeyState.IsKeyDown(Keys.D)))
{
ship.rightTurnProgress += (float)gameTime.ElapsedGameTime.TotalSeconds * 30;
}
In Update() of the Ship class:
if (currentKeyState.IsKeyDown(Keys.D))
{
Velocity += Vector3.Right * VelocityScale * 10.0f;
RotationMatrix = Matrix.CreateRotationX(MathHelper.PiOver2) *
Matrix.CreateRotationY(0.4f * rightTurnProgress);
}
This is what I'm attempting to do to make it ease back out of the lean when it stops strafing:
In Update() of the Game class:
if (ship.rightTurnProgress > 0 && currentKeyState.IsKeyUp(Keys.D))
{
ship.rightTurnProgress -= (float)gameTime.ElapsedGameTime.TotalSeconds * 30;
}
In Update() of the Ship class:
if (currentKeyState.IsKeyUp(Keys.D) && rightTurnProgress > 0)
{
RotationMatrix = Matrix.CreateRotationX(MathHelper.PiOver2) *
Matrix.CreateRotationY(-0.4f * rightTurnProgress);
}
Since easing into the lean works no problem, I thought easing out of the lean would be a simple matter of reversing the process. However, it tends to not go all the way back to the default position after a long strafe. If you tap the key, it snaps all the way back to the full lean of the -opposite- direction. This isn't what I expected at all. What am I missing here?
I suggest you represent the rotation of you ship as a quaternion. That way you can use an interpolation function such as slerp. Simply have a second quaternion that represents you targeted lean angle and the ship will smoothly rotate until it achieves the targeted angle.
Here's a good tutorial on quaternions. If you want to avoid quaternions use MathHelper.Lerp to smoothly transition from the current value to the target.
if (currentKeyState.IsKeyDown(Keys.D))
{
ship.TurnProgress = MathHelper.Lerp(ship.TurnProgress, 1, somefloat * timeDelta);
}
else if (currentKeyState.IsKeyDown(Keys.a))
{
ship.TurnProgress = MathHelper.Lerp(ship.TurnProgress, -1, somefloat * timeDelta);
}
else (currentKeyState.IsKeyDown(Keys.D))
{
ship.TurnProgress = MathHelper.Lerp(ship.TurnProgress, 0, somefloat * timeDelta);
}
Edit: Also there is a GameDev stack overflow so check it out if you have more questions.
Unless you know how long the turn is or you have some kind of acceleration vector you will have to wait until the turn is stopped before returning the sprite angle to neutral, then what happens when the player turns left before the sprite has reached its neutral position? I assume that when you turn right using RightTurnProgress you also have a LeftTurnProgress I suggest you combine them into one variable to keep it smooth and avoid the snapping effect you are getting.
You are creating an 'absolute' rotation matrix so you don't need to flip the sign to -0.4f. Why not just have a variable called ship.lean and calculate the rotation matrix every update. Then you just need logic to ease ship.lean between -1 (left lean) and 1 (right lean) or 0 for no lean.
I'm using the Motion API and I'm trying to figure out a control scheme for the game I'm currently developing.
What I'm trying to achive is for a orienation of the device to collelate directly to a position. Such that tilting the phone forward and to the left represents the top left position and back to the right would be the bottom right position.
Photos to make it clearer (the red dot would be the calculated position).
Forward and Left
Back and Right
Now for the tricky bit. I also have to make sure that the values take into account left landscape and right landscape device orientations (portrait is the default so no calculations would be needed for it).
Has anyone done anything like this?
Notes:
I've tried using the yaw, pitch, roll and Quaternion readings.
I just realised the behaviour I'm talking about would be alot like a level.
Sample:
// Get device facing vector
public static Vector3 GetState()
{
lock (lockable)
{
var down = Vector3.Forward;
var direction = Vector3.Transform(down, state);
switch (Orientation) {
case Orientation.LandscapeLeft:
return Vector3.TransformNormal(direction, Matrix.CreateRotationZ(-rightAngle));
case Orientation.LandscapeRight:
return Vector3.TransformNormal(direction, Matrix.CreateRotationZ(rightAngle));
}
return direction;
}
}
You would like to control object on screen using acceleration sensor.
protected override void Initialize() {
...
Accelerometer acc = new Accelerometer();
acc.ReadingChanged += AccReadingChanged;
acc.Start();
...
}
This is method that calculates position of object
void AccReadingChanged(object sender, AccelerometerReadingEventArgs e) {
// Y axes is same in both cases
this.circlePosition.Y = (float)e.Z * GraphicsDevice.Viewport.Height + GraphicsDevice.Viewport.Height / 2.0f;
// X axes needs to be negative when oriented Landscape - Left
if (Window.CurrentOrientation == DisplayOrientation.LandscapeLeft)
this.circlePosition.X = -(float)e.Y * GraphicsDevice.Viewport.Width + GraphicsDevice.Viewport.Width / 2.0f;
else this.circlePosition.X = (float)e.Y * GraphicsDevice.Viewport.Width + GraphicsDevice.Viewport.Width / 2.0f;
}
I'm using Z axes of sensor as my Y in game and Y axes of sensor as my X in game. Calibration will be done by subtracting Z axes of sensor from center. In this way, our sensor axes directly correspond to position (percentage) on screen.
For this to work we don't need X axes of sensor at all...
This is just quick implementation. You would find center for sensor since this Viewport.Width / 2f isn't center, sum and average of 3 measurements, calibration on X sensor axes so that you could play/use application on flat or some degree position, etc.
This code is tested on Windows Phone Device! (and works)