• Which the release of FS2020 we see an explosition of activity on the forun and of course we are very happy to see this. But having all questions about FS2020 in one forum becomes a bit messy. So therefore we would like to ask you all to use the following guidelines when posting your questions:

    • Tag FS2020 specific questions with the MSFS2020 tag.
    • Questions about making 3D assets can be posted in the 3D asset design forum. Either post them in the subforum of the modelling tool you use or in the general forum if they are general.
    • Questions about aircraft design can be posted in the Aircraft design forum
    • Questions about airport design can be posted in the FS2020 airport design forum. Once airport development tools have been updated for FS2020 you can post tool speciifc questions in the subforums of those tools as well of course.
    • Questions about terrain design can be posted in the FS2020 terrain design forum.
    • Questions about SimConnect can be posted in the SimConnect forum.

    Any other question that is not specific to an aspect of development or tool can be posted in the General chat forum.

    By following these guidelines we make sure that the forums remain easy to read for everybody and also that the right people can find your post to answer it.

MSFS Blender bones animations interesting behaviour with two animations applied.

Messages
1,072
Country
australia
I followed this video about animating the fabric cover over a control column (so it has to respond to two animations, control stick fore/aft and control stick left/right).


One thing I noticed though is that the animation of the bones only travels half the rotation distance that I have keyframed.

I made a little test to see if I wasn't imagining things. Here are 3 tubes, red, black and green. The red and green tubes have a single animation assigned to them (red tube is a single bone, green tube is two bones, a very very short base bone that doesn't move and a longer animated bone) while the black tube is set up like the control column with two animations (same two bone structure as the green tube). I placed little white boxes at the limits of the animation to help with checking. Each tube references the same animation code.

The green and red tubes animated as expected.

But the black tube doesn't reach it's little white cube. It's stopped half way short of it's expected range. It seems as though when you have more than one animation assigned to a bone that MSFS halves the effect of each animation.

Does anyone have any thoughts on this? Is this just a quirk of MSFS or is there a workaround? Perhaps an exporter update (it's been a while since I last updated the Asobo exporter for Blender)? Or will I just have to learn to deal with this quirk by doubling the range of any animations with two inputs?

Image1.jpg
 
A bit more experimenting and it definitely seems like the number of animations you have attached to a bone affects how much it will move. I added 3 animations to the green tube (left/right, fore/aft and rudder input to twist it) and it moves one third of the distance.
 
This is because of the way that MSFS "blends" the animations, when MSFS has to apply 2 animations, it runs only a part of each animation, in a calculated ratio as defined in the behavior file condition, for example 50%-50 %.
 
This is because of the way that MSFS "blends" the animations, when MSFS has to apply 2 animations, it runs only a part of each animation, in a calculated ratio as defined in the behavior file condition, for example 50%-50 %.
Do you have a link to where the ratio is defined in the behavior file?

Because I am using my own animation code which directly accesses the ASOBO_GT_Anim_Code template. The only parameter I can find that might affect a ratio is ANIM_BLENDWEIGHT which defaults to 1.0. As far as I can though, that parameter has zero effect as after changing it to a range of values from 0 to 100 nothing changed in my animations.

I also did various adjustments to the animation length in the behavior XML as I thought that might be the problem but nothing worked.

I went back and watched the video again. At 18:46 you can see that he sets the animation of the stick to 15 degrees. Later in the video (41:23) you can see the results within MSFS. There is no way that stick is moving 15 degrees so I don't think it's a problem that is affecting just me. It just seems to be the way MSFS works. If more that one animation is applied to a bone then the range of keyframes used is divided by the number of animations.

My next experiment is to determine if all keyframes are scaled down or if they are truncated. ie if your animation is 0 to 200 keyframes is MSFS only displaying 50 to 150 or is it displaying 0 to 200 but dividing the rotation in half?

EDIT: Experiment done. It looks like MSFS is dividing the rotation in half.

Here's the keyframes, what the rotation was set to in Blender and the result in MSFS. The animation in MSFS follows the animation in Blender (ie half the range there is no movement just like there is in blender but the rotation is divided by 2. So having multiple animations on a single bone is not affecting the keyframes, it's affecting the animation rotation angle. So there doesn't seem to be anyway around this. I'm just going to have to animated by stick cover to double rotation range so it displays correctly in MSFS.

On another note, I've done another experiment and it looks like armatures can be made children of parts. Many months ago using the old blender exporter this wasn't supported, the armature had to be a root part, but with the Asobo exporter it seems like armatures parented to parts (which can be animated) are supported.

KeyframeRotation in BlenderResult in MSFS
02010
502010
10000
150-20-10
200-20-10
 
Last edited:
I was talking about the following:

thresholdThis is the threshold value for choosing an animation based on the returned <Value> for the <BlendTreeState> element. If the value is between 2 consecutive thresholds, the 2 corresponding animations are blended together with weight depending on how close the value is to each threshold. If the value is more than the last threshold, the last animation is used. See the <BlendTreeState> Example.

But I don't know how you are programming the behavior of your animations.
 
I see. I don't really understand blendtreestate but I think that's probably for combining separate animations onto a character.

For example, you see them in things like asobo-simobjects-characters\SimObjects\Humans\Marshaller where the animations are saved separately and those animations are things like "Idle", "TurnLeft", "MoveAhead" etc. Essentially it's different character movement animations that need to be applied to a whole character model.

I'm just doing a simple cloth cover over a joystick like you see in the A320. For me, just doubling the rotation angles in the animation is enough to get the results I want. I was planning on adding pilot animations (left arm on the throttle, right arm on the stick) which would need 3 inputs but I think I should be able to split those out into bones with only a single animation and connecting them using parent/child which should solve the issue for me.
 
I have completed the pilot and will now provide an explanation for future reference or for anyone else who wants to know. This is complicated so be warned.

1. Build the body of the pilot model. I split both of the arms off into separate parts (otherwise I'd need to put 3 animations on a single mesh which would cause the problem that was the point of this whole post. ie MSFS will divide the angle of any rotation by the number of animations attached to the armature). To do this hide edges along the seams of the clothing of the model. When I split the arms off I also included a single row of polygons on either side to cover for any holes that might form. Slightly scaling down the edges at the end of the extra polys conceals them while concealing any holes.

2. For the left arm that operates the throttle. Create an armature at shoulder then extrude two more bones (one for the upper arm and one for the lower arm). You could do a third for the hand if you wanted to. Then make the arm mesh a child of the armature and use Mesh Deform with Automatic Weights.

Using automatic weighting mostly works but you will need to make some changes. At the shoulder remove any weight on the edge loop so that when you move the upper and lower arm bones the connection with the shoulder does not move.

Then simply animate the upper and lower bones in Pose mode to operate the throttle. Push down the animation to an NLA track and name it to the animation name in your behavior XML.

3. For the right arm it's more complicated. You need to have two animations. One rotation animation at the shoulder and then an extension animation for the arm (which operates similarly to the left arm).

The rotation animation will deform the body of the pilot moving the shoulder. Firstly create a bone at the shoulder (this can be a very short little bone, it never moves but it serves as an anchor for the rest of the mesh) and then extrude it where the arm joins. Make the body a child of the shoulder armature. Using automatic weights doesn't work so well here but it can be useful as a starting point. Use the pose mode to rotate the shoulder around the yaw axis to fine tune the vertex weights (refer to the video in the top post if you don't know how to do this).

Now, here's the clever bit. Make a dummy part in the shoulder that mirrors the armature position you have just made (this part is highlighted in the attached screenshot). This dummy part is not an armature. We will attach the right arm to this dummy part because we can't make an armature a child of another armature. In the screenshot Pilot_Right_ARM is the ARMature for the right arm and a child of Pilot_Right_Shoulder which is the dummy part.

Once the shoulder is setup you can animate it. I set things up so that keyframe 110 is my neutral position. 10 is the minimum and 210 is the maximum. At keyframe 10 I rotate the shoulder around enough so that it will line up with the stick in left back position. At 210 it should line up with the stick in the forward right position. The exact animation is not important. In fact it's advisable to over rotate a bit. Your XML will perfectly position the shoulder and arm later on. You just need to have keyframed a sufficient range.

Now we animate the right arm in the same fashion as we did for the left arm. This should be straightened out to reach the stick in the forward left position at keyframe 210 and bent back to reach the back left position at keyframe 10. Again, you don't need to be perfect as your code will clean it up but getting full extension is the tricky bit. Quick tip: In EDIT mode for the armature you can use the Transform section and the Roll parameter to align the bones. This way you only need to adjust one rotation value to extend the arm.

I will now explain the animation of the right arm and what you are trying to do. You do not animate the shoulder and arm according to the stick position. In your animation code you will read the stick position and then calculate the shoulder and arm positions. To do this you first make 8 duplicates of your control stick. You then place one of these duplicates in these positions, back left, left, forward left, forward centred, forward right, right, back right, back centred. You should now have 8 sticks arranged in a square around the neutral stick position.

Once you have done this you need to write down some settings. On a piece of paper draw a diagram of the 9 positions of the stick. Mark down 110,110 in the centre. This is the neutral position at keyframes 110 for shoulder rotation and 110 for arm extension. Next, manipulate your shoulder and arm animation keyframes so the hand is holding the stick in the left back position. I rotate the shoulder using the animation keyframe slider to the general direction, remember this keyframe and then untick the animation button. Then change the keyframe so the arm extension is in the right spot. You may need to fine tune this but eventually you should have keyframes for the rotation and extension animations that put the hand on the stick. Note these down on your diagram. Repeat this process for all the other positions.

4. You should now be able to export your model and the fun bit begins. Below is the code I use for the arm rotation and extension. This reads the elevator and aileron position and then interpolates between the values you recorded earlier to select the correct keyframes. The code firstly copies the A: vars into temporary L:vars just to make it easier to read. This code could probably be written more efficiently. But I write it like this as it's easier for me to read it later and understand what I did.

Then using if statements it ends up in one of 4 quadrants. Forward left, forward right, back left or back right.

Once in the correct quadrant we plug in the keyframes we wrote down earlier. The code in each quadrant has 2 sets of 3 lines of code. The first set determines the rotation keyframe. The second set the extension keyframe.

Looking at the first quadrant the 87 131 87 numbers are the rotation values for full forward (87) and left forward (131). The third number is the same as always the same as the first. We are trying to find the difference between the first and second numbers based on the left right position of the stick and then we have to add the first number back on because that's the baseline. For the next line 110 161 110 the values are neutral (110) and 161 full left. The second line will always refer to the neutral position at 110 because it's common to all four quadrants.

We have now interpolated the left right position based on the the stick foreaft position being full forward (stored in L:touter) and in the neutral position (stored in L:inner). The next line interpolates between touter and tinner based on the stick foreaft position and we have our keyframe which we can write into the animation variable.

The next 3 lines repeat the process but for the arm extension numbers.

You may find that if you do this yourself that the code may make things animate in the wrong direction(s). That's what happened to me and is why there is a -1 * in the aileron position. I had to mirror the left right animation.

Code:
            <UseTemplate Name="ASOBO_GT_Anim_Code">
                <ANIM_NAME>Winjeel_Right_Arm_Rotation</ANIM_NAME>
                <ANIM_CODE>
                (A:IS USER SIM,bool) if{
                    (L:tm_Prefs_joystick_lock,number) if{ 0 (&gt;L:stick_foreaft,number) 0 (&gt;L:stick_leftright,number) }
                    els{
                        (A:ELEVATOR TRIM PCT,percent) 3 / (A:ELEVATOR POSITION,percent) 100 / (&gt;L:stick_foreaft,number)
                        (A:AILERON POSITION,number) -1 * (&gt;L:stick_leftright,number)
                    }
                    (L:stick_leftright,number) 0 &gt;
                    if{                   
                        (L:stick_foreaft,number) 0 &lt;
                        if{
                             87 131  87 - (L:stick_leftright,number) abs * + (&gt;L:touter,number)
                            110 161 110 - (L:stick_leftright,number) abs * + (&gt;L:tinner,number)
                            (L:tinner,number) (L:touter,number) (L:tinner,number) - (L:stick_foreaft,number) abs * + (&gt;L:winjeel_right_arm_rotation,number)               
                            
                            177 210 177 - (L:stick_leftright,number) abs * + (&gt;L:touter,number)
                            110 126 110 - (L:stick_leftright,number) abs * + (&gt;L:tinner,number)
                            (L:tinner,number) (L:touter,number) (L:tinner,number) - (L:stick_foreaft,number) abs * + (&gt;L:winjeel_right_arm_extension,number)               

                        }
                        els{
                            155 204 155 - (L:stick_leftright,number) abs * + (&gt;L:touter,number)
                            110 161 110 - (L:stick_leftright,number) abs * + (&gt;L:tinner,number)
                            (L:tinner,number) (L:touter,number) (L:tinner,number) - (L:stick_foreaft,number) abs * + (&gt;L:winjeel_right_arm_rotation,number)                   
                        
                             42  61  42 - (L:stick_leftright,number) abs * + (&gt;L:touter,number)
                            110 126 110 - (L:stick_leftright,number) abs * + (&gt;L:tinner,number)
                            (L:tinner,number) (L:touter,number) (L:tinner,number) - (L:stick_foreaft,number) abs * + (&gt;L:winjeel_right_arm_extension,number)               

                        }               
                    }
                    els{               
                        (L:stick_foreaft,number) 0 &lt;
                        if{
                             87  35  87 - (L:stick_leftright,number) abs * + (&gt;L:touter,number)
                            110  54 110 - (L:stick_leftright,number) abs * + (&gt;L:tinner,number)
                            (L:tinner,number) (L:touter,number) (L:tinner,number) - (L:stick_foreaft,number) abs * + (&gt;L:winjeel_right_arm_rotation,number)               
                            
                            177 180 177 - (L:stick_leftright,number) abs * + (&gt;L:touter,number)
                            110  95 110 - (L:stick_leftright,number) abs * + (&gt;L:tinner,number)
                            (L:tinner,number) (L:touter,number) (L:tinner,number) - (L:stick_foreaft,number) abs * + (&gt;L:winjeel_right_arm_extension,number)               

                        }
                        els{
                            155  86 155 - (L:stick_leftright,number) abs * + (&gt;L:touter,number)
                            110  54 110 - (L:stick_leftright,number) abs * + (&gt;L:tinner,number)
                            (L:tinner,number) (L:touter,number) (L:tinner,number) - (L:stick_foreaft,number) abs * + (&gt;L:winjeel_right_arm_rotation,number)                   
                        
                             42  28  42 - (L:stick_leftright,number) abs * + (&gt;L:touter,number)
                            110  95 110 - (L:stick_leftright,number) abs * + (&gt;L:tinner,number)
                            (L:tinner,number) (L:touter,number) (L:tinner,number) - (L:stick_foreaft,number) abs * + (&gt;L:winjeel_right_arm_extension,number)               

                        }               
                    }
                    (L:winjeel_right_arm_rotation,number)
                }
                els{ 110 }
                </ANIM_CODE>
                <ANIM_LENGTH>210</ANIM_LENGTH>
            </UseTemplate>

            <UseTemplate Name="ASOBO_GT_Anim_Code">
                <ANIM_NAME>Winjeel_Right_Arm_Extension</ANIM_NAME>
                <ANIM_CODE>(A:IS USER SIM,bool) if{ (L:winjeel_right_arm_extension,number) } els{ 110 }</ANIM_CODE>
                <ANIM_LENGTH>210</ANIM_LENGTH>
            </UseTemplate>
 

Attachments

  • Image1.jpg
    Image1.jpg
    686.1 KB · Views: 91
Updating this with more information.

I was building another pilot in Blender and had set up the right arm animation with a shoulder for rotation and the upper and lower arms for extension (two separate animations in total). I had assumed I would run into the multiple animations problem so I over extended the rotations used in the animations to account for this.

But, when I loaded the pilot into the sim the arm was turning and reaching too far. "Had Asobo fixed this?" I thought.

After some thought I realised what had been happening all along. It's not the number of animations affecting the bones. It's the weighting of the mesh to the bones that is the issue. With the control column cover the top of the cover is weighted to move with both the left/right and fore/aft animations. With the pilot I am working on the hand/lower arm mesh of the pilot is only weighted to the bone in the lower arm. Likewise for the upper arm points in the mesh are weighted to the upper arm and the shoulder of the mesh to the shoulder bone. In other words, each part of the arm is weighted to only be affected the bone within.

This is why the pilot works fine. Mostly, only one part of the mesh is weighted to one bone. Where the bones join the weighting fades from one to the other. With the control column cover, because the top of the mesh is weighted 100% to two bones equally MSFS halves the effect of each bone. Theoretically I could weight the control column cover so that only the bottom of the mesh was weighted 100% to left/right and only the top 100% to fore/aft but somehow I think that would look a bit weird with the top and bottom halves of the cover moving more or less independently.

tldr: If parts of your mesh are weighted to be affected by multiple bones MSFS will split the amount of animation each weighted part will receive. So, try to avoid having parts of your mesh 100% weighted to multiple bones. But if you must have parts affected by multiple bones you need to account for MSFS splitting the animation amount.
 
Back
Top