Mostrando postagens com marcador UnrealScript. Mostrar todas as postagens
Mostrando postagens com marcador UnrealScript. Mostrar todas as postagens

quinta-feira, 28 de junho de 2012

Conclusão de "UnrealScript para Iniciantes"


Em 1999, tive o meu primeiro contato com a Unreal Engine através do jogo Unreal Tournament. Fiquei muito impressionado com os gráficos, música, inteligência artificial, armas e dinâmica do jogo. Quando me lembro deste jogo, automaticamente a música de Introdução e a voz da narradora no início do jogo começam a ecoar em minha mente.

Hoje em dia, admiro a Unreal Engine por uma perspectiva de programador. É fascinante a tecnologia e complexidade existentes no núcleo da Unreal Engine que foi elaborada pela mente brilhante de Tim Sweeney.

Gostaria de agradecer a Epic Games por ter criado essa incrível engine e ter disponibilizado ela através do UDK que permitiu que os sonhos de muitas pessoas se tornassem possíveis.

"The time has come to prove you are the best.
To crush your enemies;

to win the Tournament."

(Unreal Tournament 1 Intro)

quarta-feira, 27 de junho de 2012

Platformer Kit: SPG_AIPawn class (UnrealScript)

A classe SPG_AIPawn representa os robôs que atacam o jogador no Platform Kit. As variáveis definidas para esta classe são as seguintes:
class SPG_AIPawn extends Pawn
      placeable;

// Dynamic light environment component to help speed up lighting calculations for the pawn
var(Pawn) const DynamicLightEnvironmentComponent LightEnvironment;

// Ground speed of the pawn, display as Ground Speed in the editor
var(Pawn) const float UserGroundSpeed<DisplayName=Ground Speed>;

// Explosion particle system to play when blowing up
var(Pawn) const ParticleSystem ExplosionParticleTemplate;

// Explosion sound to play
var(Pawn) const SoundCue ExplosionSoundCue;

// Explosion damage amount 
var(Pawn) const int ExplosionDamage;

// Archetyped pick up to drop
var(Pawn) const archetype DroppedPickup ArchetypedPickup;

// Percentage chance to drop pickups
var(Pawn) const float ChanceToDropPickup;

// Current enemy
var Actor Enemy;

Existe um Archetype para a classe SPG_AIPawn. A imagem abaixo mostra as variáveis específicas da classe SPG_AIPawn.


A função Tick() é executada a cada frame. Nesta função é armazenada uma referência ao jogador na variável Enemy (inimigo), caso isto ainda não tenha sido feito.

Depois disso, caso o robô já esteja no solo andando (Physics == PHYS_Walking), ele obtém a localização do jogador e ajusta sua velocidade e rotação para mover em direção ao jogador.
simulated function Tick(float DeltaTime)
{
    local PlayerController PlayerController;
    local Vector Direction;
    local Rotator NewRotation;

    Super.Tick(DeltaTime);

    // If we don't have an enemy yet...
    if (Enemy == None)
    {
        // Find the player controller in the world
        PlayerController = GetALocalPlayerController();

        if (PlayerController != None && PlayerController.Pawn != None)
        {
            // Set the enemy to the player controller's pawn
            Enemy = PlayerController.Pawn;
        }
    }
    else if (Physics == PHYS_Walking)
    {
        // Find the direction in order for me to look at my enemy
        Direction = Enemy.Location - Location;

        // Only need to use the yaw from the direction
        NewRotation = Rotator(Direction);
        NewRotation.Pitch = 0;
        NewRotation.Roll = 0;

        // Set the rotation so that I look at the enemy
        SetRotation(NewRotation);

        // Set my velocity, so I move towards the enemy
        Velocity = Normal(Enemy.Location - Location) * UserGroundSpeed;

        // Set my acceleration, so I move towards the enemy
        Acceleration = Velocity;
    }
}



Quando um robô colide com o jogador a função Bump() é executada. Para mais informações sobre colisão leia o artigo: "Colisão em UnrealScript".

O jogador, que é representado pela variável Other nesta função, recebe o dano cujo valor está definido na variável ExplosionDamage. Na imagem do Archetype podemos ver que o valor padrão desta variável é 5.

O efeito com partículas que representa a explosão é acionado e é reproduzido o som da explosão armazenado na variável ExplosionSoundCue. Para mais informações sobre SoundCue, leia o artigo: "Tocando Sons em UnrealScript".
event Bump(Actor Other, PrimitiveComponent OtherComp, Vector HitNormal)
{
    Super.Bump(Other, OtherComp, HitNormal);

    if (SPG_PlayerPawn(Other) != None)
    {
        // Apply damage to the bumped pawn
        Other.TakeDamage(ExplosionDamage, None, Location, Vect(0, 0, 0), class'DamageType');

        // Play the particle effect
        if (ExplosionParticleTemplate != None)
        {
            WorldInfo.MyEmitterPool.SpawnEmitter(ExplosionParticleTemplate, Location);
        }

        // Play the explosion sound
        if (ExplosionSoundCue != None)
        {
            PlaySound(ExplosionSoundCue);
        }

        // Destroy the pawn
        Destroy();
    }
}


Quando um robô é eliminado pelo jogador, a função PlayDying() é executada. Nesta função a física do robô é definida como PHYS_RigidBody para que ele caia no chão.

É verificada a possibilidade de o robô deixar cair um item de energia, que é representada pela classe SPG_HealthPickup. Este teste é feito com a expressão: "FRand() <= ChanceToDropPickup". FRand() é uma função do UnrealScript que retorna um valor aleatório entre 0.0 e 1.0. A variável ChanceToDropPickup está definida no Archetype com o valor "0.3". Isto significa que existe 30% de chance de que um item de energia caia do robô.
simulated function PlayDying(class<DamageType> DamageType, vector HitLoc)
{
    local DroppedPickup DroppedPickup;

    Mesh.MinDistFactorForKinematicUpdate = 0.0;
    Mesh.ForceSkelUpdate();
    Mesh.SetTickGroup(TG_PostAsyncWork);
    CollisionComponent = Mesh;
    CylinderComponent.SetActorCollision(false, false);
    Mesh.SetActorCollision(true, false);
    Mesh.SetTraceBlocking(true, true);
    SetPawnRBChannels(true);

    SetPhysics(PHYS_RigidBody);

    Mesh.PhysicsWeight = 1.f;

    if (Mesh.bNotUpdatingKinematicDueToDistance)
    {
        Mesh.UpdateRBBonesFromSpaceBases(true, true);
    }

    Mesh.PhysicsAssetInstance.SetAllBodiesFixed(false);
    Mesh.bUpdateKinematicBonesFromAnimation = false;
    Mesh.WakeRigidBody();

    // Set the actor to automatically destroy in ten seconds.
    LifeSpan = 10.f;

    // Chance to drop a pick up
    if (ArchetypedPickup != None && FRand() <= ChanceToDropPickup)
    {
        // Spawn a dropped pickup
        DroppedPickup = Spawn(ArchetypedPickup.Class,,, Location,, ArchetypedPickup);

        if (DroppedPickup != None)
        {
            // Set the dropped pick up to falling
            DroppedPickup.SetPhysics(PHYS_Falling);

            // Set the velocity of the dropped pickup to the toss velocity
            DroppedPickup.Velocity.X = 0;
            DroppedPickup.Velocity.Y = 0;
            DroppedPickup.Velocity.Z = RandRange(200.f, 250.f);
        }
    }
}



