PFM: Diseño y creación de enemigo Speeder

Anteriormente ya hemos creado un PNJ, pero era algo de prueba y para aprender. En esta ocasión crearemos el primer personaje que aparecerá en el juego final, así que es un gran día :lol:.

El Speeder es un pequeño robot arácnido cuyo único fin es alcanzar el objetivo (PFMTarget) y autodestruirse para causarle el mayor daño posible. No hará caso del jugador, por lo que su inteligencia será bastante sencilla. Vamos a ver cómo ha sido su desarrollo, desde el diseño inicial a la programación de su comportamiento.

Diseño conceptual

Desde el comienzo el aspecto que se ha buscado es el de un sencillo robot con forma arácnida.

Primeros bocetos guarreros:

SpeederDesign00

Diseño provisional:

SpeederDesign01

Se probaron diversos diseños para la cabeza:

SpeederDesign03

Finalmente se optó por una cabeza más plana y sencilla. El motivo es que se quiere dar la impresión de que el robot es de inteligencia simple, por lo que no debe tener un «cerebro» tan grande:

SpeederDesign02

Modelado 3D

Decidido más o menos el diseño final, se comienza con un modelo inicial básico para comprobar tamaño y proporciones.

speeder00Probándolo en UDK.speeder01 Modelo final del Speeder, ya más refinado.speeder04 Rigging

Para poder animar el modelo hay que crear un esqueleto y aplicarlo al mismo. Este esqueleto no se ve luego, son sólo unas guías para animar.speeder05 speeder06Modelo final en UDKspeeder02Le faltan texturas!

Texturizado

Para texturizar el modelo primero se hace un Unwrap del mismo, que es básicamente desdoblar todos sus polígonos sobre una superficie plana sobre la que se podrá pintar la textura:unwrapUna vez hecho el Unwrap ya se puede pasar a crear las texturas. En este caso he usado 2 texturas: Una para el color difuso (izquierda) y otra para el canal Emisivo (derecha). El canal Emisivo no se ve influenciado por la iluminación, de modo que esas partes de la textura se verán cuando el personaje esté en la oscuridad.MaterialsHe aquí el material creado para el Speeder. La parte más compleja es el canal Emisivo, pues se le aplica una función Seno que varía con el tiempo para que se ilumine de forma intermitente.SpeederMaterial  Y aquí el resultado dentro de UDK:speederFinal En la oscuridad. Como se ve los visores siguen iluminados.speederFinalDarkAnimación

Para el Speeder no es necesaria más que una animación, la de caminar:

Y el AnimTree será muy sencillo:SpeederAnimtreeUnrealScript

Y ahora la parte más «divertida», la programación 😀

Como hemos comentado la inteligencia del Speeder es sencilla. Su objetivo es dirigirse al PFMEnemyTarget y una vez allí autodestruirse para inflingirle daño.

 PFMEnemyTarget

En primer lugar se modifica el PFMEnemyTarget, añadiéndole un cilindro de colisión para determinar cuándo un enemigo está suficientemente cerca. Este cilindro es modificable en el editor.


class PFMEnemyTarget extends Actor
placeable;

// Cilindro que abarca toda la zona objetivo
var() editconst const CylinderComponent    CylinderComponent;

defaultproperties
{
    // Cilindro que define la zona objetivo
    Begin Object Class=CylinderComponent NAME=CollisionCylinder
        CollideActors=true
        CollisionRadius=+0200.000000
        CollisionHeight=+0040.000000
        bAlwaysRenderIfSelected=true
    End Object
    CollisionComponent=CollisionCylinder
    CylinderComponent=CollisionCylinder
    Components.Add(CollisionCylinder)

    // Sprite para verlo en el editor
    Begin Object Class=SpriteComponent Name=Sprite
        Sprite=Texture2D'EditorResources.LookTarget'
        HiddenGame=False
    End Object
    Components.Add(Sprite)

    bCollideActors=true

}

