Keeping camera & mouse movement speeds the same on Drag in Unity3D - c#

this question is a bit long but please bare with me.
I am working with Unity3D's newer input system, and I've been able to set up camera movement so when the user clicks the middle mouse button, they can drag the camera around along the X- & Y-axis. However, has a very parallax-y feel to it, which I am trying to avoid. From my understanding this is happening because the speed of the mouse movement & the camera movements are not the same (See, visual).
My InputController class:
public class InputController : MonoBehaviour
{
private InputControls inputControls;
[Header("Camera Movement")]
[SerializeField] CameraController cameraController;
private InputAction.CallbackContext context;
void Awake()
{
inputControls = new InputControls();
}
private void Start()
{
inputControls.Mouse.MiddleButton.started += MiddleButton_started;
}
public void MiddleButton_started(InputAction.CallbackContext ctx)
{
context = ctx;
}
private void Update()
{
bool mouseIsDown = context.performed;
if (mouseIsDown)
{
Vector2 delta = inputControls.Mouse.DeltaPosition.ReadValue<Vector2>();
cameraController.Move(delta, false);
}
}
}
My CameraController class:
public class CameraController : MonoBehaviour
{
[SerializeField] Vector2 minPosition;
[SerializeField] Vector2 maxPosition;
[SerializeField] float speed = 3;
[SerializeField] float zPosition = -10f;
private Camera mainCam;
private void Start()
{
mainCam = GetComponent<Camera>();
}
public void Move(Vector2 deltaPosition, bool convertToViewportPoint = true)
{
if (convertToViewportPoint) deltaPosition = mainCam.ScreenToViewportPoint(deltaPosition);
Vector3 moveTo = new Vector3(deltaPosition.x, deltaPosition.y, 0);
Vector3 target = transform.position - moveTo;
float clampedX = Mathf.Clamp(target.x, minPosition.x, maxPosition.x);
float clampedY = Mathf.Clamp(target.y, minPosition.y, maxPosition.y);
transform.position = Vector3.Lerp(transform.position, new Vector3(clampedX, clampedY, -10), speed * Time.deltaTime);
}
}
Now, in this version of the CameraController, the parallax-y feel might be coming from the fact that the Lerp speed is constant, whereas the speed of the mouse movement is not.
However, I've tested it with various magnitudes (ie., the magnitudes of the delta mouse position, the current mouse position, the current mouse position subtracted from the delta mouse position, etc...) with generally the same results -- either the camera moves too fast for the mouse or vice versa.
From my limited but growing understanding of trig, magnitudes represents the speed of a vector but I CANNOT get the speed of the camera movement and the speed of the mouse movement to match up. I would like for this to happen so that when the user clicks a certain point, that point generally stays under the cursor when in movement (See other visual).
Can someone help me understand how I might achieve this?
Thanks kindly.

