CharacterController implementation (JmePhysic) (1 post)

  • Profile picture of anykeyh anykeyh3p said 2 years, 1 month ago:

    I post here a controller which allow to move a physics component with only gravity (no torque/force) and catch collisions with other StaticPhysicsNode.
    Good idea to move character in physics environnement!
    Jme-jbullet do that, but actually i didn't see that on jmephysics!

    import com.jme.input.InputHandler;
    import com.jme.input.action.InputAction;
    import com.jme.input.action.InputActionEvent;
    import com.jme.math.Vector3f;
    import com.jme.scene.Controller;
    import com.jmex.physics.DynamicPhysicsNode;
    import com.jmex.physics.PhysicsNode;
    import com.jmex.physics.StaticPhysicsNode;
    import com.jmex.physics.contact.ContactInfo;
    import com.jmex.physics.material.Material;
    
    /**
     * Control a ghost physic node to be moved without physic interaction,
     * like no physic, but catch collision with other StaticPhysicNode
     *
     * @author anykeyh@gmail.com
     */
    public class CharacterController extends Controller {
       private static final long serialVersionUID = 3152691286715564793L;
       
       private int contactPointCount       = 0;
       private DynamicPhysicsNode nodeToControl;
       private boolean onGround          = false;
       private Vector3f physicDecal       = new Vector3f();
       private InputHandler inputHandler    = new InputHandler();
       
       private Vector3f tmpVec          = new Vector3f();
       
       private class CollisionHandler extends InputAction {
          @Override
          public void performAction(InputActionEvent evt) {
             ContactInfo info = (ContactInfo )evt.getTriggerData();
             
             DynamicPhysicsNode self;
             PhysicsNode other;
             self = nodeToControl;
             
             if(info.getNode1() == self) {
                other = info.getNode2();
             } else {
                other = info.getNode1();
             }
          
             if(other instanceof StaticPhysicsNode) {
                contactPointCount++;
                
                info.getContactNormal(tmpVec);
                
                if(other != info.getNode2()) {
                   tmpVec.negateLocal();
                }
                
                if(tmpVec.y > groundTriggerValue) {
                   onGround = true;
                   self.clearDynamics(); //Clear gravity
                }
                
                tmpVec.multLocal(info.getPenetrationDepth());
                
                physicDecal.addLocal(tmpVec);
             }
          }
       }
       
       private float groundTriggerValue = 0.3f;
    
       private void resetGathering() {
          onGround = false;
          contactPointCount = 0;
          physicDecal.set(0,0,0);
       }
       
       private void applyTranslation() {
          if(contactPointCount > 0) {
             physicDecal.divideLocal(contactPointCount);
             physicDecal.addLocal(nodeToControl.getLocalTranslation());
             nodeToControl.getLocalTranslation().set(
                   physicDecal
             );
          }
       }
       
       public DynamicPhysicsNode getNodeToControl() {
          return nodeToControl;
       }
    
       public void setNodeToControl(DynamicPhysicsNode nodeToControl) {
          if(nodeToControl!=this.nodeToControl) {
             if(this.nodeToControl!=null) {
                inputHandler.clearActions();
             }
             
             this.nodeToControl = nodeToControl;
             nodeToControl.setMaterial(Material.GHOST);
             inputHandler.addAction(new CollisionHandler(), nodeToControl.getCollisionEventHandler() , false);
          }
       }
    
       public boolean isOnGround() {
          return onGround;
       }
       
       public float getGroundTriggerValue() {
          return groundTriggerValue;
       }
    
       /**
        * Set the value of the up vector to see if player is on ground
        * @param groundTriggerValue
        */
       public void setGroundTriggerValue(float groundTriggerValue) {
          this.groundTriggerValue = groundTriggerValue;
       }
    
       @Override
       public void update(float time) {
          if(nodeToControl!=null) {
             resetGathering();
             inputHandler.update(time);
             applyTranslation();
          }
       }
    
       public CharacterController(DynamicPhysicsNode node) {
          setNodeToControl(node);
       }
       
    }
    

    You just have to add this controller on a dynamic physic node:

    physicNode.addController(new CharacterController(physicNode));

    It seems to work properly. Thanks to return here any issues you found ;)

    And enjoy!