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");
}
Related
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
So I am trying to create a new first-person movement system with the new input system in order to make gamepad support so much easier and I am experiencing a problem when I try to read the value of the Vector2 in a FixedUpdate loop, it only outputs (0,0) but if I read it in an InputAction.performed event it works. However, I cannot use the event as it doesn't repeat on keyboard input and it isn't smooth. I've seen a tutorial linked here and at the end it does demonstrate you can pull information from outside events. Now my question is did I miss something or is there a different way to do it, my code is found below
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.Assertions;
public class MovementEngine : MonoBehaviour
{
public InputMaster Input; // My input device here
public float currentSpeed = 2f;
public float walkingSpeed = 2f;
public float runningSpeed = 4f;
public Transform cameraTransform;
public CharacterController controller;
public InputAction Movement;
// Start is called before the first frame update
void Awake()
{
Input = new InputMaster(); // Creates new instance
}
void OnEnable()
{
Movement = Input.Player.Movement;
Movement.Enable(); // it is enabled
Input.Player.Interaction.performed += Interact;
Input.Player.Interaction.Enable();
}
private void Interact(InputAction.CallbackContext context)
{
Debug.Log("Interact");
}
void OnDisable()
{
Movement.Disable();
Input.Player.Interaction.Disable();
}
// Update is called once per frame
void FixedUpdate(){
Debug.Log("Movement: " + Input.Player.Movement.ReadValue<Vector2>()); // doesn't work
}
}
Store the value (retrieved in the performed event) in a variable, and use that variable in fixed update.
Make sure to reset the variable from the cancelled event (otherwise the variable will hold the last retrieved value from performed event).
You can read the input value directly into a class variable, as shown below.
// Value read from user input
//
private Vector2 movement;
private void Start()
{
SubscribeEvents();
}
private void SubscribeEvents()
{
// Read the value directly into our movement variable
//
Input.Player.Movement.performed += ctx => movement = ctx.ReadValue<Vector2>();
// Reset the movement variable in cancelled since we are no longer interacting
//
Input.Player.Movement.cancelled += ctx => movement = Vector2.zero;
}
private void FixedUpdate()
{
Debug.Log("Movement: " + movement);
}
I fixed this a while ago but StackOverflow never posted my answer
I fixed it by downgrading to Unity InputSystem v1.0.0 as v1.1.1 seemed to not like the InputAction.ReadValue<>() function.
I just start making a game with Unity and Visual Studio, i have a bit confuse that why my Speed value always at zero even i set it equal to another variable.
public class Ballista : MonoBehaviour
{
public Transform firePoint;
public GameObject arrowPrefab;
public float speed = 10f;
public float startTimeHold;
public float holdDownTime;
public float arrowSpeed = 100f;
public float finalSpeed;
private void Update()
{
if (Input.GetKeyDown(KeyCode.M))
{
startTimeHold = Time.time;
}
if (Input.GetKeyUp(KeyCode.M))
{
holdDownTime = Time.time - startTimeHold;
Shoot();
finalSpeed = holdDownTime * arrowSpeed;
}
Debug.Log("holddowntimeis: " + holdDownTime);
Debug.Log("final speed is: " + finalSpeed);
}
public void Shoot()
{
GameObject arrowGO = (GameObject)Instantiate(arrowPrefab, firePoint.position, firePoint.rotation);
}
}
and this is the script that my speed value always zero
{
Rigidbody2D rgBody2D;
public GameObject ballista;
private float speed;
private void Awake()
{
rgBody2D = GetComponent<Rigidbody2D>();
}
private void Start()
{
\\ i though the code below will set my speed equal to my finalSpeed but it still 0
speed = ballista.GetComponent<Ballista>().finalSpeed;
Vector2 mousePos = Input.mousePosition;
Debug.Log("speed: " + speed);
rgBody2D.AddForce(mousePos * speed * Time.deltaTime);
}
void Update()
{
}
}
Different to what others said here you actually do NOT want to do it in Update.
Your goal here is to once give your newly spawned arrow a start velocity, not a continuous force.
The issue here is of other nature I think:
You are always spawning a new instance of your second script from a given prefab. This prefab seems to hold a reference to a Ballista prefab instance. At least you are never assigning a new value to the ballista! It might simply be a wrong reference where the finalSpeed is never updated.
Your are first doing the Shoot and after it set the finalSpeed -> even if it would be the correct reference you always get the wrong finalSpeed value!
I would actually change your two scripts in order toake your arrow instance being controlled by the Ballista instead of letting each spawned arrow poll the speed itself:
public class Ballista : MonoBehaviour
{
public Transform firePoint;
// By giving it the correct type here you don't need GetComponent later
public Rigidbody2D arrowPrefab;
public float startTimeHold;
public float arrowSpeed = 100f;
// I personally would add aax value and clamp since to fast RigidBodies can break collision detection eventually
public float maxArrowSpeed = 300;
private void Update()
{
if (Input.GetKeyDown(KeyCode.M))
{
startTimeHold = Time.time;
}
if (Input.GetKeyUp(KeyCode.M))
{
var holdDownTime = Time.time - startTimeHold;
// get the speed before you shoot
// Here personally I would clamp to be sure the arrow is never faster than maxArrowSpeed
// regardless of how long the user held the button pressed
var finalSpeed = Mathf.Min(holdDownTime * arrowSpeed, maxArrowSpeed);
Debug.Log("holddowntimeis: " + holdDownTime);
Debug.Log("final speed is: " + finalSpeed);
// pass in your speed
Shoot(finalSpeed);
}
}
private void Shoot(float speed)
{
// Instantiate anyway returns the type of the given prefab
// which now is a Rigidbody2D
var arrow = Instantiate(arrowPrefab, firePoint.position, firePoint.rotation);
// Directly set the speed from here
// -> your arrow doesn't even need an extra component
// Since you already spawned it with the correct rotation you maybe don't even need the mouse position thing
// AddForceRelative adds a force in the local space of the arrow so if the rotation is correctly
// this simply adds the force in its forward direction
// Note that also using Time.deltaTime actually only makes sense if you set something continuously
// For a one-time force you wouldn't need it, rather adjust your arrowSpeed field
arrow.AddForceRelative(Vector2.forward * speed);
}
}
Instead of using AddForce or AddForceRelative you could actually also simply set the target velocity:
arrow.velocity = Vector2.forward * speed;
Since you are not updateding it continuously this is totally fine and a lot easier to foresee the actual target speed since when adding a force you have to take the mass and frictions into account. You would than ofcourse have to adjust the arrowSpeed (and evtl maxArrowSpeed) accordingly to not anymore represent a force but an actual velocity in Units/second.
I hope I made my points clear enough, don't hesitate to ask if something stayed unclear ;)
speed = ballista.GetComponent().finalSpeed;
should Come in
void Update(){
}
block and not in
void Start() {
}
as void start is only run once and at that point in time the speed is zero
Hope it helped :)
Start happens only once.
Start is called on the frame when a script is enabled just before any
of the Update methods are called the first time.
Disregarding everything else, i am guessing you want it to update the speed every frame in Update
Update is called every frame, if the MonoBehaviour is enabled.
private void Update()
{
\\ i though the code below will set my speed equal to my finalSpeed but it still 0
speed = ballista.GetComponent<Ballista>().finalSpeed;
Vector2 mousePos = Input.mousePosition;
Debug.Log("speed: " + speed);
rgBody2D.AddForce(mousePos * speed * Time.deltaTime);
}
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.
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.