Preventing user from "bunny hopping" (automatically jumping upon landing) - c#

I have a simple jumping action in my game. The player presses jump, gets launched into the air, transitions to an air-state and falls down to ground again. The usual stuff.
However, if the player lands on the ground and the user is still holding the jump key, the player automatically jumps again upon landing. I don't want that. I want the user to let go of the key before being able to jump again.
Here's a simplified version of my player movement code, with only the nessessary stuff in.
public class GenesisPhysics : MonoBehaviour {
public bool isGrounded = true;
public bool jumpIsPressed;
public IA inputActions; // New input system's Input Action Asset C# class
private void Awake()
{
inputActions = new IA();
// Movement actions stuff...
inputActions.PlayerControls2D.Jump.performed += jumpContext => jumpIsPressed = true;
inputActions.PlayerControls2D.Jump.canceled += jumpContext => jumpIsPressed = false;
}
private void FixedUpdate()
{
if(isGrounded)
{
ExecuteGroundStateLoop()
} else {
ExecuteAirStateLoop()
}
}
private void ExecuteGroundStateLoop()
{
// Do stuff...
if(jumpPressed) {
isGrounded = false;
// Set x and y speeds to be applied in Air state
} else {
// Do some more stuff...
}
}
private void ExecuteAirStateLoop()
{
// Applying gravity, jump force, checking for ground and other stuff...
// If ground is found, set isGrounded to true.
}
void OnEnable()
{
inputActions.Enable()
}
void OnDisable()
{
inputActions.Disable()
}
}
This might not be enough code to fix the issue. I'll update this post if more is needed.

Create a new bool variable "jumpPressAvailable" and set it to true in the beginning. Use this new variable to control, if a jump is available. For example, if jump button is pressed, set "jumpPressAvailable" to false, and if jump button is released or "NOT pressed", set "jumpPressAvailable" to true again. A jump can only be started if jump button is pressed and "jumpPressAvailable" is true.
public bool jumpPressAvailable=true;
Add some lines in the Ground State Loop:
if(jumpPressed && jumpPressAvailable) {
isGrounded = false;
jumpPressAvailable = false;
// Set x and y speeds to be applied in Air state
} else {
if(jumpPressed == false)
{
jumpPressAvailable = true;
}
// Do some more stuff...
}

Related

Using the same script for dealing with different triggers

