Custom Object tutorial/Creating an enemy
Now that you know how a SOC file works and what all the attributes mean, it's time to create your own enemy. We will use the example from chapter 2 as a basis.
Modifying the parameters
As a first step, change the parameters of State S_POSS_RUN1
in the following way:
State S_POSS_RUN1 SpriteName = PNTY SpriteFrame = A Duration = 80 Next = S_POSS_RUN1 Action = A_BunnyHop Var1 = 0 Var2 = 0
Save your file, run SRB2 with your SOC and start a new game in Greenflower Zone Act 1. You'll immediately recognize that all blue Crawlas now turn into little hopping balls when they are seeing you. Now let's take a look at how we did that: By changing Next
to S_POSS_RUN1
we made sure the state keeps looping back to itself. That means the Crawlas will stay in their SeeState the whole time. By changing Duration
we made sure the balls don't hop at insane speeds. The state is now 80 tics long, which is a bit more than two seconds. A_BunnyHop
makes the enemy jump, while Var1 defines the vertical jumping force. And finally, changing the SpriteName
changed the Object's look. Note that now all blue Crawlas in game behave this way.
Effects with multiple states
Okay, this effect might be somewhat funny, but it's rather useless and makes the Crawlas even more harmless than before. Let's change that with a more complicated behavior. Delete the old State S_POSS_RUN1
and paste this new code into your file:
State S_POSS_RUN1 SpriteName = PNTY SpriteFrame = A Duration = 80 Next = S_POSS_RUN2 Action = A_BunnyHop Var1 = 8 Var2 = 12 State S_POSS_RUN2 SpriteName = PNTY SpriteFrame = A Duration = 5 Next = S_POSS_RUN1 Action = A_FaceTarget Var1 = 0 Var2 = 0
Save, run SRB2 with your SOC and visit GFZ1 once again. Now the balls keep jumping and following you. This can actually get quite challenging. But how does it work?
Our Object's state sequence now consists of two states that keep looping. The first one is very similar to the last one we created, except this time Var2 defines an additional horizontal jumping force into the direction the Object is facing. After the jump has been performed, our next state makes sure that the Object faces towards the player's new position with the action A_FaceTarget
. Then the whole sequence repeats over and over once again. This is a good example how a pretty simple construction of actions can create something new and interesting.
Editing Object type blocks
But you should not be satisfied just yet. The Object still looks like a Crawla as long as it does not see you, its first jump is not aimed and its 3D size is far too big for the small ball sprite. We'll change this in our last step. Delete everything inside your SOC file and paste this new code:
State S_POSS_STND SpriteName = PNTY SpriteFrame = A Duration = 5 Next = S_POSS_STND Action = A_Look Var1 = 0 Var2 = 0 State S_POSS_RUN1 SpriteName = PNTY SpriteFrame = A Duration = 80 Next = S_POSS_RUN2 Action = A_BunnyHop Var1 = 8 Var2 = 12 State S_POSS_RUN2 SpriteName = PNTY SpriteFrame = A Duration = 5 Next = S_POSS_RUN1 Action = A_FaceTarget Var1 = 0 Var2 = 0 Object MT_BLUECRAWLA MapThingNum = 100 SpawnState = S_POSS_STND SpawnHealth = 1 SeeState = S_POSS_RUN2 SeeSound = sfx_None ReactionTime = 32 AttackSound = sfx_None PainState = S_NULL PainChance = 200 PainSound = sfx_None MeleeState = S_NULL MissileState = S_NULL DeathState = S_XPLD1 XDeathState = S_NULL DeathSound = sfx_pop Speed = 3 Radius = 4*FRACUNIT Height = 8*FRACUNIT DispOffset = 0 Mass = 100 Damage = 0 ActiveSound = sfx_None Flags = MF_ENEMY|MF_SPECIAL|MF_SHOOTABLE RaiseState = S_NULL
Hit save and test everything if you want to see the results. Note that everything we did was including the default state S_POSS_STND
and changing the highlighted values. This means that the sprites were also changed for the SpawnState, the size was adjusted and the SeeState was changed to S_POSS_RUN2
. This makes the enemy use A_FaceTarget
before making the first jump, so that it jumps in your direction. Congratulations, now you have created your first enemy!
Using freeslots
The first enemy we created replaced one of the default enemies (the blue Crawla). But what if we do not want to replace something but create an entirely new Thing that is meant to be placed onto a new custom map? In this case, we need to use freeslots. Let's assume we wanted to turn the enemy we created earlier into an independent Object type. In this case, every state and Object type definition we used has to be given a new name, and those names have to be declared in a freeslot block at the top of the SOC. Additionally, the MapThingNum
must be set to a new, unused value. The new code should look something like this:
Freeslot S_BOUNCY_STND S_BOUNCY_RUN1 S_BOUNCY_RUN2 MT_BOUNCYBALL State S_BOUNCY_STND SpriteName = PNTY SpriteFrame = A Duration = 5 Next = S_BOUNCY_STND Action = A_Look Var1 = 0 Var2 = 0 State S_BOUNCY_RUN1 SpriteName = PNTY SpriteFrame = A Duration = 80 Next = S_BOUNCY_RUN2 Action = A_BunnyHop Var1 = 8 Var2 = 12 State S_BOUNCY_RUN2 SpriteName = PNTY SpriteFrame = A Duration = 5 Next = S_BOUNCY_RUN1 Action = A_FaceTarget Var1 = 0 Var2 = 0 Object MT_BOUNCYBALL MapThingNum = 1337 SpawnState = S_BOUNCY_STND SpawnHealth = 1 SeeState = S_BOUNCY_RUN2 SeeSound = sfx_None ReactionTime = 32 AttackSound = sfx_None PainState = S_NULL PainChance = 200 PainSound = sfx_None MeleeState = S_NULL MissileState = S_NULL DeathState = S_XPLD1 XDeathState = S_NULL DeathSound = sfx_pop Speed = 3 Radius = 4*FRACUNIT Height = 8*FRACUNIT DispOffset = 0 Mass = 100 Damage = 0 ActiveSound = sfx_None Flags = MF_ENEMY|MF_SPECIAL|MF_SHOOTABLE RaiseState = S_NULL
Now no default data is overwritten and the Object type can be used independently. You can also see that the freeslot block is very simple: It's nothing more than a header called Freeslot
, following by the names of the custom states and Object types we defined in each following line. The names you use for your states and Object types are up to you, but make sure they are not too short and generic, to reduce chances that somebody used the same names in a different SOC.
Implementation
Now the Object type can be implemented into your map. Paste the code into a SOC lump in your WAD or PK3 file. As mentioned in the first chapter, SOC lumps in a PK3 file must be put in the SOC/
folder. SOC lumps in a WAD file must have a name that is either MAINCFG
or OBJCTCFG
or begins with SOC_
. To put an Object into your map, place a random Thing where you want your new Object to be and change its type to the new MapThingNum
. In our case, that's 1337. If you are using Zone Builder and have the WAD or PK3 with the SOC script loaded as a resource file, the new Thing type will appear in the Thing type list in a new "Custom Things" category under the name MT_BOUNCYBALL
. However, because your custom Thing type isn't listed in the Zone Builder configuration file, it will display a red question mark graphic. This isn't a problem, however. If you run the game, the Object will be there and attack you like it's supposed to.
If you want, you can use special comments in the SOC file to specify a name and sprite to display for your custom Thing type in Zone Builder, as well as a category to list it under: Directly above the start of the Object type definition, add a line with #$Name <name>
, where <name>
is the name you want to display. To set the sprite, add another line with #$Sprite <sprite>
, where <sprite>
is the name of the sprite you want to display. The sprite should be located in srb2.srb
or one of the currently loaded files. To make the Thing type appear in a different category than "Custom Things", add a line with #$Category <category>
, where <category>
is the name of the category in which it should be displayed. This can be either an existing category or a new one. In our example, the Object type configuration could now look like this:
#$Name Bouncy Ball #$Sprite PNTYA1 #$Category Enemies Object MT_BOUNCYBALL MapThingNum = 1337 SpawnState = S_BOUNCY_STND SpawnHealth = 1 SeeState = S_BOUNCY_RUN2 SeeSound = sfx_None ReactionTime = 32 AttackSound = sfx_None PainState = S_NULL PainChance = 200 PainSound = sfx_None MeleeState = S_NULL MissileState = S_NULL DeathState = S_XPLD1 XDeathState = S_NULL DeathSound = sfx_pop Speed = 3 Radius = 4*FRACUNIT Height = 8*FRACUNIT DispOffset = 0 Mass = 100 Damage = 0 ActiveSound = sfx_None Flags = MF_ENEMY|MF_SPECIAL|MF_SHOOTABLE RaiseState = S_NULL