Musical Shooter – Basic Audio Engine

I thought I’d stick the audio engine system in from the very first test just to get a feel for how things were coming along:

I’ve added a sample to the firing of the projectiles as well as the click and beat.

Obviously, this is just a quick test and the sounds will get better but I think its got potential.
Once some enemies are in I can play around with the timing of their sounds/actions…

Advertisement

Musical Shooter – Projectiles

A little post on setting up projectiles:

With a little help from this post on the Processing forum I’ve got a stream of projectiles which are aimed using the 2nd stick on the gamepad.

To start with I drew an ellipse using code that is similar to that used to move the player sprite around, but based on the movement of the 2nd stick – this was for a visual reference when aiming (let’s call it a target)

The system uses the PVector class to store the x,y location of the player sprite and the target and then does a bit of maths on these vectors to determine the angle and direction between the two.

On every 4th frame (which equates to 1/16beat) the system creates a series of bullets (simple ellipses) within an array and moves each of them along the path defined by the vector maths and a speed variable.

Here’s a video:

I may need to constrain the aim/target so that its closer to the centre of the player sprite at all times to modify how the aiming behaves to enable faster switching from side to side…

Now to create some enemies and collision systems…

Musical Shooter – GamePad Testing

A brief post on setting up gamepad input:

This was fairly easy once I got to grips with the procontroll library that comes bundled with Processing – this gives you access to the sticks and buttons on a gamepad (once you’ve used the getDevice() function to determine the name of your gamepad).
The values from the stick are normalised (ie. -1 to +1) in both directions so using them to move the player sprite around was simply a case of using the following code:

playerSpriteX = playerSpriteX + stick1.getY();
playerSpriteY = playerSpriteY + stick1.getX();

Yes, I know the I’m using the y axis to set the x movement (and vice versa) – for some reason the x and y axis on the left stick are the wrong way round…!

I’m just using a basic ellipse at the moment until I get things working and will then change it for something that looks a bit nicer.

I then took the DynamicParticlesImmediate example from within Processing, modified the code a little and added that so that the player sprite leaves a particle trail as it moves around.

Then it was just a case of constraining the position of the player sprite so that it can’t leave the screen and adjusting the speed of movement a little.

Here’s a little video showing what I’ve got so far:

The next step is to create the weapon projectiles…

Musical Shooter – Beat Testing

I thought it would make sense to test whether the Processing engine is capable of being reliable enough to act as a metronome and to be used to trigger sounds on specific beats.

I used a tempo of 150bpm as this meant that the various beat divisions would all fall on ‘nice’ time values :
eg. 1/4beat @ 150bpm = 400ms; 1/8beat = 200ms; 1/16beat = 100ms, etc…
This then transfers quite nicely into frame rates as a frame rate of 40fps (which should be achievable) works out as 1/64beat (eg. 25ms).

The test program counts the number of frames and every 16 frames (1/4beat = 16x 1/64beat) it triggers a click sample. At the same time it is counting the number of 1/4beats and after 2bars worth it (re)triggers a 2bar loop.
This would determine whether the timing was accurate and reliable as the click and the loop should (hopefully) stay in time.

It turns out that this little test was indeed successful and everything stays in time and sounds OK!!

The next step was to determine whether the system was still reliable whilst also having to do some graphical processing.
To do this, I used one of the examples provided with Processing (‘Bouncing Ball’) and modified the code slightly so that there were 700 balls all bouncing around the screen, each one continuously checking for collisions with all of the other balls and the boundary of the screen.

This worked as well!! (we could be on to something here…)
Whilst this is not exactly the most processor intensive task ever performed by a computer, it did demonstrate that for moderately basic applications (which is all my game is going to be) the system is reliable enough.

I’ve created a brief video that demonstrates the system so far, although I’ve reduced the number of balls to only 50 as it just looks better and you can see the individual collisions more clearly.
At the moment there is no ‘player’ interaction or anything, but as the balls bounce around, it does feel like they are aligned with the musical pulse.