I have a scene with multiple triggers and a Player with collider. My idea is to have one script to deal with all interactions between triggers and Player collider. I thought that using the same script to different GameObjects would be enough but this thing doesn't work properly. While working with one trigger I need to change one GameObject attached to this trigger but the other GameObjects with this script are changing as well.
One more detail about script is that I have two types of triggers: one is for GameObjects that need some input from user, some doesn't need it. I check it by checking two fields firstChoiceText and secondChoiceText. And if they are empty, the interaction with GameObject doesn't need any choice from the player. Otherwise, I show a plane with two option and change the text of this options using firstChoiceTextfield and secondChoiceTextfield.
So the main idea of script is that when the Player enter the trigger the script activates E key of keyboard. And if we press the E button and if this interaction wasn't done yet, we go further. We check the type of trigger and if we need to make a choice, we activate so called choiceMode of PlayerScript. PlayerScript is just a script for movements of the player, and we need choiceMode just to use W and S keys to select the choice. We make our choice and return to usual movement of the player. If we don't need to make a choice we just do some action and mark the trigger as isDone.
Everything works great with one trigger but when I use several triggers and GameObjects with InteractionScript, all this triggers are mixed up. While working with one trigger I accidentally change the other script. And I don't quite understand how to separate this scripts from each other.
Can anybody give me some piece of advice?
using UnityEngine;
using UnityEngine.UI;
public class InteractionScript : MonoBehaviour
{
[SerializeField]
private GameObject buttonE;
[SerializeField]
private GameObject choiceMenu;
[SerializeField]
private PlayerScript ps;
private int choiceNum = 0;
[SerializeField]
private Transform choicePoint;
[SerializeField]
private Text firstChoiceTextfield;
[SerializeField]
private Text secondChoiceTextfield;
[SerializeField]
private string firstChoiceText;
[SerializeField]
private string secondChoiceText;
[SerializeField]
private bool enteredMe;
[SerializeField]
private bool isDone;
void OnTriggerEnter2D() {
if (!isDone) {
enteredMe = true;
buttonE.SetActive(true);
Debug.Log("++");
}
}
void OnTriggerExit2D() {
if (!isDone) {
buttonE.SetActive(false);
enteredMe = false;
}
}
void Update() {
if (!isDone) {
if (Input.GetKeyDown(KeyCode.E) && buttonE.activeSelf && enteredMe) {
if (firstChoiceText != "" && secondChoiceText != "") {
choiceMenu.SetActive(true);
buttonE.SetActive(false);
ps.choiceMode = true;
Debug.Log("with choice");
} else {
Debug.Log("without choice");
//Action
buttonE.SetActive(false);
isDone = true;
}
}
}
if (ps.choiceMode) {
firstChoiceTextfield.text = firstChoiceText;
secondChoiceTextfield.text = secondChoiceText;
if (Input.GetKeyDown(KeyCode.W)) {
choiceNum += 1;
}
if (Input.GetKeyDown(KeyCode.S)) {
choiceNum -= 1;
}
if (choiceNum > 1) {
choiceNum = 0;
} else if (choiceNum < 0) {
choiceNum = 1;
}
if (choiceNum == 0) {
choicePoint.localPosition = new Vector3(0f, 24f, 0f);
if (Input.GetKeyDown(KeyCode.Return)) {
Debug.Log("choice1");
choiceMenu.SetActive(false);
isDone = true;
enteredMe = false;
ps.choiceMode = false;
}
} else if (choiceNum == 1) {
choicePoint.localPosition = new Vector3(0f, -21.5f, 0f);
if (Input.GetKeyDown(KeyCode.Return)) {
Debug.Log("choice2");
choiceMenu.SetActive(false);
isDone = true;
enteredMe = false;
ps.choiceMode = false;
}
}
}
}
}

How to use BroadcastMessage() for a function that was detached from the gameobject sending the message?

I'm probably not doing this properly at all. I'm trying to make a script that will allow dropping and throwing of a weapon, and I'm trying to keep the script on the weapon for prefab.
void PrepareObject() {
Rigidbody gameObjectsRigidBody = Stick.AddComponent<Rigidbody>();
gameObjectsRigidBody.mass = 0.1f;
Debug.Log("added rigidbody to weapon");
}
public void OnThrow() {
anims.SetTrigger("isThrowing");
PrepareObject();
StartCoroutine(LetGo());
}
private IEnumerator LetGo() {
Debug.Log("let go of weapon");
yield return new WaitForSeconds(1f);
Hand.transform.DetachChildren();
beingThrown = true;
}
public void OnInteract() {
playerInRange = Physics.CheckSphere(Stick.transform.position, interactRange, layerPlayer);
Debug.Log("Pressed interact");
if (playerInRange && beingThrown) {
Debug.Log("attempted pickup");
Stick.transform.SetParent(HandTransform);
} else return;
beingThrown = false;
}
When using this code, I get OnInteract() while I'm holding the weapon, but as soon as I LetGo(), I no longer get OnInteract(). I am using the Unity Input System BroadcastMessage() on the parent.. I'm kind of lost on what to do, any suggestions?

Disabling colliders for a specified amount of time in Unity2D

