I am working on a simple patrol script, and everything works but the characters are randomly rolling through the floor when they turn around. Here is a short clip of them...
Here is my script...
public class Patrol : MonoBehaviour
{
public float speed = 5;
public float directionChangeInterval = 1;
public float maxHeadingChange = 30;
public bool useRootMotion;
CharacterController controller;
float heading;
Vector3 targetRotation;
Vector3 forward
{
get { return transform.TransformDirection(Vector3.forward); }
}
void Awake()
{
controller = GetComponent<CharacterController>();
// Set random initial rotation
heading = Random.Range(0, 360);
transform.eulerAngles = new Vector3(0, heading, 0);
StartCoroutine(NewHeadingRoutine());
}
void Update()
{
transform.eulerAngles = Vector3.Slerp(transform.eulerAngles, targetRotation, Time.deltaTime * directionChangeInterval);
if (useRootMotion)
{
return;
}
else
{
controller.SimpleMove(forward * speed);
}
}
void OnControllerColliderHit(ControllerColliderHit hit)
{
if (hit.gameObject.tag == "Player")
{
// Bounce off the obstacle and change direction
var newDirection = Vector3.Reflect(forward, hit.normal);
transform.rotation = Quaternion.FromToRotation(Vector3.forward, newDirection);
heading = transform.eulerAngles.y;
NewHeading();
}
if (hit.gameObject.tag == "Boundary")
{
// Bounce off the obstacle and change direction
var newDirection = Vector3.Reflect(forward, hit.normal);
transform.rotation = Quaternion.FromToRotation(Vector3.forward, newDirection);
heading = transform.eulerAngles.y;
NewHeading();
}
}
/// Finds a new direction to move towards.
void NewHeading()
{
var floor = transform.eulerAngles.y - maxHeadingChange;
var ceil = transform.eulerAngles.y + maxHeadingChange;
heading = Random.Range(floor, ceil);
targetRotation = new Vector3(0, heading, 0);
}
/// Repeatedly calculates a new direction to move towards.
IEnumerator NewHeadingRoutine()
{
while (true)
{
NewHeading();
yield return new WaitForSeconds(directionChangeInterval);
}
}
}
I have tried adding a rigidbody to the characters and constraining rotation, but that doesnt work. Oddly enough, the character control isnt rotating at all. In the scene view I can see the character collider staying as it should, but the character flips through the mesh on its own.
It looks like it's because they are walking into a corner and being bounced between the two walls constantly which causes them to behave strangely. I would add a method of checking for a series of very quick collisions to detect that they are in a corner or stuck and then adapt accordingly, perhaps with a method to rotate 180 degrees and keep walking or the like.
You can do it like this:
float fTime = 0.1f
float fTimer = 0;
int iCollisionCounter;
if(collision){
if(fTimer > 0) iCollisionCounter++;
if(iCollisionCounter >= 5) //Character is stuck
fTimer = fTime;
}
Void Update(){
fTimer -= time.deltaTime;
}
That means that if there are multiple collisions within 0.1 seconds of each other you can handle it.
Hope this helps!
Related
When my AI tracks my balls y component and moves to the same y component, it starts to vibrate as it gets closer, even if the y component is exactly the same, flicking between going up 7 and down -7. The code for it looks like this
#region Inspector Variables
[SerializeField] private float moveSpeed;
[SerializeField] private Vector2 ballPos;
[SerializeField] private GameObject ballRef;
#endregion
#region Private Variable
private float yInput;
#endregion
#region Components
Rigidbody2D rb;
#endregion
private void Start()
{
rb = GetComponent<Rigidbody2D>();
} //Onstartup executes
private void FixedUpdate()
{
UpdateBallPos();
MoveAI();
}
#region Methods
private void MoveAI()
{
if (ballPos.y > transform.position.y)
{
rb.velocity = new Vector2(0, 1 * moveSpeed);
}
else if (ballPos.y == transform.position.y)
{
rb.velocity = new Vector2(0, 0);
Debug.Log("resting pos");
}
else
{
rb.velocity = new Vector2(0, -1 * moveSpeed);
}
}
private void UpdateBallPos()
{
ballPos = new Vector2(ballRef.transform.position.x, ballRef.transform.position.y);
}
#endregion
ballRef is linked to the ball that my game tracks during FixedUpdate
When the y coordinates for both start to become similar then my AI starts to bug out and start vibrating. It still tracks accurately but I'm not sure if there is a way to resolve my vibrating.
This issue occurs as you are calculating based on precise movements/positions.
But positions are updated after every frame, which results in positioning of objects to be somewhat less-accurate when doing checks to see if they are on a point.
A very simple solution is just to add offsets to allow margin of error like so:
const float errorMargin = 7f;
private void MoveAI()
{
float yDiff = ballPos.y - transform.position.y;
if (yDiff >= errorMargin)
{
rb.velocity = new Vector2(0, 1 * moveSpeed);
}
else if (yDiff <= errorMargin)
{
rb.velocity = new Vector2(0, -1 * moveSpeed);
}
else
{
rb.velocity = new Vector2(0, 0);
Debug.Log("resting pos");
// Additionally, you can do this too:
transform.position = new Vector2(transform.position.x, ballPos.y);
// Ensures that it stays perfectly aligned during rest.
}
}
I'd probably do smoothing and clamping something like this...
private void MoveAI()
{
var distance = ballPos.y - transform.position.y;
var absDistance = Mathf.abs(distance);
if(absDistance < 1) {
// Too close to need to move, stop immediately
rb.velocity = 0;
return;
}
// Figure out a move speed that's likely not going to overshoot (this might need
// a multiplier added)
var maxMoveSpeed = Mathf.min(absDistance, moveSpeed);
// Figure out new velocity based on sign of distance (i.e. which direction to move)
// and the max move speed
var newVelocity = new Vector2(0, maxMoveSpeed * (distance < 0 ? -1 : 1));
// Linearly interpolate between the current velocity and the computed desired velocity.
// The 0.4 smoothing factor can be changed between 0..1.
rb.velocity = Vector2.Lerp(rb.velocity, newVelocity, 0.4);
}
I`m trying to make a wallrun in unity like the wallrun of the Prince of Persia.
The idea is that when you are touching the wall and you press "left shift" the player would run some seconds without falling.
I would like to add that if you press "enter" the character would jump to the side using the Wall as support for impulse.
You can watch this in the first minutes of the video:
https://www.youtube.com/watch?v=zsnB7HEiLr0
I have made this script for the character controller:
public Transform playerPosition;
//controls the x movement. (right/left)
public float horizontalmove;
//controls the y movement. (forward/back)
public float verticalmove;
//controls the movement direction.
private Vector3 playerInput;
//Here I store my character controller.
public CharacterController player;
//controls the player speed.
public float playerSpeed;
//controls de movement direction according to camera
public Vector3 movePlayer;
//controls the last movement
public Vector3 lastMovePlayer;
public float gravity = 9.8f;
public float fallVelocity;
public float jumpForce = 5.0f;
public float verticalSpeed;
private RaycastHit HitR;
private RaycastHit HitL;
//Here I store the main camera
public Camera mainCamera;
//It stores the camera direction when the player is looking forward.
private Vector3 camForward;
//It stores the camera direction when the player is looking right.
private Vector3 camRight;
//Checks
//The meaning of Caida is fall.
public bool Caida;
//The meaning of salto is jump.
public bool Salto;
public bool Wallrun;
public bool WallrunCount;
// Start is called befoe the first frame update
void Start()
{
//i store the character controler.
player = GetComponent<CharacterController>();
Caida = true;
}
// Update is called once per frame
void Update()
{
if (Wallrun == true)
{
Caida = false;
}
if (Salto == true)
{
fallVelocity -= gravity * Time.deltaTime;
movePlayer = lastMovePlayer;
}
if (Caida == true)
{
fallVelocity -= gravity * Time.deltaTime;
}
if (player.isGrounded && Wallrun == false)
{
Caida = false;
Salto = false;
WallrunCount = false;
//I assign the horizontal move to the w and s keys.
horizontalmove = Input.GetAxis("Horizontal");
//I assign the vertical move to the a and d keys.)
verticalmove = Input.GetAxis("Vertical");
//controls the movement direction
playerInput = new Vector3(horizontalmove, 0, verticalmove);
//limits the player speed. With this method if teh player waalks in diagonal doesn´t
//exceed the speed limit.
playerInput = Vector3.ClampMagnitude(playerInput, 1);
// It calls the function that give the camera look direction.
camDirection();
//Here, It`s calculates the player movement considering the camera point and the movement
//we have assing to teh player earlier
//With this method the player always moves looking to the camera
movePlayer = playerInput.x * camRight + playerInput.z * camForward;
//The movement * the speed we want.
movePlayer = movePlayer * playerSpeed;
//we are going to say to the player where is looking at.
player.transform.LookAt(player.transform.position + movePlayer);
//It gives the gravity to the player.
fallVelocity = -gravity * Time.deltaTime;
if (Input.GetKeyDown(KeyCode.Space))
{
Salto = true;
fallVelocity = jumpForce;
}
}
else if (!player.isGrounded && Salto == false && Wallrun == false)
{
Caida = true;
}
movePlayer.y = fallVelocity;
//we give the movement to th eplayer.
player.Move(movePlayer * Time.deltaTime);
lastMovePlayer = movePlayer;
}
private void OnTriggerStay(Collider other)
{
if (Input.GetKeyDown(KeyCode.LeftShift) && Wallrun == false && WallrunCount == false)
{
if (Input.GetKey("w"))
{
Wallrun = true;
WallrunCount = true;
fallVelocity = 5f;
movePlayer.y = fallVelocity;
movePlayer.z = movePlayer.z * 1.6f;
if (Physics.Raycast(transform.position, transform.right, out HitR, 1))
{
movePlayer.x = 1.6f;
}
else if (Physics.Raycast(transform.position, -transform.right, out HitL, 1))
{
movePlayer.x = -1.6f;
}
StartCoroutine(wallrunTime());
}
}
}
void camDirection()
{
//we store the forward and right directions here.
camForward = mainCamera.transform.forward;
camRight = mainCamera.transform.right;
//we block the direction and the camera direction because we are not going to use it.
camForward.y = 0;
camRight.y = 0;
//It gives as the normalized vectors.
camForward = camForward.normalized;
camRight = camRight.normalized;
}
private void OnControllerColliderHit(ControllerColliderHit hit)
{
if (!player.isGrounded && hit.normal.y < 0.1f)
{
if (Input.GetKeyDown(KeyCode.Space))
{
fallVelocity = jumpForce;
movePlayer = hit.normal * 7;
player.transform.LookAt(player.transform.position + movePlayer);
}
}
}
IEnumerator wallrunTime()
{
yield return new WaitForSeconds(1);
Wallrun = false;
}
As you can see, when the player enters the leftshift it checks to what direction is moving the character, if this is front (w) the script make the z movement * 1.6 (to make the character run a bit in the wall) and the character go a bit up bit the y axis.Then, the script checks if the Wall it i son the right or on the left and, depending on where the wall is, sticks the character to that wall.
private void OnTriggerStay(Collider other)
{
if (Input.GetKeyDown(KeyCode.LeftShift) && Wallrun == false && WallrunCount == false)
{
if (Input.GetKey("w"))
{
Wallrun = true;
WallrunCount = true;
fallVelocity = 5f;
movePlayer.y = fallVelocity;
movePlayer.z = movePlayer.z * 1.6f;
if (Physics.Raycast(transform.position, transform.right, out HitR, 1))
{
movePlayer.x = 1.6f;
}
else if (Physics.Raycast(transform.position, -transform.right, out HitL, 1))
{
movePlayer.x = -1.6f;
}
StartCoroutine(wallrunTime());
}
}
}
With this method, I can make the character jump when the player enters space because the character is hitting the wall (a condition required to bounce in the wall).
And after some seconds, the character falls simulating a wallrun.
IEnumerator wallrunTime()
{
yield return new WaitForSeconds(1);
Wallrun = false;
}
The problem is that this works perfectly if the character makes the wallrun when is looking in the same direction as the axes of the environment.
When the character z axis is looking at the same direction of the environment z axes it works perfectly. But when the axes are not looking at the same direction is a disaster. I show you a video I have recorded.
https://youtu.be/KH7rE9kh5d0
The problem, I suppose, is that with the code I have written, I'm moving the character according to the axes of the environment, and not its axes, so I have to tell him to move according to his own.
My teacher says I might have to change the way I make the character move. I would like to know if you can tell me a way to fix this without changing the movement method or if it is not possible, how can I change that movement.
Thank you in advance.
I'm trying to make a patrolling AI character that will move from point to point.
The patrol part works perfectly. However, the problem is that the sprite only faces right. When it turned the sprite stays facing the same direction.
I have tried to change the transform rotation using transform.rotate, transform.rotation, transform.Quaternion and making a variable to store the rotation value yet they all kick errors back. The errors are usually made from the rotate/rotation functions not being compatible with any of the attempts I have tried.
Here is my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// To do:
/// - make rotation of enemy sprite work when reaching the end of patrol area
/// - create attack function
/// </summary>
public class Enemy : MonoBehaviour
{
public int health;
public float speed;
public GameObject bloodEffect;
public Transform[] moveSpots; //patrol spots
public float startWaitTime; //start countdown till move to next spot
private Rigidbody2D rb;
private Animator anim;
private int randomSpot; //number of patrol spots
private float waitTime; //how long enemy stays at patrol spot for
// Start is called before the first frame update
void Start()
{
waitTime = startWaitTime; //make waittime equal to startwaittime
anim = GetComponent<Animator>();
randomSpot = Random.Range(0, moveSpots.Length); //choose a random first spot
}
// Update is called once per frame
void Update()
{
Vector3 spriteRotation = new Vector3(0, randomSpot, 0);
transform.position = Vector2.MoveTowards(transform.position, moveSpots[randomSpot].position, speed * Time.deltaTime); //move toward first patrol area
transform.eulerAngles = spriteRotation;
if (Vector2.Distance(transform.position, moveSpots[randomSpot].position) < 0.5f) //asks if patrol point is further that .5f away from enemy
{
if (waitTime <= 0) //if waitTime less than or equal to 0
{
randomSpot = Random.Range(0, moveSpots.Length); //picks new patrol point
waitTime = startWaitTime; //restarts countdown clock
}
else
{
waitTime -= Time.deltaTime; //counts down clock till next point
}
}
if (health <= 0)
{
Destroy(gameObject);
}
}
public void TakeDamage(int damage)
{
Instantiate(bloodEffect, transform.position, Quaternion.identity);
Debug.Log("Blood effect played");
health -= damage;
Debug.Log("Damage Taken");
}
}
The expected results for this code is that a random point will be chosen and the AI will move toward that chosen point. Once there it will stay idle for a specified amount of time before turning and moving to a new spot.
The actual result is mostly the same as expected only the sprite does not turn around but instead continues to face to the right even when the AI is moving left.
Image of area
the enemy is the dark red cube, the movepoints are the points that the enemy patrols between. when it reaches the left point, he should turn to the right and go back but this is not what happens, instead he just moves back and forth with no rotation. ive tried the SpriteRenderer.flipX route and it only works one time and then sticks with that direction.
The SpriteRenderer Component has a Flip attribute you could use for this.
You can access it in code
SpriteRenderer.flipX = true;
It will only flip the sprite and won't change anything else, so double check if your colliders are still in the right space :) See more in the documentation
Good luck
randomSpot is an index not an angle. So using
transform.eulerAngles = new Vector3(0, randomSpot, 0);
doens't make any sense to me ...
Instead of rotating you could also flip the sprite/Image by using a negative scale like e.g.
// Update is called once per frame
private void Update()
{
// however you determin if facing left or right
// you e.g. simply check whether the target position
// is left or right of you
var difference = moveSpots[randomSpot].position - transform.position;
var isFacingRight = difference.x > 0;
if (isFacingRight && transform.localScale.x < 0
|| !isFacingRight && transform.localScale.x > 0)
{
FlipSprite();
}
}
private void FlipSprite()
{
// invert the local X-axis scale
transform.localScale = new Vector3(-spriteTransform.localScale.x, spriteTransform.localScale.y, spriteTransform.localScale.z);
}
Script used for the example
private void Update()
{
// works only in a ScreenOverlay Canvas
var targetPosition = Input.mousePosition;
var difference = targetPosition - transform.position;
var isFacingRight = difference.x > 0 ? true : false;
if (isFacingRight && transform.localScale.x < 0
|| !isFacingRight && transform.localScale.x > 0)
{
FlipSprite();
}
// simply only move left or right on the x-axis towards the mouse
transform.position = Vector3.MoveTowards(transform.position, new Vector3(targetPosition.x, 218, 0), Time.deltaTime * 100);
}
private void FlipSprite()
{
// invert the local X-axis scale
transform.localScale = new Vector3(-transform.localScale.x, transform.localScale.y, transform.localScale.z);
}
You can try:
if (moveSpot[randomSpot].transform.position.x > transform.position.x) {
transform.localScale = new Vector3(-1, 1, 1);
}
else {
transform.localScale = new Vector3(1, 1, 1);
}
I'm trying out some procedural mesh extrusion but I've got two problems at the moment. The code that performs the extrusion is a part of a Unity example package, and is available here (MeshExtrusion.cs)
First problem is that the update to the mesh only shows when an additional point is added to the mesh, the GIF below should highlight this.
Second problem is that the mesh is twisting. I'm guessing this is due to the wrong vertices being extruded, but I'm not 100% sure. Here is my code that is implementing the mesh extrusion:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ExtrudeTest : MonoBehaviour {
private Vector3 currentMousePosition;
private float currentMouseDistance;
MeshExtrusion.Edge[] preComputedEdges;
private Mesh srcMesh;
private List<Matrix4x4> extrusionPoints = new List<Matrix4x4>();
private Matrix4x4 currentTransformMatrix;
GameObject lineObject;
LineRenderer drawLine;
private bool invertFaces = false;
// Use this for initialization
void Start () {
srcMesh = GetComponent<MeshFilter>().sharedMesh;
preComputedEdges = MeshExtrusion.BuildManifoldEdges(srcMesh);
extrusionPoints.Add(transform.worldToLocalMatrix * Matrix4x4.TRS(transform.position, Quaternion.identity, Vector3.one));
lineObject = new GameObject("lineRenderer");
drawLine = lineObject.AddComponent<LineRenderer>();
}
//Store the current world mouse position.
public void storeMouseLocation()
{
Ray ray = Camera.main.ScreenPointToRay(UnityEngine.Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, Mathf.Infinity))
{
currentMousePosition = hit.point;
currentMouseDistance = hit.distance;
currentTransformMatrix = hit.transform.localToWorldMatrix;
}
}
private void adjustMesh()
{
Matrix4x4 worldToLocal = transform.worldToLocalMatrix;
Matrix4x4[] finalSections = new Matrix4x4[extrusionPoints.Count];
Quaternion rotation;
Quaternion previousRotation = Quaternion.identity;
Vector3 direction;
for (int i=0; i < extrusionPoints.Count; i++)
{
if (i == 0)
{
direction = extrusionPoints[0].GetColumn(3) - extrusionPoints[1].GetColumn(3);
rotation = Quaternion.LookRotation(direction, Vector3.up);
previousRotation = rotation;
finalSections[i] = worldToLocal * Matrix4x4.TRS(transform.position, rotation, Vector3.one);
}
else if (i != extrusionPoints.Count - 1)
{
direction = extrusionPoints[i].GetColumn(3) - extrusionPoints[i + 1].GetColumn(3);
rotation = Quaternion.LookRotation(direction, Vector3.up);
// When the angle of the rotation compared to the last segment is too high
// smooth the rotation a little bit. Optimally we would smooth the entire sections array.
if (Quaternion.Angle(previousRotation, rotation) > 20)
{
rotation = Quaternion.Slerp(previousRotation, rotation, 0.5f);
}
previousRotation = rotation;
finalSections[i] = worldToLocal * Matrix4x4.TRS(extrusionPoints[i].GetColumn(3), rotation, Vector3.one);
}
else
{
finalSections[i] = finalSections[i - 1];
}
}
extrudeMesh(finalSections);
}
private void extrudeMesh(Matrix4x4[] sections)
{
Debug.Log("Extruding mesh");
MeshExtrusion.ExtrudeMesh(srcMesh, GetComponent<MeshFilter>().mesh, sections, preComputedEdges, invertFaces);
}
// Update is called once per frame
void Update () {
storeMouseLocation();
drawLine.SetPosition(0, extrusionPoints[extrusionPoints.Count-1].GetColumn(3));
drawLine.SetPosition(1, currentMousePosition);
if (Input.GetMouseButtonDown(0))
{
extrusionPoints.Add(transform.worldToLocalMatrix * Matrix4x4.TRS(transform.position + new Vector3(currentMousePosition.x, currentMousePosition.y, currentMousePosition.z),
Quaternion.identity,
Vector3.one));
}
if (extrusionPoints.Count >=2)
{
adjustMesh();
}
}
}
Here is a GIF that shows how it's currently performing:
https://puu.sh/xeS7e/bfc3d71fba.gif
I'm not entirely sure what is causing these problems, so if anyone could offer any advice or input, I'd greatly appreciate it. Thanks
Im trying to make simple 3rd person character controller using unitys character controller component instead of rigidbody. I have problem when making my character sliding down the slope, the motion is jerky, just as if the character was going down stairs.
I move my character using normal to the ground by reversing its y axis, then i apply some additional gravity and put this vector to charactercontroller.move() function.
Here is some of the code where i apply slide and gravity:
void ProcessMotion(){
MoveVector = transform.TransformDirection (MoveVector);
if (MoveVector.magnitude > 1)
MoveVector = Vector3.Normalize (MoveVector);
ApplySlide ();
MoveVector *= MoveSpeed;
MoveVector = new Vector3 (MoveVector.x, VerticalVel, MoveVector.z);
ApplyGravity ();
TP_Controller.CharacterController.Move (MoveVector*Time.deltaTime);
}
public void Jump(){
if (TP_Controller.CharacterController.isGrounded) {
VerticalVel=jumpSpeed;
}
}
void SnapAlignCharacterWithCamera(){
if (MoveVector.x != 0 || MoveVector.z != 0) {
transform.rotation = Quaternion.Euler(transform.eulerAngles.x, Camera.main.transform.eulerAngles.y, transform.eulerAngles.z);
}
}
void ApplyGravity(){
if (MoveVector.y > -TermVel) {
MoveVector = new Vector3 (MoveVector.x, MoveVector.y - Gravity * Time.deltaTime, MoveVector.z);
}
if (TP_Controller.CharacterController.isGrounded && MoveVector.y < - 1) {
MoveVector = new Vector3 (MoveVector.x, -1, MoveVector.z);
}
}
void ApplySlide(){
if (!TP_Controller.CharacterController.isGrounded) {
return;
}
SlideDirection = Vector3.zero;
RaycastHit hitInfo;
if (Physics.Raycast (transform.position , Vector3.down, out hitInfo)) {
if(hitInfo.normal.y < SlideTreshold){
SlideDirection = new Vector3(hitInfo.normal.x, -hitInfo.normal.y, hitInfo.normal.z)*10;
}
}
if (SlideDirection.magnitude < MaxMagnitude) {
MoveVector += SlideDirection;
//Debug.DrawLine (transform.position,transform.position + new Vector3(hitInfo.normal.x*0.5f,-hitInfo.normal.y,hitInfo.normal.z*0.5f), Color.red,1.0f);
}else {
MoveVector = SlideDirection;
}
}
And here are screens with gizmos that show path of the object:
Sliding slowly
Sliding 10xfaster
In advance thanks for your help!
I'm not sure the scale of your objects, but it looks like your raycast is coming from inside of your capsule, and possibly colliding with the bottom. The resulting RaycastHit would report that information, which is a normal that points downward, so when you reverse it, you are heading upwards, creating the sawtooth effect.