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);
}
Related
I'm making a ladder for my Unity game. Nothing much happens when you hover over it (that's normal). I want to freeze gravity by setting gravityscale 0 whenever the up arrow key is pressed (or w). Thankfully that works. Unfortunately though whenever I go down, gravity is still 0 but somehow my character falls back to the ground.
Here's my player code. Sorry the formatting is very bad; I just deleted the things that didn't have to do with ladders so you won't read irrelevant details.
// ladder
public float climb;
public float climbspeed;
public float gravitystore;
public bool laddered;
public GameObject laddercheck;
public float upaxis;
public xpositionladdercheck ladderx;
// Start is called before the first frame update
void Start()
{
ladderx = FindObjectOfType<xpositionladdercheck>();
jumptimecounter = Jumptime;
jumpscriptbetter = FindObjectOfType<BetterJump>();
gameover = false;
respawnscreen = FindObjectOfType<RespawnIntermission>();
lives = 5;
atkindex = FindObjectOfType<attackindex>();
scale =transform.localScale;
basicattackscript = FindObjectOfType<BasicAttack>();
basicattacksupply = 0;
rb = GetComponent<Rigidbody2D>();
gravitystore = rb.gravityScale;
}
// Update is called once per frame
private void Update()
{
horimove = Input.GetAxis("Horizontal");
if (isDead == false)
{
upaxis = Input.GetAxis("Vertical");
anim.SetFloat("run", Mathf.Abs(horimove));
if (Input.GetKeyDown(KeyCode.Space) && laddered)
{
jumpladder();
}
anim.SetFloat("run", Mathf.Abs(horimove));
if (laddered && Mathf.Abs(upaxis) > .01)
{
rb.gravityScale = 0;
climbm();
} else if (!laddered)
{
rb.gravityScale = gravitystore;
}
if (laddered&&Input.GetKeyDown(KeyCode.Space))
{
StartCoroutine("jumpable");
}
rb.velocity = new Vector2(speed * horimove * Time.fixedDeltaTime, rb.velocity.y);
if (Input.GetKey(KeyCode.Space) && OnGround)
{
jump();
}
}
}
IEnumerator jumpable()
{
laddercheck.SetActive(false);
yield return new WaitForSeconds(.8f);
laddercheck.SetActive(true);
}
void climbm()
{
if (laddered)
{
if (upaxis > .1f) {
} else if (!laddered)
{
rb.constraints = RigidbodyConstraints2D.FreezeRotation;
}
rb.velocity = Vector2.up * climbspeed * upaxis *Time.deltaTime;
transform.position = new Vector2(ladderx.ladderxpos, rb.position.y);
// the ladder.x things is just a script that gets the x position of the ladder but it works well so ignore that.
}
}
public void jump()
{
}public void jumpladder()
{
if (Input.GetKeyDown(KeyCode.Space))
{
rb.AddForce(Vector2.up * jumpforce * Time.fixedDeltaTime);
}
}
}
}
I set mass to the lowest (.0001) and still the same thing happens. I didn't include the ladderx position script or the sensing that I'm even on a ladder script but that's irrelevant to the question. It senses everything just fine and the xposition script works well as well.
I figured it out. For some reason I had to set the Physics2D.gravity to new Vector2 (0,0); whenever I climb the ladder. Then I made a vector 2 physgravity variable that stores the natural gravity and set it back to that whenever I leave the ladder.
public Vector2 physgravity;
physgravity = Physics2D.gravity;
Physics2D.gravity = new Vector2(0f, -9.81f);
private void Update()
{
if (!laddered)
{
Physics2D.gravity = new Vector2(0f, -9.81f);
}
____________________ (later on in the script)
if (laddered && Mathf.Abs(upaxis) > .01)
{
rb.gravityScale = 0;
Physics2D.gravity = new Vector2(0, 0);
rb.velocity = new Vector2(0, 0);
climbm();
I have a fish which is swimming across the screen. When it gets within 10% of the edge of the screen, I want it to turn begin turning around until it has completely reversed and is now swimming in the opposite direction. This should be more gradual like a fish would swim. I don't want it to rotate on its own axis.
I'm not having any luck getting it to turn around. It only turns partially.
Update
Here's the fish
public class FishSwim : MonoBehaviour
{
public enum Direction { LeftToRight, RightToLeft };
public Direction moveDirection;
[SerializeField]
private float speedMin = 0.5f;
[SerializeField]
private float speedMax = 1f;
[SerializeField]
private bool useOnlySpeedMax = false;
private float speed;
[HideInInspector]
public float removeBeyond;
private void Start()
{
var dist = (transform.position - Camera.main.transform.position).z;
if (moveDirection == Direction.RightToLeft)
removeBeyond = Camera.main.ViewportToWorldPoint(new Vector3(1, 0, dist)).x;
else
removeBeyond = Camera.main.ViewportToWorldPoint(new Vector3(1, 0, dist)).x + FindObjectOfType<SkinnedMeshRenderer>().bounds.size.x;
}
private void OnEnable()
{
speed = Random.Range(speedMin, speedMax);
if (useOnlySpeedMax)
{
speed = speedMax;
}
if (moveDirection == Direction.RightToLeft)
{
speed = -speed;
}
}
// Update is called once per frame
void Update()
{
float realSpeed = speed * Time.deltaTime;
transform.position += Vector3.right * realSpeed;
if (moveDirection == Direction.RightToLeft && transform.position.x < -Mathf.Abs(removeBeyond))
{
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(Vector3.right), Time.deltaTime * 40f);
moveDirection = Direction.LeftToRight;
}
else if (moveDirection == Direction.LeftToRight && transform.position.x > Mathf.Abs(removeBeyond))
{
}
}
}
I would use Coroutines to do this. Explanation in comments:
bool isTurning = false;
[SerializeField] float turnPeriod = 0.5f; // how long it takes to turn, smaller=faster
private void OnEnable()
{
speed = Random.Range(speedMin, speedMax);
if (useOnlySpeedMax)
{
speed = speedMax;
}
// Get rid of negative speed
}
void Update()
{
float realDistance = speed * Time.deltaTime;
transform.position += transform.right * realDistance;
// Surely there is a better way than calculating
// Mathf.Abs(removeBeyond) every frame... but that is off-topic
if (!isTurning && (
(moveDirection == Direction.RightToLeft
&& transform.position.x < -Mathf.Abs(removeBeyond)
) || (moveDirection == Direction.LeftToRight
&& transform.position.x > Mathf.Abs(removeBeyond)
) ) )
{
// If we aren't already turning and we should be, start turning:
StartCoroutine(Turn());
}
}
IEnumerator Turn()
{
isTurning = true;
Vector3 startForward = transform.forward;
// find end speed and direction
Direction endDirection = moveDirection == Direction.RightToLeft ?
Direction.LeftToRight:Direction.RightToLeft;
// keep track of how much of our turning time has elapsed
float elapsedTimePortion = Time.deltaTime/turnPeriod;
// turn until you've spent enough time turning
while (elapsedTimePortion < 1f)
{
// by whatever portion we've spent time turning, turn starting forward
// 180 degrees around up axis
float angle = 180f * elapsedTimePortion;
Vector3 newForward = Quaternion.AngleAxis(angle, Vector3.up) * startForward;
transform.rotation = Quaternion.LookRotation(newForward);
yield return null;
// next frame - update how long we've been turning
float newElapsedTimePortion = elapsedTimePortion + Time.deltaTime/turnPeriod;
if (newElapsedTimePortion >= 0.5f && elapsedTimePortion < 0.5f)
{
// If we've just passed 50% of the turn,
// make fish move opposite direction
moveDirection = endDirection;
}
elapsedTimePortion = newElapsedTimePortion;
}
// we're done turning, just set the rotation to the end rotation.
isTurning = false;
transform.rotation = Quaternion.LookRotation(-startForward);
// Does removeBeyond need to be updated here? There are a few lines in Start()
// which would suggest that.
}
I've been trying to figure this out for a few days now, and I'm very sure it has something to do with my code. What I want the enemy monster to do is to stop moving forward when the player right next to it and run the attack animation. What it actually does is run the attack animation while orbiting around the player like a planet, and you can't really outrun it.
I'm using Unity3d as the engine and my code is written in C#. It's very simple so I'm worried I'm missing something important.
public class enemyAITest : MonoBehaviour
{
public Transform player; // target
public float playerDistance; // determines how far from target before action takes place
public float rotationDamping; // determines how quickly rotation occurs
public float chaseSpeed; // determines how quickly to pursue target
public float wanderSpeed; // determines how quickly to move around map
public float maxDist;
static Animator anim;
Vector3 wayPoint;
void Awake()
{
anim = GetComponent<Animator>();
}
// Update is called once per frame
void Update ()
{
transform.position += transform.TransformDirection(Vector3.forward) * wanderSpeed * Time.deltaTime;
playerDistance = Vector3.Distance(player.position, transform.position);
while ((transform.position - wayPoint).magnitude < 3)
Wander();
if (playerDistance < 20f)
{
LookAtPlayer();
wanderSpeed = 0;
anim.SetBool("isIdle", true);
}
if (playerDistance < 10f)
{
Chase();
anim.SetBool("isIdle", false);
anim.SetBool("isWalking", false);
anim.SetBool("isRunning", true);
}
if(playerDistance < 1f)
{
wanderSpeed = 0;
anim.SetBool("isRunning", false);
anim.SetBool("isAttacking", true);
}
else anim.SetBool("isWalking", true);
}
void LookAtPlayer()
{
Quaternion rotation = Quaternion.LookRotation(player.position - transform.position);
transform.rotation = Quaternion.Slerp(transform.rotation, rotation, Time.deltaTime * rotationDamping);
wanderSpeed = 0;
}
void Chase()
{
Vector3 direction = player.position - transform.position;
direction.y = 0;
transform.Translate(Vector3.forward * chaseSpeed * Time.deltaTime);
}
void Wander()
{
Vector3 MonsterPosition = new Vector3(Random.Range(transform.position.x - maxDist, transform.position.x + maxDist), 1, Random.Range(transform.position.z - maxDist, transform.position.z + maxDist));
wayPoint.y = 1;
transform.LookAt(wayPoint);
//Debug.Log(wayPoint + " and " + (transform.position - wayPoint).magnitude);
}
}
Any advice will help (Please forgive the strange formatting, I was having trouble with keeping the entire Wander method and the on Awake line in the code block).
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!
When I use transform.Translate on my cube's x and z axis it moves according to pressing keys. But I want the cube to move back slowly to it's original position when user stops pressing keys and default axis are x=0 ,z=0.
public float move = 1f;
void Update ()
{
this.transform.Translate (Input.GetAxis ("Horizontal") / move, 0f, Input.GetAxis ("Vertical") / move);
}
So your best bet is to store the original position
private Vector3 _intialPosition;
private float _duration = 0.4f;
private float _startTime;
void Awake()
{
_initialPosition = transform.position;
}
void Start() {
_startTime = Time.time;
}
And then check if the key has been pressed, and if not, have it move back towards the initial position
void Update()
{
if(Input.GetAxis("Horizontal") != 0 || Input.GetAxis("Vertical") != 0)
{
//Logic here to move via arrows...
}
else
{
transform.position = Vector3.Lerp(transform.position, _initialPosition, (Time.time - _startTime ) / _duration);
}
}
Unity Documentation
Lerp
You could use Vector3.MoveTowards to slowly move your current transform to a target or original transform position.
private Transform original;
public float speed = 0.5f;
void Awake()
{
original = transform;
}
void Update() {
float step = speed * Time.deltaTime;
transform.position = Vector3.MoveTowards(transform.position, original.position, step);
}
Unity3d documention - Vector3.MoveTowards