Shooting RigidBody2d Projectile in the direction of movement

Discussion in '2D' started by LionFische, Jan 27, 2016.

1. LionFische

Joined:
Oct 16, 2015
Posts:
107
Hi, I have a Player Character on a 2D plane. He can move in all directions. I have scripted so that he can launch a projectile. At the moment I am using the following code to launch.

Code (CSharp):
I would however like to be able to launch in the direction the player object is moving. I'm not sure where to go with this and was wondering if some good solutions exist. I'm relatively new to Unity so am unfamiliar with most of it's functions.

Full Script:

Code (CSharp):
1. using UnityEngine;
2. using System.Collections;
3.
4. public class LaunchProjectile : MonoBehaviour {
5.
6.     public Rigidbody2D projectile;
7.
8.     public Transform Launcher;
9.
10.     public float projectileSpeed = 5000f;
11.
12.     // Use this for initialization
13.     void Start () {
14.
15.     }
16.
17.     // Update is called once per frame
18.     void Update () {
19.         if (Input.GetButtonDown ("Fire1"))
20.         {
21.             Rigidbody2D projectileInstance;
22.             projectileInstance = Instantiate(projectile, Launcher.position, Launcher.rotation) as Rigidbody2D;
23.
25.         }
26.     }
27. }

Thanks,
2h

2. jeffreyschoch

Joined:
Jan 21, 2015
Posts:
2,558
If your player is always facing the direction he's moving, then you can do the same thing with the player.

(Using the "right" property or X axis here for example)

Code (CSharp):
1. Vector2 directionalForce = player.transform.right * projectileSpeed;

If your player character is using a RigidBody to move, you can use the velocity property to do something like this:

Code (CSharp):
1. public float force;
2. Vector2 direction = characterRigidBody.velocity.normalized;
3. Vector2 directionalForce = direction * force;

Otherwise without physics you'll have to keep track of the player's position to get the direction of movement.
Code (CSharp):
1. public class MyCharacter : MonoBehaviour {
2.     private Vector3 lastPosition;
3.     private Vector2 movementDirection;
4.
5.     private void Start() {
6.         lastPosition = transform.position;
7.     }
8.
9.     private void Update() {
10.         movementDirection = (transform.position - lastPosition).normalized;
11.         lastPosition = transform.position;
12.     }
13. }

Last edited: Jan 27, 2016
3. LionFische

Joined:
Oct 16, 2015
Posts:
107
Thanks you so much for such a thoughtful answer. I will go through it now and see how I can apply it. I can follow the logic, just need time now to get comfortable with it.

2h

4. LionFische

Joined:
Oct 16, 2015
Posts:
107
Hi thanks for the help, but I'm still confused. I understand the theory but am unsure how to apply it.

So the above code helps me establish the direction of movement, but I'm unsure how to apply that to the launching of the projectile.

In my code the line

Code (CSharp):
gives direction and force to the newly created Rigidbody2D. Is there somewhere here that I can replace with the results of the above code so as to apply direction? I understand you are acquiring direction, just don't understand how to apply it then.

Sorry for the bother, it's tricky starting out!

2h

5. LionFische

Joined:
Oct 16, 2015
Posts:
107
Hi, update. I'm making progress.

I have altered my player control script as in your example to take the current transform and compare it to the last transform to calculate a direction.

I am now pulling the 'movementdirection' value to my launcher script and have found how to use this value with addForce.

Code (CSharp):
1.             Rigidbody2D projectileInstance;
2.             projectileInstance = Instantiate(projectile, Launcher.position, Launcher.rotation) as Rigidbody2D;
3.
So I am now launching missiles in the required direction. I'll work a little more on it, but it seems to be working.

Thank you very much for the help.

2h

6. jeffreyschoch

Joined:
Jan 21, 2015
Posts:
2,558
My pleasure, glad you were able to make progress and figure it out on your own.

To clarify what you've discovered, in this line:
Code (CSharp):
Launcher.up is your normalized vector, which is your Launcher's local Y axis. When you use the up/right/forward properties, they are already normalized vectors representing a direction.

These are given to you for convenience in the Transform class, and in the Vector classes for world space (for example, if you wanted something to go straight up from your object no matter what its orientation, you could use Vector2.up)

in this line:
Code (CSharp):
1. movementDirection = (transform.position - lastPosition).normalized;
You're calculating your own normalized direction vector, by first getting the difference between the positions giving you a Vector3, which in effect is the distance between the points (imagine a line connecting the two points), and then normalizing it to make it a direction vector (imagine that same line, being shrunk down to the scale of 0 to 1 but maintaining the same orientation).