PFMEnemyController

En PFMEnemyController ya tenemos el comportamiento necesario para navegar por el mapa y dirigirse al objetivo, por lo que el controlador del Speeder heredará de esta clase directamente. No obstante se ha añadido una función para notificar al controlador que el Pawn que posee ha llegado al objetivo. Esta función pasa al estado AtFinalTarget, que en PFMEnemyController está vacío, pero en el controlador del Speeder lo sobreescribiremos para que se inice la autodestrucción.

// Cada enemigo hará cosas diferentes
state AtFinalTarget
{
}

// Usada por el Pawn para notificar que se ha colisionado con el Target
function NotifyTouchTarget(PFMEnemyTarget target)
{
    if(target == FinalTarget)
    {
        GoToState('AtFinalTarget');
    }
}

 PFMEnemyPawn

Esta clase si se ha modificado sustancialmente. Código completo:


class PFMEnemyPawn extends UDKPawn
placeable;

// Efectos de destrucción
var ParticleSystem DestructionExplosion;
var SoundCue ExplosionSound;
var float DestructionDamage;    //Daño de destrucción
var float DestructionDamageRadius;  //Radio del daño de destrucción

event Touch( Actor Other, PrimitiveComponent OtherComp, vector HitLocation, vector HitNormal )
{
    Super.Touch(Other,OtherComp,HitLocation,HitNormal);
    if(PFMEnemyTarget(Other) != None)
    {
        PFMEnemyController(Controller).NotifyTouchTarget(PFMEnemyTarget(Other));
    }
}

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

    Begin:
    // Efectos de destrucción
    SpawnDestructionEffects();
    // Daño radial
    HurtRadius(DestructionDamage,DestructionDamageRadius,class'UTDmgType_Rocket',50000,Location);
    // Destrucción
    Destroy();
}

// Activa los efectos de destrucción
function SpawnDestructionEffects()
{
    // Particulas de explosión
    if(DestructionExplosion != None)
    {
        WorldInfo.MyEmitterPool.SpawnEmitter(DestructionExplosion, Location, rotator(vect(0.0,0.0,1.0)));
    }
    // Sonido de destrucción
    if (ExplosionSound != None)
    {
        PlaySound(ExplosionSound, true);
    }
}

defaultproperties
{
    // Iluminación de entorno del Pawn
    Begin Object Class=DynamicLightEnvironmentComponent Name=MyLightEnvironment
        bSynthesizeSHLight=TRUE
        bIsCharacterLightEnvironment=TRUE
        bUseBooleanEnvironmentShadowing=FALSE
        InvisibleUpdateTime=1
        MinTimeBetweenFullUpdates=.2
    End Object
    Components.Add(MyLightEnvironment)

    // SkeletalMesh
    Begin Object Class=SkeletalMeshComponent Name=EnemyMesh
        SkeletalMesh=SkeletalMesh'Test.Enemy.Enemy'
        AnimSets(0)=AnimSet'Test.Enemy.EnemyAnimSet'
        AnimTreeTemplate=AnimTree'Test.Enemy.EnemyAnimTree'
        LightEnvironment=MyLightEnvironment
        //bEnableSoftBodySimulation=True
        //bSoftBodyAwakeOnStartup=True
        bAcceptsLights=True
        //Scale3D=(X=0.25,Y=0.25,Z=0.5)
    End Object
    Mesh=EnemyMesh
    Components.Add(EnemyMesh)

    // Cilindro de colisión
    Begin Object Name=CollisionCylinder
        CollisionRadius=40.0
        CollisionHeight=25.0
        BlockNonZeroExtent=true
        BlockZeroExtent=true
        BlockActors=true
        CollideActors=true
    End Object
    CollisionComponent=CollisionCylinder
    Components.Add(CollisionCylinder)

    // Se especifica el controlador
    ControllerClass=class'PFM.PFMEnemyController'

    // Efectos de destrucción
    DestructionExplosion=ParticleSystem'WP_RocketLauncher.Effects.P_WP_RocketLauncher_RocketExplosion'
    ExplosionSound=SoundCue'A_Character_BodyImpacts.BodyImpacts.A_Character_RobotImpact_BodyExplosion_Cue'
    DestructionDamage=10
    DestructionDamageRadius=60

    GroundSpeed=400.0           //Velocidad máxima en suelo
    RotationRate=(Pitch=40000,Yaw=40000,Roll=40000)     // Ratio de rotación
    SightRadius=+05000.000000                           // Max distancia de vista

}

Se ha añadido el evento Touch para detectar cuándo un Pawn ha alcanzado su objetivo. En ese momento se invoca la función NotifyTouchTarget de PFMEnemyController. Se ha modificado el estado Dying para que al morir se genere una explosión y un sonido, evitando que el personaje desaparezca sin mas.

 PFMEnemyControllerSpeeder

El controlador del Speeder es muy sencillo. Al heredar todo el comportamiento de movimiento de PFMEnemyController solo hay que añadir que cuando entre al estado AtFinalTarget inicie la autodestrucción, de lo que se encarga el Pawn.


class PFMEnemyControllerSpeeder extends PFMEnemyController;

state AtFinalTarget
{
    Begin:
    SelfDestroy();
}

function SelfDestroy()
{
    PFMEnemyPawnSpeeder(Pawn).InitSelfDestroy();
}

defaultproperties
{
}

PFMEnemyPawnSpeeder

Como es común con otros personajes, en el Pawn definimos el SkeletalMesh y demás componentes. En este caso además, se incluye la funcionalidad para autodestruirse que posee el Speeder. Al activar el estado SelfDestroy se reproduce un sonido y hace que el Speeder se destruya activando el estado Dying (heredado de PFMEnemyPawn)


class PFMEnemyPawnSpeeder extends PFMEnemyPawn;

var SoundCue ArmedSound;        //Sonido previo a autodestrucción

state SelfDestroy
{
    Begin:
    PlaySound(ArmedSound, true);
    Sleep(0.5);
    GoToState('Dying');
}

function InitSelfDestroy()
{
    GoToState('SelfDestroy');
}

defaultproperties
{
    // SkeletalMesh
    Begin Object Name=EnemyMesh
        SkeletalMesh=SkeletalMesh'Enemies.Speeder.Speeder'
        AnimSets(0)=AnimSet'Enemies.Speeder.AS_Speeder'
        AnimTreeTemplate=AnimTree'Enemies.Speeder.AT_Speeder'
        LightEnvironment=MyLightEnvironment
        PhysicsAsset=PhysicsAsset'Enemies.Speeder.Speeder_Physics'
        //bEnableSoftBodySimulation=True
        //bSoftBodyAwakeOnStartup=True
        bAcceptsLights=True
        //Scale3D=(X=0.25,Y=0.25,Z=0.5)
    End Object
    //Components.Add(EnemyMesh)

    // Cilindro de colisión
    Begin Object Name=CollisionCylinder
        CollisionRadius=20.0
        CollisionHeight=13.0
        BlockNonZeroExtent=true
        BlockZeroExtent=true
        BlockActors=true
        CollideActors=true
    End Object
    CollisionComponent=CollisionCylinder
    Components.Add(CollisionCylinder)

    // Se especifica el controlador
    ControllerClass=class'PFM.PFMEnemyControllerSpeeder'

    ArmedSound=SoundCue'A_Vehicle_Cicada.SoundCues.A_Vehicle_Cicada_TargetLock'

    GroundSpeed=200.0           //Velocidad máxima en suelo
    MaxStepHeight=15.0
    DrawScale=1
}

Eso es todo! Veamos qué tal funciona el Speeder.


Banner Blog

11 comentarios en “PFM: Diseño y creación de enemigo Speeder

    • El unwrapping de un modelo es un asunto más relacionado con el modelado que con UDK, y hay muchas maneras de hacerlo. Según el programa de modelado que se use habrá diferentes técnicas, pero es un tema muy común, por lo que hay muchos tutoriales sobre ello en internet.
      El objetivo es asignar a cada vértice del modelo una coordenada de la textura, o visto de forma inversa, desdoblar el modelo y aplanarlo sobre la textura, parecido a como se hacen los patrones de la ropa.

  1. Muy buen post. Me ha gustado mucho como pasas de la parte del diseño conceptual (bocetos) al modelado (supongo en 3ds Max) y posteriormente la importación al UDK. La parte de programación a mi me gusta menos, pero lo expones todo bastante claro.

    A la espera del siguiente

    • Gracias Alejandro 🙂
      Ciertamente la programación es la parte menos amigable, sobre todo para el que no le guste, pero es la única manera de dar «vida» a los personajes.
      Saludos

  2. Saludos Marcos ….

    me habia puesto inactivo un tiempo ya q me enferme y estuve maluco un tiempo.
    pero volvi para q me aclares mas dudas xd.. espero no te moleste XD…

    en esta parte tengo un problema…. q cuando llegan al punto indicado los uñecos no estallan….
    pero les puedo matar yo y funciona de hasta el sonido hasta la explosion….

    pero por mas q le eche cabeza y le alla modificado quede igual….

    y no se si tambien hacen daño.. las arañitas … aunque creo q en un parte dice un radio de daño tambien… espero no estar equivocado…

    Gracias por todo ^_^

    • Cuando te atasques en algo que no funciona como debería debes intentar detectar hasta qué punto está funcionando. En este caso como has visto, lo primero que se llama es el evento Touch del Pawn. Este evento comprueba si la araña ha llegado al target y en ese caso indica al controller que ha llegado, lo que hace que posteriormente se destruya. Si como dices al llegar al target no se destruye, será que un punto de la cadena no está funcionando, lo que debemos saber es cuál. Para averiguarlo se me ocurre por ejemplo que llames a Destroy() directamente en el evento Touch. De este modo, si verdaderamente entra en ese evento, la araña se destruirá. Si no se destruye, significará que no se está activando el evento Touch y que por tanto no está entrando realmente en el target por algún motivo, no sé si me explico. Si has usado el código tal cual debe ser que no entra en Touch porque si lo hiciera la araña se destruiría. Quizá el target no está bien posicionado? Haz algunas pruebas a ver.

  3. Pingback: PFM: Diseño y creación de enemigo Razor | El Blog de Marcos

  4. El diseño final de la cabeza del Speeder parece un producto de apple, o un robot aspirador de esos automaticos. xD Me ha encantado.

    Por cierto, ¿En que ordenador realizas el trabajo?¿Puedes poner mas o menos sus prestaciones?

    Gracias.

    • Gracias xD. Sí, mi intención era que tuvieran un aspecto muy limpio y sencillo, casi minimalista, no que fueran los típicos robots llenos de tornillos por todos lados. Quizá por eso recuerda a un producto Apple xD
      Mi ordenador es un i5, 8GB de RAM y una 560 Ti. No es la repera pero tira bastante bien de momento 😉

  5. Muy buenas Marcos y ya que estamos feliz año (mejor tarde que nunca) repasando tus tutos me e encotrado con algo, nose si estare equivocado pero para que funcione bien el «PFMEnemyTarget» en la linea 21 en lugar de ser «Begin Object Name=Sprite» no deberia ser «Begin Object Class=SpriteComponent Name=Sprite»? o talvez no afecta?

    • Hola Manu, feliz año igualmente! 🙂
      Efectivamente tienes razón! No sé por qué WordPress siempre me elimina esa parte de «Class=…» al poner el código y tengo que corregirlo manualmente, pero en este caso se me pasó. Gracias por señalarlo!

Deja un comentario