(SOLVED) How to equip a weapon (Get attachment Node)

Hi all,

My goal
To have my model holding a mace and then playing an attack animation. I have a j3o model and an animation that works on its own. I have a mace j3o model as well.

Problem
How to reference my attachment node in my source code? (not sure correct terminology for this)

In my book that I’m working through I went into my armature and then right clicked my right hand bone and then selected Get attachment Node. I now have a node for my right hand that feels like if I could connect my mace to it it would work out–

I’ve been reading everything I can about it like this thread How to add clothing and the beginner node stuff

In theory I have this node and if I make my mace a node I should be able to attach them to each other, however, where I feel mainly stuck is how do I access this right hand node through code?

It makes sense to me to load my mace through my asset manager and then assign it as a node but I cannot figure out how to get this righthand node referenced in my code.

No matter what I try the mace will load in the scene but it just stays floating and does not go with the arm when it attacks. I feel so close-just have to get the mace in the hand.

animComposer = ((Node)model).getChild(0).getControl(AnimComposer.class);
                animComposer.setCurrentAction("1H_A");

Any comments or pointers on how to work with these nodes would be most appreciated

Here is full code

package mygame;

import com.jme3.anim.AnimComposer;
import com.jme3.anim.Joint;
import com.jme3.anim.SkinningControl;
import com.jme3.anim.tween.Tween;
import com.jme3.anim.tween.Tweens;
import com.jme3.anim.tween.action.Action;
import com.jme3.anim.util.AnimMigrationUtils;
import com.jme3.animation.SkeletonControl;
import com.jme3.app.SimpleApplication;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;



public class WeaponAttack extends SimpleApplication {

        private AnimComposer animComposer;
            private Spatial terrain;

        

    public static void main(String[] args) {
        WeaponAttack app = new WeaponAttack();
        app.start();
    }

    
    @Override
    public void simpleInitApp() {
        flyCam.setMoveSpeed(10f);
      cam.setLocation(new Vector3f(6.4013605f, 7.488437f, 12.843031f));
        cam.setRotation(new Quaternion(-0.060740203f, 0.93925786f, -0.2398315f, -0.2378785f));  
        
        DirectionalLight dl = new DirectionalLight();
        dl.setDirection(new Vector3f(-0.1f, -0.7f, -1).normalizeLocal());
        dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f));
        rootNode.addLight(dl);
        
        terrain = assetManager.loadModel("Scenes/gground.j3o");
                rootNode.attachChild(terrain);
        
Spatial model = assetManager.loadModel("Models/1h.j3o");
        model.center();
        
        Spatial mace = assetManager.loadModel("Models/mace.j3o");
        ((Node)model).attachChild(mace);
        
                //animComposer = model.getControl(AnimComposer.class);

                animComposer = ((Node)model).getChild(0).getControl(AnimComposer.class);
                animComposer.setCurrentAction("1H_A");
      
                          rootNode.attachChild(model);
           
    } 
}

To work with attachment nodes, you will need to work with the SkinningControl instead of the AnimComposer

If you view your model in the SDK scene editor, it can be somewhat misleading because it shows 2 controls: “AnimComposer” and “Armature” but there is not actually a control named Arnature, it is actually a SkinningControl that stores the Armature object within it.

So you just need to find the skinningControl and then you can use this method from skinningControl:

skinningControl.getAttachmentsNode(jointName).attachChild(weaponSpatial);
https://javadoc.jmonkeyengine.org/v3.3.0-beta2/com/jme3/anim/SkinningControl.html#getAttachmentsNode-java.lang.String-

So attaching weapons like this using attachment nodes is a different process than attaching clothes like I was doing in the clothing thread you linked.

The important difference is that with a wapon, it is only ever held by a single joint (the hand) so you can use an attachment node to make the weapon follow that single joint, and the waepon also doesn’t need to have any vertex groups as a result.

But when you have clothing or armor pieces that are affected by more than just one single joint, then that is when you need follow the advice in the other thread and need to setup the clothing with vertex groups and attach it directly to the spatial to be influenced by the animComposer.

3 Likes

oh ok, cool! I remember seeing this pop up in places.

Ok I guess I am not sure how to find the skinningControl that I want. I have a green bone with mixamorig:RightHand however I get a red line on getattachmentsnode when I do this

skinningControl = ((Node)model).getChild(0).getControl(SkinningControl.class);
    skinningControl.getAttachmentsNode("mixamorig:RightHand").attachChild(mace);

Am I doing this properly so far?

This looks 100% correct as long as mace is a Spatial, which it does appear to be based on your prior code.

Does the red line possibly go away if you just run the app? Or does it give you a crash error?

I frequently get “fake” red line error indicators in the SDK even when code is 100% error free, in which case the red lines will go away after i change one character in the bugged line, save, change it back to normal, and save again. I frequently encounter this issue in the SDK when using the alt-enter keybind to quickly import something based on IDE suggestions.

1 Like

If “skinningControl” is not declared as a SkinningControl then he will also get red lines for any SkinningControl-specific methods.

We’re trying to tell the length of a piece of string from just a partial view.

1 Like

ok cool!

It did give me a crash error - however I did remove the red squiggle by trying to do what pspeed was suggesting and I also found the code that I had been trying to find again where I had seen skinning control test ogre anim

My code now has no errors and the model loads and the animation plays however now my mace is nowhere to be found :thinking:
feels closer but any idea where my mace is ?

full code here excluding imports and package

public class WeaponAttack extends SimpleApplication {

        private AnimComposer animComposer;
            private Spatial terrain;

        
    public static void main(String[] args) {
        WeaponAttack app = new WeaponAttack();
        app.start();
    }

    
    @Override
    public void simpleInitApp() {
      flyCam.setMoveSpeed(10f);
      cam.setLocation(new Vector3f(6.4013605f, 7.488437f, 12.843031f));
      cam.setRotation(new Quaternion(-0.060740203f, 0.93925786f, -0.2398315f, -0.2378785f));  
        
        DirectionalLight dl = new DirectionalLight();
        dl.setDirection(new Vector3f(-0.1f, -0.7f, -1).normalizeLocal());
        dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f));
        rootNode.addLight(dl);
        
        terrain = assetManager.loadModel("Scenes/gground.j3o");
                rootNode.attachChild(terrain);
        
Spatial model = assetManager.loadModel("Models/1h.j3o");
        model.center();
        
        //animComposer = model.getControl(AnimComposer.class);
                animComposer = ((Node)model).getChild(0).getControl(AnimComposer.class);
                animComposer.setCurrentAction("1H_A");
        
  
                
        SkinningControl skinningControl = ((Node)model).getChild(0).getControl(SkinningControl.class);
        Spatial mace = assetManager.loadModel("Models/mace.j3o");

        //skinningControl.getAttachmentsNode("mixamorig:RightHand").attachChild(mace);
        Node n = skinningControl.getAttachmentsNode("mixamorig:RightHand");
                n.attachChild(mace);
                
                                          rootNode.attachChild(model);       
    } 
    
}

update

well I just tried attaching my mace to the rootNode below my skinning control code

SkinningControl skinningControl = ((Node)model).getChild(0).getControl(SkinningControl.class);
        Spatial mace = assetManager.loadModel("Models/mace.j3o");
        //skinningControl.getAttachmentsNode("mixamorig:RightHand").attachChild(mace);

        Node n = skinningControl.getAttachmentsNode("mixamorig:RightHand");
                n.attachChild(mace);
                rootNode.attachChild(mace);

and the mace loads but just floats in the air. so something is still up with my skinningcontrol set up not sure what thou :thinking:

You really need to learn how to add debug code. For example, some System.out.println calls would have told you a lot about what’s going on in every one of these cases.

You understand that the last line completely undoes the n.addChild(mace) line, right?

Most likely cause of your issue is that the mace already has a built in translation.

System.out.println its world bound and/or world translation before and after adding it to the attachment node. And maybe even on update.

These are the ways to drill in on the issue: check the values to verify all of your assumptions.

1 Like

I didn’t know for certain but that makes sense that it does.
I will start doing some side hustle research on debug code and perhaps switch my focus to that for the time being – thank you for the input!

Yeah after you attach a weapon to the hand, it almost always shrinks unless everything has a local scale of 1, and can also have its location offset (in my experience). If you zoom in close you’ll likely see a microscopic version of your weapon, or you will also see it floating off to the side somewhere.

So its definitely necessary to adjust the weapons transform when it gets attached in nearly every case, and it is also important to make sure that your mace Spatial is positioned in its own scene correctly so that the spot where you’d grip that weapon is lined up with the origin point of the mace’s j3o scene.

1 Like

hahaha oh my goodness my mace was tiny (and also floating off to the side)

I set my mace to

mace.scale(50f);      

and now I can actually see it … :face_with_monocle:

Essentially the attachment node you are attaching the weapon to has a worldScale of something tiny like 0.02, so in order to get back to a regular scale of 1.0, you had to scale it by 50. The weapon spatial will use its localScale multiplied by the localScale of all of its parents (even up to and including the rootNode) to determine its final worldScale.

However, every unique weapon and character model may have a unique scale, so (from my experience) you need to use some code like this to apply the inverse value of the attachment node’s worldScale as the weapon’s localScale, effectively evening things out.

public class Weapon{
   
    public void weaponSpatialLoaded(){
            weaponSpatial = app.getAssetManager().loadModel(equipmentAssetString); 
            originalModelLocalScale = weaponSpatial.getLocalScale().clone();
     }

    public void equipToAttachmentNode(Node attachmentsNode) {
         attachmentsNode.attachChild(weaponSpatial);
         weaponSpatial.setLocalScale(originalModelLocalScale.divide(attachmentsNode.getWorldScale()));
    
    }
}

It’s also important to do the math using the weapons original scale which I clone and keep a reference to right after loading the model, this is important in case the same weapon gets equipped and de-equipped multiple times.

And for your location offset issue, you just need to position each weapon model so that the spot the player’s hand will grab is located at the origin (0,0,0) in the SDK scene editor for each individual weapon j3o file.

1 Like

ok, scale and position… I will try to mess around with these concepts some more and see if I can get that mace in place to properly thwack some brigands. Thanks donuts!! :raised_hands:

2 Likes