Then you're applying it just like before here:
Code (CSharp):
So that means these are all the same thing:
Code (CSharp):
1. Vector3.right * 50
2. new Vector3(1,0,0) * 50
3. new Vector3(50,0,0)

7. LionFische

Joined:
Oct 16, 2015
Posts:
107
Hi thanks for the reply once again.

I have moved on and now have a different problem but with similarities.

I have a cube, I have created a list to give it waypoints for it to cycle/patrol through. I am firing a raycast from it to detect objects in the area. The issue I have is that the cube moves through the waypoints, but upon reaching a waypoint it faces the same direction and just changes vector to the next point (always facing forward).

I would like instead that it rotates, which in turn brings the raycast towards it's direction of movement and as it moves to it's next waypoint. I think similar principles will apply to the previous issue.

My code as it stands..

Raycast Script

Code (CSharp):
1. using UnityEngine;
2. using System.Collections;
3.
4. public class RaycastForward : MonoBehaviour {
5.
6.     // Update is called once per frame
7.     void Update () {
8.         RaycastHit hit;
9.         float theDistance;
10.         Vector3 fwd = transform.TransformDirection (Vector3.forward) * 10;
11.         Debug.DrawRay (transform.position, fwd, Color.green);
12.
13.         //Last var in statement limits raycast
14.         if (Physics.Raycast (transform.position, (fwd), out hit, 10))
15.         {
16.             //How far the distance the Raycast is travelling to hit
17.             theDistance = hit.distance;
18.             //Print the distance + the name of the object hit
19.             Debug.Log (theDistance + " " + hit.collider.gameObject.name);
20.         }
21.     }
22. }
Patrol Cube Control

Code (CSharp):
1. using UnityEngine;
2. using System.Collections;
3. //This allows for the use of lists
4. using System.Collections.Generic;
5.
6. public class PatrolController : MonoBehaviour {
7.
8.     public List<Transform> wayPointsList = new List<Transform>();
9.
10.     public Transform currentWaypoint;
11.
12.     public int wayPointNumber = 0;
13.
14.     public float speed =3f;
15.
16.     // Use this for initialization
17.     void Start () {
18.         currentWaypoint = wayPointsList [wayPointNumber];
19.     }
20.
21.     // Update is called once per frame
22.     void Update () {
23.         moveEnemy ();
24.     }
25.
26.     public void moveEnemy()
27.     {
28.         float distanceToCurrent = Vector2.Distance (transform.position, currentWaypoint.position);
29.
30.         if (distanceToCurrent == 0) {
31.             if (wayPointNumber != wayPointsList.Count - 1) {
32.                 wayPointNumber++;
33.                 currentWaypoint = wayPointsList [wayPointNumber];
34.             } else {
35.                 wayPointNumber = 0;
36.                 currentWaypoint = wayPointsList [wayPointNumber];
37.             }
38.         }
39.
40.         transform.position = Vector3.MoveTowards (transform.position, currentWaypoint.position, speed * Time.deltaTime);
41.     }
42.
43. }
I'll be working on this here, but any advice would be greatly appreciated.

Many thanks,
2h

8. LionFische

Joined:
Oct 16, 2015
Posts:
107
Hi Jeffrey, i think I have it working.

I'll need to work on it and smooth the motion a little.

Code (CSharp):
1.         Vector3 relativePos = currentWaypoint.position - transform.position;
2.         transform.rotation = Quaternion.LookRotation (relativePos);
My object is now turning to face towards the waypoints.

Good day's work, waypoints working, raycasts firing and detecting and now rotation.

One step at a time, each one hard worked for!

2h

9. jeffreyschoch

Joined:
Jan 21, 2015
Posts:
2,558
Try this!
Code (CSharp):
1.
2. // targetRotation would be the result of your Quat.LookRotation call
3. transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, speed * Time.deltaTime);

10. LionFische

Joined:
Oct 16, 2015
Posts:
107
Hi Jeffrey, thanks again for the reply.

I don't quite get this one, first time I have come across Slerp.

You say that targetRotation would be the result of the Quat.LookRotation call. Can you explain this a little more.

Should I store the result of

Code (CSharp):
1. transform.rotation = Quaternion.LookRotation (relativePos);
in a variable and then apply it to the tartgetRotation in line 3 of your code?

Thanks,
2h

11. jeffreyschoch

Joined:
Jan 21, 2015
Posts:
2,558
Yep, that's how you would do it.

Slerp stands for Spherical Linear Interpolation.

Code (CSharp):
1. // gets the direction from this object to the currentWaypoint
2. Vector3 direction = (currentWaypoint.position - transform.position).normalized;
3. // converts that direction vector into a Quaternion representing a rotation
4. Quaternion targetRotation = Quaternion.LookRotation(direction);
5. // spherically interpolates between the current rotation and the target
6. transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, speed * Time.deltaTime);
If your object just goes straight towards the waypoints, I would advise saving the direction and target rotation in private variables, and only setting them when currentWaypoint has changed.

