I've been trying to set an enemy on a patrol path while not chasing my player character. I want to use RayCast so that the enemy can spot the player and begin to chase the player. It functions as I intended. However, even when there's an obstacle or wall between us, or when I approach from behind, the enemy 'sees' my player and starts to chase me. It seems to ignore the Raycast, and instead focuses on the proximity to the enemy.
Enemy spotting player through wall
public class EnemyController : MonoBehaviour
{
private NavMeshAgent enemy;// assaign navmesh agent
private Transform playerTarget;// reference to player's position
private float attackRadius = 10.0f; // radius where enemy will spot player
public Transform[] destinationPoints;// array of points for enemy to patrol
private int currentDestination;// reference to current position
public bool canSeePlayer = false;
private Ray enemyEyes;
public RaycastHit hitData;
private void Awake()
{
enemy = GetComponent<NavMeshAgent>();
playerTarget = GameObject.Find("Player").GetComponent<Transform>();
enemyEyes = new Ray(transform.position, transform.forward);
}
private void Start()
{
Physics.Raycast(enemyEyes, attackRadius);
}
private void Update()
{
Lurk();
Debug.DrawRay(transform.position, transform.forward * attackRadius);
}
void Lurk()
{
Debug.Log("Lurking");
float distanceToPlayer = Vector3.Distance(transform.position, playerTarget.position);
//check if raycast hits playerLayer and enemy is close enough to attack
if (Physics.Raycast(enemyEyes, out hitData, attackRadius * 2, layerMask: ~6) && distanceToPlayer < attackRadius)
{
Debug.Log("You hit " + hitData.collider.gameObject.name);
ChasePlayer();
}
else
{
canSeePlayer = false;
Patrol();
}
}
void Patrol()
{
if (!canSeePlayer && enemy.remainingDistance < 0.5f)
{
enemy.destination = destinationPoints[currentDestination].position;
UpdateCurrentPoint();
}
}
void UpdateCurrentPoint()
{
if (currentDestination == destinationPoints.Length - 1)
{
currentDestination = 0;
}
else
{
currentDestination++;
}
}
void ChasePlayer()
{
StartCoroutine(ChaseTime());
canSeePlayer = true;
transform.LookAt(playerTarget.position);
Vector3 moveTo = Vector3.MoveTowards(transform.position, playerTarget.position, attackRadius);
enemy.SetDestination(moveTo);
}
IEnumerator ChaseTime()
{
Debug.Log("Chasing");
yield return new WaitForSeconds(10.0f);
if (!Physics.Raycast(enemyEyes, out hitData, attackRadius * 2, layerMask: ~6))
{
canSeePlayer = false;
Debug.Log("Lurking");
Lurk();
}
}
}
I've removed the tilde "~" for the layermask, but then the enemy doesn't ever see the player.
I've initialised and set a layer mask reference to the 'playerLayer' and used it in place of "layermask: ~6", to no avail.
I've used the int reference to the Player layer, to no avail.
I've used bitwise operator to reference the player layer, to no avail.
I've removed the distanceToPlayer conditional, but the enemy doesn't see the player.
I've adjusted the length of the Ray but if it's ever shorter than the attack radius, it doesn't work.
I don't understand why you need a layer mask. If you want the Raycast to hit something in front of the player then you must include all layers in the Raycast.
What you need to do is remove the layermask from Raycast and in the if statement check if the out hit collider is on Player and if the player is in attack radius. Here is a simple outline
If(raycast)
{
if(hit.collider.gameobject==player && player in proximity)
{
Then can see is true
}
}
You can give this article on Unity Raycast a read to understand more on layermask.
regarding player detection through a wall,I would try adding an extra if statement in the Lurk method.This condition would check if the raycast is touching the wall, if so, do not proceed with the method, if not, continue. Sorry for giving the code in this form but I don't have access to a computer and the phone doesn't want to cooperate
enter image description here
Related
I'm creating a basic pool game in Unity with C#, what im trying to do is that if the cue ball is moving, the stick will disappear, and once it becomes stationary again, it will reappear to where the cue ball is located. This is my code so far:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class stickDeplacement : MonoBehaviour
{
public bool bIsOnTheMove = false;
Vector3 lastPos;
public GameObject Stick;
void Start()
{
}
void Update()
{
var stick = Instantiate(Stick, gameObject.transform.position, gameObject.transform.rotation);
if (this.transform.position != lastPos)
{
Destroy(stick);
Debug.Log("Is moving");
}
else
{
Debug.Log("Is not moving");
}
lastPos = this.transform.position;
}
}
But what happens is that the ball, along with the stick, will just spasm and be buggy right from the start (when I open and play the game). Am I missing something here?
This is extremely inefficient and dangerous!
Why instantiate a stick every frame just to eventually already destroy it in that very same frame? And if the ball is stationary you want an additional stick to be spawned every frame?
Instead of all the time instantiating and destroying it at all you should rather keep one stick and only (de)activate it.
In your case you could do this in a single line
bIsOnTheMove = transform.position == lastPos;
stick.SetActive(!bIsOnTheMove);
Also I doubt a lot you would like the stick to have the same rotation as a rolling ball! Of course this will behave awkward
Most certainly you do not simply want to clone the ball's orientation. I would e.g. try to determine the closest point of the table edge to the current ball's position (iterate through the wall colliders and use Collider.ClosestPoint) and let the stick face the direction from that edge point towars the ball position (+ maybe an offset in X so the stick is slightly inclined by default).
And finally you anyway do not want to assign that rotation every frame, since you most probably later want your users to be able to rotate that stick. You only want to apply this once when the ball becomes stationary.
Something like e.g.
// The stick is not a prefab anymore but simply always exists in the scene!
[SerializeField] private Transform stick;
[SerializeField] private Vector3 eulerOffset;
[SerializeField] private Collider[] wallColliders;
public bool bIsOnTheMove;
private Vector3 lastPos;
private void Start()
{
lastPos = transform.position;
}
private void Update()
{
// is the ball currently moving?
var isMoving = transform.position == lastPos;
last Post = transform.position;
// Did this state change since the last frame?
if(bIsOnTheMove == isMoving) return;
bIsOnTheMove = isMoving;
// (de)activate the stick accordingly
stick.gameObject.SetActive(!isMoving);
// Do this ONCE when ball becomes stanionary
if(!isMoving)
{
var ballPosition = transform.position;
// among the wall colliders find which one is closest to the ball
Vector3 closestPoint;
var smallestDistance = float.PositiveInifinity;
foreach(var wall in wallColliders)
{
var edgePoint = wall.ClosestPoint(ballPosition);
var distane = (edgePoint - ballPosition).sqrMagnitude;
if(distance < smallestDistance)
{
closestPoint = point;
smallestDistance = distance;
}
}
// then make the stick look towards the ball from that edge
var direction = ballPosition - closestPoint;
var rotation = Quaternion.LookRotation(direction);
// optional add the offset
rotation *= Quaternion.Euler(eulerOffset);
stick.rotation = rotation;
}
}
I'm trying to change the direction of the enemy when collides with a wall or the character. However, it doesn't work for the wall that the enemy bumps into, but only stays where it is.
Here's what I mean:
In this picture, the goomba switches the direction when bumps into Mario, however the same does not happen when the wall is hit.
What happens instead is this:
The Goomba stands still.
Here's the code of the goomba:
public class GoombaController : MonoBehaviour
{
public float speed = 1f;
private Vector2 force = Vector2.left;
void Update()
{
transform.Translate(force * Time.deltaTime * speed);
}
void OnCollisionEnter2D(Collision2D coll) {
if ((this.transform.position.x - coll.collider.transform.position.x) < 0) {
force = Vector2.left;
} else if ((this.transform.position.x - coll.collider.transform.position.x) > 0) {
force = Vector2.right;
}
}
}
Also, I use a tile map to generate the level, and the ground and the walls are on the same tile map.
If anything else is needed as info, just leave a comment below the question.
How can I solve this?
I use Cinemachine camera collider for my project. This basically means that my entire game is inside a polygon collider.
With the following script my line renderer collides instantly with this camera collider:
[SerializeField] private float defDistanceRay = 10f;
public Transform laserFirePoint;
public LineRenderer m_linerenderer;
Transform m_transform;
private void Awake()
{
m_transform = GetComponent<Transform>();
}
private void Update()
{
ShootLaser();
}
void ShootLaser()
{
if(Physics2D.Raycast(m_transform.position, transform.right))
{
RaycastHit2D _hit = Physics2D.Raycast(m_transform.position, transform.right);
Draw2DRay(laserFirePoint.position, _hit.point);
Debug.Log(_hit.collider.name);
}
else
{
Draw2DRay(laserFirePoint.position, laserFirePoint.transform.right * defDistanceRay);
}
}
void Draw2DRay(Vector2 startPos, Vector2 endPos)
{
m_linerenderer.SetPosition(0, startPos);
m_linerenderer.SetPosition(1, endPos);
}
Can I somehow bypass this collider by setting a "tag" on it and in my code ignore colliders with that specific tag?
Anyone have a good work around for this issue?
EDIT: Forgot to mention Im looking at: https://docs.unity3d.com/ScriptReference/Physics2D.IgnoreCollision.html
But that takes in 2 colliders I just want to ignore 1.
EDIT2: I tried to set the Cinemachine camera polygon collider as a Layermask and ignore it with the following code:
public LayerMask layerMask;
void ShootLaser()
{
if (Physics2D.Raycast(m_transform.position, transform.right))
{
RaycastHit2D _hit = Physics2D.Raycast(m_transform.position, transform.right, layerMask);
Draw2DRay(laserFirePoint.position, _hit.point);
Debug.Log(_hit.collider.name);
}
else
{
Draw2DRay(laserFirePoint.position, laserFirePoint.transform.right * defDistanceRay);
}
}
EDIT 3:
RaycastHit2D _hit = Physics2D.Raycast(m_transform.position, transform.right, defDistanceRay, layerMask);
I added defDistanceRay into the Raycast and now it dosent seem to collide with the camera collider atleast! Super buggy but atleast i bypass the Cinemachine camera collider!
Sorry if the title is confusing. Basically, what I'm trying to do is make sure the player is grounded (so they can jump again), problem is in my game there will be convex shapes that the player could land on.
The current way I'm doing this is with a raycast, but since the single raycast could only come from somewhere like the centre, I made it so the raycast was going along the bottom of the player instead (this removed the problem where the player couldn't jump if more than half of their body was off a platform).
The raycast along the bottom however made it so if I tried to jump while on a shape like a ramp, it would cause me to go incredibly high, which I don't want.
Can anyone help me fix this?
Code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CharacterMovement : MonoBehaviour {
public Rigidbody2D rBody;
[Range(1, 10)]
public int Speed;
private float lastDistance;
public bool isGrounded = true;
private LayerMask environment;
void Start () {
environment = LayerMask.GetMask("Environment");
}
void FixedUpdate() {
// Check if the user is attempting to jump
if (Input.GetKey(KeyCode.A)) {
rBody.velocity = new Vector2(-Speed, rBody.velocity.y);
}
if (Input.GetKey(KeyCode.D)) {
rBody.velocity = new Vector2(Speed, rBody.velocity.y);
}
if (Input.GetKey(KeyCode.Space)) {
RaycastHit2D hit2D = Physics2D.Raycast(rBody.position+new Vector2(0,-1.5f), Vector2.right,1f,environment);
if (hit2D) {
if (hit2D.distance < lastDistance) {
lastDistance = hit2D.distance;
}
else {
lastDistance = 100f;
rBody.AddForce(new Vector2(0, 10), ForceMode2D.Impulse);
}
}
}
}
}
What happens if you try with Input.GetKeyDown(KeyCode.Space)?
Unless the game design it to keep jumping as long as you press space, this might be the reason you are jumping extra high.
If the Raycast solution is still wonky, another way of detecting if the player is grounded is using a Collider for the floor and a flag that tells you when you're colliding with it (you can set this flag using OnCollisionEnter/Exit or OnTriggerEnter/Exit)
I'm working on my very first Unity game. It's still in prototype and will be very simple anyways, consisting of a cube as the player and spheres as enemies.
I'm trying to write a code with AddForce to knock the player a pretty good distance in an arc in the direction opposite of the enemy when they come in contact, but I still have a primitive understanding of how to use AddForce and can't seem to get force applied in any direction at all. The player just moves through the enemy.
Here's the only thing I could manage to scrap together and it is obviously insufficient:
if (other.gameObject.tag == "Enemy")
{
playerDead = true;
Rigidbody rigidbody = other.GetComponent<Rigidbody> ();
rigidbody.AddForce (transform.forward * 100);
}
I can post a screenshot if it helps.
Instead of transform.forward, use a more tailored direction depending on the position of both units
public float speed = 100;
if (other.gameObject.tag == "Enemy")
{
playerDead = true;
Vector3 direction = (transform.position - other.transform.position).normalized;
other.GetComponent<Rigidbody>().AddForce (direction * speed);
}
Edit: You must make sure that the rigidbody is on the game object calling the OnCollisionEnter function, it cant just be on any of the objects involved in the collision.
I cannot comment so I must make an answer.
You have an isTrigger set up apparenty. Be sure to turn isTrigger OFF or your player will walk through the enemy collider.
[SerializeField]
private float speed = 100;
private bool playerDead;
OnCollisionEnter(Collision collision)
{
if(collision.gameObject.tag == "Enemy")
{
playerDead = true;
Vector3 direction = (transform.position - collision.transform.position).normalized;
collision.GetComponent<Rigidbody>().AddForce (direction * speed,0,0);
//AddForce is a vector3 apparently and requires (x,y,z)
// Also note that I'm getting errors with the "collision.GetComponent<>"
// You're going to want to remove "collision"
// I think the smarter approach maybe to declare a public Rigidbody variable ( Up Top )
[SerializeField]
private Rigidbody playerRidg;
playerRidg = GetComponent<Rigidbody>().AddForce(direction * speed,0,0);
}
}
My solution using coroutine which toggles a bool "spikes":
Player collision:
private void OnTriggerEnter2D(Collider2D collision){
if (collision.gameObject.tag == "Spike" )
{
StartCoroutine(Knockback());
}
}
Coroutine:
IEnumerator Knockback()
{
spikes = true;
yield return new WaitForSecondsRealtime(0.3f);
spikes = false;
}
Whenever the spikes bool is true player will be moved
void FixedUpdate()
{
if (spikes ==true)
{
Vector2 NewPosition = new Vector2(10.0f, 10.0f);
moveCharacter(NewPosition);
}
}