I am very new to programming and Unity so please have patience. I am trying to disable a collider for a specified amount of time upon pressing a button (In this case, the button "s"). I have been trying to do this by using the "WaitForSeconds" command but I have never used it before and I don't know it works, but I tried anyway and as I expected it didn't work. I have to mention that the simulation does run with no errors but when I press "s" the collider doesn't disable at all. As I have mentioned I am very new to programming and Unity so I apologize if I don't understand some solutions you may suggest. Anyways here is the code
{
public bool IsFacingR = true;
public bool IsFacingL = false;
public Rigidbody2D RB;
public BoxCollider2D m_col;
void Update()
{
if (Input.GetKeyDown(KeyCode.D))
{
IsFacingR = true;
IsFacingL = false;
}
if (Input.GetKeyDown(KeyCode.A))
{
IsFacingR = false;
IsFacingL = true;
}
if (Input.GetKeyDown(KeyCode.S))
{
Slider();
StartCoroutine(SlideCol());
m_col.enabled = false;
}
}
void Slider()
{
if (IsFacingR == true)
{
RB.AddForce(Vector2.right * 9999f);
}
if (IsFacingL == true)
{
RB.AddForce(-Vector2.right * 9999f);
}
}
IEnumerator SlideCol()
{
yield return new WaitForSeconds(1);
m_col.enabled = true;
}
}
Well i tried your code in my project and it works fine. I can see collider disable for one second. Maybe there is someting that makes your collider always enable.

Unity Input.GetKeyDown(KeyCode.Space) not detecting key Press

I'm learning Unity but my key press isn't being detected
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
public Rigidbody myBody;
private float time = 0.0f;
private bool isMoving = false;
private bool isJumpPressed = false;
void Start(){
myBody = GetComponent<Rigidbody>();
}
void Update()
{
isJumpPressed = Input.GetKeyDown(KeyCode.Space);
Debug.Log(isJumpPressed);
}
void FixedUpdate(){
if(isJumpPressed){
myBody.velocity = new Vector3(0,10,0);
isMoving = true;
Debug.Log("jump");
}
if(isMoving){
time = time + Time.fixedDeltaTime;
if(time>10.0f)
{
//Debug.Log( Debug.Log(gameObject.transform.position.y + " : " + time));
time = 0.0f;
}
}
}
}
why isJumpPressed always false. What am I doing wrong? From what I understand this should work but I'm obviously missing something
UPDATE:
Thanks to everyone who proposed ideas. I got the isJumpPressed to return true when I stopped trying to detect the space bar.
isJumpPressed = Input.GetKeyDown("a");
anyone got any ideas why this would work and not
isJumpPressed = Input.GetKeyDown(KeyCode.Space);
or
isJumpPressed = Input.GetKeyDown("space");
UPDATE 2:
Apparently this is a bug in Linux. I've read it won't happen when the game is built just in the editor. I've found a workaround at
https://forum.unity.com/threads/space-not-working.946974/?_ga=2.25366461.1247665695.1598713842-86850982.1598713842#post-6188199
If any Googler's Stumble upon this issue reference the following code because this is working for me.
public class Player : MonoBehaviour
{
public Rigidbody myBody;
private float time = 0.0f;
private bool isMoving = false;
private bool isJumpPressed = false;
void Start(){
myBody = GetComponent<Rigidbody>();
}
void Update()
{
isJumpPressed = Input.GetKeyDown(SpacebarKey());
if(isJumpPressed)
{
Debug.Log(isJumpPressed);
}
}
void FixedUpdate(){
if(isJumpPressed){
myBody.velocity = new Vector3(0,10,0);
isMoving = true;
Debug.Log("jump");
}
if(isMoving){
time = time + Time.fixedDeltaTime;
if(time>10.0f)
{
//Debug.Log( Debug.Log(gameObject.transform.position.y + " : " + time));
time = 0.0f;
}
}
}
public static KeyCode SpacebarKey() {
if (Application.isEditor) return KeyCode.O;
else return KeyCode.Space;
}
}
The problem is that FixedUpdate and Update are not called one after the other. Update is called once per frame and FixedUpdate is called once per physics update (default is 50 updates per second).
So the following could happen:
Update is called -> GetKeyDown is true (this frame only) -> isJumpPressed = true
Update is called -> GetKeyDown is false -> isJumpPressed = false
FixedUpdate is called -> isJumpPressed is false
Here is an example that prints "Update Jump" every time you press space, and "FixedUpdate Jump" only sometimes when you press space. Do not do this:
bool isJumpPressed;
void Update()
{
isJumpPressed = Input.GetKeyDown(KeyCode.Space);
if (isJumpPressed)
{
Debug.Log("Update Jump");
}
}
private void FixedUpdate()
{
if (isJumpPressed)
{
Debug.Log("FixedUpdate Jump");
}
}
Do this instead:
bool isJumpPressed;
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
isJumpPressed = true;
Debug.Log("Update Jump");
}
}
private void FixedUpdate()
{
if (isJumpPressed)
{
Debug.Log("FixedUpdate Jump");
isJumpPressed = false;
}
}
One possible problem is that your project might be using the new Input System package. If you are using this package, the old Input Manager functions will not work. To check this, go to Edit > Project Settings... > Player > Other Settings and Active Input Handling should be set to Input Manager (Old) (or Both might also work).
If you actually want to use the Input System package, you have to install it if you don't already have it, and you would check if the spacebar is pressed like so:
using UnityEngine.InputSystem;
...
jumpPressed = Keyboard.current.space.wasPressedThisFrame;
I had the same issue. Spend some hours trying to figure out.
Maybe all was good from the beginning until I figured out.
When you I executed the play to see the result and test the movement, by mistake I chose the scene tab. If I want to see the result (to play) I must click the "Game Tab". In Game Tab the keys were detected.enter image description here