12. LionFische

Joined:
Oct 16, 2015
Posts:
107
Hi Jeffrey, just wanted to say thanks for all the help. I'm doing a college course and I find it quite lacking at times. Having someone to ask questions of here has been a great help.

I had to step back from coding for the last few days to do some animation with Unity and also spend some time with 3D Study. I'll get back to the coding quite soon, hopefully today or tomorrow. I'm trying to build myself a basic set of scripts that I can then reference and expand upon. I'm sure there will be more questions!

Many thanks,
2h

13. jeffreyschoch

Joined:
Jan 21, 2015
Posts:
2,558
No problemo. Best thing to do is to ask questions and experiment. And for me, answering people's questions helps solidify my own knowledge of Unity, forcing me to actually explain the things that I believe I understand in a way that makes sense to others. So it's a win win.

14. LionFische

Joined:
Oct 16, 2015
Posts:
107
Hi Jeffrey, another issue!

I have my patrols moving around the sandbox now, moving between nav points. I have added a cube to them to act as a detection zone. When the player enters the detection zone, I fire a raycast from the patrol to the player.

I use OnTriggerEnter/Stay/Exit.

The strange thing is, the code works when the player enters and exits, I have a message to debug.log announcing the states, but the Stay does not seem to register.

Code (CSharp):
1. using UnityEngine;
2. using System.Collections;
3.
4. public class PatrolDetection : MonoBehaviour {
5.
6.     public RaycastHit hit;
7.
8.     public bool isDetected = false;
9.
10.     void OnTriggerEnter(Collider other)
11.     {
12.
13.         if (Physics.Raycast(transform.position, (other.transform.position - transform.position), out hit))
14.         {
15.             if(hit.collider.gameObject.name == "Player")
16.             {
17.                 Debug.Log ("The " + other + " entered the collider");
18.                 isDetected = true;
19.                 Debug.Log ("I can see the " + other);
20.                 Debug.DrawLine (transform.parent.position, hit.point);
21.             }
22.         }
23.     }
24.
25.     void OnTriggerStay(Collider other)
26.     {
27.
28.         if (Physics.Raycast(transform.position, (other.transform.position - transform.position), out hit))
29.         {
30.             if(hit.collider.gameObject.name == "Player")
31.             {
32.                 Debug.Log ("The " + other + " is staying in the collider");
33.                 isDetected = true;
34.                 Debug.Log ("I can see the " + other);
35.                 Debug.DrawLine (transform.parent.position, hit.point);
36.             }
37.         }
38.     }
39.
40.     void OnTriggerExit(Collider other)
41.     {
42.         isDetected = false;
43.         if(other.gameObject.CompareTag("Player"))
44.         {
45.             Debug.Log("The " + other + " is no longer detected");
46.         }
47.     }
48. }
49.
I'm really not sure what could be going wrong. The code for Enter and Stay are the same and I imagine should both function the same.

I'm wondering could it be related to the nav mesh I have baked perhaps as I think this was working beforehand.

2h

15. jeffreyschoch

Joined:
Jan 21, 2015
Posts:
2,558
You're using 3D collider functions for this, but rigidbody2D? Is that intended?

If you're certain that Enter and Exit are working properly, then there should be no reason why Stay isn't working. Your syntax looks correct, so as long as you have a 3D collider set to trigger, and at least one of the colliding objects has a rigidbody, that should be fine.

16. LionFische

Joined:
Oct 16, 2015
Posts:
107
Hi Jeffrey, I'm working in 3D now, moving on with the concepts. I have a Rigidbody rather then Rigidbody 2D now

Prompted by you recent post, I have just removed the rigidbody I had on the patrol and put one on the player instead. Now both methods are calling and operating correctly. Really strange problem, I don't actually understand why the change worked! Oh well.

The next issue to deal with is the Raycast. When my player is behind a wall, I need the raycast to be unable to reach him, so he can hide in effect. I'll have to look into how to achieve this and make it so walls etc can block raycasts.

Thanks again.

2h

17. jeffreyschoch

Joined:
Jan 21, 2015
Posts:
2,558

From the docs "Collision events are only sent if one of the colliders also has a non-kinematic rigidbody attached.".

Was your new cube collider a separate game object with no rigidbody? If it was, that could've been causing problems. Now that your player has a rigidbody, it should be able to collide with any collider whether it has a rigidbody or not.

Raycasts take a LayerMask parameter. That's how you can determine what will block the ray or not. You can assign your walls and blockable surfaces to the same layer at the top of the inspector, and then check for that layer when casting.

