OgreMesh Model Rukia Animation and Changing Weapons

http://www.youtube.com/watch?v=aVZ7LyHVPCw



I'd like to share a program that i've made. It shows how a model can have many weapons switching by using only the ordinary ogremesh export format. I've searched on this forum and all the techniques they have been mentioning is all about export format md5 and as a student game programmer, it hit me with a great wall cause it's very hard for me to implement it, :))) hahah. So i've deviced a new way to achieve this goal.

I will include my simple code here for you to see, and the trick here is to make first the character model in blender, then finish it's rigging, the bones, animations, etc.... After that make a weapon on the same blender that you've made the character, and attach it on the same skeleton, preferably on the hand. When exporting, name only the needed animations for the weapon. Disable the "Follow the mesh name as the skeleton name" on the export menu. First select only the character in object mode, then export after that export all the remaining weapons through object mode too. So Finalize it, all of the meshes only shares one skeleton file. :D

[java]package mainPackage;

import com.jme3.app.SimpleApplication;
import com.jme3.scene.Spatial;
import com.jme3.scene.Node;
import com.jme3.light.DirectionalLight;
import com.jme3.light.AmbientLight;
import com.jme3.math.Vector3f;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.animation.AnimChannel;
import com.jme3.animation.AnimControl;
import com.jme3.animation.AnimEventListener;
import com.jme3.animation.LoopMode;
import com.jme3.math.ColorRGBA;
import com.jme3.font.BitmapFont;
import com.jme3.font.BitmapText;



