PFM: Mejorando la rotación del personaje

Hasta ahora el personaje se mueve así:

Es un movimiento muy ligado a la rotación de la cámara y aburrido en el sentido de que únicamente vemos la espalda del personaje, pues este siempre está mirando en la misma dirección que la cámara. Lo ideal sería que el personaje tuviera algo más de elasticidad en su movimiento y resultara más natural. Para ello, se va a modificar el modo en que el Pawn se gira en relación a la cámara y dependiendo de lo que se haga en cada momento. Y como sabemos, el Pawn es manejado por el Controller…

Requisitos de movimiento para el personaje.

  • En primer lugar, cuando el personaje esté quieto, la cámara podrá girar libremente a su alrededor, sin que el personaje gire con ella. De esta manera podremos ver la parte frontal del personaje y no siempre su espalda.

  • Cuando se encuentre en movimiento sin armas o con un arma cuerpo a cuerpo, el personaje girará en dirección al movimiento. Así al desplazarse lateralmente por ejemplo, girará todo el cuerpo en esa dirección, sin afectar a la cámara.

  • Aunque si se mueve la cámara estando en movimiento, el personaje sí girará para ajustarse a la orientación.

  • Cuando el personaje esté con otro tipo de arma el comportamiento será mixto. Si no se mueve, podrá girarse la cámara alrededor.

  • En caso de moverse el personaje sí apuntará hacia donde mire la cámara.

  • Finalmente está el caso de estar quieto y disparar. Como no queremos que dispare de espaldas, el personaje se girará y disparará cuando haya terminado de darse la vuelta.

El código de PFMPlayerController para conseguir este comportamiento es el siguiente. El estado PlayerWalking se extiende de la clase padre y se sobreescribe la función PlayerMove para que se ajuste a los requisitos.


class PFMPlayerController extends UTPlayerController;

var float RotationSpeed;        // Velocidad de rotación del Pawn
var bool bTurningToFire;        // Flag que indica si el Pawn está girando sobre sí mismo para disparar
var bool bPendingStopFire;      // Flag que indica que durante el giro se dejó de pulsar el disparo.
var byte lastFireModeNum;       // Almacena el modo de disparo para aplicarlo tras el giro.

//Extiende el estado PlayerWalking, sobreescribiendo PlayerMove
state PlayerWalking
{
    ignores SeePlayer, HearNoise, Bump;

    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);

			// La aceleración (y en consecuencia el movimiento) es diferente según el tipo de arma que lleve el Pawn
			if(PFMPawn(Pawn).WeaponType == PWT_Default)
			{
    			NewAccel = Abs(PlayerInput.aForward)*X + Abs(PlayerInput.aStrafe)*X;
            }
            else
            {
                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;
		}
	}
}

function UpdateRotation( float DeltaTime )
{
    local Rotator DeltaRot, newRotation, ViewRotation;
    local Rotator CurrentRot;
    local vector X, Y, Z, newRotationVector;

    ViewRotation = Rotation;
    if (Pawn!=none)
    {
        Pawn.SetDesiredRotation(ViewRotation);
    }

    // Calculate Delta to be applied on ViewRotation
    DeltaRot.Yaw = PlayerInput.aTurn;
    DeltaRot.Pitch = PlayerInput.aLookUp;

    ProcessViewRotation( DeltaTime, ViewRotation, DeltaRot );
    SetRotation(ViewRotation);

    //Rotación del Pawn
    if ( Pawn != None )
    {
        // Se aplica el giro
        if (bTurningToFire || Pawn.IsFiring())  // si está disparando (o girando para disparar)
        {
            // Pawn mira a donde mira la cámara
            NewRotation = ViewRotation;
            NewRotation.Roll = Rotation.Roll;
            CurrentRot = RLerp(Pawn.Rotation, newRotation, RotationSpeed * DeltaTime, true);
            Pawn.FaceRotation(CurrentRot, deltatime);

            if(bTurningToFire)
            {
                CheckIfCanFire(lastFireModeNum);
            }
        }
        else if(PlayerInput.aForward != 0.0 || PlayerInput.aStrafe != 0.0)  // o en movimiento sin disparar
        {
            // Giro solidario con la cámara. Pawn mira a donde mira la cámara
            if(PFMPawn(Pawn).WeaponType != PWT_Default)
			{
                NewRotation = ViewRotation;
                NewRotation.Roll = Rotation.Roll;
                CurrentRot = RLerp(Pawn.Rotation, newRotation, RotationSpeed * DeltaTime, true);
            }
            else    // Giro relativo a la cámara. Pawn se gira hacia su dirección de movimiento
            {
                GetAxes(ViewRotation, X, Y, Z);
                newRotationVector = PlayerInput.aForward * X + PlayerInput.aStrafe * Y;
                newRotationVector.Z = 0;
                NewRotation = rotator(Normal(newRotationVector));
                CurrentRot = RLerp(Pawn.Rotation, NewRotation, RotationSpeed * DeltaTime, true);
            }
            Pawn.FaceRotation(CurrentRot, deltatime);
        }
    }
}