Code (CSharp):
Then set them in the inspector, and pass the variable to the raycast when you call it.

Code (CSharp):
1. Physics.Raycast(startPosition, direction, Hit, Range, sightBlockingLayers)

18. LionFische

Joined:
Oct 16, 2015
Posts:
107
Thanks Jeffrey.

Okay, I've created a layers tab for Walls (Using the layers tab in the Inspector, not above it). I've added the walls to that tab layer. The raycast doesn't seem to be firing now. I'm not sure if I have formatted the code correctly. I'd imagine that must be the problem.

Yours...
Code (CSharp):
1. Physics.Raycast(startPosition, direction, Hit, Range, sightBlockingLayers)
Mine..
Code (CSharp):
1.     if (Physics.Raycast(transform.position, (other.transform.position - transform.position), out hit, sightBlockingLayers))

2h

19. jeffreyschoch

Joined:
Jan 21, 2015
Posts:
2,558
No need to apologize. You just need to make sure that your Raycast call is passing the proper parameters.

There are a limited number of overloaded methods for Raycast, so there might not be one exact match for the combination of properties you're trying to pass.

In order to have the combination of Origin, Direction, Hit Info, and LayerMask, you'll also need to pass a Max Distance.

Try this:
Code (CSharp):
1. Physics.Raycast(transform.position, (other.transform.position - transform.position), out hit, float.MaxValue, sightBlockingLayers)
This adds a max distance which is virtually infinite for all intents and purposes.

Instead of float.MaxValue, you could pass a "sight range" float which represents how far the ray will go, or how far it can see.

Alternatively, you could use "named parameters", in which you specify which parameters you're passing, and the rest are left to the defaults. (this only works for parameters that are optional, and have defined defaults).

So you could do this I believe:
Code (CSharp):
1. Physics.Raycast(origin: transform.position, direction: (other.transform.position - transform.position), hitInfo: out hit, layerMask: sightBlockingLayers)

20. LionFische

Joined:
Oct 16, 2015
Posts:
107
Okay, tried that but I'm getting an error.

Method..