terça-feira, 26 de junho de 2012

Platformer Kit: SPG_Weapon class (UnrealScript)

A classe SPG_Weapon representa a arma utilizada no Platformer Kit. Ela é baseada na Link Gun do Unreal Tournament, mas possui apenas a opção de tiro com projétil.

Se você não conhece a classe Weapon, dê uma lida no artigo: "Classe Weapon (UnrealScript)".

As variáveis da classe SPG_Weapon estão disponíveis para modificação no editor.
class SPG_Weapon extends Weapon;

// Name of the socket which represents the muzzle socket
var(Weapon) const Name MuzzleSocketName;

// Particle system representing the muzzle flash
var(Weapon) const ParticleSystemComponent MuzzleFlash;

// Projectile classes that this weapon fires. DisplayName lets the editor show this as WeaponProjectiles
var(Weapon) const array< class<Projectile> > Projectiles<DisplayName=Weapon Projectiles>;

// Sounds to play back when the weapon is fired
var(Weapon) const array<SoundCue> WeaponFireSounds;

Também foi criado um Archetype para a classe SPG_Weapon. A imagem abaixo mostra os valores de algumas variáveis.



A classe SPG_Weapon utiliza dois Sockets. Um para marcar a posição no modelo 3D da arma de onde o tiro será disparado. O outro Socket pertence a classe SPG_PlayerPawn e marca a posição onde a arma deve ficar em relação ao jogador. Para entender mais sobre Sockets, dê uma lida no artigo da UDN: "Skeletal Mesh Sockets".

Na função PostBeginPlay() o Componente de Sistema de Partículas representado pela variável "MuzzleFlash" é anexado ao Socket da arma. A arma é anexada ao jogador na função ClientGivenTo().
simulated event PostBeginPlay()
{
    local SkeletalMeshComponent SkeletalMeshComponent;

    Super.PostBeginPlay();

    if (MuzzleFlash != None)
    {
        SkeletalMeshComponent = SkeletalMeshComponent(Mesh);
        if (SkeletalMeshComponent != None && SkeletalMeshComponent.GetSocketByName(MuzzleSocketName) != None)
        {
            SkeletalMeshComponent.AttachComponentToSocket(MuzzleFlash, MuzzleSocketName);
        }
    }
}

Uma das funções que é executada quando a arma é disparada é a PlayFireEffects(). Ela é responsável por ativar o Sistema de Partículas e reproduzir o som de disparo da arma.

Para mais informações sobre Sistemas de Partículas, acesse o artigo da UDN: "Particles Systems".

Segue abaixo a imagem de quando a arma é disparada e o código da função PlayFireEffects().

simulated function PlayFireEffects(byte FireModeNum, optional vector HitLocation)
{
    if (MuzzleFlash != None)
    {
        // Activate the muzzle flash
        MuzzleFlash.ActivateSystem();
    }

    // Play back weapon fire sound if FireModeNum is within the array bounds and if the 
    // weapon fire sound in that array index is not none
    if (FireModeNum < WeaponFireSounds.Length && WeaponFireSounds[FireModeNum] != None && Instigator != None)
    {
        Instigator.PlaySound(WeaponFireSounds[FireModeNum]);
    }
}

A outra função que é executada quando a arma é disparada é a ProjectileFire(). Ela é responsável por criar um novo projétil e iniciá-lo na posição e direção corretas.

A posição do projétil é obtida a partir da função GetWeaponStartTraceLocation() da classe SPG_PlayerPawn, que é representada no código pela variável Instigator.
simulated function Projectile ProjectileFire()
{
    local Vector SpawnLocation;
    local Rotator SpawnRotation;
    local class<Projectile> ProjectileClass;
    local Projectile SpawnedProjectile;

    // tell remote clients that we fired, to trigger effects
    IncrementFlashCount();

    // Only allow servers to spawn projectiles
    if (Role == ROLE_Authority)
    {
        // This is where we would spawn the projectile
        SpawnLocation = Instigator.GetWeaponStartTraceLocation();

        // This is the rotation we should spawn the projectile
        SpawnRotation = GetAdjustedAim(SpawnLocation);

        // Get the projectile class
        ProjectileClass = GetProjectileClass();
        if (ProjectileClass != None)
        {
            // Spawn the projectile setting the projectile's owner to myself
            SpawnedProjectile = Spawn(ProjectileClass, Self,, SpawnLocation, SpawnRotation);

            // Check if we've spawned the projectile, and that it isn't going to be deleted
            if (SpawnedProjectile != None && !SpawnedProjectile.bDeleteMe)
            {
                // Initialize the projectile
                SpawnedProjectile.Init(Vector(SpawnRotation));
            }
        }
      
        // Return it up the line
        return SpawnedProjectile;
    }

    return None;
}

segunda-feira, 25 de junho de 2012

Platformer Kit: SPG_PlayerPawn class (UnrealScript)

A classe SPG_PlayerPawn representa o personagem do jogador. Se você não conhece a classe Pawn dê uma lida no artigo: "Classe Pawn (UnrealScript)".

As variáveis abaixo da classe SPG_PlayerPawn estão disponíveis para modificação no editor.
class SPG_PlayerPawn extends Pawn;

// Dynamic light environment component to help speed up lighting calculations for the pawn
var(Pawn) const DynamicLightEnvironmentComponent LightEnvironment;

// How fast a pawn turns
var(Pawn) const float TurnRate;

// How high the pawn jumps
var(Pawn) const float JumpHeight;

// Socket to use for attaching weapons
var(Pawn) const Name WeaponSocketName;

// Height to set the collision cylinder when crouching
var(Pawn) const float CrouchHeightOverride<DisplayName=Crouch Height>;

// Radius to set the collision cylinder when crouching
var(Pawn) const float CrouchRadiusOverride<DisplayName=Crouch Radius>;

Também foi criado um Archetype para a classe SPG_PlayerPawn. A imagem abaixo mostra os valores de algumas variáveis.


A função FaceRotation() da classe SPG_PlayerPawn é chamada a partir da função UpdateRotation() da classe SPG_PlayerController.

Nesta função serão ajustadas dois tipos de Rotação: a rotação da mira e a rotação do personagem.

A rotação da mira é ajustada a partir da variável "AimNode.Aim.Y" baseada no valor da variável Pitch da Rotação que é passada como parâmetro. A variável AimNode é do tipo AnimNodeAimOffset.