Stop co-routine with a toggle button

I have a co-routine that is triggered when the bool of a toggle button changes, when the bool is changed again that co-routine should be stopped and another one should start. This is my code:
public class thermoPowerControlPanel : MonoBehaviour {
private ThermoElectric thermo;
public bool toggleBool1;
public int temperature;
private int tempUp = 10;
private int tempDown = 1;
public thermoPowerControlPanel (){
temperature = 100;
}
public void turbine1State (bool toggleBool1) {
if (toggleBool1 == false) {
Debug.Log (toggleBool1);
Invoke("ReduceTemperatureEverySecond", 1f);
}
if (toggleBool1 == true) {
Debug.Log (toggleBool1);
Invoke("IncreaseTemperatureEverySecond", 1f);
}
}
private void ReduceTemperatureEverySecond()
{
if (toggleBool1 == true)
{
Debug.Log("I was told to stop reducing the temperature.");
return;
}
temperature = temperature - tempDown;
Debug.Log (temperature);
Invoke("ReduceTemperatureEverySecond", 1f);
}
private void IncreaseTemperatureEverySecond()
{
if (toggleBool1 == false)
{
Debug.Log("I was told to stop increasing the temperature.");
return;
}
temperature = temperature + tempUp;
Debug.Log (temperature);
Invoke("ReduceTemperatureEverySecond", 1f);
}
}
When the function turbine1State(bool t1) receives the first bool (false), the routine decreaseTemperatureEverySecond() starts but it stops immediately after, sending the Debug.Log message, it should keep reducing the temperature until the bool (activated by the toggle button) turned true.
.
Can you help?
It is this easy!
public Toggle tog; // DONT FORGET TO SET IN EDITOR
in Start ...
InvokeRepeating( "Temp", 1f, 1f );
... and then ...
private void Temp()
{
if (tog.isOn)
temperature = temperature + 1;
else
temperature = temperature - 1;
// also, ensure it is never outside of 0-100
temperature = Mathf.Clamp(temperature, 0,100);
}
If you ever need to "totally stop" that action (both up and down), just do this
CancelInvoke("Temp");
So easy!
NOTE purely FYI, the other pattern I explained is this:
bool some flag;
Invoke("Temp", 1f);
private void Temp()
{
if (some flag is tripped) stop doing this
.. do something ..
Invoke( ..myself again after a second .. )
}
In real life, it is usually better to "keep Invoking yourself" rather than use InvokeRepeating.
In this simple example, just use InvokeRepeating, and then CancelInvoke.
You can stop coroutine only by it name.
Just try some like StartCoroutine("increaseTemperature"); and then StopCoroutine("increaseTemperature");.
http://docs.unity3d.com/Manual/Coroutines.html
There are a number of ways to call StartCoroutine. You can "stop" a coroutines IF you start it with the "string" method, like this StartCoroutine("FunctionNameAsStringHere"); If you start it like this StartCoroutine(FunctionNameAsStringHere()); you cannot stop it by name.
(You can also access the actual enumerator to stop coroutines, but that is far beyond the scope of a beginner using coroutines.)

Categories