Friday 6 May 2011

Creating a custom actor part 3/4

Now that the most basic foundations of the HealthFountain actor are coded, it is time to make our actor look like originally outlined in Part 1. Let’s start by adding more actor components. In the HealthFountain.uc file head over to the DefaultProperties block and add the following code:


Begin Object Class=DynamicLightEnvironmentComponent Name=HealthFountainLightEnvironment
bEnabled=true
bDynamic=false
bCastShadows=false
End Object
Components.Add(HealthFountainLightEnvironment)


DynamicLightEnvironmentComponent is used for lighting the static mesh we are using. We don’t need it to cast shadows because our static mesh is placed flat on the ground anyway, so we set bCastShadows to false. Next, let’s add collision component so that we can look for collisions with other actors during gameplay.


Begin Object Class=CylinderComponent Name=CollisionCylinder
CollisionRadius=32.0
CollisionHeight=50.000000
CollideActors=true
End Object
Components.Add(CollisionCylinder)
CollisionComponent=CollisionCylinder
bCollideActors=true


In the code above a CylinderComponent is created to serve as a primitive for collision for the actor. This defines a cylinder with 32 units radius and 100 units height (50 units in positive and negative Z). We then turn the collisions with other actors on by setting CollideActors in the CylinderComponent and bCollideActors in the actual HealthFountain actor to true. Notice that if you want to block actors when they collide with our actor, you would have to set bBlockActors to true. Because we want the player and weapon projectiles to pass through, we leave that turned off (which is the default setting).

Next, we need to take care of the StaticMeshComponent. We don’t need the mesh to collide with other actors. We do need to translate the mesh on Z axis though. Because we added the CollisionComponent by defining the CylinderComponent, the pivot point of our actor is placed in the middle of the collision cylinder, and not where the mesh pivot point was. We also assign the DynamicLightEnvironmentComponent we created to the LightEnvironment of the StaticMeshComponent, so that the static mesh can receive lighting.


Begin Object Class=StaticMeshComponent Name=BaseMesh
StaticMesh=StaticMesh'Pickups.Base_Powerup.Mesh.S_Pickups_Base_Powerup01'
CollideActors=false
Translation=(X=0.0, Y=0.0, Z=-50.0)
CastShadow=false
bCastDynamicShadow=false
bAcceptsLights=true
bForceDirectLightMap=true
LightingChannels=(BSP=true,Dynamic=true,Static=true,CompositeDynamic=true)
LightEnvironment=HealthFountainLightEnvironment
End Object
Components.Add(BaseMesh)


The next component that HealthFountain needs is the ParticleSystemComponent. This is obviously responsible for emitting particles. Before we create the component in DefaultProperties let's first add an instance variable at the top of the class definition:


var ParticleSystemComponent ParticleEffect;


You might remember that we wanted the number of particles to be getting lower depending on how many health points the HealthFountain had left. This variable will make it possible to pass values into the particle emitter at runtime to achieve that effect. So let’s look at the creation of ParticleSystemComponent in the DefaultProperties:


Begin Object Class=ParticleSystemComponent Name=ParticleSystemComponent0
Template=ParticleSystem'HealthFountain.Effects.P_HF_Whirl'
bAutoActivate=true
Translation=(X=0.0, Y=0.0, Z=-35.0)
End Object
ParticleEffect=ParticleSystemComponent0
Components.Add(ParticleSystemComponent0)


Template is where we can specify which ParticleSystem to use. Again notice the package and actual asset name. There is no need to type those paths manually, you can use Generic Browser to help a bit. With the asset selected in Generic Browser, right click and choose 'Copy Full Name to Clipboard' and then simply paste it in your code. The particle system will also auto start when the instance of our class is created. Again we need to do a translation operation to position this emitter slightly above the base static mesh. Finally, initialise instance variable ParticleEffect with this new ParticleSystemComponent and add it to actor components.

The ParticleSystem'HealthFountain.Effects.P_HF_Whirl' is a particle system I created for the purpose of this actor. So let’s have a quick look how this is set up. I won’t go into details of Unreal Cascade, but as usual I highly recommend learning more about it. You can watch 3DBuzz Unreal Cascade videos here.

The effect I tried to achieve was to have particles spinning around the Z axis with different offsets from it. I also wanted some particles to actually change their offset during their lifetime. There is a module for that called Orbit, and as the name suggests it can make the particles orbit around a certain point or axis in 3D space. In order to get the particles to change their orbiting offsets over time two things need to happen. First we need to set the Offset Distribution to Constant Curve and create several points (key frames) in the Curve Editor. In the Curve Editor we can set how the Offset values (OutVal) will change over particle lifetime (InVal). Once this is done, in the Offest Options we need to set 'Process During Spawn' to unchecked and 'Process During Update' to checked. This will make the Offset values change over time, as opposed to being assigned at particle spawn time. By default only 'Process During Spawn' is checked, turning Offset animations off and making the particle remain the same Offset value over its entire life.