A rotação do personagem é feita baseada no valor da variável Yaw da Rotação que é passada como parâmetro, utilizando a função SetRotation() da classe Pawn.
simulated function FaceRotation(Rotator NewRotation, float DeltaTime)
{
    local Rotator FacingRotation;

    // Set the desired yaw the new rotation yaw
    if (NewRotation.Yaw != 0)
    {
        DesiredYaw = NewRotation.Yaw;
    }

    // If the current yaw doesn't match the desired yaw, then interpolate towards it
    if (CurrentYaw != DesiredYaw)
    {
        CurrentYaw = Lerp(CurrentYaw, DesiredYaw, TurnRate * DeltaTime);
    }

    // If we have a valid aim offset node
    if (AimNode != None)
    {
        // Clamp the current pitch to the view pitch min and view pitch max
        CurrentPitch = Clamp(CurrentPitch + NewRotation.Pitch, ViewPitchMin, ViewPitchMax);

        if (CurrentPitch > 0.f)
        {
            // Handle when we're aiming up
            AimNode.Aim.Y = float(CurrentPitch) / ViewPitchMax;
        }
        else if (CurrentPitch < 0.f)
        {
            // Handle when we're aiming down
            AimNode.Aim.Y = float(CurrentPitch) / ViewPitchMin;
           
            if (AimNode.Aim.Y > 0.f)
            {
                AimNode.Aim.Y *= -1.f;
            }
        }
        else
        {
            // Handle when we're aiming straight forward
            AimNode.Aim.Y = 0.f;
        }
    }

    // Update the facing rotation
    FacingRotation.Pitch = 0;
    FacingRotation.Yaw = CurrentYaw;
    FacingRotation.Roll = 0;

    SetRotation(FacingRotation);
}

A função DoJump() é chamada para realizar o pulo do personagem. Ela leva em conta os diferentes modos de Física que o personagem pode se encontrar.

Em essência, a velocidade no eixo Z (para cima) recebe o valor que se encontra na variável JumpHeight que foi definido nesta classe e que pode ser ajustada no Archetype. Depois é verificado se o jogador se encontra em uma plataforma móvel. Caso seja verdade então a velocidade da plataforma (Base.Velocity.Z) influencia a velocidade do pulo do jogador.
function bool DoJump(bool bUpdating)
{
    if (bJumpCapable && !bIsCrouched && !bWantsToCrouch && (Physics == PHYS_Walking || Physics == PHYS_Ladder || Physics == PHYS_Spider))
     {
        if (Physics == PHYS_Spider)
        {
            Velocity = JumpHeight * Floor;
        }
        else if (Physics == PHYS_Ladder)
        {
            Velocity.Z = 0.f;
        }
        else
        {
            Velocity.Z = JumpHeight;
        }

        if (Base != None && !Base.bWorldGeometry && Base.Velocity.Z > 0.f)
        {
            Velocity.Z += Base.Velocity.Z;
        }

        SetPhysics(PHYS_Falling);
        return true;
    }

    return false;
}


sexta-feira, 22 de junho de 2012

Platformer Kit: SPG_PlayerController class (UnrealScript)

A classe SPG_PlayerController é responsável por adaptar os comandos do jogador para o estilo Plataforma.

Se você não conhece a classe Controller, dê uma lida no artigo: "Classe Controller (UnrealScript)".

Uma das funções que é sobrescrita é a UpdateRotation() cujo código está abaixo.
function UpdateRotation(float DeltaTime)
{
    local Rotator DeltaRot;

    // Set the delta rotation to that of the desired rotation, as the desired rotation represents
    // the rotation derived from the acceleration of the pawn
    DeltaRot = DesiredRotation;

    // Set the delta pitch to read from the look up input
    DeltaRot.Pitch = PlayerInput.aLookUp;

    // Never need to roll the delta rotation
    DeltaRot.Roll = 0;

    // Shake the camera if necessary
    ViewShake(DeltaTime);

    // If we have a pawn, update its facing rotation
    if (Pawn != None)
    {
        Pawn.FaceRotation(DeltaRot, DeltaTime);
    }
}

Existem dois tipos de Rotações que podem ocorrer no Platformer Kit. Uma é a rotação que ocorre quando o jogador muda a direção em que está se movendo. A outra é a rotação da mira que altera a direção do tiro.

A rotação do jogador é manipulada através do uso da variável DesiredRotation. A rotação da mira é obtida a partir da variável aLookUp da classe PlayerInput. A variável aLookUp mapeia o movimento para frente e para trás do Mouse. A imagem logo abaixo mostra o jogador mirando para cima.

A função FaceRotation() que será chamada a partir do objeto Pawn, é a que está definida na classe SPG_PlayerPawn.


Na função PlayerMove() é calculada uma aceleração baseada na variável "PlayerInput.aStrafe" que representa a movimentação na horizontal. Esta aceleração é armazenada na variável do tipo Vector "NewAccel". O código da função está abaixo.
state PlayerWalking
{
    function PlayerMove(float DeltaTime)
    {
        local Vector X, Y, Z, NewAccel, CameraLocation;
        local Rotator OldRotation, CameraRotation;
        local bool bSaveJump;

        // If we don't have a pawn to control, then we should go to the dead state
        if (Pawn == None)
        {
            GotoState('Dead');
        }
        else
        {
            // Grab the camera view point as we want to have movement aligned to the camera
            PlayerCamera.GetCameraViewPoint(CameraLocation, CameraRotation);

            // Get the individual axes of the rotation
            GetAxes(CameraRotation, X, Y, Z);

            // Update acceleration
            NewAccel = PlayerInput.aStrafe * Y;
            NewAccel.Z = 0;
            NewAccel = Pawn.AccelRate * Normal(NewAccel);

            // Set the desired rotation
            DesiredRotation = Rotator(NewAccel);

            // Update rotation
            OldRotation = Rotation;
            UpdateRotation(DeltaTime);

            // Update crouch
            Pawn.ShouldCrouch(bool(bDuck));

            // Handle jumping
            if (bPressedJump && Pawn.CannotJumpNow())
            {
                bSaveJump = true;
                bPressedJump = false;
            }
            else
            {
                bSaveJump = false;
            }

            // Update the movement, either replicate it or process it
            if (Role < ROLE_Authority)
            {
                ReplicateMove(DeltaTime, NewAccel, DCLICK_None, OldRotation - Rotation);
            }
            else
            {
                ProcessMove(DeltaTime, NewAccel, DCLICK_None, OldRotation - Rotation);
            }

            bPressedJump = bSaveJump;
        }
    }
}

No final da função PlayerMove() existe o seguinte teste: "if (Role < ROLE_Authority)". Isto é usado para verificar se o jogo é um cliente que está utilizando um servidor. Caso este código estiver sendo executado em um servidor ou em um jogo single player então o valor da variável "Role" será igual a "ROLE_Authority".

quinta-feira, 21 de junho de 2012

Platformer Kit: SPG_GameInfo class (UnrealScript)

Os principais ajustes feitos na classe SPG_GameInfo são para possibilitar o uso de Archetypes nas classes Pawn e Weapon do jogador.