Now that I know the idea is worth pursuing, its time to start working on ‘playable’ elements such as the being able to move a sprite around using a gamepad, weapon projectiles, enemies, collisions, etc…

Update

OK, so I’ve been a bit slack in posting anything for the past month as I’ve been busy with work. But I’ve managed to carve out some free time and have started a little project in Processing which will keep me pretty busy for a while and should lead to some mor regular posts.

The idea is to create a top-down space shooter in the style of Asteroids / Geometry Wars, with the aim of aligning as many of the ingame events / actions to the music.
Essentially this is a follow on from some the work I was doing in UDK to try and create a stable metronome system – turns out this isn’t possible without ‘Developer Access’ to the underlying source code.

Hopefully I’ll be posting reasonably regularly as I try out ideas and work on this…

Comparing Floats within a range

So, today I needed to be able to check whether a float fell within a given range. At first I set about using a series of nested Compare objects but this was proving to be a headache getting everything in the right order and preventing ‘false triggers’.

After a while I thought, ‘why don’t I just script something that does this – how hard can it be?’ – turns out, not very.

So, here’s the code for a comparison object within Kismet that checks to see if value A falls within a range defined by value B and value C. 
The outputs are for whether value A falls within the range or outside of the defined range.

/**
* Checks to see whether input value A is within range defined by values B & C
*/
class SeqCond_CompareFloatRange extends SequenceCondition;

var() float ValueA;
var() float ValueB;
var() float ValueC;

function Activated()
{
// compare the values and set appropriate output impulse
if (ValueA <= ValueC && ValueA >= ValueB) {
OutputLinks[0].bHasImpulse = TRUE;
}
else
{
OutputLinks[1].bHasImpulse = TRUE;
}
}
defaultproperties
{
ObjName="Compare Float Range"
ObjCategory="GATComparison"
InputLinks(0)=(LinkDesc="In")
OutputLinks(0)=(LinkDesc="In Range")
OutputLinks(1)=(LinkDesc="Out of Range") VariableLinks(0)=(ExpectedType=class'SeqVar_Float',LinkDesc="A",PropertyName=ValueA)
VariableLinks(1)=(ExpectedType=class'SeqVar_Float',LinkDesc="B",PropertyName=ValueB)
VariableLinks(2)=(ExpectedType=class'SeqVar_Float',LinkDesc="C",PropertyName=ValueC)
}

Feel free to take and use as you see fit…

Metronome

So, I decided to try and build a Kismet-based metronome system so that I can trigger small musical elements (reliably through a sort of music sequencer type system) and also in-game events (eg. enemy spawn, explosions, etc…) in time with the music. Originally I tried to do this directly in Kismet using looping Delay objects, but I found that they didn’t keep reliable time.

The first attempt involved using the SetTimer() function, which was set to the 16th beat division of a given tempo (eg. 125ms @ 120bpm). This was then used to ‘fire’ a custom Kismet event. But again, I found that the timing was unreliable.
After some research, I surmised that this was due to the fact that Actors in UDK are updated every tick and so the delay function could be out by a full frame length (approx. 20ms on my system).

So, I decided to do the following:
Establish the ‘target’ time of the next beat division by getting the system time and adding the beat division length (eg. 125ms) to this.
Then use the Tick() function to check whether the elapsed time was equal to the ‘target’ time, using the DeltaTime variable (returned within the Tick function).
Now, the elapsed time is unlikely to actual equal the target time so I built in a ‘checking system’ to see if the elapsed time was within 1/2 of a frame length (either under or over) and if so this would be used as the 16th beat trigger. This is not ideal, but I figured that it would only be around 10ms out either way which should be close enough (for now anyways…)
I was also counting the number of 16th beats so that I could trigger 8th/4th/etc. beats as well.

