public float GetProposedHeight(IUnitFacade unit, CellMatrix matrix, Vector3 velocityNormal) { var heightMap = HeightMapManager.instance.GetHeightMap(unit.position); var lookAhead = heightMap.granularity; var unitRadius = unit.radius; var offsetFactor = (unitRadius + lookAhead); var perpOffset = new Vector3(-velocityNormal.z * offsetFactor, 0f, velocityNormal.x * offsetFactor); var center = unit.position; var backLeft = center - (velocityNormal * unitRadius) - perpOffset; var backRight = center - (velocityNormal * unitRadius) + perpOffset; var frontLeft = center + (velocityNormal * (unitRadius + lookAhead)) - perpOffset; var frontRight = center + (velocityNormal * (unitRadius + lookAhead)) + perpOffset; _heightCandidates[0] = heightMap.SampleHeight(backLeft); _heightCandidates[1] = heightMap.SampleHeight(backRight); _heightCandidates[2] = heightMap.SampleHeight(frontLeft); _heightCandidates[3] = heightMap.SampleHeight(frontRight); _heightCandidates[4] = heightMap.SampleHeight(center); var maxHeight = Mathf.Max(_heightCandidates); //Ground pos cannot be less than actual ground level. The reason it can be higher is if the unit is in free fall, in which case gravity will handle the rest. var groundPos = Mathf.Max(center.y - unit.baseToPositionOffset, _heightCandidates[4]); var diff = maxHeight - groundPos; return(diff <= unit.heightNavigationCapability.maxClimbHeight ? maxHeight + unit.baseToPositionOffset : _heightCandidates[4] + unit.baseToPositionOffset); }
/// <summary> /// Gets the desired steering output. /// </summary> /// <param name="input">The steering input containing relevant information to use when calculating the steering output.</param> /// <param name="output">The steering output to be populated.</param> public override void GetDesiredSteering(SteeringInput input, SteeringOutput output) { _selfCollisionPos = Vector3.zero; _lastAvoidVector = Vector3.zero; _lastAvoidPos = Vector3.zero; var otherUnits = _scanner.units; int othersCount = otherUnits.count; if (othersCount == 0) { // if the scanner has found no units to avoid, exit return; } _unitData = input.unit; Vector3 avoidVector = Avoid(otherUnits, othersCount, input.currentPlanarVelocity); if (avoidVector.sqrMagnitude < (this.minimumAvoidVectorMagnitude * this.minimumAvoidVectorMagnitude)) { // if the computed avoid vector's magnitude is less than the minimumAvoidVectorMagnitude, then discard it return; } // apply the avoidance force as a full deceleration capped force (not over time) Vector3 steeringVector = Vector3.ClampMagnitude(avoidVector / Time.fixedDeltaTime, input.maxDeceleration); _lastAvoidVector = steeringVector; output.desiredAcceleration = steeringVector; }
public float GetProposedHeight(IUnitFacade unit, CellMatrix matrix, Vector3 velocityNormal) { var radius = unit.radius + _lookAhead; var start = unit.basePosition; var baseHeight = start.y; var maxClimb = unit.heightNavigationCapability.maxClimbHeight; //Put the start at the unit's head or max climb above its center start.y = matrix != null ? matrix.origin.y + matrix.upperBoundary : start.y + Mathf.Max(unit.height, radius + maxClimb); RaycastHit hit; if (!Physics.SphereCast(start, radius, Vector3.down, out hit, Mathf.Infinity, Layers.terrain)) { return Consts.InfiniteDrop; } var sampledHeight = hit.point.y; var diff = sampledHeight - baseHeight; if (diff <= maxClimb) { return sampledHeight + unit.baseToPositionOffset; } return SampleHeight(unit.position, matrix) + unit.baseToPositionOffset; }
public ApiController(IUnitGroupFacade unitGroupFacade, IUnitFacade unitFacade, IColorFacade colorFacade, IUnitTypeFacade unitTypeFacade) { _unitGroupFacade = unitGroupFacade; _unitFacade = unitFacade; _colorFacade = colorFacade; _unitTypeFacade = unitTypeFacade; }
private void StartPendle(IUnitFacade steerer) { steerer.DisableMovementOrders(); steerer.Wait(null); var curParent = steerer.transform.parent; var rb = steerer.gameObject.GetComponent <Rigidbody>(); if (this.isOnboardTrigger) { rb.isKinematic = true; steerer.transform.parent = pendler.transform; } pendler.MoveTo( this.movesTo, () => { if (this.isOnboardTrigger) { steerer.transform.parent = curParent; rb.isKinematic = false; } else { steerer.EnableMovementOrders(); } steerer.Resume(); }); }
/// <summary> /// Convenience method for telling this unit to start solo pathing along a given path. /// </summary> /// <param name="path">The path.</param> /// <param name="unit">The unit.</param> private void FollowPath(Path path, IUnitFacade unit) { unit.MoveAlong(path); _requestedPath = false; StartSoloPath(); }
public float GetProposedHeight(IUnitFacade unit, CellMatrix matrix, Vector3 velocityNormal) { var radius = unit.radius + _lookAhead; var start = unit.basePosition; var baseHeight = start.y; var maxClimb = unit.heightNavigationCapability.maxClimbHeight; //Put the start at the unit's head or max climb above its center start.y = matrix != null ? matrix.origin.y + matrix.upperBoundary : start.y + Mathf.Max(unit.height, radius + maxClimb); RaycastHit hit; if (!Physics.SphereCast(start, radius, Vector3.down, out hit, Mathf.Infinity, Layers.terrain)) { return(Consts.InfiniteDrop); } var sampledHeight = hit.point.y; var diff = sampledHeight - baseHeight; if (diff <= maxClimb) { return(sampledHeight + unit.baseToPositionOffset); } return(SampleHeight(unit.position, matrix) + unit.baseToPositionOffset); }
/// <summary> /// Toggles the selected state of a unit. Actually it only toggles when <paramref name="append"/> is true, otherwise it will select the unit while deselecting all others. /// </summary> /// <param name="unit">The unit.</param> /// <param name="append">if set to <c>true</c> [append].</param> public void ToggleSelected(IUnitFacade unit, bool append) { //So this works differently depending on the append modifier. If append then the unit is toggled, if not the unit is not toggled but always selected while all others aren't. if (!append || _selected == _emptyGroup) { DeselectAll(); unit.isSelected = true; _selected = GroupingManager.CreateGrouping(unit); return; } unit.isSelected = !unit.isSelected; if (unit.isSelected) { _selected.Add(unit); } else { _selected.Remove(unit); } PostUnitsSelectedMessage(_selected); }
/// <summary> /// Unregisters the a unit as selectable. /// </summary> /// <param name="unit">The unit.</param> public void UnregisterSelectable(IUnitFacade unit) { //The unit will be removed from its transient group so we do not need to do that here unit.isSelected = false; _selectableUnits.Remove(unit); //No need to create an enumerator if the groups dict is empty if (_groups.Count > 0) { foreach (var grpEntry in _groups) { grpEntry.Value.Remove(unit); if (grpEntry.Value.memberCount == 0) { _removalBuffer.Add(grpEntry.Key); } } for (int i = 0; i < _removalBuffer.Count; i++) { _groups.Remove(_removalBuffer[i]); } _removalBuffer.Clear(); } }
/// <summary> /// Stops the unit's patrol. /// </summary> /// <param name="unit">The unit.</param> public static void StopPatrol(this IUnitFacade unit) { PatrolClient client; if (!_clients.TryGetValue(unit.gameObject, out client)) { if (!_pausedClients.TryGetValue(unit.gameObject, out client)) { return; } } _clients.Remove(unit.gameObject); _pausedClients.Remove(unit.gameObject); if (_clients.Count == 0) { _handler.Stop(); _handler = null; } if (unit.isAlive) { client.Stop(); } }
public float GetProposedHeight(IUnitFacade unit, CellMatrix matrix, Vector3 velocityNormal) { var heightMap = HeightMapManager.instance.GetHeightMap(unit.position); var lookAhead = heightMap.granularity; var unitRadius = unit.radius; var offsetFactor = (unitRadius + lookAhead); var perpOffset = new Vector3(-velocityNormal.z * offsetFactor, 0f, velocityNormal.x * offsetFactor); var center = unit.position; var backLeft = center - (velocityNormal * unitRadius) - perpOffset; var backRight = center - (velocityNormal * unitRadius) + perpOffset; var frontLeft = center + (velocityNormal * (unitRadius + lookAhead)) - perpOffset; var frontRight = center + (velocityNormal * (unitRadius + lookAhead)) + perpOffset; _heightCandidates[0] = heightMap.SampleHeight(backLeft); _heightCandidates[1] = heightMap.SampleHeight(backRight); _heightCandidates[2] = heightMap.SampleHeight(frontLeft); _heightCandidates[3] = heightMap.SampleHeight(frontRight); _heightCandidates[4] = heightMap.SampleHeight(center); var maxHeight = Mathf.Max(_heightCandidates); //Ground pos cannot be less than actual ground level. The reason it can be higher is if the unit is in free fall, in which case gravity will handle the rest. var groundPos = Mathf.Max(center.y - unit.baseToPositionOffset, _heightCandidates[4]); var diff = maxHeight - groundPos; return diff <= unit.heightNavigationCapability.maxClimbHeight ? maxHeight + unit.baseToPositionOffset : _heightCandidates[4] + unit.baseToPositionOffset; }
/// <summary> /// Gets the formation position of this particular formation type. Meant to be used in a loop to generate formation positions for an entire group. /// </summary> /// <param name="count">The group's member count.</param> /// <param name="formationIndex">The unit's formation index - i.e. index in the group. Must never be below 0 if meant to be valid.</param> /// <param name="unit">The unit to calculate this particular formation position for.</param> /// <returns> /// A single formation position based on the count, index and the particular unit. /// </returns> public Vector3 GetFormationPosition(int count, int formationIndex, IUnitFacade unit) { float forward = Mathf.Abs((count * 0.5f) - formationIndex) * -0.5f * _formationSpacing; float right = ((count * 0.5f) - formationIndex) * -_formationSpacing; return(new Vector3(right, 0f, forward)); }
/// <summary> /// Called on Awake /// </summary> protected override void Awake() { base.Awake(); this.WarnIfMultipleInstances(); _transform = this.transform; _wayPoints = new SimpleQueue <Vector3>(); _pathboundWayPoints = new SimpleQueue <Vector3>(); _navMessage = new UnitNavigationEventMessage(this.gameObject); _resultProcessors = this.GetComponents <SteerForPathResultProcessorComponent>(); Array.Sort(_resultProcessors, (a, b) => a.processingOrder.CompareTo(b.processingOrder)); _unit = this.GetUnitFacade(); _pathSettings = _unit.pathNavigationOptions; if (this.arrivalDistance > _pathSettings.nextNodeDistance) { Debug.LogError("The Arrival Distance must be equal to or less that the Next Node Distance."); } _stopped = true; _unit.hasArrivedAtDestination = true; }
private void Awake() { _radius = this.GetComponent <SphereCollider>().radius; //This could be more elegantly made, but will suffice for this example _workerModel = FindObjectOfType <PickupWorker>().GetUnitFacade(); }
/// <summary> /// Gets the formation position of this particular formation type. Meant to be used in a loop to generate formation positions for an entire group. /// </summary> /// <param name="count">The group's member count.</param> /// <param name="formationIndex">The unit's formation index - i.e. index in the group. Must never be below 0 if meant to be valid.</param> /// <param name="unit">The unit to calculate this particular formation position for.</param> /// <returns> /// A single formation position based on the count, index and the particular unit. /// </returns> public Vector3 GetFormationPosition(int count, int formationIndex, IUnitFacade unit) { float forward = Mathf.Abs((count * 0.5f) - formationIndex) * -0.5f * _formationSpacing; float right = ((count * 0.5f) - formationIndex) * -_formationSpacing; return new Vector3(right, 0f, forward); }
private void SpawnUnit() { var go = Instantiate(this.unitMold, this.spawnPosition, Quaternion.identity) as GameObject; go.SetActive(true); _unit = go.GetUnitFacade(); }
private void Start() { _unit = this.GetUnitFacade(); if (_master != null) { _master.RegisterWorker(this); } }
public float GetProposedHeight(IUnitFacade unit, CellMatrix matrix, Vector3 velocityNormal) { if (matrix != null) { return matrix.origin.y + unit.baseToPositionOffset; } return unit.position.y; }
protected override string OnInit() { unit = (agent as Component).GetUnitFacade(); if (unit == null) { return("Unable to create Unit Facade"); } return(null); }
public float GetProposedHeight(IUnitFacade unit, CellMatrix matrix, Vector3 velocityNormal) { if (matrix != null) { return(matrix.origin.y + unit.baseToPositionOffset); } return(unit.position.y); }
/// <summary> /// Creates a path request for the unit. /// </summary> /// <param name="unit">The unit.</param> /// <param name="to">The destination.</param> /// <param name="callback">TThe callback to be called when the result is ready.</param> /// <returns>A path request for the unit.</returns> public static IPathRequest CreatePathRequest(this IUnitFacade unit, Vector3 to, Action <PathResult> callback) { return(new CallbackPathRequest(callback) { from = unit.position, to = to, requesterProperties = unit, pathFinderOptions = unit.pathFinderOptions }); }
/// <summary> /// Gets the formation position of this particular formation type. Meant to be used in a loop to generate formation positions for an entire group. /// </summary> /// <param name="count">The group's member count.</param> /// <param name="formationIndex">The unit's formation index - i.e. index in the group. Must never be below 0 if meant to be valid.</param> /// <param name="unit">The unit to calculate this particular formation position for.</param> /// <returns> /// A single formation position based on the count, index and the particular unit. /// </returns> public Vector3 GetFormationPosition(int count, int formationIndex, IUnitFacade unit) { int columns = Mathf.CeilToInt(Mathf.Sqrt(count)); int rows = Mathf.FloorToInt(count / (float)columns); float column = (formationIndex % columns) - (columns * 0.5f); float row = Mathf.FloorToInt((formationIndex / columns) - (rows * 0.5f)); return(new Vector3((column * _formationSpacing), 0f, (-row * _formationSpacing))); }
/// <summary> /// Gets the formation position of this particular formation type. Meant to be used in a loop to generate formation positions for an entire group. /// </summary> /// <param name="count">The group's member count.</param> /// <param name="formationIndex">The unit's formation index - i.e. index in the group. Must never be below 0 if meant to be valid.</param> /// <param name="unit">The unit to calculate this particular formation position for.</param> /// <returns> /// A single formation position based on the count, index and the particular unit. /// </returns> public Vector3 GetFormationPosition(int count, int formationIndex, IUnitFacade unit) { int columns = Mathf.CeilToInt(Mathf.Sqrt(count)); int rows = Mathf.FloorToInt(count / (float)columns); float column = (formationIndex % columns) - (columns * 0.5f); float row = Mathf.FloorToInt((formationIndex / columns) - (rows * 0.5f)); return new Vector3((column * _formationSpacing), 0f, (-row * _formationSpacing)); }
void Awake() { _unitFacade = this.GetUnitFacade(); _apexUnit = this.As <IMovable> (); if (_dynamicObstacle != null) { setDynamicAINumber(); } }
/// <summary> /// Gets the formation position of this particular formation type. Meant to be used in a loop to generate formation positions for an entire group. /// </summary> /// <param name="count">The group's member count.</param> /// <param name="formationIndex">The unit's formation index - i.e. index in the group. Must never be below 0 if meant to be valid.</param> /// <param name="unit">The unit to calculate this particular formation position for.</param> /// <returns> /// A single formation position based on the count, index and the particular unit. /// </returns> public Vector3 GetFormationPosition(int count, int formationIndex, IUnitFacade unit) { float radius = count / (2f * Mathf.PI) * _formationRadius; float step = (2f * Mathf.PI) / count; float t = step * formationIndex; float x = Mathf.Cos(t); float z = Mathf.Sin(t); return new Vector3(x, 0f, z) * radius; }
/// <summary> /// Called on Start and OnEnable, but only one of the two, i.e. at startup it is only called once. /// </summary> protected override void OnStartAndEnable() { _heightSampler = GameServices.heightStrategy.heightSampler; if (_unit == null) { _unit = this.GetUnitFacade(); } base.OnStartAndEnable(); }
/// <summary> /// Gets the formation position of this particular formation type. Meant to be used in a loop to generate formation positions for an entire group. /// </summary> /// <param name="count">The group's member count.</param> /// <param name="formationIndex">The unit's formation index - i.e. index in the group. Must never be below 0 if meant to be valid.</param> /// <param name="unit">The unit to calculate this particular formation position for.</param> /// <returns> /// A single formation position based on the count, index and the particular unit. /// </returns> public Vector3 GetFormationPosition(int count, int formationIndex, IUnitFacade unit) { float radius = count / (2f * Mathf.PI) * _formationRadius; float step = (2f * Mathf.PI) / count; float t = step * formationIndex; float x = Mathf.Cos(t); float z = Mathf.Sin(t); return(new Vector3(x, 0f, z) * radius); }
/// <summary> /// Gets the formation position of this particular formation type. Meant to be used in a loop to generate formation positions for an entire group. /// </summary> /// <param name="count">The group's member count.</param> /// <param name="formationIndex">The unit's formation index - i.e. index in the group. Must never be below 0 if meant to be valid.</param> /// <param name="unit">The unit to calculate this particular formation position for.</param> /// <returns> /// A single formation position based on the count, index and the particular unit. /// </returns> public Vector3 GetFormationPosition(int count, int formationIndex, IUnitFacade unit) { float radius = count / (2f * Mathf.PI) * _formationRadius; float i = (float)formationIndex / count; float angle = i * Mathf.PI * 2f; float x = Mathf.Cos(angle); float z = Mathf.Sin(angle); return new Vector3(x, 0f, z) * radius * (0.5f + i); }
/// <summary> /// Called on Start and OnEnable, but only one of the two, i.e. at startup it is only called once. /// </summary> protected override void OnStartAndEnable() { _heightSampler = GameServices.heightStrategy.heightSampler; if (_unit == null) { _unit = this.GetUnitFacade(); } base.OnStartAndEnable(); }
/// <summary> /// Gets the formation position of this particular formation type. Meant to be used in a loop to generate formation positions for an entire group. /// </summary> /// <param name="count">The group's member count.</param> /// <param name="formationIndex">The unit's formation index - i.e. index in the group. Must never be below 0 if meant to be valid.</param> /// <param name="unit">The unit to calculate this particular formation position for.</param> /// <returns> /// A single formation position based on the count, index and the particular unit. /// </returns> public Vector3 GetFormationPosition(int count, int formationIndex, IUnitFacade unit) { float radius = count / (2f * Mathf.PI) * _formationRadius; float i = (float)formationIndex / count; float angle = i * Mathf.PI * 2f; float x = Mathf.Cos(angle); float z = Mathf.Sin(angle); return(new Vector3(x, 0f, z) * radius * (0.5f + i)); }
private void Awake() { this.WarnIfMultipleInstances(); _unit = this.GetUnitFacade(); if (_unit == null) { Debug.LogError("WanderBehaviour requires a component that implements IMovable."); this.enabled = false; } }
/// <summary> /// Stops the unit from wandering. /// </summary> /// <param name="unit">The unit.</param> public static void StopWander(this IUnitFacade unit) { _clients.Remove(unit.gameObject); if (_clients.Count == 0) { _handler.Stop(); _handler = null; } unit.Stop(); }
/// <summary> /// Called on Start and OnEnable, but only one of the two, i.e. at startup it is only called once. /// </summary> protected override void OnStartAndEnable() { NavLoadBalancer.scanners.Add(this, this.scanInterval, false); _unitData = this.GetUnitFacade(); _units = new DynamicArray <IUnitFacade>(5); if (this.forecastDistance > this.scanRadius) { // forecast distance can never surpass the scan radius this.forecastDistance = this.scanRadius; } }
/// <summary> /// Removes the specified member from this steering group. /// Also disposes itself if the last member is removed. /// </summary> public override void Remove(IUnitFacade member) { base.Remove(member); if (this.count == 0) { // if the last member was just removed, the group should dispose of and cleanup itself CleanupGroup(); } else if (_currentFormation != null && _modelUnit.isAlive) { SetFormation(_currentFormation); } }
/// <summary> /// Called on Start /// </summary> protected override void Start() { base.Start(); //Get unit ref _unit = this.GetUnitFacade(); _steeringInput.unit = _unit; //Init other vars _lastFramePosition = _unit.position; _stopped = true; UpdateInput(); }
/// <summary> /// Adds the specified member to this steering group. /// Also initializes model unit-based settings, if they haven't already been initialized. /// </summary> public override void Add(IUnitFacade member) { base.Add(member); // if the group was created using the 'capacity' ctor overload, we must create model unit when the first unit is added if (_modelUnit == null) { CreateModelUnit(); InitializeModelUnitSettings(); _groupCog = member.position; _currentGrid = GridManager.instance.GetGrid(_groupCog); } }
/// <summary> /// Pauses the patrol. /// </summary> /// <param name="unit">The unit.</param> public static void PausePatrol(this IUnitFacade unit) { PatrolClient client; if (!_clients.TryGetValue(unit.gameObject, out client)) { return; } _clients.Remove(unit.gameObject); _pausedClients[unit.gameObject] = client; client.Stop(); }
/// <summary> /// Convenience method for queuing up path requests. /// </summary> /// <param name="fromPos">From position.</param> /// <param name="toPos">To position.</param> /// <param name="unitData">The unit UnitFacade.</param> /// <param name="callback">The path request callback.</param> private void QueuePathRequest(Vector3 fromPos, Vector3 toPos, IUnitFacade unitData, Action <PathResult> callback) { _requestedPath = true; // request a path... var req = new CallbackPathRequest(callback) { from = fromPos, to = toPos, requesterProperties = unitData, pathFinderOptions = unitData.pathFinderOptions, }; GameServices.pathService.QueueRequest(req, unitData.pathFinderOptions.pathingPriority); }
public float GetProposedHeight(IUnitFacade unit, CellMatrix matrix, Vector3 velocityNormal) { var unitRadius = unit.radius; var perpOffset = new Vector3(-velocityNormal.z * unitRadius, 0f, velocityNormal.x * unitRadius); var center = unit.position; _samplePoints[0] = center - (velocityNormal * unitRadius) - perpOffset; _samplePoints[1] = center - (velocityNormal * unitRadius) + perpOffset; _samplePoints[2] = center + (velocityNormal * (unitRadius + _lookAhead)) - perpOffset; _samplePoints[3] = center + (velocityNormal * (unitRadius + _lookAhead)) + perpOffset; _samplePoints[4] = center; float maxClimb = unit.heightNavigationCapability.maxClimbHeight; float rayStart = matrix != null ? matrix.origin.y + matrix.upperBoundary : center.y + maxClimb; float sampledHeight = Consts.InfiniteDrop; float maxHeight = Consts.InfiniteDrop; RaycastHit hit; for (int i = 0; i < 5; i++) { _samplePoints[i].y = rayStart; if (Physics.Raycast(_samplePoints[i], Vector3.down, out hit, Mathf.Infinity, Layers.terrain)) { sampledHeight = hit.point.y; if (sampledHeight > maxHeight) { maxHeight = sampledHeight; } } } var diff = maxHeight - unit.basePosition.y; //Since sampledHeight will be the height at the center we can just use that directly here in case the max height is too high. return diff <= maxClimb ? maxHeight + unit.baseToPositionOffset : sampledHeight + unit.baseToPositionOffset; }
/// <summary> /// Called on Start /// </summary> protected override void Start() { base.Start(); _scanner = this.GetComponent<SteeringScanner>(); if (_scanner == null) { Debug.LogError(this.gameObject.name + " is missing its SteeringScanner component"); } _unitData = this.GetUnitFacade(); float deg2Rad = Mathf.Deg2Rad; _fovReverseAngleCos = Mathf.Cos(((360f - _unitData.fieldOfView) / 2f) * deg2Rad); _omniAwareRadius = _unitData.radius * 2f; _cosAvoidAngle = Mathf.Cos(this.headOnCollisionAngle * deg2Rad); }
/// <summary> /// Convenience method for telling this unit to start solo pathing along a given path. /// </summary> /// <param name="path">The path.</param> /// <param name="unit">The unit.</param> private void FollowPath(Path path, IUnitFacade unit) { unit.MoveAlong(path); _requestedPath = false; StartSoloPath(); }
/// <summary> /// Called on Start and OnEnable, but only one of the two, i.e. at startup it is only called once. /// </summary> protected override void OnStartAndEnable() { NavLoadBalancer.scanners.Add(this, this.scanInterval, false); _unitData = this.GetUnitFacade(); _units = new DynamicArray<IUnitFacade>(5); if (this.forecastDistance > this.scanRadius) { // forecast distance can never surpass the scan radius this.forecastDistance = this.scanRadius; } }
/// <summary> /// Called on Awake /// </summary> protected override void Awake() { base.Awake(); this.WarnIfMultipleInstances(); _transform = this.transform; _wayPoints = new WaypointList(); _navMessage = new UnitNavigationEventMessage(this.gameObject); _resultProcessors = this.GetComponents<SteerForPathResultProcessorComponent>(); Array.Sort(_resultProcessors, (a, b) => a.processingOrder.CompareTo(b.processingOrder)); _unit = this.GetUnitFacade(); _pathSettings = _unit.pathNavigationOptions; _stopped = true; }
/// <summary> /// Gets the formation position of this particular formation type. Meant to be used in a loop to generate formation positions for an entire group. /// </summary> /// <param name="count">The group's member count.</param> /// <param name="formationIndex">The unit's formation index - i.e. index in the group. Must never be below 0 if meant to be valid.</param> /// <param name="unit">The unit to calculate this particular formation position for.</param> /// <returns> /// A single formation position based on the count, index and the particular unit. /// </returns> public Vector3 GetFormationPosition(int count, int formationIndex, IUnitFacade unit) { float half = (count * 0.5f); return new Vector3(0f, 0f, (formationIndex - half) * _formationSpacing); }
/// <summary> /// Called on start /// </summary> protected override void Start() { _unit = this.GetUnitFacade(); }
/// <summary> /// Gets the desired steering output. /// </summary> /// <param name="input">The steering input containing relevant information to use when calculating the steering output.</param> /// <param name="output">The steering output to be populated.</param> public override void GetDesiredSteering(SteeringInput input, SteeringOutput output) { _selfCollisionPos = Vector3.zero; _lastAvoidVector = Vector3.zero; _lastAvoidPos = Vector3.zero; var otherUnits = _scanner.units; int othersCount = otherUnits.count; if (othersCount == 0) { // if the scanner has found no units to avoid, exit return; } _unitData = input.unit; Vector3 avoidVector = Avoid(otherUnits, othersCount, input.currentPlanarVelocity); if (avoidVector.sqrMagnitude < (this.minimumAvoidVectorMagnitude * this.minimumAvoidVectorMagnitude)) { // if the computed avoid vector's magnitude is less than the minimumAvoidVectorMagnitude, then discard it return; } // apply the avoidance force as a full deceleration capped force (not over time) Vector3 steeringVector = Vector3.ClampMagnitude(avoidVector / Time.fixedDeltaTime, input.maxDeceleration); _lastAvoidVector = steeringVector; output.desiredAcceleration = steeringVector; }
/// <summary> /// Gets the desired steering output. /// </summary> /// <param name="input">The steering input containing relevant information to use when calculating the steering output.</param> /// <param name="output">The steering output to be populated.</param> public override void GetDesiredSteering(SteeringInput input, SteeringOutput output) { // Set the formation position to null to make sure that it is invalid when it's supposed to be input.unit.formationPos = null; _formationPos = null; // set instance variables to be used in load balanced update method _unitData = input.unit; _grid = input.grid; if (_grid == null) { // if not on a grid, drop formation return; } // must cast the group to have access to GetFormationPosition var group = _unitData.transientGroup as DefaultSteeringTransientUnitGroup; if (group == null) { // if not in a group, drop formation return; } int count = group.count; if (count == 0) { // if there are no members in the group, drop formation return; } var modelUnit = group.modelUnit; if (modelUnit == null) { // if group's model unit is missing, then drop formation return; } if (_unitData.formationIndex < 0) { // if this unit has not been given a valid formation index, then drop formation return; } if (_unitData.hasArrivedAtDestination && this.dropFormationOnArrival) { return; } _formationPos = group.GetFormationPosition(_unitData.formationIndex); if (_formationPos == null || _stopped) { // if there is no valid formation position calculated or it is invalid currently, then drop formation return; } // make sure desiredSpeed does not spill over or under var desiredSpeed = Mathf.Min(1.5f * input.desiredSpeed, _unitData.maximumSpeed); _unitData.formationPos = _formationPos; Vector3 arrivalVector = Arrive(_formationPos.position, input, desiredSpeed); if (modelUnit.hasArrivedAtDestination && this.hasArrived) { // When the unit arrives at the designated formation position, make sure hasArrivedAtDestination becomes true // Also, must return here, to avoid outputting a result _unitData.hasArrivedAtDestination = true; return; } output.maxAllowedSpeed = desiredSpeed; output.desiredAcceleration = arrivalVector; }
/// <summary> /// Convenience method for queuing up path requests. /// </summary> /// <param name="fromPos">From position.</param> /// <param name="toPos">To position.</param> /// <param name="unitData">The unit UnitFacade.</param> /// <param name="callback">The path request callback.</param> private void QueuePathRequest(Vector3 fromPos, Vector3 toPos, IUnitFacade unitData, Action<PathResult> callback) { _requestedPath = true; // request a path... var req = new CallbackPathRequest(callback) { from = fromPos, to = toPos, requesterProperties = unitData, pathFinderOptions = unitData.pathFinderOptions, }; GameServices.pathService.QueueRequest(req, unitData.pathFinderOptions.pathingPriority); }
/// <summary> /// Called on Start /// </summary> protected override void Start() { base.Start(); //Get unit ref _unit = this.GetUnitFacade(); _steeringInput.unit = _unit; //Init other vars _lastFramePosition = _unit.position; _stopped = true; UpdateInput(); }
private void CreateModelUnit() { GameObject unit = this[0].gameObject; GameObject modelUnitGO = new GameObject("Model Unit"); //Start out inactive so all components are added before life cycle begins, i.e. Awake, Start etc. modelUnitGO.SetActive(false); var collider = modelUnitGO.AddComponent<SphereCollider>(); collider.isTrigger = true; // make sure model unit is kinematic and does not respond to Unity Gravity var rigidbody = modelUnitGO.AddComponent<Rigidbody>(); rigidbody.isKinematic = true; rigidbody.useGravity = false; // now we need to copy and clone all the relevant components from the group's units to the new model unit (group leader) // copy and clone SteerableUnitComponent, if it exists and is enabled var steerableComponent = unit.GetComponent<SteerableUnitComponent>(); if (steerableComponent != null && steerableComponent.enabled) { var newSteerable = modelUnitGO.AddComponent<SteerableUnitComponent>(); newSteerable.CloneFrom(steerableComponent); } //copy and clone IHeightNavigator component var heightNav = unit.As<IHeightNavigator>(); if (heightNav != null) { var newNav = modelUnitGO.AddComponent(heightNav.GetType()) as IHeightNavigator; newNav.CloneFrom(heightNav); } // copy and clone IDefineSpeed component, if it exists and is enabled var speedComp = unit.As<IDefineSpeed>(); if (speedComp != null) { var newSpeedComp = modelUnitGO.AddComponent(speedComp.GetType()) as IDefineSpeed; newSpeedComp.CloneFrom(speedComp); } // copy and clone PathOptionsComponent, if it exists and is enabled var pathOptions = unit.GetComponent<PathOptionsComponent>(); if (pathOptions != null && pathOptions.enabled) { var newPathOptions = modelUnitGO.AddComponent<PathOptionsComponent>(); newPathOptions.CloneFrom(pathOptions); } // copy and clone SteerToAlignWithVelocity, if it exists and is enabled var steerToAlign = unit.GetComponent<SteerToAlignWithVelocity>(); if (steerToAlign != null && steerToAlign.enabled) { var newSteerToAlign = modelUnitGO.AddComponent<SteerToAlignWithVelocity>(); newSteerToAlign.CloneFrom(steerToAlign); } // copy and cone PathVisualizer, if it exists and is enabled var pathVisualizer = unit.GetComponent<PathVisualizer>(); if (pathVisualizer != null && pathVisualizer.enabled) { var newPathVisualizer = modelUnitGO.AddComponent<PathVisualizer>(); newPathVisualizer.CloneFrom(pathVisualizer); } // copy and clone SteerForPathComponent, if it exists and is enabled var steerForPath = unit.GetComponent<SteerForPathComponent>(); if (steerForPath != null && steerForPath.enabled) { var newSteerForPath = modelUnitGO.AddComponent<SteerForPathComponent>(); newSteerForPath.CloneFrom(steerForPath); } // copy and clone UnitComponent, if it exists and is enabled var unitComp = unit.GetComponent<UnitComponent>(); if (unitComp != null && unitComp.enabled) { var newUnitComponent = modelUnitGO.AddComponent<UnitComponent>(); newUnitComponent.CloneFrom(unitComp); newUnitComponent.isSelectable = false; newUnitComponent.selectionVisual = null; newUnitComponent.enabled = true; } // copy and clone SteerForVectorFieldComponent, if it exists and is enabled var steerForVectorField = unit.GetComponent<SteerForVectorFieldComponent>(); if (steerForVectorField != null && steerForVectorField.enabled) { var newSteerForVector = modelUnitGO.AddComponent<SteerForVectorFieldComponent>(); newSteerForVector.CloneFrom(steerForVectorField); newSteerForVector.arrivalRadiusMargin = 0f; newSteerForVector.arrivalDistance = 0.2f; } // copy and clone SteeringController, if it exists and is enabled var steeringController = unit.GetComponent<SteeringController>(); if (steeringController != null && steeringController.enabled) { modelUnitGO.AddComponent<SteeringController>(); } // copy Apex Component Master var componentMaster = unit.GetComponent<ApexComponentMaster>(); if (componentMaster != null && componentMaster.enabled) { modelUnitGO.AddComponent<ApexComponentMaster>(); } // active the model unit here at last - also make sure a UnitFacade is made and that it knows of this group modelUnitGO.SetActive(true); _modelUnit = modelUnitGO.GetUnitFacade(); _modelUnit.transientGroup = this; if (nextNodePosition.HasValue) { // if there is a path and the next node position is valid, then place the model unit at the same position as the nearest unit in the group compared to the next node position Vector3 nearestPos = Vector3.zero; float shortestDistance = float.MinValue; for (int i = 0; i < this.count; i++) { Vector3 pos = this[i].position; float distance = (pos - nextNodePosition.Value).sqrMagnitude; if (distance < shortestDistance) { shortestDistance = distance; nearestPos = pos; } } _modelUnit.transform.position = nearestPos; } else { // if there is no path or the next node position is not valid, just place model unit at the center of the group _modelUnit.transform.position = GetGroupCenterOfGravity(); } _modelUnit.transform.forward = this[0].forward; }
/// <summary> /// Removes the specified member from this steering group. /// Also disposes itself if the last member is removed. /// </summary> public override void Remove(IUnitFacade member) { base.Remove(member); if (this.count == 0) { // if the last member was just removed, the group should dispose of and cleanup itself CleanupGroup(); } else if (_currentFormation != null && _modelUnit.isAlive) { SetFormation(_currentFormation); } }
/// <summary> /// Gets the formation position of this particular formation type. Meant to be used in a loop to generate formation positions for an entire group. /// </summary> /// <param name="count">The group's member count.</param> /// <param name="formationIndex">The unit's formation index - i.e. index in the group. Must never be below 0 if meant to be valid.</param> /// <param name="unit">The unit to calculate this particular formation position for.</param> /// <returns> /// A single formation position based on the count, index and the particular unit. /// </returns> public Vector3 GetFormationPosition(int count, int formationIndex, IUnitFacade unit) { float start = (count - 1) * -0.5f * _formationSpacing; return new Vector3(start + (formationIndex * _formationSpacing), 0f, 0f); }
/// <summary> /// Gets the desired steering output. /// </summary> /// <param name="input">The steering input containing relevant information to use when calculating the steering output.</param> /// <param name="output">The steering output to be populated.</param> public override void GetDesiredSteering(SteeringInput input, SteeringOutput output) { _lastAvoidVector = Vector3.zero; var grid = input.grid; if (grid == null) { // if no grid is available, return return; } _unitData = input.unit; if (!_unitData.isGrounded) { // in the air, give up return; } Vector3 selfPos = _unitData.position; var selfCell = grid.GetCell(selfPos); if (selfCell == null || !selfCell.isWalkable(_unitData.attributes)) { // already within a blocked or missing cell, give up return; } // Find and avoid 4 neighbour cells - if they are blocked _neighbours.Clear(); _neighbours.Add(selfCell.GetNeighbour(-1, 0)); _neighbours.Add(selfCell.GetNeighbour(0, -1)); _neighbours.Add(selfCell.GetNeighbour(1, 0)); _neighbours.Add(selfCell.GetNeighbour(0, 1)); // the total radius is the unit's radius + the radius margin + half of the current grid's cell size float selfRadius = _unitData.radius + radiusMargin + (grid.cellSize / 2f); float selfRadiusSqr = selfRadius * selfRadius; Vector3 avoidVector = Vector3.zero; int neighboursCount = _neighbours.count; for (int i = 0; i < neighboursCount; i++) { var neighbourCell = _neighbours[i]; if (neighbourCell == null) { // ignore missing cells, this is not containment continue; } var dir = neighbourCell.position.DirToXZ(selfPos); float distanceSqr = dir.sqrMagnitude; if (distanceSqr < selfRadiusSqr && !neighbourCell.isWalkableFrom(selfCell, _unitData)) { // sum up a vector comprising of all avoid vectors avoidVector += dir; } } if (avoidVector.sqrMagnitude == 0f) { return; } // set the repulsion vector's magnitude to repulsionStrength Vector3 steeringVector = avoidVector.normalized * repulsionStrength; _lastAvoidVector = steeringVector; output.desiredAcceleration = steeringVector; }
/// <summary> /// Adds the specified member to this steering group. /// Also initializes model unit-based settings, if they haven't already been initialized. /// </summary> public override void Add(IUnitFacade member) { base.Add(member); // if the group was created using the 'capacity' ctor overload, we must create model unit when the first unit is added if (_modelUnit == null) { CreateModelUnit(); InitializeModelUnitSettings(); _groupCog = member.position; _currentGrid = GridManager.instance.GetGrid(_groupCog); } }
/// <summary> /// Gets an avoidance vector. /// </summary> /// <param name="selfPos">This unit's position.</param> /// <param name="currentVelocity">This unit's current velocity.</param> /// <param name="normalVelocity">This unit's normalized current velocity.</param> /// <param name="unitData">This unit's UnitFacade.</param> /// <param name="otherPos">The other unit's position.</param> /// <param name="otherVelocity">The other unit's velocity.</param> /// <param name="otherData">The other unit's UnitFacade.</param> /// <param name="combinedRadius">The combined radius.</param> /// <returns>An avoidance vector from the other unit's collision position to this unit's collision position - if a collision actually is detected.</returns> private Vector3 GetAvoidVector(Vector3 selfPos, Vector3 currentVelocity, Vector3 normalVelocity, IUnitFacade unitData, Vector3 otherPos, Vector3 otherVelocity, IUnitFacade otherData, float combinedRadius) { Vector3 avoidDirection = GetAvoidDirectionVector(selfPos, currentVelocity, otherPos, otherVelocity, combinedRadius); float avoidMagnitude = avoidDirection.magnitude; if (avoidMagnitude == 0f) { // if there is absolutely no magnitude to the found avoid direction, then ignore it return Vector3.zero; } float vectorLength = combinedRadius * 0.5f; if (vectorLength <= 0f) { // if the units' combined radius is 0, then we cannot avoid return Vector3.zero; } // normalize the avoid vector and then set it's magnitude to the desired vector length (half of the combined radius) Vector3 avoidNormalized = (avoidDirection / avoidMagnitude); Vector3 avoidVector = avoidNormalized * vectorLength; float dotAngle = Vector3.Dot(avoidNormalized, normalVelocity); if (dotAngle <= _cosAvoidAngle) { // the collision is considered "head-on", thus we compute a perpendicular avoid vector instead avoidVector = new Vector3(avoidVector.z, avoidVector.y, -avoidVector.x); } else if (preventPassingInFront && (otherData.determination > unitData.determination) && (Vector3.Dot(otherVelocity, avoidVector) > 0f && Vector3.Dot(currentVelocity, otherVelocity) >= 0f)) { // if supposed to be preventing front-passing, then check whether we should prevent it in this case and if so compute a different avoid vector avoidVector = _selfCollisionPos.DirToXZ(selfPos); } // scale the avoid vector depending on the distance to collision, shorter distances need larger magnitudes and vice versa float collisionDistance = Mathf.Max(1f, selfPos.DirToXZ(_selfCollisionPos).magnitude); avoidVector *= currentVelocity.magnitude / collisionDistance; return avoidVector; }
/// <summary> /// Called on Start and OnEnable, but only one of the two, i.e. at startup it is only called once. /// </summary> protected override void OnStartAndEnable() { base.OnStartAndEnable(); _unit = this.GetUnitFacade(); }
/// <summary> /// The path request callback method. /// </summary> /// <param name="result">The path result.</param> /// <param name="unit">This unit's UnitFacade.</param> /// <param name="unitPos">This unit's position.</param> /// <param name="destination">The destination.</param> /// <param name="grid">The grid.</param> private void PathRequestCallback(PathResult result, IUnitFacade unit, Vector3 unitPos, Vector3 destination, IGrid grid) { // if the normal path request fails... Action<PathResult> callback = (secondResult) => { if (secondResult.status == PathingStatus.Complete) { // add the unit's current position to the path var path = result.path; path.Push(unit); FollowPath(path, unit); return; } // if the second path request fails, there is nothing to do _stopped = true; }; // try to request a path from the nearest (from) walkable cell Cell fromCell = grid.GetNearestWalkableCell(unitPos, unitPos, true, 1, unit); if (fromCell != null) { QueuePathRequest(fromCell.position, destination, unit, callback); return; } // if that fails, search through all 8 neighbour cells and attempt to request a path from the nearest isWalkable neighbour int neighboursCount = _neighbours.count; for (int i = 0; i < neighboursCount; i++) { var neighbourCell = _neighbours[i]; if (neighbourCell.isWalkable(unit.attributes)) { QueuePathRequest(neighbourCell.position, destination, unit, callback); return; } } }
/// <summary> /// Initializes a new instance of the <see cref="DefaultSteeringTransientUnitGroup"/> class. /// </summary> /// <param name="members">The members.</param> public DefaultSteeringTransientUnitGroup(IUnitFacade[] members) : base(members) { Initialize(); }
/// <summary> /// Gets the desired steering output. /// </summary> /// <param name="input">The steering input containing relevant information to use when calculating the steering output.</param> /// <param name="output">The steering output to be populated.</param> public override void GetDesiredSteering(SteeringInput input, SteeringOutput output) { _lastContainVector = Vector3.zero; if (input.grid == null) { // if off-grid, exit early return; } _unitData = input.unit; if (!_unitData.isGrounded) { // while in the air, exit early return; } // prepare local variables Vector3 selfPos = _unitData.position; Vector3 containVector = Vector3.zero; float selfHeight = _unitData.basePosition.y; // generate positions to the left and right Vector3 rightPos = selfPos + (Vector3.right * bufferDistance); Vector3 leftPos = selfPos + (Vector3.left * bufferDistance); float rightHeight, leftHeight; // sample left and right bool rightContain = _heightSampler.TrySampleHeight(rightPos, out rightHeight); bool leftContain = _heightSampler.TrySampleHeight(leftPos, out leftHeight); // if cell is missing or drop is more than allowed drop height or climb is more than allowed climb height, then compute an axis-aligned containment vector if (!rightContain || (selfHeight - rightHeight > _heightCaps.maxDropHeight) || (rightHeight - selfHeight > _heightCaps.maxClimbHeight)) { containVector = Vector3.left; } else if (!leftContain || (selfHeight - leftHeight > _heightCaps.maxDropHeight) || (leftHeight - selfHeight > _heightCaps.maxClimbHeight)) { containVector = Vector3.right; } // generate positions forward and backwards Vector3 forwardPos = selfPos + (Vector3.forward * bufferDistance); Vector3 backwardPos = selfPos + (Vector3.back * bufferDistance); float forwardHeight, backHeight; // sample forward and backwards bool forwardContain = _heightSampler.TrySampleHeight(forwardPos, out forwardHeight); bool backContain = _heightSampler.TrySampleHeight(backwardPos, out backHeight); // if cell is missing or drop is more than allowed drop height or climb is more than allowed climb height, then compute an axis-aligned containment vector if (!forwardContain || (selfHeight - forwardHeight > _heightCaps.maxDropHeight) || (forwardHeight - selfHeight > _heightCaps.maxClimbHeight)) { // we need to check whether containVector has a value beforehand, in which case we need to normalize containVector = containVector.sqrMagnitude != 0f ? (containVector + Vector3.back).normalized : Vector3.back; } else if (!backContain || (selfHeight - backHeight > _heightCaps.maxDropHeight) || (backHeight - selfHeight > _heightCaps.maxClimbHeight)) { // we need to check whether containVector has a value beforehand, in which case we need to normalize containVector = containVector.sqrMagnitude != 0f ? (containVector + Vector3.forward).normalized : Vector3.forward; } if (containVector.sqrMagnitude == 0f) { // no contain vectors to worry about - no containment necessary return; } // Containment vectors are always "full strength" Vector3 steeringVector = containVector * input.maxAcceleration; _lastContainVector = steeringVector; output.desiredAcceleration = steeringVector; }
/// <summary> /// Handles portalling. /// </summary> /// <param name="unitData">This unit's UnitFacade.</param> /// <param name="group">This unit's current/old group.</param> /// <param name="vectorField">The vector field.</param> /// <param name="pathPortalIndex">Index of the portal in the current path.</param> /// <param name="grid">The grid.</param> private void HandlePortal(IUnitFacade unitData, DefaultSteeringTransientUnitGroup group, IVectorField vectorField, int pathPortalIndex, IGrid grid) { var portal = _currentPath[pathPortalIndex] as IPortalNode; if (portal == null) { // if the path node that was reported as a portal turns out not to be - return return; } if (object.ReferenceEquals(unitData, group.modelUnit)) { // don't ever let model unit jump portal return; } int groupCount = group.count; var to = _currentPath[pathPortalIndex + 1]; // we consider a portal a "far portal" when the distance between it and its partner is more than the diagonal cell size // 'far portal' means that it is NOT considered a grid stitching connector portal // Requires that grid stitching portals are always placed adjacent to each other float portalNewGroupThreshold = (grid.cellSize * Consts.SquareRootTwo) + 0.1f; bool isFarPortal = (portal.position - portal.partner.position).sqrMagnitude > (portalNewGroupThreshold * portalNewGroupThreshold); if (isFarPortal) { // if it is a far portal, we need to make or use the next group if (group.nextGroup == null) { // new group does not exist yet, so create it and tell the old group about it var groupStrat = GroupingManager.GetGroupingStrategy<IUnitFacade>(); if (groupStrat == null) { Debug.Log("No Grouping Strategy has been registered for IUnitFacade"); return; } var newGroup = groupStrat.CreateGroup(groupCount) as DefaultSteeringTransientUnitGroup; if (newGroup == null) { return; } group.nextGroup = newGroup; _nextGroup = newGroup; } else { // new group exists, so just use it _nextGroup = group.nextGroup; } // make sure to remove the unit from the old group group.Remove(unitData); } _isPortalling = true; // actually execute the portal portal.Execute( unitData.transform, to, () => { _isPortalling = false; if (isFarPortal) { // if it is a far portal, we are supposed to join up with the new group _nextGroup.Add(unitData); unitData.transientGroup = _nextGroup; if (_nextGroup.count == 1) { // let the first unit in the new group be responsible for setting the new group up if (vectorField.destination != null) { // the new group's path starts on the other side of the portal... int pathCount = _currentPath.count; var newPath = new Path(pathCount - (pathPortalIndex + 2)); for (int i = pathCount - 1; i >= pathPortalIndex + 2; i--) { newPath.Push(_currentPath[i]); } // the first member that joins the new group tells the new group to move along the path of the old group Vector3 destination = vectorField.destination.position; if ((to.position - destination).sqrMagnitude > 1f) { // check though that the destination is not the same as the starting position _nextGroup.MoveAlong(newPath); } // pass along old group's waypoints to new group if (group.currentWaypoints.count > 0) { _nextGroup.SetWaypoints(group.currentWaypoints); } // pass along old group's formation to the new group if (group.currentFormation != null) { _nextGroup.TransferFormation(group.currentFormation, groupCount, group); } } } } }); }