Esta classe contém as referências dos Archetypes nas variáveis DefaultPawnArchetype e DefaultWeaponArchetype.
class SPG_GameInfo extends SimpleGame;

// Variable which references the default pawn archetype stored within a package
var() const archetype Pawn DefaultPawnArchetype;

// Variable which references the default weapon archetype stored within a package
var() const archetype Weapon DefaultWeaponArchetype;

Se você não conhece a classe GameInfo, dê uma lida no meu artigo: "Classe GameInfo (UnrealScript)".

A classe SPG_GameInfo sobrescreve a função SpawnDefaultPawnFor() para que o objeto da classe Pawn do jogador seja criado baseado no Archetype.
function Pawn SpawnDefaultPawnFor(Controller NewPlayer, NavigationPoint StartSpot)
{
    local Rotator StartRotation;
    local Pawn SpawnedPawn;

    // Quick exit if NewPlayer is none or if StartSpot is none
    if (NewPlayer == None || StartSpot == None)
    {
        return None;
    }

    // Only the start spot's yaw from its rotation is required
    StartRotation = Rot(0, 0, 0);
    StartRotation.Yaw = StartSpot.Rotation.Yaw;

    // Spawn the default pawn archetype at the start spot's location and the start rotation defined above
    // Set SpawnedPawn to the spawned
    SpawnedPawn = Spawn(DefaultPawnArchetype.Class,,, StartSpot.Location, StartRotation, DefaultPawnArchetype);

    // Return the value of SpawnedPawn
    return SpawnedPawn;
}

A função SpawnDefaultPawnFor() utiliza uma variável local do tipo Rotator chamada StartRotation para obter apenas a rotação no eixo vertical (Yaw) do ponto de ínicio (StartSpot) do jogo. Para mais informações sobre Rotação, leia o meu artigo: "Usando Rotators em UnrealScript".

Após isso, é criado um novo objeto Pawn baseado no Archetype "DefaultPawnArchetype" usando a função Spawn().

Outra função que é sobrescrita na classe SPG_GameInfo é a AddDefaultInventory(). Nesta função é obtida uma referência da classe SPG_InventoryManager para poder criar a arma baseada no Archetype DefaultWeaponArchetype.
event AddDefaultInventory(Pawn P)
{
    local SPG_InventoryManager SPG_InventoryManager;

    Super.AddDefaultInventory(P);

    // Ensure that we have a valid default weapon archetype
    if (DefaultWeaponArchetype != None)
    {
        // Get the inventory manager
        SPG_InventoryManager = SPG_InventoryManager(P.InvManager);
        if (SPG_InventoryManager != None)
        {
            // Create the inventory from the archetype
            SPG_InventoryManager.CreateInventoryArchetype(DefaultWeaponArchetype, false);
        }
    }
}

No bloco defaultproperties são especificados os Archetypes do Pawn e da Arma, além das classes responsáveis pelo HUD e pelo PlayerController.

defaultproperties
{
    // What player controller class to create for the player
    PlayerControllerClass=class'SPG_PlayerController'

    // What default pawn archetype to spawn for the player
    DefaultPawnArchetype=SPG_PlayerPawn'StarterPlatformGameContent.Archetypes.PlayerPawn'

    // What default weapon archetype to spawn for the player
    DefaultWeaponArchetype=SPG_Weapon'StarterPlatformGameContent.Archetypes.LinkGunWeapon'

    // What HUD class to create for the player
    HUDType=class'SPG_HUD'
}

terça-feira, 19 de junho de 2012

Platformer Kit: SPG_InventoryManager class (UnrealScript)

A classe SPG_InventoryManager existe para permitir a criação de itens de inventário baseados em Archetypes. No Platformer Kit ela é usada para a criação da arma do jogador.

O código da classe está abaixo:
class SPG_InventoryManager extends InventoryManager;

simulated function Inventory CreateInventoryArchetype(Inventory NewInventoryItemArchetype, optional bool bDoNotActivate)
{
    local Inventory    Inv;
  
    // Ensure the inventory archetype is valid
    if (NewInventoryItemArchetype != None)
    {
        // Spawn the inventory archetype
        Inv = Spawn(NewInventoryItemArchetype.Class, Owner,,,, NewInventoryItemArchetype);
      
        // Spawned the inventory, and add the inventory
        if (Inv != None && !AddInventory(Inv, bDoNotActivate))
        {
            // Unable to add the inventory, so destroy the spawned inventory
            Inv.Destroy();
            Inv = None;
        }
    }

    // Return the spawned inventory
    return Inv;
}

defaultproperties
{
    // Create the pending fire array
    PendingFire(0)=0
    PendingFire(1)=0
}

A função CreateInventoryArchetype() é chamada a partir da classe SPG_GameInfo. Ela utiliza a função Spawn() para criar uma instância da classe Inventory baseada no Archetype recebido como parâmetro.

A função Spawn() é definida na classe Actor e possui os seguintes parâmetros:
native noexport final function coerce actor Spawn
(
    class<actor>      SpawnClass,
    optional actor    SpawnOwner,
    optional name     SpawnTag,
    optional vector   SpawnLocation,
    optional rotator  SpawnRotation,
    optional Actor    ActorTemplate,
    optional bool     bNoCollisionFail
);

Depois de criada a instância de Inventory, ela é adicionado ao inventário do jogador através da função AddInventory(). Você encontra o código desta função no meu artigo "PickupFactory e Inventory em UnrealScript".

Se por alguma razão não for possível adicionar o Inventory, deve ser chamada a função Destroy() para remover o objeto da memória. A função Destroy() não é executada imediatamente, por isso é necessário atribuir "None" à variável logo após a chamada à função Destroy().

sexta-feira, 15 de junho de 2012

Platformer Kit: SPG_HealthPickup class (UnrealScript)


A classe SPG_HealthPickup é usada para representar um item de energia que pode cair de um inimigo quando ele morre. Esta classe é uma subclasse de DroppedPickup. As suas variáveis são as seguintes:
class SPG_HealthPickup extends DroppedPickup
      placeable;

// Static mesh used by the pick up
var(HealthPickup) const StaticMeshComponent Mesh;

// Sound to play when the pick up is picked up by a pawn
var(HealthPickup) const SoundCue PickupSound;

// How much health to give to the pawn when picked up
var(HealthPickup) const int HealthToGive;

O Platformer Starter Kit também utiliza um Archetype desta classe para facilitar a mudanças dos valores de suas variáveis.

A imagem abaixo mostra os valores padrões do Archetype da classe SPG_HealthPickup.


Se você não sabe nada sobre Pickups, recomendo a leitura do meu artigo "PickupFactory e Inventory em UnrealScript".

A função "GiveTo(Pawn P)" é chamada quando o jogador pega o item. Ela apenas toca um Som e atribui mais energia ao jogador:
function GiveTo(Pawn P)
{
    // Play sound
    if (PickupSound != None)
    {
        PlaySound(PickupSound);        
    }

    // Add health to the player, but clamp it to the pawn's maximum health
    P.Health = Min(P.Health + HealthToGive, P.HealthMax);

    // Handle the rest of pick up
    PickedUpBy(P);
}