The problem is that when I set up a basic Kismet system to trigger off a sound using my ‘trigger’s it wasn’t keeping reliable time when compared to a long looping drum beat at the same tempo — it feels like its out by more than 10ms.

___________________________________________________________________________

Here’s the code for the Timer (this currently a placeable actor rather than a Kismet Action as at the time this seemed easier, but it will become Kismet-based eventually):

/*
// actor to act as a metronome
// uses time elapsed to check against target time intervals - performed every tick
// setting the ShouldBeOn property (via Kismet) does the following
// - 1 = activate
// - 2 = deactivate
// tempo calculations performed on activation
//
// CHANGES:
// would using an average of DT help with accuracy (would possibly counter effects of occasional long tick time)????
// need to fire off all Kismet events & update 16thBeat Count on start
//
*/
class AwesomeTimerActor extends Actor
 hidecategories(Attachment, Physics, Debug, Object) // hide these categories in the properties of this actor
 placeable;
var StaticMeshComponent MyMesh;
var int ShouldBeOn;
var float Tempo;
var float DivLength16th;
var int Year, Month, DayOfWeek, Day, Hour, Min, Sec, MSec;
var int SystemStartTimeIntA, SystemStartTimeIntB;
var int DeltaTimeMsec, accDT, TargetTime, CurrentTime, Count16thBeats;
var int StartTime;
function Tick(float DeltaTime)
{
 //`log(DeltaTime);
if(ShouldBeOn == 1) //start Actor functionality
 {
 //`log("======================================== ShouldBeOn == 1");
GetSystemTime( Year, Month, DayOfWeek, Day, Hour, Min, Sec, MSec ); //Get the system time
 //`log(" Year:" $ Year $ " Month:" $ Month $ " DayOfWeek:" $ DayOfWeek $ " Day:" $ Day $ " Hour:" $ Hour $ " Min:" $ Min $ " Sec:" $ Sec $ " MSec:" $ MSec); //log system time
SystemStartTimeIntA = (Year * 10000) + (Month * 100) + (Day); //combine Year, Month & Day - done to preserve leading 0's
 //`log("SystemStartTimeIntA: " $ SystemStartTimeIntA);
 SystemStartTimeIntB = (Hour * 10000000) + (Min * 100000) + (Sec * 1000) + (MSec); //combine Hour, Min, Sec & MSec - done to preserve leading 0's
 //`log("SystemStartTimeIntB: " $ SystemStartTimeIntB);
DivLength16th = ((1000 / (Tempo / 60)) / 4); //calculate lenth (in msec) of 16th beat division
 //`log("DivLength16th: " $ DivLength16th);
Count16thBeats = 1; //number of 16th beat divisions
ShouldBeOn = 3; //set to 3 so can use another if check - used to move into the metronome system
 }
if(ShouldBeOn == 2) //stop Actor functionality
 {
 //`log("========================================= ShouldBeOn == 2");
 ShouldBeOn = 0;
 }
if(ShouldBeOn == 3) //metronome system
 {
 DeltaTimeMSec = DeltaTime * 1000; //convert DT from secs to msecs
 //`log("DeltatimeMSec: " $ DeltaTimeMSec);
 accDT += DeltaTimeMSec; //how much time has elapsed (in msec) since start
 //`log("accDT: " $ accDT);
 TargetTime = SystemStartTimeIntB + (DivLength16th * Count16thBeats); //calculate target time of next 16th beat division
 //`log("TargetTime: " $ TargetTime);
 CurrentTime = SystemStartTimeIntB + accDT; //increase current time
 //`log("CurrentTime: " $ CurrentTime);
 
 if(CurrentTime == TargetTime) //if current time is equal to target time (unlikely, but possible)
 {
 //`log("16th Beat Trigger - equal");
 TriggerKismet16thBeat(); //Trigger Kismet 16th Beat
if(Count16thBeats % 2 == 0) //check for 8th Beat
 {
 //`log("8th Beat Trigger");
 TriggerKismet8thBeat(); //Trigger Kismet 8th Beat
 }
if(Count16thBeats % 4 == 0) //check for 4th Beat
 {
 //`log("4th Beat Trigger");
 TriggerKismet4thBeat(); //Trigger Kismet 4th Beat
 }
if(Count16thBeats % 16 == 0) //check for 1 Bar
 {
 //`log("1 Bar Trigger");
 TriggerKismet1Bar(); //Trigger Kismet 1 Bar
 }
if(Count16thBeats % 32 == 0) //check for 2 Bar
 {
 //`log("2 Bar Trigger");
 TriggerKismet2Bar(); //Trigger Kismet 2 Bar
 }
Count16thBeats++; //increase count of 16th beat divisions
 }
else if(CurrentTime >= (TargetTime - (DeltaTimeMSec / 2)) && CurrentTime <= TargetTime) //if current time is less than 1/2DT under target time (ie. close enough)
 {
 //`log("16th Beat Trigger - under");
 TriggerKismet16thBeat(); //Trigger Kismet 16th Beat
if(Count16thBeats % 2 == 0) //check for 8th Beat
 {
 //`log("8th Beat Trigger");
 TriggerKismet8thBeat(); //Trigger Kismet 8th Beat
 }
if(Count16thBeats % 4 == 0) //check for 4th Beat
 {
 //`log("4th Beat Trigger");
 TriggerKismet4thBeat(); //Trigger Kismet 4th Beat
 }
if(Count16thBeats % 16 == 0) //check for 1 Bar
 {
 //`log("1 Bar Trigger");
 TriggerKismet1Bar(); //Trigger Kismet 1 Bar
 }
if(Count16thBeats % 32 == 0) //check for 2 Bar
 {
 //`log("2 Bar Trigger");
 TriggerKismet2Bar(); //Trigger Kismet 2 Bar
 }
Count16thBeats++; //increase count of 16th beat divisions
 }
else if(CurrentTime <= (TargetTime + (DeltaTimeMSec / 2)) && CurrentTime >= TargetTime) //if current time is less then 1/2DT over target time (ie. close enough)
 {
 //`log("16th Beat Trigger - over");
 TriggerKismet16thBeat(); //Trigger Kismet 16th Beat
if(Count16thBeats % 2 == 0) //check for 8th Beat
 {
 //`log("8th Beat Trigger");
 TriggerKismet8thBeat(); //Trigger Kismet 8th Beat
 }
if(Count16thBeats % 4 == 0) //check for 4th Beat
 {
 //`log("4th Beat Trigger");
 TriggerKismet4thBeat(); //Trigger Kismet 4th Beat
 }
if(Count16thBeats % 16 == 0) //check for 1 Bar
 {
 //`log("1 Bar Trigger");
 TriggerKismet1Bar(); //Trigger Kismet 1 Bar
 }
if(Count16thBeats % 32 == 0) //check for 2 Bar
 {
 //`log("2 Bar Trigger");
 TriggerKismet2Bar(); //Trigger Kismet 2 Bar
 }
Count16thBeats++; //increase count of 16th beat divisions
 }
 //`log(""); //used to put a line break into log to separate data streams
 }
}

function TriggerKismet2Bar()
{
 //`log("============================= Timer2Bar");
 TriggerEventClass(class'AwesomeSeqEvent_Timer2Bar', self);
}
function TriggerKismet1Bar()
{
 //`log("============================= Timer1Bar");
 TriggerEventClass(class'AwesomeSeqEvent_Timer1Bar', self);
}
function TriggerKismet4thBeat()
{
 //`log("============================== Timer4thBeat");
 TriggerEventClass(class'AwesomeSeqEvent_Timer4thBeat', self);
}
function TriggerKismet8thBeat()
{
 //`log("================================== Timer8thBeat");
 TriggerEventClass(class'AwesomeSeqEvent_Timer8thBeat', self);
}
function TriggerKismet16thBeat()
{
 //`log("================================== Timer16thBeat");
 TriggerEventCLass(class'AwesomeSeqEvent_Timer16thBeat', self);
}
defaultproperties
{
 CustomTimeDilation=1.0
Tempo=120
ShouldBeOn=0
SupportedEvents.Add(class'AwesomeSeqEvent_Timer2Bar')
 SupportedEvents.Add(class'AwesomeSeqEvent_Timer1Bar')
 SupportedEvents.Add(class'AwesomeSeqEvent_Timer4thBeat')
 SupportedEvents.Add(class'AwesomeSeqEvent_Timer8thBeat')
 SupportedEvents.Add(class'AwesomeSeqEvent_Timer16thBEat')
// add a static mesh with a material
 Begin Object Name=PickupMesh
 StaticMesh=StaticMesh'UN_SimpleMeshes.TexPropCube_Dup'
 Materials(0)=Material'EditorMaterials.WidgetMaterial_Y'
 Scale3D=(X=0.125,Y=0.125,Z=0.125)
 End Object
 Components.Add(PickupMesh)
 MyMesh=PickupMesh
}