public class mainClass extends SimpleApplication implements AnimEventListener
{
public static void main(String args[])
{
mainClass app = new mainClass();
app.start();
}



// Game Variables
private DirectionalLight sun;
private AmbientLight sunAmbient;
private Spatial player;
private Spatial weaponHammer, weaponSword;
private Node playerNode;
private boolean isHammerOut = false, isSwordOut = false;
private AnimControl playerControl, weaponHammerControl, weaponSwordControl;
private AnimChannel channelTopBody;
private AnimChannel channelBaseBody;
private AnimChannel channelWeaponHammer, channelWeaponSword;
private BitmapFont defaultFont;
private BitmapText currentAnimation;
private BitmapText currentKeyPressed;




public void simpleInitApp()
{
flyCam.setEnabled(false);
guiNode.detachAllChildren();
viewPort.setBackgroundColor(ColorRGBA.White.multLocal(0.95f));

sun = new DirectionalLight();
sun.setDirection(new Vector3f(0, -1, -1));
sunAmbient = new AmbientLight();
rootNode.addLight(sun);
rootNode.addLight(sunAmbient);

playerNode = new Node();
player = assetManager.loadModel("Models/Rukia/Rukia.mesh.xml");
player.setLocalScale(0.8f);
weaponHammer = assetManager.loadModel("Models/Rukia/Hammer.mesh.xml");
weaponHammer.setLocalScale(0.8f);
weaponSword = assetManager.loadModel("Models/Rukia/Sword.mesh.xml");
weaponSword.setLocalScale(0.8f);
playerNode.attachChild(player);
playerNode.setLocalTranslation(-2, 0, 0);
rootNode.attachChild(playerNode);

playerControl = player.getControl(AnimControl.class);
playerControl.addListener(this);
channelTopBody = playerControl.createChannel();
channelBaseBody = playerControl.createChannel();
channelTopBody.setAnim("Idle");
weaponHammerControl = weaponHammer.getControl(AnimControl.class);
weaponHammerControl.addListener(this);
weaponSwordControl = weaponSword.getControl(AnimControl.class);
weaponSwordControl.addListener(this);
channelWeaponHammer = weaponHammerControl.createChannel();
channelWeaponSword = weaponSwordControl.createChannel();

defaultFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
currentAnimation = new BitmapText(defaultFont);
currentAnimation.setSize(25);
currentAnimation.setColor(ColorRGBA.Black);
currentAnimation.setLocalTranslation(380, 400, 0);
currentAnimation.setText("Current Animation: Idle");
currentKeyPressed = new BitmapText(defaultFont);
currentKeyPressed.setSize(28);
currentKeyPressed.setColor(ColorRGBA.Black);
currentKeyPressed.setLocalTranslation(400, 300, 0);
currentKeyPressed.setText("Key Pressed: NONE");
guiNode.attachChild(currentAnimation);
guiNode.attachChild(currentKeyPressed);


inputManager.addMapping("Run", new KeyTrigger(KeyInput.KEY_W));
inputManager.addMapping("RotateLeft", new KeyTrigger(KeyInput.KEY_E));
inputManager.addMapping("RotateRight", new KeyTrigger(KeyInput.KEY_Q));
inputManager.addMapping("Attack1", new KeyTrigger(KeyInput.KEY_SPACE));
inputManager.addMapping("ToggleHammer", new KeyTrigger(KeyInput.KEY_1));
inputManager.addMapping("ToggleSword", new KeyTrigger(KeyInput.KEY_2));
inputManager.addListener(AnalogL, "RotateLeft", "RotateRight");
inputManager.addListener(ActionL, "Attack1", "Run", "ToggleHammer", "ToggleSword");
}



public ActionListener ActionL = new ActionListener()
{
public void onAction(String name, boolean keyPressed, float tpf)
{
if (name.equals("Run"))
{
if (keyPressed)
{
channelTopBody.setAnim("RunTop", 0.5f);
channelBaseBody.setAnim("RunBase", 0.5f);
currentAnimation.setText("Current Animation: Running");
currentKeyPressed.setText("Key Pressed: W");

if (isHammerOut)
channelWeaponHammer.setAnim("RunTop", 0.5f);
else if (isSwordOut)
channelWeaponSword.setAnim("RunTop", 0.5f);
}
else
{
if (!isHammerOut && !isSwordOut)
{
channelTopBody.setAnim("Idle", 0.5f);
channelBaseBody.setAnim("Idle", 0.5f);
currentAnimation.setText("Current Animation: Idle");
}
else if (isHammerOut)
{
channelTopBody.setAnim("IdleHammerStance", 0.5f);
channelBaseBody.setAnim("IdleHammerStance", 0.5f);
channelWeaponHammer.setAnim("IdleHammerStance", 0.5f);
currentAnimation.setText("Current Animation: Hammer Stance");
}
else if (isSwordOut)
{
channelTopBody.setAnim("IdleSwordStance", 0.5f);
channelBaseBody.setAnim("IdleSwordStance", 0.5f);
channelWeaponSword.setAnim("IdleSwordStance", 0.5f);
currentAnimation.setText("Current Animation: Sword Stance");
}

currentKeyPressed.setText("Key Pressed: NONE");
}
}


if (name.equals("Attack1") && keyPressed)
{
if (!channelTopBody.getAnimationName().equals("HammerAttack") && isHammerOut)
{
channelTopBody.setAnim("HammerAttack", 0.2f);
channelTopBody.setLoopMode(LoopMode.DontLoop);
channelBaseBody.setAnim("HammerAttack", 0.2f);
channelBaseBody.setLoopMode(LoopMode.DontLoop);
channelWeaponHammer.setAnim("HammerAttack", 0.2f);
channelWeaponHammer.setLoopMode(LoopMode.DontLoop);
currentAnimation.setText("Current Animation: Hammer Attack");
currentKeyPressed.setText("Key Pressed: SPACE");
}
else if (!channelTopBody.getAnimationName().equals("SwordAttack") && isSwordOut)
{
channelTopBody.setAnim("SwordAttack", 0.2f);
channelTopBody.setLoopMode(LoopMode.DontLoop);
channelBaseBody.setAnim("SwordAttack", 0.2f);
channelBaseBody.setLoopMode(LoopMode.DontLoop);
channelWeaponSword.setAnim("SwordAttack", 0.2f);
channelWeaponSword.setLoopMode(LoopMode.DontLoop);
currentAnimation.setText("Current Animation: Sword Attack");
currentKeyPressed.setText("Key Pressed: SPACE");
}
}

if (name.equals("ToggleHammer") && keyPressed)
{
if (!isHammerOut)
{
isHammerOut = true;
channelWeaponHammer.setAnim("GetHammer", 0.1f);
channelWeaponHammer.setLoopMode(LoopMode.DontLoop);
channelTopBody.setAnim("GetHammer", 0.1f);
channelTopBody.setLoopMode(LoopMode.DontLoop);
channelBaseBody.setAnim("GetHammer", 0.1f);
channelBaseBody.setLoopMode(LoopMode.DontLoop);
playerNode.attachChild(weaponHammer);
currentAnimation.setText("Current Animation: Get Hammer");
currentKeyPressed.setText("Key Pressed: 1");

if (isSwordOut)
{
isSwordOut = false;
playerNode.detachChild(weaponSword);
}

}
else
{
isHammerOut = false;
playerNode.detachChild(weaponHammer);
channelTopBody.setAnim("Idle", 0.4f);
channelBaseBody.setAnim("Idle", 0.4f);
currentAnimation.setText("Current Animation: Idle");
currentKeyPressed.setText("Key Pressed: NONE");
}
}

if (name.equals("ToggleSword") && keyPressed)
{
if (!isSwordOut)
{
isSwordOut = true;
channelTopBody.setAnim("GetSword", 0.1f);
channelTopBody.setLoopMode(LoopMode.DontLoop);
channelBaseBody.setAnim("GetSword", 0.1f);
channelBaseBody.setLoopMode(LoopMode.DontLoop);
channelWeaponSword.setAnim("GetSword", 0.1f);
channelWeaponSword.setLoopMode(LoopMode.DontLoop);
playerNode.attachChild(weaponSword);
currentAnimation.setText("Current Animation: Get Sword");
currentKeyPressed.setText("Key Pressed: 2");

if (isHammerOut)
{
isHammerOut = false;
playerNode.detachChild(weaponHammer);
}
}
else
{
isSwordOut = false;
playerNode.detachChild(weaponSword);
channelTopBody.setAnim("Idle", 0.4f);
channelBaseBody.setAnim("Idle", 0.4f);
currentAnimation.setText("Current Animation: Idle");
currentKeyPressed.setText("Key Pressed: NONE");
}
}

}
};


public AnalogListener AnalogL = new AnalogListener()
{
public void onAnalog(String name, float value, float tpf)
{
if (name.equals("RotateLeft"))
{
playerNode.rotate(0, 3*tpf, 0);
currentKeyPressed.setText("Key Pressed: E");
}
if (name.equals("RotateRight"))
{
playerNode.rotate(0, -3*tpf, 0);
currentKeyPressed.setText("Key Pressed: Q");
}
}
};


public void onAnimChange(AnimControl control, AnimChannel channel, String animName)
{

}

public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName)
{
if (animName.equals("GetHammer"))
{
channelTopBody.setAnim("IdleHammerStance", 0.3f);
channelBaseBody.setAnim("IdleHammerStance", 0.3f);
channelWeaponHammer.setAnim("IdleHammerStance", 0.3f);
currentAnimation.setText("Current Animation: Hammmer Stance");
currentKeyPressed.setText("Key Pressed: NONE");
}

if (animName.equals("GetSword"))
{
channelTopBody.setAnim("IdleSwordStance", 0.3f);
channelBaseBody.setAnim("IdleSwordStance", 0.3f);
channelWeaponSword.setAnim("IdleSwordStance", 0.3f);
currentAnimation.setText("Current Animation: Sword Stance");
currentKeyPressed.setText("Key Pressed: NONE");
}

if (animName.equals("HammerAttack"))
{
channelTopBody.setAnim("IdleHammerStance", 0.6f);
channelBaseBody.setAnim("IdleHammerStance", 0.6f);
channelWeaponHammer.setAnim("IdleHammerStance", 0.6f);
currentAnimation.setText("Current Animation: Hammer Stance");
currentKeyPressed.setText("Key Pressed: NONE");
}

if (animName.equals("SwordAttack"))
{
channelTopBody.setAnim("IdleSwordStance", 0.6f);
channelBaseBody.setAnim("IdleSwordStance", 0.6f);
channelWeaponSword.setAnim("IdleSwordStance", 0.6f);
currentAnimation.setText("Current Animation: Sword Stance");
currentKeyPressed.setText("Key Pressed: NONE");
}

}

}
[/java]
1 Like

Great post, and thanks for all this valuable information!

It’s very good man, impressive and intelligent solution and good character+video too ( love the girl) …But from what I see your trick simply solve the problem of meshes with the same skeleton file!

In the game, I may prefer the method that a weapon tie with some particular bone in real-time …

Tell me what you think about this situation :

  • A sword man drop his sword onto the ground…
  • He take his sword from his back pack and hold in one hand…
  • A West gunner change his gun-hand for impress a chick in the town :stuck_out_tongue:

    Yeah , It’s a joke but it maybe an easy implement for your “weapon” problem …

    Make separated mesh file and separated animation , then code the “hold-weapon” method for your game, it’s way more flexible !!

atomix, I am confused about the following things you said from your post

atomix said:
But from what I see your trick simply solve the problem of meshes with the same skeleton file!
In the game, I may prefer the method that a weapon tie with some particular bone in real-time ...
Make separated mesh file and separated animation , then code the "hold-weapon" method for your game, it's way more flexible !!

My interpretation of fidelsurtida's post is that there are separate mesh files(one for the character, and others containing only the weapons) with separate animations, and the weapons are trying to the bones in real time. Isn't the "hold-weapon" method functionality you are referring to implemented in the above code?

The reason I am asking all of this because I am starting to research/develop a skeletal animation system of my own and want to absorb all the information I can. Please help me clear up my confusion.