So I’m starting with something a bit more fun. We’re taking this AxieObject I made in maya and we’re going to fill a room with a single object placement. Using similar techniques you can build procedural buildings and fill scenes with more interesting objects than normal.
So lets make a basic component with the following code:
class AxisObject extends Actor
placeable;
defaultproperties
{
Begin Object class=SkeletalMeshComponent Name=SkeletalMeshComponent0
SkeletalMesh=SkeletalMesh'AxisObject.AxisObject_SKM'
End Object
Components.Add(SkeletalMeshComponent0)
}
Save that as AxisObject.uc
This will take the axisobject_SKM inside of the AxisObject.UPK (download it here) and make it into a placeable actor object from the generic browser. So far nothing interesting, but we need a code version of the skeletalmesh so that other code-objects can see it. Other scripts will see this as “RoboGame.AxisObject”.
What we’re going to do is make another code-object that “spawns” or makes an instance of the AxisObject in the level just before you begin to play. A Very basic version of this looks like the following code:
class AxisObjectSpawner extends actor
placeable;
simulated function PostBeginPlay()
{
local AxisObject AxObject;
AxObject = spawn(class'AxisObject');
}
defaultproperties
{
}
In the function PostBeginPlay() we’re doing two things, first we’re creating a new local variable for an AxisObject, then we’re filling it in with an actual AxisObject by calling the spawn function. But what would this look like if we wanted to spawn two objects? Here’s the straight forward way to accomplish that:
class AxisObjectSpawner extends actor
placeable;
simulated function PostBeginPlay()
{
local AxisObject FirstObject, SecondObject;
FirstObject = spawn(class'AxisObject');
SecondObject = spawn(class'AxisObject');
}
defaultproperties
{
}
Not very interesting, and what would that look like if you wanted 50 of them? That’s a lot of copy and paste. Worse yet, what if you wanted one of these spawners to make 30 and one to make 100, you’d have to write a new version for each different amount of spawned objects. There’s got to be a better way! In fact there is.
class AxisObjectSpawner extends Actor
placeable;
var() int NumberOfObjects;
simulated function PostBeginPlay()
{
local array AxisObjects;
local int i;
for (i = 0; i < NumberOfObjects ; i++)
{
AxisObjects.AddItem(spawn(class'AxisObject'));
`log(AxisObjects[i]);//This will print out what item we've added to the array
}
}
defaultproperties
{
NumberOfObjects = 10
}
So, this is what the code does in the game. but all 10 objects are in the exact same place, Not so interesting is it? So how would we be able to make each one in a different place? We can use the for functions “i” variable, if you’re not up to date on the for loop then you can take a look back on this article here. We’ll use “set location” to make each object appear in a different place.
simulated function PostBeginPlay()
{
local array AxisObjects;
local int i;
local vector vec;
vec.y = 0;
vec.z = 0;
for (i = 0; i < NumberOfObjects ; i++)
{
vec.x = 10*i;
AxisObjects.AddItem(spawn(class'AxisObject'));
AxisObjects[i].SetLocation(vec); //this is how we set the objects location here
`log(AxisObjects[i]);
`log(vec.x);
}
}
Alright, so we’ve got a new vector we’re going to use and we’re going to notice a couple of interesting things. In the for loop we’ve got vec.x (x is a part of the vector object) and we’re going to multiply it by “i” remember that i gets increased by 1 each time the for loop is iterated. and we’re going to see what the vec.x will be from the `log(vec.x); printing out each iteration.
But it’s so far away? why is that? well, depending on where your level’s origin is if you think about it, the new objects are spawning around 0,0,10, then 0,0,20, etc… So if the world’s origin is somwhere different form where the actor was placed then your spawned objects are doing exactly what they’er told. (if your placed object is at like 323,12,-500) Thankfully we’ve got the object whos spawning all of these things location. We can add that location to our new vec and make sure that the objects spawn around the placed actor in the scene.
AxisObjects[i].SetLocation(vec + location);
So that’s what you’d add to the SetLocation to make the axis objects appear around the actor that’s spawning them.
Okay, that’s better! However we should be more thoughtful about this; PostBeginPlay() is starting to get crowded. The more we do, the more important it is to stay organized. Lets consider making this into it’s own function. There are a bunch of reasons why we’d do this, mostly it’s an to remain organized and allow other programmers look at your code and only edit a specific part. lets consider the following version of AxisObjectSpawner:
class AxisObjectSpawner extends Actor
placeable;
var() int NumberOfObjects;
//When this object is placed we can change how many are spawned.
simulated function PostBeginPlay()
{
CreateObjects();
}
function CreateObjects()
{
local array AxisObjects;
local int i;
local vector vec;
vec.y = 0;
vec.z = 0;
for (i = 0; i < NumberOfObjects ; i++)
{
vec.x = 10*i;
AxisObjects.AddItem(spawn(class'AxisObject'));
AxisObjects[i].SetLocation(vec + location);
`log(AxisObjects[i]);
`log(vec.x);
}
}
defaultproperties
{
NumberOfObjects = 10
}
So what’s happening here? we’ve just copied and pasted the entire contents of the PostBeginPlay and made a new function CreateObjects() where we’ve put everything. This also means that we could super class this AxisObjectSpawner and use the CreateObjects() function from a different script, more on this in a bit. But for now lets get a bit more clever so we can make this process even more flexible so another class can access even more functions.
Perhaps we want to change the placement behavior of this class, for instance what if we wanted a random pattern versus our current linear pattern how would we accomplish that? we’re going to need a function to allow us to choose how the objects are spawned. Lets do a bit of thinking on this for a bit.
First we’re going to want to be able to choose between random and linear. I could go on about a switch statement, but I’ll hold off for a moment. Right now, lets look at this trick.
class AxisObjectSpawner extends Actor
placeable;
var() int NumberOfObjects;
simulated function PostBeginPlay()
{
CreateObject();
}
function CreateObject()
{
local array AxisObjects;
local int i;
for (i = 0; i < NumberOfObjects ; i++)
{
AxisObjects.AddItem(spawn(class'AxisObject'));
AxisObjects[i].SetLocation( SetRandomLocation() + location); //something tricky is going on here
`log(AxisObjects[i]);
`log(AxisObjects[i].location);
}
}
function vector SetRandomLocation()
{
local vector vec;
vec.x = (FRand()*10 - FRand()*10);
vec.y = (FRand()*10 - FRand()*10);
vec.z = (FRand()*10 - FRand()*10);
return vec;
}
defaultproperties
{
NumberOfObjects = 10
}
In particular look at this line:
AxisObjects[i].SetLocation( SetRandomLocation() + location);
Functions can act as values. Before we’d have to use a vector + location (location is also a vector) so we can only use variable types which are the same. What’s this all about?
function vector SetRandomLocation()
{
local vector vec;
vec.x = (FRand()*10 - FRand()*10);
vec.y = (FRand()*10 - FRand()*10);
vec.z = (FRand()*10 - FRand()*10);
return vec;
}
We’ve used a new way to declare a function. This is “function vector SetRandomLocation” The added vector means that this function is actually a vector. To make sure that this function is a vector we need to “return” a vector at some point. That’s why at the end of this very simple function we have “return vec;” this means that we can do a lot of other processes in this function before returning as a vector. Compilers will remind you that you need to have your function return a value if you don’t add the “return” somewhere in the function.
how clever is that? But this function is particular to setting a random location, this is good, we wanted to be able to do that. So how do we pick between random or linear? well I guess we might want another linear function as well. We could make one function that can do either, or one function for each one. Lets explore both
A quick note about FRand(), FRand() will make a random number between 0 and 1, so it’s best to multiply it by something. Additionally, why did we use FRand()*10 – FRand()*10 rather than just FRand()*10? Simply put, we would like to put the objects around the actor’s location not just positive X Y and Z. You can try for yourself and see what vec.x = (FRand()*10); looks like. You might not notice it at first but the objects are only spawning to one sort of area arund the placed actor.
Lets make a change to our code to make it a bit more generic, lets take a look at this new revision:
class AxisObjectSpawner extends Actor
placeable;
var() int NumberOfObjects;
var() float ObjectSpread;
var() enum eSpawnPattern
{
Random,
Linear
}SpawnPattern;
simulated function PostBeginPlay()
{
CreateObject();
}
function CreateObject()
{
local array AxisObjects;
local int i;
for (i = 0; i < NumberOfObjects ; i++)
{
AxisObjects.AddItem(spawn(class'AxisObject'));
AxisObjects[i].SetLocation( AxisObjectLocation(SpawnPattern) + location);
`log(AxisObjects[i]);
`log(AxisObjects[i].location);
}
}
function vector AxisObjectLocation(eSpawnPattern Pattern)
{
local vector vec;
`log(Pattern);//We'll start using this in a bit.
vec.x = (FRand()*ObjectSpread - FRand()*ObjectSpread);
vec.y = (FRand()*ObjectSpread - FRand()*ObjectSpread);
vec.z = (FRand()*ObjectSpread - FRand()*ObjectSpread);
return vec;
}
defaultproperties
{
Begin Object Class=SpriteComponent Name=Sprite
Sprite=Texture2D'EditorResources.S_Actor'
HiddenGame=False
End Object
Components.Add(Sprite)
NumberOfObjects = 10
ObjectSpread = 64
SpawnPattern = Linear
}
So what are the major changes here? We’ve added two new var()’s the first is float for “object spread” and the next one is an interesting one caalled enum. Basically this is a sort of list where at least one of them is active. This is one of the few variables which has to be filled with a bit of data before it’s used. That’s why it’s structure looks the way it does.
If you look closely, it’s got a few different parts. The first is the var declaration of course. Then it’s setup as enum, which is followed by “eSpawnPattern” this can be called anything, but this is also now the type of enum we’re creating. To use the enum we need to ask the last part after the {}’s which is “SpawnPattern” so when we look at SpawnPattern it will return either Random or Linear which are the choices which were set inside of the {}’s. This is also how a pull down menu is created in an objects property editor.
What else did we change? If you look we changed our SetRandomLocation() function to a AxisObjectLocation(SpawnPattern) function. This way we can tell this what sort of pattern we’re asking for. This is much more flexible than what we had before.
Next we swapped out the FRand()*10 to FRand()*ObjectSpread, this will allow us to change the size of our pattern.
Just for convenience I also added a sprite in the defaultproperties just so we can see the actor that’s doing all the work. Then last I set up some basic defaults for our actor.
Lets add in a switch statement based on the Pattern!
function vector AxisObjectLocation(eSpawnPattern Pattern)
{
local vector vec;
switch( Pattern )
{
case Random:
vec.x = (FRand()*ObjectSpread - FRand()*ObjectSpread);
vec.y = (FRand()*ObjectSpread - FRand()*ObjectSpread);
vec.z = (FRand()*ObjectSpread - FRand()*ObjectSpread);
break;
case Linear:
vec.x = vec.x + ObjectSpread;
vec.y = vec.y + ObjectSpread;
vec.z = vec.z + ObjectSpread;
break;
default: break;
}
return vec;
}
Lets take a look at this version of the function. switch ( Pattern ) this means that we’re going to look at the “cases” inside of the {}’s that follow switch. if the case is Random, then do these operations then “break” or stop, or if the case is Linear do these operations then stop. If its none of them don’t do anything then stop. So run this and you might notice that if you choose linear you only get ONE axis object, oh no! Actually, they’re just on top on one another, that’s because each time we enter this function vec is set to 0,0,0 + ObjectSpread.
We need to remember what the previous value for vec was, so we’re going to need to move it outside of this function so it doesn’t get reinitialized each time the function is run. Lets look at the changes:
var() int NumberOfObjects;
var() float ObjectSpread;
var() enum eSpawnPattern
{
Random,
Linear
}SpawnPattern;
var vector vec; //Move the vector outside of the function
simulated function PostBeginPlay()
{
CreateObject();
}
...
function vector AxisObjectLocation(eSpawnPattern Pattern)
{
//Remove the variable from here
switch( Pattern )
{
case Random:
vec.x = (FRand()*ObjectSpread - FRand()*ObjectSpread);
vec.y = (FRand()*ObjectSpread - FRand()*ObjectSpread);
vec.z = (FRand()*ObjectSpread - FRand()*ObjectSpread);
break;
case Linear:
vec.x = vec.x + ObjectSpread;
vec.y = vec.y + ObjectSpread;
vec.z = vec.z + ObjectSpread;
break;
default: break;
}
return vec;
}
...
Now if you add those changes then you should be able to see more than one of the axis object.
PHEW!! that was a lot to learn! and heck, it was a lot to write as well. enum’s switch statements, and arrays oh my! let your brain rest, it’s been through a lot.
I think there a little mistake with the “< " sign.
Anyway keep the good job
Thanks for sharing knowledge