Site Tools


topic:havok_behavior_editing

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
topic:havok_behavior_editing [2025/02/25 15:06] admintopic:havok_behavior_editing [2025/02/27 00:44] (current) admin
Line 89: Line 89:
 The env function gets info about the character from the game, it has the following syntax: The env function gets info about the character from the game, it has the following syntax:
  
-<code>+<code Lua>
 env(ID, args) env(ID, args)
 </code> </code>
Line 103: Line 103:
 The function has the following syntax: The function has the following syntax:
  
-<code>+<code Lua>
 act(ID, args) act(ID, args)
 </code> </code>
Line 114: Line 114:
  
 Most basic onUpdate functions have the following form: Most basic onUpdate functions have the following form:
-<code>+<code Lua>
 function SMSIName_onUpdate() function SMSIName_onUpdate()
     if SomeCommonFunction(args) == TRUE then     if SomeCommonFunction(args) == TRUE then
Line 126: Line 126:
 As an example the first one-handed r1 attack: As an example the first one-handed r1 attack:
  
-<code>+<code Lua>
 function AttackRightLight1_onUpdate() function AttackRightLight1_onUpdate()
     if AttackCommonFunction("W_AttackRightLight2", "W_AttackRightHeavy1SubStart", "W_AttackLeftLight1",     if AttackCommonFunction("W_AttackRightLight2", "W_AttackRightHeavy1SubStart", "W_AttackLeftLight1",
Line 143: Line 143:
 ===== Basic Editing ===== ===== Basic Editing =====
  
-The following section will cover the basics of behavior editing which should serve to establish a rudimentary understanding of the process, which due to the modular nature of the behavior system, should convey the necessary information to allow you to create any behavior structure you desire. See the [[[common-refmat:havok_behavior_reference | reference page]]] for a full list of classes and what they do, and always refer to vanilla examples to see how they are used.+The following section will cover the basics of behavior editing which should serve to establish a rudimentary understanding of the process, which due to the modular nature of the behavior system, should convey the necessary information to allow you to create any behavior structure you desire. See the [[common-refmat:havok_behavior_reference | reference page]] for a full list of classes and what they do, and always refer to vanilla examples to see how they are used.
  
 Each example will build on what is discussed in the previous ones so I recommend looking at them in order if you do not yet possess a solid understanding of the behavior system. I will also cover a lot of general info, including tips and good practices, in the first few examples. Each example will build on what is discussed in the previous ones so I recommend looking at them in order if you do not yet possess a solid understanding of the behavior system. I will also cover a lot of general info, including tips and good practices, in the first few examples.
Line 159: Line 159:
 Once we have opened the behavior file we will want to find the CustomManualSelectorGenerator, or CMSG for short, of the animation we want to register. The animation ID of a CMSG is stored in its hkparam called "animId", in the XML it looks like this: Once we have opened the behavior file we will want to find the CustomManualSelectorGenerator, or CMSG for short, of the animation we want to register. The animation ID of a CMSG is stored in its hkparam called "animId", in the XML it looks like this:
  
-<code>+<code Lua>
 <hkparam name="animId">30000</hkparam> <hkparam name="animId">30000</hkparam>
 </code> </code>
Line 165: Line 165:
 Thus an effective method for finding the CMSG you want is searching for:  Thus an effective method for finding the CMSG you want is searching for: 
  
-<code>+<code Lua>
 "animId">[Id of your animation] "animId">[Id of your animation]
 </code> </code>
Line 194: Line 194:
 Here is the final version of our new hkbClipGenerator: Here is the final version of our new hkbClipGenerator:
  
-<code>+<code Lua>
 <hkobject class="hkbClipGenerator" name="#12802" signature="0xd4cc9f6"> <hkobject class="hkbClipGenerator" name="#12802" signature="0xd4cc9f6">
     <hkparam name="variableBindingSet">null</hkparam>     <hkparam name="variableBindingSet">null</hkparam>
Line 249: Line 249:
 To find the parent state machine, we once again search for the name ID of our object, this time hkbStateMachineStateInfo ''#638'' and see that it is referenced in hkbStateMachine ''#285'', called AttackRight_SM. This state machine, as we can see by the name, references all right hand attack states, so this is where we will reference our CMSG from. Before we do that, we will look for the last generator in the state list (#1180) and check its userData value, which is 17104921. Therefore we pick 17104922 as the userData value for our new CMSG. Performing a search for this value shows no other instances thereof, so we know it's safe to use. To find the parent state machine, we once again search for the name ID of our object, this time hkbStateMachineStateInfo ''#638'' and see that it is referenced in hkbStateMachine ''#285'', called AttackRight_SM. This state machine, as we can see by the name, references all right hand attack states, so this is where we will reference our CMSG from. Before we do that, we will look for the last generator in the state list (#1180) and check its userData value, which is 17104921. Therefore we pick 17104922 as the userData value for our new CMSG. Performing a search for this value shows no other instances thereof, so we know it's safe to use.
  
-I will not cover the hkparams of hkbStateMachines in depth in this example, please refer to the [[[common-refmat:havok_behavior_reference | reference page]]] for documentation on that. For the purposes of this example we will look at two hkparams, "states" and "wildcardTransitions"+I will not cover the hkparams of hkbStateMachines in depth in this example, please refer to the [[common-refmat:havok_behavior_reference | reference page]] for documentation on that. For the purposes of this example we will look at two hkparams, "states" and "wildcardTransitions"
  
 The ''states'' parameter is a reference list like ''generators'' for CMSGs, the key difference being that it does not reference [common-refmat:havok_behavior_reference#Generators|generator] type objects, but hkbStateMachineStateInfo objects. The active state is selected based on the state ID of the state info objects.  The ''states'' parameter is a reference list like ''generators'' for CMSGs, the key difference being that it does not reference [common-refmat:havok_behavior_reference#Generators|generator] type objects, but hkbStateMachineStateInfo objects. The active state is selected based on the state ID of the state info objects. 
Line 265: Line 265:
 Once we have created our new state info hkobject, we add a reference to it to the end of the state machine state list and change the "generator" hkparam to reference our new CMSG. We will also change the "name" hkparam to AttackRightLight4. This is important because this is the name we will use in hks for the state's onUpdate function. The last thing we need to change is the stateId. For this I have found no apparent ID system, so we will now go to the transition info array referenced in the "wildcardTransitions" hkparam of our parent state machine (''#1184'') and look for an available stateId. Once we have created our new state info hkobject, we add a reference to it to the end of the state machine state list and change the "generator" hkparam to reference our new CMSG. We will also change the "name" hkparam to AttackRightLight4. This is important because this is the name we will use in hks for the state's onUpdate function. The last thing we need to change is the stateId. For this I have found no apparent ID system, so we will now go to the transition info array referenced in the "wildcardTransitions" hkparam of our parent state machine (''#1184'') and look for an available stateId.
  
-HkbStateMachineTransitionInfoArrays consist of a single hkparam called "transitions" which is an array of unnamed transition info objects. To map an event name to our new state we will want to duplicate one of these objects. The main hkparams which interest us here are "eventId", "toStateId", once again see the [[[common-refmat:havok_behavior_reference | reference page]]] for full documentation.+HkbStateMachineTransitionInfoArrays consist of a single hkparam called "transitions" which is an array of unnamed transition info objects. To map an event name to our new state we will want to duplicate one of these objects. The main hkparams which interest us here are "eventId", "toStateId", once again see the [[common-refmat:havok_behavior_reference | reference page]] for full documentation.
  
 After creating our new transition info object within the "transitions" hkparam we will want to look through the existing objects for an unused stateId. In this case I will pick 47 and set the "toStateId" in our transition info object as well as the "stateId" hkparam of our hkbStateMachineStateInfo accordingly. After creating our new transition info object within the "transitions" hkparam we will want to look through the existing objects for an unused stateId. In this case I will pick 47 and set the "toStateId" in our transition info object as well as the "stateId" hkparam of our hkbStateMachineStateInfo accordingly.
Line 285: Line 285:
 To finish setting up our new attack behavior we will set up its onUpdate function and trigger it from the onUpdate function of the preceding attack, which looks like this: To finish setting up our new attack behavior we will set up its onUpdate function and trigger it from the onUpdate function of the preceding attack, which looks like this:
  
-<code>+<code Lua>
 function AttackRightLight3_onUpdate() function AttackRightLight3_onUpdate()
     if AttackCommonFunction("W_AttackRightLight2", "W_AttackRightHeavy1SubStart", "W_AttackLeftLight1", "W_AttackLeftHeavy1", "W_AttackBothLight2", "W_AttackBothHeavy1Start", FALSE, TRUE) == TRUE then     if AttackCommonFunction("W_AttackRightLight2", "W_AttackRightHeavy1SubStart", "W_AttackLeftLight1", "W_AttackLeftHeavy1", "W_AttackBothLight2", "W_AttackBothHeavy1Start", FALSE, TRUE) == TRUE then
Line 297: Line 297:
 As we can see it consists of a single function call to AttackCommonFunction which has the following arguments: As we can see it consists of a single function call to AttackCommonFunction which has the following arguments:
  
-<code>+<code Lua>
 AttackCommonFunction(r1, r2, l1, l2, b1, b2, guardcondition, use_atk_queue) AttackCommonFunction(r1, r2, l1, l2, b1, b2, guardcondition, use_atk_queue)
 </code> </code>
Line 307: Line 307:
 The [#env env] to check for the override tae section of the player's weapon is: The [#env env] to check for the override tae section of the player's weapon is:
  
-<code>+<code Lua>
 env("装備武器特殊カテゴリ番号取得", hand) env("装備武器特殊カテゴリ番号取得", hand)
 </code> </code>
Line 313: Line 313:
 Since this is a right hand attack we want to get the right hand weapon's tae section, so for the argument "hand" we choose the variable "HAND_RIGHT" which results in the following code: Since this is a right hand attack we want to get the right hand weapon's tae section, so for the argument "hand" we choose the variable "HAND_RIGHT" which results in the following code:
  
-<code>+<code Lua>
 function AttackRightLight3_onUpdate() function AttackRightLight3_onUpdate()
     local r1 = "W_AttackRightLight2"     local r1 = "W_AttackRightLight2"
Line 329: Line 329:
 For two handed attacks, you need to account for the possibility of the player two-handing their left hand weapon, which can be accomplished using the following code: For two handed attacks, you need to account for the possibility of the player two-handing their left hand weapon, which can be accomplished using the following code:
  
-<code>+<code Lua>
 local hand = HAND_RIGHT local hand = HAND_RIGHT
 if c_Style == HAND_LEFT_BOTH then if c_Style == HAND_LEFT_BOTH then
Line 338: Line 338:
 Now we want to set up the onUpdate function for our new behavior. Make sure to use the name of its hkbStateMachineStateInfo along with the "_onUpdate" prefix. In this case I chose to have it combo to the first r1 attack but you can choose whichever behavior you want. Now we want to set up the onUpdate function for our new behavior. Make sure to use the name of its hkbStateMachineStateInfo along with the "_onUpdate" prefix. In this case I chose to have it combo to the first r1 attack but you can choose whichever behavior you want.
  
-<code>+<code Lua>
 function AttackRightLight4_onUpdate() function AttackRightLight4_onUpdate()
     if AttackCommonFunction("W_AttackRightLight1", "W_AttackRightHeavy1SubStart", "W_AttackLeftLight1", "W_AttackLeftHeavy1", "W_AttackBothLight1", "W_AttackBothHeavy1Start", FALSE, TRUE) == TRUE then     if AttackCommonFunction("W_AttackRightLight1", "W_AttackRightHeavy1SubStart", "W_AttackLeftLight1", "W_AttackLeftHeavy1", "W_AttackBothLight1", "W_AttackBothHeavy1Start", FALSE, TRUE) == TRUE then
Line 391: Line 391:
 Looking at common_define we can see that each actionId has three corresponding global variables. One called "SWORDARTS_[name of action]" which is equivalent to the actionId, and two called "SWORDARTS_REQUEST_RIGHT_[name of action]" and "SWORDARTS_REQUEST_LEFT_[name of action]" respectively which have the values 200 + actionId and 100 + actionId. For example the parry weapon art with actionId 1 has the following variables defined in common_define.hks: Looking at common_define we can see that each actionId has three corresponding global variables. One called "SWORDARTS_[name of action]" which is equivalent to the actionId, and two called "SWORDARTS_REQUEST_RIGHT_[name of action]" and "SWORDARTS_REQUEST_LEFT_[name of action]" respectively which have the values 200 + actionId and 100 + actionId. For example the parry weapon art with actionId 1 has the following variables defined in common_define.hks:
  
-<code>+<code Lua>
 SWORDARTS_PARRY = 1 SWORDARTS_PARRY = 1
 SWORDARTS_REQUEST_LEFT_PARRY = 101 SWORDARTS_REQUEST_LEFT_PARRY = 101
Line 399: Line 399:
 Thus for our new actionId 37 with name NewSwordArt we put the following: Thus for our new actionId 37 with name NewSwordArt we put the following:
  
-<code>+<code Lua>
 SWORDARTS_NEW = 37 SWORDARTS_NEW = 37
 SWORDARTS_REQUEST_LEFT_NEW = 137 SWORDARTS_REQUEST_LEFT_NEW = 137
Line 409: Line 409:
 Unlike standard attacks, which weapon art to execute is not directly determined in onUpdate functions. Every CommonFunction calls ExecAttack, passing on its attack arguments which are set differently in each onUpdate function. ExecAttack in turn calls the GetAttackRequest function to determine which of the attacks to execute. GetAttackRequest checks which button is currently being pressed and returns the appropriate ATTACK_REQUEST, if the button being pressed is L2 however it calls the GetSwordArtsRequest() function which does the  following: Unlike standard attacks, which weapon art to execute is not directly determined in onUpdate functions. Every CommonFunction calls ExecAttack, passing on its attack arguments which are set differently in each onUpdate function. ExecAttack in turn calls the GetAttackRequest function to determine which of the attacks to execute. GetAttackRequest checks which button is currently being pressed and returns the appropriate ATTACK_REQUEST, if the button being pressed is L2 however it calls the GetSwordArtsRequest() function which does the  following:
  
-<code>+<code Lua>
 function GetSwordArtsRequest() function GetSwordArtsRequest()
     local style = c_Style     local style = c_Style
Line 432: Line 432:
 The variable arts_id corresponds to the actionId from the SwordArtsParam and thus our variable "SWORDARTS_NEW" when wielding a weapon with a weapon art with actionId 37. This function checks which hand we are wielding our weapon with and returns the appropriate request to ExecAttack where the local variable "request" is used to store it. Now all we need to do is tell the ExecAttack function what to do when it receives our new request. Since our weapon art does not depend on which hand we are wielding our weapon in we will check for either request == SWORDARTS_REQUEST_LEFT_NEW or request == SWORDARTS_REQUEST_RIGHT_NEW and trigger our new behavior if true using the following code: The variable arts_id corresponds to the actionId from the SwordArtsParam and thus our variable "SWORDARTS_NEW" when wielding a weapon with a weapon art with actionId 37. This function checks which hand we are wielding our weapon with and returns the appropriate request to ExecAttack where the local variable "request" is used to store it. Now all we need to do is tell the ExecAttack function what to do when it receives our new request. Since our weapon art does not depend on which hand we are wielding our weapon in we will check for either request == SWORDARTS_REQUEST_LEFT_NEW or request == SWORDARTS_REQUEST_RIGHT_NEW and trigger our new behavior if true using the following code:
  
-<code>+<code Lua>
 elseif request == SWORDARTS_REQUEST_RIGHT_NEW or request == SWORDARTS_REQUEST_LEFT_NEW then elseif request == SWORDARTS_REQUEST_RIGHT_NEW or request == SWORDARTS_REQUEST_LEFT_NEW then
         ExecEventAllBody("W_NewSwordArt")         ExecEventAllBody("W_NewSwordArt")
Line 441: Line 441:
 Finally we need to create an onUpdate function for our new weapon art, which unlike the update functions of regular attacks will call SwordArtsCommonFunction instead of AttackCommonFunction. Finally we need to create an onUpdate function for our new weapon art, which unlike the update functions of regular attacks will call SwordArtsCommonFunction instead of AttackCommonFunction.
  
-<code>+<code Lua>
 function ChargeContinue3_onUpdate() function ChargeContinue3_onUpdate()
     if SwordArtsCommonFunction("W_AttackRightLight1", "W_AttackRightHeavy1Start", "W_AttackLeftLight1", "W_AttackLeftHeavy1", "W_AttackBothLight1", "W_AttackBothHeavy1Start", FALSE, FALSE, FALSE, GEN_TRANS_LEFT) ==     if SwordArtsCommonFunction("W_AttackRightLight1", "W_AttackRightHeavy1Start", "W_AttackLeftLight1", "W_AttackLeftHeavy1", "W_AttackBothLight1", "W_AttackBothHeavy1Start", FALSE, FALSE, FALSE, GEN_TRANS_LEFT) ==
Line 470: Line 470:
   - Set the variable in hks.   - Set the variable in hks.
  
-First, we will look for the "variableInfos" hkparam of the hkbBehaviorGraphData hkobject and add a new entry to the end of the list by duplicating one of the existing hkobjects under said hkparam. Once we've got that we will set the parameters of our new entry. The hkparam "role" with its single sub-object is always identical so we do not need to touch it. The hkparam "type" determines the variable type, check the [[[common-refmat:havok_behavior_reference#Enums | reference page]]] for possible values. In this case we will use "VARIABLE_TYPE_INT16" like the other weapon art selector variables.+First, we will look for the "variableInfos" hkparam of the hkbBehaviorGraphData hkobject and add a new entry to the end of the list by duplicating one of the existing hkobjects under said hkparam. Once we've got that we will set the parameters of our new entry. The hkparam "role" with its single sub-object is always identical so we do not need to touch it. The hkparam "type" determines the variable type, check the [[common-refmat:havok_behavior_reference#Enums | reference page]] for possible values. In this case we will use "VARIABLE_TYPE_INT16" like the other weapon art selector variables.
  
 Next we will set the minimum and maximum values of our variable in the "variableBounds" hkparam of the hkbBehaviorGraphData hkobject. Once again we will duplicate an entry and adjust the values as desired. Common values for each variable type can be found on the reference page. For our variable we will set a minimum value of 0 and a maximum value of 1, as our selector hkbMSG only has 2 child generators. Next we will set the minimum and maximum values of our variable in the "variableBounds" hkparam of the hkbBehaviorGraphData hkobject. Once again we will duplicate an entry and adjust the values as desired. Common values for each variable type can be found on the reference page. For our variable we will set a minimum value of 0 and a maximum value of 1, as our selector hkbMSG only has 2 child generators.
Line 482: Line 482:
 The function which sets the IsEnoughArtPoints variables in hks is called SetSwordArtsPointInfo. For our new followup attack, which is activated by pressing R2, we want to add the following condition under the "elseif button == ACTION_ARM_R2" condition: The function which sets the IsEnoughArtPoints variables in hks is called SetSwordArtsPointInfo. For our new followup attack, which is activated by pressing R2, we want to add the following condition under the "elseif button == ACTION_ARM_R2" condition:
  
-<code>+<code Lua>
 elseif IsNodeActive("ChargeContinue2_Selector") == TRUE then elseif IsNodeActive("ChargeContinue2_Selector") == TRUE then
     val = "IsEnoughArtPointsR2_4"     val = "IsEnoughArtPointsR2_4"
Line 531: Line 531:
 HalfBlend animations are triggered using the ExecEventHalfBlend function which has the following arguments: HalfBlend animations are triggered using the ExecEventHalfBlend function which has the following arguments:
  
-<code>+<code Lua>
 ExecEventHalfBlend(event_table, blend_type) ExecEventHalfBlend(event_table, blend_type)
 </code> </code>
Line 539: Line 539:
 Event tables consist of the base event name and a state Id for every state machine between HalfBlend_SM and the behavior's ClipGenerators. For example the upper blend of GestureStart animations are all referenced by the hkbMSG "Gesture_Upper_Selector", which is state 0 of "Gesture_Upper_SM". "Gesture_Upper_SM" in turn is state 22 of "Upper_SM" which is a state of "HalfBlend_SM". So between "HalfBlend_SM" and the hkbClipGenerators we have 2 state machines, "Gesture_Upper_SM" and "Upper_SM", thus our the event table needs to have 3 elements: our base event name and our 2 state ids. Taking a look at the event name for GestureStart we can see that this is indeed the case. Event tables consist of the base event name and a state Id for every state machine between HalfBlend_SM and the behavior's ClipGenerators. For example the upper blend of GestureStart animations are all referenced by the hkbMSG "Gesture_Upper_Selector", which is state 0 of "Gesture_Upper_SM". "Gesture_Upper_SM" in turn is state 22 of "Upper_SM" which is a state of "HalfBlend_SM". So between "HalfBlend_SM" and the hkbClipGenerators we have 2 state machines, "Gesture_Upper_SM" and "Upper_SM", thus our the event table needs to have 3 elements: our base event name and our 2 state ids. Taking a look at the event name for GestureStart we can see that this is indeed the case.
  
-<code>+<code Lua>
 GESTURE_DEF0 = 22 GESTURE_DEF0 = 22
 GESTURESTART_DEF1 = 0 GESTURESTART_DEF1 = 0
Line 547: Line 547:
 It is important that the stateIds are in hierarchical order because they are systematically assigned to the default state variables mentioned in the previous section by the function ExecEventHalfBlend. Looking at the ExecEventHalfBlend function we can also see the reason for the eventName naming scheme. It is important that the stateIds are in hierarchical order because they are systematically assigned to the default state variables mentioned in the previous section by the function ExecEventHalfBlend. Looking at the ExecEventHalfBlend function we can also see the reason for the eventName naming scheme.
  
-<code>+<code Lua>
 function ExecEventHalfBlend(event_table, blend_type) function ExecEventHalfBlend(event_table, blend_type)
     if blend_type == ALLBODY then     if blend_type == ALLBODY then
topic/havok_behavior_editing.1740495978.txt.gz · Last modified: by admin