___________________________________________________________________________

Here’s the code for the Kismet Events:

class AwesomeSeqEvent_Timer16thBeat extends SequenceEvent;
event Activated()
{
 //`log("=============================== Timer8thBeat EventActivated");
}
defaultproperties
{
 MaxTriggerCount=0
 ObjName="Timer16thBeat"
 ObjCategory="Awesome Game"
 VariableLinks.Empty
 bPlayerOnly=false
}

______________

class AwesomeSeqEvent_Timer8thBeat extends SequenceEvent;
event Activated()
{
 //`log("=============================== Timer8thBeat EventActivated");
}
defaultproperties
{
 MaxTriggerCount=0
 ObjName="Timer8thBeat"
 ObjCategory="Awesome Game"
 VariableLinks.Empty
 bPlayerOnly=false
}

_______________

class AwesomeSeqEvent_Timer4thBeat extends SequenceEvent;
event Activated()
{
 //`log("=============================== Timer4thBeat EventActivated");
}
defaultproperties
{
 MaxTriggerCount=0
 ObjName="Timer4thBeat"
 ObjCategory="Awesome Game"
 VariableLinks.Empty
 bPlayerOnly=false
}

_______________

class AwesomeSeqEvent_Timer2Bar extends SequenceEvent;
event Activated()
{
 //`log("=============================== Timer2Bar EventActivated");
}
defaultproperties
{
 MaxTriggerCount=0
 ObjName="Timer2Bar"
 ObjCategory="Awesome Game"
 VariableLinks.Empty
 bPlayerOnly=false
}

_______________

class AwesomeSeqEvent_Timer1Bar extends SequenceEvent;
event Activated()
{
 //`log("=============================== Timer1Bar EventActivated");
}
defaultproperties
{
 MaxTriggerCount=0
 ObjName="Timer1Bar"
 ObjCategory="Awesome Game"
 VariableLinks.Empty
 bPlayerOnly=false
}

___________________________________________________________________________

Now, I’m not sure if the unreliable timing is just the way it is within UDK without proper access to the underlying engine code (I’m not a developer) or if its due to inefficiencies in my code..?

Target Marked 02

Today I finished off the prototype system for this little mini-game.
I came up with a little list of things I had to do to:
– cross-hairs for aiming
– confirmation beep for when the player aims at a target
– music bed (looping)
– air strike music (non-looping)
– Matinee sequence for air strike
– explosions, destroy buildings, etc…

I worked my way through the list in order of easiness!

