Sunday, April 7, 2013

Управление Particle System с помощью unreal script

Автор: Vadakuma
Используемые источники:
    UDN: Distributions
             UsingInstanceParameters
и исходники UT3.


    Возможности UDK позволяют управлять отдельными параметрами системы частиц через скрипты, что бывает очень кстати.
    В этой статье я покажу, как можно управлять количеством дыма и огня в зависимости от урона. Подразумевается, что читатель уже имеет как минимум базовые знания по UDK и unrealscript в частности, так как эта статья лишь показ идеи в целом, и некоторые простые шаги пропущены.


Буду использовать:
Particle: ParticleSystem'GenericFoliage01.Ruins.FX.P_UDK_TorchFire01'
SkeletalMesh: SkeletalMesh'FoliageDemo.Animated.SP_Bird1_SKMesh'


Часть 1

Шаг 1

    Сначала необходимо настроить сам particle. Выбираем particle в ContentBrowser и открываем его в CascadeЭффект обычно состоит из нескольких модулей, выберем сначала первый и в нем Initial Size.


    В настройках жмем на треугольник напротив Distribution и выбираем DistributionVectorParticleParameter. Изменяемая нами величина будет вектором, т.е. эффект можно растягивать, расширять, или наоборот сужать размеры более гибким образом, нежели при использовании лишь одного числового параметра. (Учтите, если до этого параметр был настроен, то проделав описанную процедуру, все сбросится!)



Окно настроек изменится:





Шаг 2

    1. Чтобы к этому параметру можно было обратиться из кода, необходимо задать Parameter Name.

    2-3.Вписать необходимый диапазон изменения размеров эффекта.

    4. Специально оставлено DPM_Normal. В таком режиме когда из кода приходит относительное значение от 0 до 1 (указано в MinInput/MaxInput), сам параметр будет меняться в диапазоне указанном в MinOutput/MaxOutput.

    5. Какое относительное значение параметра будет использоваться по умолчанию при активации эффекта. (1 – берется максимальное значение)





Шаг 3

    Туже процедуру необходимо проделать с оставшимися модулями. В них можно указать тот же Parameter Name что и в первом модуле, тогда изменение параметра Size будет происходить в нескольких модулях, т.е. можно управлять размером дыма даже если он состоит из составных частей.
    Все тоже самое проделать с модулями, отвечающими за огонь, разве что сменить Parameter Name на соответствующее имя, если есть потребность управлять огнем отдельно от дыма.

Примечание: процедура была выполнена с векторным параметром, из кода тоже будет приходить векторный параметр. Если управляемый параметр не векторный, ничего не меняется, изменится лишь тип. К примеру, если параметр - это просто значение с плавающей запятой, тогда тип - float, и выбирать надо будет DistributionFloatParticleParameter.



Часть 2


Шаг 4

    Теперь разберемся в коде. Алгоритм управления взят из исходников UT3, здесь есть отличия, но сама суть остается неизменной.

    Для примера создадим класс Pawn, который будет загораться, при нанесении достаточного урона и огонь с дымом будут увеличиваться при большем уроне. При нанесении урона (событие TakeDamage) срабатывает функция CheckDamageSmoke(), которая проверяет, на сколько мало отношение Жизнь/МаксЖизнь, и если порог DamageSmokeThreshold пройден, активируется и аттачится к сокету BirdHead эффект пожара(по тегу DamageSmoke) в CreatePawnEffect(), далее количество огня корректируется через SetPawnEffectParms() при каждом последующим получении урона.

class CPSAnotherPawn extends GamePawn;

// порог после прохождения которого начнется возгорание.
var float DamageSmokeThreshold;

/**************************************************************************
 Pawn Effects
************************************************************************ */

/** Структура содержащая необходимые параметры для управления*/
struct  PawnEffect
{
    /** Тег для активации эффекта */
    var() name EffectStartTag;
    /** Тег для выключения эффекта */
    var() name EffectEndTag;
    /** Если необходимо перезапустить particle */
    var() bool bRestartRunning;
    /** Template to use */
    var() ParticleSystem EffectTemplate;
    /** Названия сокета для присоединения */
    var() name EffectSocket;
    /** The Actual PSC */
    var ParticleSystemComponent EffectRef;

    structdefaultproperties
    {
        bRestartRunning=true;
    }
};
/** массив с эффектами и данными необходимыми для управления */
var(Effects) array<pawneffect>PawnEffects;

/**
* Проверка достаточно ли поврежден pawn для того чтобы начать гореть.
*/
simulated function CheckDamageSmoke()
{
    Local float Pct;

    Pct = (float(Health) / float(HealthMax); 
    // для примера, если наша птичка убита, дым отключится.
    PawnEvent( Pct &lt; DamageSmokeThreshold &amp;&amp; Health &gt; 0) ? 'DamageSmoke' : 'NoDamageSmoke');
}


/**
* An interface for causing various events on the vehicle.
*/
simulated function PawnEvent(name EventTag)
{
    // Включаем или выключаем эффекты
    TriggerPawnEffect(EventTag);

    // Play any animations
    //PlayPawnAnimation(EventTag);
    //PlayPawnSound(EventTag);
}


