How to animate a model – ArmA: Armed Assault

From Bohemia Interactive Community
Jump to navigation Jump to search
m (Text replacement - "^=+ * " to "")
m (Text replacement - " ( *class [a-zA-Z0-9_]+): *([a-zA-Z0-9_]+ *) " to " $1 : $2 ")
 
(6 intermediate revisions by the same user not shown)
Line 1: Line 1:
{{TOC|side}}
'''Attention:'''
'''Attention:'''
This article is a tutorial on how to animate parts of your model (e.g. wheels, rotors, etc.).
This article is a tutorial on how to animate parts of your model (e.g. wheels, rotors, etc.).
Line 10: Line 11:


'''Model preparation:'''
'''Model preparation:'''
Besides adding selections (same as in OFP but in ArmA bone selections should never overlap) you also need to add a following named property to your first resolution LOD (press Alt+P to open named properties window):
Besides adding selections (same as in OFP but in ArmA bone selections should never overlap) you also need to add a following named property to your first resolution LOD (press {{Controls|alt|P}} to open named properties window):
  property name: autocenter'''
property name: autocenter'''
  value: 0'''
value: 0'''
If your model has Geometry LOD the above needs to be also done for your Geometry LOD.
If your model has Geometry LOD the above needs to be also done for your Geometry LOD.
This property stopps engine from shifting the animation axes.
This property stopps engine from shifting the animation axes.


If you encounter a bug when your weapon bones fall apart after you dropp it to the ground the reason might be lack of Geometry or View - pilot LODs.
If you encounter a bug when your weapon bones fall apart after you dropp it to the ground the reason might be lack of Geometry or View - pilot LODs.


== model.cfg ==
== model.cfg ==
Line 25: Line 27:
This way, you do not need to binarize your addon, so it is a perfect way to test it.
This way, you do not need to binarize your addon, so it is a perfect way to test it.
However, the first method is prefered and should always be used when releasing your addon.
However, the first method is prefered and should always be used when releasing your addon.


== cfgSkeletons ==
== cfgSkeletons ==
The ''cfgSkeletons'' class defines, as mentioned before, the bones (<nowiki>=</nowiki> animated selections) of a vehicle.
The ''cfgSkeletons'' class defines, as mentioned before, the bones (<nowiki>=</nowiki> animated selections) of a vehicle.


Each skeleton is a subclass within the cfgSkeletons class, consisting of three parameters:
Each skeleton is a subclass within the cfgSkeletons class, consisting of three parameters:


{|width="80%"
{| width="80%"
!width="50%"|Parameter
! width="50%" | Parameter
!width="50%"|Description
! width="50%" | Description
|-
|-
|'''isDiscrete'''
| '''isDiscrete'''
|currently unknown, set to 1.
| currently unknown, set to 1.
|-
|-
|'''skeletonInherit'''
| '''skeletonInherit'''
|inherit bones from given class.
| inherit bones from given class.
|-
|-
|'''skeletonBones[]'''
| '''skeletonBones[]'''
|define your own bones here.
| define your own bones here.
|-
|-
|}
|}


=== Defining a bone ===
=== Defining a bone ===
Bones are defined in the skeletonBones[]-array which is made of a list of unsorted bones. Each bone is the name of a selection you want to animate.
Bones are defined in the skeletonBones[]-array which is made of a list of unsorted bones. Each bone is the name of a selection you want to animate.


==== A single bone ====
==== A single bone ====
A bone is defined by using two strings:
A bone is defined by using two strings:
:"bone",""
:"bone", ""
You may define multiple bones by strining them togeter.
You may define multiple bones by strining them togeter.


Line 57: Line 62:


===== Example =====
===== Example =====
<syntaxhighlight lang=cpp>skeletonBones[]=
<syntaxhighlight lang="cpp">skeletonBones[]=
{
{
"bone1","", //defines bone1
"bone1", "", // defines bone1
"bone2","" //defines bone2
"bone2", "" // defines bone2
};</syntaxhighlight>
};
</syntaxhighlight>


==== Linked bones ====
==== Linked bones ====
The second argument (empty in the example above) is used for linking two bones:<br>
The second argument (empty in the example above) is used for linking two bones:<br>
:"bone1","bone2"
:"bone1", "bone2"


Linking is used to make the animation of "bone1" depending on the movement of "bone2". If you e.g. have a turret, you have to make use of linking here, because the up and down movement of the turrets weapon is typically influenced by left and right movement of the turret:
Linking is used to make the animation of "bone1" depending on the movement of "bone2". If you e.g. have a turret, you have to make use of linking here, because the up and down movement of the turrets weapon is typically influenced by left and right movement of the turret:


===== Example =====
===== Example =====
<syntaxhighlight lang=cpp>skeletonBones[]=
<syntaxhighlight lang="cpp">
skeletonBones[] =
{
{
"turret_x","",         //defines bone turret_x
"turret_x", "", // defines bone turret_x
"turret_y","turret_x" //defines bone turret_y and makes it linked to bone turret_x
"turret_y", "turret_x" // defines bone turret_y and makes it linked to bone turret_x
};</syntaxhighlight>
};
</syntaxhighlight>


'''Attention:'''<br>
'''Attention:'''<br>
You can not link more than two bones in a row!<br>
You can not link more than two bones in a row!<br>
If you do something like
If you do something like
<syntaxhighlight lang=cpp> "bone1","bone2","bone3"</syntaxhighlight>
<syntaxhighlight lang="cpp"> "bone1", "bone2", "bone3"</syntaxhighlight>
this will result in an error, as Armed Assault interprets this as
this will result in an error, as Armed Assault interprets this as
*defining "bone1" which is linked to "bone2"
*defining "bone1" which is linked to "bone2"
Line 85: Line 93:


However, it should be possible to use a syntax like this (not tested yet):
However, it should be possible to use a syntax like this (not tested yet):
<syntaxhighlight lang=cpp>skeletonBones[]=
<syntaxhighlight lang="cpp">skeletonBones[]=
{
{
"bone1","bone2", //defines bone "bone1" and makes it linked to "bone2"
"bone1", "bone2", // defines bone "bone1" and makes it linked to "bone2"
"bone2","bone3", //defines bone "bone2" and makes it linked to "bone3"
"bone2", "bone3", // defines bone "bone2" and makes it linked to "bone3"
"bone3",""       //defines bone "bone3".
"bone3", "" // defines bone "bone3".
};</syntaxhighlight>
};
</syntaxhighlight>


In conclusion, "bone1" is linked to "bone2", which is linked to "bone3".<br>
In conclusion, "bone1" is linked to "bone2", which is linked to "bone3".<br>
Line 96: Line 105:


=== cfgSkeletons Example ===
=== cfgSkeletons Example ===
<syntaxhighlight lang=cpp>class cfgSkeletons
 
<syntaxhighlight lang="cpp">
class cfgSkeletons
{
{
class BWMod_Tiger_Skeleton
class BWMod_Tiger_Skeleton
{
{
isDiscrete=1;
isDiscrete = 1;


skeletonInherit="";
skeletonInherit = "";
skeletonBones[]=
skeletonBones[] =
{
{
"wheelL","",
"wheelL", "",
"wheelR","",
"wheelR", "",


"turret_RMK_x","",
"turret_RMK_x", "",
"turret_RMK_y","turret_RMK_x",
"turret_RMK_y", "turret_RMK_x",


"turret_OSIRIS_y","",
"turret_OSIRIS_y", "",
"turret_OSIRIS_x","turret_OSIRIS_y"
"turret_OSIRIS_x", "turret_OSIRIS_y"
};
};
};
};
};</syntaxhighlight>
};
</syntaxhighlight>


== cfgModels ==
== cfgModels ==
The cfgModels class is used to declare the selections of a model you want to animate or access via the setObjectTexture command. Since ArmA, the cfgModels class has been extended an is now used to define all animations of a model.
The cfgModels class is used to declare the selections of a model you want to animate or access via the setObjectTexture command. Since ArmA, the cfgModels class has been extended an is now used to define all animations of a model.


Line 124: Line 137:
Each modelclass consists of three parameters and an additional subclass which defines the animations of your models:
Each modelclass consists of three parameters and an additional subclass which defines the animations of your models:


{|width="80%"
{| width="80%"
!width="50%"|Parameter
! width="50%" | Parameter
!width="50%"|Description
! width="50%" | Description
|-
|'''sectionsInherits'''
|inherit sections (= selections) from given glass.
|-
|-
|'''sections[]'''
| '''sectionsInherits'''
|define your sections here.
| inherit sections (= selections) from given glass.
|-
|-
|'''skeletonName'''
| '''sections[]'''
|class name of skeleton used by this model.
| define your sections here.
|-
|-
|'''class Animations {}'''
| '''skeletonName'''
|subclass which defines the animations of your model.
| class name of skeleton used by this model.
|-
|-
| '''class Animations {}'''
| subclass which defines the animations of your model.
|}
|}


=== Sections ===
=== Sections ===
A section is the same as a selection: a part of the model which may be animated or changed via the setObjectTexture command. To define selections in your modelclass make use of the section[]-array. This array is an unordered list of all selections you want to use in the ways described above.
A section is the same as a selection: a part of the model which may be animated or changed via the setObjectTexture command. To define selections in your modelclass make use of the section[]-array. This array is an unordered list of all selections you want to use in the ways described above.


==== Example ====
==== Example ====
<syntaxhighlight lang=cpp>sections[]=
<syntaxhighlight lang="cpp">
sections[] =
{
{
"mainRotor","mainRotor_static","mainRotor_blur","mainRotor_dive",
"mainRotor", "mainRotor_static", "mainRotor_blur", "mainRotor_dive",
"tailRotor","tailRotor_static","tailRotor_blur","tailRotor_dive",
"tailRotor", "tailRotor_static", "tailRotor_blur", "tailRotor_dive",


"turret_RMK_x","turret_RMK_y"
"turret_RMK_x", "turret_RMK_y"
};</syntaxhighlight>
};
</syntaxhighlight>


=== Animations ===
=== Animations ===
To define animations for your model, you have to make use of the class animations in your modelclass.
To define animations for your model, you have to make use of the class animations in your modelclass.
Each animation is a subclass in the class animations with a userdefinable name and consists of the following parameters:
Each animation is a subclass in the class animations with a userdefinable name and consists of the following parameters:


{|border="1" cellpadding="3" cellspacing="0"  
{| class="wikitable"
!bgcolor="#ddddff" |Parameter
! Parameter
!bgcolor="#ddddff" |Description
! Description
|-
|-
|'''type'''
| '''type'''
|the type of the animation, e.g. rotating or translation. Refer to [[Model_Config#Animation_types|Model Config]] for a list of all animation types.
| the type of the animation, e.g. rotating or translation. Refer to [[Model_Config#Animation_types|Model Config]] for a list of all animation types.
|-
|-
|'''source'''
| '''source'''
|The source used to animate the selection. Refer to [[Model_Config#Animation_sources|Model Config]] for a list of all sources.
| The source used to animate the selection. Refer to [[Model_Config#Animation_sources|Model Config]] for a list of all sources.
|-
|-
|'''selection'''
| '''selection'''
|The name of the bone (= selection) you want to animate. Has to be defined in the cfgSkeletons class.
| The name of the bone (= selection) you want to animate. Has to be defined in the cfgSkeletons class.
|-
|-
|'''axis'''
| '''axis'''
|The name of the axis you want to use (only necessary for types ''rotation'' and ''translation'').
| The name of the axis you want to use (only necessary for types ''rotation'' and ''translation'').
|-
|-
|'''memory'''
| '''memory'''
|If using an own axis (by the axis-parameter) use value 1 if axis is located in the memory lod of your model or 0 if the axis is located in the lod (or better: every lod) where your animated selection is used.
| If using an own axis (by the axis-parameter) use value 1 if axis is located in the memory lod of your model or 0 if the axis is located in the lod (or better: every lod) where your animated selection is used.
|-
|-
|'''sourceAdress'''
| '''sourceAdress'''
|Use "loop" if you want your animation to "loop" (e.g. on wheels) or "clamp" if you want your animation to stop at a specific angle (e.g. on the steering wheel of a car).
| Use "loop" if you want your animation to "loop" (e.g. on wheels) or "clamp" if you want your animation to stop at a specific angle (e.g. on the steering wheel of a car).
|-
|-
|'''minValue'''
| '''minValue'''
|If source returns a value <= minValue, the animation is animated with angle0 (see below)
| If source returns a value <= minValue, the animation is animated with angle0 (see below)
|-
|-
|'''maxValue'''
| '''maxValue'''
|If source returns a value >= maxValue, the animation is animated with angle1 (see below)
| If source returns a value >= maxValue, the animation is animated with angle1 (see below)
|-
|-
|'''angle0'''
| '''angle0'''
|The angle the selection is animated when minValue is reached.
| The angle the selection is animated when minValue is reached.
|-
|-
|'''angle1'''
| '''angle1'''
|The angle the selection is animated when maxValue is reached.
| The angle the selection is animated when maxValue is reached.
|-
|-
|valign="top"|'''offset0'''
| valign="top"|'''offset0'''
|The distance the bone is moved when using type="Translation" and minValue is returned. Distance is calculated by the distance of axis' vertices. If the have a distance of 1m and you are using offset0 = 1, the bone will be moved by 1m. If using offset0=0.5, the bone will be moved by 0.5m, etc.
| The distance the bone is moved when using type="Translation" and minValue is returned. Distance is calculated by the distance of axis' vertices. If the have a distance of 1m and you are using offset0 = 1, the bone will be moved by 1m. If using offset0=0.5, the bone will be moved by 0.5m, etc.
|-
|-
|'''offset1'''
| '''offset1'''
|Same as offset0, but when maxValue is returned.
| Same as offset0, but when maxValue is returned.
|-
|-
|}
|}
Line 202: Line 218:
==== Attention ====
==== Attention ====
I am assuming that each source is returning a value between 0 and 1 (the ''animationPhase'') which is used to animate your selection. Some may even return a value from -1 to 1:
I am assuming that each source is returning a value between 0 and 1 (the ''animationPhase'') which is used to animate your selection. Some may even return a value from -1 to 1:
*If using source="speed", the source is returning 0 when the vehicle is not moving and 1 when the vehicle is moving at maximum speed.
* If using <syntaxhighlight lang="cpp" inline>source = "speed"</syntaxhighlight>, the source is returning 0 when the vehicle is not moving and 1 when the vehicle is moving at maximum speed.
*If using source="drivingWheel", the source is returning -1 when the vehicle is turning left, 0 when the vehicle is not turning and 1 when the vehicle is turning right.
* If using <syntaxhighlight lang="cpp" inline>source = "drivingWheel"</syntaxhighlight>, the source is returning -1 when the vehicle is turning left, 0 when the vehicle is not turning and 1 when the vehicle is turning right.


''On turrets, it is necessary that the classnames of the animations match the according turret selection names.<br>''
''On turrets, it is necessary that the classnames of the animations match the according turret selection names.<br>''
Line 210: Line 226:


==== Example ====
==== Example ====
<syntaxhighlight lang=cpp>class mainRotor
<syntaxhighlight lang="cpp">
class mainRotor
{
{
type="rotationY"; //rotation around the Y axis
type = "rotationY"; // rotation around the Y axis
source="rotorH";
source = "rotorH";
selection="mainRotor";
selection = "mainRotor";
axis=""; //no own axis, use centre of selection
axis = ""; // no own axis, use centre of selection
memory=1;
memory = 1;
sourceAddress="loop";
sourceAddress = "loop";
minValue=0;
minValue = 0;
maxValue=1;
maxValue = 1;
angle0=0;
angle0 = 0;
angle1="rad -360";
angle1 = "rad -360";
};</syntaxhighlight>
};
</syntaxhighlight>


==== Axes ====
==== Axes ====
{|
{|
|valign="top"|Since the axes seem a bit messed up when using ''rotationX'', ''rotationY'', ''rotationZ'', ''translationX'', ''translationY'', ''translationZ'', here is a small image of how the axes are arranged.
| valign="top" | Since the axes seem a bit messed up when using ''rotationX'', ''rotationY'', ''rotationZ'', ''translationX'', ''translationY'', ''translationZ'', here is a small image of how the axes are arranged.


Keep in mind that this image shows only the direction of these axes and ''not'' their position.
Keep in mind that this image shows only the direction of these axes and ''not'' their position.
The position is always defined by the center of your animated selection.
The position is always defined by the center of your animated selection.


As a short examle, the wheels would need "rotationX" as animation type.
As a short example, the wheels would need "rotationX" as animation type.
|[[Image:Howtoanimmodel axes.jpg]]
| [[File:Howtoanimmodel axes.jpg]]
|-
|}
|}


Line 239: Line 256:


==== cfgSkeletons ====
==== cfgSkeletons ====
*Binarise gets confused if your cfgSkeletons classname is same as your cfgModels.
* Binarise gets confused if your cfgSkeletons classname is same as your cfgModels.
**Declare your skeleton class as MyModelSkeleton vs MyModel
** Declare your skeleton class as MyModelSkeleton vs MyModel
*Similar to any other classes in any standard config.cpp of any pbo, it is possible, albeit less likely, to have same class(es) over declared in multiple folders. This is typical if you are running more than one project on the p: drive. Far better, you always use an OFPEC_TAG name
* Similar to any other classes in any standard config.cpp of any pbo, it is possible, albeit less likely, to have same class(es) over declared in multiple folders. This is typical if you are running more than one project on the p: drive. Far better, you always use an OFPEC_TAG name


<syntaxhighlight lang=cpp>class OFPEC_TAG_MyModelSkeleton</syntaxhighlight>
<syntaxhighlight lang="cpp">class OFPEC_TAG_MyModelSkeleton</syntaxhighlight>




*Declaring non-existent bones in a skeleton is 'ok'
* Declaring non-existent bones in a skeleton is 'ok'


Typically, you will have a base class skeleton (such as a 'vehicle') from which, you derive a tank.
Typically, you will have a base class skeleton (such as a 'vehicle') from which, you derive a tank.
Line 255: Line 272:


==== cfgModels ====
==== cfgModels ====
* sections[] = have nothing to do with this document. They are a list of NamedSelections, from which, [[setObjectTexture]] can be used in-game. They are part of hiddenselections[]= and are, confusingly, not related to skeletons and animations. If a non-existent NamedSelection is, in fact, listed in this segment, it is ignored, and not present in the binary model.


*sections[]= have nothing to do with this document. They are a list of NamedSelections, from which, [[setObjectTexture]] can be used in-game. They are part of hiddenselections[]= and are, confusingly, not related to skeletons and animations. If a non-existent NamedSelection is, in fact, listed in this segment, it is ignored, and not present in the binary model.
* source= is the ANIMATION CLASS name. Often called 'controller'
* selection= is the BONE NAME in cfgSkeletons
* axis= is an axis. it is rarely declared as a bone, it rarely appears in sections[]=. It often appears in the NamedSelections list of the model.


*source= is the ANIMATION CLASS name. Often called 'controller'
* When creating class animation{... it is ok to declare non existent bones.
*selection= is the BONE NAME in cfgSkeletons
*axis= is an axis. it is rarely declared as a bone, it rarely appears in sections[]=. It often appears in the NamedSelections list of the model.
 
*When creating class animation{...   it is ok to declare non existent bones.


This is very prevalent, very common, and very 'ok' when you have a generalised base class that deals with four standard doors, only one of which appears in a *specific* model.
This is very prevalent, very common, and very 'ok' when you have a generalised base class that deals with four standard doors, only one of which appears in a *specific* model.
<syntaxhighlight lang=cpp>
<syntaxhighlight lang="cpp">
  class WideDoor{...
class WideDoor { //...
  class FrontDoor{...
class FrontDoor { // ...
  class BackDoor{...
class BackDoor { // ...
  class EmergencyExit{....
class EmergencyExit{ // ...
</syntaxhighlight>
</syntaxhighlight>
Highly complex models such as aircraft '''frequently''' have multiple compass, rpm, altimeter and other classes in their base classes which may, or may not be present in THE model.
Highly complex models such as aircraft '''frequently''' have multiple compass, rpm, altimeter and other classes in their base classes which may, or may not be present in THE model.
Line 278: Line 294:


=== User Animation ===
=== User Animation ===
<syntaxhighlight lang=cpp>
 
class Rotation; //very standard, most often defined in the first model.cfg of the p:tree
<syntaxhighlight lang="cpp">
class Rotation; // very standard, most often defined in the first model.cfg of the p:tree
cfgModels
 
{
cfgModels
  class InheritedFromSomething //most often defined in the first model.cfg of the p:tree
{
  {
class InheritedFromSomething // most often defined in the first model.cfg of the p:tree
      class Animations;
{
  };
class Animations;
  class PinkElephant:InheritedFromSomething// this is for the PinkElephant.p3d and no other
};
  {
class PinkElephant : InheritedFromSomething // this is for the PinkElephant.p3d and no other
    class Animations:Animations // bring in all the base class classes
{
    {
class Animations : Animations // bring in all the base class classes
        class OFPEC_TAG_MyPinkElephant_SlidingDoor:Rotation
{
        {
class OFPEC_TAG_MyPinkElephant_SlidingDoor : Rotation
          source = SomeClassName;// defined in config.cpp
{
          animPeriod=1;
source = SomeClassName; // defined in config.cpp
          selection=SomeBoneName;
animPeriod = 1;
          axis=SomeAxis;
selection = SomeBoneName;
          angle1=rad 85;
axis = SomeAxis;
        };
angle1 = rad 85;
    };
};
  };
};
  };
};
};
};
</syntaxhighlight>
</syntaxhighlight>


config.cpp
config.cpp
<syntaxhighlight lang=cpp>
<syntaxhighlight lang="cpp">
class CfgVehicles
class CfgVehicles
{
{
  class SomeBaseClass
class SomeBaseClass
  {
{
    class AnimationSources
class AnimationSources
    {
{
    class UserActions; // maybe
class UserActions; // maybe
    };
};
  };
};
  class OFPEC_TAG_MyPinkElephant:SomeBaseClass
class OFPEC_TAG_MyPinkElephant : SomeBaseClass
  {
{
    .....
// ...
    model=PinkElephant;
model = PinkElephant;
    class AnimationSources:AnimationSources
class AnimationSources : AnimationSources
    {
{
      class OFPEC_TAG_MyPinkElephant_SlidingDoor /// <<<<<THE name used in THE cfgModels for THIS p3d
class OFPEC_TAG_MyPinkElephant_SlidingDoor /// <<<<<THE name used in THE cfgModels for THIS p3d
      {
{
        source = "user";
source = "user";
        animPeriod = 1;
animPeriod = 1;
        initPhase = 0;
initPhase = 0;
      };
};
  class UserActions :UserActions
  {
    class OFPEC_TAG_OpenDoor1// ensure no possibilty of a clash with base names (if any)
    {
    displayName = "Open Sliding door";
    onlyforplayer = "true";
    position = "SomeAxis";// THE axis declared in the model
    radius = 18100;
    condition = "this animationPhase ""OFPEC_TAG_MyPinkElephant_SlidingDoor"" < 0.5 ";
    statement = "this animate [""OFPEC_TAG_MyPinkElephant_SlidingDoor"", 1]; this say ""OFPEC_TAG_ElephantOpenSound""";
    };
    class OFPEC_TAG_CloseDoor1:OFPEC_TAG_OpenDoor1
    {
    displayName = "Close Sliding door";
    condition = "this animationPhase ""OFPEC_TAG_MyPinkElephant_SlidingDoor"" >= 0.5 ";
    statement = "this animate [""OFPEC_TAG_MyPinkElephant_SlidingDoor"", 0]; this say ""OFPEC_TAG_ElephantCloseSound""";
    };
  };//endof useractions
  };//endof animations
};//endof PinkElephant
};// endof cfgVehicles


class CfgSounds  
class UserActions : UserActions
{
{
  class OFPEC_TAG_ElephantOpenSound// <<< make sure no other class could possibly called this
class OFPEC_TAG_OpenDoor1 // ensure no possibilty of a clash with base names (if any)
  {
{
    name = "whatever";
displayName = "Open Sliding door";
    sound[] = {"\some\stupid\hard\path\somesound.ogg", 1, 1};
onlyforplayer = "true";
    titles[] = {};
position = "SomeAxis"; // THE axis declared in the model
  };
radius = 18100;
condition = "this animationPhase ""OFPEC_TAG_MyPinkElephant_SlidingDoor"" < 0.5";
  class OFPEC_TAG_ElephantCloseSound:OFPEC_TAG_ElephantOpenSound
statement = "this animate [""OFPEC_TAG_MyPinkElephant_SlidingDoor"", 1]; this say ""OFPEC_TAG_ElephantOpenSound""";
  {
};
    name = "think of a number";
class OFPEC_TAG_CloseDoor1 : OFPEC_TAG_OpenDoor1
    sound[] = {"\some\stupid\hard\path\someOthersound", 1, 1};// wss equivalent
{
  };
displayName = "Close Sliding door";
};
condition = "this animationPhase ""OFPEC_TAG_MyPinkElephant_SlidingDoor"" >= 0.5";
statement = "this animate [""OFPEC_TAG_MyPinkElephant_SlidingDoor"", 0]; this say ""OFPEC_TAG_ElephantCloseSound""";
};
};
};
};
};
 
class CfgSounds
{
class OFPEC_TAG_ElephantOpenSound // <<< make sure no other class could possibly called this
{
name = "whatever";
sound[] = { "\some\stupid\hard\path\somesound.ogg", 1, 1 };
titles[] = {};
};
 
class OFPEC_TAG_ElephantCloseSound : OFPEC_TAG_ElephantOpenSound
{
name = "think of a number";
sound[] = { "\some\stupid\hard\path\someOthersound", 1, 1 }; // wss equivalent
};
};
</syntaxhighlight>
</syntaxhighlight>


=== cfgModels example ===
=== cfgModels example ===
<syntaxhighlight lang=cpp>class cfgModels
 
<syntaxhighlight lang="cpp">
class cfgModels
{
{
class bwmod_tiger
class bwmod_tiger
{
{
sectionsInherit="";
sectionsInherit = "";
sections[]=
sections[]=
{
{
"mainRotor","mainRotor_static","mainRotor_blur","mainRotor_dive",
"mainRotor", "mainRotor_static", "mainRotor_blur", "mainRotor_dive",
"tailRotor","tailRotor_static","tailRotor_blur","tailRotor_dive",
"tailRotor", "tailRotor_static", "tailRotor_blur", "tailRotor_dive",


"turret_RMK_x","turret_RMK_y"
"turret_RMK_x", "turret_RMK_y"
};
};


skeletonName="BWMod_Tiger_Skeleton";
skeletonName = "BWMod_Tiger_Skeleton";


class Animations
class Animations
Line 388: Line 406:
class mainRotor
class mainRotor
{
{
type="rotationY";
type = "rotationY";
source="rotorH";
source = "rotorH";
selection="mainRotor";
selection = "mainRotor";
axis="";
axis = "";
memory=1;
memory = 1;
sourceAddress="loop";
sourceAddress = "loop";
minValue=0;
minValue = 0;
maxValue=1;
maxValue = 1;
angle0=0;
angle0 = 0;
angle1="rad -360";
angle1 = "rad -360";
};
};


class tailRotor
class tailRotor
{
{
type="rotationX";
type = "rotationX";
source="rotorV";
source = "rotorV";
selection="tailRotor";
selection = "tailRotor";
axis="";
axis = "";
memory=1;
memory = 1;
sourceAddress="loop";
sourceAddress = "loop";
minValue=0;
minValue = 0;
maxValue=1;
maxValue = 1;
angle0=0;
angle0 = 0;
angle1="rad -360";
angle1 = "rad -360";
};
};


class wheelL
class wheelL
{
{
type="translation";
type = "translation";
source="altRadar"; //using altRadar, since damper doesn't seem to work on  
source = "altRadar"; // using altRadar, since damper doesn't seem to work on
                    //helicopters, even though [[Model_Config]] states something
// helicopters, even though [[Model_Config]] states something else.
                    //else.
selection = "wheelL";
selection="wheelL";
axis = "axis_damper"; // vertical axis, vertex distance 1 m
axis="axis_damper"; //vertical axis, vertex distance 1 m
memory = 0;
memory=0;
animPeriod = 0;
animPeriod=0;
minValue = 0;
minValue=0;
maxValue = 0.05; // max value 0.05m above ground
maxValue=0.05;     //max value 0.05m above ground
offset0 = 0;
offset0=0;
offset1=-0.05; // animate wheels downwards for 0.05m when maxValue is reached.
offset1=-0.05;     //animate wheels downwards for 0.05m when maxValue is reached.
};
};


class wheelR
class wheelR
{
{
type="translation";
type = "translation";
source="altRadar";
source = "altRadar";
selection="wheelR";
selection = "wheelR";
axis="axis_damper";
axis = "axis_damper";
memory=0;
memory = 0;
animPeriod=0;
animPeriod = 0;
minValue=0;
minValue = 0;
maxValue=0.05;
maxValue = 0.05;
offset0=0;
offset0 = 0;
offset1=-0.05;
offset1=-0.05;
};
};


class turret_RMK_x //the horizontal moving part of the turret
class turret_RMK_x // the horizontal moving part of the turret
{
{
type="rotationY";
type = "rotationY";
source="mainTurret";
source = "mainTurret";
selection="turret_RMK_x";
selection = "turret_RMK_x";
axis="axis_turret_RMK_x";
axis = "axis_turret_RMK_x";
animPeriod=0;
animPeriod = 0;
memory=1;
memory = 1;
minValue="rad -360";
minValue = "rad -360";
maxValue="rad +360";
maxValue = "rad +360";
angle0="rad -360";
angle0 = "rad -360";
angle1="rad +360";
angle1 = "rad +360";
};
};


class turret_RMK_y //the vertical moving part of the turret
class turret_RMK_y // the vertical moving part of the turret
{
{
type="rotationX";
type = "rotationX";
source="mainGun";
source = "mainGun";
selection="turret_RMK_y";
selection = "turret_RMK_y";
axis="axis_turret_RMK_y";
axis = "axis_turret_RMK_y";
animPeriod=0;
animPeriod = 0;
memory=1;
memory = 1;
minValue="rad -360";
minValue = "rad -360";
maxValue="rad +360";
maxValue = "rad +360";
angle0="rad -360";
angle0 = "rad -360";
angle1="rad +360";
angle1 = "rad +360";
};
};
};
};
};
};
};</syntaxhighlight>
};
</syntaxhighlight>
 


{{GameCategory|arma1|Editing Tutorials}}
{{GameCategory|arma1|Editing Tutorials}}

Latest revision as of 10:58, 6 December 2023

Attention: This article is a tutorial on how to animate parts of your model (e.g. wheels, rotors, etc.). Take all information of this article with a pinch of salt: everything is based on experiments and has not been confirmed nor documented by BIS, yet.

To animate a model, you have to make use of both, the cfgModels and cfgSkeletons class.

The cfgSkeletons class defines the bones of a vehicle. Bones are, more or less, the animated selections of a model.

The cfgModels class is an extended version of the OFP cfgModels class. It defines the selections of a model which you want to animate or use with the setObjectTexture command, but since ArmA, you have to put everything related to animate your model in here.

Model preparation: Besides adding selections (same as in OFP but in ArmA bone selections should never overlap) you also need to add a following named property to your first resolution LOD (press Alt + P to open named properties window):

property name: autocenter
value: 0

If your model has Geometry LOD the above needs to be also done for your Geometry LOD. This property stopps engine from shifting the animation axes.

If you encounter a bug when your weapon bones fall apart after you dropp it to the ground the reason might be lack of Geometry or View - pilot LODs.


model.cfg

According to the article about Model Config, the cfgSkeletons and cfgModels class should be part of a model.cfg file which is located in the addon pbo file.
However, if you use the model.cfg file, you always need to binarize your addon - otherwise, your animations won't work.
Another option is to add both classes (cfgSkeletons and cfgModels) to the config.cpp file of your addon.
This way, you do not need to binarize your addon, so it is a perfect way to test it. However, the first method is prefered and should always be used when releasing your addon.


cfgSkeletons

The cfgSkeletons class defines, as mentioned before, the bones (= animated selections) of a vehicle.

Each skeleton is a subclass within the cfgSkeletons class, consisting of three parameters:

Parameter Description
isDiscrete currently unknown, set to 1.
skeletonInherit inherit bones from given class.
skeletonBones[] define your own bones here.

Defining a bone

Bones are defined in the skeletonBones[]-array which is made of a list of unsorted bones. Each bone is the name of a selection you want to animate.

A single bone

A bone is defined by using two strings:

"bone", ""

You may define multiple bones by strining them togeter.

Attention: Do not leave out the second string as this will lead to errors! (see linked bones for more information on the second string argument)

Example
skeletonBones[]=
{
	"bone1", "",	// defines bone1
	"bone2", ""		// defines bone2
};

Linked bones

The second argument (empty in the example above) is used for linking two bones:

"bone1", "bone2"

Linking is used to make the animation of "bone1" depending on the movement of "bone2". If you e.g. have a turret, you have to make use of linking here, because the up and down movement of the turrets weapon is typically influenced by left and right movement of the turret:

Example
skeletonBones[] =
{
	"turret_x", "",			// defines bone turret_x
	"turret_y", "turret_x"	// defines bone turret_y and makes it linked to bone turret_x
};

Attention:
You can not link more than two bones in a row!
If you do something like

	"bone1", "bone2", "bone3"

this will result in an error, as Armed Assault interprets this as

  • defining "bone1" which is linked to "bone2"
  • defining "bone3", which misses the second argument.

However, it should be possible to use a syntax like this (not tested yet):

skeletonBones[]=
{
	"bone1", "bone2",	// defines bone "bone1" and makes it linked to "bone2"
	"bone2", "bone3",	// defines bone "bone2" and makes it linked to "bone3"
	"bone3", ""			// defines bone "bone3".
};

In conclusion, "bone1" is linked to "bone2", which is linked to "bone3".
So "bone1" should be depending on the movement of both, "bone2" and "bone3".

cfgSkeletons Example

class cfgSkeletons
{
	class BWMod_Tiger_Skeleton
	{
		isDiscrete = 1;

		skeletonInherit = "";
		skeletonBones[] =
		{
			"wheelL", "",
			"wheelR", "",

			"turret_RMK_x", "",
			"turret_RMK_y", "turret_RMK_x",

			"turret_OSIRIS_y", "",
			"turret_OSIRIS_x", "turret_OSIRIS_y"
		};
	};
};

cfgModels

The cfgModels class is used to declare the selections of a model you want to animate or access via the setObjectTexture command. Since ArmA, the cfgModels class has been extended an is now used to define all animations of a model.

Each of your model is a subclass inside of the cfgModels class and the models filename is used as the name of your class (without .p3d). E.g. your model p3d is named "myVehicle.p3d" your class is "class myvehicle {}". We will call them "modelclasses" in this article for the sake of simplicity.

Each modelclass consists of three parameters and an additional subclass which defines the animations of your models:

Parameter Description
sectionsInherits inherit sections (= selections) from given glass.
sections[] define your sections here.
skeletonName class name of skeleton used by this model.
class Animations {} subclass which defines the animations of your model.

Sections

A section is the same as a selection: a part of the model which may be animated or changed via the setObjectTexture command. To define selections in your modelclass make use of the section[]-array. This array is an unordered list of all selections you want to use in the ways described above.

Example

sections[] =
{
	"mainRotor", "mainRotor_static", "mainRotor_blur", "mainRotor_dive",
	"tailRotor", "tailRotor_static", "tailRotor_blur", "tailRotor_dive",

	"turret_RMK_x", "turret_RMK_y"
};

Animations

To define animations for your model, you have to make use of the class animations in your modelclass. Each animation is a subclass in the class animations with a userdefinable name and consists of the following parameters:

Parameter Description
type the type of the animation, e.g. rotating or translation. Refer to Model Config for a list of all animation types.
source The source used to animate the selection. Refer to Model Config for a list of all sources.
selection The name of the bone (= selection) you want to animate. Has to be defined in the cfgSkeletons class.
axis The name of the axis you want to use (only necessary for types rotation and translation).
memory If using an own axis (by the axis-parameter) use value 1 if axis is located in the memory lod of your model or 0 if the axis is located in the lod (or better: every lod) where your animated selection is used.
sourceAdress Use "loop" if you want your animation to "loop" (e.g. on wheels) or "clamp" if you want your animation to stop at a specific angle (e.g. on the steering wheel of a car).
minValue If source returns a value <= minValue, the animation is animated with angle0 (see below)
maxValue If source returns a value >= maxValue, the animation is animated with angle1 (see below)
angle0 The angle the selection is animated when minValue is reached.
angle1 The angle the selection is animated when maxValue is reached.
offset0 The distance the bone is moved when using type="Translation" and minValue is returned. Distance is calculated by the distance of axis' vertices. If the have a distance of 1m and you are using offset0 = 1, the bone will be moved by 1m. If using offset0=0.5, the bone will be moved by 0.5m, etc.
offset1 Same as offset0, but when maxValue is returned.

Attention

I am assuming that each source is returning a value between 0 and 1 (the animationPhase) which is used to animate your selection. Some may even return a value from -1 to 1:

  • If using source = "speed", the source is returning 0 when the vehicle is not moving and 1 when the vehicle is moving at maximum speed.
  • If using source = "drivingWheel", the source is returning -1 when the vehicle is turning left, 0 when the vehicle is not turning and 1 when the vehicle is turning right.

On turrets, it is necessary that the classnames of the animations match the according turret selection names.
So if you're mainTurret selection is named "turret_RMK_x" you have to name your class "turret_RMK_y", too. The same goes for the mainGun selection.

Example

class mainRotor
{
	type = "rotationY"; // rotation around the Y axis
	source = "rotorH";
	selection = "mainRotor";
	axis = ""; // no own axis, use centre of selection
	memory = 1;
	sourceAddress = "loop";
	minValue = 0;
	maxValue = 1;
	angle0 = 0;
	angle1 = "rad -360";
};

Axes

Since the axes seem a bit messed up when using rotationX, rotationY, rotationZ, translationX, translationY, translationZ, here is a small image of how the axes are arranged.

Keep in mind that this image shows only the direction of these axes and not their position. The position is always defined by the center of your animated selection.

As a short example, the wheels would need "rotationX" as animation type.

Howtoanimmodel axes.jpg

Rules Of Engagement

cfgSkeletons

  • Binarise gets confused if your cfgSkeletons classname is same as your cfgModels.
    • Declare your skeleton class as MyModelSkeleton vs MyModel
  • Similar to any other classes in any standard config.cpp of any pbo, it is possible, albeit less likely, to have same class(es) over declared in multiple folders. This is typical if you are running more than one project on the p: drive. Far better, you always use an OFPEC_TAG name
class OFPEC_TAG_MyModelSkeleton


  • Declaring non-existent bones in a skeleton is 'ok'

Typically, you will have a base class skeleton (such as a 'vehicle') from which, you derive a tank.

Typically, all vehicles have number plates (vez, spz). Some, don't.

It is 'ok' to format for the general case. The only effect is that this non-existent bone will appear in the binary p3d as part of the skeleton list. No harm done.

cfgModels

  • sections[] = have nothing to do with this document. They are a list of NamedSelections, from which, setObjectTexture can be used in-game. They are part of hiddenselections[]= and are, confusingly, not related to skeletons and animations. If a non-existent NamedSelection is, in fact, listed in this segment, it is ignored, and not present in the binary model.
  • source= is the ANIMATION CLASS name. Often called 'controller'
  • selection= is the BONE NAME in cfgSkeletons
  • axis= is an axis. it is rarely declared as a bone, it rarely appears in sections[]=. It often appears in the NamedSelections list of the model.
  • When creating class animation{... it is ok to declare non existent bones.

This is very prevalent, very common, and very 'ok' when you have a generalised base class that deals with four standard doors, only one of which appears in a *specific* model.

class WideDoor {		//...
class FrontDoor {		// ...
class BackDoor {		// ...
class EmergencyExit{	// ...

Highly complex models such as aircraft frequently have multiple compass, rpm, altimeter and other classes in their base classes which may, or may not be present in THE model.

If the bone is not in cfgSkeletons, the cfgModel class is entirely ignored during the binarisation process.

If the bone IS declared in the skeleton but doesn't actually exist in the model. The cfgModel class IS binarised (probably not what you want to happen).

User Animation

class Rotation; // very standard, most often defined in the first model.cfg of the p:tree

cfgModels
{
	class InheritedFromSomething // most often defined in the first model.cfg of the p:tree
	{
		class Animations;
	};
	class PinkElephant : InheritedFromSomething // this is for the PinkElephant.p3d and no other
	{
		class Animations : Animations // bring in all the base class classes
		{
			class OFPEC_TAG_MyPinkElephant_SlidingDoor : Rotation
			{
				source = SomeClassName; // defined in config.cpp
				animPeriod = 1;
				selection = SomeBoneName;
				axis = SomeAxis;
				angle1 = rad 85;
			};
		};
	};
};

config.cpp

class CfgVehicles
{
	class SomeBaseClass
	{
		class AnimationSources
		{
			class UserActions; // maybe
		};
	};
	class OFPEC_TAG_MyPinkElephant : SomeBaseClass
	{
		// ...
		model = PinkElephant;
		class AnimationSources : AnimationSources
		{
			class OFPEC_TAG_MyPinkElephant_SlidingDoor /// <<<<<THE name used in THE cfgModels for THIS p3d
			{
				source = "user";
				animPeriod = 1;
				initPhase = 0;
			};

			class UserActions : UserActions
			{
				class OFPEC_TAG_OpenDoor1 // ensure no possibilty of a clash with base names (if any)
				{
					displayName = "Open Sliding door";
					onlyforplayer = "true";
					position = "SomeAxis"; // THE axis declared in the model
					radius = 18100;
					condition = "this animationPhase ""OFPEC_TAG_MyPinkElephant_SlidingDoor"" < 0.5";
					statement = "this animate [""OFPEC_TAG_MyPinkElephant_SlidingDoor"", 1]; this say ""OFPEC_TAG_ElephantOpenSound""";
				};
				class OFPEC_TAG_CloseDoor1 : OFPEC_TAG_OpenDoor1
				{
					displayName = "Close Sliding door";
					condition = "this animationPhase ""OFPEC_TAG_MyPinkElephant_SlidingDoor"" >= 0.5";
					statement = "this animate [""OFPEC_TAG_MyPinkElephant_SlidingDoor"", 0]; this say ""OFPEC_TAG_ElephantCloseSound""";
				};
			};
		};
	};
};

class CfgSounds
{
	class OFPEC_TAG_ElephantOpenSound // <<< make sure no other class could possibly called this
	{
		name = "whatever";
		sound[] = { "\some\stupid\hard\path\somesound.ogg", 1, 1 };
		titles[] = {};
	};

	class OFPEC_TAG_ElephantCloseSound : OFPEC_TAG_ElephantOpenSound
	{
		name = "think of a number";
		sound[] = { "\some\stupid\hard\path\someOthersound", 1, 1 }; // wss equivalent
	};
};

cfgModels example

class cfgModels
{
	class bwmod_tiger
	{
		sectionsInherit = "";
		sections[]=
		{
			"mainRotor", "mainRotor_static", "mainRotor_blur", "mainRotor_dive",
			"tailRotor", "tailRotor_static", "tailRotor_blur", "tailRotor_dive",

			"turret_RMK_x", "turret_RMK_y"
		};

		skeletonName = "BWMod_Tiger_Skeleton";

		class Animations
		{
			class mainRotor
			{
				type = "rotationY";
				source = "rotorH";
				selection = "mainRotor";
				axis = "";
				memory = 1;
				sourceAddress = "loop";
				minValue = 0;
				maxValue = 1;
				angle0 = 0;
				angle1 = "rad -360";
			};

			class tailRotor
			{
				type = "rotationX";
				source = "rotorV";
				selection = "tailRotor";
				axis = "";
				memory = 1;
				sourceAddress = "loop";
				minValue = 0;
				maxValue = 1;
				angle0 = 0;
				angle1 = "rad -360";
			};

			class wheelL
			{
				type = "translation";
				source = "altRadar";	// using altRadar, since damper doesn't seem to work on
									// helicopters, even though [[Model_Config]] states something else.
				selection = "wheelL";
				axis = "axis_damper"; // vertical axis, vertex distance 1 m
				memory = 0;
				animPeriod = 0;
				minValue = 0;
				maxValue = 0.05;		// max value 0.05m above ground
				offset0 = 0;
				offset1=-0.05;		// animate wheels downwards for 0.05m when maxValue is reached.
			};

			class wheelR
			{
				type = "translation";
				source = "altRadar";
				selection = "wheelR";
				axis = "axis_damper";
				memory = 0;
				animPeriod = 0;
				minValue = 0;
				maxValue = 0.05;
				offset0 = 0;
				offset1=-0.05;
			};

			class turret_RMK_x // the horizontal moving part of the turret
			{
				type = "rotationY";
				source = "mainTurret";
				selection = "turret_RMK_x";
				axis = "axis_turret_RMK_x";
				animPeriod = 0;
				memory = 1;
				minValue = "rad -360";
				maxValue = "rad +360";
				angle0 = "rad -360";
				angle1 = "rad +360";
			};

			class turret_RMK_y // the vertical moving part of the turret
			{
				type = "rotationX";
				source = "mainGun";
				selection = "turret_RMK_y";
				axis = "axis_turret_RMK_y";
				animPeriod = 0;
				memory = 1;
				minValue = "rad -360";
				maxValue = "rad +360";
				angle0 = "rad -360";
				angle1 = "rad +360";
			};
		};
	};
};