Announcement

Collapse
No announcement yet.

Spawning Prefabs in UnrealScript

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    Spawning Prefabs in UnrealScript

    Please note: This is a nearly done work in progress - its stable and can be used, but there could be some bugs and / or inefficient mistakes around the place. Feel free to command and ask questions, and post if you find or have any issues.

    Also note that I'm still learning to program (but really who isn't?) - I've come far, but do have a lot lot more to go in learning UnrealScript, so bear with me should I say that's wrong or inaccurate. I welcome feedback and would love to have my work corrected, should a more efficient / better-standard way of doing something exists. I've got to learn somehow .

    Also note I'll likely post a link to a video showing off this feature once I've got the rest of it working (Prefab spawning is fine - its issues relating to doors right now).

    Now on with the show...

    One of my projects was making a game similar to Rouge Legacy - A great game which features in it a randomly generated castle layout. While this tutorial is not directly about that, I will go into the principle behind the creation of the layout:

    The idea is that you have a grid system of x by y squares. Each room is a quadrilateral with four right angles - that is to say, a rectangle. Regardless of the interior shape, the exterior is always a rectangle. Hence its very simple (in theory) to work out if a room can fit on the grid location picked out, because while a room could feature curves on the interior, its still always a rectangle. You do this EVEN if the space given wont be used AT ALL. Hence you end up with some rooms have one or two squares that have no content (such as the corner rooms in Rouge Legacy).

    However before each room can be spawned, they need to be made. The answer to this is Prefabs.

    Prefabs are - in a nutshell - a collection of archetypes with data such as relative location and rotation. An important thing you must know and keep in mind with Prefabs: NEVER SPAWN THEM. Which is ironic given this tutorial.

    Now, from what I know about spawning prefabs, you can do so in two ways:
    • Spawning the prefab directly, which doesn't really work well.
    • Spawning the actors that make up the prefab, which is what this tutorial will show you how to do


    If you have read the UDN Using Prefabs page, which you should anyway, you'll see it says "once a prefab is placed in a level, it should be immediately converted back to straightforward actors by right-clicking and choosing Convert PrefabInstance To Normal Actors." - That's basically what this code will do: Take the actors in the prefab, and spawn them as normal actors.

    Also a very quick note on archetypes: In a very very dumbed down nut shell they are version of the default properties of a class - Hence you spawn them by spawning the class they base off and then apply the archetype values to as its DP (Default Properties). A save game system does a VERY similar thing : Effectively they save a archetype of an entire game to disk.

    Now on to the code:

    First you need something that will handle the spawning. In my case I made a special actor called "roomSpawnMarker" - Its sole purpose is to spawn the prefab it's given and then do nothing ( I plan to have it destroy itself if I can't find a use for it ). You want to have a function in there, which I'll call spawnRoom, and some local variables like so:

    Code:
    class roomSpawnMarker extends actor
        placeable;
    
    /** Stores all the possible walls for this room */
    var array<staticmesh> wallList;
    
    /** Prefab to spawn */
    var() Prefab prefabToSpawn;
    
    
    event PostBeginPlay()
    {
        super.PostBeginPlay();
       spawnRoom( prefabToSpawn ); 
    }
    
    
    function spawnRoom( Prefab prefabToCreate)
    {
        local int i; //Counter
        local actor archetype, tempA; //The archetype itself and the tempA which is spawned
        local RoomWalls tempWall;
    }
    The postBeginPlay event and the prefabToSpawn variable are both there so you can simply place this actor in the editor, assign it a prefab and hit Play In Editor to have it spawn the prefab.

    Now note that RoomWalls is a special class I made, and while its not needed for spawning prefabs, I'm going show you an example of how you can change prefab-related actors as they spawn, for doing some randomness. As you might have guessed, roomWalls represent a staticmeshactor that A. Can be Spawned and B. Can have its mesh changed. The meshes themselves are kept within the array wallList, and all you need for it is an actor that extends staticmeshactor and has following:

    Code:
    class roomWalls extends StaticMeshActor
        placeable;
    
    
    /** Has this wall got a special mesh assigned to it that we don't want to change? */
    var() bool bFixedMesh;
    
    
    DefaultProperties
    {
        bNoDelete         =    false    //This actor can be deleted, and hence can also be spawned
        bStatic          =    false    //This actor can change and is dynamic
        bMovable         =    true  //This actor can be moved around with commands such as setLocation
        bEdShouldSnap  =     true     //Snap to grid in the editor - if this is false, death to thy
    }
    If you're interested in doing your own stuff, that's ok - Just remove anything related to roomWall as it shows up (I'm make it clear if its related to roomWall) and there is "roomWallless" copy of the code at the end of this tutorial. Moving on...

    Now within each prefab there is an array called "PrefabArchetypes" - its this that holds all the archetypes for all the actors within the prefab, and its this that we are using to spawn our prefab:

    Code:
    class roomSpawnMarker extends actor
        placeable;
    
    /** Stores all the possible walls for this room */
    var array<staticmesh> wallList;
    
    /** Prefab to spawn */
    var() Prefab prefabToSpawn;
    
    
    event PostBeginPlay()
    {
        super.PostBeginPlay();
       spawnRoom( prefabToSpawn ); 
    }
    
    
    function spawnRoom( Prefab prefabToCreate)
    {
        local int i; //Counter
        local actor archetype, tempA; //The archetype itself and the tempA which is spawned
        local RoomWalls tempWall;
    
    
        if( prefabToCreate == none )
            return; //For safety, we will return early if we've not been given a prefab. If we don't, we will throw some errors
    
    
        for ( i = 0; i < prefabToCreate.PrefabArchetypes.Length; i++ )
        {
            archetype = actor( prefabToCreate.PrefabArchetypes[i] ); //First typecast the current archetype at location i in the array to an actor... 
    
    
            `log( "Spawning: "$ archetype );
    
    
            // The location of the object your spawning must be the location of this object + the location of the archetype, which is an offset
            tempA = spawn( archetype.class, self ,, location + archetype.location , archetype.Rotation , archetype);
    
    
            //Check to see if what was just spawned is a roomWall, because we want to randomly change the mesh if it is
            if( RoomWalls( tempA ) != none )
            {
                tempWall = RoomWalls( tempA ); //Typecast from an actor var to a roomWalls var, so we can call roomWalls based functions
                if( !tempWall.bFixedMesh )
                {
                    tempWall.StaticMeshComponent.SetStaticMesh( wallList[ RandRange( 0,  wallList.Length ) ] ); //Assign this wall a random wall mesh
                }
            }
        }
    }
    Now to explain a few important things:
    • Prefab archetype location is RELATIVE to the prefabs pivot point, and therefore an OFFSET - its a local not a world location. Thus you must add it to the location of where the prefab is being spawned, which is in this case the roomSpawnMarker's location
    • When using the spawn function to spawn the archetype, there is a special parameter for applying the archetype to the actor's class
    • When using the spawn function to spawn the archetype, the archetype IS NOT the class your spawning - you need to get the class that archetype is based off, hence "archetype.class"
    • roomWall related: bFixedMesh allows you to have "special" meshes that should not be changed


    Also note there is a difference between tempA and archetype - If you forget this and pass archetype as a reference to something, for example:

    Code:
    tempA.attackTarget = archetype; //Note this is a strange example because this is setting the actors attact target to itself, and hence would cause it to commit suicide - Lucky for the actor this is just an example
    You will get an error - You will also get an error when you try and close the PIE reading something like "GC detected illegal reference to PIE object from content" and relates to " NON-PIE object" - This error I've had before, and it can be a pain. But you must remember: All references kept by an actor in the prefab, once spawned, are destroyed or invalid - you will need to have a system in place for fixing / updating them with their new "spawned" self.

    A way to think of it is its a bit like Stasis that you see in sci-fi programs and games. Until your woken from Stasis, you cant do anything nor can you be referenced to, and in the same way you can't hold any references to objects because when you wake up, they could have all been changed. Hence you need to remake any references you had and need.

    And now here is the complete code: Once with the roomWalls, and the other without. Note that in the DP (Default Properties) there is a sprite added. That's so you can find the actor in the editor.

    Code:
    class roomSpawnMarker extends actor
        placeable;
    
    /** Stores all the possible walls for this room */
    var array<staticmesh> wallList;
    
    /** Prefab to spawn */
    var() Prefab prefabToSpawn;
    
    
    event PostBeginPlay()
    {
        super.PostBeginPlay();
       spawnRoom( prefabToSpawn ); 
    }
    
    function spawnRoom( Prefab prefabToCreate)
    {
        local int i; //Counter
        local actor archetype, tempA; //The archetype itself and the tempA which is spawned
        local RoomWalls tempWall;
    
    
        if( prefabToCreate == none )
            return; //For safety, we will return early if we've not been given a prefab. If we don't, we will throw some errors
    
    
    
    
        for ( i = 0; i < prefabToCreate.PrefabArchetypes.Length; i++ )
        {
            archetype = actor( prefabToCreate.PrefabArchetypes[i] ); //First typecast the current archetype at location i in the array to an actor... 
    
    
            `log( "Spawning: "$ archetype );
    
    
            // The location of the object your spawning must be the location of this object + the location of the archetype, which is an offset
            tempA = spawn( archetype.class, self ,, location + archetype.location , archetype.Rotation , archetype);
    
    
            //Check to see if what was just spawned is a roomWall, because we want to randomly change the mesh if it is
            if( RoomWalls( tempA ) != none )
            {
                tempWall = RoomWalls( tempA ); //Typecast from an actor var to a roomWalls var, so we can call roomWalls based functions
                if( !tempWall.bFixedMesh )
                {
                    tempWall.StaticMeshComponent.SetStaticMesh( wallList[ RandRange( 0,  wallList.Length ) ] ); //Assign this wall a random wall mesh
                }
            }
        }
    }
    
    
    DefaultProperties
    {
        Begin Object Class=SpriteComponent Name=Sprite
            Sprite=Texture2D'EditorResources.S_Trigger'
            AlwaysLoadOnClient=False
            AlwaysLoadOnServer=False
            SpriteCategoryName="Triggers"
            HiddenGame=true             //This is only to make the actor visible in the editor, and hence we dont want to see it in game
        End Object
        Components.Add( Sprite )     //We want a sprite so we can see, find and use this actor in the editor
    
    
        wallList[0]=StaticMesh'Chance_PlaceHolderModels.chancePH_BackWall_1'
        wallList[1]=StaticMesh'Chance_PlaceHolderModels.chancePH_BackWall_2'
    
    
        bNoDelete         =    false    //This actor can be deleted, and hence can also be spawned
        bStatic          =    false    //This actor can change and is dynamic
        bEdShouldSnap  =     true     //Snap to grid in the editor - if this is false, death to thy
    }

    Note that wallList has its meshes assigned in the DP (Default Properties) and hence you can add as many as you want there.

    Code:
    class roomSpawnMarker extends actor
        placeable;
    
    /** Prefab to spawn */
    var() Prefab prefabToSpawn;
    
    
    event PostBeginPlay()
    {
        super.PostBeginPlay();
       spawnRoom( prefabToSpawn ); 
    }
    
    
    function spawnRoom( Prefab prefabToCreate)
    {
        local int i; //Counter
    
        local actor archetype, tempA; //The archetype itself and the tempA which is spawned
    
    
        if( prefabToCreate == none )
            return; //For safety, we will return early if we've not been given a prefab. If we don't, we will throw some errors
    
    
    
        for ( i = 0; i < prefabToCreate.PrefabArchetypes.Length; i++ )
        {
            archetype = actor( prefabToCreate.PrefabArchetypes[i] ); //First typecast the current archetype at location i in the array to an actor... 
    
    
            `log( "Spawning: "$ archetype );
    
    
            // The location of the object your spawning must be the location of this object + the location of the archetype, which is an offset
            tempA = spawn( archetype.class, self ,, location + archetype.location , archetype.Rotation , archetype);
        }
    }
    
    
    DefaultProperties
    {
        Begin Object Class=SpriteComponent Name=Sprite
            Sprite=Texture2D'EditorResources.S_Trigger'
            AlwaysLoadOnClient=False
            AlwaysLoadOnServer=False
            SpriteCategoryName="Triggers"
            HiddenGame=true             //This is only to make the actor visible in the editor, and hence we dont want to see it in game
        End Object
        Components.Add( Sprite )     //We want a sprite so we can see, find and use this actor in the editor
    
    
        bNoDelete         =    false    //This actor can be deleted, and hence can also be spawned
        bStatic          =    false    //This actor can change and is dynamic
        bEdShouldSnap  =     true     //Snap to grid in the editor - if this is false, death to thy
    }
    Well thats that - I hope this is of use to at least someone

    #2
    Nice work

    Comment


      #3
      I posted a similar code snipped quite some time ago, but yours explains the 'whys' better
      the problem is that you spawn the actor using the archetype sub-actor as a template which doesn't work universally (ie. particles, decals, lights won't look at all like in the prefab), which is why in my case I did a manual typecast to every class I intend to spawn and then make it copy the relevant properties differently depending on the actor class

      I also used them to randomly generate a level too

      Comment


        #4
        Yes your name is familiar, I may have used some of your work That was the main reason I posted this: When I was looking around I found one slightly-solid snippet, but nothing fixed or up-to-date. So all I really got from the posts I found was the logic, which is all there is to it really. And once I had gotten my system working, I felt I should do my part and share it around - finally give something back to the community, after all its given me.

        Speaking of lights, that is the major flaw in this system: UDK's tolerance of dynamic lighting is shocking at times, and so this system really needs a custom lighting setup. It works (or will once I've got it running) much better with UE4, with its support for dynamic lights being just right for this sort of system - Once I've converted this to UE4, either with C++ or Blueprints (I've not really decided which I'll do, among other things) I'll make sure to post this tutorial with its UE4 version

        Comment

        Working...
        X