PFM: Disparando en UDK

Nuestro personaje ya apunta, pero no dispara… ¡Queremos que dispare! En este tutorial veremos cómo funciona el sistema de armas de UDK y conseguiremos que el personaje dispare con un par de armas diferentes que crearemos para la ocasión.

Para introducirse y profundizar en el funcionamiento de las armas en UDK es muy recomendable la lectura de este artículo de udn, donde se explica con bastante detalle.

Además todo lo hecho a continuación está basado en parte en varios tutoriales de Cédric Hauteville (Mougli): Concretamente este, este y este.

Resumiendo por encima el artículo de UDN, nos cuenta que el sistema de armas de UDK no debe entenderse como exclusivo para el uso de armas, sino que su uso podría extenderse fácilmente a cualquier otro objeto no ofensivo como una raqueta en un juego de ténis o cualquier otro objeto que requiere ser equipado para utilizarse, no necesariamente disparando.

UDK provee un sistema de inventario para gestionar las armas que posee el jugador en cada momento, además de otros artículos que pueda utilizar (armaduras, botiquines, objetos especiales…). La clase principal de este sistema es el InventoryManager que se encarga de llevar la cuenta de los objetos del jugador y gestiona cómo se activan y desactivan.

Concretando en el aspecto de las armas, existen 2 tipos básicos de armas en Unreal, las de impacto instantáneo y las de proyectiles:

  • Impacto instantáneo: Como indica el nombre, al disparar, el impacto con el objetivo se produce al instante, sin tener en cuenta ningún tipo de efecto producido por la gravedad. Simplemente se traza un rayo desde el arma al objetivo y se calculan los daños provocados. Este tipo se utiliza para modelar armas tipo láser o ametralladoras.
  • Proyectiles: Obviamente este tipo de arma lanza proyectiles, ya sean cohetes, bolas de energía o piedras. El arma se encarga únicamente de inicar el proyectil, siendo éste mismo el que luego deberá desplazarse y determinar el daño infligido en el objetivo.

Por supuesto tenemos la posibilidad de crear nuestros propios tipos de arma, con un comportamiento totalmente diferente. Además en UDK podemos asignar varios modos de disparo a cada arma, con lo que podríamos combinar los diferentes tipos en un sólo arma.

Basta de rollo, a las armas.

Vamos a crear 2 armas diferentes, una de impacto instantáneo (tipo láser) y otra de proyectiles (cohetes), pues son los dos tipos más comúnes de arma. Para ello, crearemos sus respectivas clases. Sin embargo dado que ambas son armas, hay ciertas características y funciones que comparten y que no necesitamos escribir por duplicado, por lo que crearemos una superclase por encima de ellas que será su clase padre. Esta clase, PFMWeapon, será padre también de cualquier otro arma que incluyamos en el juego.

PFMWeapon

La clase PFMWeapon es la más larga que habrá que crear para esta ocasión, pues tiene muchas características y funciones propias de las armas. Veamos el código y a continuación una breve explicación de las funciones más importantes.


class PFMWeapon extends UDKWeapon;

var byte InventoryGroup;        // Grupo de inventario. Se usa para cambiar de arma

// ****************************************************************
// Instant HIT
// ****************************************************************
var ParticleSystem BeamTemplate;    // Sistema de partículas del rayo mostrado en Instant Hit

var MaterialInterface ExplosionDecal;       // Decal (calcomanía) que deja en las superficies el Instant Hit
var float DecalWidth, DecalHeight;          // Ancho y altura del decal
var float DurationOfDecal;                  // Duración del decal antes de desaparecer
var name DecalDissolveParamName;            // Nombre del parámetro del MaterialInstance para disolver el material

// Determina la posición real del arma
simulated event SetPosition(UDKPawn Holder)
{
    local SkeletalMeshComponent compo;
    local SkeletalMeshSocket socket;
    local Vector FinalLocation;

    compo = Holder.Mesh;

    if (compo != none)
    {
        socket = compo.GetSocketByName('Mano');
        if (socket != none)
        {
            FinalLocation = compo.GetBoneLocation(socket.BoneName);
        }
    }
    SetLocation(FinalLocation);
}

// Calcula la posición real desde donde se disparan los proyectiles
simulated event vector GetPhysicalFireStartLoc(optional vector AimDir)
{
    local SkeletalMeshComponent compo;
    local SkeletalMeshSocket socket;

    compo = Instigator.Mesh;

    if (compo != none)
    {
        socket = compo.GetSocketByName('Mano');
        if (socket != none)
        {
             return compo.GetBoneLocation(socket.BoneName);
        }
    }
}

