PFM: Combate cuerpo a cuerpo en UDK (parte 1)

Para los casos en que la munición se agota, siempre viene bien tener a mano un arma cuerpo a cuerpo con la que proseguir el «trabajo» y eso es lo que crearemos en esta entrada. Se diseñará un sistema de combate cuerpo a cuerpo y se implementará en forma de una espada que el protagonista (de momento Mr. Pepe Dummy) esgrimirá cual mosquetero.

Diseño del sistema de combate

El sistema de combate que vamos a implementar es el más común en los videojuegos pues consiste en ir enlazando ataques. Dado que no coincide exactamente con la manera en que UDK trata las armas habrá que sobreescribir algunas funcionalidades, como se verá más abajo. Es importante definir exactamente cómo se comportará el sistema de combate cuerpo a cuerpo para luego implementarlo correctamente. En mi caso, el comportamiento del sistema será el siguiente:

  • Cada animación de ataque está compuesta por dos partes, una primera que consiste en el propio ataque y una segunda que contiene la recuperación (que es pasar del momento final del ataque a la postura neutral)
  • Las animaciones se irán enlazando al hacer clic con el ratón en la parte de ataque, siendo la última la que ejecute la recuperación (ataque-ataque-ataque-recuperación).
  • Mantener pulsado el botón del ratón tendrá el mismo efecto que hacer un sólo clic (ataque-recuperación)
  • Tras una recuperación se comienza un nuevo ataque siempre por la primera animación.
  • Si durante una fase de ataque se hace clic de nuevo, al terminar la fase de ataque se enlazará el segundo ataque.
  • Si durante la recuperación se hace clic, instantáneamente se activará la siguiente animación de ataque.
  • Durante la fase de ataque no puede controlarse el movimiento del personaje.
  • Si se pulsa una tecla de movimiento durante la fase de ataque, al terminar esta el personaje comenzará el movimiento, sin mostrarse la fase de recuperación.
  • Si podrá modificarse la rotación del personaje, para cambiar la orientación entre un ataque y el siguiente.

Una vez se tiene claro el sistema de combate a utilizar, se puede pasar a la implementación.

Modelos, animaciones y preparativos en UDK

Lo primero será crear el modelo del arma. Para probar el modelo es lo de menos, así que yo no le pondré ni textura:

pfm12_01Lo que sí será importante es que sea un Skeletal Mesh (que contenga un esqueleto), pues usaremos la posición de los huesos para calcular las colisiones con los enemigos. Así, colocaremos una articulación en la base de la espada y el final del hueso en la punta, como se muestra:

pfm12_02Una vez importado en UDK crearemos 2 sockets en el esqueleto, uno en el hueso de la base del arma y otro en su punta, usando para ello el Socket Manager.

Abriendo el personaje podemos previsualizar cómo quedará el modelo de la espada en el socket de la mano del mismo:

pfm12_03A continuación importaremos las animaciones que hemos creado para el movimiento del personaje. En mi caso he hecho 2 (muy cutres por cierto):

Como se puede ver, cada animación contiene tanto la parte del ataque en sí, como la de la recuperación de la posición. Además, la segunda animación comienza en el punto en que termina el ataque de la primera, lo que será importante para enlazar una animación con otra.

Finalmente en UDK ampliaremos el AnimTree del personaje para permitir ejecutar animaciones desde el código cuando sea necesario. Para ello usaremos un nodo AnimNodeSlot, que posibilita esto. Le cambiaremos el nombre para poder acceder a él desde el código, llamándolo MeleeAnimNode.

pfm12_04Hecho esto, podemos pasar al código, donde haremos que se pueda agitar la espada como un loco.

Swing! swing!

Lo primero que haremos será conseguir un acceso al nodo del AnimTree que acabamos de añadir, y para ello modificaremos PFMPawn. Simplemente crearemos una variable para almacenarlo:


var AnimNodeSlot MeleeAnimNode;

Y la inicializaremos en la función PostInitAnimTree, que ya habíamos usado al hacer que el personaje apuntara.


simulated event PostInitAnimTree(SkeletalMeshComponent SkelComp)
{
    // Se busca la referencia del nodo de Aiming
    PFMAimNode = AnimNodeAimOffset(SkelComp.FindAnimNode('PFMAimNode'));

    // Se busca la referencia del nodo de animaciones melee
    MeleeAnimNode = AnimNodeSlot(SkelComp.FindAnimNode('MeleeAnimNode'));
}

Con esto ya tendremos una referencia al nodo y podremos activar las animaciones fácilmente.

PFMWeaponBlade

A continuación vamos a crear la clase que representa el arma en sí. Yo la llamaré PFMWeaponBlade. Ella se encargará de activar las animaciones correspondientes del Pawn y calcular las colisiones con los enemigos. El diseño del sistema de combate no se ajusta muy bien a lo que UDK tiene predefinido (por ejemplo, las armas normales siguen disparando hasta soltar el botón de disparo), así que hay que hacer pequeñas modificaciones en su comportamiento por defecto. Muy recomendable conocer el funcionamiento de las armas en UDK.


class PFMWeaponBlade extends PFMWeapon;

var array <Name> SwingAnimations;         // Nombres de las animaciones a usar
var array <float> SwingIntervals;         // Intervalos de tiempo de cada animación
var int currentSwing;                    // Animación actual

var SoundCue SwingSound;                // Sonido de espadazo

Se declaran algunas variables: Un array para guardar los nombres de las animaciones, un array con los tiempos de la fase de ataque de cada animación, un contador para almacenar qué animación se ejecuta actualmente, etc.


// Se sobreescribe para evitar que al soltar el botón se desactive cualquier ataque
simulated function StopFire(byte FireModeNum);

Por defecto al soltar el botón del ratón se llama a la función StopFire, que automáticamente desactiva el estado de disparar. Lo sobreescribimos para que no haga eso.


// Disparo efectivo
simulated function FireAmmunition()
{
 // Se declara que el ataque se ha cumplido
 Super.StopFire(CurrentFireMode);

 // Se elimina posible Timer de reseteo del sistema de melee
 ClearTimer(nameof(ResetMelee));

 // Animación de melee (AnimName, Rate, BlendInTime, BlendOutTime, bLooping, bOverride)
 PFMPawn(Owner).MeleeAnimNode.PlayCustomAnim(SwingAnimations[currentSwing], 1.0,0.05,0.1,false,true);

 // Efectos
 SpawnSwingEffects();

 // Se reprograma el intervalo de RefireCheckTimer adecuadamente
 FireInterval[0] = SwingIntervals[currentSwing];
 TimeWeaponFiring(CurrentFireMode);

 // Aumenta el índice de las animaciones para pasar a la siguiente
 currentSwing = (currentSwing + 1) % SwingAnimations.length;

 // Se desactiva el desplazamiento del Pawn
 PFMPlayerController(Pawn(Owner).Controller).SetRotationOnly(true);

 Super.FireAmmunition();
}

FireAmmunition es la función que efectúa el disparo del arma, por lo que la sobreescribimos para que ejecute las animaciones, configure el tiempo entre ataques, etc.


// Programa un timer para llamar a RefireCheckTimer
simulated function TimeWeaponFiring( byte FireModeNum )
{
 // Si desde que se inició el ataque anterior se ha pulsado otra vez clic, RefireCheckTimer volverá a
 // lanzar un ataque, pues PendingFire será = 1
 SetTimer( GetFireInterval(FireModeNum), false, nameof(RefireCheckTimer) );
}

La función RefireCheckTimer comprueba si hay que volver a llamar a FireAmmunition (por defecto se haría manteniendo el ratón pulsado). En nuestro caso, se volverá a disparar si hay algún clic pendiente.


// Llamado al desactivar este arma
simulated function PutDownWeapon()
{
 HandleFinishedFiring();
 Super.PutDownWeapon();
}

// Llamado cuando se deja de "disparar". Se llama si RefireCheckTimer determina que no hay que seguir atacando.
simulated function HandleFinishedFiring()
{
 if (PFMPawn(Owner).MeleeAnimNode.GetCustomAnimNodeSeq() != None)
 { // Se activa un Timer que reseteará el sistema de Melee cuando termine la animación actual
 SetTimer(PFMPawn(Owner).MeleeAnimNode.GetCustomAnimNodeSeq().GetTimeLeft(), false, nameof(ResetMelee));
 }

 // Se reactiva el movimiento del Pawn
 PFMPlayerController(Pawn(Owner).Controller).SetRotationOnly(false);

 Super.HandleFinishedFiring();
}

// Se llama cuando la animación de melee concluye. Resetea el sistema para que las animaciones comiencen desde la primera.
function ResetMelee()
{
 // Se resetea el combo
 currentSwing = 0;
}

Estas funciones se encargan de resetear el sistema cuando se cambia de arma o termina una combinación de ataques.

//********************************
// EFECTOS VISUALES Y DE SONIDO
//********************************

// Efectos de ataque
function SpawnSwingEffects()
{
 // Sonido de impacto
 if (SwingSound != None)
 {
 PlaySound(SwingSound, true);
 }
}

defaultproperties
{
 // Mesh
 Begin Object Class=SkeletalMeshComponent Name=GunMesh
 SkeletalMesh=SkeletalMesh'Weapons.PepeDummyWeaponBlade.SM_PepeDummyWeaponBlade'
 HiddenEditor=FALSE
 Scale=1.0
 End Object
 Mesh=GunMesh
 Components.Add(GunMesh)

 bMeleeWeapon=true;
 bInstantHit=true;
 bCanThrow=false;

 FiringStatesArray(0)=WeaponFiring
 WeaponFireTypes(0)=EWFT_Custom

 FireInterval(0)=0.0 // Intervalo entre disparos
 currentSwing=0 // Animación actual
 SwingAnimations=("PepeDummy_Swing1","PepeDummy_Swing2") //Animaciones diferentes
 SwingIntervals=(0.4,0.35) //Intervalos de cada animación

 SwingSound=SoundCue'A_Vehicle_Scorpion.SoundCues.A_Vehicle_Scorpion_BladeExtend'

 // Grupo de inventario
 InventoryGroup=1

 // Tipo de postura
 WeaponType=PWT_Default
}

Inicialización de variables como es habitual. Se especifica el SkeletalMesh, los nombres de las animaciones, sus tiempos de ataque, etc.

Como se ve en el código se hace uso en varias ocasiones la función SetRotationOnly de PFMPlayerController para desactivar/activar el movimiento del personaje. Esta función no existe por defecto en el controlador por lo que hay que modificar PFMPlayerController para añadirla:


var bool bRotationOnly;         // Especifica que el Pawn no puede desplazarse, sólo rotar

function SetRotationOnly(bool rotationOnly)
{
    bRotationOnly = rotationOnly;
}

Se modifica la función PlayerMove del estado PlayerWalking para que sólo pueda moverse el personaje cuando la variable bRotationOnly sea falsa. (Pongo todo el código de PlayerWalking por claridad, pero la única modificación es añadir la llamada a ResetMeleeIfCanMove() y el if(!bRotationOnly), que hace que la función no aclcule la aceleración del personaje si no debe desplazarse. Si te preguntas de dónde sale el código de PlayerWalking, échale un vistazo a esta entrada anterior)

//Extiende el estado PlayerWalking, sobreescribiendo ProcessMove
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);

			// Se resetea el melee en caso necesario
			ResetMeleeIfCanMove();

            if(!bRotationOnly)  // Sólo se calcula si podemos desplazarnos
            {
                // 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 || PFMPawn(Pawn).WeaponType == PWT_Sword)
    			{
        			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;
		}
	}
}

El resto del código no hay que modificarlo. Se añadirá la función ResetMeleeIfCanMove(), que se encarga de desactivar la animación de Melee si queremos movernos durante la fase de recuperación.

//*************************************************************************
// MELEE
//*************************************************************************
function ResetMeleeIfCanMove()
{
    if(!bRotationOnly) // El arma de melee ya lo ha activado (Nos permite movernos)
    {
        if(PFMPawn(Pawn).MeleeAnimNode.GetPlayedAnimation() != '')
        {
            if(PlayerInput.aForward != 0.0 || PlayerInput.aStrafe != 0.0) // Queremos movernos
            {
                // Se para la animación de melee
                PFMPawn(Pawn).MeleeAnimNode.StopCustomAnim(0.2);
                // y resetea el sistema de melee
                if(PFMWeaponBlade(Pawn.Weapon) != None)
                {
                    PFMWeaponBlade(Pawn.Weapon).ResetMelee();
                }
            }
        }
    }
}

Finalmente hay que añadir la espada al inventario por defecto del Pawn, por lo que en PFMPawn, añadiremos o modificaremos la función AddDefaultInventory:


// Sobreescribe la funcion de Pawn
function AddDefaultInventory()
{
    // Se le da al Pawn el arma al comenzar
    InvManager.CreateInventory(class'PFM.PFMWeaponBlade',true);

}

Compilado y ejecutando, este es el resultado:

El sistema está montado, pero si intentamos atacar a los enemigos, no servirá de nada. Estamos ejecutando las animaciones, pero todavía no calculamos los impactos con el entorno ni le aplicamos daño a los enemigos, por lo que de poco sirve menear la espada. El cálculo de daños lo abordaremos en la segunda parte 🙂
Banner Blog

64 comentarios en “PFM: Combate cuerpo a cuerpo en UDK (parte 1)

  1. Muy buenos tutoriales , una buena guia!! tengo una consulta, probe el codigo y me tira un error, missing ‘<' in 'array' en la clase PFMWeaponBlade.uc, porque sera?

    • Arg! Al copiar el código WordPress decidió eliminar las palabras entre ‘<' y ‘>’ pensando que era html incorrecto. En la declaración de los arrays el código correcto es así:

      var array <Name> SwingAnimations; // Nombres de las animaciones a usar
      var array <float> SwingIntervals; // Intervalos de tiempo de cada animación

      Al copiar el código WordPress decidió eliminar «<Name>» y «<float>»… Ahora lo corrijo. Gracias por descubrir el error!

    • Uh de una, ahora si funciona perfecto!! ah otra consulta mas, en el codigo en swingsanimations nombras las animaciones directamente en defaultproperties «SwingAnimations=(«PepeDummy_Swing1″,»PepeDummy_Swing2″)», es posible nombrar la animacion como de un AnimNodeSequence del animtree?

    • Pues no estoy muy seguro de si se podría, en todo caso habría que conectar los AnimNodeSequence directamente al node AnimNodeSlot e intentar activar la entrada deseada en cada momento. No he visto ningún ejemplo en el que se active un AnimNodeSequence directamente, así que no sé si será posible

  2. Qué de código hay que escribir… no me atrevo aún porque apenas sé nada del lenguaje, y copiando y pegando tampoco aprendo nada. Sin una base no me lanzo, y ahora estoy liado con el tema de importación de personajes, animaciones, morphs… es un mundo esto.

    Sobre el tema de la espada, ¿siempre la lleva en el brazo o se puede cambiar con la rueda del ratón al cambiar de arma? ¿Se podrá hacer que la espada se coloque en la espalda cuando no la esté usando, con una animación para cogerla y dejarla?

    • Sí que es un mundo sí, hay que tener paciencia e ir poco a poco xD

      La espada se comporta como un arma, por tanto al cambiar de arma la espada desaparece. Y si, se podría hacer que la espada se quede en la espalda. Para ello usaría un socket, que te permite colocar en él cualquier mesh, de igual manera que se usan para colocar las armas en los brazos.

  3. Creo que olvidaste poner el WeaponBlade en el inventario, al menos a mí no me ha funcionado hasta que no lo he hecho.

    ¿Hay alguna forma de colocar la espada en un socket distinto? En mi caso la espada es una katana y no está alineada con el brazo, forma un ángulo de 90º, y al usarla pues sale doblada.

    • Cierto olvidé poner esa parte del código, gracias!

      En el Socket Manager puedes modificar la rotación del socket para que se alinee correctamente.

  4. No, lo que ocurre es que las dos armas del juego tienen una orientación y la katana otra, así que si usan el mismo socket, una de ellas saldrá orientada donde no debe.

    Al final lo que he hecho ha sido girar la katana para que al colocarse en el socket de las armas quede bien orientada.

    Lo que no me funciona es lo de destruir enemigos con la katana (he hecho lo de la segunda parte). Suena el «HitSound» siempre, aunque no alcance a nada. Con las armas de disparos si que funciona.

    • Aah ok, sí, yo me he encontrado con un caso parecido e hice lo mismo, rotar el propio mesh.

      Si suena el hitSound es que «algo» está colisionando, ya sea un objeto o el propio escenario. Descomenta el for que hay donde pone Debug Lines en la función TraceSwing y se mostrarán las líneas usadas para calcular la colisión con cada golpe. Quizá así veas mejor el problema.

  5. Este es el error que sale, pero ni idea de lo que ocurre…

    [0035.59] ScriptWarning: Control reached the end of non-void function (make certain that all paths through the function ‘return ‘
    TESTWeaponBlade uedpietests.TheWorld:PersistentLevel.TESTWeaponBlade_0
    Function Test.TestWeapon:GetSocketLocation:0091

    • La función en la que se queja es esta:

      function vector GetSocketLocation(Name SocketName, SkeletalMeshComponent SMC)
      {
      local SkeletalMeshSocket SMS;

      if (SMC != none)
      {
      SMS = SMC.GetSocketByName(SocketName);
      if (SMS != none)
      {
      return SMC.GetBoneLocation(SMS.BoneName);
      }
      }
      }

      Puede ser que el SkeletalMeshComponent que le pasas es incorrecto, o el nombre del socket esté mal y por eso no lo encuentre. Prueba haciendo un log de las diferentes variables de la función a ver qué es. Tal que así:

      `log("SocketName:"@SocketName);

  6. En este artículo decías que había que crear dos sockets en el mesh de la espada, uno en la base y otra en la punta de la hoja (en la imagen muestras tres sockets).

    Sin embargo no veo ninguna referencia a esos sockets en el código.

    Supongo que habrá que pasarle esos parámetros para que calcule las colisiones, o tal vez lo coge directamente del mesh. ¿Puede que esté ahí mi fallo?

    En la segunda parte del artículo pone:

    numPoints=5
    baseSocketName=»Base»
    tipSocketName=»Tip»

    ¿Son esos los nombres de los sockets de la espada?

    • Lo que se muestra en la imagen son los nombres de los huesos que forman el esqueleto de la espada, no los sockets.

      En esta parte no se hace referencia a los sockets en el código ya que no se calcula la colisión del arma todavía. Eso es lo que se hace en la parte 2 y ahí ya sí es necesario especificar el nombre de los sockets (en mi caso llamados «Base» y «Tip») para que la función GetSocketLocation los localice. Si tus sockets se llaman diferente seguramente esté ahí el fallo.

  7. He estado cambiando «Base» por los nombres de los sockets de la espada y de la mano, y en ese caso funciona a medias:
    Los enemigos explotan si me pego a ellos, es como si detectara la colisión desde la base pero no hasta el final de la espada.

    Si golpeo «al aire» sigue sonando el histsound y las chispas aparecen en el centro del mapa, como si le pasaras los parámetros 0,0,0. Esto me pasa sin modificar lo de antes también.

    Creo que el fallo está en pasarle el nombre del socket de la punta de la espada, pero no veo por el código dónde hacerlo.

    • Corrijo, veo en el código que base y tip son los nombres de las variables de los sockets. Las he puesto en base y tip y ya no golpea al aire. Eso me pasa por copiar sin mirar mucho el código…

      Destruye a los enemigos pero sólo si me pego a ellos, es decir, parece que solo detecta la colisión con la base, no con la punta.

      Luego veo algunas cosas que no sé si deberían ocurrir:

      – el «hit» ocurre nada más empezar la animación. Como la animación que estoy usando es un poco más larga queda un poco raro, ya que los enemigos explotan antes de ser alcanzados.

      – El pawn puede desplazarse y rotar mientras está en la animación del golpe.

    • xD Suele pasar, y más con este código que es un poco más lioso.

      Para comprobar la colisión correctamente activa las líneas de Debug que te comento y ya en el juego escribe en consola «slomo 0.1» para ralentizar el tiempo y verlo mejor y «show collision» para ver los cilindros de colisión de los enemigos. Se me ocurre que quizá sea muy grande este cilindro y la espada colisione con él antes de lo que querrías.

      La rotación en mi caso es correcta, pero el desplazamiento sí lo evito modificando el estado PlayerWalking de mi PFMPlayerController para que no se desplace cuando la variable bRotationOnly esté activa. Esta variable se activa desde la clase de la espada cuando se inicia el ataque. Lo comento en el tutorial. De manera parecida podrías evitar la rotación durante la animación.

    • Si sigue saliendo el warning anterior debe ser algo del nombre o que el socket no esté bien configurado y la función devuelva la posición 0,0,0.

      Para visualizar mejor las colisiones que se están realizando prueba lo de descomentar el código de debug que te comenté en por arriba, donde pone DebugLines en la función TraceSwing().

  8. Nada, el código está ahora bien. El problema está en otro sitio.

    La comprobación de colisiones se hace justo al principio de la animación. Como tus animaciónes son muy rápidas, con la espada yendo hacia el enemigo desde el principio, funciona bien.

    Mi animación es un poco más larga, casi dos segundos. Primero echa la espada hacia atrás, para luego golpear hacia adelante.
    Como la comprobación de colisión se hace en el primer frame, la espada aún no está golpeando, y sólo detecta colisión si está muy pegada al enemigo.

    He hecho otra animación con la espada golpeando hacia delante desde el frame 0 y funciona perfectamente.

    Lo ideal sería que se pudiera configurar el frame de la animación donde detectar las colisiones, o hacerlo con un timer, o con un notify desde la animación. Así se podría hacer funcionar el sistema con cualquier animación.

    • Si es cierto que la comprobación de la colisión se hace desde el inicio de la animación, ya que en mi caso eso funciona bien, pero lo que si es configurable es la duración de dicha comprobación. En el array que yo llamo SwingIntervals puede configurarse el tiempo que dura la fase de «ataque» de cada animación, y ese es el tiempo que se realiza la comprobación de colisión mientras la animación se ejecuta.

      Extendiendo esta idea de fases, podrías añadir una fase de «preparación» del ataque en la que no se haga la comprobación de la colisión o usar una notificación. De hecho, para los ataques del enemigo Razor, que también son cuerpo a cuerpo, he usado el sistema de las notificaciones en vez de los temporizadores, pues tienen esta fase de preparación del ataque. Seguramente este sistema es más limpio que usar Timers y guardar los tiempos explícitos de la animación pero bueno, en su momento opté por los timers con la espada. Aún no he escrito la entrada sobre eso pero en próximos días espero tenerla.

  9. Sí, aumentando «SwingIntervals» se consigue que golpee con la animación larga, aunque habría que excluir el principio de la animación, tal y como dices.

    Esto ya se va pareciendo al Skyrim 😛

  10. Gracias a ti y tus tutoriales. El modelo de la chica es obra propia, pero el pelo lo pillé de un modelo de Resident Evil, si no recuerdo mal. Total, si esto es sólo para aprender.

    Hoy traté de incluir una animación de parada, para después de la animación de correr y antes de la de idle. Me ayudaron en el foro de Epic, y funcionó, pero el resultado no es tan bueno como esperaba.

    Y ahora estoy liado con las físicas, animar a mano el pelo y otras partes blandas sujetas a la inercia y la gravedad es un poco tedioso y no queda demasiado bien.

    • En teoría no…

      Tenía el problema de que en el editor los bones tenían colisiones pero luego en el juego no. Lo he conseguido arreglar colocando unas variables en el pawn.
      Pero lo raro es que las físicas sólo funcionan después de entrar y salir en un vehículo. Seguramente tenga que colocar algo más en el pawn, algo que aún no conozco.

      El pelo queda bien, mucho mejor que animado a mano. Lo que no sé es cómo cambiar las propiedades físicas, el valor de la gravedad, o que se termine deteniendo porque parece que siempre está en funcionamento.

    • Yo la verdad que aún no he hecho nada de físicas. Sé muy por encima lo básico de que hay que hacer el rigging de las partes blandas a un hueso y luego en el editor indicarle que ese hueso lo simule etc pero realmente no lo he puesto en práctica. Me gustaría aplicárselo al pelo de mi personaje pero como lo considero un detalle lo dejaré para más adelante si me da tiempo. Si al final lo intento te pediré referencias 🙂

  11. Lo que he hecho es activar la opción «always full animweight» para todos los huesos del pelo. También les activé «twist limited» para que no girasen sobre si mismos.
    Con eso ya funcionaba en el editor, pero en el juego no había colisiones. Tuve que añadir estas líneas al pawn que vi por ahí para que hubiese colisiones:

    PhysicsWeight=1
    RBChannel=RBCC_GameplayPhysics
    RBCollideWithChannels=(Default=TRUE,BlockingVolume =TRUE,GameplayPhysics=TRUE,EffectPhysics=TRUE)
    BlockActors=true
    CollideActors=true
    BlockRigidBody=true
    BlockZeroExtent=true
    bUpdateSkelWhenNotRendered=false
    bIgnoreControllersWhenNotRendered=TRUE
    bSkipAllUpdateWhenPhysicsAsleep=TRUE

    Seguro que la mitad sobran 😛

    Y con eso hay físicas con colisiones, pero sólo cuando entro y salgo de un vehículo.

    • Y acabo de darme cuenta que las físicas no funcionan del todo cuando las modificas en el editor y las pruebas en el juego.

      Me han funcionado sólo después de guardar y reiniciar el UDK. Me traía un poco de cabeza eso.

    • Ya encontré la solución a lo de tener que entrar en el vehículo para que se activaran las físicas. Hay que añadir ésto al PostBeginPlay() :

      Mesh.PhysicsAssetInstance.SetFullAnimWeightBonesFixed(FALSE, Mesh);

      Y con eso ya funcionan desde que se inicia el juego. En las líneas de las propiedades he ido anulando las que no eran necesarias, quedando así:

      bHasPhysicsAssetInstance=True
      PhysicsWeight=1
      RBChannel=RBCC_GameplayPhysics
      RBCollideWithChannels=(Default=TRUE,BlockingVolume =TRUE,GameplayPhysics=TRUE,EffectPhysics=TRUE)
      BlockRigidBody=true

  12. También se les puede añadir a los bones afectados un «pysical material» que es lo que ajusta la densidad, la fricción, inercia, y otras cosas.

    Ahora me trae de cabeza algo que ocurre en algunos mapas. Hay una especie de fuerza adicional de gravedad que atrae a los huesos con físicas hacia abajo, a veces de forma constante y otra a base de tironcitos, lo que hace que los huesos estén constantemente oscilando.
    Me pasa por ejemplo en los mapas foliage y necropolis, pero por zonas, hay zonas donde ocurre y otras donde no.

    He estado probando a cambiar todos los valores en el editor de fisicas, poniéndole y quitándole cosas en el default properties y nada. También he probado a crear un gravity volume y tampoco. He ido borrando cosas de los mapas para ver qué era lo que afectaba y nada de nada.

    En el mapa que tengo para pruebas, que es uno de los que saca UDK como template, no ocurre, salvo un pequeño tirón hacia abajo que ocurre cada 34 segundos y sólo afecta a los huesos que tienen activadas las colisiones. Un tirón parecido ocurre en el editor de físicas cuando muevo el ratón sobre el modelo (los pokes desactivados). Supongo que será el mismo problema, sólo que en algunos mapas ocurre cada 34 segundos y en otros constántemente. Pero no encuentro dónde está el problema.

    • Bueno, ya he medio solucionado el tema de las físicas. El problema ocurría cuando había una variación del framerate, cuando la escena estaba más cargada entonces aparecía ese tirón oscilatorio.
      Para solucionarlo he desactivado la opción «Physics ignore Delta Time» y he tenido que reajustar los huesos. Delta Time creo que es una especie de contador de «ticks» desde que el juego se inicia, y por lo visto no es estable cuando el framerate varía, mientras que las físicas si son constantes. Desactivando esa opción desaparece el problema.

      Pero no estoy muy contento, con esta nueva forma funciona peor, los límites de rotación parecen no funcionar y luego hacen extraños en el juego, hay más incrustaciones, el pelo que atraviesa el cuerpo más a menudo, y a veces parece que los huesos con físicas se muevan a la mitad de framerate que el juego.

      Esas cosas con el Delta activado no pasaban, pero… no es muy serio que el tema de las físicas se descujaringue cuando caiga o suba el framerate. Supongo que debe haber una solución, pero después de día y medio googleando no encontré nada salvo desactivar el Delta.

    • El delta es el tiempo que pasa entre un frame y el siguiente, que no tiene por qué ser estable. Me extraña que no haya que tenerlo en cuenta para el cálculo de físicas pues justamente se usa para ajustar movimientos entre frames. Con esto de momento no te puedo ayudar mucho la verdad, no he investigado nada sobre físicas aún.

    • Seguramente por no usar el delta el resultado es peor…
      Las físicas irán en tiempo real y lo otro a lo que dé de sí la máquina. Y por el camino se desincronizan.

      A ver si en el UE4 lo arreglan 😛

  13. Pingback: PFM: Diseño y creación de enemigo Razor (y parte 2) | El Blog de Marcos

  14. Hola, me gustó mucho tu blog, me servirá mucho en proyecyos propios.
    Por ahi tengo una duda con la creacion del Weapon; veo que estas extendiendo de una PFMWeapon que imagino es una clase tuya…podrias dejar tambien las funciones que trae adentro?

    • Vale gracias, esto de la programación no es por ahi mi fuerte, pero con tus tutoriales creo que no tendré mucho problemas. Eventualmente debería estar subiendo videos de mis avances ,aunque por lo pronto solo estoy usando material default del disponible en un kit de principiantes disponible en los recursos del motor.

  15. Hola otra vez, he seguido tus tutoriales y con ayuda de otros recursos monté lo siguiente:

    Pero como podrás ver despues de añadir el código para realizar colisiones y habiendo añadido unos rigid body del content browser no detectan la colisión (las armas de fuego si detectan los golpes) Incluso me di cuenta q si me acerco, aparecen chispas pero los tanques no se mueven ni medio centimetro. ¿ Qué puede estar pasando ? …

    • Tampoco se detectan colisiones con otros objetos del entorno o sólo pasa con los RigidBodies?

      El hecho de que aparezcan chispas me hace pensar que si se detectan las colisiones, pero que te tengas que acercar puede ser que no se esté haciendo bien el trazado de colisiones. En la función TraceSwing hay un bucle for después de un comentario que dice Debug Lines. Si descomentas el bucle quitando /* y */ podrás ver las líneas de trazado que se realizan.

      Una vez que veas que las líneas son correctas y siguen toda la trayectoria, fíjate en la llamada a HitActor.TakeDamage(…) Se le pasa un argumento llamado Momentum que no se inicializa en ningún momento en la función, por lo que vale (0,0,0). Esta variable momentum puedes usarla para darle una velocidad al objeto golpeado. Como ahora está a 0, los objetos no se mueven aunque los golpees.

  16. Ok ya revisando las lineas del trazado del arma, me di cuenta que en realidad solo esta tomando en cuenta la base de la espada, dibujando entonces solo una linea (diferente al tuyo que veo dibuja cinco lineas a lo largo de la espada) y eso que me he fijado de ajustar los nombres de los sockets de la espada que son StartControl y EndControl para baseSocketName y tipSocketName respectivamente.

  17. Amigo, quiero hacer un personaje que solo tenga una espada, (IY posteriormente tener varias y cambiarlas en inventario) pero tengo dos dudas.

    1.- Como agrego el arma al personaje, ya la modele le coloque los huesos, le coloque los sockets, son dos skeletal meshes separados como hago para unirlos???

    2.- Como quedaría la clase Weapon si fuese solo la espada como arma?

    • Hola Miguel.

      En este tutorial se explica cómo agregar el arma al personaje. Cuando equipes el arma se mostrará.

      En caso de sólo usar armas de cuerpo a cuerpo, la espada puede extender directamente de la clase Weapon, sin ser necesario usar mi PFMWeapon.

  18. Pingback: PFM: Diseño y creación de personaje protagonista (parte 3 – Animaciones y armas) | El Blog de Marcos

  19. Hola Marcos, gracias a tu blog, esfuerzo (y peleas con UDK xD) he podido crear este pequeño avance para mi prototipo:

    Pude iñadir mi propio modelo y animaciones correspondientes (corrigiendo por fin el problema que te había comentado anteriormente del trazado de la espada para las colisiones). Usando recursos de la documentación de UDK tambien pude implementar mi propia cámara tipo sidescroller.
    Por ahi todavía estoy un poco liado con los controles melee y la camara pero ahi voy avanzando xD
    Tambien tengo implementado un bot que me persigue y explota al contacto, luego subiré video.

  20. Hola hola una vez mas 😛 ya va tomando mas formita (con respecto a mecanicas)


    Por ahi hay dos cosas que me inquietan un poco:
    1) cuando el dragon ataqca detecta perfecto la colisión de un brazo, pero no del otro.
    2) Una de los cosas que diseñe para las mecanicas fue poder realizar dos tipos de ataque con la espada; es decir, dos Firing mode diferentes, pero los dos de fire type custom. Logré hacer que detectara los dos botones del mouse para hacer el ataque basico…pero no estoy muy seguro como debería hacer para que con botón derecho ejecute otra acción.

    Si no es mucha molestia espero puedas ayudarme con estos detalles 🙂

    • Hola Alberto,
      – Cuando el dragon ataca, tienes en cuenta con qué brazo está atacando para hacer los traces de cada uno por separado?
      – Para hacer un ataque diferente con cada botón puedes consultar la variable CurrentFireMode, accesible en todas las clases que heredan de Weapon, antes de hacer el ataque y así ejecutar una animación diferente en función del valor de dicha variable.

    • Por ahi el arma que mejor entendí fue la UTWeap_PhysicsGun, y lo q hace es sobreescribe la función StartFire y crea un comportamiento si FireModeNum==0 y otra acción para == 1.

      Ahi empezaron mas confusiones, en mi caso añadí en las propiedades estas lineas:
      FiringStatesArray(1)=WeaponFiring
      WeaponFireTypes(1)=EWFT_Custom

      Y lo que hace es q el click derecho continua la secuencia q llevaba con el click izquierdo. Sin embargo no detecta las colisiones con el derecho.

      Despues sobreescribí tambien la funcion StartFire asi:

      simulated function StartFire(byte FireModeNum)
      {
      if(FireModeNum == 0)
      {
      FireAmmunition();
      }else{}
      Super.StartFire(FireModeNum);
      }

      Ahora se daña el sistema y no funcionan bien las animaciones; las envía en desorden y no siempre deja hacer la secuencia completa….
      Entonces no se muy bien como sería ese pedasito para acomodar bien las dos acciones para cada click. Por su parte el segundo ataque no es de combo entonces la unica variación que necesitaría sería añadir uan animación mas al array y decirle en DireAmmunition que no actualice current swing y que siempre utilice la animación que yo le diga.

    • Emmm bueno molestando un poco mas he hecho un avance:

      Lo q hice fue añadir esta condición a FireAmmunitio()
      if (CurrentFireMode ==0)
      if (CurrentFireMode ==1)

      Con esto ya logré hacer q haga la secuencia completa si es 0 (click izquierdo) o un ataque individual si es 1 (click derecho). En ambas condiciones esta el mismo codigo, salvo la actualización del currentSwing en el segndo caso que no necesito que haga.

      El único problema que encuentro es que para el ataque de click derecho, no me esta detectando las colisiones y tampoco esta dibujando las lineas del trazado de la espada =/

    • Hola Alberto, si ya consigues que ejecute la otra animación debe faltar activar el tracing para ese modo de ataque, revisa tu código y haz lo mismo que haces para el modo 0 en el modo 1. Perdona la brevedad pero no tengo mucho tiempo 😦

    • hola dario, me paso el mismo error que a ti, como lo solucione fue borrando

      ———————————————————————-
      // La aceleración (y en consecuencia el movimiento) es diferente según el tipo de arma que lleve el

      Pawn
      if(AzukenPawn(Pawn).WeaponType = PWT_Default || AzukenPawn(Pawn).WeaponType = PWT_Sword)
      {

      ———————————————————————–
      y una } al final de las funciones

      eso si todavia no se si valla a tener problemas pero al menos hace las animaciones junto con el sonido, todavia no le hecho la segunda parte del tutorial

    • En el editor funcionan bien las animaciones sobre el personaje?
      Comprueba que los nombres de los AnimSet, las distintas animaciones, etc, coincidan entre el código y el editor

  21. EXELENTES TUTORIALES!!! te felicito ,se que no es el sitio correcto ,pero seria posible que hicieras algun tutorial ( no importa que no sea exacto) de como lograr un efecto de telekynesis,me gustaria una referencia , bueno no solo a mi supongo que hay varios interesados…saludos!!

    • Hola jhonny, UDK incluye una PhysicsGun que puede servirte de referencia para crear el efecto de telequinesis, ya que permite mover objetos y lanzarlos, échale un vistazo 😉

  22. me han servido muchisimo tus tutoriales , ya he logrado un muy buen nivel en mi juego me falta algo q no encuentro en ningun lado y no me hago idea , quisiera hacer , gancho q tire y se agarre o tipo el arman de batman o algo parecido alguna idea?

Replica a Darìo Morillo Cancelar la respuesta