//the PrepareMove method prepares the movement of a list of units by sorting them based on their radius, distance to target and generating target positions public void PrepareMove(List <Unit> units, Vector3 destination, float offsetRadius, Entity target, InputMode targetMode, bool playerCommand, bool attackMovement = false, AttackModes attackMode = AttackModes.none, FactionEntity targetEntity = null) { if (units.Count == 0) { Debug.LogError("[MovementManager] Attempting to move an empty list of units is not allowed!"); return; } if (!RTSHelper.IsPlayerFaction(units[0])) //if the source unit is not part of the player's faction then, override the playerCommand value to false even if it was set to true { playerCommand = false; } //create a new chained sorted list where units will be sorted based on their radius or stopping distance towards the target unit/building (in case of an attack) ChainedSortedList <float, Unit> radiusSortedUnits = SortUnits(units, attackMovement ? UnitSortTypes.attackRange : UnitSortTypes.radius, offsetRadius, attackMode, targetEntity, destination, playerCommand); AudioClip movementOrderAudio = null; //we'll just pick one of the movement order audios from the units list if (playerCommand && (!targetEntity || attackMovement == false)) //if this is a player command and it's not an attack movement with an assigned target { SpawnTargetEffect(!attackMovement, destination); } foreach (KeyValuePair <float, List <Unit> > currUnits in radiusSortedUnits) //for each different list of units that share the same radius { float currOffsetRadius = 0.0f; //for attack movement offset if (attackMovement == true) //if this is an attack movement { currOffsetRadius += currUnits.Key; //increment the offset radius by the attack range (which is represented by the key in case) } //sort units in ascending order of the distance to the target position currUnits.Value.Sort((x, y) => Vector3.Distance(x.transform.position, destination).CompareTo(Vector3.Distance(y.transform.position, destination))); UnitMovement movementRef = currUnits.Value[0].MovementComp; //reference movement component Vector3 unitsDirection = Vector3.zero; //direction of units regarding the target position foreach (Unit unit in currUnits.Value) { unitsDirection += (destination - unit.transform.position).normalized; } unitsDirection /= currUnits.Value.Count; Formations currentFormation = GetFormation(currUnits.Value[0], attackMovement); //current movement formation for this units group List <Vector3> targetPositions = GenerateTargetPositions(currentFormation, destination, currUnits.Value.Count, movementRef.GetAgentRadius(), movementRef.GetAgentAreaMask(), movementRef.CanFly(), ref currOffsetRadius, unitsDirection); //get the target positions for the current units list for (int i = 0; i < currUnits.Value.Count; i++) //go through the sorted units list { if (targetPositions.Count == 0) //no more target positions available? { if (playerCommand == true && currUnits.Value[i].FactionID == GameManager.PlayerFactionID) //if this is a player command then inform the player { ErrorMessageHandler.OnErrorMessage(ErrorMessage.targetPositionNotFound, currUnits.Value[i]); } continue; } switch (currentFormation) { case Formations.circular: if (attackMovement == true) //only pick the closest target position in case this is an attack movement type and we're using the circular formation { targetPositions.Sort((x, y) => Vector3.Distance(x, currUnits.Value[i].transform.position).CompareTo(Vector3.Distance(y, currUnits.Value[i].transform.position))); //sort the target positions list depending on the distance to the actual unit to move } break; case Formations.rectangular: //if this is a rectangular formation, then the closest unit always gets to the farthest position targetPositions.Sort((x, y) => Vector3.Distance(y, currUnits.Value[i].transform.position).CompareTo(Vector3.Distance(x, currUnits.Value[i].transform.position))); //sort the target positions list depending on the distance to the actual unit to move break; } if (movementOrderAudio == null) //if the movement order audio hasn't been set yet { movementOrderAudio = attackMovement ? currUnits.Value[i].AttackComp.GetOrderAudio() : (!currUnits.Value[i].MovementComp.IsMoving() ? currUnits.Value[i].MovementComp.GetMvtOrderAudio() : null); //set it. } EnqueueMovementTask(new MovementTask { unit = currUnits.Value[i], targetPosition = targetPositions[0], lookAtPosition = targetPositions[0] + unitsDirection, target = target, targetMode = targetMode, attack = attackMovement, attackTarget = targetEntity, attackTargetPosition = destination, }); //add a new movement task to the movement queue targetPositions.RemoveAt(0); } } if (playerCommand) //if this is a player command { AudioManager.Play(gameMgr.GetGeneralAudioSource(), movementOrderAudio, false); //play the movement order audio } }
//the PrepareMove method prepares the movement of a single unit public void PrepareMove(Unit unit, Vector3 destination, float offsetRadius, Entity target, InputMode targetMode, bool playerCommand, bool attackMovement = false, AttackModes attackMode = AttackModes.none, FactionEntity targetEntity = null) { if (!RTSHelper.IsPlayerFaction(unit)) //if the source unit is not part of the player's faction then, override the playerCommand value to false even if it was set to true { playerCommand = false; } if (!unit.MovementComp.CanMove()) //if the unit can't move and this is no attack movement { if (attackMovement) //if this is an attack movement, just check if the target is in range or not { LaunchNonMovementAttackLocal(unit, targetEntity, playerCommand); return; } else { if (playerCommand == true) //player notification message { ErrorMessageHandler.OnErrorMessage(ErrorMessage.notMovable, unit); } unit.MovementComp.OnInvalidPath(true, false); return; //nothing to do here } } if (attackMovement == true) //if this is an attack movement { //change the target? bool canChangeTarget = (attackMode == AttackModes.full || attackMode == AttackModes.change) ? true : false; //assigned attack only? bool assignedAttackOnly = (attackMode == AttackModes.full || attackMode == AttackModes.assigned) ? true : false; if (CanProceedAttackMovement(unit, playerCommand, assignedAttackOnly, canChangeTarget, targetEntity) == false) //do not proceed if the attack is not possible { return; } offsetRadius += unit.AttackComp.GetRange().GetStoppingDistance(targetEntity); //update sort key if (Vector3.Distance(unit.transform.position, destination) < offsetRadius) //if the attack unit is already inside its target range { //attack directly EnqueueMovementTask(new MovementTask { unit = unit, targetPosition = unit.transform.position, lookAtPosition = destination, target = target, targetMode = InputMode.attack, attack = true, attackTarget = targetEntity, attackTargetPosition = destination }); //add a new movement task to the movement queue to attack directly return; //move to next unit } } unit.MovementComp.TriggerTargetPositionCollider(false); //disable the target position collider so it won't intefer in the target position collider if (playerCommand && (!targetEntity || attackMovement == false)) //if this is a player command and it's not an attack movement with an assigned target { SpawnTargetEffect(!attackMovement, destination); } Vector3 originalDestination = destination; //save the original destination here //first check if the actual destination is a valid target position, if it can't be then search for a target position if (offsetRadius > 0.0f || IsPositionClear(ref destination, unit.MovementComp.GetAgentRadius(), unit.MovementComp.GetAgentAreaMask(), unit.MovementComp.CanFly()) == false) { List <Vector3> targetPositions = GenerateTargetPositions(Formations.circular, //it doesn't really matter what formation to use for one unit destination, 1, unit.MovementComp.GetAgentRadius(), unit.MovementComp.GetAgentAreaMask(), unit.MovementComp.CanFly(), ref offsetRadius, Vector3.zero); //get the target positions for the current units list if (targetPositions.Count == 0) //no target positions found? { if (playerCommand == true) //if this is a player command then inform the player { ErrorMessageHandler.OnErrorMessage(ErrorMessage.targetPositionNotFound, unit); } unit.CancelJob(Unit.jobType.all); //cancel all the unit's jobs. return; } targetPositions.Sort((x, y) => Vector3.Distance(x, unit.transform.position).CompareTo(Vector3.Distance(y, unit.transform.position))); //sort the target positions list depending on the distance to the actual unit to move destination = targetPositions[0]; //get the nearest target position } if (playerCommand && !unit.MovementComp.IsMoving()) //if this is a player command and the unit is not currently moving. { AudioManager.Play(gameMgr.GetGeneralAudioSource(), attackMovement == true ? unit.AttackComp.GetOrderAudio() : unit.MovementComp.GetMvtOrderAudio(), false); //play the movement order audio } EnqueueMovementTask(new MovementTask { unit = unit, targetPosition = destination, lookAtPosition = originalDestination, target = target, targetMode = targetMode, attack = attackMovement, attackTarget = targetEntity, attackTargetPosition = originalDestination, }); //add a new movement task to the movement queue. }