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
topic:havok_behavior_editing [2025/02/25 15:07] 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 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 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 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.1740496024.txt.gz · Last modified: by admin