Custom Object tutorial/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.