// Sobreescribe la funcion de Weapon. Procesa los impactos instantaneos.
simulated function ProcessInstantHit(byte FiringMode, ImpactInfo Impact, optional int NumHits)
{
    local MaterialInstanceTimeVarying MITV_Decal;

    // Calcula el daño total y lo aplica al actor
    Super.ProcessInstantHit(FiringMode, Impact, NumHits);

    // Creación del Decal
    MITV_Decal = new(self) class'MaterialInstanceTimeVarying';
    MITV_Decal.SetParent(ExplosionDecal);
    WorldInfo.MyDecalManager.SpawnDecal(MITV_Decal, Impact.HitLocation, rotator(-Impact.HitNormal), DecalWidth, DecalHeight, 10.0, FALSE );
    MITV_Decal.SetScalarStartTime( DecalDissolveParamName, DurationOfDecal );

    // Generación del rayo de Instant Hit
    SpawnBeam(GetPhysicalFireStartLoc() + (FireOffset >> Instigator.GetViewRotation()), Impact.HitLocation, false);
}

// Se llama al equipar el arma
simulated function TimeWeaponEquipping()
{
    AttachWeaponTo( Instigator.Mesh,'Mano' );
    super.TimeWeaponEquipping();
}

// Acopla el arma a la malla del pawn
simulated function AttachWeaponTo( SkeletalMeshComponent MeshComp, optional Name SocketName )
{
    MeshComp.AttachComponentToSocket(Mesh,SocketName);
    // Se coge la iluminación de entorno del Pawn
    Mesh.SetLightEnvironment(PFMPawn(Instigator).LightEnvironment);
}

// Llamado al dejar de usar el arma
simulated function DetachWeapon()
{
    Instigator.Mesh.DetachComponent(Mesh);
    SetBase(None);
    SetHidden(True);
}

// Genera el rayo del arma. Extraído de UTAttachment_ShockRifle
simulated function SpawnBeam(vector Start, vector End, bool bFirstPerson)
{
    local ParticleSystemComponent E;
    local actor HitActor;
    local vector HitNormal, HitLocation;

    if (End == Vect(0,0,0))
    {
        if (!bFirstPerson || (Instigator.Controller == None))
        {
            return;
        }
        End = Start + vector(Instigator.Controller.Rotation) * class'UTWeap_ShockRifle'.default.WeaponRange;
        HitActor = Instigator.Trace(HitLocation, HitNormal, End, Start, TRUE, vect(0,0,0),, TRACEFLAG_Bullet);
        if ( HitActor != None )
        {
            End = HitLocation;
        }
    }
    E = WorldInfo.MyEmitterPool.SpawnEmitter(BeamTemplate, Start);
    E.SetVectorParameter('ShockBeamEnd', End);
    if (bFirstPerson && !class'Engine'.static.IsSplitScreen())
    {
        E.SetDepthPriorityGroup(SDPG_Foreground);
    }
    else
    {
        E.SetDepthPriorityGroup(SDPG_World);
    }
}

defaultproperties
{
    FireOffset=(X=50,Y=0,Z=0)
}

Como vemos, PFMWeapon extiende de la clase UDKWeapon para aprovechar algunas funcionalidades de la clase que ya viene implementada. Basta entonces con sobreescribir las funciones cuyo comportamiento por defecto queremos modificar.

  • SetPosition: Determina la posición real del arma al mostrarse en el escenario. En nuestro caso queremos que el arma se sitúe en la mano derecha del personaje. Para ello utilizamos una característica de UDK llamada sockets.
    Un socket permite definir posiciones en los Skeletal Mesh en las que después podemos colocar otros objetos como armas, sombreros, armaduras o cualquier cosa. Para definir un socket  en el Skeletal Mesh de nuestro personaje debemos abrir su Skeletal Mesh en el editor y acceder al Socket Manager (pfm07_01en el menú del AnimSet Editor). Ahí podremos crear un nuevo socket, darle un nombre y asignarlo a un hueso del esqueleto del personaje. El hueso que asignemos determinará su posición.
    Una vez tengamos definido el socket podremos acceder a él desde el código para poner el arma en su lugar o lo que deseemos. En el caso de la función SetPosition accedemos a la posición del hueso asignado al socket ‘Mano’, y determinamos que esa posición es la del arma.
  • GetPhysicalFireStartLoc: En este caso se calcula la posición desde la que se lanzan los proyectiles del arma en caso de tenerlos.
  • ProcessInstantHit: Procesa los disparos de tipo instantáneo. En esta función nos encargamos de calcular el daño del disparo y aplicarlo al actor que lo recibe, crear el Decal del arma (textura de impacto que se pone en las paredes) y generar el rayo visible del disparo del arma.
  • TimeWeaponEquipping: Se llama cuando el arma es equipada y en nuestro caso simplemente adjuntamos el arma al socket ‘Mano’ llamando a la función:
  • AttachWeaponTo: Adhiere el arma al socket.
  • DetachWeapon: Llamada al dejar de usar este arma, por lo que su función será desconectarla del personaje.
  • SpawnBeam: Función auxiliar que dibuja el rayo para las armas de impacto instantáneo.

