Custom Object tutorial/Advanced techniques

From SRB2 Wiki
Jump to navigation Jump to search
  Custom Object tutorial [view]

Chapter 1: Basic knowledgeChapter 2: File structureChapter 3: Syntax issues and conventionsChapter 4: Creating an enemyChapter 5: Sounds and spritesChapter 6: Advanced techniques

To do
Add a section on FF_ANIMATE.

This is the last chapter of the tutorial. Here you will learn two slightly advanced concepts that you will often come in contact with when making SOCs. This chapter also gives you some closing advice regarding SOC making.

Multi-health enemies

A_SetObjectFlags and A_SetObjectFlags2 are very powerful actions, because they are capable of changing an Object's attributes and status. However, they are somewhat complicated, especially the latter one. As an example of what these actions can do, we will create a multi-health enemy.

Some of SRB2's enemies have multiple hitpoints that are hardcoded into the game. However, we cannot make use of hardcoded features through SOCs and simple enemies don't have something like this. "What's the big deal?", you might ask, "Objects have the SpawnHealth variable we can raise, so why bother?" Well, if you recall the chapter where the attributes were explained, it might come to your mind that a hostile contact makes a Object immediately go into its PainState and lower its SpawnHealth by one for each tic the contact lasts. Hostile contacts usually last much longer than that.

Of course, you could give the enemy thousands of life points and generate a similar effect to what you originally wanted to do. But then you'd have the problem that the Object can get stuck in its PainState if the player keeps spinning at one point and the Object runs into him. A better solution is to make the enemy turn invincible for a short time when it's hit, so that hitting it once will lower its SpawnHealth by only one. Luckily, where an Object can be attacked or not is defined by the flag MF_SHOOTABLE. If this flag is removed each time the PainState is called and then added back a short moment later, our problem is solved.

Let's cover how A_SetObjectFlags works. Var1 should contain the flags you want to affect, separated with the "|" character as usual so that they are added together with a bitwise OR. What is done with these flags is determined by the value of Var2:

  • If Var2 = 0, then the flags of the Object will be completely replaced by the flags in Var1.
  • If Var2 = 1, then the flags in Var1 will be removed from the Object.
  • If Var2 = 2, then the flags in Var1 will be added to the Object.

In our case, we want to remove the flag MF_SHOOTABLE and re-add it after a short period of time. Here is one approach to solve the problem:

# Declare pain states.

Freeslot
S_POSS_PAIN1
S_POSS_PAIN2
S_POSS_PAIN3
S_POSS_PAIN4

# Change health and add pain animation.

Object MT_BLUECRAWLA
MapThingNum = 100
SpawnState = S_POSS_STND
SpawnHealth = 3
SeeState = S_POSS_RUN1
SeeSound = sfx_None
ReactionTime = 32
AttackSound = sfx_None
PainState = S_POSS_PAIN1
PainChance = 200
PainSound = sfx_None
MeleeState = S_NULL
MissileState = S_NULL
DeathState = S_XPLD1
XDeathState = S_NULL
DeathSound = sfx_pop
Speed = 3
Radius = 24*FRACUNIT
Height = 32*FRACUNIT
DispOffset = 0
Mass = 100
Damage = 0
ActiveSound = sfx_None
Flags = MF_ENEMY|MF_SPECIAL|MF_SHOOTABLE
RaiseState = S_NULL

# Start of our pain cycle. Make the enemy invulnerable.

State S_POSS_PAIN1
SpriteName = POSS
SpriteFrame = F
Duration = 1
Next = S_POSS_PAIN2
Action = A_SetObjectFlags
Var1 = MF_SHOOTABLE
Var2 = 1

# Play hit sound.

State S_POSS_PAIN2
SpriteName = POSS
SpriteFrame = F
Duration = 1
Next = S_POSS_PAIN3
Action = A_PlaySound
Var1 = sfx_dmpain
Var2 = 0

# Hop away from the player.

State S_POSS_PAIN3
SpriteName = POSS
SpriteFrame = F
Duration = 17
Next = S_POSS_PAIN4
Action = A_BunnyHop
Var1 = 0
Var2 = -16

# Make the enemy vulnerable again.

State S_POSS_PAIN4
SpriteName = POSS
SpriteFrame = F
Duration = 1
NEXT = S_POSS_RUN1
Action = A_SetObjectFlags
Var1 = MF_SHOOTABLE
Var2 = 2

# End of our pain cycle 

The Object has to go through a set of states after the PainState has been called because multiple things need to happen. At the end and the beginning, the flags are changed like we planned it. The two states in the middle make sure that a sound effect is played and the Object will be knocked back (it hops in the opposite direction). Of course, you could put much more into the pain cycle if you want to. The Object can jump around or perform some other kind of special attack after it has been hit or spawn another Object that will help it out. Your ideas can be applied anywhere.

Note that some actions (especially those using complex thinkers) modify the flags. Using A_SetObjectFlags if your Object makes use of A_SkullAttack, for example, will lead to issues.

A_SetObjectFlags2 works the same way as A_SetObjectFlags, except this time special flags are modified that describe the Object's current status. Many of these are rather specific and difficult to understand, but some can be very useful.

Upper bits and lower bits

You already know that the two variables Var1 and Var2 can modify the way certain actions operate. However, some of the more complex actions make use of more than two parameters. Instead of implementing more than two variables, the variables, which are 32-bit integers, are divided into their upper 16 bits and their lower 16 bits:

  • Lower bits: This value equals the assigned value as is, but the value cannot exceed 65535 (because this is the highest number that can be represented with 16 bits).
  • Upper bits: This value must be shifted to the left by 16 bits. This is done with the left shift operation, e.g. value<<16. Again, the value cannot exceed 65535.

Let's clarify this with an example. A_CapeChase is used to let one Object immediately jump to the position of its target (or, if constantly executed, to attach an Object to its target). Var1's lower bit value defines if the Object's target (Var1 = 0) or tracer (Var1 = 1) will be chased. Var2's lower bit value defines the horizontal offset: left if Var2 is negative; right if Var2 is positive. You could execute the action by only making use of the lower bit values:

State S_POSS_RUN1
SpriteName = POSS
SpriteFrame = A
Duration = 1
Next = S_POSS_RUN1
Action = A_CapeChase
Var1 = 0
Var2 = -128

This, for example, will force all blue Crawlas to keep hovering next to you if they see you. The value of Var2 is negative, so they will float to the left. There are however more variables that can be set:

Var1's upper bits value defines the vertical offset of the chasing Object: a positive value will shift it up and a negative value will shift it down (note that the vertical offset has a different length unit than the horizontal offset). Var2's upper bits value determines the Objects forward/backward offset (positive = forward; negative = backward). Now let's say we wanted the Crawlas to keep hovering on the left side but raise them 64 units into the air and shift them 128 units forward. We would have to add the uppercase values to the lowercase ones. Since SOCs support mathematical operations, we don't actually have to calculate the results, we only have to shift the upper bit value to the left by 16 and add it to the lower bit value:

State S_POSS_RUN1
SpriteName = POSS
SpriteFrame = A
Duration = 1
Next = S_POSS_RUN1
Action = A_CapeChase
Var1 = 64<<16
Var2 = -128+128<<16

Don't leave spaces in your mathematical operations, the game will stop reading everything after the first space in the line.

Closing advice

  • Don't create SOCs you plan on releasing if you have no certain idea in mind. An original idea in mind is the first step to create a good SOC. You might get some ideas by playing the game and thinking about improvements or by browsing through the actions.
  • Mess around with every little thing you can mess around with and try out everything you can. Trial and error is the best way to learn and understand SOCcing mechanics.
  • Look inside SOC scripts of others and try to understand what they did.
  • Even though it is possible to start from scratch each time you make another SOC script, it is more advisable to use some of SRB2's default simple enemies as basic models. Copy them and edit their parameters step by step. A Crawla is a perfect start for a ground based enemy while you might consider taking a Buzz for a flying one.
  Custom Object tutorial [view]

Chapter 1: Basic knowledgeChapter 2: File structureChapter 3: Syntax issues and conventionsChapter 4: Creating an enemyChapter 5: Sounds and spritesChapter 6: Advanced techniques