How to set destination to the closest grid on the side of the target position depend on camera direction?

Hi, I’m making 2.5d game where player and enemies are sprite and can move in 3d space .
(also I’m new to a* ). I’m using grid graph and I would like to set destination to closest grid which is on the side the player depend on the main camera’s direction instead of on any direction of the target position. You can have a look at my pictures for the reference. Is it possible and what is the first solution you came up with?, Thank you!


Hi

I’m not quite sure what you are asking for. Would you mind clarifying a bit?

If I understand collect, If I set Seeker(script) End Point Snapping to “Node Center (Snap To Node)”
Unity_GeDTK3feDn

and set player as the target in AI Destination Setter(script)
Unity_qx5ARuWZY5

AI will walk to center of the node which is the closest one to player’s position right?

Instead I want to set the destination to be any nodes on the blue area instead of nodes in red area, so the AI will be on the same z-index (next to the player) before strike the player

Yes

What you ask for is not quite possible with the default code. You could use a MultiTargetPath and specify the targets as one node on the left of the player, and one node on the right of the player.

Thanks! I’m having a look at the Class document and the example right now. I’ll come back to this forum, if I encounter any problems.

Hi, its been a while ,I hoped, I can still contact you through this forum.
this might not be very easy to read, but just in case.

public class ChaseBehavior : UnitBehavior{
		//--------------------------------------------------------------------------------------------------------------
		// INSPECTOR
		//--------------------------------------------------------------------------------------------------------------
		#if ODIN_INSPECTOR && UNITY_EDITOR
		[TabGroup("Status")]
		[ReadOnly]
		#endif
		public Actor LastTarget;
		
		#if ODIN_INSPECTOR && UNITY_EDITOR
		[TabGroup("Options")]
		#endif
		public Actor Target;
		
		#if ODIN_INSPECTOR && UNITY_EDITOR
		[TabGroup("Options")]
		[MinValue(0)]
		#endif
		public float ChaseDistance;

		//--------------------------------------------------------------------------------------------------------------
		#region MEMBERS
		private Seeker   seeker;
		private IAstarAI ai;
		private Vector3  targetPos;
		
		#endregion
		
		//--------------------------------------------------------------------------------------------------------------
		// SubState INTERFACE
		//--------------------------------------------------------------------------------------------------------------
		public override void OnStateValidation(){
			base.OnStateValidation();
			Behavior = EUnitBehavior.Chasing;
		}

		public override void OnStateEnter(){
			base.OnStateEnter();
			seeker = Owner.GetComponent<Seeker>();
			ai = Owner.GetComponent<IAstarAI>();
			ai.onSearchPath += OnSearchingPath;
			if (!Owner.CanWalk && !Owner.CanSprint)
			{ BackToDefaultBehavior($"{UnitOwner.UnitType}", "has entered ChaseBehavior but can't walk or sprint"); }
			
			if (Target == null){
				if (UnitOwner.Detecter != null){
					if (UnitOwner.Detecter.ClosestTarget != null){
						Target = UnitOwner.Detecter.ClosestTarget;
						if (Target == null)
							BackToDefaultBehavior($"{UnitOwner.UnitType}", "has entered ChaseBehavior but can't find any target");
					}
					else
					{ BackToDefaultBehavior($"{UnitOwner.UnitType}", "has entered ChaseBehavior but detecter module can't find any target"); }
				}
				else
				{ BackToDefaultBehavior($"{UnitOwner.UnitType}", "has no target and detecter module"); }
			}
		}

		public override void OnStateUpdate(){
			base.OnStateUpdate();
			Target = UnitOwner.Detecter.ClosestTarget;
			if (Target == null)
				BackToDefaultBehavior($"{UnitOwner.UnitType}", "has lost target while chasing");
			FindPathToTarget();
		}

		public override void OnStateExit(){
			base.OnStateExit();
			ai.onSearchPath -= OnSearchingPath;
			if (Target != null){
				LastTarget = Target;
				Target = null;
			}
		}
		
		//--------------------------------------------------------------------------------------------------------------
		// VIRTUAL METHODS
		//--------------------------------------------------------------------------------------------------------------
		public void OnSearchingPath(){
			ai.destination = targetPos;
		}
		
		public virtual void OnChasingPathComplete (Path path){
			Debug.Log("[Callback] OnChasingPathComplete");
			if (path.error) {
				Debug.Log("Ouch, the path returned an error\nError: " + path.errorLog);
				return;
			}

			if (path is not MultiTargetPath mp) {
				Debug.LogError("The Path was no MultiTargetPath");
				return;
			}

			// All paths
			var paths = mp.vectorPaths;
			for (var i = 0; i < paths.Length; i++) {
				var p = paths[i];
				if (p == null) {
					Debug.Log("Path number "+i+" could not be found");
					continue;
				}

				var color = AstarMath.IntToColor(i, 0.5F);
				for (var j = 0; j < p.Count-1; j++) 
					Debug.DrawLine(p[j], p[j+1], color, 10);
			}
		}
		
		//--------------------------------------------------------------------------------------------------------------
		// METHODS
		//--------------------------------------------------------------------------------------------------------------
		public void SetTarget(Actor target)
			=> Target = target;

		private void FindPathToTarget(){
			if (Target == null)
				return;
			var cameraBackwardDir = Vector3.ProjectOnPlane(CameraHandler.ActiveVirtualCamera.VirtualCameraGameObject.transform.forward, Vector3.up).normalized;
			var sides = new Vector3[2];
			var right = Vector3.Cross(Vector3.up,       cameraBackwardDir);
			var left = Vector3.Cross(cameraBackwardDir, Vector3.up);
			sides[0] = Target.transform.position + right * ChaseDistance;
			sides[1] = Target.transform.position + left * ChaseDistance;
			targetPos = seeker.StartMultiTargetPath(transform.position, sides, true, OnChasingPathComplete).endPoint;	
		}
	}

This is what I’ve done so far, I’ll chunk down some part of the code, so you don’t have to read it all.

ai.onSearchPath += OnSearchingPath; // WhenEnterState
ai.onSearchPath -= OnSearchingPath; // WhenExitState
public void OnSearchingPath(){
	ai.destination = targetPos;
}
// Called in Update
private void FindPathToTarget(){
	if (Target == null)
		return;
	var cameraBackwardDir = Vector3.ProjectOnPlane(CameraHandler.ActiveVirtualCamera.VirtualCameraGameObject.transform.forward, Vector3.up).normalized;
	var sides = new Vector3[2];
	var right = Vector3.Cross(Vector3.up,       cameraBackwardDir);
	var left = Vector3.Cross(cameraBackwardDir, Vector3.up);
	sides[0] = Target.transform.position + right * ChaseDistance;
	sides[1] = Target.transform.position + left * ChaseDistance;
	targetPos = seeker.StartMultiTargetPath(transform.position, sides, true, OnChasingPathComplete).endPoint;	
}
public virtual void OnChasingPathComplete (Path path){
	Debug.Log("[Callback] OnChasingPathComplete");
	if (path.error) {
		Debug.Log("Ouch, the path returned an error\nError: " + path.errorLog);
		return;
	}

	if (path is not MultiTargetPath mp) {
		Debug.LogError("The Path was no MultiTargetPath");
		return;
	}

	// All paths
	var paths = mp.vectorPaths;
	for (var i = 0; i < paths.Length; i++) {
		var p = paths[i];
		if (p == null) {
			Debug.Log("Path number "+i+" could not be found");
			continue;
		}

		var color = AstarMath.IntToColor(i, 0.5F);
		for (var j = 0; j < p.Count-1; j++) 
			Debug.DrawLine(p[j], p[j+1], color, 10);
	}
}

[Short Explain] This class is a state in my statemachine which Unit will chase down player till it reach player which are why I put FindPathToTarget() inside Update Function

[QUESTION#1] Is it bad to put FindPathToTarget() inside an update function?

My goal is to make it chase down player and change to another behavior on reached, So what I’m thinking is

[QUESTION#2] Is How I Call a function on reached Endpoint in ai.onSearchPath the right call?

The unit still don’t move along the path and there is a bit of problem :frowning:
This is how it’s look like now : 2024-04-09 22-52-36.mp4 - Google Drive

Yes. You should at least wait until the previous path calculation is done using seeker.IsDone().

Maybe you want ai.reachedDestination instead?

Wait could it be that my unit doesn’t walk to endpoint because it has rigidbody instead of charactercontroller?

I couldn’t upload my 8mb .gif for some reason, please take a look at the drive link instead, if you don’t mind.

Edit: I set Recalculate Path Automatically on AIPath and the problem in the video went away. Now my only problem is it still doesn’t move even I set the destination somehow.

To make the agents move, you shouldn’t use seeker.StartMultiTargetPath. That’s for when you are writing your own movement script. Instead use ai.SetPath(MultiTargetPath.Construct(...)), to make the agent aware of the path. You will want to disable the agent’s own path recalculations for this.

1 Like