A função "int Min( int A, int B )" retorna o menor número entre dois números inteiros e é usada para evitar que o valor da energia do jogador ultrapasse o valor máximo. A função "PickedUpBy(Pawn P)" pertence a classe DroppedPickup e ela apenas chama a função "Destroy()" para remover este Pickup do jogo.

A classe SPG_HealthPickup sobrescreve a função "ValidTouch(Pawn Other)", que está dentro do estado "Pickup" da classe DroppedPickup, com uma versão mais simples da função. Esta função é chamada toda vez que ocorre uma colisão com um objeto do tipo Pawn para verificar se é o jogador que está pegando o item.
auto state Pickup
{
    function bool ValidTouch(Pawn Other)
    {
        // make sure its a live player
        if (Other == None || !Other.bCanPickupInventory || (Other.DrivenVehicle == None && Other.Controller == None))
        {
            return false;
        }

        // make sure thrower doesn't run over own weapon
        if (Physics == PHYS_Falling && Other == Instigator && Velocity.Z > 0)
        {
            return false;
        }

        return true;
    }
}

Para concluir, temos o bloco defaultproperties cujo código está abaixo. É definido um objeto do tipo StaticMeshComponent com o nome MyStaticMeshComponent. Se você observar na imagem do Archetype que foi criado para esta classe, a propriedade Mesh é associada ao objeto MyStaticMeshComponent que será usado como componente, utilizando ":" e o nome do objeto.
defaultproperties
{
    // Remove the sprite component
    Components.Remove(Sprite);

    // Add the static mesh component
    Begin Object Class=StaticMeshComponent Name=MyStaticMeshComponent
    End Object
    Mesh=MyStaticMeshComponent
    Components.Add(MyStaticMeshComponent);
}

sexta-feira, 8 de junho de 2012

Platformer Kit: SPG_Camera class (UnrealScript)


O Platformer Starter Kit usa uma visão lateral semelhante aos antigos jogos 2D de plataforma. São utilizados as classes SPG_Camera e SPG_CameraProperties para a definição da câmera.

A classe SPG_CameraProperties possui apenas um Vector usado para definir a posição relativa da câmera em relação ao jogador. O código da classe está abaixo:
class SPG_CameraProperties extends Object
      HideCategories(Object);

// How much to offset the camera by
var(CameraProperties) const Vector CameraOffset;

defaultproperties
{
}

Foi criado um Archetype baseado nesta classe no pacote "StarterPlatformGameContent.Archetypes.CameraProperties", conforme imagem abaixo.


A classe SPG_Camera possui uma instância da classe SPG_CameraProperties carregada a partir do Archetype:
class SPG_Camera extends Camera;

var const archetype SPG_CameraProperties CameraProperties;

/**
 * Updates the camera's view target. Called once per tick
 *
 * @param    OutVT        Outputted camera view target
 * @param    DeltaTime    Time since the last tick was executed
 */
function UpdateViewTarget(out TViewTarget OutVT, float DeltaTime)
{
    // Early exit if:
    // - We have a pending view target
    // - OutVT currently equals ViewTarget
    // - Blending parameter is lock out going
    if (PendingViewTarget.Target != None && OutVT == ViewTarget && BlendParams.bLockOutgoing)
    {
        return;
    }

    // Add the camera offset to the target's location to get the location of the camera
    OutVT.POV.Location = OutVT.Target.Location + CameraProperties.CameraOffset;
    // Make the camera point towards the target's location
    OutVT.POV.Rotation = Rotator(OutVT.Target.Location - OutVT.POV.Location);
}

defaultproperties
{
    CameraProperties=SPG_CameraProperties'StarterPlatformGameContent.Archetypes.CameraProperties'
}

Se você não sabe nada sobre a classe Camera, recomendo a leitura do meu artigo "Classe Camera (UnrealScript)".

A classe SPG_Camera modifica a câmera de uma forma bem simples, apenas sobrescrevendo a função UpdateViewTarget() da classe Camera. Esta função recebe como parâmetro de saída uma variável do tipo TViewTarget, que é uma estrutura (struct) definida na classe Camera:
//Extraído da classe Camera...

/**
 * View Target definition
 * A View Target is responsible for providing the Camera with an ideal Point of View (POV)
 */
struct native TViewTarget
{
    /** Target Actor used to compute ideal POV */
    var()    Actor                   Target;

    /** Controller of Target (only for non Locally controlled Pawns) */
    var()    Controller              Controller;

    /** Point of View */
    var()    TPOV                    POV;

    /** Aspect ratio */
    var()    float                   AspectRatio;

    /** PlayerReplicationInfo (used to follow same player through pawn transitions, etc., when spectating) */
    var()    PlayerReplicationInfo   PRI;
};

Dentro da estrutura TViewTarget existe uma variável chamada POV que representa o Ponto de Visão. Ela é do tipo TPOV que é uma estrutura definida na classe Object:
//Extraído da classe Object...

/**
 * Point Of View type.
 */
struct TPOV
{
    /** Location */
    var() Vector    Location;

    /** Rotation */
    var() Rotator    Rotation;

    /** FOV angle */
    var() float        FOV;

    structdefaultproperties
    {
        FOV=90.f
    }
};

Em relação ao código existente na função UpdateViewTarget() da classe SPG_Camera, o teste condicional abaixo foi copiado da função UpdateViewTarget() da classe Camera e é usado apenas para evitar que a ViewTarget seja alterada enquanto estiver ocorrendo uma interpolação.
if (PendingViewTarget.Target != None && OutVT == ViewTarget && BlendParams.bLockOutgoing)

O ajuste da câmera é feito na duas linhas de código abaixo, que define a posição do Ponto de Visão como sendo a posição do jogador somado com o Vector existente na classe SPG_CameraProperties. Após isso é definida a rotação necessária para que a câmera aponte em direção ao jogador.
OutVT.POV.Location = OutVT.Target.Location + CameraProperties.CameraOffset;
OutVT.POV.Rotation = Rotator(OutVT.Target.Location - OutVT.POV.Location);

sexta-feira, 25 de maio de 2012

Platformer Kit: SPG_HUD class (UnrealScript)

Já vimos diversos conceitos relacionados à programação com UnrealScript. Agora chegou a hora de ver todos esses conceitos juntos sendo aplicados em um jogo.

Na UDN (Unreal Developer Network) existe um excelente exemplo de jogo para estudo de UnrealScript que é o Platformer Starter Kit, disponível no link: 

Esta página da UDN possui todas as orientações necessárias para configurar e executar o Kit no UDK.

O código fonte em UnrealScript do Platformer Starter Kit está muito bem comentado. Mesmo assim, algumas partes do código são bem complexas para Iniciantes em UnrealScript. Vou iniciar uma série de artigos analisando as classes UnrealScript existentes no Platformer Starter Kit com o objetivo de gerar um material complementar para ajudar na compreensão do funcionamento deste exemplo de um jogo funcional.

