/** * Copyright 1998-2013 Epic Games, Inc. All Rights Reserved. */ class SimplePC extends GamePlayerController; /** How fast to increase the rate of rotation toward the target */ var float AutoRotationAccelRate; /** How quickly to decelerate when no rotation is applied */ var float AutoRotationBrakeDecelRate; /** Maximum auto rotation velocity */ var float MaxAutoRotationVelocity; /** How fast to increase the rate of rotation toward the target */ var float BreathAutoRotationAccelRate; /** How quickly to decelerate when no rotation is applied */ var float BreathAutoRotationBrakeDecelRate; /** Maximum auto rotation velocity */ var float MaxBreathAutoRotationVelocity; /** When rotating to a touch-to-move target, how much to increase yaw acceleration when the target is nearby */ var float RangeBasedYawAccelStrength; /** Distance at which we start to change rotation acceleration rate based on distance to touch-to-move target */ var float RangeBasedAccelMaxDistance; /** True if we should look at our click-to-move destination. */ var bool bLookAtDestination; /** Holds the location we are looking at */ var vector LookAtDestination; var float LookAtDestAutoPitchAmount; /** Is camera breathing engaged yet*/ var bool bCameraBreathing; /** The location that the camera was approximately looking at when breathing began*/ var vector CameraBreathCenterLocation; /** Delta from the desired rotation*/ var Rotator CameraBreathRotator; /** Last location where breathing began */ var vector CameraBreathSampleLocation; /** Time of the last random breath rotator sampling */ var float LastCameraBreathDeltaSelectTime; /** Time between direction changes*/ var float TimeBetweenCameraBreathChanges; /** How fast we're currently rotating toward the target (yaw, pitch) */ var vector2d AutoRotationVelocity; /** Holds the dimensions of the viewport */ var vector2D ViewportSize; /** List of footstep sounds, chosen at random to play while the player is walking */ var array FootstepSounds; /** How far we should move before playing the next footstep sound */ var float DistanceUntilNextFootstepSound; /** Commandline to run when being a server */ var config string ServerCommandline; /** Used for turn smoothing */ var float OldTurn, OldLookup; /** How much to smooth rotation. */ var config float RotationSmoothingFactor; /** Whether to use rotation smoothing */ var config bool bSmoothRotation; var config int DefaultInputGroup; /** Cache a reference to the MobilePlayerInput */ var MobilePlayerInput MPI; /** Cache a reference to various zones */ var MobileInputZone SliderZone; var MobileInputZone StickMoveZone; var MobileInputZone StickLookZone; var MobileInputZone FreeLookZone; /** Used for stats tracking */ var int NoTapToMoves; /** Used for stats tracking */ var float LastEnteredTapToMove; /** Used for stats tracking */ var float TotalTimeInTapToMove; /** Holds the time of the last movement/look change */ var float TimeOfLastUserViewChange; /** If true, apply an offset to the view target during a matinee */ var bool bApplyBackTouchToViewOffset; var bool bFingerIsDown; var Vector2D TouchCenter; var Rotator LastOffset; var Rotator MatineeOffset; /** * When we init the input system, find the TapToMove zone and hook up the delegate */ event InitInputSystem() { Super.InitInputSystem(); SetupZones(); } /** * Kismet hook to trigger console events */ function OnConsoleCommand( SeqAct_ConsoleCommand inAction ) { local string Command; foreach inAction.Commands(Command) { // don't allow music before startmatch if ( WorldInfo.Game.bWaitingToStartMatch && (Left(Command,15) ~= "mobile playsong") ) { continue; } // prevent "set" commands from ever working in Kismet as they are e.g. disabled in netplay if (!(Left(Command, 4) ~= "set ") && !(Left(Command, 9) ~= "setnopec ")) { ConsoleCommand(Command); } } } /** * Zones have to be setup on both sides of the network pond. This function is a good place to do that from. * * @param GameClass holds the class that's being setup. */ simulated function ReceivedGameClass(class GameClass) { Super.ReceivedGameClass(GameClass); // Setup the zones SetupZones(); } /** * The main purpose of this function is to size and reset zones. There's a lot of specific code in * here to reposition zones based on if it's an phone vs pad. */ function SetupZones() { // Cache the MPI MPI = MobilePlayerInput(PlayerInput); LocalPlayer(Player).ViewportClient.GetViewportSize(ViewportSize); StickMoveZone = MPI.FindZone("UberStickMoveZone"); StickLookZone = MPI.FindZone("UberStickLookZone"); FreeLookZone = MPI.FindZone("UberLookZone"); } /** * Setup the in world indicator for Touch to move and some other subsystems */ simulated function PostBeginPlay() { Super.PostBeginPlay(); // Time of the last target sampling LastCameraBreathDeltaSelectTime = 0; // Setup footstep sounds SetNextFootstepDistance(); } /** * Checks to see if we are moving via the virtual stick * * @returns true if the virtual stick is being used to move the player */ function bool IsStickMoveActive() { return StickMoveZone.State != ZoneState_Inactive; } /** Sets the distance until the next footstep sound plays */ function SetNextFootstepDistance() { // Determine how far to go until the next foot step (with a bit of randomness!) DistanceUntilNextFootstepSound = 200 + FRand() * 32; } /** * PlayerMove is called each frame to manage the input. We will use it to hook in and * see if the player has changed their view. If they do, then stop auto-rotating */ function PlayerMove( float DeltaTime ) { Super.PlayerMove(DeltaTime); // @todo: Is this needed? UpdateRotation( DeltaTime ); } /** * Optionally smooth rotation */ function UpdateRotation( float DeltaTime ) { local float Smooth; if ( bSmoothRotation ) { Smooth = 1.0 - FMin(0.9, RotationSmoothingFactor*DeltaTime); OldTurn = PlayerInput.aTurn * (1.0 - Smooth) + OldTurn * Smooth; OldLookup = PlayerInput.aLookup * (1.0 - Smooth) + OldLookup * Smooth; PlayerInput.aLookup = OldLookup; PlayerInput.aTurn = OldTurn; } Super.UpdateRotation(DeltaTime); } /** * Called from PlayerMove, it's here that we adjust the viewport */ function ProcessViewRotation( float DeltaTime, out Rotator out_ViewRotation, Rotator DeltaRot ) { // Accumulate a desired new rotation. local float DistToDestination; local Vector2D MaxVelocityScalar; local float YawRotationSign; local float PitchRotationSign; local float FinalYawAccelRate; local float FinalPitchAccelRate; local vector VectorToTarget; local vector TargetDirection; local Rotator NewRotation; local Rotator CameraRotationYawOnly; local Rotator CameraRotationPitchOnly; local Rotator TargetRotationYawOnly; local Rotator TargetRotationPitchOnly; //values to use during calculation local float RotationAccelRate; local float RotationBreakDecelRate; local float MaxRotationVelocity; //Using a fixed time delta to avoid stutters on device DeltaTime = FMin(DeltaTime, 1/25.0); Super.ProcessViewRotation(DeltaTime, out_ViewRotation, DeltaRot); if (PlayerInput.aTurn != 0.0 || PlayerInput.aLookUp != 0.0) { TimeOfLastUserViewChange = WorldInfo.RealTimeSeconds; } // If the player has moved the camera recently, then forcibly disable auto-rotation if ( WorldInfo.RealTimeSeconds - TimeOfLastUserViewChange < 0.1 ) { bLookAtDestination = false; //reset camera breath bCameraBreathing = false; LastCameraBreathDeltaSelectTime = 0; } //Update camera breathing UpdateCameraBreathing(); if (bLookAtDestination || !bCameraBreathing) { RotationAccelRate = AutoRotationAccelRate; RotationBreakDecelRate = AutoRotationBrakeDecelRate; MaxRotationVelocity = MaxAutoRotationVelocity; } else { RotationAccelRate = BreathAutoRotationAccelRate; RotationBreakDecelRate = BreathAutoRotationBrakeDecelRate; MaxRotationVelocity = MaxBreathAutoRotationVelocity; } MaxVelocityScalar.X = 1.0; MaxVelocityScalar.Y = 1.0; if( bLookAtDestination || bCameraBreathing) { CameraRotationYawOnly = out_ViewRotation; CameraRotationYawOnly.Pitch = 0; CameraRotationYawOnly.Roll = 0; CameraRotationPitchOnly = out_ViewRotation; CameraRotationPitchOnly.Yaw = 0; CameraRotationPitchOnly.Roll = 0; if( bLookAtDestination ) { VectorToTarget = LookAtDestination; } else //breathing { VectorToTarget = CameraBreathCenterLocation - Pawn.Location; } TargetDirection = Normal( VectorToTarget ); TargetRotationYawOnly = Rotator( TargetDirection ) + CameraBreathRotator; TargetRotationPitchOnly = TargetRotationYawOnly; TargetRotationYawOnly.Pitch = 0; TargetRotationYawOnly.Roll = 0; TargetRotationPitchOnly.Yaw = 0; TargetRotationPitchOnly.Roll = 0; if( bLookAtDestination ) { // For click to move, we limit the amount of pitching the camera will do since usually the height // of the target isn't that interesting, however sometimes a bit of pitching helps with slope-alignment TargetRotationPitchOnly = RLerp( CameraRotationPitchOnly, TargetRotationPitchOnly, LookAtDestAutoPitchAmount, true /* Take shortest route? */ ); } // How close is the current camera rotation to the target orientation? We'll rotate more quickly if // we're further off course, and more slowly as we approach the desired angle. This makes the rotation // appear to ease-out as we approach the desired orientation. if (!bCameraBreathing) { MaxVelocityScalar.x *= 1.0 - FMax( 0.0, Vector( CameraRotationYawOnly ) dot Vector( TargetRotationYawOnly ) ); MaxVelocityScalar.y *= 1.0 - FMax( 0.0, Vector( CameraRotationPitchOnly ) dot Vector( TargetRotationPitchOnly ) ); } DistToDestination = VSize2D( VectorToTarget ); // For destination-look at, allow distance to affect speed. (Note, we take the 2D distance here) FinalYawAccelRate = RotationAccelRate; FinalPitchAccelRate = RotationAccelRate; if( bLookAtDestination ) { // Increase the yaw rate as we get closer to the target (up to 1.0 + RangeBasedYawAccelStrength). // This is because the yaw angle relative to the target may still be very wide as we approach // (especially if the user touched a location near the player) we want to get most of the horizontal // turning out of the way early so the player can see where they're going FinalYawAccelRate *= 1.0 + ( 1.0 - FMin( DistToDestination, RangeBasedAccelMaxDistance ) / RangeBasedAccelMaxDistance ) * RangeBasedYawAccelStrength; // Decrease the pitch rate as we get closed to the target. This is because the pitch angle relative // to the target will usually become steeper as we approach the target and we won't want to pitch // up and down erratically as we're arriving FinalPitchAccelRate *= FMin( DistToDestination, RangeBasedAccelMaxDistance ) / RangeBasedAccelMaxDistance; } CheckDistanceToDestination(DistToDestination); // Accelerate yaw YawRotationSign = ( out_ViewRotation.Yaw ClockwiseFrom TargetRotationYawOnly.Yaw ) ? -1.0 : 1.0; AutoRotationVelocity.x += YawRotationSign * FinalYawAccelRate * DeltaTime; // Accelerate pitch PitchRotationSign = ( out_ViewRotation.Pitch ClockwiseFrom TargetRotationPitchOnly.Pitch ) ? -1.0 : 1.0; AutoRotationVelocity.y += PitchRotationSign * FinalPitchAccelRate * DeltaTime; } else { // Yaw brake if( AutoRotationVelocity.x > 0.01 ) { AutoRotationVelocity.x = FMax( 0.0, AutoRotationVelocity.x - RotationBreakDecelRate * DeltaTime ); } else if( AutoRotationVelocity.x < -0.01 ) { AutoRotationVelocity.x = FMin( 0.0, AutoRotationVelocity.x + RotationBreakDecelRate * DeltaTime ); } else { AutoRotationVelocity.x = 0.0; } // Pitch brake if( AutoRotationVelocity.y > 0.01 ) { AutoRotationVelocity.y = FMax( 0.0, AutoRotationVelocity.y - RotationBreakDecelRate * DeltaTime ); } else if( AutoRotationVelocity.y < -0.01 ) { AutoRotationVelocity.y = FMin( 0.0, AutoRotationVelocity.y + RotationBreakDecelRate * DeltaTime ); } else { AutoRotationVelocity.y = 0.0; } } // Clamp max velocity if( AutoRotationVelocity.x > MaxRotationVelocity * MaxVelocityScalar.x ) { AutoRotationVelocity.x = MaxRotationVelocity * MaxVelocityScalar.x; } else if( AutoRotationVelocity.x < -MaxRotationVelocity * MaxVelocityScalar.x ) { AutoRotationVelocity.x = -MaxRotationVelocity * MaxVelocityScalar.x; } if( AutoRotationVelocity.y > MaxRotationVelocity * MaxVelocityScalar.y ) { AutoRotationVelocity.y = MaxRotationVelocity * MaxVelocityScalar.y; } else if( AutoRotationVelocity.y < -MaxRotationVelocity * MaxVelocityScalar.y ) { AutoRotationVelocity.y = -MaxRotationVelocity * MaxVelocityScalar.y; } if( Abs( AutoRotationVelocity.X ) > 0.01 || Abs( AutoRotationVelocity.Y ) > 0.01 ) { // Rotate! NewRotation.Yaw = fixedTurn(out_ViewRotation.Yaw, out_ViewRotation.Yaw + AutoRotationVelocity.x * DeltaTime, Abs( AutoRotationVelocity.x * DeltaTime )); NewRotation.Pitch = fixedTurn(out_ViewRotation.Pitch, out_ViewRotation.Pitch + AutoRotationVelocity.y * DeltaTime, Abs( AutoRotationVelocity.y * DeltaTime )); NewRotation.Roll = out_ViewRotation.Roll; // Set new rotation out_ViewRotation = NewRotation; } } /** * Stub Function. It get's called by ProcessViewRotation and allows children to perform actions based on the * distance to a destination. */ simulated function CheckDistanceToDestination(float DistToDestination) { } /**Function that selects a slightly offset direction to look and will trend the camera towards that*/ simulated function UpdateCameraBreathing() { local Vector PawnX, PawnY, PawnZ; local float DegreeDelta; local float YawSign; local float PitchDegrees; local float YawDegrees; if ( Pawn == None ) { bCameraBreathing = false; LastCameraBreathDeltaSelectTime = 0; return; } //if we've just moved if (IsInState('PlayerClickToMove') || (CameraBreathSampleLocation != Pawn.Location)) { bCameraBreathing = false; LastCameraBreathDeltaSelectTime = 0; CameraBreathSampleLocation = Pawn.Location; return; } //if it's time to try to breath again if (WorldInfo.TimeSeconds - LastCameraBreathDeltaSelectTime >= TimeBetweenCameraBreathChanges) { DegreeDelta = 0.5; PitchDegrees = ((FRand() * 2.f) - 1.f); YawSign = (FRand() >= 0.5f) ? 1.0f : -1.0f; YawDegrees = YawSign * (Sqrt(1.0f - PitchDegrees*PitchDegrees)); //+ or - DegreeDeltas CameraBreathRotator.Pitch = (PitchDegrees*DegreeDelta*65536/360); CameraBreathRotator.Yaw = (YawDegrees*DegreeDelta*65536/360); CameraBreathRotator.Roll = 0.0; //if we're not actively tracking something, set a new desired look location relative to where we are already looking if (!bLookAtDestination && !bCameraBreathing) { //`log("NOTE: Chosen a new look at dest"@CameraBreathRotator); GetAxes(Rotation, PawnX, PawnY, PawnZ); CameraBreathCenterLocation = Pawn.Location + PawnX*1000.0; bCameraBreathing = true; } LastCameraBreathDeltaSelectTime = WorldInfo.TimeSeconds; } } function ActivateControlGroup() { MPI.ActivateInputGroup("UberGroup"); } /** Offset matinee via back touch */ function OffsetMatineeTouch(int Handle, ETouchType Type, Vector2D TouchLocation, float DeviceTimestamp, int TouchpadIndex) { // only first finger on back panel if (Handle != 0 || TouchpadIndex != 1) { return; } if (Type == Touch_Began) { TouchCenter = TouchLocation; bFingerIsDown = true; } else if (Type == Touch_Ended) { LastOffset = MatineeOffset; bFingerIsDown = false; } else if (bFingerIsDown) { MatineeOffset.Yaw = LastOffset.Yaw + 60 * (TouchLocation.X - TouchCenter.X); MatineeOffset.Pitch = LastOffset.Pitch + -60 * (TouchLocation.Y - TouchCenter.Y); } } event NotifyDirectorControl(bool bNowControlling, SeqAct_Interp CurrentMatinee) { super.NotifyDirectorControl(bNowControlling, CurrentMatinee); if (bNowControlling) { MPI.OnInputTouch = OffsetMatineeTouch; } else { MPI.OnInputTouch = none; LastOffset.Yaw = 0; LastOffset.Pitch = 0; MatineeOffset.Yaw = 0; MatineeOffset.Pitch = 0; bFingerIsDown = false; } // remember if we are controlling or not bApplyBackTouchToViewOffset = bNowControlling; } simulated event GetPlayerViewPoint( out vector out_Location, out Rotator out_Rotation ) { super.GetPlayerViewPoint(out_Location, out_Rotation); if (bApplyBackTouchToViewOffset) { out_Rotation += MatineeOffset; } } /** * Handle footsteps */ function PlayerTick(float DeltaTime) { local float CurrentWalkSpeed; local int FootstepSoundIndex; local TraceHitInfo HitInfo; local Vector TraceStart; local Vector TraceEnd; local Vector TraceExtent; local Vector OutHitLocation, OutHitNormal; local Actor TraceActor; if (bApplyBackTouchToViewOffset && !bFingerIsDown) { MatineeOffset.Yaw *= 0.99; MatineeOffset.Pitch *= 0.99; LastOffset.Yaw *= 0.99; LastOffset.Pitch *= 0.99; } // Only play footsteps if we're actually walking at a decent speed if ( Pawn != None ) { CurrentWalkSpeed = VSize2D( Pawn.Velocity ); } if ( CurrentWalkSpeed > 32.0 ) { // Update distance until the next foot step DistanceUntilNextFootstepSound -= CurrentWalkSpeed * DeltaTime; if( DistanceUntilNextFootstepSound < 0.0 ) { TraceStart = Pawn.Location; // @todo probably need a better way to get the end location instead of a magic number TraceEnd = Pawn.Location; TraceEnd.Z -= 100.0f; // trace down and see what we are standing on. TraceActor = Trace(OutHitLocation, OutHitNormal, TraceEnd, TraceStart, TRUE, TraceExtent, HitInfo, TRACEFLAG_Bullet|TRACEFLAG_Blocking|TRACEFLAG_SkipMovers); if( TraceActor != None && HitInfo.PhysMaterial != None && HitInfo.PhysMaterial.ImpactSound != None ) { // Play the sound from the hit physical material if it exists PlaySound( HitInfo.PhysMaterial.ImpactSound ); } else if (FootstepSounds.Length > 0) { // Play a random footstep sound if we couldnt find a physical material to get a sound from. FootstepSoundIndex = Rand( FootstepSounds.Length ); PlaySound( FootstepSounds[ FootstepSoundIndex ] ); } // Queue up the next footstep SetNextFootstepDistance(); } } else { // We stopped walking so reset time until next footstep SetNextFootstepDistance(); } Super.PlayerTick(DeltaTime); } exec function SetFootstepsToStone(); exec function SetFootstepsToSnow(); defaultproperties { InputClass=class'GameFramework.MobilePlayerInput' AutoRotationAccelRate=10000.0 AutoRotationBrakeDecelRate=10000.0 MaxAutoRotationVelocity=300000 BreathAutoRotationAccelRate=250.0 BreathAutoRotationBrakeDecelRate=1.0 MaxBreathAutoRotationVelocity=75 TimeBetweenCameraBreathChanges = 2.0 RangeBasedYawAccelStrength=8.0 RangeBasedAccelMaxDistance=512.0 }