What I did to solve this, in case anyone comes across this, is get the position of the cursor when the mouse is first clicked. We can call that dragOrigin. Then we can get the change in position as the mouse moves (polling the input through Unity's input system can be done through an Update function). Then we subtract the new mouse position from dragOrigin. Once we have that difference -- lets call it deltaPosition -- we just add it to the camera's current position.
To ensure that the speed matches that of the mouse, run the deltaPosition and the currentMousePosition through SmoothDamp to get the velocity of the change. Its magnitude will be the speed you want the camera to move.
So for example:
// Call this when the user first clicks the mouse.
// Lets assume this is part of a class that controls the camera.
public void SetDragOrigin(Vector2 mousePosition)
{
// could probably cache Camera.main for efficiency
dragOrigin = Camera.main.ScreenToWorldPoint(mousePosition);
}
// This is called in an update function of another class that deals with input.
// (Or perhaps you can simply pass the input controls to the class controlling the camera, but that's not the case here).
public void Move(Vector2 newMousePosition)
{
mousePosition = mainCam.ScreenToWorldPoint(mousePosition);
Vector2 deltaPosition = dragOrigin - mousePosition;
Vector3.SmoothDamp(deltaPosition, mousePosition, ref dragVelocity, 0.1f, 250f, Time.deltaTime);
cameraPosition += new Vector3(deltaPosition.x, deltaPosition.y, 0) * dragVelocity.magnitude * Time.deltaTime;
// anything else you want to do like clamping camera position
// ....
}
One thing to note here, is that the 5th argument of SmoothDamp is the maxSpeed. I hardcoded it in this example, but you might want to play around with it. When I did not set the maxSpeed, there was some wonky things happening to the dragVelocity over time, so it may be better to set the maxSpeed to what suits you best.

Related

Unity creating a script for a square to move up on user input and fall down when there is no user input

I am trying to create a minigame similar to the fishing mechanism in Stardew Valley. I have two boundaries which are empty objects positioned at each end of a rectangle to keep the fish and the hook inside of the zone. When I run my script, the fish spawns at the bottom boundary and moves up instead of the position it is initially in. The hook generates at the bottom boundary and doesn't move upon user input.
Here is the code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FishingMiniGame : MonoBehaviour
{
//Make the Egg move
[Header("Fishing Area")]
[SerializeField] Transform topBounds;
[SerializeField] Transform bottomBounds;
[Header("Fish Settings")]
[SerializeField] Transform Fish;
[SerializeField] float smoothMotion = 3f;
[SerializeField] float fishTimeRandomizer = 3f;
float fishPosition;
float fishSpeed;
float fishTimer;
float fishTargetPosition;
[Header("Hook Settinds")]
[SerializeField] Transform Hook;
[SerializeField] float HookSize = .18f;
[SerializeField] float HookSpeed = .1f;
[SerializeField] float HookGravity = .05f;
float hookPosition;
float hookPullVelocity;
private void FixedUpdate()
{
MoveFish();
MoveHook();
}
private void MoveHook()
{
if(Input.GetMouseButtonDown(0))
{
//increase our pull velocity
hookPullVelocity += HookSpeed * Time.deltaTime; //raises out hook
}
hookPullVelocity -= HookGravity * Time.deltaTime;
hookPosition += hookPullVelocity;
hookPosition = Mathf.Clamp(hookPosition, 0, 1); //keep the jook withon bounds
Hook.position = Vector3.Lerp(bottomBounds.position, topBounds.position, hookPosition);
}
private void MoveFish()
{
//based on timer, pick random position
//move fish to that position smoothly
fishTimer -= Time.deltaTime;
if(fishTimer < 0)
{
//pick a new target position
//reset timer
fishTimer = Random.value * fishTimeRandomizer;
fishTargetPosition = Random.value;
}
fishPosition = Mathf.SmoothDamp(fishPosition, fishTargetPosition, ref fishSpeed, smoothMotion);
Fish.position = Vector3.Lerp(bottomBounds, topBounds, fishPosition);
}
}
Well, first, making all the code in one script will be confuse. Try giving that fish its own script and game object.
Plus, take a look into Lerp. I think you are missundertanding how to use it. It takes 3 parameters with a being the original Vector3, so your current hook or fish position, b is your Vector3 target position, and t is the step or timestep. Should be delta or delta*someMultiplier.
What #MickyD said is also true, input should be performed during Update(), but that will not fix you code. Update(), FixedUpdate() and LateUpdate() are kinda the same but they run always in that order: Update -> FixedUpdate -> LateUpdate. So keep that in mind.
By the way, your gravity aplies every frame, so your hook is fighting against it even when you are lifting, resulting in half the force being neutralized. Making the gravity only apply on an else after your input will give you a much clear control of forces.

Strange behaviour of the Cinemachine virtual camera

I am using the new input system in my unity project. I also use Cinemachine. I use Cinemachine Input Provider to change the input from the old to the new system.
When I change max speed to Input Value Gain in speed field of virtual camera's settings (I did it because it is the most comfortable way to control camera) I face a problem.
My problem: When my character moves after some time the camera speed changes. If I start to move in the opposite direction, the camera speed returns to normal.
This is independent of the other components in the scene. My scene has only plane, cube, camera and my character.
Here's my character control code (ignore the awful calculation of movement direction):
private Rigidbody _rb;
private Vector2 _moveDirection;
private float speed = 5f;
private void Awake()
{
_rb = GetComponent<Rigidbody>();
Cursor.lockState = CursorLockMode.Locked;
}
public void OnMove(InputAction.CallbackContext context)
{
_moveDirection = context.ReadValue<Vector2>();
}
private void FixedUpdate()
{
Move(_moveDirection);
}
private void Move(Vector3 moveDirection)
{
float scaledMoveSpeed = speed * Time.deltaTime;
moveDirection = new Vector3(Camera.main.transform.forward.x, 0, Camera.main.transform.forward.z).normalized * moveDirection.y + new Vector3(Camera.main.transform.right.x, 0, Camera.main.transform.right.z).normalized * moveDirection.x;
_rb.MovePosition(transform.position + moveDirection * scaledMoveSpeed);
}
Here's a screenshot of the camera settings and Cinemachine Input Provider:
And screenshots of the Input Actions settings:
I found a solution for those who will ever face this problem! On your main camera, in the CinemachineBrain component, change the Update Method from Smart Update to Late Update. That should help

How to fix spasming and buggy instantiation in Unity

I'm creating a basic pool game in Unity with C#, what im trying to do is that if the cue ball is moving, the stick will disappear, and once it becomes stationary again, it will reappear to where the cue ball is located. This is my code so far:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class stickDeplacement : MonoBehaviour
{
public bool bIsOnTheMove = false;
Vector3 lastPos;
public GameObject Stick;
void Start()
{
}
void Update()
{
var stick = Instantiate(Stick, gameObject.transform.position, gameObject.transform.rotation);
if (this.transform.position != lastPos)
{
Destroy(stick);
Debug.Log("Is moving");
}
else
{
Debug.Log("Is not moving");
}
lastPos = this.transform.position;
}
}
But what happens is that the ball, along with the stick, will just spasm and be buggy right from the start (when I open and play the game). Am I missing something here?
This is extremely inefficient and dangerous!
Why instantiate a stick every frame just to eventually already destroy it in that very same frame? And if the ball is stationary you want an additional stick to be spawned every frame?
Instead of all the time instantiating and destroying it at all you should rather keep one stick and only (de)activate it.
In your case you could do this in a single line
bIsOnTheMove = transform.position == lastPos;
stick.SetActive(!bIsOnTheMove);
Also I doubt a lot you would like the stick to have the same rotation as a rolling ball! Of course this will behave awkward
Most certainly you do not simply want to clone the ball's orientation. I would e.g. try to determine the closest point of the table edge to the current ball's position (iterate through the wall colliders and use Collider.ClosestPoint) and let the stick face the direction from that edge point towars the ball position (+ maybe an offset in X so the stick is slightly inclined by default).
And finally you anyway do not want to assign that rotation every frame, since you most probably later want your users to be able to rotate that stick. You only want to apply this once when the ball becomes stationary.
Something like e.g.
// The stick is not a prefab anymore but simply always exists in the scene!
[SerializeField] private Transform stick;
[SerializeField] private Vector3 eulerOffset;
[SerializeField] private Collider[] wallColliders;
public bool bIsOnTheMove;
private Vector3 lastPos;
private void Start()
{
lastPos = transform.position;
}
private void Update()
{
// is the ball currently moving?
var isMoving = transform.position == lastPos;
last Post = transform.position;
// Did this state change since the last frame?
if(bIsOnTheMove == isMoving) return;
bIsOnTheMove = isMoving;
// (de)activate the stick accordingly
stick.gameObject.SetActive(!isMoving);
// Do this ONCE when ball becomes stanionary
if(!isMoving)
{
var ballPosition = transform.position;
// among the wall colliders find which one is closest to the ball
Vector3 closestPoint;
var smallestDistance = float.PositiveInifinity;
foreach(var wall in wallColliders)
{
var edgePoint = wall.ClosestPoint(ballPosition);
var distane = (edgePoint - ballPosition).sqrMagnitude;
if(distance < smallestDistance)
{
closestPoint = point;
smallestDistance = distance;
}
}
// then make the stick look towards the ball from that edge
var direction = ballPosition - closestPoint;
var rotation = Quaternion.LookRotation(direction);
// optional add the offset
rotation *= Quaternion.Euler(eulerOffset);
stick.rotation = rotation;
}
}

How to continue moving when I decrease speed

I am creating a game that is set in Zero-gravity. I am using a script that can be found here https://github.com/brihernandez/ArcadeSpaceFlightExample
The movement is controlled by a throttle that can be increased/decreased using the mouse wheel or 'w' and 's' keys.
This code controls the mouse wheel and is the same for the 'w' and 's' keys.
private void UpdateMouseWheelThrottle()
{
throttle += Input.GetAxis("Mouse ScrollWheel");
throttle = Mathf.Clamp(throttle, 0.0f, 1.0f);
}
The object that is accelerated by the throttle stops moving when I decrease the throttle and does not drift or continue moving as it should in zero gravity.
This is the code that controls the physics.
public class PlayerPhysics: MonoBehaviour
{
[Tooltip("X: Lateral thrust\nY: Vertical thrust\nZ: Longitudinal Thrust")]
public Vector3 linearForce = new Vector3(100.0f, 100.0f, 100.0f);
[Tooltip("X: Pitch\nY: Yaw\nZ: Roll")]
public Vector3 angularForce = new Vector3(100.0f, 100.0f, 100.0f);
[Range(0.0f, 1.0f)]
[Tooltip("Multiplier for longitudinal thrust when reverse thrust is requested.")]
public float reverseMultiplier = 1.0f;
[Tooltip("Multiplier for all forces. Can be used to keep force numbers smaller and more readable.")]
public float forceMultiplier = 100.0f;
public Rigidbody Rigidbody { get { return rbody; } }
private Vector3 appliedLinearForce = Vector3.zero;
private Vector3 appliedAngularForce = Vector3.zero;
private Rigidbody rbody;
private Player player;
void Awake()
{
rbody = GetComponent<Rigidbody>();
if (rbody == null)
{
Debug.LogWarning(name + ": ShipPhysics has no rigidbody.");
}
player = GetComponent<Player>();
}
void FixedUpdate()
{
if (rbody != null)
{
rbody.AddRelativeForce(appliedLinearForce * forceMultiplier, ForceMode.Force);
rbody.AddRelativeTorque(appliedAngularForce * forceMultiplier, ForceMode.Force);
}
}
public void SetPhysicsInput(Vector3 linearInput, Vector3 angularInput)
{
appliedLinearForce = MultiplyByComponent(linearInput, linearForce);
appliedAngularForce = MultiplyByComponent(angularInput, angularForce);
}
private Vector3 MultiplyByComponent(Vector3 a, Vector3 b)
{
Vector3 ret;
ret.x = a.x * b.x;
ret.y = a.y * b.y;
ret.z = a.z * b.z;
return ret;
}
}
And here is the code that instantiates both.
public class Player: MonoBehaviour
{
public static Player Playerdrone { get { return player; } }
private static Player player;
private PlayerInput input;
private PlayerPhysics physics;
public Vector3 Velocity { get { return physics.Rigidbody.velocity; } }
public float Throttle { get { return input.throttle; } }
// Start is called before the first frame update
void Awake()
{
input = GetComponent<PlayerInput>();
physics = GetComponent<PlayerPhysics>();
}
// Update is called once per frame
void Update()
{
physics.SetPhysicsInput(new Vector3(input.strafe, 0.0f, input.throttle), new Vector3(input.pitch, input.yaw, input.roll));
}
}
I cannot get the Rigidboy to keep drifting when I decrease the throttle, my option is to either abandon the throttle concept and use a simple "Addforce" but I would prefer to keep it.
I have edited the script to have a similar function to the 'Roll' that used 'w' and 's' but moves me forwards and backward. I still find myself decelerating when I let go of the input.
Lowering drag and angular causes the object to spin out of control due to the mouse following function. The mouse does not center the camera and instead causes the camera to keep rotating and tracking the mouse. Changing the mouse script will cause it's own issues since it controls the pitch and yaw of the object.
private void SetStickCommandsUsingMouse()
{
Vector3 mousePos = Input.mousePosition;
// Figure out most position relative to center of screen.
// (0, 0) is center, (-1, -1) is bottom left, (1, 1) is top right.
pitch = (mousePos.y - (Screen.height * 0.5f)) / (Screen.height * 0.5f);
yaw = (mousePos.x - (Screen.width * 0.5f)) / (Screen.width * 0.5f);
// Make sure the values don't exceed limits.
pitch = -Mathf.Clamp(pitch, -1.0f, 1.0f);
yaw = Mathf.Clamp(yaw, -1.0f, 1.0f);
}
The object that is accelerated by the throttle stops moving when I decrease the throttle and does not drift or continue moving as it should in zero gravity.
The Rigidbody component has properties that can be changed in the Inspector (documented here) (and also via script (documented here)):
Mass: The mass of the object (in kilograms by default).
Drag: How much air resistance affects the object when moving from forces. 0 means no air resistance, and infinity makes the object
stop moving immediately.
It sounds like your Drag value might be too large (?).
Although this example project uses "space mechanics" (effectively with no air resistance), the Drag property can still be used to tune the movement and "drift behavior" of the rigidbody, and to control how long it will keep decelerating after the throttle value went down to 0.
Alternatively, you could try to experiment with other ForceMode parameters for the Add*Force() calls: ForceMode.Acceleration or ForceMode.VelocityChange (which is ignoring the mass of the rigidbody).
To make your game more "realistic", you could instead set the Drag property to 0 (no air resistance like in real space -> infinite drift), and try to actively decelerate the ship by experimenting with retrograde rockets/thrusters, by adding a force in backward direction. However, that's probably quite impractical for an arcade game, I guess.
There are also the global Physics settings of the project, which can be found under Edit -> Project settings -> Physics (documented here). There, you'll also find many important physics settings (like Gravity, for example).
(It can also be interesting to have a look at the official docs of the PhysX physics engine, which Unity uses internally for its 3D physics, to understand how things actually work under the hood...)

How to determine an android phones viewing direction when starting an app

I'm working on an Augmented Reality app for Android without tracking images/objects. The user stands at a predefined position and virtual objects are placed into the real world. when the user turns around or moves the phone, the objects are fixed at their respective places. I do this by applying the gyroscope data to the camera.
My problem: I want the objects positions to be always fixed to the same places regardless of the users viewing direction when he starts up the app. Right now, on starting the app, the objects are positioned depending on the camera. After that, they are fixed to their places, when the user changes his viewing direction.
I drew an image of what the exact problem is to better elaborate:
I want to know which sensors are relevant to solve this problem. Since Google Maps accurately determines the viewing direction of a user, I assume there are built in sensors to find out in which direction the user is looking in order to apply this information to the camera's rotation at the start.
This is the code I use to apply the phones rotation to the camera (I'm using Unity and C#):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Gyrotransform : MonoBehaviour
{
// STATE
private float _initialYAngle = 0f;
private float _appliedGyroYAngle = 0f;
private float _calibrationYAngle = 0f;
private Transform _rawGyroRotation;
private float _tempSmoothing;
// SETTINGS
[SerializeField] private float _smoothing = 0.1f;
private IEnumerator Start()
{
Input.gyro.enabled = true;
Application.targetFrameRate = 60;
_initialYAngle = transform.eulerAngles.y;
_rawGyroRotation = new GameObject("GyroRaw").transform;
// _rawGyroRotation.parent = Core.Instance.transform;
_rawGyroRotation.position = transform.position;
_rawGyroRotation.rotation = transform.rotation;
// Wait until gyro is active, then calibrate to reset starting rotation.
yield return new WaitForSeconds(1);
StartCoroutine(CalibrateYAngle());
}
private void Update()
{
ApplyGyroRotation();
ApplyCalibration();
transform.rotation = Quaternion.Slerp(transform.rotation, _rawGyroRotation.rotation, _smoothing);
}
private IEnumerator CalibrateYAngle()
{
_tempSmoothing = _smoothing;
_smoothing = 1;
_calibrationYAngle = _appliedGyroYAngle - _initialYAngle; // Offsets the y angle in case it wasn't 0 at edit time.
yield return null;
_smoothing = _tempSmoothing;
}
private void ApplyGyroRotation()
{
_rawGyroRotation.rotation = Input.gyro.attitude;
_rawGyroRotation.Rotate(0f, 0f, 180f, Space.Self); // Swap "handedness" of quaternion from gyro.
_rawGyroRotation.Rotate(90f, 180f, 0f, Space.World); // Rotate to make sense as a camera pointing out the back of your device.
_appliedGyroYAngle = _rawGyroRotation.eulerAngles.y; // Save the angle around y axis for use in calibration.
}
private void ApplyCalibration()
{
_rawGyroRotation.Rotate(0f, -_calibrationYAngle, 0f, Space.World); // Rotates y angle back however much it deviated when calibrationYAngle was saved.
}
public void SetEnabled(bool value)
{
enabled = true;
StartCoroutine(CalibrateYAngle());
}
}
As far as I understand it the Gyroskope returns the rotational difference since it was started.
That's why your objects appear in the direction you are facing during start.
I guess what you rather want might be Compass.magneticHeading at least for setting the correct rotation once at gamestart
// Orient an object to point to magnetic north.
transform.rotation = Quaternion.Euler(0, -Input.compass.magneticHeading, 0);
You could do this once at start on the parent of all the objects you want to show in order to orient them correctly on the GPS north.

Categories

Resources