Vamos começar analisando a classe mais simples: SPG_HUD. O único objetivo desta classe é desenhar na parte inferior esquerda da tela o texto "Health:" com o valor numérico que representa a energia atual do jogador, conforme imagem abaixo.


Se você ainda não conhece nada sobre a classe HUD, recomendo a leitura do meu artigo "Desenhando o HUD".

A classe SPG_HUD possui apenas a função PostRender() que contém o seguinte código.
event PostRender()
{
    local float XL, YL;
    local String Text;

    Super.PostRender();

    // Ensure that PlayerOwner and PlayerOwner.Pawn are valid
    if (PlayerOwner != None && PlayerOwner.Pawn != None)
    {
        // Set the text to say Health: and the numerical value of the player pawn's health
        Text = "Health: " $ PlayerOwner.Pawn.Health;
        // Set the font
        Canvas.Font = class'Engine'.static.GetMediumFont();
        // Set the current drawing color
        Canvas.SetDrawColor(255, 255, 255);
        // Get the dimensions of the text in the font assigned
        Canvas.StrLen(Text, XL, YL);
        // Set the current drawing position to the be at the bottom left position with a padding of 4 pixels
        Canvas.SetPos(4, Canvas.ClipY - YL - 4);
        // Draw the text onto the screen
        Canvas.DrawText(Text);
    }
}

Os comentários abaixo estão relacionados a conceitos existentes no código acima.

Existem duas funções principais na classe HUD usadas para desenhar na tela, que são as funções "DrawHud()" e a "PostRender()". A função "DrawHud()" só é chamada se a variável bShowHud for igual a true e se a tabela de pontos não estiver sendo exibida (bShowScores igual a false). Para entender a relação entre as funções "DrawHud()" e "PostRender()", analise a função "PostRender()" da classe HUD (pacote Engine).

A variável PlayerOwner é declarada na classe HUD e representa o PlayerController responsável pelo HUD. A sua inicialização é feita na função "PostBeginPlay()" da classe HUD.

A concatenação de strings pode ser feita com o uso de dois operadores: $ e @. A única diferença entre eles é que o operador @ coloca um espaço em branco entre as strings. Valores numéricos usados na concatenação são automaticamente convertidos para strings. Outra forma de concatenar strings é utilizando os operadores $= e @= que acrescenta uma string a uma variável string já existente. Por exemplo para complementar o texto do HUD com a informação HealthMax, podemos fazer assim:
Text = "Health: " $ PlayerOwner.Pawn.Health;
Text @= "- HealthMax:" @ PlayerOwner.Pawn.HealthMax;

A fonte usada para escrever o texto é obtida a partir de uma função estática da classe Engine. Existem quatro tamanhos pré-definidos de fontes que são obtidas através das funções:
native static final function Font GetTinyFont();
native static final function Font GetSmallFont();
native static final function Font GetMediumFont();
native static final function Font GetLargeFont();

A definição da cor da fonte é feita através da função SetDrawColor() da classe Canvas. A cor é representada no formato RGB, onde cada parâmetro é um byte (0 a 255). Pode ser definido também o elemento Alfa da cor que indica sua opacidade. Existe também a função SetDrawColorStruct() que recebe uma struct do tipo color que representa uma cor. Abaixo temos as duas funções:
native final function SetDrawColor(byte R, byte G, byte B, optional byte A = 255);

/** Set the draw color using a color struct */
final function SetDrawColorStruct(color C)
{
    SetDrawColor(C.R, C.G, C.B, C.A);
}

sábado, 28 de abril de 2012

Usando Archetypes em UnrealScript

O uso de archetype nos permite armazenar uma instância de uma classe para ser modificada e usada como modelo na criação de outras instâncias desta mesma classe no editor de níveis. Este recurso é muito útil para os Designers fazerem adaptações nos Atores do cenário sem depender totalmente do programador.

Criar um Archetype é simples, basta selecionar um Ator do cenário, clicar com o botão direito e escolher a opção "Create Archetype...", como pode ser visto na figura abaixo:


Outra forma de criar o archetype é através da janela "Actor Classes". Selecione uma classe e clique com o botão direito para aparecer a opção "Create Archetype...".


Após isso será exibida uma caixa de diálogo onde você deve informar o nome deste novo archetype e o package onde será armazenado. O campo "Grouping" é opcional.


Depois da criação, o novo archetype passará a ser exibido no "Content Browser". É possível mudar suas propriedade clicando com o botão direito no Archetype e escolhendo a opção "Properties...". Para obter o nome completo do Archetype escolha a opção "Copy Full Name to Clipboard" e depois cole no lugar desejado.


Criei uma classe chamada ArchetypeSpawner para ilustrar o uso de archetypes em UnrealScript. Esta classe é placeable e possui uma variável com o nome archetypeReference que é usada para referenciar um archetype. Foi definida uma função que pode ser chamada por outras classes para criar uma instância baseada no archetype referenciado. Neste exemplo foi criado um Timer de 5 segundos que irá chamar esta função uma única vez. A grande vantagem deste exemplo usando archetypes é que a variável está exposta no editor para o Designer, dessa forma ele pode criar qualquer Archetype e informar o nome completo na variável archetypeReference na janela de propriedades, como pode ser visto na imagem e código abaixo.

class ArchetypeSpawner extends Actor
      placeable;

var() const archetype Actor archetypeReference;
//O uso do modificador archetype é opcional.
      
event simulated PostBeginPlay()
{
    SetTimer(5);    
}

function Timer()
{
    SpawnArchetypeAt( self.Location );
}    

function Actor SpawnArchetypeAt( vector startLocation ) 
{    
    local Actor archetypeSpawned;
    
    archetypeSpawned = Spawn(archetypeReference.Class, , , startLocation, , archetypeReference);
    
    return archetypeSpawned;
}      

defaultproperties
{    
    archetypeReference=UltraHealthPack'ArchetypeExample.HealthPackArchetype'
}

Para mais informações sobre Archetypes:

domingo, 8 de abril de 2012

Navegação IA em UnrealScript

A movimentação em um cenário é um dos aspectos fundamentais da Inteligência Artificial em jogos. A forma mais comum de navegação IA é baseada em waypoints que são colocados no cenário para criar uma rede de caminhos. O UDK também suporta um sistema mais eficiente de navegação baseado em "Navigation Mesh" que resulta em uma movimentação mais natural.

Os waypoints são representados pela classe PathNode:


Na classe AIController temos um exemplo de código que utiliza o sistema de waypoints:
//Extraído da classe AIController (pacote Engine)

