The idea is, when the player starts talking to an NPC, I want all movement functions to stop. I’ve sort of done it in my script, but the character can still move on the spot, whether facing up, left or right. I’ve even tried setting the Vector3 to 0, 0, 0 for both moveHorizontal and moveVertical. How can I stop movement altogether? Thanks.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class PlayerController : MonoBehaviour
{
public Animator anim;
public float moveSpeed;
public float jumpForce;
public bool jumped;
public bool attack;
public static bool interact = false;
public static bool allowInteract = false;
public float gravityScale;
public float knockBackForce;
public float knockBackTime;
public float invincibilityLength;
public Renderer playerRenderer;
public Material textureChange;
public Material textureDefault;
public bool allowCombat;
public bool allowJump;
public bool notDestroyed;
public bool canMove;
private Vector3 moveDirection;
private Vector3 extraDirections;
private float knockBackCounter;
private float invincibilityCounter;
private CharacterController controller;
void Start()
{
Cursor.visible = false;
controller = GetComponent<CharacterController>();
canMove = true;
if (SceneManager.GetActiveScene() == SceneManager.GetSceneByName("start_area"))
{
allowCombat = false;
allowJump = true;
}
if (SceneManager.GetActiveScene() == SceneManager.GetSceneByName("hut_interior"))
{
allowCombat = false;
allowJump = false;
}
if (SceneManager.GetActiveScene() == SceneManager.GetSceneByName("start_area 2"))
{
allowCombat = false;
allowJump = true;
}
if (SceneManager.GetActiveScene() == SceneManager.GetSceneByName("level 1"))
{
allowCombat = true;
allowJump = true;
}
}
void Update()
{
if (knockBackCounter <= 0 && canMove)
{
float moveHorizontal = Input.GetAxis("Horizontal");
float moveVertical = Input.GetAxis("Vertical");
moveDirection = new Vector2(moveHorizontal * moveSpeed, moveDirection.y);
extraDirections = new Vector2(moveVertical * moveSpeed, extraDirections.y);
if (moveHorizontal > 0)
{
transform.eulerAngles = new Vector2(0, 90);
}
else if (moveHorizontal < 0)
{
transform.eulerAngles = new Vector2(0, -90);
}
//To possibly prevent diagonal movement with some control setups, try adding 'else if'
else if (SceneManager.GetActiveScene() == SceneManager.GetSceneByName("hut_interior"))
{
if (moveVertical > 0)
{
transform.eulerAngles = new Vector2(0, 0);
}
else if (NPC.charactersTalking)
{
canMove = false;
anim.SetFloat("Speed", 0F);
}
//Use this to make the character face towards the camera.
/*else if (moveVertical < 0)
{
transform.eulerAngles = new Vector3(0, 180);
}*/
}
if (controller.isGrounded)
{
if (allowJump)
{
moveDirection.y = -1f;
//GetKeyDown will require the player to press the button each time they want to jump. GetKey will allow the player to spam the jump button if they keep pressing it down.
if (Input.GetKeyDown(KeyCode.KeypadPlus) || Input.GetKeyDown("joystick button 1"))
{
moveDirection.y = jumpForce;
jumped = true;
}
else if (!Input.GetKeyDown(KeyCode.KeypadPlus) || !Input.GetKeyDown("joystick button 1"))
{
jumped = false;
}
}
if (allowCombat)
{
if (Input.GetKey(KeyCode.Space) || Input.GetKey("joystick button 7"))
{
attack = true;
playerRenderer.material = textureChange;
}
else if (!Input.GetKey(KeyCode.Space) || !Input.GetKey("joystick button 7"))
{
attack = false;
playerRenderer.material = textureDefault;
}
}
else if (!allowCombat)
{
attack = false;
playerRenderer.material = textureDefault;
}
if (allowInteract)
{
if (SceneManagement.xbox360Controller == 1)
{
if (Input.GetKeyDown("joystick button 2"))
{
interact = true;
}
}
else if (SceneManagement.ps4Controller == 1)
{
if (Input.GetKeyDown("joystick button 0"))
{
interact = true;
}
}
else
{
interact = true;
}
}
}
}
else
{
knockBackCounter -= Time.deltaTime;
}
moveDirection.y = moveDirection.y + (Physics.gravity.y * gravityScale * Time.deltaTime);
controller.Move(moveDirection * Time.deltaTime);
anim.SetBool("isGrounded", controller.isGrounded);
anim.SetFloat("Speed", Mathf.Abs(Input.GetAxis("Horizontal")));
if (interact)
{
if (Input.GetKeyDown(KeyCode.Return))
{
anim.SetBool("Interact", controller.isGrounded);
FindObjectOfType<Pickup>().ObjectActivation();
interact = false;
allowInteract = false;
}
}
if (attack)
{
anim.SetTrigger("Attack");
}
}
public void Knockback(Vector3 direction)
{
knockBackCounter = knockBackTime;
moveDirection = direction * knockBackForce;
moveDirection.y = knockBackForce;
}
}
You’ve got this canMove bool you are checking, but you are doing controller.Move outside of that. If all your character movement actually occurs with that controller.Move on line 160, then I’d only call that if canMove is true. Or maybe I’m misunderstanding?
Nicely spotted with the controller.Move; I totally forgot about that. However, I’m still not sure how to get this right. I’ve added the controller.Move where all the controls are under knockBackCounter <= 0 && canMove, but I’m not sure where I should disable it. I’ve tried a few places. I’ve also set controller.Move(new Vector3(0, 0, 0)); but that just makes my character jump up and down on the spot.
Anyone? I tried adding an isIdle bool in the Animator as a condition to use for when every movement transitions back to my Idle animation, but it isn’t working. If I try and define what it is in the script (controller.isIdle) it just gives me a red squiggly line under isIdle. And yet my isGrounded works fine.
I can stop the Animator altogether when talking to an NPC by using controller.enabled = false, but ideally, I’d like to keep the player’s idle animation playing.
Just an idea, didn’t look at your script in too much detail, and even if I did I’m quite the newb, but couldn’t you maybe just disable the CharacterController when you don’t want the player moving at all?
Edit: Whoops, didn’t see your last comment, I guess you already thought of that.
I guess the first thing to establish is the source of the movement. By that, I mean is the movement a result of some of your own code changing the transform.position, or, is it movement being controlled by an animator?
You mention that the player “moves on the spot” and also that you want to “keep the player’s idle animation playing”. The latter is a case of the player moving on the spot. Can you explain a little more what this unwanted movement is exactly.
When I say the player moves on the spot, I mean their walk animation plays if you press left/right, but they walk on the spot. If you look at the script I shared in the first post, I believe the source of movement is through the CharacterController and a Vector3.
In that case, presumably the animator state machine is set to go idle if the speed is below a minimum threshold? If so, then this line seems a potential candidate as the culprit: anim.SetFloat("Speed", Mathf.Abs(Input.GetAxis("Horizontal"))); because there is no guard there for “not currently meeting NPC”.
This is basically the same point as Joe-Censored made above. It looks like you want to handle the movement according to canMove.
I wonder if it may help if you code were structured something more like this? :
public class PlayerController : MonoBehaviour
{
/*
* === Here we have our public methods (no variables) that constitute our interface ===
*/
public void Knockback(Vector2 direction)
{
myKnockBackCounter = myKnockBackTime;
myKnockBack = direction * myKnockBackForce;
}
public bool CanMove
{
set
{
myCanMove = value;
if(!myCanMove)
{
myAnim.SetFloat(ourSpeed, 0f);
}
}
}
/*
* === Here we have our private methods (and variables) ===
*/
void Start()
{
Cursor.visible = false;
myCanMove = true;
myController = GetComponent<CharacterController>();
}
void Update()
{
Vector2 thisMove = Vector2.zero;
if (myKnockBackCounter > 0)
{
myKnockBackCounter -= Time.deltaTime;
thisMove = myKnockBack * Time.deltaTime;
}
else if(myCanMove)
{ // Here we are neither knocked back nor immobilised.
thisMove.x = Input.GetAxis(ourAxisX);
thisMove.y = Input.GetAxis(ourAxisY);
// ... other movement related stuff.
}
myAnim.SetBool("isGrounded", myController.isGrounded);
myController.Move(thisMove);
}
static readonly string ourSpeed = "Speed";
static readonly string ourAxisX = "Horizontal";
static readonly string ourAxisY = "Vertical";
[SerializeField] [Range(0.3f, 1f)] float myKnockBackTime;
[SerializeField] [Range(0.5f, 5f)] float myKnockBackForce;
Animator myAnim;
CharacterController myController;
Vector2 myKnockBack;
float myKnockBackCounter;
bool myCanMove;
}
Thanks for trying to help, but inputting all of that is causing countless issues and ‘object reference not set to an instance of an object’ errors. It’s too much to get my head around. :-\
Apologies, it was not intended to simply replace what you had- it would cause the issues you mention. Really the main point was to keep as little public as possible to restrict and control how the internals of the class can be affected from the outside.
Ok, so to try and keep with the original code as you had it above, I’ll hazard a guess based on what you previously mentioned, you could try changing line 163 from anim.SetFloat("Speed", Mathf.Abs(Input.GetAxis("Horizontal")));
to
This says, “if I can’t move, then set the ‘Speed’ to zero, otherwise use the horizontal input value”. Does that fix it?
Incidentally, what methods are you currently using to track down faults? Are you comfortable with debug logging and using a debugger? They will prove themselves to be invaluable friends.
I’ve never seen that structure before. I wasn’t sure if that was how you were supposed to type it out or not. We’re half-way there at least. My character’s idle animation plays when talking to the NPC, but can’t be moved. The issue now is giving movement back to the player once the talking is done. I have this…
…and within my NPC script, I have charactersTalking = true;. But once the text box/dialogue has been disabled after pressing Return, charactersTalking = false; isn’t giving me back player movement. :-\
As for debugging, I’m not 100% confident, no. I occasionally use debug logging to see if parts of the code are being read, but I’ve not had much, if any, experience with a debugger.
There are two possibilities here - (1) this is setting a variable value, or, (2) this is setting a property. I can’t tell which it is without seeing the code. So let’s look at them in turn :
As this is only setting the value to false, it is therefore completely independent of the checking code you just posted. That means that something else will have to call the if (NPC.charactersTalking) code.
As this is a method call, then that could call the checking code if (NPC.charactersTalking).
Either way, something has to link the setting of the charactersTalking = false to the checking code that sets canMove.
There are many tricks and tips that can be applied when debugging. Debug logging (for example, Debug.Log("canMove is " + (canMove ? "true" : "false"));) can prove helpful.
However, a good debugger is just unspeakably useful. If you are on Windows, I would highly recommend that you install Visual Studio (community edition is free). Spend a little time familiarising yourself with it. The dividends from that time spent will be manifold.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class NPC : MonoBehaviour
{
public GameObject[] buttonPrompts;
public GameObject[] dialogueBoxes;
public bool characterVicinity = false;
public static bool charactersTalking = false;
//public TypeWriterText typeWriter;
private int xbox360Controller = 0;
private int ps4Controller = 0;
void Start()
{
//PlayerController thePlayer = GetComponent<PlayerController>();
//typeWriter = FindObjectOfType<TypeWriterText>();
}
public void OnTriggerEnter(Collider other)
{
if (other.gameObject.CompareTag ("Player"))
{
characterVicinity = true;
ControllerDetection();
if (ps4Controller == 1)
{
PS4Prompts();
}
else if (xbox360Controller == 1)
{
Xbox360Prompts();
}
else
{
PCPrompts();
}
}
}
public void OnTriggerExit(Collider other)
{
characterVicinity = false;
}
public void Update()
{
if (characterVicinity)
{
if (xbox360Controller == 1)
{
if (Input.GetKeyDown("joystick button 2"))
{
charactersTalking = true;
}
}
else if (ps4Controller == 1)
{
if (Input.GetKeyDown("joystick button 0"))
{
charactersTalking = true;
}
}
*** else
{
if (Input.GetKeyDown(KeyCode.Return))
{
charactersTalking = true;
DialogueBoxes();
if (buttonPrompts[3].activeSelf)
{
if (Input.GetKeyDown("joystick button 2") || Input.GetKeyDown("joystick button 0") || Input.GetKeyDown(KeyCode.Return))
{
dialogueBoxes[0].SetActive(false);
//charactersTalking = false;
//DialogueBoxes();
//buttonPrompts[3].SetActive(false);
//dialogueBoxes[0].SetActive(false);
//dialogueBoxes[1].SetActive(true);
}
}
}
}
}
}
public void DialogueBoxes()
{
if (!dialogueBoxes[0].activeSelf)
{
dialogueBoxes[0].SetActive(true);
PCPrompts();
}
/*else if (!dialogueBoxes[1].activeSelf)
{
buttonPrompts[3].SetActive(false);
dialogueBoxes[1].SetActive(true);
PCPrompts();
}*/
}
public void Timer()
{
if (!buttonPrompts[3].activeSelf)
{
buttonPrompts[3].SetActive(true);
}
else
{
buttonPrompts[3].SetActive(false);
}
}
public void Hide()
{
buttonPrompts[0].SetActive(false);
buttonPrompts[1].SetActive(false);
buttonPrompts[2].SetActive(false);
}
public void Xbox360Prompts()
{
buttonPrompts[1].SetActive(true);
Invoke("Hide", 3f);
}
public void PS4Prompts()
{
buttonPrompts[2].SetActive(true);
Invoke("Hide", 3f);
}
public void PCPrompts()
{
buttonPrompts[0].SetActive(true);
Invoke("Hide", 3f);
if (charactersTalking)
{
//Turn off the button prompt completely whilst the player is talking
buttonPrompts[0].SetActive(false);
if (dialogueBoxes[0].activeSelf)
{
//Timer until a button prompt appears to skip to the next dialogue box
Invoke("Timer", 3f);
}
}
} ***
public void ControllerDetection()
{
string[] names = Input.GetJoystickNames();
for (int x = 0; x < names.Length; x++)
{
//print(names[x].Length);
if (names[x].Length == 19)
{
//print("PS4 CONTROLLER IS CONNECTED");
ps4Controller = 1;
xbox360Controller = 0;
if (ps4Controller == 1)
{
//Debug.Log("PS4 controller detected");
}
}
else if (names[x].Length == 33)
{
//print("XBOX 360 CONTROLLER IS CONNECTED");
ps4Controller = 0;
xbox360Controller = 1;
if (xbox360Controller == 1)
{
//Debug.Log("Xbox 360 controller detected");
}
}
else
{
ps4Controller = 0;
xbox360Controller = 0;
}
if (xbox360Controller == 0 && ps4Controller == 0)
{
//Debug.Log("No controllers detected");
}
}
}
}
I did try making the parts I’m working on in bold, but it’s not showing up. :-\ So it’s from and to the three stars. I’m trying to set up dialogue boxes when the NPC talks to the player. The first pops up, comes up with a button prompt to continue and then the box disappears. I’m struggling to activate the next box, get a button prompt, press it, and then the conversation ends. But for testing purposes I’m trying to end the conversation after pressing the appropriate button after the first box.
I have played around a bit with the Debugger, but not enough to be proficient with it and understand how it works completely. I will try and get into a habit of using it from now on.
And I don’t really ‘get’ the debugger. I set a breakpoint at the part I don’t think is working - charactersTalking = false; but that’s where it stops at. I already know it doesn’t work correctly, but it doesn’t exactly give me any information if I continue after that. I don’t know what it’s doing from there.
Yes, exactly that. So A ? B : C is the same as “if A is true, then use B else use C”.
If this is in Visual Studio :
F11 will step either down into functions (but not into properties and operators by default, although this can be changed in the options) or to the next line.
F10 will step to the next line (it will not step down into a function call like F11 does).
F5 will continue running at full speed until the next breakpoint.
So, for the most part, you may want to be stepping line by line using F10 and F11 accordingly. With each step you have the opportunity to ask yourself “did that go to the line I was expecting?”. Also you can inspect the contents of variables- do they contain the values expected at that point?
There is one thing that stands out in that section: GetKeyDown only returns true in the same frame as the action happens. So to have nested checks means that both keys have to be pressed in exactly the same frame. I suspect that may not be what you intended?
I sort of get what you mean and sort of don’t. I know I’ve had problems before with key presses and actions happening at the same time in the Update function. I’m not sure what you mean by both keys have to be pressed in exactly the same frame. So far I have one button prompt to talk to the character, which then turns off/false when I press Enter, the first text box comes up, a button prompt appears after 3 seconds, I press enter and it all disappears. I’ve still yet to get the second box to appear and then its button prompt. Unless you were referring to GetKeyDown preventing that from happening properly…?
In post #13, line 71 says if (Input.GetKeyDown(KeyCode.Return)) and, within that scope, line 83 then checks (in the same frame) if (Input.GetKeyDown ......
This means that both the return key have to be pressed down and one of the joystick buttons have to be pressed down. Both in the same frame. For the avoidance of doubt, pressed down means going from not-pressed-down to being-pressed-down. This is not the same as being held down. Which is why I say they are unlikely to occur at the same instant in time.
… oh, right … just scrolled to the end of line 83 and realised it also has || Input.GetKeyDown(KeyCode.Return) which, given line 71, means that condition will always be evaluated to true.