protected override bool _OnRegister(TorqueObject owner) { if (!base._OnRegister(owner)) return false; // keep track of the owning scene object _platform = owner as T2DSceneObject; // grab a reference to the owner's platform component _platformComponent = owner.Components.FindComponent<SolidPlatformComponent>(); Assert.Fatal(_platform != null && _platformComponent != null, "PlatformBehavior - Invalid owner or missing PlatformComponent on owner."); return true; }
/// <summary> /// Update whether or not the Actor is on some sort of "ground" (specifically, any type of platform). /// </summary> /// <param name="elapsed">Elapsed time passed from _postUpdate.</param> /// <returns>True if the Actor is standing on a ground surface. This value can also be accessed via the public OnGround property.</returns> private bool _updateOnGround(float elapsed) { // make sure we're alive if (!_alive) return false; // run pickGround _pickGround(elapsed); // init vars float groundMinY = 0; float groundLowestDot = 0; T2DSceneObject newGroundObject = null; SolidPlatformComponent newPlatformComponent = null; Vector2 platformVel = Vector2.Zero; float velMod = 0; // clear the active platforms list _activePlatforms.Clear(); // check pick results foreach (ISceneObject sobj in _containerQueryResults) { // skip if the object is not a platform object type if (!(sobj as T2DSceneObject).TestObjectType(PlatformerData.PlatformObjectType)) continue; // grab the platform component of this object SolidPlatformComponent platComp = (sobj as T2DSceneObject).Components.FindComponent<SolidPlatformComponent>(); // skip if it doesn't have a platform component // or it's platform component is marked as disabled if (platComp == null || !platComp.PlatformEnabled) continue; // do extra checks for normal one-way platforms if (platComp is OneWayPlatformComponent) { // skip this platform if we're jumping down if (!CanActivatePlatforms) continue; // skip this platform if the actor is climbing -and- it's possible to climb through this platform if (_Climbing && (platComp as OneWayPlatformComponent).CanClimbThrough) continue; } // do a poly check against this ground object _testGroundPolyMove(sobj as T2DSceneObject); // if there were no collisions with this object, skip it if (_groundCollisionList.Count == 0) continue; // chose a ground object and activate all platforms for (int i = 0; i < _groundCollisionList.Count; i++) { // skip vertical walls.. duh >_< if (_groundCollisionList[i].Normal.Y > _maxGroundNormalY) continue; // get the ground object's velocity if ((sobj as T2DSceneObject).Physics != null) platformVel = (sobj as T2DSceneObject).Physics.Velocity; else platformVel = Vector2.Zero; // get the dot product of the actor's velocity relative to the platform's velocity Vector2 ourVel = _actor.Physics.Velocity - platformVel; // normalize the velocity only if it's not zero //(this avoids "divide by zero" errors) if (ourVel != Vector2.Zero) ourVel.Normalize(); float dot = Vector2.Dot(ourVel, _groundCollisionList[i].Normal); // skip platforms we are moving away from // (allows us to move through one-ways! because we ignore them here they // are omitted from the active platforms list, which is checked in TestEarlyOut) if (dot > Epsilon.Value) continue; // if we are on a slope, make sure to find an exact ground contact position if (Math.Abs(_groundCollisionList[i].Normal.X) > Epsilon.Value) { // get the offset based on direction float directionOffset = 0; if (_groundCollisionList[i].Normal.X > 0) { if (_actor.FlipX) directionOffset = _ActorMaxX; else directionOffset = -_ActorMinX; } else { if (_actor.FlipX) directionOffset = _ActorMinX; else directionOffset = -_ActorMaxX; } // get the X difference float xDiff = _groundCollisionList[i].Position.X - (_actor.Position.X - directionOffset); // get an appropriate y difference along the surface float yDiff = xDiff * (_groundCollisionList[i].Normal.X / _groundCollisionList[i].Normal.Y); // modify the ground contact position T2DCollisionInfo newInfo = _groundCollisionList[i]; newInfo.Position = newInfo.Position + new Vector2(xDiff, yDiff); _groundCollisionList[i] = newInfo; } // choose a velocity modifier to use when checking one-way platforms // (if on the ground, use X velocity; if in the air, use Y velocity) if (_onGround) velMod = Math.Abs(_actor.Physics.VelocityX) * elapsed; else velMod = Math.Abs(_actor.Physics.VelocityY) * elapsed; velMod += _groundCheckYThreshold; // make sure we are above the potential ground object's collision point // (some leeway is given based on penetration and ground check threshold) if (platComp is OneWayPlatformComponent && _actor.Position.Y + _actorMaxY - _groundCheckYThreshold - velMod > _groundCollisionList[i].Position.Y) continue; // add the one-way platform component to the list of currently active platforms // (if the platform reached this point it's a platform that we might collide with // .. in other words, it is either a solid platform or a one-way platform and // we are moving either parallel to the surface or towards the surface in some way) if (platComp is OneWayPlatformComponent && CanActivatePlatforms) { (platComp as OneWayPlatformComponent).PlatformActive = true; _activePlatforms.Add(platComp.SceneObject); } // modify the dot product to force the following platform selection to favor angled // platforms a litle more than they would otherwise // (this is done specifically to avoid a terrible and obnoxious case where you are // falling down towards the intersection of a flat one-way plat and a sloped one-way plat // and the actor chooses the flat one, causing you to run straight through the slope. // this happens because at the time that you hit the ground your velocity is more // directly towards the flat platform than the sloped one. it's extremely annoying. // this bug still prevails sometimes, despite my best efforts. the only other solution // I can think of would be to support multiple ground objects - WNF for initial release) float fancyDot = dot - (1 + (_groundCollisionList[i].Normal.Y / 1.1f)); // if our velocity is more directly into this platform than the last // or it's the first platform we got to, grab its info // (note that if dot is equal, this check will always prefer the // current ground object over a new one to avoid needless assignments // later on in this method) if (newGroundObject == null || (_groundCollisionList[i].Position.Y < groundMinY) || (_onGround && (fancyDot < groundLowestDot || ((!_moveLeft && !_moveRight) || fancyDot == groundLowestDot) && sobj == _groundObject))) { // grab highest point so far if this is the first ground object if (newGroundObject == null) groundMinY = _groundCollisionList[i].Position.Y; // store values for this ground object newGroundObject = sobj as T2DSceneObject; newPlatformComponent = platComp; groundLowestDot = fancyDot; _groundSurfaceNormal = _groundCollisionList[i].Normal; _groundContactPosition = _groundCollisionList[i].Position; _groundPenetration = _groundCollisionList[i].Penetration; } // record the highest ground contact position found if (_groundCollisionList[i].Position.Y < groundMinY) groundMinY = _groundCollisionList[i].Position.Y; } } // deactivate all unused platforms for (int i = 0; i < _previouslyActivePlatforms.Count; i++) { if (!_activePlatforms.Contains(_previouslyActivePlatforms[i])) { OneWayPlatformComponent platComp = _previouslyActivePlatforms[i].Components.FindComponent<OneWayPlatformComponent>(); platComp.PlatformActive = false; _previouslyActivePlatforms.Remove(_previouslyActivePlatforms[i]); i--; } } // update previously active platforms list for (int i = 0; i < _activePlatforms.Count; i++) { if (!_previouslyActivePlatforms.Contains(_activePlatforms[i])) _previouslyActivePlatforms.Add(_activePlatforms[i]); } // if no valid ground objects found... if (newGroundObject == null) { // notify current ground component that we are leaving (if newly airborne) if (_onGround && _groundObjectComponent != null) _groundObjectComponent.ActorLeft(this); // set onGround flag to false _previouslyOnGround = _onGround; _onGround = false; // return false return false; } // set the onGround flag to true _onGround = true; // if this is a new ground object -and- we were on the ground before // (we ran from one platform to another without leaving the ground) if (newGroundObject != _previousGroundObject && _previouslyOnGround) { // notify the previous ground component we're leaving // (this would not get called otherwise) if (_groundObjectComponent != null) _groundObjectComponent.ActorLeft(this); } // if this is a new ground component -or- we just landed // (either running from one to the other, or just landing on a new one) // (in other words: every platform we use and only when we first get it) if (newGroundObject != _previousGroundObject || !_previouslyOnGround) { // update previous ground object for next check _previousGroundObject = _groundObject; // record the ground object that we found _groundObject = newGroundObject; // find the new ground component _groundObjectComponent = newPlatformComponent; // set the ground force and friction based on the ground component // (note: PlatformFriction property pulls friction value from owner's T2DPhysicsMaterial) if (_groundObjectComponent != null) { _groundSurfaceForce = _groundObjectComponent.SurfaceForce; _groundFriction = _groundObjectComponent.PlatformFriction; } else { // no ground object component.. // just init them to normal values _groundSurfaceForce = 0; _groundFriction = 1; } // notify the new ground component we arrived if (_groundObjectComponent != null) _groundObjectComponent.ActorLanded(this); // notify the controller of this actor that it landed on something if (Controller != null) (Controller as ActorController).ActorLanded(this, _groundObject); // instantly remove any inherited velocity directly toward or away from the surface of the ground object float dot = Vector2.Dot(_inheritedVelocity - platformVel, _groundSurfaceNormal); _inheritedVelocity = _inheritedVelocity - dot * _groundSurfaceNormal; // make sure Climbing is set to false if (_Climbing) _Climbing = false; // zero out platform error // (this can accumulate over a series of collisions with the same platform. // must be cleared so each platform can be corrected properly, if neccesary. // this value will remain zero for the vast majority of all actor movement.) if (_platformError != Vector2.Zero) _platformError = Vector2.Zero; // modify the process list so we get physics updates after the ground object we're standing on ProcessList.Instance.SetProcessOrder(_actor, _groundObject); } // if we wound up with a Y value beneath the highest found // modify the one we have to be match the highest // (sounds crazy, but happens a lot when running between sloped platforms) if (_groundContactPosition.Y > groundMinY) { // get the y difference float yDiff = groundMinY - _groundContactPosition.Y; // init xDiff to zero float xDiff = 0.0f; // get an appropriate x difference along the surface if (_groundSurfaceNormal.X != 0.0f) xDiff = yDiff * (_groundSurfaceNormal.Y / _groundSurfaceNormal.X); // flip the x difference based on the direction we are going if (_groundSurfaceNormal.X < 0) xDiff *= -1; // modify the ground contact position _groundContactPosition += new Vector2(xDiff, yDiff); } // get the previous position for move clamping Vector2 prevPos = _previousPosition; // factor in platform movement in the Y direction if (_groundObject.Physics != null && Math.Abs(_groundObject.Physics.VelocityY) > Epsilon.Value) { // get the vel diff this tick Vector2 velDiff = _groundObject.Physics.Velocity * elapsed; // modify contact position and previous position by vel diff _groundContactPosition += velDiff; prevPos += velDiff; } // get the new position on the ground Vector2 newPosition = new Vector2(_actor.Position.X, _groundContactPosition.Y - _ActorMaxY) + _platformError; // if the platform is slanted, get a more exact offset if (_previouslyOnGround && newPosition.X != prevPos.X && _groundSurfaceNormal == _previousSurfaceNormal && Math.Abs(_groundSurfaceNormal.X) > Epsilon.Value) { // get the desired move vector Vector2 desiredMoveVector = newPosition - prevPos; // get the dot product of the move vector and the ground normal float moveDot = Vector2.Dot(desiredMoveVector, _groundSurfaceNormal); // get the move vector corrected by the surface normal Vector2 correctedMoveVector = desiredMoveVector - (moveDot * _groundSurfaceNormal); // modify newPosition's Y component by the difference newPosition = new Vector2(newPosition.X, newPosition.Y + (correctedMoveVector.Y - desiredMoveVector.Y)); } // snap to the ground _actor.Position = newPosition; // record several states at the end of this update _previouslyOnGround = _onGround; _previousPosition = newPosition; _previousSurfaceNormal = _groundSurfaceNormal; // record the last known onGround time // (which is.. right now!) _lastOnGroundTime = TorqueEngineComponent.Instance.TorqueTime; // record the current offset from the platform // (this is just useful to have sometimes) _platformOffset = _actor.Position - _groundObject.Position; // return true return true; }