nipplejs.js joystick control panel

NippleJs is a js library for virtual joysticks. It is designed to provide interfaces for touch-enabled devices. It is often used in games and apps or web pages that can operate hardware devices.

In a 3D scene built using Threejs, use nippleJs to control character walking and camera following functions. The effect is as shown below:

1. Download nippleJs: Nipplejs by yoannmoinet

Or npm install:

npm install nipplejs --save

2. Create a rocker:

const joystickContainer = document.getElementById("joystick-container");
const option = {
    zone: joystickContainer,
    mode: "static", // Static mode, the joystick is fixed on the screen
    position: { top: "70%", left: "20%" }, // Initial position of the joystick
    color: "red", // Color of the joystick
}
//Initialize virtual joystick
const joystick = nipplejs.create(option);

The other configuration parameters of option are as follows:

//options parameter description
var options = {
    zone: Element, // active zone
    color: String,
    size: Integer,
    threshold: Float, // before triggering a directional event
    fadeTime: Integer, // transition time
    multitouch: Boolean,
    maxNumberOfNipples: Number, // when multitouch, what is too many?
    dataOnly: Boolean, // no dom element whatsoever
    position: Object, // preset position for 'static' mode
    mode: String, // 'dynamic', 'static' or 'semi'
    restJoystick: Boolean|Object, // Re-center joystick on rest state
    restOpacity: Number, // opacity when not 'dynamic' and rested
    lockX: Boolean, // only move on the X axis
    lockY: Boolean, // only move on the Y axis
    catchDistance: Number, // distance to recycle previous joystick in
                                    // 'semi' mode
    shape: String, // 'circle' or 'square'
    dynamicPage: Boolean, // Enable if the page has dynamically visible elements
    follow: Boolean, // Makes the joystick follow the thumbstick
};

3. Add character binding

// Main character
function host() {
    let gloader = new THREE.GLTFLoader();
    const dracoLoader = new THREE.DRACOLoader();
    dracoLoader.setDecoderPath("./plugins/Three/libs/draco/"); // Set the decoding path under public, pay attention to the last /
    gloader.setDRACOLoader(dracoLoader);
        gloader.load("./models/man.glb", (result) => {
        peopleObj = result.scene;
        peopleObj.position.set(0, 0, 1);
        peopleAnimations = result.animations;
 
        // Add the combined object to the scene
        scene.add(peopleObj);
        
        calcplayerdir(); // Calculate angle

        mixer = new THREE.AnimationMixer(peopleObj);
        mixerArr.push(mixer);
        activeAction = mixer.clipAction(peopleAnimations[1]);
        activeAction.play();
    });
}

4. Add events to the joystick

joystick
  .on("start", function (evt, data) {
    canmove = true;
    hostWalk(); //Character walking animation
    controls.enabled = false;
    lastpx = data.position.x;
    lastpy = data.position.y;
  })
  .on("move", function (evt, data) {
    // Direction does not exist
    // console.log(data.direction);
    if (data.direction) {
      canmove = true;
      peopleObj.movedistance = data.distance;
      if (data.direction) {
        playerforward.set(
          lastpx - data.position.x,
          0,
          lastpy-data.position.y
        );
      }
    }
  })
  .on("end", function (evt, data) {
    canmove = false;
    hostStop(); //The character walking animation stops
    if (!peopleObj) return;
    controls.target.copy(peopleObj.position).add(new THREE.Vector3(0, 1, 0));
    controls.enabled = true;
  });

5. Camera lens and character following

//Camera following
function cameraFollow() {
  if (!peopleObj || controls.enabled) return;

  const d = new THREE.Vector3();
  d.copy(dir);
  d.multiplyScalar(1);
  d.negate();

  const playerpos = new THREE.Vector3();
  playerpos.copy(peopleObj.position);
  playerpos.add(d);
  camera.position.copy(playerpos).add(new THREE.Vector3(0, 1, 0));
  camera.lookAt(new THREE.Vector3(peopleObj.position.x, peopleObj.position.y + 1, peopleObj.position.z));
}
//character walking
function run(delta) {
    if (qqq) {
        const forward = new THREE.Vector3(0, 0, 0);
        forward.copy(playerforward);
        forward.applyQuaternion(qqq);
        forward.normalize();
        forward.multiplyScalar(movedistance * 0.01 * delta);
        const p = new THREE.Vector3().copy(peopleObj.position);
        p.add(forward);
        peopleObj.lookAt(p);
        peopleObj.position.copy(p);
    }
}

6. Calculate the angle

//After touching or raising the mouse, recalculate the angle
renderer.domElement.addEventListener("touchend", () => {
  calcplayerdir();
});
renderer.domElement.addEventListener("mouseup", () => {
  calcplayerdir();
});

/* Calculate angle*/
function calcplayerdir() {
  const campos = new THREE.Vector3();
  campos.copy(camera.position);
  const playerpos = new THREE.Vector3();
  playerpos.copy(peopleObj.position);
  campos.y = playerpos.y;
  dir = playerpos.sub(campos);
  console.log("ddd: ", dir);

  const d = new THREE.Vector3();
  d.copy(dir);
  d.multiplyScalar(2);
  const p = new THREE.Vector3().copy(peopleObj.position);
  p.add(d);
  const player1 = peopleObj.clone();
  player1.lookAt(p);
  player1.updateMatrixWorld();
  player1.visible = false;
  qqq = new THREE.Quaternion();
  qqq.copy(player1.quaternion);
}

7. Put it in render to render

controls.update(dt);
if (peopleObj) {
        if (canmove) {
        run(dt);
        }
        cameraFollow();
  }

Changing the joystick style and only changing the color can be achieved through the parameters of option. First, make the disc look like a picture you designed, and then change the style. Mainly add !important to enforce constraints.

.back{
    background: url(images/back_bg.png) no-repeat center !important;
    background-size: 100% !important;
}
.front{
    background: url(images/front_bg.png) no-repeat center !important;
    background-size: 100% !important;
}

This is the end of the case for using nippleJs to create a joystick to control characters and lenses~