I am trying to get a few gameobject to randomly walk around a maze. I currently have it working properly, where the objects can go left/right/forward. The issue I am having is when the have the opportunity to go forward and left/right the always choose to turn left or right, they never choose to go forward, and I can not figure out why... What am I missing here?
public float speed;
public bool changed = false;
public bool forward, left, right;
protected Transform frontRaycast, leftRaycast, rightRaycast;
void Update(){
if(!changed){
forward = Physics2D.Linecast(transform.position, frontRaycast.position, 1 << LayerMask.NameToLayer("Wall"));
left = Physics2D.Linecast(transform.position, leftRaycast.position, 1 << LayerMask.NameToLayer("Wall"));
right = Physics2D.Linecast(transform.position, rightRaycast.position, 1 << LayerMask.NameToLayer("Wall"));
List<int> possibleDirs = new List<int>();
if(!forward){
possibleDirs.Add(0);
}
if(!left){
possibleDirs.Add(1);
}
if(!right){
possibleDirs.Add(2);
}
int dir = 0;
//Debug.Log(Random.Range(0, possibleDirs.Count));
if(possibleDirs.Count > 0){
dir = (int)possibleDirs[Random.Range(0, possibleDirs.Count)];
}
Debug.Log(dir);
switch(dir){
case 0:
transform.Rotate(new Vector3(0,0,0));
break;
case 1:
transform.Rotate(new Vector3(0,0,90));
changed = true;
Invoke("reset", 0.5f);
break;
case 2:
transform.Rotate(new Vector3(0,0,-90));
changed = true;
Invoke("reset", 0.5f);
break;
}
}
transform.Translate(Vector2.right * Time.deltaTime * speed);
}
void reset(){
changed = false;
}
Related
I made a simple script to check what position the player is facing and put that in my animator
1 = up
2 = right
3 = down
4 = left
private Vector2 velocity;
private Animator animator;
private int direction;
private void Awake() {
animator = GetComponent<Animator>();
}
void Update(){
velocity.x = Input.GetAxisRaw("Horizontal");
velocity.y = Input.GetAxisRaw("Vertical");
switch(velocity){
case Vector2(0,1):
direction = 1;
break;
case Vector2(1,0):
direction = 2;
break;
case Vector2(0,-1):
direction = 3;
break;
case Vector2(-1,0):
direction = 4;
break;
}
animator.SetFloat("Facing",direction);
then I get the error
Assets/Scripts/PlayerMovement.cs(21,25): error CS8129: No suitable 'Deconstruct' instance or extension method was found for type 'Vector2', with 2 out parameters and a void return type.
I believe your issue comes with how you are trying to use your switch statement as Vector(1,0) is attempting to call a function not actually reference the value you are looking a viable alternative is to use when like in the following example
case Vector2 v when v.Equals(Vector2.up):
Debug.Log("Up");
break;
case Vector2 v when v.Equals(Vector2.left):
Debug.Log("Left");
break;
case Vector2 v when v.Equals(Vector2.back):
Debug.Log("Back");
break;
case Vector2 v when v.Equals(Vector2.right):
Debug.Log("Right");
break;
I'm also using the shorthand up, left, right and down instead of defining the values manually
Assuming velocity is of type Vector2 you can't set the values of x and y as they are read-only properties.
Use this instead:
velocity = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));
Vectors are structs and you can't create them without using new e.g. case new Vector2(0f, 1f):. There are some ready made shortcuts for direction built in that you can use:
if(velocity == Vector2.up)
{
print("direction = 1");
} else if (velocity == Vector2.right)
{
print("direction = 2");
} else if (velocity == Vector2.up)
{
print("direction = 3");
} else if (velocity == Vector2.down)
{
print("direction = 4");
} else if (velocity == Vector2.left)
{
print("direction = 1");
}
I have a scene that contains a square sprite in the middle at 0,0,0 position as player and two more square sprites on top and bottom of it with some space between all.
I want the player start to move when I press the up arrow ker or the down arrow key. When the player hits one of those squares on top or the bottom the player must return back to it's first position.
{
Rigidbody2D playerRb;
float axisY = 0f;
bool canMove = true;
public float playerSpeed = 0f;
public float returnSpeed = 0f;
public float speed = 1f;
void Start()
{
playerRb = GetComponent<Rigidbody2D>();
}
//decides to which axis player gonna move to
private void Update()
{
if (Input.anyKey)
{
axisY = Input.GetAxisRaw("Vertical");
}
}
private void FixedUpdate()
{
PlayerMovement(axisY, playerSpeed);
}
//give velocity to player depending on axis
void PlayerMovement(float axis, float speed)
{
switch (axisY)
{
case 1:
if (canMove)
{
canMove = false;
playerRb.velocity = new Vector2(0, axis * speed);
}
break;
case -1:
if (canMove)
{
canMove = false;
playerRb.velocity = new Vector2(0, axis * speed);
}
break;
default:
//do nothing
break;
}
}
//return back after hit
private void OnCollisionEnter2D(Collision2D collision)
{
axisY = 0;
playerRb.velocity = Vector2.zero;
//the code below is my way to get back before i decide to lerp
//playerRb.MovePosition(Vector2.MoveTowards(playerRb.position,Vector2.zero,returnSpeed));
StopAllCoroutines();
StartCoroutine(BackToZero());
}
IEnumerator BackToZero()
{
float t = 0;
while (t < 1)
{
t += Time.deltaTime;
playerRb.MovePosition(Vector2.Lerp(playerRb.position, Vector2.zero, returnSpeed));
yield return null;
}
canMove = true;
}
}
I managed to achieve this with my code but the problem is when the lerp gets closer to 0 it is taking more time to reach there. I have to wait for one second to reach to the position and my canMove bool value gets back to true.
How can I skip this and when my object position is close to 0 point snap it to there?
The problem come from the fact you use playerRb.position as the start position of your Lerp in your coroutine.
To fix this, I recommend you to buffer the playerRb.position in a member variable at the OnCollisionEnter() method right before starting your coroutine and use this buffered position in your Lerp as start position.
This way you should have a nice linear movement at a sweet linear speed without the "damping"
Hope that helped ;)
Here is the solution. When player object's position is too close to the starting position you will detect it then snap it to the position immediately by
playerRb.MovePosition().
Then enable player to move right after by setting canMove bool to true.
private void OnCollisionEnter2D(Collision2D collision)
{
axisY = 0;
playerRb.velocity = Vector2.zero;
Vector2 playerCurrentPosition = playerRb.position;
//playerRb.MovePosition(Vector2.MoveTowards(playerRb.position,Vector2.zero,returnSpeed));
StopAllCoroutines();
StartCoroutine(BackToZero(playerCurrentPosition));
}
IEnumerator BackToZero(Vector2 playerStartPos)
{
float t = 0;
while (t < 1)
{
t += Time.deltaTime;
if (Mathf.Round(Mathf.Abs(playerRb.position.y*2))==0)
{
playerRb.MovePosition(Vector2.zero);
canMove = true;
StopAllCoroutines();
}
else
playerRb.MovePosition(Vector2.Lerp(playerRb.position, Vector2.zero, returnSpeed));
yield return null;
}
canMove = true;
}
}
Try Vector3.MoveTowards(); in a while loop until you have reached your target destination
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 3 years ago.
Improve this question
I've been trying to make a script for NPCs in a grid based game (the squares are 1 unit wide). The NPCs should be moving from 1 square (es. (1, 0)) to an adiacent one ((0, 0), (1, -1) (1, 1), (2, 0)), then he should stay idle for a certain amount of time before choosing another direction. Obviously, it already has a script that detects colliding objects in the adiacent squares.
The problem is that sometimes it moves to a position that isn't an integer (es. (0, 0,768)), but it corrects itself after a few steps. Could someone correct the code where it's needed?
P.S., the token is a GameObject that reserves the square for the NPC that created it, so to block any other entity from moving to those coordinates
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NPCMoveToPointScript : MonoBehaviour
{
public float speed = 2.0f; //movement speed
public Vector3 pos;
List<int> directions; //available directions
//where he's facing
public bool up;
public bool down;
public bool left;
public bool right;
public int dir; //value to use for animator
public float idleTime= 0.5f;
public bool idle; //false if a movement button is pressed
public bool canMove = true;
public GameObject token; //to avoid collisions between entities while moving towards a tile
CollisionScript cs;
void Start()
{
pos = transform.position;
down = true;
cs = GetComponent<CollisionScript>();
}
void Update() //move stickman in the direction of the keys and maintain the direction he's facing with animations
{
dir = ChooseDirection();
if (transform.position == pos) //go idle
{
StartCoroutine(StayIdle(idleTime));
}
//go in the chosen direction
if (!idle && dir == 0 && transform.position == pos)
{
AllFalse();
down = true;
pos += Vector3.down;
}
else if (!idle && (dir ==1) && (transform.position == pos))
{
AllFalse();
left = true;
pos += Vector3.left;
}
else if (!idle && (dir == 2) && (transform.position == pos))
{
AllFalse();
up = true;
pos += Vector3.up;
}
else if (!idle && dir == 3 && transform.position == pos)
{
AllFalse();
right = true;
pos += Vector3.right;
}
//if you just started moving towards a different tile, spawn a Token in that tile to avoid other npcs moving towards it
if (((transform.position.x - (int)transform.position.x == 0) && (transform.position.y - (int)transform.position.y == 0)) && pos != transform.position)
{
Instantiate(token, pos, Quaternion.identity);
}
transform.position = Vector3.MoveTowards(transform.position, pos, Time.deltaTime * speed);
}
//avoid bug by deactivating all direction booleans before changing one
void AllFalse()
{
up = false;
down = false;
left = false;
right = false;
idle = false;
}
//checks how many directions are available and then chooses one of them randomly
int ChooseDirection()
{
directions = new List<int>();
//check the directions
if (!cs.hitDown)
{
directions.Add(0);
}
if (!cs.hitLeft)
{
directions.Add(1);
}
if (!cs.hitUp)
{
directions.Add(2);
}
if (!cs.hitRight)
{
directions.Add(3);
}
//turn directions into an array and pick a random direction
var array = directions.ToArray();
directions.Clear();
return array[Random.Range(0, (array.Length))];
}
//literaly what the name says (for some seconds) lol
public IEnumerator StayIdle(float idleTime)
{
yield return new WaitForSeconds(idleTime);
idle = true;
yield return new WaitForSeconds(4*idleTime);
idle = false;
}
}
I've found a solution that works perfectly (thanks to various suggestions in the comments). If anyone else needs a working casual grid movement script, here it is
public class NPCMovementNew : MonoBehaviour {
public float speed = 2.0f; //movement speed
public float idleTime = 1.0f; //how much time does he spend idling
public int dir; //where he's facing
public bool idle; //false if a movement button is pressed
public bool canMove = true; //to disable casual movement
public GameObject token; //to avoid collisions between entities while moving towards a tile
Vector3 pos;
List<int> directions; //available directions
CollisionScript cs;
// Use this for initialization
void Start () {
cs = GetComponent<CollisionScript>();
pos = transform.position;
dir = 0;
}
// Update is called once per frame
void Update () {
if(canMove && !idle) //check if he can move
{
if (transform.position != pos) //if he's not at the center of a square, he's not idling
{
idle = false;
transform.position = Vector3.MoveTowards(transform.position, pos, Time.deltaTime * speed); //move to that position
}
else //here he decides to move
{
StartCoroutine(IdleThenMove(idleTime));
}
}
}
//function that goes idle for a certain time, then moves to an adiacent square
IEnumerator IdleThenMove(float t)
{
idle = true;
yield return new WaitForSeconds(t);
//here it randomly chooses a direction and translates it to a position vector
dir = ChooseDirection();
switch (dir)
{
case 0:
pos += Vector3.down;
break;
case 1:
pos += Vector3.left;
break;
case 2:
pos += Vector3.up;
break;
case 3:
pos += Vector3.right;
break;
default:
pos = transform.position;
break;
}
idle = false;
Instantiate(token, pos, Quaternion.identity); //spawn token to occupy square
transform.position= Vector3.MoveTowards(transform.position, pos, Time.deltaTime * speed); //move to that position
}
//checks how many directions are available and then chooses one of them randomly
int ChooseDirection()
{
directions = new List<int>();
//check the directions
if (!cs.hitDown)
{
directions.Add(0);
}
if (!cs.hitLeft)
{
directions.Add(1);
}
if (!cs.hitUp)
{
directions.Add(2);
}
if (!cs.hitRight)
{
directions.Add(3);
}
//turn directions into an array and pick a random direction
var array = directions.ToArray();
directions.Clear();
return array[Random.Range(0, (array.Length))];
}
}
At the top of the script:
public Transform traveller;
public GameObject[] waypoints;
public Transform nextWaypoint;
public bool reverse = false;
private int targetsIndex = 0;
In Start:
void Start()
{
waypoints = GameObject.FindGameObjectsWithTag("Waypoint").OrderBy(go => go.name).ToArray();
targetsIndex = 0;
}
In Update:
void Update()
{
WayPointsAI();
}
Then WayPointsAI:
private void WayPointsAI()
{
if (targetsIndex == waypoints.Length)
targetsIndex = 0;
nextWaypoint = waypoints[targetsIndex].transform;
float distance = Vector3.Distance(traveller.transform.position, nextWaypoint.transform.position);
traveller.transform.rotation = Quaternion.Slerp(traveller.transform.rotation, Quaternion.LookRotation(nextWaypoint.position - traveller.transform.position), rotationSpeed * Time.deltaTime);
if (reverse == true)
{
var targetAngles = traveller.transform.eulerAngles + 180f * Vector3.up;
traveller.transform.eulerAngles = Vector3.Lerp(traveller.transform.eulerAngles, targetAngles, rotationSpeed * Time.deltaTime);
var lastWaypoint = waypoints[currentIndex];
}
if (distance < distancetoRotate)
{
traveller.transform.position += traveller.transform.forward * slowDownSpeed * Time.deltaTime;
}
else
{
traveller.transform.position += traveller.transform.forward * moveSpeed * Time.deltaTime;
}
if (distance < nextWaypoint.transform.localScale.magnitude)
{
rotateNumber = true;
currentIndex = targetsIndex;
if (random == false)
{
targetsIndex++;
}
else
{
targetsIndex = UnityEngine.Random.Range(0, waypoints.Length);
}
}
}
And the relevant part is the reverse:
if (reverse == true)
{
var targetAngles = traveller.transform.eulerAngles + 180f * Vector3.up;
traveller.transform.eulerAngles = Vector3.Lerp(traveller.transform.eulerAngles, targetAngles, rotationSpeed * Time.deltaTime);
var lastWaypoint = waypoints[currentIndex];
}
When i check the checkbox on reverse it will get to this part.
Now for example let's say the last waypoint the object visited was waypoint 1 (currentIndex = 1)
Now the object make u turn of 180 degrees facing the other way now i want him to keep moving to the other way but between the waypoints just the opposite direction.
The problem is how to change the waypoints array order. If for example last waypoint the object visited was 1 now he make a u turn should go back to 1 then to 0 then to 4 3 2 1 0 and so on.
So in any place i check the checkbox on reverse it should change the order of the waypoints array. This is what i can't figure how to do.
It should be in the reverse part after the line:
var lastWaypoint = waypoints[currentIndex];
I think this might be better approached by splitting up the task more logically.
Create a field that's the target waypoint
I'd recommend making a function that's "headtowaypoint" that's in charge of ONLY heading towards the target waypoint.
Then, have another function that deals with what happens AT a waypoint, this will call a function setnextwaypoint,
and then have another function that is "reverse" which sets a bool that heads towards the last waypoint, and tells setnextwaypoint that you're going to the previous one, and tells headtowaypoint that you're now going down rather than up the list.
I am trying to get characters to move around by randomly going left/right/forward at turns, and for the most part the characters do that, but every once in a while they walk through the walls, and I don't want that to happen. Is there anything you can see in my code that I am missing?
The walls have colliders on them, and so do the circles, but the circles colliders are just a trigger because I want the circles to be able to go through each other, just not through the walls.
It seems like the character turns, and some time turns again towards a wall.
Here is a video example: https://www.youtube.com/watch?v=ZOfGn3bsuLA
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
[RequireComponent(typeof(Rigidbody2D))]
public class Character : MonoBehaviour {
public float speed;
protected Transform frontRaycast, leftRaycast, rightRaycast;
protected List<int> lastPossibleDirs = new List<int>();
protected int lastDir;
protected bool changed = false;
protected bool forward, left, right;
// Use this for initialization
void Awake () {
frontRaycast = transform.FindChild("FrontRaycast").transform;
leftRaycast = transform.FindChild("LeftRaycast").transform;
rightRaycast = transform.FindChild("RightRaycast").transform;
}
void Update(){
// Test for a wall in front and on sides
forward = Physics2D.Linecast(transform.position, frontRaycast.position, 1 << LayerMask.NameToLayer("Wall"));
left = Physics2D.Linecast(transform.position, leftRaycast.position, 1 << LayerMask.NameToLayer("Wall"));
right = Physics2D.Linecast(transform.position, rightRaycast.position, 1 << LayerMask.NameToLayer("Wall"));
// Add each direction to the list of possible turns
List<int> possibleDirs = new List<int>();
if(!forward){
possibleDirs.Add(0);
}
if(!left){
possibleDirs.Add(1);
}
if(!right){
possibleDirs.Add(2);
}
int dir = 0;
if(possibleDirs.Count > 0){
dir = (int)possibleDirs[Random.Range(0, possibleDirs.Count)];
}
if(changed){
// Move forward with left or right option
if(lastPossibleDirs.Exists(element => element == 0) && lastPossibleDirs.Exists(element => element > 0) && (left || right) && lastDir == 0){
changed = false;
}
// Left
else if(lastPossibleDirs.Exists(element => element == 1) && left && lastDir == 1){
changed = false;
}
// Right
else if(lastPossibleDirs.Exists(element => element == 2) && right && lastDir == 2){
changed = false;
}
}else{
switch(dir){
case 0:
if(!left || !right){
transform.Rotate(new Vector3(0,0,0));
changed = true;
}
break;
case 1:
transform.Rotate(new Vector3(0,0,90));
changed = true;
break;
case 2:
transform.Rotate(new Vector3(0,0,-90));
changed = true;
break;
}
lastDir = dir;
lastPossibleDirs = possibleDirs;
}
transform.Translate(Vector2.right * Time.deltaTime * speed);
}
}
Here is a screenshot of where the detection areas are located on the character
Fist thing I would do is uncheck trigger on your circle and make use of
Physics2d.IgnoreLayerCollision(circle layer)
in the Awake()