En esta entrada analizaremos cómo poder modificar el movimiento de nuestro Pawn. Para ello, debemos explorar antes cómo funcionan básicamente los estados integrados en UDK.
Lo primero que hacemos es, irnos a la carpeta Development/Src/Engine/Classes/PlayerController.uc
Una vez allí buscamos "State PlayerWalking"
Allí nos encontraremos el estado que tiene un PlayerController por defecto cuando estamos en el juego. Como vemos, tiene una serie de métodos internos propios independientes de los que hay fuera de las llaves que lo engloban. El esqueleto básico sería algo así:
state PlayerWalking
{
//ignores SeePlayer, HearNoise, NotifyBump, TakeDamage, PhysicsVolumeChange, NextWeapon, PrevWeapon, SwitchToBestWeapon;
event NotifyPhysicsVolumeChange( PhysicsVolume NewVolume )
{
}
//Called when the state begins! Useful when we want to setup things before it starts
event BeginState(Name PreviousStateName)
{
//Acordaos de llamar al método padre para que funcione el estado bien!!!! (Ver PlayerController.uc)
}
//Called when the stateends! Useful when we want to setup things before another one starts
event EndState(Name NextStateName)
{
}
//Called when the stateends! Useful when we want to setup things before another one starts
event EndState(Name NextStateName)
{
//Acordaos de llamar al método padre para que funcione el estado bien!!!! (Ver PlayerController.uc)
}
Begin:
}
state PlayerWalking
{
ignores SeePlayer, HearNoise, Bump;
Begin:
}
}
Begin:
}
La nomenclatura sería algo como: [auto] state NameOfTheState [extends NameOfTheParentState]
El flag "auto" se puede colocar antes de declarar el estado para indicar que es un estado inicial. Como ya podéis intuir, podremos pasar de un estado a otro. Ésta acción se realiza mediante GoToState('NombreDelEstado');
Cuando una clase se encuentra en un estado, puede llamar normalmente a los métodos que tiene dentro, pudiendo encapsular de este modo, diferentes comportamientos para los mismos métodos.
BeginState y EndState, son llamados internamente cada vez que se entra en el estado y se sale de él respectivamente, con información del estado del que venimos y del que vamos. Nótese que los parámetros que reciben no son string, sino Name. La nomenclatura de name es con comilla simple ' ' en lugar de comillas dobles " ".
Por último, debajo observamos un Begin: Aquí comienza una especie de scripting dentro de los hilos de ejecución que pueden darnos más de un quebradero de cabeza. Sólo ciertos comandos y directivas son permitidas en este espacio. Begin no deja de ser una etiqueta pudiéndose desplazarse entre etiquetas mediante goto 'NombreDeEtiqueta'. Hay que tener cuidado con esto, ya que podemos crear un bucle infinito en la ejecución (claramente con goto 'Begin' xD). Igualmente se usan otros comandos como Sleep(númeroDeFramesAEsperar);
Como última mención, si tenéis métodos externos al ámbito del estado que se llaman igual que uno de los internos, se pueden acceder desde los métodos del estado a los exteriores mediante Global.NombreDelMétodo(ParametrosDelMetodo).
Una vez introducidos los estados, pasamos a analizar el estado PlayerWalking:
Añadimos en nuestro NULLPlayerController lo siguiente:
NULLPlayerController.uc
//NOTA: Este símbolo lo coloco para que sepáis que puede haber código donde se encuentre [...]
//En este caso, podéis colocar dónde queráis este pedazo de código, excepto después de DefaultProperties //o dentro de su ámbito. Es decir, dentro del ámbito de la clase, pero fuera de la inicialización de variables
state PlayerWalking
{
ignores SeePlayer, HearNoise, Bump;
function ProcessMove(float DeltaTime, vector NewAccel, eDoubleClickDir DoubleClickMove, rotator DeltaRot)
{
if( Pawn == None )
{
return;
}
if (Role == ROLE_Authority)
{
// Update ViewPitch for remote clients
Pawn.SetRemoteViewPitch( Rotation.Pitch );
}
Pawn.Acceleration = NewAccel;
CheckJumpOrDuck();
}
function PlayerMove( float DeltaTime )
{
local vector X,Y,Z, NewAccel;
local eDoubleClickDir DoubleClickMove;
local rotator OldRotation;
local bool bSaveJump;
if( Pawn == None )
{
GotoState('Dead');
}
else
{
GetAxes(Pawn.Rotation,X,Y,Z);
// Update acceleration.
NewAccel = PlayerInput.aForward*X + PlayerInput.aStrafe*Y;
NewAccel.Z = 0;
NewAccel = Pawn.AccelRate * Normal(NewAccel);
if (IsLocalPlayerController())
{
AdjustPlayerWalkingMoveAccel(NewAccel);
}
DoubleClickMove = PlayerInput.CheckForDoubleClickMove( DeltaTime/WorldInfo.TimeDilation );
// Update rotation.
OldRotation = Rotation;
UpdateRotation( DeltaTime );
bDoubleJump = false;
if( bPressedJump && Pawn.CannotJumpNow() )
{
bSaveJump = true;
bPressedJump = false;
}
else
{
bSaveJump = false;
}
if( Role < ROLE_Authority ) // then save this move and replicate it
{
ReplicateMove(DeltaTime, NewAccel, DoubleClickMove, OldRotation - Rotation);
}
else
{
ProcessMove(DeltaTime, NewAccel, DoubleClickMove, OldRotation - Rotation);
}
bPressedJump = bSaveJump;
}
}
event BeginState(Name PreviousStateName)
{
DoubleClickDir = DCLICK_None;
bPressedJump = false;
GroundPitch = 0;
if ( Pawn != None )
{
Pawn.ShouldCrouch(false);
if (Pawn.Physics != PHYS_Falling && Pawn.Physics != PHYS_RigidBody) // FIXME HACK!!!
Pawn.SetPhysics(Pawn.WalkingPhysics);
}
}
event EndState(Name NextStateName)
{
GroundPitch = 0;
if ( Pawn != None )
{
Pawn.SetRemoteViewPitch( 0 );
if ( bDuck == 0 )
{
Pawn.ShouldCrouch(false);
}
}
}
Begin:
}
//[...]
Como podéis ver, hemos copiado directamente el estado de la clase PlayerController.
El funcionamiento principal reside en los métodos PlayerMove y ProcessMove.
PlayerMove
Inicialmente llama a GetAxes(Pawn.Rotation,X,Y,Z); que divide la rotación del Pawn en tres vectores X, Y, y Z que contendrán los valores de su rotación como vectores. Después, crea una variable NewAccel, con la que, utilizando PlayerInput (clase que tiene el control del mando y teclado), accediendo a aForward y aStrafe (Movimiento hacia delante y lateral respectivamente), rellena unos valores que indicarán hacia donde se mueve el Pawn.
Después podemos ver que llama a actualizar a la rotación UpdateRotation que se encarga de Pitch y Roll de la cámara así como de llamar a actualizar la rotación de nuestro Pawn (Método FaceRotation).
También realiza algunas comprobaciones para red y finalmente llama a ProcessMove para dar los últimos retoques a la nueva aceleración del Pawn. Por defecto, Pawn.Acceleration = NewAccel; le da la aceleración tal cual la hemos creado anteriormente en PlayerMove.
Viendo este proceso, ya podemos atacar al sistema de movimiento de nuestro personaje, haciendo que se mueva como nosotros deseamos. Del mismo modo, podemos ver que, al cambiar a tipos de volúmenes se llama a un método event NotifyPhysicsVolumeChange( PhysicsVolume NewVolume )
Podemos trabajar con nuevos estados y maneras de movernos haciendo uso de todas estas herramientas.
Un buen ejercicio ahora, sería el dar controles de SideScroller y realizar una cámara acorde.
No hay comentarios:
Publicar un comentario