Code (CSharp):
1.     void OnTriggerStay(Collider other)
2.     {
3.         isDetected = true;
4.         //if (Physics.Raycast(transform.position, (other.transform.position - transform.position), out hit, sightBlockingLayers))
5.         //if(Physics.Raycast(transform.position, (other.transform.position - transform.position), out hit, 100, sightBlockingLayers))
6.
7.         if(Physics.Raycast(origin: transform.position, direction: (other.transform.position - transform.position), hitInfo: out hit, layerMask: sightBlockingLayers))
8.         {
9.             //if(hit.collider.gameObject.name == "Player")
10.             if(other.gameObject.name == "Player")
11.             {
12.                 Debug.Log ("The " + other + " is staying in the collider");
13.                 canSee = true;
14.                 //Debug.Log ("I can see the " + other);
15.                 Debug.DrawLine (transform.parent.position, hit.point);
16.             }
17.         }
18.     }
error...
error CS1525: Unexpected symbol `out'

and
error CS8025: Parsing error at the closing bracket on the OnTriggerStay
All the brackets seem to be matched.

21. jeffreyschoch

Joined:
Jan 21, 2015
Posts:
2,558
Hm, that's interesting. I'm not certain why you're getting those specific errors.

I'm seeing that Raycast is complaining when I try to give it only 4 parameters like this, so maybe just stick with the first way and include the maxDistance, making it 5 parameters, hopefully that gets rid of those errors.

Maybe use Mathf.Infinity for clarity if you want the ray to go 'forever'.

22. LionFische

Joined:
Oct 16, 2015
Posts:
107
I think I got it! Argh!

The code is working, it was to do with the Layers.

The public LayerMask that I had created, I hadn't chosen it in the assigned blocking layer in the inspectors script component. I just see it now. I've adjusted it and the raycast are not firing through the walls.

I'll test it more, but fingers crossed this is the problem. Thanks again Jeffrey, I owe you!

2h

23. LionFische

Joined:
Oct 16, 2015
Posts:
107
Spoke too soon...not working.

24. jeffreyschoch

Joined:
Jan 21, 2015
Posts:
2,558
Whats the problem?

25. LionFische

Joined:
Oct 16, 2015
Posts:
107

Okay, it seemed to work at first. I then posted a reply, came back and it's not working.

Not just that, but my patrol is no longer following the player when it detects the player. Not sure what has gone wrong. I'll attach both scripts.

So basically I have waypoints that get overwritten when the player is seen. That did seem to be working previously.

Code (CSharp):
1. using UnityEngine;
2. using System.Collections;
3. //This allows for the use of lists
4. using System.Collections.Generic;
5.
6. public class PatrolController : MonoBehaviour {
7.
8.     public List<Transform> wayPointsList = new List<Transform>();
9.
10.     public Transform currentWaypoint;
11.
12.     public Transform currentPatrolTarget;
13.
14.     public int wayPointNumber = 0;
15.
16.     public float speed =4f;
17.
18.     public float turnSpeed = 1f;
19.
20.     public PatrolDetection PT;
21.
23.     public NavMeshAgent navAgent;
24.
25.     // Use this for initialization
26.     void Start () {
27.         currentWaypoint = wayPointsList [wayPointNumber];
28.     }
29.
30.     // Update is called once per frame
31.     void Update () {
32.         moveEnemy ();
33.     }
34.
35.     public void moveEnemy()
36.     {
37.
38.         if (GameObject.Find ("Player") != null && PT.canSee == true) {
39.
40.             //Debug.Log ("The Player is in detected!");
41.             currentPatrolTarget = GameObject.Find ("Player").transform;
42.             navAgent.SetDestination(currentPatrolTarget.position);
43.         }
44.         else
45.         {
46.             if(navAgent.remainingDistance <= navAgent.stoppingDistance)
47.             {
48.                 if (wayPointNumber != wayPointsList.Count - 1) {
49.                     wayPointNumber++;
50.                     navAgent.SetDestination(wayPointsList [wayPointNumber].position);
51.                 }
52.                 else
53.                 {
54.                     wayPointNumber = 0;
55.                     navAgent.SetDestination(wayPointsList [wayPointNumber].position);
56.                 }
57.             }
58.         }
59.     }
60.
61. }
the the detection code again.

Code (CSharp):
1. using UnityEngine;
2. using System.Collections;
3.
4. public class PatrolDetection : MonoBehaviour {
5.
7.
8.     public RaycastHit hit;
9.
10.     public float maxRayCastRange = 50.0f;
11.
12.     //general detection
13.     public bool isDetected = false;
14.     //can see target
15.     public bool canSee = false;
16.
17.     void OnTriggerStay(Collider other)
18.     {
19.         //Something has triggered Patrol Detection
20.         isDetected = true;
21.
22.         //if (Physics.Raycast(transform.position, (other.transform.position - transform.position), out hit))
23.         //if (Physics.Raycast(transform.position, (other.transform.position - transform.position), out hit, sightBlockingLayers))
24.         if(Physics.Raycast(transform.position, (other.transform.position - transform.position), out hit, 50, sightBlockingLayers))
25.         //if(Physics.Raycast(origin: transform.position, direction: (other.transform.position - transform.position), hitInfo: out hit, layerMask: sightBlockingLayers))
26.         //if(Physics.Raycast(transform.position, (other.transform.position - transform.position), out hit, maxRayCastRange, sightBlockingLayers))
27.         {
28.             //if(hit.collider.gameObject.name == "Player")
29.             if(other.gameObject.name == "Player")
30.             {
31.                 Debug.Log ("The " + other + " is staying in the collider");
32.                 canSee = true;
33.                 //Debug.Log ("I can see the " + other);
34.                 Debug.DrawLine (transform.parent.position, hit.point);
35.             }
36.         }
37.     }
38.
39.     void OnTriggerExit(Collider other)
40.     {
41.         isDetected = false;
42.         if(other.gameObject.CompareTag("Player"))
43.         {
44.             Debug.Log("The " + other + " exited the collider");
45.             canSee = false;
46.         }
47.     }
48. }
49.
This is driving me nuts at this stage! Thanks for your help.

2h

26. LionFische

Joined:
Oct 16, 2015
Posts:
107
Got the Patrol back tracking me...still casting through walls though.

27. jeffreyschoch

Joined:
Jan 21, 2015
Posts:
2,558
Hey there. I read through your code, and I think you could do with some reorganization. For your sanity, and for ease of debugging through readability. It should do the exact same thing your original code intended.

Your patrol has two states, one for chasing the player, and one for patrolling the waypoints.

I added a PatrolState enum to your PatrolController, with a "Patrolling" state, and a "Chasing" state. Then I added a variable "currentState" of type PatrolState to keep track of what the Patrol is doing. This is my preferred method of keeping state and I think it will help you to use this pattern.

I also divided up your "moveEnemy" functionality into more functions, and a "ReachedTarget" property just for clarity.

One rule I try to follow is that if you ever have duplicate code in your classes, you should probably create a function for it and just call that function instead.

I haven't tested this, so forgive me if I've made an error somewhere. Let me know if you're unfamiliar with anything I've done, and I'll give some further explanation.

PatrolController.cs
Code (CSharp):
1. public enum PatrolState {
2.     Patrolling,
3.     Chasing,
4. }
5. public class PatrolController : MonoBehaviour {
6.
7.     public float speed = 4f;
8.     public float turnSpeed = 1f;
9.     public List<Transform> wayPointsList;
10.
11.     public PatrolDetection PT;
12.
14.     public NavMeshAgent navAgent;
15.
16.     private int currentWaypointIndex = 0;
17.     private GameObject player;
18.     private Transform currentTarget;
19.     private PatrolState currentState;
20.
21.     public bool ReachedTarget
22.     {
23.         get { return navAgent.remainingDistance <= navAgent.stoppingDistance; }
24.     }
25.
26.     // Use this for initialization
27.     private void Start() {
28.         player = GameObject.Find("Player");
29.         SetTarget(wayPointsList[currentWaypointIndex]);
30.         currentState = PatrolState.Patrolling;
31.     }
32.
33.     // Update is called once per frame
34.     private void Update() {
36.
37.         // what is the patrol currently doing?
38.         switch(currentState) {
39.             case PatrolState.Chasing:
40.                 Chase();
41.                 break;
42.             case PatrolState.Patrolling:
43.                 Patrol();
44.                 break;
45.         }
46.     }
47.
49.         // if the player can be seen
50.         if(player != null && PT.CanSeePlayer) {
51.             // chase if not already chasing
52.             if(currentState != PatrolState.Chasing) {
53.                 SetTarget(player.transform);
54.                 currentState = PatrolState.Chasing;
55.             }
56.         } else {
57.             // patrol if not already patrolling
58.             if(currentState != PatrolState.Patrolling) {
59.                 SetTarget(wayPointsList[currentWaypointIndex]);
60.                 currentState = PatrolState.Patrolling;
61.             }
62.         }
63.     }
64.
65.     private void SetTarget(Transform target) {
66.         currentTarget = target;
67.         navAgent.SetDestination(currentTarget.position);
68.     }
69.
70.     private void Chase() {
71.         if(ReachedTarget) {
72.             // reached player
73.         }
74.     }
75.
76.     private void Patrol() {
77.         if(ReachedTarget) {
78.             // go to next waypoint
79.             NextWaypoint();
80.         }
81.     }
82.
83.     private void NextWaypoint() {
84.         if(currentWaypointIndex == wayPointsList.Count - 1) {
85.             currentWaypointIndex = 0;
86.         } else {
87.             currentWaypointIndex++;
88.         }
89.         currentTarget = wayPointsList[currentWaypointIndex];
90.         navAgent.SetDestination(currentTarget.position);
91.     }
92.
93.     private void PreviousWaypoint() {
94.         if(currentWaypointIndex == 0) {
95.             currentWaypointIndex = wayPointsList.Count - 1;
96.         } else {
97.             currentWaypointIndex--;
98.         }
99.         currentTarget = wayPointsList[currentWaypointIndex];
100.         navAgent.SetDestination(currentTarget.position);
101.     }
102. }
PatrolDetection.cs
Code (CSharp):
1. public class PatrolDetection : MonoBehaviour {
2.
4.     public float maxRayCastRange = 50.0f;
5.
6.     // general detection
7.     public bool IsDetecting { get; private set; }
8.     // can see player
9.     public bool CanSeePlayer { get; private set; }
10.
11.     private RaycastHit hit;
12.
13.     void OnTriggerStay(Collider other) {
14.         Debug.Log("The " + other.name + " is staying in the collider");
15.         IsDetecting = true;
16.         if(Physics.Raycast(transform.position, (other.transform.position - transform.position), out hit, maxRayCastRange, sightBlockingLayers)){
17.             Debug.DrawLine(transform.parent.position, hit.point);
18.             // did it hit the player?
19.             CanSeePlayer = hit.collider.CompareTag("Player");
20.         }
21.     }
22.
23.     void OnTriggerExit(Collider other) {
24.         Debug.Log("The " + other.name + " has exited the collider");
25.         IsDetecting = false;
26.         if(other.CompareTag("Player")) {
27.             CanSeePlayer = false;
28.         }
29.     }
30. }

Last edited: Feb 12, 2016
28. LionFische

Joined:
Oct 16, 2015
Posts:
107
Thanks Jeffrey.

Nice neat code.

I put it in place but unfortunately the patrol isn't following the player and raycasts are still going through objects.

I'll continue look at it. I feel like I'm banging my head against a wall at this stage. There must be something missing in the approach.

2h

29. jeffreyschoch

Joined:
Jan 21, 2015
Posts:
2,558
Hang on, I just discovered a bug in what I wrote, I'm going to edit my code.

30. LionFische

Joined:
Oct 16, 2015
Posts:
107
Oh and when the raycast does fire, it is not at the player. It's fires at a variety of objects.

I really hope to find a solution to this that may also be of use to you. It's a tough nut to crack.

2h

31. jeffreyschoch

Joined:
Jan 21, 2015
Posts:
2,558
Okay, I think it should be all written as intended now. However I'm not certain why your raycasts would be going through walls if you set the layer mask properly.

What I would advise is to use the Debugger and step through the code and see what's not working correctly.

If you don't know how to use the Debugger, start adding Debug.Log calls where you think things aren't going right to verify your assumptions.

Right now the Detector code does fire a ray at every object that stays in its detection, and only if it's the player does it set "CanSeePlayer" to true. If you want it only to fire a ray if it's the player, then replace the OnTriggerStay function with this:

Code (CSharp):
1. void OnTriggerStay(Collider other) {
2.         Debug.Log("The " + other.name + " is staying in the collider");
3.         IsDetecting = true;
4.         if(other.CompareTag("Player")) {
5.             if(Physics.Raycast(transform.position, (other.transform.position - transform.position), out hit, maxRayCastRange, sightBlockingLayers)) {
6.                 Debug.DrawLine(transform.parent.position, hit.point);
7.                 // did it hit the player?
8.                 CanSeePlayer = hit.collider.CompareTag("Player");
9.             }
10.         }
11.     }

32. jeffreyschoch

Joined:
Jan 21, 2015
Posts:
2,558
If you can't figure it out, once I get home later I'll be able to test this code out and find out what the missing piece is.

Don't give up though! Debugging requires a lot of patience. Keep verifying the things you're assuming to be correct, and eventually you'll find where the snag is and learn something valuable from it.

33. LionFische

Joined:
Oct 16, 2015
Posts:
107
Thanks Jeffrey.

I owe you for this!

2h

34. jeffreyschoch

Joined:
Jan 21, 2015
Posts:
2,558
Once you become a Unity wizard just pay it forward to another budding developer.

35. LionFische

Joined:
Oct 16, 2015
Posts:
107

Hi Jeffrey, tried that version. Still no luck with it.

It doesn't target the player correctly and fails to follow.

I might try a fresh post and see what comes up. Are you aware of any other unity forums that might be worth a post?

2h

36. jeffreyschoch

Joined:
Jan 21, 2015
Posts:
2,558
I just tested the code in a new project, and it all appears to be working for me on my end. I think you might just have something set up wrong in your scene. Some suggestions:
• Make sure that the player and any obstacles are on a layer that the Detector script has checked in the LayerMask.
• Make sure that at least one of the objects colliding has a Rigidbody, probably the Player and not the Detector.
• Make sure your Player is tagged "Player".

37. LionFische

Joined:
Oct 16, 2015
Posts:
107
Thanks Jeffrey. I just did some screen grabs of the layers for my player, the walls and the patrol.

I have a rigidbody on the player and the player it is tagged.

Do you think I have the layers chosen correctly?

2h

File size:
730.3 KB
Views:
429
File size:
176 KB
Views:
413
File size:
180.3 KB
Views:
400
38. LionFische

Joined:
Oct 16, 2015
Posts:
107
Here is my assets folder, maybe you will get a better view of my setup.

Sorry for all the bother.

2h

File size:
84.8 KB
Views:
97
39. LionFische

Joined:
Oct 16, 2015
Posts:
107
And here is an image of the raycast going through a wall.

File size:
53.1 KB
Views:
423
40. jeffreyschoch

Joined:
Jan 21, 2015
Posts:
2,558
Took a quick look at your project. You need to update your OnTriggerStay function to the updated one I posted. You also need to add the "Default" layer to your Detector in the inspect, because the player needs to be hit by the raycast. Either that or change your Player to the SightBlocking layer.

Here's the function again, I made the raytrace change color when the player can be seen.

Code (CSharp):
1. void OnTriggerStay(Collider other)
2.     {
3.         Debug.Log("The " + other.name + " is staying in the collider");
4.         isDetected = true;
5.         if (other.CompareTag("Player"))
6.         {
7.             if (Physics.Raycast(transform.position, (other.transform.position - transform.position), out hit, maxRayCastRange, sightBlockingLayers))
8.             {
9.                 // did it hit the player?
10.                 canSee = hit.collider.CompareTag("Player");
11.                 if (canSee)
12.                 {
13.                     Debug.DrawLine(transform.parent.position, hit.point, Color.red);
14.                 }
15.                 else
16.                 {
17.                     Debug.DrawLine(transform.parent.position, hit.point, Color.white);
18.                 }
19.             }
20.         }
21.     }

41. LionFische

Joined:
Oct 16, 2015
Posts:
107
Thanks Jeffrey.

I like the idea of the colour coded ray.

Unfortunately I'm still having the same issue.

So the Player is on the Default Layer.

The Patrol is on the Default Layer.

The Walls are on the 'Walls' Layer.

The Walls Layer is assigned to the SightBlocking Layer. (Tried adding Default also)

So from this I think the Walls Layer should block the line of sight from the patrol? But they don't.

This is really some issue at this stage!

I'm doing a course at the moment, but my tutor is really not so good. I emailed a week ago for help and still have not received any reply. I won't get to see him for another two weeks. It's frustrating, I'm spending a lot of time on this and not making progress in other areas.

If you get a version working, could you send me a copy. I'd really like to see this working.

2h

File size:
64.2 KB
Views:
347
File size:
60.4 KB
Views:
458
42. LionFische

Joined:
Oct 16, 2015
Posts:
107
I've also noticed that the patrol will follow upon detection, but after a while if you escape detection and then return to detection, it no longer raycasts at the player.

I have found a Stealth Tutorial on the Unity site. I'm am going to go through it. It's in 4.2 but hopefully I can still use it. I'm interested to see how they have implemented these mechanics. I hope it works!

2h

43. jeffreyschoch

Joined:
Jan 21, 2015
Posts:
2,558
Don't get frustrated. Bugs like these happen all the time. Take it as an opportunity to practice your debugging and deduction skills. A successful debug is always a great learning experience, and will prevent bugs in the future.

That being said, I will figure out why your project isn't working after work today. It was working for me when I tested it over the weekend, so we must have some key difference in our setup, or maybe there's an oversight in the code. Either way, I'll fix it tonight and get back to you. This thread needs some resolution! haha

44. LionFische

Joined:
Oct 16, 2015
Posts:
107
Thank you very much Jeffrey. I really wish there was some way I could pay you back for all the help.

I hope some time in the future I might be able to help you.

I'm working on creating some models in 3D Studio all day. Another aspect of my studies. Just about to start into the stealth tutorial now. I'm really interested to see how they have dealt with detection.

Fingers crossed on this raycast!

2h

45. jeffreyschoch

Joined:
Jan 21, 2015
Posts:
2,558
Hey man, just went through the code again and saw that I made a typo in my function.

In the actual raycast I was casting from transform.position (the detector) and not transform.parent.position (the cube patrol).

The Debug.draw was using the correct positions, so it looked fine, but the actual raycast was wrong.

Code (CSharp):
1. void OnTriggerStay(Collider other)
2.     {
3.         Debug.Log("The " + other.name + " is staying in the collider");
4.         isDetected = true;
5.         if (other.CompareTag("Player"))
6.         {
7.             if (Physics.Raycast(transform.parent.position, (other.transform.position - transform.parent.position), out hit, maxRayCastRange, sightBlockingLayers))
8.             {
9.                 // did it hit the player?
10.                 canSee = hit.collider.CompareTag("Player");
11.                 if (canSee)
12.                 {
13.                     Debug.DrawLine(transform.parent.position, hit.point, Color.red);
14.                 }
15.                 else
16.                 {
17.                     Debug.DrawLine(transform.parent.position, hit.point, Color.white);
18.                 }
19.             }
20.         }
21.     }

46. LionFische

Joined:
Oct 16, 2015
Posts:
107
Hi Jeffrey, thanks for that.

I'm just trying it here, the detection is working, but not the canSee. It's not casting at the player and then following the player.

2h

47. jeffreyschoch

Joined:
Jan 21, 2015
Posts:
2,558
Make sure that your "sightBlockingLayers" includes the layer the player is on. You said he's on Default? Then include Default in the layermask. Otherwise the player will never be hit by the ray.

48. LionFische

Joined:
Oct 16, 2015
Posts:
107
Okay Jeffrey, but it looks like the player is being hit by the ray. The isDetected bool And more importantly the canSee bool are registering true states on the inspector.

You know Jeffrey, I was thinking last night on the issue and I've started to wonder could the Navmesh be in some way related to this problem. I've just used one for the first time on this project and I am wondering can it in some way be interfering with the possible places that the player can cast a ray.

I'm going to look into that today.

BTW, I was in college last night talking to a colleague, they are having exactly the same issue with raycasts going through objects. Frustration there also!

2h

49. LionFische

Joined:
Oct 16, 2015
Posts:
107
Okay, I've removed the Nav mesh and behaviour is changing.

For one the patrol knows how to get to the player. It is still detecting and seeing the player through objects though.

The raycast is different though, it only seems to penetrate one wall.

Perhaps this isn't the issue then.

2h