Coding
|
Something you do once and then easily forget. At the top of the
first CNS file, you'll find parameters that define your character :
life, attack, defense, power, width, height, walk / run / jump
velocities, position of the head and of the middle that are useful when
someone tries to grab you for them to display you at the right
position... It wouldn't hurt to remember checking them up once in a
while.
|
When coding a character, there will be SCTRLs you will be using
significantly more often than the others. It's nice to take a look at
the complete list to see if there isn't something you can make use of,
but here is a quick sum-up of the most commmon ones. If this is your
first time, a simple look at these will let you get used to them.
- Type = ChangeState : sends your character to
another state. There's only one thing to note (beside the obvious), as
soon as a ChangeState is triggered, Mugen stops reading the state you
were just in, so make sure your ChangeStates are at the end of the
state.
[State 1000, end]
type = ChangeState
trigger1 = !AnimTime ; when AnimTime reaches 0, i.e. at the end of the animation
value = 0
- Type = PosAdd and Type = PosSet : what makes
your character shift position by a fixed amount, either horizontally, or
vertically, or both. It's better to use PosAdd in a punctual fashion,
rather than over a range of time, as you will want to change the
velocity for that. Beside PosAdd, there's also PosSet, but this sets the
position relatively to the center of the screen. PosSet can be useful
if you set the position with some expression that makes use of the
position of another element - such as helpers or parents.
[State 1000, pos]
Type = PosAdd
trigger1 = !AnimElemNo(2)
x = 8 ; moves 8 pixels forward
y = -2 ; also moves 2 pixels upward
[State 1000, pos]
Type = PosSet
trigger1 = !Time
y = 0 ; instantly places you on the ground no matter how high you are in the air
- Type = VelSet and Type = VelAdd : to change
the velocity. You'll be using it a lot, the only thing you need to keep
in mind is the Physics and StateType parameters, since those affect the
evolution of your velocity, as described in earlier sections. The
important thing to always keep in mind is that Mugen does what you tell
it to do, and that your character moves around because he is coded that
way ; if weird things happen (this can be frequent if you are not quite
used to it yet), try to keep track of your velocity to always know what
it is (we'll have a word on the debug mode for that in a moment) and
know what can influence it. Physics are probably going to be your usual
suspect.
[State 1100, VSet]
type = Velset
trigger1 = Anim = 1100 || Anim = 1102
trigger1 = AnimElem = 4
x = 1.5 ; starts moving you forward at the rate of 1.5 pixels per tick until further notice
y = -8 ; starts moving you upward at the rate of 8 pixels per tick
[State 1200, Velocity2]
type = veladd
trigger1 = Anim = 1200
trigger1 = AnimElem = 2,>= 0
trigger2 = Anim = 640
y = .29 ; starts decreasing your Y velocity by the noted amount every tick
With velocities, something else also easy to overlook and thus
important to note is that the SCTRL keeps being applied as long as the
triggers are true. Hence, if you see an odd behavior in your velocities,
make sure you are not constantly triggering a VelSet, VelAdd or VelMul
when it shouldn't.
- Type = HitDef : what allows you to hit the opponent.
First, for a HitDef to work, you need to have a MoveType of A ; if
you don't have it, the hit will not occur (this may be used as a trick
to activate or deactivate an attack based on various kinds of triggers).
This is something easy to overlook when you aren't quite used to Mugen
coding, so look there if a hit misses for some reason. Second, you also
need a Clsn1 to be able to hit.
After all that, there are a lot of parameters that can be specified
for a HitDef - but not all of them are required, or even important.
Only a few are absolutely needed and require attention. Among those
parameters, you specify anything related to the hit : the statetype the
target needs to be in for you to hit them (something important to look
at if a hit misses for no apparent reason), how they can be guarded, the
type of hit that will determine what standard gethit animation the
target will play, how far they will be pushed back...
Last thing about HitDefs is that you trigger them at one point, and
then they stay active during the whole statedef, or until you trigger
another HitDef, or until it hits - you can specify how many times it can
hit, but once one hitdef runs out of hits, you'll have to trigger
another one for more. You can only use one HitDef at a time (but you can
use a Helper with its own HitDef if needed), and you don't need to keep
triggering the HitDef for the whole duration of the hit : just trigger
it once at the start of your attack (of the state, even) and let it be.
If you trigger another HitDef, it will replace any active HitDef.
[State 200, 1]
type = HitDef
trigger1 = 1
attr = S, NA ; a standing, normal attack (here, a light punch)
damage = ceil(FVar(13) * 21),floor(21*8/100) ; a formula to change the damage according to specific parameters
animtype = Light ; whoever gets hit will display their "light" and...
ground.type = Low ; ... "low" gethit animation (you need to know both height and depth of the attack to know the animation)
air.animtype = Back
guardflag = MA ; can be guarded on the ground and in the air
hitflag = MAF ; can hit someone on the ground, in the air, or falling after getting hit
getpower = 40,20
givepower = 20,20
pausetime = 4,5
sparkno = S(7010+random%8) ; some randomizer to vary the hitspark
guard.sparkno = S7000
sparkxy = -5, -85
hitsound = S5,0
guardsound = S6,0
ground.slidetime = 10
ground.hittime = 12
air.hittime = 12
ground.velocity = -6 ; whoever gets hit gets a backward velocity of 6 pixels per tick, then friction does the rest
guard.velocity = -5.5
air.velocity = -4, -5.25
ground.cornerpush.veloff = -8.8
persistent = 0 There are a number of optional parameters for
the HitDef, not all are needed, but it doesn't hurt to make sure you're
not forgetting one.
- Type = Explod : the one controller to display a
graphic effect of any sort beside the character. The explods are visual
effects that are just there and do nothing, they cannot interact with
the other objects on the stage. However, there are a number of
parameters and other SCTRLs that can be used to change the behavior of
the explod - its size, its movements...
[State 105, Dust1]
type = Explod
trigger1 = Anim = 370
trigger1 = AnimElem = 5
anim = 7100
sprpriority = 5
Ownpal = 1
scale = .5,.5
ID = 7500 ; will serve for the upcoming ModifyExplod
[State 7500, shrink]
type = ModifyExplod
trigger1 = Var(1) = 1 ; the trigger can be more important than usual if you want to do one special something
ID = 7500 ; to know what explod you want to modify
scale = 1.5-(.033*time), 1.5-(.033*time)
Notice how, if the ModifyExplod is triggered every tick for a
certain duration, the value of "time" increases, meaning that the values
given for the "scale" parameter changes as time passes : basically,
this here is making the explod shrinking with time.
- Type = PlaySnd : also a very common SCTRL.
Plays a sound. Different channels can be used ; if one sound uses the
same channel as an already playing sound (of your own character), the
first sound will be interrupted by the second.
[State 105, SFX]
type = PlaySnd
trigger1 = Anim = 370
trigger1 = AnimElem = 5
value = 40,0
channel = 2
- Type = Helper : a quite important SCTRL. As of
the moment of creation of the helper, just give it a statedef number to
start at and maybe an ID.
[State 1000, 1]
type = Helper
trigger1 = AnimElem = 4
ID = 1010 ; a unique identifier for the helper, so the parent can look at it later as longas it's here
name = "KoOhken" ; a name for the helper, just to look good and feel pretty
pos = 65,-75
Stateno = 1010 ; the helper starts with the code at that state and then reads it just as the character would
ownpal = 1
- Type = TargetState : used for throws - in a broad sense, as long as you want to be able to control the animation and movements of the target.
[State -2, Hou'ooLv2Cancel]
type = TargetState
triggerall = NumTarget(2000) ; check if you do have a target
trigger1 = Anim != 2002 ; other triggers, skipped for this example
value = 2054
Puts the target in the indicated statedef from your own file. Do
note that you actually need a Target for it to work. You have a target
when you hit and as long as the victim stays in a MoveType = H state.
[State 916, HitGround2]
type = SelfState
trigger1 = Pos Y >= 0 && Vel Y > 0
value = 5100
An example of a SelfState, to put at the end of your custom Gethit
state where the victim is put. Here, the victim is sent to the state
where he lands on the ground after falling from getting hit. Triggered
when he actually reaches the ground.
|
As with state controllers, there are a number of triggers you will be using much more often than others.
- Trigger1 = Command = "[name]" : triggers when
you have pressed a button input sequence that has a name defined in the
CMD file. You will often be using that trigger in the CMD file, but you
may easily want to trigger some changestate or variable set directly in a
state in your CNS file.
Trigger1 = Command = "SmashKFUpper" ; as defined in the first part of the CMD file
- Trigger1 = StateType = [state] : an important
trigger for changestates in the CMD file, rarely useful in the CNS file.
When you want to perform a standing light punch, you don't want to
trigger it if you are in the air. Use StateType for a trigger. One thing
to note, for standing and crouching attacks, the trigger commonly used
is not statetype = C or = S, but statetype != A ; you simply
differentiate crouching and standing attacks by checking if you are
holding the direction "down" or not.
Trigger1 = StateType != A ; you must not be in the air
- Trigger1 = StateNo = [state number] : a very
common trigger for chains and combos. If you want to chain a crouch
light punch into a stand light punch, you will simply go to the
changestate to the second attack and allow it to trigger while you are
in stateno = [crouch light punch state].
Trigger1 = StateNo = [1000,1500] ; the number of the state you are currently in is between 1000 and 1500
- Trigger1 = Anim = [number], trigger1 = AnimElemNo([tick
offset]) = [animation element number], trigger1 = AnimElemTime([anim
number]) = [animation element number] : an extremely common
trigger in general. All sorts of effects and movements need to be
triggered at a specific point of the animation - AnimElemNo is the
trigger you'll want to use most of the time. [tick offset] is a number
of tick, [animation element number] is the number of the frame in that
animation. If the animation is made of 5 frames, the first is element 0,
the second is element 1, and so on. If you need a more precise timing,
you can use AnimeElemTime([element number]) = ([number of ticks]), where
you can trigger your controller a specific number of ticks after the
beginning of the specified animation element.
Trigger1 = Anim = 500 ; just as it says on the tin
Trigger2 = AnimElemNo(0) < 5 ; currently displaying the fifth element of the current animation, however many ticks that frame lasts
Trigger3 = AnimElemTime(5) = 3 ; currently three
ticks after the start of the fifth element of the current animation,
regardless of how many ticks the element lasts
Trigger4 = AnimElem = 2, >= 0 ; another way of saying "0 or more ticks after the start of the second element of the current animation"
- Trigger1 = P2Dist X = [0,number], trigger1 = P2BodyDist X = [0, number]
: useful for grabs which you don't want to trigger if the opponent is
too far away. P2Dist X gives the distance between the axes of both
characters, and P2BodyDist X is the distance between the front (as
defined by the Width constant) of both players. The same triggers exist
with P2Dist Y and P2BodyDist Y. This may also be useful for AI coding,
about which we will talk in a moment.
Trigger1 = P2BodyDist X = [0, 15] ; a typical distance range for a grab to be allowed
|
Activating the debug mode gives you plenty of information on what
really is happening on the screen at this exact instant. To turn on the
debug mode, hit ctrl+d (assuming you allowed it in the mugen.cfg file,
it is enabled by default). Hitting ctrl+c will display the collision
boxes of both players.
For the debug text, you can see on the bottom left corner one of the
lines telling you what stateno you currently are in (this is useful if
you think you are in a given stateno but actually are in another, or if
you are stuck and need to know where...), what animation you are in,
what element of the animation you are in ; use that in conjunction with
the pause key and frame advance key (the keys on your keyboard next to
the F1~12 keys) to keep track of exactly where you are in your code when
things happen on the screen. You can use that to fix your triggers and
fix which states and animations are used. When a helper is on screen,
you can hit ctrl+d again until the information for the helper are
displayed to check the same things ; you can also see the state of the
other character.

State number, animation (action) number, animation element, number
of ticks into the animation are displayed in the bottom left corner. The
name of the character it talks about is also displayed, as you can
switch between characters, even see the data of helpers. Note that if
you are currently reading your opponent's file (by being put in a custom
gethit state), this line appears in yellow. This is useful to notice
when someone used a TargetState and forgot to release the target through
a SelfState.
It is also possible to display the value of triggers of your choice,
with a Type = DisplayToClipboard. Check the docs that come with Mugen
for the detail on how to use that controller, which you will probably
want to put in StateDef -2 with something like a trigger1 = 1 (to let it
be constantly activated). This controller lets you display on the debug
text the value of whichever trigger you want, such as the distance to
the opponent, the current value of a variable (extremely useful to keep
track of when your variable takes a different value) and so on.
;---------------------------------------------------------------------------
[State -2, Clipboard]
type = DisplayToClipboard
trigger1 = time
text = "Combo : %f, dampener : %f, button : %d, vy : %f, anti-cheat : %d \n CvS2 Kim Kaphwan by Byakko"
params = FVar(10),FVar(13),var(4), Vel Y, Var(50)
ignorehitpause = 1
With this code...
... you get this sort of extra info, right below the bottom debug text.
Other than this state debug, the debug mode also gives you some more
information : if your character is missing a required sprite or a
required animation, or if his code is performing an illegal operation
that doesn't directly crash Mugen (such as trying to display a
non-existent animation), Mugen will display a warning in the top part of
the screen. A term you might come across is a "debug flood" - just as
the name says, if an error is constantly happening, the debug text will
be flooded by the same message constantly.
Warnings and coding problems will be displayed on the top left side.
This debug mode MUST be one of your first reflexes when coding and troubleshooting. It will always help you.
|
The very first step you can take in Mugen coding is to open one of
KFM's files, look for one something you're interested in, change the
value, and see what it does. Then change the value again, and test the
difference. Keep doing that until you get an idea of what the controller
or parameter does, and how the value affects what you get on the
screen.
This is the best method to learn how to code : trials and errors.
Change a value, test how it looks, and change it again. Break
something, test what it does, and change it. Since the code is nothing
but text where you can revert all your changes, you aren't risking
anything by changing random values and testing what it does. You're probably not going to get the right velocity or timing on your first try anyway
(unless you happen to have a tool to obtain it from the original game),
so never fear changing a single value by 0.2 over and again until it
looks perfectly right. You only need to close Mugen, save the changes,
and restart it.
You'll also easily get into situations where something you thought
was made right ends up causing troubles with something you are currently
adding - just change it. Of course, that implies you're probably going
to have to re-test everything that was related to it, but this is the price of getting it right. It's never difficult, and it's always a matter of spending an extra couple of minutes ; this is where you dedication shows.
Despite how simple this advice sounds, this is probably the point that will weight the most out of everything you can learn about coding.
This cannot be stressed enough. Never feel down about spending an extra
five minutes to change a value and see if it looks better. Never feel
it's a waste of time. It never is.
|
We have seen that helpers can have their own variables. We have seen
that sometimes, you might want to access information that belongs to
another character, or more generally, another element / another object
in play. The tool for that is trigger redirection.
Take the example of a variable that belongs to the helper. The
helper is considered as a separate object, that has its own variables -
while the parent has his own variables as well. Sometimes, the helper
needs to look at a variable from the parent, and vice-versa, the parent
at a variable owned by his helper. Quite simply put, trigger redirection
is a redirection for triggers : putting "Helper([helper's ID]),var(1)"
will return the value of the helper's var(1) instead of the character's
var(1). With that method, a given object (a character, a helper) can
have access to any trigger of another object (the opponent, the
parent/root, a helper, the target being hit, the partner) just by adding
the redirection with a coma in front of the trigger. All triggers can
be used together with a trigger redirection, and that can give you a lot
of freedom. As with custom GetHit states, just try not to modify the
value of another character's variable, you don't know what it might
break. Trigger redirection will also be very useful for things like AI
detection to know what exactly the enemy is doing and where he is - a
simple example is "EnemyNear,StateType" to check if the nearest enemy is
on the ground or in the air, at the very least.
Just as an additional note, in the case of redirection to enemies
and helpers, you'll want to add a "trigger1 = numenemy" or "trigger1 =
numhelper" just before, because Mugen might give you a debug flood
(whether you actually have a helper/enemy at this point or not).
trigger1 = EnemyNear, StateType = A ; if the nearest enemy is in the air
trigger2 = EnemyNear, MoveType = A && EnemyNear, Vel X >= 6 ; if the nearest enemy is attacking and moving forward
At the time of writing, it is impossible to use multiple redirection
(to check the enemy's helper's variable, for example) ; it may be
possible to use a slight workaround by grabbing the PlayerID (which I
quickly mentionned earlier) of each element and making just one
redirection directly with that ID, but this can't do everything and will
quickly show some limits. Multiple redirection is planned for future
versions of Mugen.
|
Although some people (and some games) like to make AIs that can pick
up your slightest mistake or delay to maul you and chain you for dozens
of seconds without letting you touch the ground, the point of an
artificial intelligence is to behave like a human as much as possible.
Paying more for your mistakes may be a mark of increasing difficulty,
but telegraphed sequences set in stone where you know exactly what the
AI is going to do if you do this or that action are just plain bad. No,
there's not necessarily any direction corelation between the former and
the latter, but the underlying concept here is balance and variety. And
behaviors that look as human as possible.
Let's take it from the beginning : make a list of the moves that are
available to you depending on the situation, and then try to determine
when the AI will attempt to use it. Is the opponent all the way across
the screen doing nothing ? You'll probably want to charge up your
powerbar if you can, or you'll run at him, or you'll throw a projectile
and hope he'll be forced to move. Or you might do nothing, if you want a
few seconds to relax. Is the opponent jumping at you, a couple of
meters in front and above you, going down ? Dragon Punch. If he's close,
throw a light punch or a crouching light kick - and if it connects, try
a combo, like a second light punch or a special, or maybe a super if
you can. Pointing out that the AI can perform combos too is important.
If you know all your moves, which moves are safe in what situation,
everything about them, you'll know what to do in every single situation
(provided your character doesn't have a crappy movelist, of course).
Well, artificial intelligence is all about making the computer do
the same thing as you. When you have more than one option (I hope your
movelist is good enough for that), add a bit of randomness in your moves
(with the trigger "Random", see the docs for details) ; adding random
in your triggers helps in making sure your character doesn't always
instantly attack every tick it can do something, but sometimes lets half
a second or two pass before attacking. Simply remember that your
triggers will be evaluated on every single tick, 60 times per second, so
adjust your "random" trigger and make it loose enough to not be over
aggressive.
Most of the work here is to determine the right triggers - to be used along with trigger redirection.
Once you have established a flowchart of all your possibilities (I
hope they're varied enough), you can start porting the conditions for
each move into code.
The first thing you want about the AI is to detect if it actually is
the AI controlling the character. Basically, you use a switch (a
variable) that will check if the AI is in control, and then be the main
trigger for everything AI-related : if the AI is in control, do
everything the AI should do, simple as that.
Up until Mugen 1.0, a few methods were made to detect that, some
more complicated than others, and a number of which didn't work properly
- if you see a character you control suddenly take over and start
moving on its own, it means you inadvertantly activated the AI control
switch, meaning the code thinks the AI is the one playing. You don't
want that. As far as I know, the AI detection method that is the most
trustful is the one set up by Winane - look up Winane AI activation and
follow the steps very carefully (and I mean veeeeery carefully). Or get
Mugen 1.0 and just use the shiny new AILevel trigger.
Once you have determined that the AI is in control, look at your
ChangeStates in your CMD file : they say that the ChangeState can
trigger if you input the corresponding command. Well, also allow it to
be triggered if the AI is on. The AI version should have the same
triggers (the same restrictions and availability) as the human input.
And then add the triggers you set up for the AI, as described in the
previous step. Again, usage of the "Random" trigger is important. Mix in
the availability in combos, if a special move is available when in the
middle of another attack for a combo, point out that the AI can do it
(with a trigger on your AI switch replacing the command input) - again
with a "Random" factor, for all of your combo options.
;---------------------------------------------------------------------------
; AI Ko Hou
[State -1, AIKH]
type = ChangeState
triggerall = StateType != A ; if I am not in the air
triggerall = AILevel && StateType != A && RoundState = 2
&& (ctrl || StateNo = 500) && P2StateNo != [120,160] ; if I can do it
trigger1 = EnemyNear, StateType = A && P2BodyDist X < 150
&& (Random < 100+35*(Life<400)+15*(P2BodyDist X <55)) ;
if the enemy is in the air and coming at me
trigger2 = EnemyNear, MoveType = A && EnemyNear, Vel X >= 6
&& Random < 120 && P2BodyDist X = [130,220] ; if the enemy is attacking and comming at me (yeah, you tell me if that's smart)
trigger3 = AnimElem = 3, > 1 && (Random < 75+30*(Life<400))
trigger3 = StateNo = 400 || StateNo = 410 || StateNo = 430 || StateNo = 200 || StateNo = 205 || StateNo = 235 ; if I am in one of the listed state numbers (all are attacks that don't need to connect)
trigger4 = StateNo = 210 || StateNo = 225 || StateNo = 300 || StateNo = 1460 ; if I am in one of the listed state numbers (all are attacks)
trigger4 = MoveContact && Random < (75+35*(Life<400)) ; if the attack in the above trigger4 has connected
value = 1100
An example of triggers allowing the AI to detect when to perform an
uppercut. Note that most of these triggers include an increasing chance
of performing the attack the less life I have, making the AI more
aggressive as I lose life. Also notice how often I look at where the
opponent is with "EnemyNear, StateType = A" or "EnemyNear, Vel X".
This is the generic guideline. For more technical details on how to
make your triggers more specific, you can try to look up other
characters or more detailed tutorials, if you can find some. But the
largest part of the work is getting the right triggers, the ones that
check where the opponent is and what he is doing, and deciding which
move to perform depending on that.
|
|