I started off by implementing a confirmation beep for when the player aims at a target (when the glowing cube appears). This uses the result of the Object comparison – if there’s a match (ie. the Player is aiming at one of the designated targets) then playa beep sound. I had to use a Gate to prevent repeated triggers of the Play Sound object whilst the Player continues to aim at the same target. I also had to set up 3x separate systems, one for each target to prevent false triggers.

The next step was to create a weapon that would provide us with some cross-hairs so that the Player can more accurately aim at the target buildings.
I simply modified the existing Shock rifle weapon and removed all references to Skeletal Meshes and Anim Sets so that there is no actual weapon visible in the Player’s hands – this just left the cross-hairs. The scripts compiled OK, but there were some minor errors as a result of the missing references. The weapon would still fire, which looked quite odd as the projectiles just appeared in thin air – so I had to solve this next.
This was actually relatively simple, I just worked my way through the weapon’s code and commented out any line that instructed the game engine to play firing sounds and/or generate any firing animations/graphics/projectiles.

The next step was to be able to trigger off Kismet systems when the Player presses what used to be fire to initiate the air strike sequences. We did something similar in the Game Audio Tutorial for the sniper rifle weapon, so I used that as a reference and adapted the code to suit my purpose here.
The line “PlayerController(Instigator.Controller).ServerCauseEvent(‘TargetMarked’);” was inserted into the StartFire function so that when the Player ‘fires’ the aiming weapon, a Console Event named TargetMarked is triggered. This is received within Kismet using the Console Event object. In order to prevent false triggerings, a Gate is used between the Console Event and the Announcement / Matinee objects – the Gate is only opened when the Player is aiming at a designated target (otherwise they could call an airstrike on anything!). Separate Console Event objects and their associated Announcement / Matinee objects were needed for each of the targets.

For the music itself, some pre-existing tracks were used – nothing particularly fancy, but they serve a purpose – which was to enable me to quickly produce a prototype system.
The system that controls the music is relatively straightforward – a looping delay is used as a metronome and is set to generate a pulse on every beat at the tempo of the music bed. A Counter counts the number of beats and on the 8th beat (ie. 2x bars = length of music bed) it resets the count and re-triggers the music bed to produce a looping musical bed. When the Player targets a building (ie. aims and ‘fires’) 2x Gates are switched; the first prevents the music bed being re-triggered (this Gate was originall Open); the second Gate (which was originally Closed) is opened in order to allow the air strike music to be triggered. The next time the Counter reaches 8 (ie. the end of the current loop), the air strike music plays – This system ensures that the transition from music bed to air strike music always happens on a musical point.
At the same time, a Matinee (which controls a plane – well actually just a Manta vehicle!) is started which has been timed such that the plane reaches the targeted building just as the music climaxes. On the climax of the music some explosion Emitters are Toggled on, as is a RB_RadialImpulseActor which casues the building to ‘blow up’ (all the Actors that make up the target buildings are KActors and so respond to Physics), and an explosion sound is played.
When the explosion has finished, the music bed is faded back up and looped again.

The system has a number of switching Gates that prevent the Player from targetting buildings that have already been destroyed and also targetting buildings while the current air strike is still in progress.

The timing of the music ‘metronome’ and therefore the looping of the music bed is not 100% accurate (but when is it ever with UDK!) – the exact values of the system can be tweaked once the ‘proper’ music has been integrated rather than the temp music currently in use.

——————————————

Next Steps:
– Does it need a custom cross-hair image?
– Do I need to create ’empty’ Skeletal Maesh and Anim Set?
– ‘Proper’ music
– The system may need some tweaking when ‘proper’ music is out in – timings, etc…

Target Marked 01

This is another little system that we will be using as part of the AES Presentation.
The aim is to create a ‘mini-game’ where the player has to mark some targets for airstrike and the mechanics of which are timed to fit within musical bounds.

The first step was to knock up a quick environment for prototyping. I wanted the Player to on a hill and to have a number of targets (ie. buildings) in the environment.
I created some basic Terrain and then extruded one corner vertically to create a hill for the Player to stand on and look down on the game area.