state ScriptedMove
{
    // state functions...
    
    Begin:
    // while we have a valid pawn and move target, and
    // we haven't reached the target yet
    while (Pawn != None &&
           ScriptedMoveTarget != None &&
           !Pawn.ReachedDestination(ScriptedMoveTarget))
    {
        // check to see if it is directly reachable
        if (ActorReachable(ScriptedMoveTarget))
        {
            // then move directly to the actor
            MoveToward(ScriptedMoveTarget, ScriptedFocus);
        }
        else
        {
           // attempt to find a path to the target
           MoveTarget = FindPathToward(ScriptedMoveTarget);
           if (MoveTarget != None)
           {
               // move to the first node on the path
               MoveToward(MoveTarget, ScriptedFocus);
           }
           else
           {
               // abort the move                
               ScriptedMoveTarget = None;
           }
        }
    }
}

No sistema baseado em "Navigation Mesh", o caminho é representado por um conjunto de polígonos convexos que são gerados automaticamente no cenário a partir de um Actor conhecido como Pylon:


As funções relacionadas ao PathFinding usando "Navigation Mesh" estão contidas em uma classe chamada NavigationHandle. Essa classe trabalha com o conceito de Path Constraints e Goal Evaluators. O uso de Path Constraints permite que sejam definidos critérios a serem levados em conta durante o PathFinding. O Path Constraint mais simples é o NavMeshPath_Toward. O Goal Evaluator é usado para definir o fim do PathFinding. O Goal Evaluator mais comum é o NavMeshGoal_At.

Procurando no código fonte que acompanha o UDK, encontrei um exemplo simples de PathFinding usando "Navigation Mesh". O código abaixo é da função GeneratePathToActor() da classe GameCrowdAgent:
//Extraído da classe GameCrowdAgent (pacote GameFramework)

/**
  * Generate a path to Goal on behalf of the QueryingAgent
  */
event vector GeneratePathToActor( Actor Goal, optional float WithinDistance, optional bool bAllowPartialPath )
{
    local vector NextDest;

    LastPathingAttempt = WorldInfo.TimeSeconds;
    NextDest = Goal.Location;

    // make sure we have a valid navigation handle
    if ( NavigationHandle == None )
    {
        InitNavigationHandle();
    }
    if( (NavigationHandle != None) && !NavigationHandle.ActorReachable(Goal) )
    {
        class'NavMeshPath_Toward'.static.TowardGoal( NavigationHandle, Goal );
        class'NavMeshGoal_At'.static.AtActor( NavigationHandle, Goal, WithinDistance, bAllowPartialPath );

        if ( NavigationHandle.FindPath() )
        {
            NavigationHandle.GetNextMoveLocation(NextDest, SearchExtent.X);
        }
        NavigationHandle.ClearConstraints();
    }

    return NextDest;
}

Para mais informações sobre Navegação IA:
AI & Navigation

sábado, 17 de março de 2012

Usando Arquivos de Configuração em UnrealScript

Arquivos de Configuração são arquivos em formato de texto simples com a extensão ".ini" que contém informações que serão utilizadas pelo jogo.

Na pasta "..\UDK-20??-??\UDKGame\Config" você encontrará diversos arquivos de configuração usados pelo UDK. Como exemplo temos o arquivo "DefaultInput.ini" que é usado para o mapeamento de teclas.

A principal vantagem no uso de arquivos de configuração é permitir o ajuste de um jogo sem a necessidade de recompilar o código fonte. Dessa forma um Game Designer pode fazer os ajustes dos dados de um jogo sem depender do programador.

Para utilizar um arquivo de configuração em uma classe, adicione o modificador config da seguinte forma:
class NomeClasse extends SuperClasse config(NomeConfig); 

Para identificar quais são as variáveis que irão obter seus valores a partir de arquivos de configuração, utilize também o modificador config ao declarar variáveis:
var config int exemploVar;

O próximo passo é criar na pasta "..\UDK-20??-??\UDKGame\Config" um arquivo de configuração com o nome "DefaultNomeConfig.ini" contendo os valores que devem ser armazenados nas variáveis. Quando o jogo for executado será procurado um arquivo chamado "UDKNomeConfig.ini", caso  este arquivo não seja encontrado, será procurado o arquivo "DefaultNomeConfig.ini" para poder criar o "UDKNomeConfig.ini" com valores padrões. Você não deve editar diretamente o arquivo "UDKNomeConfig.ini", porque em algum momento ele poderá ser sobrescrito e você irá perder suas modificações.

Usando a função "SaveConfig()" é possível salvar no arquivo "UDKNomeConfig.ini" novos valores das variáveis de configuração que foram modificadas durante a execução do jogo. Existe outra função chamada "StaticSaveConfig()" que permite que os valores padrões (default) sejam restaurados. Esta é uma opção comum na tela de configuração dos jogos, como pode ser visto na imagem abaixo.


A função "StaticSaveConfig()" é estática, isso significa que ela não precisa de um objeto para poder ser executada. As funções estáticas são como se fossem funções de classe, para executá-la basta usar o nome da classe mais a palavra static como neste exemplo:
class'NomeClasse'.static.StaticSaveConfig();

Como exemplo, fiz uma classe que desenha na tela algumas informações que foram obtidas em um arquivo de configuração, como pode ser visto na imagem abaixo.


Criei um arquivo de configuração chamado "DefaultWarrior.ini" que contém as seguintes informações:
[RomeroScripts.CyberWarrior]
codename=DeadLocker
age=33

implants=Bionic Eye
implants=Night Vision
implants=Thermal Resistence

Attributes=(strength=12, agility=10, baseExperience=5000, currentExperience=6000)

A primeira linha indica qual é a classe que irá utilizar este arquivo. "RomeroScripts" é o nome do pacote (package) que eu uso e "CyberWarrior" é o nome da classe.

A classe "CyberWarrior" define as variáveis de configuração. Além disso, ela utiliza um Timer de 1 segundo para aumentar o nível de experiência atual e utiliza outro Timer de 10 segundos para salvar os dados do jogador. A função "DrawHUD()" desenha as informações na tela.
class CyberWarrior extends SimplePawn
      config(Warrior);

var config string codename;      
var config int age;

var config Array<string>    implants;

struct stAttributes
{
    var int strength, agility, baseExperience, currentExperience;
};

var config stAttributes Attributes;

event simulated PostBeginPlay()
{
    SetTimer(1, true);
    SetTimer(10, true, 'SaveWarrior');
}
function Timer()
{
    Attributes.currentExperience++;    
}
function SaveWarrior()
{
    SaveConfig();
}

simulated function DrawHUD( HUD H )
{
    local int i;
    
    super.DrawHUD(H);
    
    H.Canvas.SetDrawColor(255, 255, 255); // Branco   
    
    H.Canvas.SetPos(50, 10);   
    H.Canvas.DrawText("Codename:" @ codename @ "- Age:" @ age) ;
    
    H.Canvas.SetPos(50, 40);
    H.Canvas.DrawText( "*** Attributes ***" );
    H.Canvas.SetPos(50, 55);
    H.Canvas.DrawText( "Strength :" @ Attributes.strength);
    H.Canvas.SetPos(50, 70);
    H.Canvas.DrawText( "Agility :" @ Attributes.agility);
    H.Canvas.SetPos(50, 85);
    H.Canvas.DrawText( "Base Experience :" @ Attributes.baseExperience);
    H.Canvas.SetPos(50, 100);
    H.Canvas.DrawText( "Current Experience :" @ Attributes.currentExperience);
    
    H.Canvas.SetPos(300, 40);
    H.Canvas.DrawText( "*** Implants ***");
    for( i =0; i < implants.Length; i++)
    {
       H.Canvas.SetPos(300, 55 + i * 15);       
       H.Canvas.DrawText( implants[i] );
    }    
} 

Para utilizar esta classe precisamos criar o novo GameInfo que irá referenciar a classe CyberWarrior:
class GameWarrior extends SimpleGame;

defaultproperties
{        
    DefaultPawnClass=class'RomeroScripts.CyberWarrior'    
}

Estou usando como classe base o "SimpleGame" porque é mais fácil de escrever na Tela no "SimpleGame" do que usando o "UTGame".

Ao executar o jogo você verá que a informação "Current Experience" é alterado a cada segundo. Espere pelo menos 10 segundos antes de sair do jogo para que seja salvo o estado atual da classe. Ao sair do jogo, procure pelo arquivo "UDKWarrior.ini" na pasta dos arquivos de configuração. Você verá que o "currentExperience", que se encontra dentro de "Attributes", foi modificado. Neste arquivo aparecerão também outras variáveis de configuração que pertencem à superclasse "SimplePawn".

Para mais informações:
Configuration files

terça-feira, 6 de março de 2012

Classe Vehicle (UnrealScript)

A classe Vehicle é uma subclasse de Pawn. O sistema de veículos no UDK é bem complexo e permite a criação de diferentes tipos de veículos. A imagem abaixo mostra as subclasses de Vehicle existentes no UDK:


Podemos ver quatro tipo de veículos definidos que são: UTVehicle_Cicada, UTVehicle_Hoverboard, UTVehicle_Manta e UTVehicle_Scorpion. Entretanto, todas essas classes possuem o modificador abstract. Isto indica que não podem ser criados objetos dessas classes. A principal idéia das classes abstratas é criar uma classe base com diversas funcionalidades para servir como modelo. As classes que realmente definem os veículos são as que terminam com o nome "_Content".

Para disponibilizar veículos no cenário é preciso utilizar a classe UDKVehicleFactory que é uma subclasse de "Actor->NavigationPoint". A imagem abaixo mostra os tipos existentes no UDK:


Existem apenas três Factory. O veículo que está faltando é o Hoverboard porque ele já está disponível automaticamente para o jogador no tipo de jogo "Vehicle Capture The Flag". Para testá-lo no editor defina o Game Type como UTVehicleCTFGame_Content e após iniciar o jogo pressione a tecla "Q" para usar o Hoverboard que é uma espécie de skate sem rodas como pode ser visto na imagem abaixo.


Para entrar em um veículo que esteja disponível no cenário, chegue perto dele e pressione a tecla "E". Se for possível entrar no veículo, umas das funções da classe Vehicle que será chamada é a "DriverEnter(Pawn P)" cujo código se encontra abaixo.
//Extraído da classe Vehicle (pacote Engine)

/** DriverEnter()
 * Make Pawn P the new driver of this vehicle
 * Changes controller ownership across pawns
 */
function bool DriverEnter(Pawn P)
{
    local Controller C;

    // Set pawns current controller to control the vehicle pawn instead
    C = P.Controller;
    Driver = P;
    Driver.StartDriving( self );
    if ( Driver.Health <= 0 )
    {
        Driver = None;
        return false;
    }
    SetDriving(true);

    // Disconnect PlayerController from Driver and connect to Vehicle.
    C.Unpossess();
    Driver.SetOwner( Self ); // This keeps the driver relevant.
    C.Possess( Self, true );

    if( PlayerController(C) != None && !C.IsChildState(C.GetStateName(), LandMovementState) )
    {
        PlayerController(C).GotoState( LandMovementState );
    }

    WorldInfo.Game.DriverEnteredVehicle(self, P);
    return true;
}

Esta função pega a referência do objeto Pawn que entrou no veículo e guarda na variável Driver para que possa restaurá-lo quando ele sair do veículo. O Controller deixa de referenciar o Pawn através da função "Unpossess()" e passa agora a referenciar o veículo através da função "Possess( Self, true )". O objeto Self é a referência ao veículo e o segundo parâmetro com o valor true indica que é uma transição para um veículo. É feito um teste para verificar se o Controller se trata de um PlayerController e se ainda não se encontra no estado LandMovementState. LandMovementState é uma variável do tipo name definida na classe Pawn. Esse tipo de variável é usada para armazenar o nome de um item em UnrealScript como o nome de classes, funções e estados. Na classe Pawn ela recebe o valor PlayerWalking no DefaultProperties. Na classe Vehicle essa variável recebe o valor PlayerDriving. Isto significa que ao entrar em um veículo o PlayerController passará para o estado PlayerDriving.

No PlayerController podemos ver um bom exemplo do uso de estados. A cada frame é executada a função "PlayerMove()", se estiver no estado PlayerWalking a função "PlayerMove()" chamará a função "ProcessMove()", mas se estiver no estado PlayerDriving a função "PlayerMove()" chamará a função "ProcessDrive()". O código abaixo mostra as funções definidas para o estado PlayerDriving na classe PlayerController.
//Extraído da classe PlayerController (pacote Engine)

// Player Driving a vehicle.
state PlayerDriving
{
ignores SeePlayer, HearNoise, Bump;

    function ProcessMove(float DeltaTime, vector NewAccel, eDoubleClickDir DoubleClickMove, rotator DeltaRot);

    // Set the throttle, steering etc. for the vehicle based on the input provided
    function ProcessDrive(float InForward, float InStrafe, float InUp, bool InJump)
    {
        local Vehicle CurrentVehicle;

        CurrentVehicle = Vehicle(Pawn);
        if (CurrentVehicle != None)
        {            
            bPressedJump = InJump;
            CurrentVehicle.SetInputs(InForward, -InStrafe, InUp);
            CheckJumpOrDuck();
        }
    }

    function PlayerMove( float DeltaTime )
    {        
        UpdateRotation(DeltaTime);
      
        ProcessDrive(PlayerInput.RawJoyUp, PlayerInput.RawJoyRight, PlayerInput.aUp, bPressedJump);
        if (Role < ROLE_Authority)
        {
            ServerDrive(PlayerInput.RawJoyUp, PlayerInput.RawJoyRight, PlayerInput.aUp, bPressedJump, ((Rotation.Yaw & 65535) << 16) + (Rotation.Pitch & 65535));
        }

        bPressedJump = false;
    }

    unreliable server function ServerUse()
    {
        local Vehicle CurrentVehicle;

        CurrentVehicle = Vehicle(Pawn);
        CurrentVehicle.DriverLeave(false);
    }

    event BeginState(Name PreviousStateName)
    {
        CleanOutSavedMoves();
    }

    event EndState(Name NextStateName)
    {
        CleanOutSavedMoves();
    }
}

Para mais informações sobre a Classe Vehicle: