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()
Related
I am trying to make enemy patrolling system, where evrytime guard reaches his point, he stopes for 10 seconds, and then continue his movement. I've tried combining animations from Blend tree with isStopped property from NavMeshAgent.
EDIT: My current script makes agent move to point, then he stopes for some time, and then only walk animation plays, but he staing on one place.
public Transform[] points;
private int destPoint = 0;
public NavMeshAgent agent;
public Animator animator;
public int time;
void Start()
{
agent = GetComponent<NavMeshAgent>();
animator = transform.Find("Enemy").GetComponent<Animator>();
// Disabling auto-braking allows for continuous movement
// between points (ie, the agent doesn't slow down as it
// approaches a destination point).
//agent.autoBraking = false;
}
void GotoNextPoint()
{
// Returns if no points have been set up
if (points.Length == 0)
return;
// Set the agent to go to the currently selected destination.
agent.destination = points[destPoint].position;
// Choose the next point in the array as the destination,
// cycling to the start if necessary.
destPoint = (destPoint + 1) % points.Length;
//agent.speed = 1f;
//animator.SetFloat("Blend", agent.speed);
}
void Update()
{
if (agent.remainingDistance == 0f && time == 100000)
{
agent.speed = 1f;
Debug.Log(agent.remainingDistance);
animator.SetFloat("Blend", 1);
GotoNextPoint();
}
else if (agent.remainingDistance <= 0.5f && agent.remainingDistance != 0f && time == 100000)
{
animator.SetFloat("Blend",0);
agent.enabled = false;
GotoNextPoint();
}
else if(animator.GetFloat("Blend") == 0)
{
time--;
}
if (time == 99000 && animator.GetFloat("Blend") == 0)
{
time = 10000;
agent.enabled = true;
agent.isStopped = false;
animator.SetFloat("Blend", 1);
agent.autoRepath = true;
GotoNextPoint();
}
}
I changed few lines of code, now agent moves after first stop, but second time he stops at second poitm,walking animation still working, time doesn't decrementing
if (time == 99000 && animator.GetFloat("Blend") == 0)
{
time = 10000;
agent.enabled = true;
agent.isStopped = false;
animator.SetFloat("Blend", 1);
//New lines of code
agent.ResetPath();
destPoint = (destPoint + 1) % points.Length;
agent.SetDestination(points[destPoint].position);
}[enter image description here][1]
First of all, I would use the "SetDestination" function in order to set the next destination.
In the end you wrote:
if (time == 99000 && animator.GetFloat("Blend") == 0)
{
time = 10000; *-> needs to be 100000, not 10000*
agent.enabled = true;
agent.isStopped = false;
animator.SetFloat("Blend", 1);
agent.autoRepath = true;
GotoNextPoint();
}
You can use "NavMeshAgent.ResetPath" to reset the path instead of using "autoRepath".
After the "ResetPath", use the "SetDestination" inside the function GotoNextPoint().
One more very important thing - Check that there is more than one point.
If there is just one point, your guard will just walk at the same spot.
For more information, is suggest to check out Unity NavMeshAgent
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))];
}
}
This question already has answers here:
Detect swipe gesture direction
(5 answers)
Closed 5 years ago.
I'm implementing specific type of touch controller.
The player needs to hold their finger on screen in order to move.
Without lifting the finger, the player can swipe in different direction to change direction while still moving.
Once the finger is lifted, the player stops moving.
It's been a hard time isolating specific swipes (i.e. lines drawn on screen) ignoring any other movements that are not intended to draw a line.
For instance slight finger movements done when the player's finger is "stationary", they mess up my algorithm.
I thought about different approaches such as storing the last few touches and evaluate upon them to determine if there has been a swipe or not, but couldn't implement it properly.
Here's what I've tried so far. Most of the time it works fine, but often the player does erratic movement and goes absolutely the opposite direction to what I intended.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TouchInputHandler : AbstractInputHandler {
private const int SWIPE_MIN_DISTANCE = 35;
private Vector2 touchStartPoint;
private Vector2 touchMovePoint;
void Update () {
Touch[] touches = Input.touches;
if (touches.Length == 1) {
Touch firstTouch = touches [0];
if (firstTouch.phase == TouchPhase.Began) {
this.touchStartPoint = firstTouch.position;
fireNextDirectionChanged (currentDirection);
} else if (firstTouch.phase == TouchPhase.Moved) {
this.touchMovePoint = firstTouch.position;
if (Vector2.Distance(touchStartPoint, touchMovePoint) > SWIPE_MIN_DISTANCE) {
detectSwipeDirection ();
}
} else if (firstTouch.phase == TouchPhase.Stationary) {
touchStartPoint.x = touchMovePoint.x;
touchStartPoint.y = touchMovePoint.y;
} else if (firstTouch.phase == TouchPhase.Ended) {
fireNextDirectionChanged (Constants.Direction.NONE);
}
}
}
private void detectSwipeDirection() {
float xDiff = touchMovePoint.x - touchStartPoint.x;
float yDiff = touchMovePoint.y - touchStartPoint.y;
Constants.Direction nextDirection;
bool yGreater = Mathf.Abs(yDiff) >= Mathf.Abs(xDiff);
if (yGreater) {
// direction is up or down
nextDirection = yDiff < 0 ? Constants.Direction.DOWN : Constants.Direction.UP;
} else {
// direction is left or right
nextDirection = xDiff < 0 ? Constants.Direction.LEFT : Constants.Direction.RIGHT;
}
if (nextDirection != this.currentDirection)
{
fireNextDirectionChanged (nextDirection);
this.currentDirection = nextDirection;
}
}
}
I think you simply forgot a line setting the previous touch position to the current one in the TouchPhase.Moved block. Just add touchStartPoint = this.touchMovePoint; and it should work (only tested using mouse inputs but logic remains the same).
Also I commented the TouchPhase.Stationary block: I feel like it does what I suggested before but only when the finger is completely immobile. The final code looks like this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TouchInputHandler : AbstractInputHandler
{
private const int SWIPE_MIN_DISTANCE = 35;
private Vector2 touchStartPoint;
private Vector2 touchMovePoint;
void Update()
{
Touch[] touches = Input.touches;
if(touches.Length == 1)
{
Touch firstTouch = touches[0];
if(firstTouch.phase == TouchPhase.Began)
{
this.touchStartPoint = firstTouch.position;
this.currentDirection = Constants.Direction.NONE;
fireNextDirectionChanged(currentDirection);
}
else if(firstTouch.phase == TouchPhase.Moved)
{
this.touchMovePoint = firstTouch.position;
if(Vector2.Distance(touchStartPoint, touchMovePoint) > SWIPE_MIN_DISTANCE)
{
detectSwipeDirection();
}
touchStartPoint = this.touchMovePoint; // <= NEW !
}
//else if(firstTouch.phase == TouchPhase.Stationary)
//{
// touchStartPoint.x = touchMovePoint.x;
// touchStartPoint.y = touchMovePoint.y;
//}
else if(firstTouch.phase == TouchPhase.Ended)
{
this.currentDirection = Constants.Direction.NONE;
fireNextDirectionChanged(Constants.Direction.NONE);
}
}
}
private void detectSwipeDirection()
{
float xDiff = touchMovePoint.x - touchStartPoint.x;
float yDiff = touchMovePoint.y - touchStartPoint.y;
Constants.Direction nextDirection;
bool yGreater = Mathf.Abs(yDiff) >= Mathf.Abs(xDiff);
if(yGreater)
{
// direction is up or down
nextDirection = yDiff < 0 ? Constants.Direction.DOWN : Constants.Direction.UP;
}
else
{
// direction is left or right
nextDirection = xDiff < 0 ? Constants.Direction.LEFT : Constants.Direction.RIGHT;
}
if(nextDirection != this.currentDirection)
{
fireNextDirectionChanged(nextDirection);
this.currentDirection = nextDirection;
}
}
}
Also when using a distance based on in game distance to detect swipe, I'd suggest adding a Screen.width/height ratio somewhere if you want to port your project to multiple resolutions devices (on Start() do something like SWIPE_MIN_DISTANCE *= Screen.width / BASE_SCREEN_WIDTH with private const int BASE_SCREEN_WIDTH = 1024;).
Hope this helps,
Basically I have been given a project to develop a game in unity using c# and the device Leap Motion. The problem I am having is that I have a basic power bar that goes up and down through a range of 0 - 100, when the user releases his fist I want to shoot using the selected power. the problem I am having is I cannot figure out a way to set the bool "shoot" to true on release of the fists.
Controller m_leapController;
public GameObject CannonBarrel;
[Range(0, 100)] public float ballPower = 0f;
public bool increasing = true;
public bool shoot = false;
if (frame.Hands.Count >= 2)
{
Hand leftHand = GetLeftMostHand(frame);
Hand rightHand = GetRightMostHand(frame);
Vector3 handDiff = leftHand.PalmPosition.ToUnityScaled() - rightHand.PalmPosition.ToUnityScaled();
Vector3 newRot = CannonBarrel.transform.localRotation.eulerAngles;
float leftRightSpeed = handDiff.y * 5.0f;
float handPitch = leftHand.Direction.Pitch + rightHand.Direction.Pitch * 0.5f;
newRot.x = 0;
newRot.y = 90;
newRot.z = 70 + -handPitch * 20.0f;
shoot = true;
// if closed fist...
if (frame.Fingers.Count < 3)
{
leftRightSpeed = 0;
shoot = false;
if (increasing == true)
{
ballPower++;
if (ballPower >= 100)
{
increasing = false;
}
}
else if (increasing == false)
{
ballPower--;
if (ballPower <= 0)
{
increasing = true;
}
}
}
else
{
//Move left or right depending on hands height difference.
transform.parent.rigidbody.velocity = transform.parent.right * leftRightSpeed;
//Rotate the barrel
CannonBarrel.transform.localRotation = Quaternion.Slerp(CannonBarrel.transform.localRotation, Quaternion.Euler(newRot), 0.1f);
}
if (shoot == true)
{
Debug.Log("fired");
//add code here to spawn projectile
}
}
I hope that this code makes sense to you. All that is happening is if you're holding your fists above the Leap sensor the ball power will increase till you hit 100, then it will decrease back down to 0 and so on until you release your fists. I need a way to set the "shoot" bool to true once a power has been selected as the way I currently do it I can only set it to true either during the power selection or it gets set to true before you clench your fists.
Thanks in advance.
A somewhat simple way you could do this is by setting a bool to check if the player has clenched his fist at least once, thus setting the power. Let's call it hasClenchedFist:
if (frame.Hands.Count >= 2)
{
Hand leftHand = GetLeftMostHand(frame);
Hand rightHand = GetRightMostHand(frame);
Vector3 handDiff = leftHand.PalmPosition.ToUnityScaled() - rightHand.PalmPosition.ToUnityScaled();
Vector3 newRot = CannonBarrel.transform.localRotation.eulerAngles;
float leftRightSpeed = handDiff.y * 5.0f;
float handPitch = leftHand.Direction.Pitch + rightHand.Direction.Pitch * 0.5f;
newRot.x = 0;
newRot.y = 90;
newRot.z = 70 + -handPitch * 20.0f;
//shoot = true;
// if closed fist...
if (frame.Fingers.Count < 3)
{
leftRightSpeed = 0;
hasClenchedFist = true;
shoot = false;
if (increasing == true)
{
ballPower++;
if (ballPower >= 100)
{
increasing = false;
}
}
else if (increasing == false)
{
ballPower--;
if (ballPower <= 0)
{
increasing = true;
}
}
}
else if(hasClenchedFist)
{
shoot = true;
}
else
{
//Move left or right depending on hands height difference.
transform.parent.rigidbody.velocity = transform.parent.right * leftRightSpeed;
//Rotate the barrel
CannonBarrel.transform.localRotation = Quaternion.Slerp(CannonBarrel.transform.localRotation, Quaternion.Euler(newRot), 0.1f);
}
if (shoot == true)
{
Debug.Log("fired");
//add code here to spawn projectile
}
}
This should work, unless I've misunderstood the code/requirements.
When my AABB physics engine resolves an intersection, it does so by finding the axis where the penetration is smaller, then "push out" the entity on that axis.
Considering the "jumping moving left" example:
If velocityX is bigger than velocityY, AABB pushes the entity out on the Y axis, effectively stopping the jump (result: the player stops in mid-air).
If velocityX is smaller than velocitY (not shown in diagram), the program works as intended, because AABB pushes the entity out on the X axis.
How can I solve this problem?
Source code:
public void Update()
{
Position += Velocity;
Velocity += World.Gravity;
List<SSSPBody> toCheck = World.SpatialHash.GetNearbyItems(this);
for (int i = 0; i < toCheck.Count; i++)
{
SSSPBody body = toCheck[i];
body.Test.Color = Color.White;
if (body != this && body.Static)
{
float left = (body.CornerMin.X - CornerMax.X);
float right = (body.CornerMax.X - CornerMin.X);
float top = (body.CornerMin.Y - CornerMax.Y);
float bottom = (body.CornerMax.Y - CornerMin.Y);
if (SSSPUtils.AABBIsOverlapping(this, body))
{
body.Test.Color = Color.Yellow;
Vector2 overlapVector = SSSPUtils.AABBGetOverlapVector(left, right, top, bottom);
Position += overlapVector;
}
if (SSSPUtils.AABBIsCollidingTop(this, body))
{
if ((Position.X >= body.CornerMin.X && Position.X <= body.CornerMax.X) &&
(Position.Y + Height/2f == body.Position.Y - body.Height/2f))
{
body.Test.Color = Color.Red;
Velocity = new Vector2(Velocity.X, 0);
}
}
}
}
}
public static bool AABBIsOverlapping(SSSPBody mBody1, SSSPBody mBody2)
{
if(mBody1.CornerMax.X <= mBody2.CornerMin.X || mBody1.CornerMin.X >= mBody2.CornerMax.X)
return false;
if (mBody1.CornerMax.Y <= mBody2.CornerMin.Y || mBody1.CornerMin.Y >= mBody2.CornerMax.Y)
return false;
return true;
}
public static bool AABBIsColliding(SSSPBody mBody1, SSSPBody mBody2)
{
if (mBody1.CornerMax.X < mBody2.CornerMin.X || mBody1.CornerMin.X > mBody2.CornerMax.X)
return false;
if (mBody1.CornerMax.Y < mBody2.CornerMin.Y || mBody1.CornerMin.Y > mBody2.CornerMax.Y)
return false;
return true;
}
public static bool AABBIsCollidingTop(SSSPBody mBody1, SSSPBody mBody2)
{
if (mBody1.CornerMax.X < mBody2.CornerMin.X || mBody1.CornerMin.X > mBody2.CornerMax.X)
return false;
if (mBody1.CornerMax.Y < mBody2.CornerMin.Y || mBody1.CornerMin.Y > mBody2.CornerMax.Y)
return false;
if(mBody1.CornerMax.Y == mBody2.CornerMin.Y)
return true;
return false;
}
public static Vector2 AABBGetOverlapVector(float mLeft, float mRight, float mTop, float mBottom)
{
Vector2 result = new Vector2(0, 0);
if ((mLeft > 0 || mRight < 0) || (mTop > 0 || mBottom < 0))
return result;
if (Math.Abs(mLeft) < mRight)
result.X = mLeft;
else
result.X = mRight;
if (Math.Abs(mTop) < mBottom)
result.Y = mTop;
else
result.Y = mBottom;
if (Math.Abs(result.X) < Math.Abs(result.Y))
result.Y = 0;
else
result.X = 0;
return result;
}
It's hard to read code of other people, but I think this is one possible (purely brainstormed) workaround, although of course I'm not able to test it:
Before the collision detection happens, save the players velocity to some temporary variable.
After you've done your collision response, check if the players X or Y position has been corrected
If X position has been changed, manually reset (as a kind of "safety reset") the players Y velocity to the one that he had before the response.
By the way, what happens with your current code when your players hits the roof while jumping?
I had that same problem. Even the Microsoft platformer starterskit seems to have that bug.
The solution I found so far is to either use multisampling (argh) or to use the movedirection of the object: only move the distance between those 2 objects and no further when a collision is detected (and do that for each axis).
One possible solution I found is sorting the objects before resolving based on the velocity of the player.