Here I share my AiPathfinding script. I want my AI walks to a destination (target) when game starts. however, when AI collide an certain object (finding with tag="bullet") then I want to set new destination. Although code does not give error, when playing AI goes to first destination then stops there even though it collides with certain object on the way.
Can someone have a look please?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Pathfinding;
public class AIPathfinding : MonoBehaviour
{
[SerializeField] public Transform target;
[SerializeField] public Transform target2;
[SerializeField] public float speed = 200f;
[SerializeField] public float nextWayPointDistance = 3f;
[SerializeField] Path path;
[SerializeField] int currentWaypoint = 0;
[SerializeField] bool reachedEndOfPath = false;
[SerializeField] Seeker seeker;
[SerializeField] Rigidbody2D rb;
// Start is called before the first frame update
void Start()
{
seeker = GetComponent<Seeker>();
rb = GetComponent<Rigidbody2D>();
InvokeRepeating("UpdatePath", 0f, 0.5f);
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag == "bullet")
{
UpdatePath(target2);
}
}
public void UpdatePath(Transform target2)
{
if (seeker.IsDone())
{
seeker.StartPath(rb.position, target.position, OnPathComplete);
}
}
void OnPathComplete(Path p)
{
if (!p.error)
{
path = p;
currentWaypoint = 0;
}
}
void Update()
{
if (path == null)
{
return;
}
if (currentWaypoint >= path.vectorPath.Count)
{
reachedEndOfPath = true;
return;
}
else
{
reachedEndOfPath = false;
}
Vector2 direction = ((Vector2)path.vectorPath[currentWaypoint] - rb.position).normalized;
Vector2 force = direction * speed * Time.deltaTime;
rb.AddForce(force);
float distance = Vector2.Distance(rb.position, path.vectorPath[currentWaypoint]);
if (distance < nextWayPointDistance)
{
currentWaypoint++;
}
}
}
My guess is that you never stopped the Coroutine InvokeRepeating("UpdatePath", 0f, 0.5f). So do call CancelInvoke() when the bullet hits and change the target of the seeker (Start InvokeRepeating(...) again).
I assume NavMeshAgent.SetDestination(Vector3 position) is called in the function seeker.SetTarget(). And somehow Path gets calculated and set.
You could also just move the body of Update Path into the Update function or call it from there. Nevermind the Interval. Or do call it in your custom interval. Just calculate the next time you want to call it so if the nextUpdatePathTime (Time.time plus your cooldown) is smaller than Time.time then call it and calculate the next nextUpdatePathTime.
Related
I wanted to make an endless runner game where the player can jump and he moves forward automatically, so, for the forward movement i wanted to use the rigidbody velocity function in order to make the movement smoother and for the jump i made a custom function. My problem is, when i use in my update method the velocity function alone, it works, when i use it with my jump function it stops working. What can i do?. I tried changing the material of the floor and of the player's box collider to a really slippery one but nothing
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MovePrima : MonoBehaviour
{
[SerializeField]
private float _moveSpeed = 0f;
[SerializeField]
private float _gravity = 9.81f;
[SerializeField]
private float _jumpspeed = 3.5f;
public float speedAccel = 0.01f;
private CharacterController _controller;
private float _directionY;
public float startSpeed;
public float Yspeed = 10f;
private int i = 0;
public float touchSensibility = 50f;
public int accelTime = 600;
[SerializeField]
Rigidbody rb;
// Start is called before the first frame update
void Start()
{
startSpeed = _moveSpeed;
_controller = GetComponent<CharacterController>();
rb = GetComponent<Rigidbody>();
}
// Update is called once per frame
void Update()
{
rb.velocity = new Vector3(0f, 0f, 1f) * _moveSpeed;
touchControls();
}
public void touchControls()
{
if (SwipeManager.swipeUp && _controller.isGrounded)
{
_directionY = _jumpspeed;
print("Entrato");
}
_directionY -= _gravity * Time.fixedDeltaTime;
_controller.Move((new Vector3(0, _directionY, 0)) * Time.fixedDeltaTime * Yspeed);
i += 1;
if (i % accelTime == 0)
{
_moveSpeed += startSpeed * speedAccel;
i = 0;
}
}
private void OnControllerColliderHit(ControllerColliderHit hit) //Checks if it collided with obstacles
{
if(hit.transform.tag == "Obstacle")
{
GameOverManager.gameOver = true;
}
}
}
You should either use the CharacterController or the Rigidbody. With the CharacterController you tell Unity where to place the character and you have to check yourself if it is a valid position. With the Rigidbody you push it in the directions you want, but the physics around the character is also interfering.
You can use Rigidbody.AddForce to y when jumping instead of _controller.Move.
OK, I have a particle which is prefabed as it appears on the left of the image below.
The left side is the result of the projectile code:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(Collider2D))]
[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(SpriteRenderer))]
public class Projectile : MonoBehaviour
{
[SerializeField] float velocity;
[SerializeField] float acceleration;
new Collider2D collider;
Rigidbody2D rb;
SpriteRenderer sr;
[SerializeField] LayerMask targetLayer;
[SerializeField] float lifetime;
[SerializeField] float damage;
[SerializeField] ParticleSystem HitEffect;
public event Action<Projectile,Collider2D> OnHit;
public Color Color
{
get
{
return sr.color;
}
set
{
sr.color = value;
}
}
// Start is called before the first frame update
void Start()
{
collider = GetComponent<Collider2D>();
rb = GetComponent<Rigidbody2D>();
sr = GetComponent<SpriteRenderer>();
collider.isTrigger = false;
}
// Update is called once per frame
void Update()
{
lifetime -= Time.deltaTime;
if (lifetime <=0)
{
Destroy(this.gameObject);
}
}
public void SetTargetMask(LayerMask _mask)
{
targetLayer = _mask;
}
public void SetDamage(float amount)
{
this.damage = amount;
}
protected virtual void OnCollisionEnter2D(Collision2D other)
{
var _hitEffect = Instantiate(HitEffect.gameObject, transform).GetComponent<ParticleSystem>();
if (!_hitEffect.isPlaying) _hitEffect.Play();
rb.velocity = Vector2.zero;
collider.isTrigger = true;
sr.enabled = false;
Damageable othersDamage;
other.gameObject.TryGetComponent<Damageable>(out othersDamage);
//Debug.Log("dmg not null: " + (othersDamage != null) + "; presumed layer:" + other.gameObject.layer + "; actual layer:" + targetLayer.value);
if (othersDamage != null && other.gameObject.layer == targetLayer.value)
{
OnHit?.Invoke(this,other.collider);
othersDamage.HandleDamage(damage);
}
Destroy(this.gameObject,HitEffect.main.duration);
}
}
So, the idea is to have the particle as it appears on the left at each contact point, play, and vanish.
Instead, I have a non-animated particle that floats off after hitting the target.
What am I missing to get to play the single explosion at point, and how to get the sub particle to play properly.
What am I missing to get to play the single explosion at point
Either stop the rigidbody completely:
rb.angularVelocity = Vector2.zero;
rb.velocity = Vector2.zero;
Alternately, unparent the hit effect from the projectile, and destroy it after a delay
_hitEffect.transform.SetParent(null);
Destroy(_hitEffect.gameObject,afterDelay);
i'm new to unity and i'm trying to make an endless runner. When my player (a ball) hits one of the walls the game needs to go the scene: LostMenu. My problem is that the collision doesn't work. Nothing happens when they collide... Here are the inspectors on both the player and the walls: Walls http://prntscr.com/a2pgzm Player http://prntscr.com/a2ph66 .
The collision script on the walls:
using UnityEngine;
using System.Collections;
public class LostByWallCSR : MonoBehaviour
{
void OnCollisionEnter (Collision col)
{
if(col.gameObject.name == "Player")
{
Application.LoadLevel("LostMenu");
Debug.Log ("WORKS!");
}
}
}
The movement script on the player:
using UnityEngine;
using System.Collections;
public class CharacterControllerz : MonoBehaviour {
public float speed = 5f;
public float gravity = 20f;
private Vector3 moveDirections = new Vector3();
private Vector3 inputs = new Vector3();
void FixedUpdate()
{
CharacterController cc = GetComponent<CharacterController>();
if (cc.isGrounded)
{
if (Input.GetKey("left"))
inputs.z = 3;
else if (Input.GetKey("right"))
inputs.z = -3;
else
inputs.z = 0;
moveDirections = transform.TransformDirection(inputs.x, 0, inputs.z) * speed;
}
inputs.x = 5;
moveDirections.y = inputs.y - gravity;
cc.Move(moveDirections * Time.deltaTime);
}
}
Any idea on how to setup the collision properly? Attaching a rigibody is not helping it. Unless i'm missing something ofcourse.
Make sure both objects have a collider component.
You need to attach a rigidbody component to one of the objects.(if you don't attach rigid body it will not work)
your Sphere collider is Trigger , remove that or change your function to
public void OnTriggerEnter(Collider other)
{
}
You need to use OnControllerColliderHit method
private void OnControllerColliderHit(ControllerColliderHit hit)
{
if (hit.gameObject.CompareTag("Trap"))
{
Debug.Log("colisiono with trap");
playerDentro = true;
}
else
{
if (!hit.gameObject.CompareTag("Trap"))
{
Debug.Log("player salio de la trampa");
playerDentro = false;
}
}
}e
I am attempting to follow a few unity3d exmaples on c# scripting for tower defense style games. I need a turret to 'aim' at another gameobject. The examples I find do not seem to account for a origin that is not at 0,0,0. Meaning, when the turret is in a different location, it aims based on a starting point, not its current location.
how it is behaving now:
http://screencast.com/t/Vx35LJXRKNUm
In the script I am using, how do I give Quaternion.LookRotation information about the current location of the turret for it to include in it's calculation? script, function CalculateAimPosition, line59 :
using UnityEngine;
using System.Collections;
public class TurretBehavior : MonoBehaviour {
public GameObject projectile;
public GameObject muzzleEffect;
public float reloadTime = 1f;
public float turnSpeed = 5f;
public float firePauseTime = .25f;
public Transform target;
public Transform[] muzzlePositions;
public Transform turretBall;
private float nextFireTime;
private float nextMoveTime;
private Quaternion desiredRotation;
private Vector3 aimPoint;
// Update is called once per frame
void Update ()
{
if (target)
{
if (Time.time >= nextMoveTime)
{
CalculateAimPosition(target.position);
transform.rotation = Quaternion.Lerp(turretBall.rotation, desiredRotation, Time.deltaTime * turnSpeed);
}
if (Time.time >= nextFireTime) {
FireProjectile();
}
}
}
void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "TurretEnemy")
{
nextFireTime = Time.time +(reloadTime *.5f);
target = other.gameObject.transform;
}
}
void OnTriggerExit(Collider other)
{
if (other.gameObject.transform == target) {
target = null;
}
}
void CalculateAimPosition(Vector3 targetPosition)
{
aimPoint = new Vector3 (targetPosition.x, targetPosition.y, targetPosition.z);
desiredRotation = Quaternion.LookRotation (aimPoint);
}
void FireProjectile()
{
nextFireTime = Time.time + reloadTime;
nextMoveTime = Time.time + firePauseTime;
foreach(Transform transform in muzzlePositions)
{
Instantiate(projectile, transform.position, transform.rotation);
}
}
}
The error is in the usage of Quaternion.LookRotation.
The function takes two Vector3 as input which are a forward direction in world space (and an optional up vector - default Vector3.up), and returns a Quaternion representing the orientation of such a reference frame.
You are instead supply a world space position as input (targetPosition), which makes no sense. Accidentally a normalized position vector expressed in world space is the direction from origin to the given point, so it works correctly when the tower is placed on the origin.
What you need to use as LookRotation parameter is the world space direction from the tower to the target:
Vector3 aimDir = (targetPosition - transform.position).normalized;
desiredRotation = Quaternion.LookRotation (aimDir );
i have a turret,as a game object when a enemy enters it's collison box,the turret starts firing towards it,the logic is when the enemy exits the collider,it should stop its firing ,and other problem is that when again an enemy enters the collison box i.e the second enemy,it gives me an exception ,"MissingReferenceException :the object of type 'transform' has been destroyed but you are still trying to access it.Your script should eihter be check if it is null or you should not destroy it",but i am checking if the list in not null in my code.here is my code
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class TurretScript : MonoBehaviour {
public float shotInterval = 0.2f; // interval between shots
public GameObject bulletPrefab; // drag the bullet prefab here
public float bulletSpeed;
private float shootTime = 0.0f;
private List<Transform> targets;
private Transform selectedTarget;
private Transform myTransform;
private Transform bulletSpawn;
void Start(){
targets = new List<Transform>();
selectedTarget = null;
myTransform = transform;
bulletSpawn = transform.Find ("bulletSpawn"); // only works if bulletSpawn is a turret child!
}
void OnTriggerEnter2D(Collider2D other){
if (other.tag == "enemy"){ // only enemies are added to the target list!
targets.Add(other.transform);
}
}
void OnTriggerExit2D(Collider2D other){
if (other.tag == "enemy"){
targets.Remove(other.transform);
Debug.Log("gone out");
}
}
void TargetEnemy(){
if (selectedTarget == null){ // if target destroyed or not selected yet...
SortTargetsByDistance(); // select the closest one
if (targets.Count > 0) selectedTarget = targets[0];
}
}
void SortTargetsByDistance(){
targets.Sort(delegate(Transform t1, Transform t2){
return Vector3.Distance(t1.position, myTransform.position).CompareTo(Vector3.Distance(t2.position, myTransform.position));
});
}
void Update(){
TargetEnemy(); // update the selected target and look at it
if (selectedTarget)
{
// if there's any target in the range...
Vector3 dir = selectedTarget.position - transform.position;
float angle = Mathf.Atan2(dir.y,dir.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.AngleAxis(angle, Vector3.forward);// aim at it
if (Time.time >= shootTime){// if it's time to shoot...
// shoot in the target direction
Vector3 lookPos = new Vector3(bulletSpawn.position.x,bulletSpawn.position.y,0);
lookPos = lookPos - transform.position;
float ang = Mathf.Atan2(lookPos.y,lookPos.x)*Mathf.Rad2Deg;
GameObject b1 = Instantiate(bulletPrefab,new Vector3(transform.position.x,transform.position.y,5),transform.rotation)as GameObject;
b1.rigidbody2D.velocity = new Vector3(Mathf.Cos(ang*Mathf.Deg2Rad),Mathf.Sin(ang*Mathf.Deg2Rad),0)*bulletSpeed;
shootTime = Time.time + shotInterval; // set time for next shot
}
}
}
}
here is my enemy script
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class EnemyScript : MonoBehaviour {
public Transform target;
public float speed = 2f;
public int Health;
public float GetHealth()
{
return Health;
}
void Update ()
{
transform.position = Vector2.MoveTowards(transform.position, target.position, speed * Time.deltaTime);
}
void TakeDamage(int damage){
Health -= damage;
if (Health <= 0)
Destroy(gameObject);
}
void OnTriggerEnter2D(Collider2D otherCollider)
{
PlayerControl shot = otherCollider.gameObject.GetComponent<PlayerControl>();
if (shot != null)
{
SpecialEffectsHelper.Instance.Explosion(transform.position);
Destroy(shot.gameObject);
}
}
}
you need to check if the selected target is the target leaving the collider. You remove the target from the targets list but the selectedTarget var is still populated.
For the null ref exception. Are you using Destroy() to kill the target? Destroy() doesn't cause an OnTriggerExit() or OnCollisionExit() event call, the object is just gone.
edit: you can get around the lack of a call by adding an OnDestroy() function to the dying object that sets it's position to something well outside the level/view of the player. This way the target leaves the collider and then disappears rather than just disappearing in place.