We are currently rotating the player based upon the view rotation - which causes a number of issues. Firstly, the player is not correctly aligned to the surface it is walking on; it will often sink into the floor. Secondly, the player rotation is determined by the view rotation - which means it will rotate based upon where the camera is looking. This does not work unless the camera is always perfectly behind the player; which it is not, as the pitch and yaw can be altered at any time - only the roll is correct. If the player looks up or down, the player rotates up or down.
So, how are we going to solve this? The theoretical best solution, would be to somehow determine the rotation (pitch, roll, yaw) for the player not based upon the view rotation, but based upon the normal to the plane surface the player is currently standing on. Easy? Probably not.
Mathematical thinking caps people. We can only rotate the player using a rotator.
Current code:
state PlayerSpidering { ignores SeePlayer, HearNoise, Bump; event bool NotifyHitWall(vector HitNormal, actor HitActor) { Pawn.SetPhysics(PHYS_Spider); Pawn.SetBase(HitActor, HitNormal); return true; } // if spider mode, update rotation based on floor function UpdateRotation(float DeltaTime) { local rotator ViewRotation, PlayerRotation; local vector MyFloor, CrossDir, FwdDir, OldFwdDir, OldX, RealFloor; if ( (Pawn.Base == None) || (Pawn.Floor == vect(0,0,0)) ) { MyFloor = vect(0,0,1); } else { MyFloor = Pawn.Floor; } if ( MyFloor != OldFloor ) { // smoothly transition between floors RealFloor = MyFloor; MyFloor = Normal(6*DeltaTime * MyFloor + (1 - 6*DeltaTime) * OldFloor); if ( (RealFloor dot MyFloor) > 0.999 ) { MyFloor = RealFloor; } else { // translate view direction CrossDir = Normal(RealFloor Cross OldFloor); FwdDir = CrossDir cross MyFloor; OldFwdDir = CrossDir cross OldFloor; ViewX = MyFloor * (OldFloor dot ViewX) + CrossDir * (CrossDir dot ViewX) + FwdDir * (OldFwdDir dot ViewX); ViewX = Normal(ViewX); ViewZ = MyFloor * (OldFloor dot ViewZ) + CrossDir * (CrossDir dot ViewZ) + FwdDir * (OldFwdDir dot ViewZ); ViewZ = Normal(ViewZ); OldFloor = MyFloor; ViewY = Normal(MyFloor cross ViewX); } } Pawn.mesh.SetRotation(OrthoRotation(ViewX,ViewY,ViewZ)); if ( (PlayerInput.aTurn != 0) || (PlayerInput.aLookUp != 0) ) { // adjust Yaw based on aTurn if ( PlayerInput.aTurn != 0 ) { ViewX = Normal(ViewX + 2 * ViewY * Sin(0.0005*DeltaTime*PlayerInput.aTurn)); } // adjust Pitch based on aLookUp if ( PlayerInput.aLookUp != 0 ) { OldX = ViewX; ViewX = Normal(ViewX + 2 * ViewZ * Sin(0.0005*DeltaTime*PlayerInput.aLookUp)); ViewZ = Normal(ViewX Cross ViewY); // bound max pitch if ( (ViewZ dot MyFloor) < 0.707 ) { OldX = Normal(OldX - MyFloor * (MyFloor Dot OldX)); if ( (ViewX Dot MyFloor) > 0) { ViewX = Normal(OldX + MyFloor); } else { ViewX = Normal(OldX - MyFloor); } ViewZ = Normal(ViewX cross ViewY); } } // calculate new Y axis ViewY = Normal(MyFloor cross ViewX); } ViewRotation = OrthoRotation(ViewX,ViewY,ViewZ); SetRotation(ViewRotation); Pawn.FaceRotation(ViewRotation, deltaTime ); } function bool NotifyLanded(vector HitNormal, Actor FloorActor) { Pawn.SetPhysics(PHYS_Spider); return bUpdating; } event NotifyPhysicsVolumeChange( PhysicsVolume NewVolume ) { if ( NewVolume.bWaterVolume ) { GotoState(Pawn.WaterMovementState); } } function ProcessMove(float DeltaTime, vector NewAccel, eDoubleClickDir DoubleClickMove, rotator DeltaRot) { if ( Pawn.Acceleration != NewAccel ) { Pawn.Acceleration = NewAccel; } if ( bPressedJump ) { Pawn.DoJump(bUpdating); } } function PlayerMove( float DeltaTime ) { local vector NewAccel; local eDoubleClickDir DoubleClickMove; local rotator OldRotation, ViewRotation; local bool bSaveJump; GroundPitch = 0; ViewRotation = Rotation; //Pawn.CheckBob(DeltaTime,vect(0,0,0)); // Update rotation. SetRotation(ViewRotation); OldRotation = Rotation; UpdateRotation(DeltaTime); // Update acceleration. NewAccel = PlayerInput.aForward*Normal(ViewX - OldFloor * (OldFloor Dot ViewX)) + PlayerInput.aStrafe*ViewY; if ( VSize(NewAccel) < 1.0 ) { NewAccel = vect(0,0,0); } if ( bPressedJump && Pawn.CannotJumpNow() ) { bSaveJump = true; bPressedJump = false; } else bSaveJump = false; ProcessMove(DeltaTime, NewAccel, DoubleClickMove, OldRotation - Rotation); bPressedJump = bSaveJump; } event BeginState(Name PreviousStateName) { //if ( Pawn.Mesh == None ) // Pawn.SetMesh(); OldFloor = vect(0,0,1); GetAxes(Rotation,ViewX,ViewY,ViewZ); DoubleClickDir = DCLICK_None; Pawn.ShouldCrouch(false); bPressedJump = false; if (Pawn.Physics != PHYS_Falling) { Pawn.SetPhysics(PHYS_Spider); } GroundPitch = 0; Pawn.bCrawler = true; } event EndState(Name NextStateName) { GroundPitch = 0; if ( Pawn != None ) { Pawn.ShouldCrouch(false); Pawn.bCrawler = Pawn.default.bCrawler; } } }