Changing the direction of the gravity - c#

In my game, normally the player goes up while the space bar is pressed and fall if space bar is not pressed. My aim is: when the player collides with a specific object, the gravity should be work in an opposite way and when we press the space bar , the player should go down (for 10 seconds). Here is my code to move the player upwards:
public void Update(){
Vector2 vel=rb.velocity;
float ang = Mathf.Atan2(vel.y,10)*Mathf.Rad2Deg;
transform.rotation = Quaternion.Euler(new Vector3(0,0,ang));
if(Input.GetKey(KeyCode.Space)){
rb.AddForce(Vector2.up*gravity*Time.deltaTime*2000f);
}

Without knowing too much about how your code is structured, perhaps I can suggest something like this:
public class GameEngine
{
private double gravity = 9.81;
private void GravityFlip_Event(object sender, EventArgs e)
{
InvertGravity();
Task.Run(() => InvertGravity(10));
}
private void InvertGravity(int delaySeconds = 0)
{
Thread.Sleep(delaySeconds * 1000);
gravity = 0 - gravity;
}
}
When the "player collides with a specific object", invoke the GravityFlip_Event. This will first "flip gravity" with immediate event and then invoke a task with a delay of the required length; flipping it back there after.
A better approach may be to create an "effects" class that can modify the environment in any other way you might like, perhaps base around a System.Timers.Timer.

Related

Read "zero input" in Unity's Input System

I am creating a very simple player horizontal movement script for a 2D game (code below). I want to use the newer Input System, instead of the default Input Manager.
Following Unity's guide, I was able to set the controls to work as in the example. However, the tutorial is updating the player-controlled object's position, while I want to update it's velocity. Zeroing the value read in the input, as in the example (step 4.25) works for updating positions, but not velocities, as the velocity is raised and immediately goes back to zero. But, if the velocity is not zeroed, the object's velocity is kept even after the player releases the movement key (I haven't tested it with an analog input).
Basically, when the composite input has no keys pressed (or the joystick is in the neutral position), it does not report it as a Vector2.zero. The input event only happens when a key is pressed, it seems, but not when released... at least that is what I could gather.
So I'd like to know a way to set a velocity goal, based on the Input System, that will zero out when the keys are released. Something similar to OnButtonUp() from the Input Manager.
My code:
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerHorizontalMovement : MonoBehaviour
{
[Tooltip("[units/s]")]
[SerializeField] private float horizontalSpeed = 3f;
[Tooltip("[units/s^2]")]
[SerializeField] private float horizontalAccel = 60f;
private PlayerControls controls;
private Rigidbody2D myRigidbody;
private float velocityGoal;
private void Awake()
{
controls = new PlayerControls();
controls.Player.Move.performed += context => velocityGoal = context.ReadValue<Vector2>().x;
}
private void Start()
{
myRigidbody = GetComponent<Rigidbody2D>();
velocityGoal = 0f;
}
private void FixedUpdate()
{
Vector3 newVelocity = myRigidbody.velocity;
// X axis velocity update
newVelocity.x = Mathf.MoveTowards(newVelocity.x, velocityGoal, horizontalAccel * Time.fixedDeltaTime);
myRigidbody.velocity = newVelocity;
}
private void OnEnable() => controls.Player.Enable();
private void OnDisable() => controls.Player.Disable();
}
You’ve got a couple of options, but based on what you’ve got and the tutorial you were following, I feel the simplest would be just adding that cancelled action status, like so:
private void Awake()
{
controls = new PlayerControls();
controls.Player.Move.performed += context => velocityGoal = context.ReadValue<Vector2>().x;
controls.Player.Move.canceled += context => velocityGoal = 0;
}
As per the Unity docs describing Actions, these are the phases you might be interested in:
Phase
Description
Disabled
The Action is disabled and can't receive input.
Waiting
The Action is enabled and is actively waiting for input.
Started
The Input System has received input that started an Interaction with the Action.
Performed
An Interaction with the Action has been completed.
Canceled
An Interaction with the Action has been canceled.
This should work for the old/default input system but I have no idea about the new one:
if (!Input.anyKey)
{
Debug.Log("No button is being pressed");
}

How to make value scale up at same rate as user keypress in unity

I'm currently trying to make an AI move smoothly when going up and down and I'm struggling to find something or a way to replicate the way the player controlled Game Object move.
When my code runs currently, the Enemy AI appears to jump up and down as if it were vibrating in order to match the other game Objects y component. I understand that this happens because it tracks it makes those micro adjustments in order to match the y component, so I'm trying to find a way to simulate the scale at which a users keypress occurs, but within my AI script.
#region Public Variables
#endregion
#region Inspector Variables
[SerializeField] private float moveSpeed = 0f;
[SerializeField] private Vector2 ballPos;
[SerializeField] private GameObject ballRef;
#endregion
#region Private Variable
#endregion
#region Components
Rigidbody2D rb;
#endregion
private void Start()
{
rb = GetComponent<Rigidbody2D>();
} //Onstartup executes
private void FixedUpdate()
{
UpdateBallPos();
MoveAI();
}
#region Methods
private void MoveAI()
{
if (ballPos.y > transform.position.y)
{
rb.velocity = new Vector2(0, //artificially made speed scale// * moveSpeed);
}
else if (ballPos.y + 0.2 > transform.position.y || ballPos.y - 0.2 > transform.position.y)
{
rb.velocity = new Vector2(0, 0);
}
else
{
rb.velocity = new Vector2(0, //artifically made speed scale// -moveSpeed);
}
}
private void UpdateBallPos()
{
ballPos = new Vector2(ballRef.transform.position.x, ballRef.transform.position.y);
}
#endregion
}
When a user presses a button referring to the Input manager, rather than jumping straight to the maximum (1), it scales quickly to simulate a smooth motion.
My player script looks like this
#region Public Variables
#endregion
#region Inspector Variables
[SerializeField] private float moveSpeed;
#endregion
#region Private Variables
private float yInput;
#endregion
#region Components
Rigidbody2D rb;
#endregion
private void Start()
{
rb = GetComponent<Rigidbody2D>();
} //Onstartup executes
private void Update()
{
GetMoveInput();
}
private void FixedUpdate()
{
MovePlayer();
}
#region Methods
private void MovePlayer()
{
rb.velocity = new Vector2(0, yInput * moveSpeed);
}
private void GetMoveInput()
{
yInput = Input.GetAxis("PlayerLeft");
}
#endregion
I'm trying to find something which can replace the "yInput" in the player script and use that in my AI script.
I thought I might be able to simulate sending a keypress within my script but there doesn't seem to be one, plus it would create the issue of users potentially pressing that button and sending a real keypress.
I apologise about the explanation, I'm not sure how else to say it and I've been trying to figure something out for a while. Any help at all is appreciated and any advice for my code in general would be appreciated as well!
I can't exactly understand your goal, but this seems either a case for linear interpolation (or "lerp"), for straight-up Time.deltaTime usage or for a mix of both. Plus, the code you posted kind of looks like Pong :)
If you need to track and follow an object, moving frame-by-frame via deltaTime, by "lerping" or by both can be a good starting point, and can easily evolve into more complex behaviours or can be "released as is", if you're fine with the end result.
In short and in their simplest form, Time.deltaTime is the fraction of time that passed between two frames in your main loop, while "Linear interpolation" means that, given two points "A" and "B", you're able to get any "N" point between those two.
You can use one, the other or a mix of both in Unity to smoothly move an object towards a specific destination, rather than by teleporting it or moving it at a constant speed.
Here's an untested snippet to hopefully set you in the right direction.
It uses normalized vectors, since you're using velocities, as using raw Vector2s off the bat will likely result in weird or near-instant movements.
It's also using vectors to give you a general idea of how lerping works, but it can be done only on the y axis too, by using Mathf.lerp rather than Vector3.Lerp
private void MoveAI()
{
Vector3 from = transform.position; //This is your point "A"
Vector3 to = ballPos; //This is your point "B"
to.x = from.x; // You're interested in finding the "N" offset on the y axis
to.z = from.z;
Vector3 offset = Vector3.Lerp(from.normalized, to.normalized, moveSpeed * Time.fixedDeltaTime); //using fixedDeltaTime because the method is called from FixedUpdate
rb.velocity = offset;
}
As for side notes on your code and how I wrote the snippet:
Avoid creating vectors with new if you can, especially when it's done each frame/each physics-update-tick. It's not a problem when you're experimenting, but it can become an unwanted overhead down the line.
Avoid introducing hard-coded constants when checking for ranges unless it's supposed to be by design. I'm talking about the 0.2 offset you're adding and removing in your AI script. If you need to check for "deadzones", Mathf.epsilon, used either raw or with multipliers, will most likely yield better and more reliable results
Always factor in Time.deltaTime/Time.fixedDeltaTime if you're doing something to GameObjects each frame. In short words, it allows you to easily keep consistency between your game logic and the framerate, preventing odd behaviors when the latter is above or below the targeted one

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

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.

Unity: Smooth camera movement on UI button click

I'm trying to make the camera move to a certain position after clicking a UI button.
For the positioning, I use empty game objects' (CameraPositionStart and CameraPositionFinish) coordinates.
For some reason, the camera just teleports to the position needed instead of smoothly moving to it.
Here's my code:
using UnityEngine;
using UnityEngine.UI;
public class CameraMover : MonoBehaviour
{
public float speed;
public Button ButtonStartNew;
public GameObject CameraPositionStart;
public GameObject CameraPositionFinish;
private void Update()
{
ButtonStartNew.onClick.AddListener(moveCamera);
}
public void moveCamera()
{
transform.position = Vector3.Lerp(transform.position, CameraPositionFinish.transform.position, Time.deltaTime * speed);
}
}
This behavior is likely caused because of two reasons.
The method Update is executed once every frame. Here every frame you are adding an additional call that should be made when you click the button on the UI. The result of this, I suspect, is that when the several hundred calls to moveCamera all happen at the same time, and despite moveCamera only moving the transform by Lerp(Time.DeltaTime * Speed) -- the object moves closer(since you're lerping) instantly because you're calling it a bunch.
private void Update()
{
ButtonStartNew.onClick.AddListener(moveCamera);
}
Consider only adding one call to moveCamera to the on click event. We can do this in Start or Awake since they're only called once.
private void Start()
{
ButtonStartNew.onClick.AddListener(moveCamera);
}
However this along does not solve the problem. Because if we do that moveCamera will only be called once and the result would be the camera would move very slightly toward the target and stop.
Consider implementing a Coroutine that will move the transform over time(every frame) until it has reached the target.
private void moveCamera()
{
// A coroutine runs every frame until it stops returning values
StartCoroutine(MoveCamera);
}
private IEnumerator MoveCamera()
{
// check the distance and see if we still need to move towards the destination ​
while(Vector3.Distance(transform.position, CameraPositionFinish.transform.position) > 1.0f)
​{
transform.position = Vector3.Lerp(transform.position, CameraPositionFinish.transform.position, Time.deltaTime * speed);
// Return nothing meaningful and wait until next frame​
yield return null;
}
}

Unity Jump function issue

I've been working on this script for the past day. For some reason my character will not jump as long as it's animator is active. I've got into the animation (there is only one) and removed all references to the animation placing a position anywhere and still the issue presides.
I have discovered that I can make my player jump if I use Co-routine which I'm using. However, I'm still new to using them and I can't work out why my player won't fall to the ground once a force has been added to it. And my player only moves up when the button is clicked. Could someone please take a look at my script and tell me what I'm doing wrong?
public float jumpSpeed = 100.0f;
public float jumpHeight = 2.0f;
public AudioClip jumpSound;
private GameObject pos;
private bool moving;
private bool isJumping;
void Start()
{
}
// Update is called once per frame
void Update ()
{
if(Input.GetMouseButtonDown(0))// && !moving)
{
isJumping = true;
StartCoroutine(JumpPlayer(gameObject.transform.localPosition));
}
else
{
isJumping = false;
}
}
IEnumerator JumpPlayer(Vector3 startPos)
{
Vector3 jump = new Vector3(transform.localPosition.x, jumpHeight, transform.localPosition.z);
float t = 0f;
t += Time.deltaTime / jumpSpeed;
rigidbody.AddForce(Vector3.up * jumpSpeed);
//gameObject.transform.localPosition = Vector3.Lerp(startPos, jump, 0.5f);
//isJumping = false;
yield return null;
}
Firstly, your use of coroutine isn't doing anything in particular - because it only does yield return null at the end, it'll run in a single frame and then exit. You could make it a regular void function and you shouldn't see any change in behaviour.
Removing other redundant code and you have just this:
if(Input.GetMouseButtonDown(0))
{
rigidbody.AddForce(Vector3.up * jumpSpeed);
}
This force is added for only a single frame: the frame where the mouse button is pressed down (if you used Input.GetMouseButton instead, you'd see the force applied for multiple frames).
You say "my player only moves up when the button is clicked" but I'm not clear why that's a problem - perhaps you mean that the player should continue to move up for as long as the button is held, in which case you should refer to my previous paragraph.
The most obvious reasons for the player not falling again are related to the RigidBody component: do you have weight & drag set to suitable values? An easy way to test this would be to position your player some distance from the ground at the start of the scene, and ensure that they fall to the ground when you start the scene.
Another reason might be that you're using the default override of .AddForce in an Update cycle. The default behaviour of this method applies force during the FixedUpdate calls, and you might find that using ForceMode.Impulse or ForceMode.VelocityChange gives you the result you're looking for.

Categories

Resources