Apart from Orbit module, there are also other modules applied to the 4 emitters in this Particle System. 'Size By Life' makes particles smaller towards the end of their life time, 'Acceleration Over Life' changes their velocity and Rotation modules set how the particles rotate around their own center. 'Color Over Life' changes particle brightness with time. Notice that in order to be able to change 'Color Over Life' in Cascade, the material needs to have a 'Vertex Color' material node affecting the color. In my case I used Material 'Envy_Effects.Tests.Materials.M_EFX_Particles_Flare01'.

Spawn module is where we can control how many particles should be spawned. In order to be able to change that number by code at runtime we need to set 'Spawn Rate Distribution' to DistributionFloatParticleParameter.

'Parameter Name' specifies the name of the parameter used to pass the value in UnrealScript or Kismet into this emitter.



'Min Input' and 'Max Input' define the range of values passed into the parameter. 'Min Output' and 'Max Output' specify the range of values for the 'Spawn Rate'. The Input and Output values are mapped (when 'Param Mode' is set to DPM_Normal), so for example if 'Min Input' is 0.0 and 'Max Input' is 1.0 and the 'Min Output' is 0.0 and 'Max Output' is 10.0, a parameter value of 0.5 will spawn the 50% value between 0.0 and 10.0. You can check that in Cascade by changing the Constant value. The Constant value will be used as the default value if no parameter value will be passed into the particle system through Kismet or UnrealScript at game time. For this reason let’s leave that at maximum, which in this case is 1.0, to see the HealthFountain full of particles. Each emitter in this particle system has the 'Spawn Rate Distribution' set to DistributionFloatParticleParameter and uses SpawnRateParameter as the 'Parameter Name'. This allows to control 4 emitter spawn rates with one single parameter, mapped differently. For example one emitter could have the 'Min Output' and 'Max Output' set to 0.0 and 10.0, while the other to 0.0 and 50.0.

Back in the DefaultProperties we can add one more component. An ambient sound that will play the Jump Pad sound in a loop (this SoundCue asset is set to looped):


Begin Object Class=AudioComponent Name=AmbientSound
SoundCue=SoundCue'A_Gameplay.JumpPad.JumpPad_Ambient01Cue'
bAutoPlay=true
bUseOwnerLocation=true
bShouldRemainActiveIfDropped=true
bStopWhenOwnerDestroyed=true
End Object
Components.Add(AmbientSound)


At this point the HealthFountain class has all the actor components we need. Next time we will add the code responsible for the actor's behaviour during gameplay.

Below is the entire code of HealthFountain class so far:


class HealthFountain extends Actor
ClassGroup(Custom)
placeable;

/** Health points stored by this instance */
var() int HealthPoints;

/** How much health points are regenerated */
var() int HealingAmount;


var ParticleSystemComponent ParticleEffect;



DefaultProperties
{
Begin Object Class=DynamicLightEnvironmentComponent Name=HealthFountainLightEnvironment
bEnabled=true
bDynamic=false
bCastShadows=false
End Object
Components.Add(HealthFountainLightEnvironment)

Begin Object Class=CylinderComponent Name=CollisionCylinder
CollisionRadius=32.0
CollisionHeight=50.000000
CollideActors=true
End Object
Components.Add(CollisionCylinder)
CollisionComponent=CollisionCylinder
bCollideActors=true

Begin Object Class=StaticMeshComponent Name=BaseMesh
StaticMesh=StaticMesh'Pickups.Base_Powerup.Mesh.S_Pickups_Base_Powerup01'
CollideActors=false
Translation=(X=0.0, Y=0.0, Z=-50.0)
CastShadow=false
bCastDynamicShadow=false
bAcceptsLights=true
bForceDirectLightMap=true
LightingChannels=(BSP=true,Dynamic=false,Static=true,CompositeDynamic=true)
LightEnvironment=HealthFountainLightEnvironment
End Object
Components.Add(BaseMesh)

Begin Object Class=ParticleSystemComponent Name=ParticleSystemComponent0
Template=ParticleSystem'HealthFountain.Effects.P_HF_Whirl'
bAutoActivate=true
Translation=(X=0.0, Y=0.0, Z=-35.0)
End Object
ParticleEffect=ParticleSystemComponent0
Components.Add(ParticleSystemComponent0)

Begin Object Class=AudioComponent Name=AmbientSound
SoundCue=SoundCue'A_Gameplay.JumpPad.JumpPad_Ambient01Cue'
bAutoPlay=true
bUseOwnerLocation=true
bShouldRemainActiveIfDropped=true
bStopWhenOwnerDestroyed=true
End Object
Components.Add(AmbientSound)

HealthPoints=100

HealingAmount=5
}

1 comment: