I have a little companion that follows the player around, but he is also supposed to turn around when the player does. But instead, he constantly keeps turning could someone help me see where I went wrong
public float speed;
public float stoppingDistance;
private Transform target;
Player Player;
void Start()
{
Player = GameObject.Find("Axel").GetComponent<Player>();
target = GameObject.FindGameObjectWithTag("Player").GetComponent<Transform>();
}
void Update()
{
if (Vector2.Distance(transform.position, target.position) > stoppingDistance)
{
transform.position = Vector2.MoveTowards(transform.position, target.position, speed * Time.deltaTime);
if (Player.facingRight == false)
{
Turn();
}
else if (Player.facingRight == true)
{
Turn();
}
}
}
void Turn()
{
Vector3 Scaler = transform.localScale;
Scaler.x *= -1;
transform.localScale = Scaler;
}
Well you call Turn "all the time" but you never check if you probably already are looking in the correct direction ...
either change your condition or add a parameter to Turn and set a fixed direction instead of toggling between two directions without controlling which is the current direction.
You could do it e.g. with an enum Direction like
public float speed;
public float stoppingDistance;
private Transform target;
Player Player;
private enum Direction
{
right = 1,
left = -1;
}
// for storing the scale on start
private Vector3 originalScale;
private void Start()
{
Player = GameObject.Find("Axel").GetComponent<Player>();
// If you just want the transform of the Player
// than simply use
target = Player.transform;
// instead of
//target = GameObject.FindGameObjectWithTag("Player").GetComponent<Transform>();
originalScale = transform.localScale;
}
// Btw: for reacting to any user input (as the player movement)
// it is always better to do these things
// in LateUpdate instead of Update
// LateUpdate is executed after all Update calls in the Scene finished
private void LateUpdate()
{
if (Vector2.Distance(transform.position, target.position) > stoppingDistance)
{
transform.position = Vector2.MoveTowards(transform.position, target.position, speed * Time.deltaTime);
if (Player.facingRight == false)
{
Turn(Direction .left);
}
else if (Player.facingRight == true)
{
Turn(Direction .right);
}
}
}
private void Turn(Direction direction)
{
transform.localScale = Vector3.Scale(originalScale, Vector3.right * (int)direction);
}
As alternative if you want to keep your original Turn you could as well simply add more conditions before you even call Turn. Something like
// ...
if (Player.facingRight == false && transform.localScale.x > 0)
{
Turn();
}
else if (Player.facingRight == true && transform.localScale.x < 0)
{
Turn();
}
// ...
private void Turn()
{
transform.localScale = Vector3.Scale(transform.localScale, -Vector3.right);
}
Related
I have a problem where when I click the dialogue while passing through an NPC or while moving, the character will continue moving in the direction of the joystick before it is set active to false.
I have tried setting the horizontal and vertical input to zero and even the movement direction which is the calculated horizontal and vertical input, but the character still moves in the last direction it was moving. I'm not sure if the if statement that I created is correct but that was the idea that came to mind.
This is the script for the movement of the player:
using UnityEngine;
using UnityEngine.EventSystems;
public class PlayerMovement : MonoBehaviour
{
[SerializeField] private GameObject interactButton;
public float speed, rotationSpeed, ySpeed, originalStepOffset;
public Joystick joystick;
private Animator animator;
private CharacterController characterController;
void Start()
{
animator = GetComponent<Animator>();
characterController = GetComponent<CharacterController>();
originalStepOffset = characterController.stepOffset;
}
// Update is called once per frame
void FixedUpdate()
{
float horizontalInput = joystick.Horizontal;
float verticalInput = joystick.Vertical;
Vector3 movementDirection = new Vector3(horizontalInput, 0, verticalInput);
movementDirection = Quaternion.Euler(0, 45f, 0) * movementDirection;
float magnitude = Mathf.Clamp01(movementDirection.magnitude) * speed;
movementDirection.Normalize();
ySpeed += Physics.gravity.y * Time.deltaTime;
if(characterController.isGrounded)
{
characterController.stepOffset = originalStepOffset;
ySpeed = -0.5f;
}
else
{
characterController.stepOffset = 0;
}
Vector3 velocity = movementDirection * magnitude;
velocity = AdjustVelocityToSlope(velocity);
velocity.y += ySpeed;
//transform.Translate(movementDirection * speed * Time.deltaTime, Space.World);
characterController.Move(velocity * Time.deltaTime);
if (movementDirection != Vector3.zero)
{
if(EventSystem.current.currentSelectedGameObject != null)
{
if (EventSystem.current.currentSelectedGameObject.name == "InteractButton")
{
horizontalInput = 0f;
verticalInput = 0f;
movementDirection = Vector3.zero;
animator.SetBool("IsMoving", false);
}
else
{
//transform.forward = movementDirection;
animator.SetBool("IsMoving", true);
Quaternion toRotation = Quaternion.LookRotation(movementDirection, Vector3.up);
transform.rotation = Quaternion.RotateTowards(transform.rotation, toRotation, rotationSpeed * Time.deltaTime);
}
}else
{
animator.SetBool("IsMoving", true);
Quaternion toRotation = Quaternion.LookRotation(movementDirection, Vector3.up);
transform.rotation = Quaternion.RotateTowards(transform.rotation, toRotation, rotationSpeed * Time.deltaTime);
}
} else
{
animator.SetBool("IsMoving", false);
}
}
private Vector3 AdjustVelocityToSlope(Vector3 velocity)
{
var ray = new Ray(transform.position, Vector3.down);
if(Physics.Raycast(ray, out RaycastHit hitInfo, 0.2f))
{
var slopeRotation = Quaternion.FromToRotation(Vector3.up, hitInfo.normal);
var adjustedVelocity = slopeRotation * velocity;
if(adjustedVelocity.y < 0)
{
return adjustedVelocity;
}
}
return velocity;
}
}
The function that will make the button to set active to true and add its onclick listeners is a trigger enter function and a trigger exit for setting it active to false and removing the onclick listeners. It is attached to multiple NPC whenever I go near them, the button will appear. In case someone needs to see the script for the button here it is, but I'm gonna paste only until the related part:
using System;
using System.Collections;
using System.Drawing;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
[System.Serializable]
public class DialogueManager : MonoBehaviour
{
/*
// NPC INTERACTION
private static DialogueManager buttonOwner;
private Camera mainCamera;
*/
// NPC DATA
private Character characterJson;
// FOR DIALOG FLOW
private int multiDialogCycle;
public static int dialogId;
// GAME OBJECTS REFERENCE
[SerializeField] private GameObject dialogBox, addPanel, m1, id, darkPanel;
[SerializeField] private Button nameBtn, choiceOneBtn, choiceTwoBtn;
[SerializeField] private TextMeshProUGUI npcName, choiceOne, choiceTwo, dialogName, dialogMessage, addedText;
[SerializeField] private TextAsset characterData;
// FOR MULTIPLE DIALOGUES WITHOUT CHOICES
private bool clickEnable;
private string[] multiDialog;
// CHOICES STORAGE
private static Choice[] choices = new Choice[2];
//private TryInstantiate tryInstantiate = new TryInstantiate();
private GameData gameData;
private void Start()
{
// LOAD NPC DATA
characterJson = JsonUtility.FromJson<Character>(characterData.text);
// SET DEFAULT VALUE
//dialogId = 0;
multiDialogCycle = 0;
clickEnable = false;
// SET SCRIPTABLE OBJECT FOR DATA STORAGE
gameData = Resources.Load<GameData>("GameData/GameData");
}
private void Update()
{
if(nameBtn.gameObject.activeInHierarchy)
{
if(Input.GetKey(KeyCode.Space))
{
EventSystem.current.SetSelectedGameObject(nameBtn.gameObject);
}
}
// FOR ENDING DIALOGUES WITHOUT CHOICES
if (Input.GetMouseButtonDown(0) && clickEnable == true)
{
if (multiDialog.Length > 1 )
{
if(getDialog(dialogId).choices.Length == 0 && multiDialogCycle == multiDialog.Length - 1)
{
addArticles();
addObjectives();
addClues();
closeDialog();
Debug.Log(getDialog(dialogId).minigame);
}
else
{
if(getDialog(dialogId).minigame != "" && multiDialogCycle < multiDialog.Length - 1)
{
if(!gameData.idShown)
{
darkPanel.SetActive(true);
id.SetActive(true);
}
}else
{
multiDialogCycle++;
loadCharacterData();
}
}
}
else
{
if (getDialog(dialogId).minigame != "")
{
if(getDialog(dialogId).minigame == "spot_object")
{
m1.SetActive(true);
}
dialogBox.SetActive(false);
gameData.dialogActive = false;
}
else
{
addArticles();
addObjectives();
addClues();
closeDialog();
}
}
}
if(gameData.idShown)
{
multiDialogCycle++;
loadCharacterData();
gameData.idShown = false;
}
if (gameData.dialogActive && dialogId != 0 && getDialog(dialogId).minigame != "" && !gameData.loadedData)
{
updateID();
loadCharacterData();
}
// FOR NPC NAMES
/*
if (buttonOwner != this) return;
if (!mainCamera) mainCamera = Camera.main;
var position = mainCamera.WorldToScreenPoint(head.position + offset);
//uiUse.transform.position = position;
nameBtn.transform.position = position;
*/
}
private void OnTriggerEnter(Collider collisionInfo)
{
if (collisionInfo.CompareTag("Player"))
{
nameBtn.gameObject.SetActive(true);
nameBtn.GetComponent<Image>().sprite = Resources.Load<Sprite>("InteractionAsset/DIALOGUE");
nameBtn.transform.GetChild(0).GetComponent<TextMeshProUGUI>().color = new Color32(75,75,75,255);
//buttonOwner = this;
nameBtn.onClick.RemoveListener(onNameClick);
nameBtn.onClick.AddListener(onNameClick);
choiceOneBtn.onClick.RemoveListener(onChoiceClick);
choiceOneBtn.onClick.AddListener(onChoiceClick);
choiceTwoBtn.onClick.RemoveListener(onChoiceClick);
choiceTwoBtn.onClick.AddListener(onChoiceClick);
npcName.text = characterJson.name;
}
}
private void OnTriggerExit(Collider collisionInfo)
{
if (collisionInfo.CompareTag("Player"))
{
nameBtn.onClick.RemoveListener(onNameClick);
choiceOneBtn.onClick.RemoveListener(onChoiceClick);
choiceTwoBtn.onClick.RemoveListener(onChoiceClick);
nameBtn.gameObject.SetActive(false);
//buttonOwner = null;
}
}
// DIALOGUE SYSTEM
public void onNameClick()
{
nameBtn.gameObject.SetActive(false);
dialogBox.SetActive(true);
gameData.dialogActive = true;
FindObjectOfType<AudioManager>().Play("ButtonSound");
if (dialogBox.activeInHierarchy)
{
if (dialogMessage != null && dialogName != null)
{
loadCharacterData();
interactedNPC();
}
else
{
// Debug.Log("null dialog message");
}
}
}
public void updateID()
{
if (gameData.win && !gameData.loadedData)
{
dialogId = gameData.targetId_1;
gameData.loadedData = true;
}
else if (!gameData.win && !gameData.loadedData)
{
dialogId = gameData.targetId_2;
gameData.loadedData = true;
}
}
How can I atleast reset the Joystick's position. I am currently using the Joystick Pack from Unity Asset Store.
I for one would start the Conversation flow from the dialogue step. Once the dialog starts, you can either set a bool or any other type of property (even a referenced object such as the dialogue itself or the NPC that it's chatting to, but a bool is simpler) that would be a marker. Simply put: public bool IsTalking;. This could be in PlayerMovement or the main Player-like component (or SO) which you can access from PlayerMovement via GameObject or a different public variable.
Next, once the dialogue mechanism starts, you can set the marker (such as the bool or NPC ref) in the Player-like component or PlayerMovement to true, false when the chatting stops.
Then in PlayerMovement.FixedUpdate() you can just stop code execution:
// Update is called once per frame
void FixedUpdate()
{
// or if (Player.IsTalking == true) if you want the marker there,
// but you can have it inside PlayerMovement if you wish as long as
// you can reference it without overhead - meaning don't do crazy
// stuff like searching all game objects/components for PlayerMovement)
if (IsTalking == true)
{
if (characterController != null)
characterController.Move(Vector3.zero);
if (animator != null)
animator.SetBool("IsMoving", false);
return;
}
// everything starts executing once the player stops talking to NPCs
float horizontalInput = joystick.Horizontal;
float verticalInput = joystick.Vertical;
Vector3 movementDirection = new Vector3(horizontalInput, 0, verticalInput);
movementDirection = Quaternion.Euler(0, 45f, 0) * movementDirection;
float magnitude = Mathf.Clamp01(movementDirection.
As I understood you dialogue window is modal. Depending on your needs you can stop the internal increment of time progression with
Time.timeScale = 0f;
But this will stop e.g. (UI) animations and the proper function of the event system, so some people use
Time.timeScale = 0.0001f;
This has the drawback that the gameplay continues slowly and our player hero could be hit by a very slow rocket while the user was resting in a pause screen.
I'm getting this error in Unity after pressing the relative "eastButton":
Error: MissingReferenceException while executing 'canceled' callbacks of 'Gameplay/Block[/DualShock4GamepadHID/buttonEast,/Mouse/rightButton]'
UnityEngine.InputSystem.InputActionAsset:OnDestroy () (at Library/PackageCache/com.unity.inputsystem#1.0.2/InputSystem/Actions/InputActionAsset.cs:794)
I'm attempting to set up 'block' functionality within my game which simply locks the player's movement then disables my "canBeHit" bool which will then put the player in a state where they cannot be hit, however upon interacting with the eastButton all player movement is completely locked preventing you from moving, however rotation is still possible. Another part I'd like to note is that I'm working with the Mirror package so I can network my game, unsure if this could also be causing problems. Would really appreciate if someone could point out where I've gone wrong, I'd be very grateful.
PlayerMovementTest.cs
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
using Mirror;
public class PlayerMovementTest : NetworkBehaviour
{
[Header ("Default")]
Rigidbody rb;
PlayerInput playerInput;
PlayerControls playerInputActions;
public bool isStunned;
public float stunLockTimer;
public float stunLockTimerEnd;
public Animator animations;
public ParticleSystem stunParticle;
[Header("Movement")]
public float speed;
public float baseSpeed;
[Header ("Dodge")]
public float dodgeMultiplier;
public float dodgeTimer;
public float dodgeTimerMax;
public int dodgeCheck;
public float howLongDodgeLast;
public bool isDodging;
public GameObject enemyPlayer;
public GameObject dodgeParticle;
[Header("Attack")]
public int attackReset;
public float attackTimer;
public float attackTimerResetMax;
public float attackPlayingTimerEnd;
public float lungeAttackLungeMultiplier;
public bool isAttacking;
public GameObject attackParticle;
public GameObject attackHitboxObject;
public GameObject lungeAttackHitboxObject;
[Header("Block")]
public bool canBeHit;
private void Awake()
{
playerInput = GetComponent<PlayerInput>();
rb = GetComponent<Rigidbody>();
playerInputActions = new PlayerControls();
// freezes rotation
rb.freezeRotation = true;
// enables the input controller
playerInputActions.Gameplay.Enable();
playerInputActions.Gameplay.Attack.performed += Attack;
playerInputActions.Gameplay.Attack.canceled += Attack;
playerInputActions.Gameplay.Dodge.performed += Dodge;
playerInputActions.Gameplay.Block.canceled += Block;
playerInputActions.Gameplay.Block.performed += Block;
isStunned = false;
}
public void Update()
{
if (!isLocalPlayer)
return;
// dodge timers and reset
if (dodgeCheck != 1)
dodgeTimer += Time.deltaTime;
if (dodgeTimer > dodgeTimerMax)
{
dodgeCheck += 1;
dodgeTimer = 0;
}
// turns on the slide particle when sliding
if (isDodging == true)
dodgeParticle.SetActive(enabled);
else
dodgeParticle.SetActive(false);
// sets how long the EFFECT of the dash lasts for
// not how long the player will move but how long
// they will be able to collide with another player
// and stun them
if (dodgeTimer > howLongDodgeLast)
isDodging = false;
// attack timers and reset
if (attackReset != 1)
{
attackTimer += Time.deltaTime;
}
// after a certian amount of time the player attack goes away
if (attackTimer > attackPlayingTimerEnd)
{
//attackParticle.SetActive(false);
attackHitboxObject.SetActive(false);
lungeAttackHitboxObject.SetActive(false);
}
if (attackTimer > attackTimerResetMax)
{
attackReset += 1;
attackTimer = 0;
}
// makes it so attacks dont go into the minuses
if (attackReset < 0)
attackReset = 0;
// when player gets stunned it starts a timer where they cannot do any moves
if (isStunned == true)
{
stunLockTimer += Time.deltaTime;
if (stunLockTimer > stunLockTimerEnd)
{
isStunned = false;
stunLockTimer = 0f;
speed = baseSpeed;
}
}
// chanegs between animations depending on how fast the player is moving
if (rb.velocity.magnitude > 4f)
animations.SetFloat("Walk", rb.velocity.magnitude);
if (rb.velocity.magnitude < 4f)
animations.SetFloat("Idle", rb.velocity.magnitude);
// if player is stunned then play stun particle
if (isStunned == true)
stunParticle.Play();
else
stunParticle.Stop();
//edited out the print as it got distracting
// print(rb.velocity.magnitude);
}
// for dodge stun
public void OnCollisionEnter(Collision col)
{
// if you hit a player
if( col.gameObject.tag == "Player")
{
// and in dodge, number picked just as an idea of how long the dash could last
if (dodgeTimer > .1f && dodgeTimer < 1f)
{
// sets the player hit as a gameobject that cabn be affected in script
enemyPlayer = col.gameObject;
// decreases speed (or in this case increases as i cannot test 2 players
enemyPlayer.GetComponent<PlayerMovementTest>().speed = 0;
enemyPlayer.GetComponent<PlayerMovementTest>().isStunned = true;
}
}
}
public void FixedUpdate()
{
// calls movement function
Movement();
}
public void Attack(InputAction.CallbackContext context)
{
if (attackReset == 1 && isStunned == false)
{
isAttacking = true;
attackHitboxObject.SetActive(enabled);
attackReset -= 1;
animations.SetTrigger("Attack");
attackParticle.SetActive(enabled);
}
//if (attackReset == 1 && isStunned == false)
/*
if (context.performed)
{
print("attack start");
}
if (context.canceled)
{
// for luneg attack
rb.AddForce(transform.forward * lungeAttackLungeMultiplier, ForceMode.Impulse);
isAttacking = true;
lungeAttackHitboxObject.SetActive(enabled);
attackReset -= 1;
// animations.SetTrigger("Attack");
// attackParticle.SetActive(enabled);
print("attack end");
}*/
}
public void Dodge(InputAction.CallbackContext context)
{
// checks if the player has a dodge
if (dodgeCheck == 1 && isStunned == false)
{
// used for lockign slide in place and particle effect
isDodging = true;
// adds a force to the player, spped can be adjusted with dodgeMultiplier
rb.AddForce(transform.forward * dodgeMultiplier, ForceMode.Impulse);
// removes dodge
dodgeCheck -= 1;
animations.SetTrigger("Slide");
}
}
public void Block(InputAction.CallbackContext context)
{
if (isStunned == false)
{ // when you hold the button, you lose your speed
if (context.performed)
{
canBeHit = false;
speed = 3;
animations.SetBool("Block", true);
}
// when you release it your speed goes back to normal
if (context.canceled)
{
canBeHit = true;
speed = 15;
animations.SetBool("Block", false);
}
}
}
public void Movement()
{
// used for locking slide in place, no rotation
if (isDodging == false)
{
//player movement, can only use vector2 for controller so we use a vector3
// but store the x and z in a vector 2
Vector2 inputVector = playerInputActions.Gameplay.Walk.ReadValue<Vector2>();
Vector3 tempVec = new Vector3(inputVector.x, 0, inputVector.y);
// adds force to the vector, do this seperately so we can use
//the variable for the player rotation
rb.AddForce(tempVec * speed, ForceMode.Force);
if (tempVec != Vector3.zero)
{
// finds the direction the player is moving
Quaternion targetRotation = Quaternion.LookRotation(tempVec);
// rotates players towards the way they are facing
targetRotation = Quaternion.RotateTowards(transform.rotation, targetRotation, 360 * Time.fixedDeltaTime);
rb.MoveRotation(targetRotation);
}
}
}
}
If any additional information is needed to solve my problem I'm more than happy to submit any necessary info.
So I was trying to make a game just for fun and to learn for future use. So I encountered this problem in making the enemy NPC. I want it to follow me or chase me but I want the NPC to only move horizontal and vertical and I want the NPC to move per tile as well just like my Player.
Here's the video of how it looks
https://www.youtube.com/watch?v=CB_vdt1Z3nA
and here's the NPC script
public class ChaseScript : MonoBehaviour
{
public float speed;
private GameObject player;
private Transform player_transform;
void Start()
{
player = GameObject.Find("Player");
}
void Update()
{
player_transform = player.GetComponent<Transform>();
transform.position = Vector3.MoveTowards(transform.position, player_transform.position, speed * Time.deltaTime);
}
}
Here's my player controller
public void InputMove()
{
if (!isMoving)
{
input.x = Input.GetAxisRaw("Horizontal");
input.y = Input.GetAxisRaw("Vertical");
if (input.x != 0) input.y = 0;
if (input != Vector2.zero)
{
playerAnimation.SetParameterValue(animator);
var movePos = transform.position;
movePos.x += input.x;
movePos.y += input.y;
FacingForward.transform.position = movePos;
if (IsWalkable(movePos))
StartCoroutine(Move(movePos));
}
playerAnimation.SetParameterValueisMoving(animator);
}
if (Input.GetKey(KeyCode.LeftShift))
{
moveSpeed = 6f;
animator.speed = 1.5f;
}
else
{
moveSpeed = 4f;
animator.speed = 1f;
}
}
IEnumerator Move(Vector3 movePos)
{
isMoving = true;
while ((movePos - transform.position).sqrMagnitude > Mathf.Epsilon)
{
transform.position = Vector3.MoveTowards(transform.position, movePos, moveSpeed * Time.deltaTime);
yield return null;
}
transform.position = movePos;
isMoving = false;
}
private bool IsWalkable(Vector3 movePos)
{
if (Physics2D.OverlapCircle(movePos, 0.1f, SolidObjectLayer | NPC) != null)
{
return false;
}
return true;
}
What I did to my Player to move per tile is I just add 1 to transform so It'll be a constant movement but I don't know how to apply it on the NPC with the Vector3.MoveTowards but if it's not possible to do then it's fine
Check if this could work (you can adapt it to your 2D case)
using UnityEngine;
public class ChaseOrthoScript : MonoBehaviour
{
public float speed;
private GameObject player;
private Transform player_transform;
bool isMoving = false;
void Start()
{
player = GameObject.Find("Player");
player_transform = player.GetComponent<Transform>();
transform.LookAt(player_transform.position);
}
void Update()
{
if (transform.InverseTransformPoint(player_transform.position).z > 0) {
transform.position += transform.forward * speed * Time.deltaTime;
isMoving = true;
} else {
if (isMoving) {
float angle = Vector3.Angle(transform.forward, player_transform.position - transform.position);
transform.Rotate(Vector3.up, Mathf.Sign(angle) * 90);
isMoving = false;
} else if (transform.InverseTransformPoint(player_transform.position).z <= 0) { //player is back
transform.Rotate(Vector3.up, 180);
}
}
}
}
note that the player_transform = player.GetComponent<Transform>(); is moved to the Start(). Usually you dont want GetComponents in an update as you need to get it only once. ALso its much cleaner to have a public GameObject player; variable in the script and attach the reference in the editor that the player = GameObject.Find("Player");. Usually you dont want scene elements found by a hardcoded magic value in your code.
Hope that helps.
I am new to Unity and I am using the following CharacterController for my character. Everything is working well, except that sometimes the character jumps and sometimes it doesn't when I hit the spacebar. I used Debog.Log using Raycast to check if my character is grounded, and the result was True. So what is preventing my character from jumping whenever I hit the key?
using UnityEngine;
using System.Collections;
[RequireComponent(typeof(CharacterController))]
public class RPGMovement : MonoBehaviour
{
public float ForwardSpeed = 8f;
public float BackwardSpeed = 4f;
public float StrafeSpeed = 5f;
public float RotateSpeed = 110f;
CharacterController m_CharacterController;
Vector3 m_LastPosition;
Animator m_Animator;
PhotonView m_PhotonView;
PhotonTransformView m_TransformView;
float m_AnimatorSpeed;
Vector3 m_CurrentMovement;
float m_CurrentTurnSpeed;
Vector3 playerVelocity;
private bool groundedPlayer;
private float jumpHeight = 0.9f;
private float gravityValue = -20.81f;
void Start()
{
m_CharacterController = GetComponent<CharacterController>();
m_Animator = GetComponent<Animator>();
m_PhotonView = GetComponent<PhotonView>();
m_TransformView = GetComponent<PhotonTransformView>();
}
void Update()
{
if (m_PhotonView.isMine == true)
{
ResetSpeedValues();
UpdateRotateMovement();
UpdateForwardMovement();
UpdateBackwardMovement();
UpdateStrafeMovement();
MoveCharacterController();
UpdateJump();
ApplySynchronizedValues();
}
UpdateAnimation();
}
void UpdateAnimation()
{
Vector3 movementVector = transform.position - m_LastPosition;
float speed = Vector3.Dot(movementVector.normalized, transform.forward);
float direction = Vector3.Dot(movementVector.normalized, transform.right);
if (Mathf.Abs(speed) < 0.2f)
{
speed = 0f;
}
if (speed > 0.6f)
{
speed = 1f;
direction = 0f;
}
if (speed >= 0f)
{
if (Mathf.Abs(direction) > 0.7f)
{
speed = 1f;
}
}
m_AnimatorSpeed = Mathf.MoveTowards(m_AnimatorSpeed, speed, Time.deltaTime * 5f);
m_Animator.SetFloat("Speed", m_AnimatorSpeed);
m_Animator.SetFloat("Direction", direction);
m_LastPosition = transform.position;
}
void ResetSpeedValues()
{
m_CurrentMovement = Vector3.zero;
m_CurrentTurnSpeed = 0;
}
void ApplySynchronizedValues()
{
m_TransformView.SetSynchronizedValues(m_CurrentMovement, m_CurrentTurnSpeed);
}
void MoveCharacterController()
{
m_CharacterController.Move(m_CurrentMovement * Time.deltaTime);
}
void UpdateForwardMovement()
{
if (Input.GetKey(KeyCode.W) || Input.GetAxisRaw("Vertical") > 0.1f)
{
m_CurrentMovement = transform.forward * ForwardSpeed;
}
}
void UpdateBackwardMovement()
{
if (Input.GetKey(KeyCode.S) || Input.GetAxisRaw("Vertical") < -0.1f)
{
m_CurrentMovement = -transform.forward * BackwardSpeed;
}
}
void UpdateStrafeMovement()
{
if (Input.GetKey(KeyCode.Q) == true)
{
m_CurrentMovement = -transform.right * StrafeSpeed;
}
if (Input.GetKey(KeyCode.E) == true)
{
m_CurrentMovement = transform.right * StrafeSpeed;
}
}
void UpdateRotateMovement()
{
if (Input.GetKey(KeyCode.A) || Input.GetAxisRaw("Horizontal") < -0.1f)
{
m_CurrentTurnSpeed = -RotateSpeed;
transform.Rotate(0.0f, -RotateSpeed * Time.deltaTime, 0.0f);
}
if (Input.GetKey(KeyCode.D) || Input.GetAxisRaw("Horizontal") > 0.1f)
{
m_CurrentTurnSpeed = RotateSpeed;
transform.Rotate(0.0f, RotateSpeed * Time.deltaTime, 0.0f);
}
}
void UpdateJump()
{
groundedPlayer = m_CharacterController.isGrounded;
if (groundedPlayer && playerVelocity.y < 0)
{
playerVelocity.y = 0f;
}
if (Input.GetButtonDown("Jump") && groundedPlayer)
{
playerVelocity.y += Mathf.Sqrt(jumpHeight * -3.0f * gravityValue);
m_Animator.SetTrigger("Jump");
print("Jumping Now");
}
playerVelocity.y += gravityValue * Time.deltaTime;
m_CharacterController.Move(playerVelocity * Time.deltaTime);
}
}
Best guess is that "m_PhotonView.isMine" is not returning true on the frames where you're missing input. It only checks jump input for that frame, so if the last frame you pressed it but jumping wasn't checked then that input is lost forever. First test this. Change the update code to this:
void Update()
{
if (Input.GetButtonDown("Jump")) { Debug.Log("Jump was pressed at {Time.time}"); }
if (m_PhotonView.isMine == true)
{
if (Input.GetButtonDown("Jump")) { Debug.Log("Attempting Jump at {Time.time}"); }
ResetSpeedValues();
UpdateRotateMovement();
UpdateForwardMovement();
UpdateBackwardMovement();
UpdateStrafeMovement();
MoveCharacterController();
UpdateJump();
ApplySynchronizedValues();
}
UpdateAnimation();
}
Then play the game and jump a bunch. The first debug log line should happen every time you click the spacebar no matter what. The second debug line would only happen if physics are calculated that frame. Both have times attached. Keep jumping until the jump doesn't work. If that jump only produces the first debug log and not the second, then I am correct and that is your issue.
If so, then it's an easy fix. Add a new bool variable called "jumpInput". Whenever you check if jump was pressed, instead check if "jumpInput" is true. Then, change update to this:
void Update()
{
if (Input.GetButtonDown("Jump")) { jumpInput = true; }
if (m_PhotonView.isMine == true)
{
ResetSpeedValues();
UpdateRotateMovement();
UpdateForwardMovement();
UpdateBackwardMovement();
UpdateStrafeMovement();
MoveCharacterController();
UpdateJump();
ApplySynchronizedValues();
jumpInput = false;
}
UpdateAnimation();
}
This way if you pressed jump, it's set to true... but it's only set to false after physics are done. So if you press jump on frame 20 and physics are somehow not calculated until frame 25, it'll still know that you pressed jump at some point and thus execute it. If you're using networking, you might want to also have another variable that's what frame jump was pressed. That way you can figure out how many frames it's been since input and compensate for missed time in the jump if necessary.
So I was trying to implement double jumping in my game, which doesn't work. And now, somehow, not only can't my players double jump, they can't even jump either!
update: they can jump now, still can't double jump though.
This is my whole movement script:
using UnityEngine;
namespace Players
{
public class Actor : MonoBehaviour
{
//in order to control both players using 1 script.
public int playerIdx;
//Variables.
public float movementSpeed = 150f;
public float jumpForce = 250f;
//Ground stuff.
public LayerMask whatIsGround;
public bool grounded;
//boolean stuff.
private bool facingRight;
private bool moving;
//Needed to check if player is on the ground.
public Transform groundCheck;
//Limit player's movement speed.
public float maxMovementSpeed = 400f;
//Double jump stuff.
private bool doubleJumpReady;
//rb
private Rigidbody2D rb;
// Start is called before the first frame update
void Start()
{
doubleJumpReady = true;
rb = GetComponent<Rigidbody2D>();
facingRight = true;
}
// Update is called once per frame
void FixedUpdate()
{
SlowDown();
}
private void LateUpdate()
{
grounded = Physics2D.OverlapCircle(groundCheck.position, 0.1f, whatIsGround);
if (grounded)
doubleJumpReady = true;
}
private void SlowDown()
{
if (moving) return;
//if player is not moving, slow them down.
if (rb.velocity.x > 0.2f)
rb.AddForce(movementSpeed * Time.deltaTime * -Vector2.right);
if (rb.velocity.x < -0.2f)
rb.AddForce(movementSpeed * Time.deltaTime * Vector2.right);
}
public void Move(int dir)
{
//Flip the player.
Flip(dir);
//Moving the player.
moving = true;
float xVel = rb.velocity.x; //Get x velocity.
if ( dir > 0)
rb.AddForce(movementSpeed * Time.deltaTime * Vector2.right * dir);
else if (dir < 0)
rb.AddForce(movementSpeed * Time.deltaTime * Vector2.right * dir);
else if (dir == 0) { } //do nothing.
//Help player turn around faster.
if (xVel > 0.2f && dir < 0)
rb.AddForce(movementSpeed * 3.2f * Time.deltaTime * -Vector2.right);
if (xVel < 0.2f && dir > 0)
rb.AddForce(movementSpeed * 3.2f * Time.deltaTime * Vector2.right);
}
private void Flip(int dir)
{
if (facingRight && dir == -1 || !facingRight && dir == 1)
{
facingRight = !facingRight;
transform.Rotate(0f, 180f, 0f);
}
}
protected void Jump()
{
if (grounded)
{
rb.AddForce(Vector2.up * jumpForce);
grounded = false;
doubleJumpReady = true;
}
else if (!grounded && doubleJumpReady)
{
rb.AddForce(Vector2.up * jumpForce);
doubleJumpReady = false;
}
}
}
}
I don't know if it is because of my jump script, or my player script:
void Update()
{
if (playerIdx == 1)
{
if (Input.GetKey(KeyCode.A))
Move(-1);
if (Input.GetKey(KeyCode.D))
Move(1);
if (Input.GetKey(KeyCode.W))
Jump();
}
if (playerIdx == 2)
{
if (Input.GetKey(KeyCode.LeftArrow))
Move(-1);
if (Input.GetKey(KeyCode.RightArrow))
Move(1);
if (Input.GetKey(KeyCode.UpArrow))
Jump();
}
}
So how can I fix this?
as far as i can see you never reset the
doubleJumpReady = false;
Variable. To fix this simply change the jump code to:
protected void Jump()
{
if (grounded)
{
rb.AddForce(Vector2.up * jumpForce);
grounded = false;
doubleJumpReady = true;
}
else if (!grounded && doubleJumpReady)
{
rb.AddForce(Vector2.up * jumpForce);
doubleJumpReady = false;
}
}
Hope it works ;).
EDIT:
grounded is set by overlapping spheres. Therefore no need to set it here.
Use this code and press your jump btn 2 times and see if the Debug.Log message shows up. Also, your player ID (idx is not needed.) As far as i can see your script is attached two to different objects. Therefore their variables are not shared anyways.
protected void Jump()
{
if (grounded)
{
rb.AddForce(Vector2.up * jumpForce);
doubleJumpReady = true;
}
else if (!grounded && doubleJumpReady)
{
rb.AddForce(Vector2.up * jumpForce);
doubleJumpReady = false;
Debug.Log("I am double jumping");
}
}
And the final problem is, you do not execute one of your jumps you execute both at once.
THis happens due to your execution.
Input.GetKey(KeyCode.UP)
instead use:
Input.GetKeyDown(KeyCode.Up);
GetKeyDown returns true when the button is pressed.
GetKey returns true WHILE the button is pressed.
Hope it works now ;)
I would implement it with a counter, you can set the number of jumps you want.
The code would be like this:
jumpCount = 0;
protected void Jump()
{
if(!grounded && jumpCount < 2)
{
jumpCount++;
rb.AddForce(Vector2.up * jumpForce);
}
if(grounded)
jumpCount = 0;
}
Going off the assumption that you can now perform the normal jump again after reading the comments. I think the reason you can't 'double jump' is that when you call the Jump() method, you don't just call it once, you call it twice, so what happens is the player jumps and then immediately double jumps and so you don't actually notice that the double jump has occurred. You could make it so that your doubleJumpReady boolean is only true after a set amount of time after you have jumped initially using some sort of co-routine or something I implemented for a sort of double jump mechanic once was that the user could press the jump button again to double jump only when the player had reached the maximum height of the initial jump or after.