I started off with a simple test system using the Trace object and a crate Static Mesh.
The Trace object within Kismet draws a straight line between 2 objects and determines whether that line is obstrcuted or not. We used the Trace object a few times within the Game Audio Tutorial – an example would be the rotating turret in room 406.
If you use the same object for the start and end points of the trace you can use the End Offset property to determine the length of the trace – ie. using the Player as the start and end point and applying an offset along the x axis of 1024 would produce a trace that started at the Player and drew a line 1024 units along the x axis.
If/when an object is ‘hit’ as part of the trace, the object passes the name of the ‘hit’ object, the distance from the start of the trace and the location of the ‘hit’ object.
The intention was to simply determine whether the Trace object could identify when the player is looking/aiming at the crate. I just attached the Obstructed output of the Trace object to a Log.

This little test kind of worked. In that the Trace was successful only if the Player were to stand in exactly the right location relative to the object and jump – that took some figuring out!
The Trace uses the x,y,z location of the player and appeared to eminate from the feet of the Player (hence the need to jump) – the main problem was that it didn’t take into account the direction of the Player’s view/aim.

I spent a little time searching the various forums and came across this very useful tutorial (http://utforums.epicgames.com/showthread.php?p=28104486) which positions a simple cube static mesh (it could be anything really) in exactly the same position and rotation as the Player and then attaches a DynamicTrigger to this cube with an offset in the x axis. The Player is the start of the Trace and the DynamicTrigger is the end. Ideally you’d just attach the DynamicTrigger directly to the Player, but the Attach to Actor object doesn’t extract the rotation of the Player very consistently, but it works fine for an InterpActor. The DynamicTrigger follows the movement of the Player’s view perfectly.
The Trace object determines whether any object falls between the Player and the DynamicTrigger.
The original tutorial did some quite clever stuff that was unncessary for these purposes and so the system has been simplified quite a bit and simply compares the name of the ‘hit’ object with the ‘known’ name of the crate and if they match (ie. the Player is looking at the crate) I unhid a glowing cube – this will be used in the final system to indicate that the Player is looking at a ‘target’.
The system is driven by a repeating Delay object so that the Trace object is continuously re-checking the trace between the Player and the DynamicTrigger.

After a brief testing period to ensure everything worked as it should I expanded the game to contain 5 small buildings, 3 of which are targets (identified with barrels/crates outside them). If the Player looks at one of the targets a glowing cube appears surrounding the target building. If the Player looks away from a target, the glowing cube disappears.

Next steps:
– Modify the sniper rifle weapon system from the Game Audio Tutorial – has a zoom-in aiming mode
— will need a new image for the cross-hair
– implement music system
— looping ambient bed
— ‘air strike’ music to accompany timed air strike Matinee sequence – both only triggered at musical points

Lego Blocks 02

After some quick experiments it turns out that using Matinees to ‘reset’ the blocks, as proposed in an earlier post, does not work as I’d hoped!

So, an alternative was required…

I decided to create a system that uses multiple copies of the blocks for each sub-sequence that is required, with only the current set of blocks being visible at any one time – I couldn’t figure out how to create a more elegant solution that enabled a single set of blocks to be formed into various structures, yet also respond to Physics when shot.
The first, prototype system started off with a set of blocks that were immediately affected by a [RB_RadialImpulseActor] Physics Actor which ‘pushes’ all the blocks outwards to form a pile.
When the Player presses E a sequence of [RB_RadialImpulseActor] Physics Actors are used to ‘pull’ the pile of blocks into a swirling mass of blocks.
These are then destroyed and the next set of blocks, pre-formed into a structure, is ‘unhidden’ – a series of particle emitters are used to mask the transition (eg. smoke, sparks, etc).
This worked fairly well – the movement of the blocks as a result of the various Physics Actors looked quite nice.
In order to prevent the 2nd set of blocks being affected by the Physics Actors their Movement property was set to PHYS_None.

As the different sets of blocks will be positioned in the same location this could make selecting/editing each set of blocks problematic.
To help with this I am using the Group feature of an object’s properties. You can define any Actor as belonging to a specific Group and then, using the Content Browser’s Group page, set each Group’s visibilty independently.

As the Physics of each subsequent set of blocks has been set to PHYS_None to prevent them being affected by the various Physic Actors, they now longer respond to being shot – so for the time being I’ve set up a system that uses key presses to trigger each stage of the system rather than a Take Damage event.
This is working OK, but may need to be resolved for the presentation…

After extending the system up to the point where it forms the ‘solid’ castle, I thought I’d found an odd behaviour!
The blocks appear to respond to Physics Actors inconsisently… When the Physics Actor is activated to break up the boat structure it seemed to be ‘pulling’ them in rather than ‘pushing’ them out. It turned out this behaviour was due to the previous ‘pulling’ Physics Actor not being toggled off and so it’s effect was still being applied to the boat structure.

One problem I did have was that even though the blocks/structures were hidden from the start they still ‘exist’ in terms of physics and so would get in the way of visible blocks as they fly around the area. I originally thought that this was due to the COLLIDE property being set to BlockAll – so I set all blocks to be COLLIDE_NoCollision and then used Modify Property Actions to set it back to BlockAll when I wanted them to respond to Physics. Now however, as soon as the blocks are set back to PHYS_RigidBody (so they will respond to Physics Actors) they just fall through the floor!
After a little investigation I found that it’s the Block Rigid Body property that prevents/causes this behaviour depending on whether it’s set to true/false – it’s also worth noting that setting to COLLIDE_NoCollision automatically sets the Block Rigid Body to false – it would seem that setting the COLLIDE property to BlockAll using a Modify Property Action doesn’t set it back to true. I tried to get around this using an additional Modify Property to set the Block Rigid Body but this did not seem to work.
Eventually I found the Set Rigid Body Action within Kismet – this must be a relatively new addition to UDK; I must have missed it…! Using this to set all the blocks to Block Rigid Body and then to PHYS_RigidBody meant that they behaved as they should.

Now I had the first stages of the planned system (albeit responding to key presses rather than being shot) – namely a set of blocks that immediately form a pile, which re-form into a castle strtucture, this then falls apart into a pile which re-forms into a boat structure. This falls apart and re-forms into a second castle structure.
The next step was to create the ‘solid’ castle…

I originally tried ‘attaching’ all the blocks in the structure to a ‘master’ block and then using Physics Actors in the thought that as all blocks are attached to one, they would follow the movement of the ‘master’ (one block to rule them all!). However, this didn’t work as they all just flew apart as they had done before.
The next attempt involved keeping the ‘attached’ system and using Matinee to animate the movement of the ‘master’ block – to do this the blocks had to be set to PHYS_Interpolating rather than RigidBody to enable the Matinee to control them. This works quite nicely, although the actual animation could be improved with a bit of effort – it looks a little simple at the moment.

Once I had all the behaviours I wanted I then needed to get the structure to break apart when shot rather than rely on a key press as this would look/feel better – the key press can still be used to re-form the structures.
This was actually relatively simple – the blocks just had to be set to COLLIDE_BlockAll. There were no problems with blocks interfering with earlier movements/systems as their Block Rigid Body property is set to false. I used the Attach to Event Action within Kismet to attach each block within a structure to a Take Damage event which simply replaced the original key press events.

I now have the system that was originally planned and everything seems to be working as intended, although the boat structure doesn’t appear to respond to being shot at every possible location (I’ve double-checked the properties and I can’t see anything!). It may be that this is not a major problem as the system is only for our use during a presentation.

Next steps:
– does it need sound?
– check with RS that he’s happy with it (and can’t put up with the boat’s behaviour)