/**
* Активирует или деактивирует эффекты
* param    EventTag    тег указывающий на конкретный эффект
*/
simulated function TriggerPawnEffect(name EventTag)
{
    local int i;

    for (i = 0; i &lt; PawnEffects.length; i++)
    {
        // находим эффект по тегу, с которым надо отработать
        if (PawnEffects[i].EffectStartTag == EventTag) 
        {
            if (PawnEffects[i].EffectRef == None)  // если не создан, создаем
            {
                CreatePawnEffect(i);
            }
            if (PawnEffects[i].bRestartRunning)  // если надо, перезапускаем
            {
                PawnEffects[i].EffectRef.KillParticlesForced();
                PawnEffects[i].EffectRef.ActivateSystem();
            }
            else if (!PawnEffects[i].EffectRef.bIsActive) // если не активен, включаем
            {
                PawnEffects[i].EffectRef.ActivateSystem();
            }
            // начинаем менять параметры
            SetPawnEffectParms(EventTag, PawnEffects[i].EffectRef);
        }
        // Если эффект действует, а тэг стоит на выключении - выключаем
        else if (PawnEffects[i].EffectRef != None &amp;&amp; PawnEffects[i].EffectEndTag == EventTag)
        {
            PawnEffects[i].EffectRef.DeActivateSystem();
        }
    }
}


/**
* После активации какого-либо эффекта, вызывается эта функция, для корректировки 
* параметров.
* @param    TriggerName   тег говорящий, какой именно эффект был активирован
* @param    PSC     Particle System component связанный с эффектом
*/
simulated function SetPawnEffectParms(name TriggerName, ParticleSystemComponent PSC)
{
    local float Pct;
    local vector ratioVect;

    if (TriggerName == 'DamageSmoke')
    {
        /* домножено на DamageSmokeThreshold, чтобы Pct изменялся от 1 до 0. ||1 -  
        небольшой пожар. 0-максимально возможный. Без умножения  Pct изменялся от 
        DamageSmokeThreshold до 0, т.е. эффект начинает работать не с минимального 
        значения*/
        Pct = float(Health) / (float(HealthMax)*DamageSmokeThreshold);  

        ratioVect.X = (1 - Pct);
        ratioVect.Y = (1 - Pct);
        ratioVect.Z = (1 - Pct);

        PSC.SetVectorParameter('smokeamount', ratioVect);
        PSC.SetVectorParameter('fireamount', ratioVect);
    }
}


/**
* Создаем новый эффект
*/
simulated function CreatePawnEffect(int EffectIndex)
{
    PawnEffects[EffectIndex].EffectRef = new(self) class'UDKParticleSystemComponent';
    PawnEffects[EffectIndex].EffectRef.SetTemplate(PawnEffects[EffectIndex].EffectTemplate);

    // присоединяем созданный эффект к мешу павна
    if(PawnEffects[EffectIndex].EffectSocket != 'None')
        Mesh.AttachComponentToSocket(PawnEffects[EffectIndex].EffectRef, PawnEffects[EffectIndex].EffectSocket);
}


/**
* @See Actor.TakeDamage()
*/
simulated event TakeDamage(
                           int Damage,
                           Controller EventInstigator,
                           vector HitLocation,
                           vector Momentum, 
                           class<damagetype> DamageType,
                           optional TraceHitInfo HitInfo, 
                           optional Actor DamageCauser
                           )
{
    Super.TakeDamage(Damage, EventInstigator, HitLocation, Momentum, DamageType, HitInfo, DamageCauser);

    CheckDamageSmoke();
}


defaultproperties
{
    DamageSmokeThreshold = 0.85

    Begin Object Class=SkeletalMeshComponent Name=InitialSkeletalMesh 
        CastShadow=true
        bCastDynamicShadow=true
        bOwnerNoSee=false
        BlockRigidBody=true;
        CollideActors=true;
        BlockZeroExtent=true;
        BlockNonZeroExtent=TRUE
        bIgnoreControllersWhenNotRendered=TRUE
        bUpdateSkelWhenNotRendered=FALSE
        SkeletalMesh=SkeletalMesh'FoliageDemo.Animated.SP_Bird1_SKMesh'
    End Object
    Mesh=InitialSkeletalMesh;
    Components.Add(InitialSkeletalMesh);


    // Вписываем набор эффектов
    PawnEffects.Empty
    PawnEffects(0)=(
         EffectStartTag=DamageSmoke,
         EffectEndTag=NoDamageSmoke,
         bRestartRunning=false,
         EffectTemplate=ParticleSystem'GenericFoliage01.Ruins.FX.P_UDK_TorchFire01',
         EffectSocket=BirdHead
                       )
}

    В функции SetPawnEffectParms() передача текущего значения настроенным в редакторе эффектов параметрам происходит через указание их имен, в нашем случае это smokeamount и fireamount.
    Чем меньше соотношение Жизнь/МаксЖизнь, тем большее значение (в нашем до 1.0) отсылается в particlesystem, тем самым вызывая увеличение дыма и огня.

Примечание: отмечу, что использовался skeletal mesh птички с созданным одним сокетом, с названием BirdHead. По умолчанию у этого скелетал меша нет сокетов!




Видео с полученным результатом:



В исходниках реализована простая проверка контроля:

class CPSPlayerController extends GamePlayerController;

var Pawn SP;

exec function SpawnBird()
{
    SP = spawn(class'CPSAnotherPawn',,,Location + vect(350,350, 250));
}


exec function BirdStrike()
{
    local  class<damagetype> DamageType;

    DamageType = class'DamageType';
    SP.TakeDamage(10, self, SP.Location, SP.Location,DamageType,,);
    ClientMessage("Bird Health:"@SP.Health);
}

defaultproperties 
{

}
    В классе CPSPlayerController прописаны exec функции, которые можно вызвать через консоль. SpawnBird - рождается птичка. BirdStrike - "бъем" по созданной птичке нанося урон.
Так же в архиве находится пакет с измененным эффектом и птичкой с добавленным сокетом.


Исходники

PS. Пишите, если в предложенном материале обнаружили ошибки, недочеты, и т.д. - исправлю.

No comments:

Post a Comment