Esta es la clase básica, no representa un arma específica sino que agrupa características comunes a todas. PFMWeapon también tiene una serie de variables para que sean las subclases las que les den valor, como el grupo de inventario, el decal a utilizar, etc.

Teniendo esta clase básica bastará con configurar las subclases de esta para tener armas funcionales. Deberemos escoger el modelo que representará el arma en el mundo de juego, el tipo de disparo, intervalo de disparos, dispersión, daño, etc.

PFMWeapon1

Este primer arma será de tipo instantáneo, por lo que visualmente generará un rayo desde el arma hasta el objetivo. Como hereda de PFMWeapon, sólo hay que configurarla correctamente para que actúe como deseamos:


class PFMWeapon1 extends PFMWeapon;

defaultproperties
{
    // Mesh
    Begin Object Class=SkeletalMeshComponent Name=GunMesh
        SkeletalMesh=SkeletalMesh'WP_LinkGun.Mesh.SK_WP_Linkgun_3P'
        HiddenGame=FALSE
        HiddenEditor=FALSE
        Scale=2.0
    End Object
    Mesh=GunMesh
    Components.Add(GunMesh)

    // Configuración del arma
    FiringStatesArray(0)=WeaponFiring       // Estado de disparo para el modo de disparo 0
    WeaponFireTypes(0)=EWFT_InstantHit      // Tipo de disparo para el modo de disparo 0
    FireInterval(0)=0.1                     // Intervalo entre disparos
    Spread(0)=0.05                          // Dispersion de los disparos
    InstantHitDamage(0)=20                  // Daño de Instant Hit
    InstantHitMomentum(0)=50000             // Inercia de Instant Hit

    // Beam
    BeamTemplate=particlesystem'WP_ShockRifle.Particles.P_WP_ShockRifle_Beam'

    // Decal
    ExplosionDecal=MaterialInstanceTimeVarying'WP_RocketLauncher.Decals.MITV_WP_RocketLauncher_Impact_Decal01'
    DecalWidth=128.0
    DecalHeight=128.0
    DurationOfDecal=24.0
    DecalDissolveParamName="DissolveAmount"

    // Grupo de inventario
    InventoryGroup=1
}

PFMWeapon2

Este segundo arma será de proyectiles, por lo que una de las cosas a configurar es la clase que representa al proyectil. Como puede verse es aún más sencilla que un arma de impacto instantáneo, pues al lanzar el proyectil es éste el que se encargará de calcular el impacto y los daños causados, a diferencia de lo que ocurre con un arma de impacto instantáneo.


class PFMWeapon2 extends PFMWeapon;

defaultproperties
{
    // Mesh
    Begin Object Class=SkeletalMeshComponent Name=GunMesh
        SkeletalMesh=SkeletalMesh'WP_RocketLauncher.Mesh.SK_WP_RocketLauncher_3P'
        HiddenGame=FALSE
        HiddenEditor=FALSE
        Scale=2.0
    End Object
    Mesh=GunMesh
    Components.Add(GunMesh)

    // Configuración del arma
    FiringStatesArray(0)=WeaponFiring           // Estado de disparo para el modo de disparo 0
    WeaponFireTypes(0)=EWFT_Projectile          // Tipo de disparo para el modo de disparo 0
    WeaponProjectiles(0)=class'UTProj_Rocket'   // Clase del proyectil
    FireInterval(0)=0.5                         // Intervalo entre disparos
    Spread(0)=0.01                              // Dispersion de los disparos

    // Grupo de inventario
    InventoryGroup=2
}

En este caso hemos usado modelos ya contenidos en UDK pero podríamos usar cualquiera que deseemos para el arma, proyectiles, rayos, sonidos, etc.

PFMInventoryManager

Como ya comentamos el Inventory Manager se encarga de gestionar todos los objetos de los que dispone el Pawn, entre ellos las armas. Crearemos nuestro propio InventoryManager para especificar su comportamiento a nuestra conveniencia.


class PFMInventoryManager extends InventoryManager;

// Devuelve el primer arma que se encuente que coincida con el grupo de inventario
function PFMWeapon GetWeaponByGroup(byte InventoryGroup)
{
    local PFMWeapon Weap;

    ForEach InventoryActors(class'PFMWeapon', Weap)
    {
        if(Weap.InventoryGroup == InventoryGroup)
        {
            return Weap;
        }
    }
    return none;
}

// Cambia de arma. Llamado desde PFMPawn
simulated function SwitchWeapon(byte NewGroup)
{
    local PFMWeapon NewWeapon;

    NewWeapon = GetWeaponByGroup(NewGroup);

    if(NewWeapon != none)
    {
        // TODO: Deberá comprobarse si tiene munición y está cargada
        // if (NewWeapon.HasAmmo())
        SetCurrentWeapon(NewWeapon);
    }
}

defaultproperties
{
    // Se añade una entrada al array PendingFire del InventoryManager por cada tipo de disparo
    PendingFire(0)=0
    PendingFire(1)=0
}

En él definimos la función SwitchWeapon, que servirá para efectuar el cambio entre un arma y otra. Esta función se llamará desde el Pawn como veremos en el siguiente apartado. Además en la sección defaultproperties se añade una entrada al array PendingFire por cada tipo de disparo que tengan las armas. Si no hiciéramos esto las armas no dispararían…

Modificaciones en otras clases

Para acoplar las nuevas clases a nuestro código hay que hacer algunas modificaciones:

PFMPawn

Deberemos añadir las siguientes funciones:


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

// Cambio de arma, llamado desde PFMPlayerController
simulated function SwitchWeapon(byte NewGroup)
{
    if (PFMInventoryManager(InvManager) != None)
    {
        // Se le indica al Manager del Inventario que cambie de arma
        PFMInventoryManager(InvManager).SwitchWeapon(NewGroup);
    }
}

  • AddDefaultInventory: Se llama al comenzar la partida, y se encarga de dar al Pawn su inventario por defecto. En nuestro caso queremos que el Pawn obtenga desde el principio las 2 armas que hemos creado.
  • SwitchWeapon: Invoca el SwitchWeapon del InventoryManager. Se llama desde el controller.

Además en la sección defaultproperties habrá que añadir la siguiente línea para que el Pawn utilice nuestro InventoryManager:


InventoryManagerClass=class'PFM.PFMInventoryManager'

Y una última modificación en PFMPawn. Para que las armas puedan acceder a la iluminación de entorno del Pawn, deberemos guardarla en una variable. Así, declaramos la siguiente variable en PFMPawn


var DynamicLightEnvironmentComponent LightEnvironment;

Y de nuevo en la sección de defaultproperties, le asignaremos la luz de entorno tras crearla (LightEnvironment=MyLightEnvironment) :


// Iluminacion de entorno del Pawn
Begin Object Class=DynamicLightEnvironmentComponent Name=MyLightEnvironment
    bSynthesizeSHLight=false
    bIsCharacterLightEnvironment=false
    bUseBooleanEnvironmentShadowing=FALSE
    InvisibleUpdateTime=1
    MinTimeBetweenFullUpdates=.2
    End Object
Components.Add(MyLightEnvironment)
LightEnvironment=MyLightEnvironment

PFMPlayerController

En el controlador escribiremos la siguiente función que le indicará al Pawn que cambie de arma. Como vemos es una función exec, lo que significa que se ejecuta mediante comandos de consola o con los controles del jugador. Si revisamos los controles en el fichero DefaultInput.ini veremos que esta función se ejecuta al pulsar alguno de los botones numéricos del teclado. La variable T contiene qué número se ha pulsado, y es lo que usará para determinar qué arma hay que utilizar, según el InventoryGroup que se definió en sus propiedades de cada una.


exec function SwitchWeapon(byte T)
{
    if (PFMPawn(Pawn) != None)
    {
        PFMPawn(Pawn).SwitchWeapon(t);
    }
}

PFMGame

Un punto importante es que PFMGame extenderá de UDKGame para evitar que se nos de el inventario por defecto de una partida de DeathMatch de Unreal Tournament.


class PFMGame extends UDKGame;

Como efecto secundario perderemos la interfaz y con ella la mirilla.

PFMEnemyPawn

Para apreciar el efecto destructivo de nuestras armas, modificaremos la respuesta de los enemigos al morir. Por defecto lo que hacen es quedarse congelados durante 25 segundos hasta desaparecer. Sobreescribimos el estado Dying para que desaparezcan instantáneamente cuando su vida llegue a 0.


state Dying
{
    event BeginState(Name PreviousStateName)
    {
        Super.BeginState(PreviousStateName);
        Destroy();
    }
}

Payback Time

¡Es todo! Por fín podemos deshacernos de los pesados robots araña que no dejan de perseguirnos, y además de 2 maneras diferentes!

Con el arma de disparo instantáneo:

Vemos cómo aparece el rayo y los decals que hemos indicado.

Y con el arma de cohetes:

Sería más satisfactorio si los enemigos explotaran en mil pedazos, pero eso lo dejaremos para más adelante!
Banner Blog

Anuncios

43 pensamientos en “PFM: Disparando en UDK

  1. Pingback: PFM: Meeting Mr. Pepe Dummy | El Blog de Marcos

  2. Saludos Marcos.. tengo otro problema … el juego avanza y las armas cambian y disparan y se ven los lacers y todo….

    pero no me aparecen las armas … y pareciera q disparara desde arriba… como arreglo eso ?

    gracias de antemano.

    • Tiene toda la pinta de que no has configurado correctamente el socket. En mi código el socket se llama “Mano” pero eso es porque así lo definí en mi personaje. Si tu estas usando el personaje por defecto de UDK, debes entrar al Socket Manager (En el articulo comento cómo entrar) y buscar el nombre del socket donde se colocan las armas.

      Por otro lado, si únicamente copias y pegas el código que aquí muestro sin estudiarlo no vas a aprender gran cosa ni sabrás modificarlo para que funcione a tu gusto. Te recomiendo que vayas más despacio y procurando entender cada línea y no avances de un artículo al siguiente sin entender el anterior. 🙂

    • okay …. sorry solo q leo y leo pero no todas las cosas se me quedan xD….
      pero a lo menos si me ayudan un monton…. y gracias por lo del socket se me hbaia pasado……

    • y ya pille lo del mal disparo era q el personaje era muy pequeño y le cambie el tamaño .. cuando mencionastes q lso personajes de UDK eran mas grandes…

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

  4. En mi caso las armas se ven todas sin iluminación ambiental, por la parte de sombra se ven totalmente negras hasta en el mapa más iluminado.

    Veo que defaultproperties está la iluminación, pero pese a eso no se ve iluminado. ¿Tal vez habría que ponerlo en el fichero de cada arma en lugar del general?

    • Si, cuando hice este tutorial también tenía ese problema. Encontré la solución pero olvidé actualizar esto.
      Basta con especificar en el Mesh de cada arma que el LightEnvironment sea el definido en la clase general, por ejemplo en PFMWeapon1:
      // Mesh
      Begin Object Class=SkeletalMeshComponent Name=GunMesh
      SkeletalMesh=SkeletalMesh’WP_LinkGun.Mesh.SK_WP_Linkgun_3P’
      HiddenEditor=FALSE
      LightEnvironment=MyLightEnvironment
      Scale=1.5
      End Object

  5. Hecho, pero pasa algo muy raro, la primera vez que saco el arma se ve perfecta, pero si hago un ciclo con la rueda del ratón para pasar todas las armas, la segunda vez que salen aparecen totalmente sin iluminación ambiental, sólo la directa.

    • Vale, veo que no es la única modificación que tuve que hacer. En la función DetachWeapon() elimina la línea:
      Mesh.SetLightEnvironment(None);

    • Vaya, pues eso sí que es raro. Intenta averiguar exactamente haciendo qué o en qué situación falla a ver si sacamos algo..

    • He estado probando y también me pasa xD, por algún motivo de repente desaparece la iluminación de entorno. He estado mirando las clases de UT y viendo cómo la aplican ellos y parece que se quita el problema. Lo que hacen es acceder a la luz de entorno del Pawn y aplicársela al arma cada vez que se equipa.

      Para hacer esto hay que guardar la luz de entorno en nuestro Pawn, creando una variable (en PFMPawn):

      var DynamicLightEnvironmentComponent LightEnvironment;

      Y en las defaultproperties asignarle la luz de entorno después de crearla

      LightEnvironment=MyLightEnvironment

      Luego en PFMWeapon, en la función AttachWeaponTo añadir la línea:

      Mesh.SetLightEnvironment(PFMPawn(Instigator).LightEnvironment);

      Así cada vez que se equipa el arma se le aplica la luz de entorno correspondiente al Pawn.
      Haciéndolo así se puede quitar la luz de entorno del defaultproperties de PFMWeapon, así que esto fuera:

      // Iluminacion de entorno
      Begin Object Class=DynamicLightEnvironmentComponent Name=MyLightEnvironment
      bSynthesizeSHLight=TRUE
      bIsCharacterLightEnvironment=TRUE
      bUseBooleanEnvironmentShadowing=FALSE
      InvisibleUpdateTime=1
      MinTimeBetweenFullUpdates=.2
      End Object
      Components.Add(MyLightEnvironment)

      Y también se quitan las líneas donde ponía LightEnvironment=MyLightEnvironment en cada una de las armas (PFMWeapon1, PFMWeapon2, PFMWeaponBlade…).
      Con esto no me ha vuelto a ocurrir, mira a ver si se te corrige también.

    • Ahora sí, he probado un rato y ya no falla. Y además has simplifcado el código un poco. Gracias, maestro!

  6. Hay un par de cosas que no sé si vas a tocar en el próximo tutorial. Son el sonido al disparar y el muzzle (el fogonazo).

    El sonido al disparar lo he resuelto poniéndolo en la funcion “consumeammo”, donde también he puesto una animación de disparar. Aunque creo que se ponen en otra función, tendré que revisar.

    Sobre los fogonazos no he sacado nada en claro, he estado mirando tutoriales y casi todo es para crear los efectos, pero nada de programación. O códigos que no entiendo y que extienden de UT y dan fallos al ponerlos en mi código. También he mirado las fuentes de UTgame y UDKgame, y en este último prácticamente no hay nada, y en el otro… demasiado enrevesado.
    Lo que busco es algo sencillo como definir en una variable el efecto de alguno de los que incluye UDK, pero no encuentro nada, ni sé si es posible.

    También estoy buscando algún efecto para el impacto de las balas usando “instanthit”. Lo más que he conseguido es poner un decal en la zona de impacto, pero no una pequeña explosión, como las que salen usando proyectiles en lugar de instant.

    ¿Vas a incluir algo de esto en la próxima entrada?

    • No iba a detallarlo mucho pero quizás lo comente un poco o haga una segunda parte de las armas para todos estos efectos. De todas formas te lo pongo por aquí también.

      El sonido es fácil, aunque yo lo he metido en FireAmmunition:

      var SoundCue ShotSound; // Sonido de disparo

      simulated function FireAmmunition()
      {
      Super.FireAmmunition();

      PlayFiringSound();
      }

      simulated function PlayFiringSound()
      {
      if(ShotSound != none)
      {
      Instigator.PlaySound(ShotSound, false, true);
      }
      }

      Para el Muzzle puede usarse la función PlayFireEffects, que se llama desde el Pawn cada vez que se dispara. Sólo hay que crear un emisor de partículas en el punto que se quiera. En UT lo que hacen es crear un Socket en el arma y obtener su posición, yo hice un poco el apaño y le sumo un vector FireOffset a la posición del socket de la mano (rotado convenientemente)

      var ParticleSystem Muzzle; // Sistema de particulas de Muzzle

      simulated function PlayFireEffects( byte FireModeNum, optional vector HitLocation )
      {
      if(Muzzle != None)
      {
      WorldInfo.MyEmitterPool.SpawnEmitter(Muzzle, GetPhysicalFireStartLoc() + (FireOffset >> Instigator.GetViewRotation()), Instigator.GetViewRotation());
      }
      }

      Y por último para que aparezcan partículas en el lugar de impacto instantáneo lo más fácil es hacer algo parecido modificando la función ProcessInstantHit, añadiéndole las partículas:

      var ParticleSystem ImpactParticles; // Sistema de partículas del impacto de instant Hit

      simulated function ProcessInstantHit(byte FiringMode, ImpactInfo Impact, optional int NumHits)
      {
      local MaterialInstanceTimeVarying MITV_Decal;

      // Calcula el daño total y lo aplica al actor
      Super.ProcessInstantHit(FiringMode, Impact, NumHits);

      // Creación del Decal
      MITV_Decal = new(self) class'MaterialInstanceTimeVarying';
      MITV_Decal.SetParent(ExplosionDecal);
      WorldInfo.MyDecalManager.SpawnDecal(MITV_Decal, Impact.HitLocation, rotator(-Impact.HitNormal), DecalWidth, DecalHeight, 10.0, FALSE );
      MITV_Decal.SetScalarStartTime( DecalDissolveParamName, DurationOfDecal );

      // Generación del rayo de Instant Hit
      SpawnBeam(GetPhysicalFireStartLoc() + (FireOffset >> Instigator.GetViewRotation()), Impact.HitLocation, false);

      // Generación de las partículas del impacto
      if(ImpactParticles != None)
      {
      WorldInfo.MyEmitterPool.SpawnEmitter(ImpactParticles, Impact.HitLocation, rotator(Impact.HitNormal));
      }
      }

      Los sonidos y particulas se especifican en las defaultproperties y listo

    • Por cierto todo esto iría en PFMWeapon porque es válido para todas las armas. Luego la gracia está en que cada “sub-arma” redefina las variables del sonido y partículas.

  7. Pff… de verdad que yo había estado mirando cosas más complicadas, y luego eran cuatro líneas….

    Funciona bien, aunque el muzzle no sale donde debiera. Voy a mirar lo del socket en el arma, así parece que será más preciso con cada arma que se use.

    Gracias de nuevo, en cuanto lo tenga subo un vídeo.

  8. Bueno, estoy mirando lo del socket, y me funciona a medias, el muzzle sale en la base del arma y no en el socket. Tengo esto:

    simulated function PlayFireEffects( byte FireModeNum, optional vector HitLocation )
    {
    local vector MuzzleSock; // contiene la punta del arma
    MuzzleSock = GetSocketLocation(‘MuzzleFlashSocket’,SkeletalMeshComponent(Self.Mesh));

    if(Muzzle != None)
    {
    WorldInfo.MyEmitterPool.SpawnEmitter(Muzzle, MuzzleSock, Instigator.GetViewRotation());
    }
    }

    ¿Hay que sumar a MuzzleSock algún offset? El muzzle sale más o menos en la base del arma, incluyendo cuando se desplaza. O sea, que pillar el socket, lo pilla. ¿Quizás haya que sumar el desplazamiento relativo del socket en el arma o algo así? En teoría GetSocketLocation (la función que está en la clase madre Weapon) debería darme la posición absoluta del socket.

    La función la he puesto en el script del arma, no en la clase madre Weapon, porque no sabía como acceder al mesh del arma, siempre obtenía el mesh del pawn o daba error, con self, instigator, owner…

  9. Y aunque mueva de sitio el socket, el muzzle sigue saliendo en el mismo sitio. Es como si en lugar de pillar la posición del socket pillase la posición del bone que lo contiene.

    • Recuerda que si estás usando mi GetSocketLocation lo que se obtiene es la posición del hueso que contiene ese socket.

  10. Sí, era eso. Es que el nombre de la función confunde un poco 😛

    He usado GetSocketWorldLocationAndRotation y ya funciona. Además tenía el efecto del muzzle desplazado en Z 10 unidades, no podía salir bien.

    Ahora voy a ver si hago que los proyectiles partan también del socket en lugar de usar el fireoffset.

  11. Bueno, sólo tuve que cambiar el SpawnBean. He subido un vídeo, aún quedan cosas por pulir, como los sonidos y personalizar un poco los muzzles.

    Además el tema de sacar/guardar el arma lo he hecho en SwitchWeapon. Quise hacerlo con weaponequipping, pero cuando cambiaba el arma no llamaba a la función, y lo hice por otro lado. Luego descubrí que tenía dos weaponequipping en dos scripts diferentes y estaba trabajando con el que no era…Ahora a rehacerlo, pero así se aprende 😛
    Ahora voy a ver si consigo que los enemigos disparen.

  12. Hola Otra vez Marco! Sigo avanzando gracias a tus tutoriales. He logrado Crear el arma (Espada) y adjuntarla al personaje. PERO no se muestra la animación cuando ataca. ten en cuenta que mi clase Weapon.uc la edite como pude para que sea solo esa la arma. Me imagino que mi problema esta en el Animtree pero no se como decirle que se reproduzca esa animacion cuando ataque…Que debo colocar entre (Animtree y AnimBlnendByPhysic)? te dejo el codigo de la clase:

    class TESTWeapon extends UDKWeapon;

    simulated function TimeWeaponEquipping()
    {
    AttachWeaponTo( Instigator.Mesh,’ParaEspada’ );
    super.TimeWeaponEquipping();
    }

    simulated function AttachWeaponTo( SkeletalMeshComponent MeshCpnt, optional Name SocketName )
    {
    MeshCpnt.AttachComponentToSocket(Mesh,SocketName);
    }

    DefaultProperties
    {
    Begin Object class=SkeletalMeshComponent Name=Espada
    SkeletalMesh=SkeletalMesh’CATPaquete.Espada’
    HiddenGame=FALSE
    HiddenEditor=FALSE
    Scale=2.0
    end object
    Mesh=Espada
    Components.Add(Espada)

    FiringStatesArray(0)=WeaponFiring //We don’t need to define a new state
    WeaponFireTypes(0)=EWFT_InstantHit
    FireInterval(0)=0.1
    Spread(0)=0
    }

    • LISTO! La respuesta estaba en el TUTO DE Combate cuerpo a cuerpo, es que no lo entendía, modifique algunas cosas pero despues muestro. Gracias Hermano

  13. Hola Marcos, me encanta como explicas las cosas, sencillo y al grano, muchas gracias por el blog.
    Estoy haciendo cosas con el UDK y necesito ayuda con las armas por que no consigo lo que quiero. En concreto estoy haciendo una escopeta de cartuchos y me gustaria que el arma procesara multiples instanthit en un solo ataque. De esta forma se apreciaria la dispersion del arma con la distancia, ya que cada hit tendria su decal y el efecto de escopeta seria bastante bueno.
    La funcion ProcessInstantHit tiene un parametro opcional NumHits, pero no he conseguido nada dandole un valor y si lo hace pone todos los impactos en el mismo sitio.
    ¿Sabrias ayudarme?

    Muchas gracias por los tutoriales tan buenos que haces.

    • Hola Guille. La función ProcessInstantHit sólo procesa un único disparo. Lo que debes conseguir es que en un sólo disparo se llame varias veces a ProcessInstantHit, o más concretamente a InstantFire, que es la función encargada de calcular a dónde va a ir el disparo teniendo en cuenta la dispersión, etc.

      Para hacerlo lo más fácil que se me ocurre es sobreescribir la función FireAmmunition en tu clase de escopeta, haciendo algo de este estilo:

      simulated function FireAmmunition()
      {
      local int i;

      // Use ammunition to fire
      ConsumeAmmo( CurrentFireMode );

      for(i = 0; i < 8; i++)
      {
      InstantFire();
      }

      NotifyWeaponFired( CurrentFireMode );
      }

      Así en cada disparo se calcularán tantos “perdigones” como pongas en el bucle for.
      Prueba a ver si funciona y me dices 🙂

    • Mil gracias, no habia subido tanto en la herencia de clases como para encontrar esto. Estoy con unas animaciones, pero en cuanto tenga un rato lo pruebo.
      Muchas gracias.

    • No he podido esperarme, llevaba unos dias buscando la solucion, y tus lineas de codigo han funcionado a la primera, si es que el que vale vale :p

      Muchisimas gracias de nuevo, que sepas que lo mismo te rapiño la mejora de la vista en 3ª persona, que yo tambien la estoy usando, pero la tuya esta tela trabajada.
      Tienes un seguidor asiduo, te lo aseguro.

    • De nada! Me alegro que funcione sin problemas. Rapiña lo que quieras, para eso está xD Y cuando tengas algo enseñable deja algún video por aquí 🙂

    • Bueno ya tenemos algo enseñable :p por la cuenta que nos trae,
      es un proyecto de un curso experto de la Universidad de Alicante que esta a un mes vista de terminar y quedan por hacer un monton de cosas pequeñas que se comen toda una tarde entre pruebas y errores, como por ejemplo las animaciones que comente antes.
      La prueba de la escopeta aqui la tienes

  14. Disculpa marcos me podrias ayudar en algo como ago para limitar mi municion es decir definir cuantas balas tendra el arma y como recargar el arma…..encuentro tutoriales desde UTWeapon pero nada que extienda desde UDKWeapon porfavor podrias ayudarme o darme un tuto te lo agradeceria mucho Marcos.

  15. Querido Mark, estoy siguiendo tus tutoriales y me encontré con un problema cuando me encontré con “Dispatando en UDK” está experimentando el siguiente error al compilar la secuencia de comandos ”

    PFMEnemyPawn.uc: Error, Mala definición de clase “” / “” / 143/143.

    Me pregunto qué se puede hacer para solucionarlo.

    Aquí está una imagen de lo que sucede.

    Ah, y si elimino funciona el motor archivo “PFMEnemy”, sin embargo no aparecen las armas (sin embargo, es posible disparar sin mostrar las armas).

  16. Hola, ¿de acuerdo? Gracias por la punta, de hecho me salté este tutorial, no es en el menú UDK en tu blog, muchas gracias ya, pero estoy añadiendo el nuevo código aquí: D

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