Hello everyone!
My buddy
Andrew contacted me the other day and asked if I wanted to help him redo a lot of the stuff that we made for
SpaceWings now that we have a bit more experience. So, we dredged up as many of the old files that we could and got to work figuring out what we needed to redo. He wanted to tackle most of the base code himself, while I took on getting the menus put together.
Unfortunately, the only assets we had from the original menus were screenshots. A bit of Photoshop magic and I had almost all the elements I needed. A major thing I was still missing was the font. None of the other guys that worked on it before that I talked to had the name of the font we used in the original. So, I had to find some new fonts. I then had to try and replicate the effects that Justin Bucher made for the originals. He's much more proficient at Photshop than I am though, but I did my best.
Here are some of the HUD assets:
Once I got the main aspects of the menus done I decided to start working on getting the pickup system working. This has four main parts to it: Picking up and classifying an item, adding to the inventory if applicable, updating the HUD if something was added to the inventory, and checking to see if the player has a particular item.
Picking Up and Classifying
Getting the item to be picked up proved to be surprisingly difficult for me. I haven't really worked with skeletal meshes/skinning/animation before in UDK. I got almost all of it set up thanks to some help from Andrew, mostly setting up the anim tree. The part that kept throwing me off was the collision. To get collision on a static mesh is really easy, but I was having a trouble with a skeletal mesh. Turns out I was looking at too much code and over looked adding BlockNonZeroExtent = true default properties of the skeletal mesh component...
As for classifying it, there is a variable in the actual pickup that holds what type of pickup it is. When the pickup is touched it calls a function in the player controller class that takes that variable and decides what to do with it. Here is my base pickup class:
class SW_Pickup extends Actor
abstract
placeable
ClassGroup(SW_Pickups);
var string PickupType;
var SpaceWingsPlayerController PC;
var SpaceWingsPawn P;
event Touch(Actor Other, PrimitiveComponent OtherComp, vector HitLocation, vector HitNormal)
{
PC = SpaceWingsPlayerController(GetALocalPlayerController());
P = SpaceWingsPawn(PC.Pawn);
PC.MyLog("A" @ PickupType @ "Pickup was touched", 'PickupTouch');
//If the instigator is the player, then process the pickup
if(Other == P)
{
PC.ProcessPickup(PickupType);
Destroy();
}
}
defaultproperties
{
PickupType = "None"
Begin Object Class=SkeletalMeshComponent Name=PickupMesh
Scale3D=(X=1.0, Y=1.0, Z=1.0)
CollideActors=true
BlockNonZeroExtent=true
End Object
Components.Add(PickupMesh)
CollisionComponent=PickupMesh
bCollideActors = true
bBlockActors = false
CollisionType = COLLIDE_TouchAll
}
And then all you have to do is extend off of that and add the relevant skeletal mesh properties in the defaultproperties of the child class and assign the item type to that variable I mentioned earlier. For example, here is the the red key card pickup class.
class SW_RedKeyCardPickup extends SW_Pickup;
defaultproperties
{
PickupType = "RedKeyCard"
Begin Object Name=PickupMesh
SkeletalMesh=SkeletalMesh'SW_Meshes.KeyCard.KeyCard'
Materials(0)=Material'SW_Meshes.KeyCard.RedKeyCard_Mat'
AnimSets(0)=AnimSet'SW_Meshes.KeyCard.KeyCard_Anim'
AnimTreeTemplate=AnimTree'SW_Meshes.KeyCard.KeyCard_AnimTree'
PhysicsAsset=PhysicsAsset'SW_Meshes.KeyCard.KeyCard_Physics'
End Object
}
Adding To My Inventory
Adding it to the inventory was really simple. Since the inventory can hold a maximum of 3 items I didn't need to implement a grand inventory manager. For simplicity I assigned each pickup that could be added to the inventory a number instead of trying to save a class into the array, or something overly complex like that. I could have done a string, but I end up using the id number when updating the HUD. Here are the functions that manage my small inventory:
exec function AddItem(int ItemID)
{
local int i;
if(ItemList.length < 3)
{
i = ItemList.length;
ItemList.length = ItemList.length + 1;
ItemList[i] = ItemID;
HUDGFx.UpdateItems();
}
else
{
MyLog("ItemList is full", 'AddItem');
}
}
function bool CheckForItem(int ItemID, out int FoundItemID)
{
local int i;
local bool bHaveItem;
bHaveItem = false;
for(i = 0; i < ItemList.length; i++)
{
if(ItemList[i] == ItemID)
{
bHaveitem = true;
FoundItemID = i;
}
}
return bHaveItem;
}
//This would be used like this in whatever function needs the check
//If not being called from this class, get PC reference like normal
/*
local int FoundID;
if(CheckForItem(1, FoundID)) //If we have a keycard (ID 1)
{
RemoveItem(FoundID);
//Insert the rest of the code that happens if they have the keycard
}
*/
exec function RemoveItem(int ItemID)
{
ItemList.Remove(ItemID, 1);
HUDGFx.UpdateItems();
}
Updating the HUD
Now that I have all of that in order I have to update the HUD to show which items I have in my inventory. As I do more and more Scaleform projects I try and migrate as much code as I can from AS3 to Unrealscript, since Unrealscript runs faster than AS3. Since I will be spawning movieclips directly from Unrealscript I have to keep a reference to them in order to adjust them or delete them. To help manage my references I decided to make my AttachMovie function a bit more dynamic on what parameters are passed. The first step was to create an array that holds the linkage names(to match the ones in Flash) and the desired instance names for all of the different item types that can be added to the inventory. The element number then corresponds to the ID I gave the items when I added them to the inventory. Here is how I have the AttachMovie and the reference array set up:
var struct ItemInfo
{
var string LinkageName; //Name used in Flash
var string InstanceName; //Desired Instance Name
} ItemReference;
//ItemSlot[0] holds the id number of the item assigned to that spot.
ItemOneSlot.AttachMovie(ItemReference[ItemList[0]].LinkageName, ItemReference[ItemList[0]].InstanceName);
ItemOneMC = GetVariableObject("_root.itemOneSlot_mc." $ ItemReference[ItemList[0]].InstanceName);
//In the defualt properties
ItemReference[1] = (LinkageName = "redKeyCard_item", InstanceName = "redKeyCard")
ItemReference[2] = (LinkageName = "blueKeyCard_item", InstanceName = "blueKeyCard")
CheckForItem Kismet Node
Since I like using Kismet for setting up triggers and other level specific things I needed a way to check if I had the correct item before the actual action could be executed. For instance, if they have the red keycard when trying to lower the force field. If they don't have it they shouldn't be able to lower it. So I made a custom Kistmet node that calls my check inventory function in my PlayerController class(see above), then outputs the correct result. I wanted to be able to set which item was being checked and also if I wanted it to be removed when the check was made. If I had like a master key card or something that could be used over and over again I wouldn't want it to be removed from my inventory. Anyway, here is my Kismet node:
class SeqCond_CheckForItem extends SequenceCondition;
var bool bResult;
var int ItemID;
/**Remove the item if found? */
var(Items) bool bRemove;
/**Expected Item Type*/
var(Items) enum ItemTypes
{
ITEM_RedKeyCard,
ITEM_BlueKeyCard
} ExpectedItem;
event Activated()
{
local SpaceWingsPlayerController PC;
PC = SpaceWingsPlayerController(class'WorldInfo'.static.GetWorldInfo().Game.GetALocalPlayerController());
bResult = PC.CheckForItem(ExpectedItem + 1, ItemID);
if(bResult && bRemove)
{
PC.RemoveItem(ItemID);
}
//If the check result is true, activate the first output link, else the second
OutputLinks[(bResult == true) ? 0 : 1].bHasImpulse = true;
}
defaultproperties
{
ObjName="CheckForItem"
ObjCategory="SpaceWings"
bRemove = true
InputLinks(0)=(LinkDesc="In")
OutputLinks(0)=(LinkDesc="True")
OutputLinks(1)=(LinkDesc="False")
VariableLinks(0)=(ExpectedType=class'SeqVar_Bool',LinkDesc="Result",bWriteable=true,PropertyName=bResult)
}
Wow, this post ended up being a lot longer than I had anticipated. I was going to throw up a video of the pickups and HUD in action, but that will have to wait until later. Time to work!