// Añadida funcionalidad para girar antes de disparar
exec function StartFire( optional byte FireModeNum )
{
	if ( Pawn != None && !bCinematicMode && !WorldInfo.bPlayersOnly && !IsPaused())
	{
        lastFireModeNum = FireModeNum;      // Se guarda el modo de disparo para los disparos retardados.
        if(CheckIfCanFire(FireModeNum))     // Si se puede disparar directamente:
        {
            Pawn.StartFire( FireModeNum );  // se hace.
        }
	}
}

exec function StopFire(optional byte FireModeNum)
{
    Super.StopFire(FireModeNum);
    if(bTurningToFire)              // Si se ha dejado de pulsar el botón de disparo mientras se gira,
    {
        bPendingStopFire = true;    // se activa el flag para tenerlo en cuenta.
    }
}

// Comprueba si el ángulo del Pawn es el adecuado para disparar. En caso contrario, activa el giro y recuerda el disparo pendiente.
function bool CheckIfCanFire(optional byte FireModeNum)
{
    local float cosAng;     //Coseno de ángulo

    // Podrá disparar si Postura es Default
    if(PFMPawn(Pawn).WeaponType == PWT_Default)
    {
        return true;
    }
    // Si la diferencia entre la rotación actual del Pawn y del Controller es inferior a cierto límite, puede disparar
    cosAng = Normal(vector(Rotation) * vect(1.0,1.0,0.0)) dot Normal(vector(Pawn.Rotation));
    if((1 - cosAng) < 0.01)
    {
        if(bTurningToFire)  // Si estábamos girando...
        {
            bTurningToFire=false;           // se desactiva el flag
            Pawn.StartFire(FireModeNum);    // y se inicia el disparo.
            if(bPendingStopFire)            // Si durante el giro se dejó de disparar:
            {
                Pawn.StopFire(FireModeNum); // Se detiene el disparo
                bPendingStopFire = false;   // y se desactiva el flag
            }
        }
        return true;
    }
    else    // En caso contrario se activa el giro para disparar
    {
        bTurningToFire=true;
        return false;
    }
}

defaultproperties
{
    RotationSpeed=12
}

La parte quizás más compleja es la de disparar, pues hay que esperar a que termine de girar antes de iniciar el disparo. Esto implica usar unas cuantas variables para determinar que hay un disparo pendiente e incluso que se ha dejado de disparar durante el giro. El resultado final:


Banner Blog

Anuncios

39 pensamientos en “PFM: Mejorando la rotación del personaje

  1. Buenas, estoy siguiendo tus tutoriales sobre como cambiar el personaje y luego este de la cámara.
    El prefijo que he ido usando es TEST en lugar de PMF, y el personaje que he usado es el soldado que viene con el UDK (IronGuard_MaleA), al no tener aún un personaje ni animaciones propios.

    En este apartado de la cámara me sale un error en el fichero TESTPlayerController.uc:

    error: Unrecognized member “WeaponType” in class “TESTPawn”

    Aparece en las tres líneas que contienen “WeaponType” en el fichero.

    Editando el fichero desaparecen los errores, pero el player no tiene animación, y sale sin arma.

    ¿Tengo que declarar la clase WeaponType en algún sitio?

    —-

    En el TESTPawn tengo estas tres líneas para elegir el modelo:

    SkeletalMesh=SkeletalMesh’CH_IronGuard_Male.Mesh.SK_CH_IronGuard_MaleA’
    AnimSets(0)=AnimSet’CH_AnimHuman.Anims.K_AnimHuman_BaseMale’
    AnimTreeTemplate=AnimTree’CH_AnimHuman_Tree.AT_CH_Human’

    • Hola Cobalt, primero gracias por leerme y por exponer tu problema con detalle (esto último es muy de agradecer xD).

      WeaponType es una variable que creé en esta entrada. Para lo que me sirve es para distinguir el tipo de arma que mi Pawn está utilizando y con esto, cambiar el comportamiento de la cámara o del propio personaje dependiendo de esta variable. Por tanto, si no quieres hacer esta distinción, puedes quitar las referencias a WeaponType en el controller como bien dices.

      Por otro lado, si quieres usar el personaje de UDK y mantener las mismas animaciones y disparos, creo que sería mejor que tu TestPawn extienda de UTPawn directamente en lugar de extender de UDKPawn, que es lo que yo hago (ya que quería modificar las animaciones y demás). Esto es porque los modelos de UDK son bastante más complejos que lo que yo hice en el tutorial que mencionas, tanto en animaciones como comportamiento, y la clase UDKPawn no está hecha para controlar esos personajes. Si te fijas por ejemplo en el tutorial de cambiar la camara a tercera persona yo extendía directamente de UTPawn y luego al usar mi propio personaje cambié a UDKPawn. Creo que tendrá que ver con eso, prueba a extender de UTPawn y me comentas.

  2. Gracias, he cambiado UT por UDK, y me sale el player levitando y el arma siguiéndole, pero de lejos… Empecé ayer con el UDK, estoy verde no, lo siguiente…

    Ahora lo que intento es cargar un personaje propio, lo he conseguido importar, aplicar texturas, con su esqueleto y un único “animset” que contiene una sola “Animation secuence”. El original era un biped (con physique que he tenido que pasar a skin) con varias animaciones en un único fichero. Creo haber leido algo por ahí que es posible ir separando esas animaciones y asignarlas a diferentes “Animation secuences”, pero no doy con la tecla.

    En principio lo que quiero lograr es poder importar mi personaje propio, con su propia estructura de huesos y animaciones. Es decir, no busco que use las animaciones del UT.

    • Ah pues si quieres usar tu propio personaje es más sencillo, porque ya te digo que los de UDK son bastante complicados. Me parece que UDK permite importar varias animaciones desde un solo fichero aunque no lo he probado. Yo lo que he ido haciendo es crear el personaje por un lado y después generar un fichero FBX por cada animación, importándolas en UDK una a una. Una vez que tengas las animaciones importadas en el AnimSet tendrás que montar el AnimTree para que el personaje sepa cuándo usar cada una.

  3. Sí, así lo he hecho y ha funcionado perfecto. Lo que creo es que las animaciones no se adaptan a distintas dimensiones de esqueletos. Por ejemplo con el biped de Max, da igual el esqueleto que tengas, que al cargar una animación bip, esta se adapta al esqueleto.

    Aquí por lo que veo las animaciones redimensionan el esqueleto, con lo que habría que ir importando las mismas animaciones para diferentes modelos. O, si mejoras el modelo, tienes que volver a exportar/importar las animaciones de nuevo.

    • Efectivamente cada animación funciona sólo en el esqueleto con la que la hayas creado, pues se guardan las rotaciones exactas de cada hueso. Lo que sí se puede es aplicar una animación a diferentes modelos, siempre que compartan el mismo esqueleto. Por ejemplo los personajes que vienen en UDK comparten el mismo AnimSet, teniendo diferentes modelos (pero el mismo esqueleto). Una vez que importes la animación puedes aplicársela a cualquier modelo que tenga el esqueleto adecuado.

  4. Claro, habrá que guardarse el “bip” y aplicárselo a cada mesh antes de exportarlo a fbx.

    Ahora quiero ver cómo hacer lo de añadir piezas independientes al mesh principal, como pelo, ropa, etc.
    Luego el tema de las físicas, los morphs para animación facial… cosas que he tardado meses en dominar con el 3ds.

  5. Lo del Physx son palabras mayores de momento. Lo que busco es un sistema simple de añadir objetos al player.

    He creado meshes por separado para ropa y pelo, con el smismo skin y biped que el player. En el “Animset editor” se los añado con la opción “Extra mesh 1-3” y quedan perfectos, incluyendo las animaciones. Pero por lo visto ahí sólo se puede probar como queda, no colocar en el juego.
    Luego he intentado con los sockets. Añado el “prop” y se queda fijo, pero no funciona la animación. Y en el juego tampoco aparece.
    Sospecho que hay que usar el Kismet y el “attach to actor”, pero no me aclaro con eso aún.

    • Para añadir cosas como espadas o cualquier objeto que sujete el personaje lo mejor son los sockets. En el tutorial de disparar muestro cómo se hace con código y es muy sencillo, pero imagino que en Kismet también se puede.

      Para hacer personajes modulares cuyos meshes estén sincronizados en animación es un poco más complicado pero también está contemplado, pues se permiten tener varios Skeletal Mesh Components por personaje: http://udn.epicgames.com/Three/DevelopmentKitGemsCreatingAModularPawn.html

    • Bueno, he probado el sistema de componer meshes y funciona. Pero tal y como viene en el tutorial funciona con NPCs. He intentado integrarlo en el Pawn principal pero me da un montón de errores al compilar.

    • Seguramente parte de mi código del Pawn no sea directamente compatible con ese código de Pawns modulares y haya que hacer ajustes.

  6. Bueno, parece que sólo está ese método, porque los sockets son para objetos que no sincronicen la animación.

    Ahora tengo un problema con el UDK, se me cuelga al darle al “build” con el error “BUILD WITH UDK dejó de funcionar”. No he tocado nada más que lo que he ido haciendo de añadir animaciones e importar modelos. También se cuelga al cargar algunos escenarios como el Necrópolis, Reinicié el pc y sigue igual ¿se te ocurre por qué puede ser?

    • Creo que en alguna ocasión me ha pasado, de repente sale ese error y ya no vuelve a funcionar… Si te sigue pasando guarda los paquetes que hayas creado y reinstala UDK, no creo que haya otra solución y menos con las explicaciones que da el mensaje de error…

  7. Arreglado. Un poco coñazo porque hay que volver a editar los ficheros del config, además de copiar los udk.

    Gracias por tu tiempo y tus tutoriales, me han hecho avanzar mucho.

  8. Pingback: PFM: Combate cuerpo a cuerpo en UDK (parte 1) | El Blog de Marcos

  9. Disculpa tengo una pequeña consulta queria saber si me podrias ayudar con esto
    My player no tiene sonido al caminar es decir las pisadas me puedes ayudar e dado sonido a las armas pero no logro al player

  10. Buenas. Tengo una duda aquí. He usado tu código y tengo un ligero problema, y es que cuando intento moverme en el eje Y, todo bien. W y S mueven hacia delante y detrás respectivamente, pero A y D no mueven al personaje hacia la izquierda/derecha, aunque el pawn sí que está rotado en dicha dirección.

    Tuve que eliminar algunas partes del código, principalmente los ifs porque en mi código no necesito saber si lleva una arma o no, siempre quiero el mismo estilo de cámara.

    ¿Puedo saber a qué se debe este problemilla?

    • Quizás al borrar algunas partes del código hayas sin querer eliminado alguna funcionalidad. Lo más fácil es que uses el código tal cual y compruebes que funciona primero y después vayas eliminando el código que te sobre y comprobando que sigue funcionando.

    • Sí, gracias. Ya he encontrado el problema. Usé NewAccel = PlayerInput.aForward*X + PlayerInput.aStrafe*Y; en vez de NewAccel = Abs(PlayerInput.aForward)*X + Abs(PlayerInput.aStrafe)*X;
      Ahora el movimiento es correcto en todos los ejes. Muchas gracias. ¡Magníficos tutoriales!

  11. La camara me ha quedado muy bien, el unico problema que ai es que cuando camino en cualquier mapa el personaje va como deslizandose… Como puedo reparar esto ?

    • Deslizándose pero con animación? Quizá la animacion es muy lenta para la velocidad que tiene el personaje y parece que patina

  12. Hola es muy interesante tu tutorial pero estoy estancado…. yo hice todo eso lo dela camara pero:
    1. Puedo rotar camara y ver mi personaje pero éste no se mueve para ningun lado cuando presiono las teclas de movimiento
    2. He comentado el codigo donde esta el weapontype y aun asi sin resultados, no puedo mover mi personaje pero si la camara

    QUEEEEEEE HAAGOOOOO!!!!!!

    • Hola Asher, los tutoriales están en secuencia, por eso recomiendo que se empiece por el primero y se vayan siguiendo poco a poco. Puedes ver los tutoriales ordenados cronológicamente aquí: https://marcosdiez.wordpress.com/pfm/
      Si haces un tutorial posterior sin aplicar antes los primeros es probable que no consigas buenos resultados 😛

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s