BabylonJS 6.0文档 Deep Dive 摄像机(三):自定义摄像机输入

1. 如何自定义摄像机输入

当你调用摄像机的attachControl方法之后,摄像机都会自动为您处理输入。可以使detachControl方法撤消该控件。大多数Babylon.js专家使用两步流程来激活和连接相机:

//First, set the scene's activeCamera... to be YOUR camera.
scene.activeCamera = myCamera;
// Then attach the activeCamera to the canvas.
//Parameters: canvas, noPreventDefault
scene.activeCamera.attachControl(canvas, true);

一个简单的写法类似这样:

myCamera.attachControl(canvas);

默认情况下,noPreventDefault设置为false(上述代码后面这个布尔类型参数),这意味着在所有canvas上的鼠标单击和触摸事件中都会自动调用preventDefault函数。(不太明白preventDefault的可以百度js事件处理

Babylon.js v2.4开始引入了一种不同的方法来管理摄像机输入,即提供一种面向输入的、可组合的方式。现在,您可以使用输入管理器,每种输入都可以被视为一个插件,所有的相机都可以使用它,并通过输入类型(鼠标、键盘、游戏板、其他设备的)来的区分。

使用输入管理器,您可以添加、删除、启用或禁用摄像机可用的任何输入。您还可以轻松地实现自定义输入机制或覆盖现有的输入机制。

输入管理器可通过camera的inputs属性使用。例如:

const camera = new BABYLON.FreeCamera("sceneCamera", new BABYLON.Vector3(0, 1, -15), scene);
const inputManager = camera.inputs;

2. 配置输入

大多数输入都提供了自定义灵敏度的设置,并使其适应您自己的场景。

每种输入都提供了管理器上可用的简称。目标是在使用输入时提供友好的语法。

const camera = new BABYLON.FreeCamera("sceneCamera", new BABYLON.Vector3(0, 1, -15), scene);
camera.inputs.add(new BABYLON.FreeCameraGamepadInput());
camera.inputs.attached.gamepad.gamepadAngularSensibility = 250;

3. 添加已存在的输入

ArcRotateCamera和FreeCamera的输入管理器暴露了用于添加内置输入的函数。

const camera = new BABYLON.FreeCamera("sceneCamera", new BABYLON.Vector3(0, 1, -15), scene);
camera.inputs.addGamepad();

如果您愿意,您也可以添加定制化输入的实例(将在第6节介绍如何实现定制化输入)。

const camera = new BABYLON.FreeCamera("sceneCamera", new BABYLON.Vector3(0, 1, -15), scene);
camera.inputs.add(new BABYLON.FreeCameraGamepadInput());

4. 启用和禁用输入

当您在相机上调用attachControl函数时,将会激活输入管理器的所有输入。同样的,您可以通过调用相机上的detachControl函数来关闭所有输入。

如果您想暂时禁用输入,可以直接在某个输入上调用detachControl函数, 下面以鼠标输入举例:

const camera = new BABYLON.FreeCamera("sceneCamera", new BABYLON.Vector3(0, 1, -15), scene);
camera.inputs.attached.mouse.detachControl();
camera.inputs.addGamepad();

然后,当您想再次打开,调用attachInput函数即可。

camera.inputs.attachInput(camera.inputs.attached.mouse);

5. 删除输入

有时你想要一个非常特定的输入机制。在这种情况下,最好的方法可能是清除所有输入,只在场景中添加您可能需要的输入。

const camera = new BABYLON.FreeCamera("sceneCamera", new BABYLON.Vector3(0, 1, -15), scene);
camera.inputs.clear();
camera.inputs.addMouse();

您还可以从输入管理器中删除单个输入。您可以按实例或按类型名称删除它们。

const camera = new BABYLON.FreeCamera("sceneCamera", new BABYLON.Vector3(0, 1, -15), scene);
// remove by instance
camera.inputs.remove(camera.inputs.attached.mouse);
// remove by type
camera.inputs.removeByType("FreeCameraKeyboardMoveInput");

6. 自定义输入

要实现自定义输入则需要创建为一个函数对象,这个函数对象需要具备以下几个方法并编写实现代码。方法名称和用途如下:(完成之后,按照第2节的方式配置即可生效

// This function must return the class name of the camera, it could be used for serializing your scene
getClassName();

// This function must return the simple name that will be injected in the input manager as short hand
// for example "mouse" will turn into camera.inputs.attached.mouse
getSimpleName();

//T his function must activate your input event.  Even if your input does not need a DOM element
// element and noPreventDefault must be present and used as parameter names.
// Return void.
attachControl(noPreventDefault);

// Detach control must deactivate your input and release all pointers, closures or event listeners
// element must be present as a parameter name.
// Return void.
detachControl();

// This optional function will get called for each rendered frame, if you want to synchronize your
// input to rendering, no need to use requestAnimationFrame. It's a good place for applying
// calculations if you have to.
// Return void.
checkInputs();

7. HammerJS输入

如果ArcRotateCamera的内置的触摸控件对您来说不够强大的话,您可以使用著名的触摸手势库HammerJS当作相机输入。Github地址:https://hammerjs.github.io/ 

我们有一个如何使用HammerJS来模拟类似Google Earth控件的示例。为此,我们使用BabylonJS的ArcRotateCamera。摄影机锁定在Y轴上,水平平移沿X轴移动摄影机,垂直平移沿Z轴移动摄影机。平行移动时会考虑摄影机的alpha角度。通过捏可以更改相机的半径,以便放大和缩小。您可以通过两个手指旋转手势来更改相机的alpha角度,这样基本上就可以围绕相机目标旋转。

示例应用程序:https://demos.babylonjs.xyz/hammerjs-example/#/

GitHub仓库:https://github.com/RolandCsibrei/babylonjs-hammerjs-arc-rotate-camera

该示例的文件位于utils/ArcRotateCameraHammerJsInput.ts,它实现ICameraInput<ArcRotateCamera>接口。有多个输入参数,代码很简单且能够自解释。请参考源代码,并设置适合灵敏度参数和其他入参。

首先,您需要安装HammerJS,请参https://hammerjs.github.io/getting-started/并导入。

import "hammerjs";

要使用新的输入,请在代码中创建相机后将其添加到camera.input中。为了避免多个输入之间的冲突,请从camera.input中删除ArcRotateCameraPointersInput。创建输入后,可以设置其参数。

默认的配置适用于普通触摸屏显示器,因此可能需要根据实际情况进行修改。(请参阅https://github.com/RolandCsibrei/babylonjs-hammerjs-arc-rotate-camera/blob/680cf12155924a818faac5ff9d7f0a0271bb632b/src/utils/ArcRotateCameraHammerJsInput.ts#L21)

// remove mouse input
camera.inputs.removeByType("ArcRotateCameraPointersInput");

// add hammer js input
const hammerJsInput = new ArcRotateCameraHammerJsInput();
// now you can set the parameters you like
// let's double the zoomSensitivity (default is 1)
hammerJsInput.zoomSensitivity = 2;
// add the input to the camera
camera.inputs.add(hammerJsInput);

请随意使用这个Input类作为您自己的基于HammerJS的输入的入门。

 使用JavaScript实现自定义输入

以下代码用于使用键盘向左、向右、向前和向后移动摄影机以及在其当前位置进行旋转。

首先,删除默认的键盘输入。

camera.inputs.removeByType("FreeCameraKeyboardMoveInput");

接下来创建新的输入函数FreeCameraKeyboardRotateInput:

const FreeCameraKeyboardRotateInput = function () {
  this._keys = [];
  this.keysLeft = [37];
  this.keysRight = [39];
  this.sensibility = 0.01;
};

添加获取类名称的方法:

FreeCameraKeyboardRotateInput.prototype.getClassName = function () {
  return "FreeCameraKeyboardRotateInput";
};
FreeCameraKeyboardRotateInput.prototype.getSimpleName = function () {
  return "keyboardRotate";
};

添加attach和detach方法:

FreeCameraKeyboardRotateInput.prototype.attachControl = function (noPreventDefault) {
  const _this = this;
  const engine = this.camera.getEngine();
  const element = engine.getInputElement();
  if (!this._onKeyDown) {
    element.tabIndex = 1;
    this._onKeyDown = function (evt) {
      if (_this.keysLeft.indexOf(evt.keyCode) !== -1 || _this.keysRight.indexOf(evt.keyCode) !== -1) {
        const index = _this._keys.indexOf(evt.keyCode);
        if (index === -1) {
          _this._keys.push(evt.keyCode);
        }
        if (!noPreventDefault) {
          evt.preventDefault();
        }
      }
    };
    this._onKeyUp = function (evt) {
      if (_this.keysLeft.indexOf(evt.keyCode) !== -1 || _this.keysRight.indexOf(evt.keyCode) !== -1) {
        const index = _this._keys.indexOf(evt.keyCode);
        if (index >= 0) {
          _this._keys.splice(index, 1);
        }
        if (!noPreventDefault) {
          evt.preventDefault();
        }
      }
    };

    element.addEventListener("keydown", this._onKeyDown, false);
    element.addEventListener("keyup", this._onKeyUp, false);
    BABYLON.Tools.RegisterTopRootEvents(canvas, [{ name: "blur", handler: this._onLostFocus }]);
  }
};

FreeCameraKeyboardRotateInput.prototype.detachControl = function () {
  const engine = this.camera.getEngine();
  const element = engine.getInputElement();
  if (this._onKeyDown) {
    element.removeEventListener("keydown", this._onKeyDown);
    element.removeEventListener("keyup", this._onKeyUp);
    BABYLON.Tools.UnregisterTopRootEvents(canvas, [{ name: "blur", handler: this._onLostFocus }]);
    this._keys = [];
    this._onKeyDown = null;
    this._onKeyUp = null;
  }
};

添加检查输入的方法(可选)

FreeCameraKeyboardRotateInput.prototype.checkInputs = function () {
  if (this._onKeyDown) {
    const camera = this.camera;
    // Keyboard
    for (let index = 0; index < this._keys.length; index++) {
      const keyCode = this._keys[index];
      if (this.keysLeft.indexOf(keyCode) !== -1) {
        camera.cameraRotation.y += this.sensibility;
      } else if (this.keysRight.indexOf(keyCode) !== -1) {
        camera.cameraRotation.y -= this.sensibility;
      }
    }
  }
};

最后,把新的输入配置给相机

camera.inputs.add(new FreeCameraKeyboardRotateInput());

完整示例:

使用TypeScript实现自定义输入

使用TypeScript,您通过实现接口ICameraInput来完成这个类:

interface ICameraInput {
  // the input manager will fill the parent camera
  camera: TCamera;

  //this function must return the class name of the camera, it could be used for serializing your scene
  getClassName(): string;

  //this function must return the simple name that will be injected in the input manager as short hand
  //for example "mouse" will turn into camera.inputs.attached.mouse
  getSimpleName(): string;

  //this function must activate your input, event if your input does not need a DOM element
  attachControl: (noPreventDefault?: boolean) => void;

  //detach control must deactivate your input and release all pointers, closures or event listeners
  detachControl: () => void;

  //this optional function will get called for each rendered frame, if you want to synchronize your input to rendering,
  //no need to use requestAnimationFrame. It's a good place for applying calculations if you have to
  checkInputs?: () => void;
}

相机实现第一人称行走案例

以下的示例使用通用相机的并自定义键盘、鼠标输入。使用键盘的上下左右箭头可以在场景中向前和向后行走,并旋转以向左和向右看。使用鼠标,您可以四处查看,也可以上下查看。

在本例中,有两个视角,上面的视角为供第一人称。下面的一个为第三视角,可表示相机和周围碰撞。

请记住在使用箭头键之前单击场景。

案例地址

BabylonJS 6.0文档 Deep Dive 摄像机(三):自定义摄像机输入_第1张图片

使用BaseCameraPointersInput

除了以上第6节所述的创建自定义输入的方法外,您还可以扩展一些已实现的基类,从而更容易的实现。其中一个类是BaseCameraPointersInput:

对于Javascript(ES6+)或Typescript,您应该能够扩展BaseCameraPointersInput类的功能。从那里,您只需要覆盖一些函数。

// You need to extend the BaseCameraPointersInput to get the required functionality
class YourCustomInputClass extends BABYLON.BaseCameraPointersInput {
  // This is the constructor.  Unless you have something specific that you need
  // to do when you create your object, you don't need to implement this. You
  // must call the super() function though, if you do.
  // constructor() { super(); }

  // This is exactly the same the function in the previous section and will still need to be
  // implemented.
  getClassName() {};

  // This function is the exact same thing as the previous section.  However, it has already
  // been implemented with a value of "pointers" and is technically optional.
  // getSimpleName() {};

  // This function is already implemented.  If you are planning to use this class, it is 
  // recommened to not override it.
  // attachControl(noPreventDefault) {};

  // Same thing with detachControl
  // detachControl() {};

  // This optional function will get called for each rendered frame, if you want to synchronize your
  // input to rendering, no need to use requestAnimationFrame. It's a good place for applying
  // calculations if you have to.
  // Return void.
  checkInputs() {};

  // This function will fire during a POINTERMOVE event where there is either an active mouse 
  // button down or only one active touch.  "point" will contain the coordinates, pointerId,
  // and pointer type.  The offsets are just the changes in position from the previous point.
  // This will NOT fire if multiple touches are active.  This method is required.
  onTouch(point, offsetX, offsetY) {};

  // This function will only fire during a POINTERMOVE event where more than one touch is active.
  // This function will only support the first two active touches and all others will be ignored.
  // Points A and B are said touches.  Both previous and current pinch distances and positions are
  // available to support basic gesture logic, as needed.  As a warning, the previous movement may
  // be null at the beginning of a multi-touch movement.
  onMultiTouch(
        pointA,
        pointB,
        previousPinchSquaredDistance,
        pinchSquaredDistance,
        previousMultiTouchPanPosition,
        multiTouchPanPosition
    ) {};

  // This function will only fire during a POINTERDOUBLETAP event.  The "type" parameter
  // is just the pointer type (mouse, touch, etc.).  This is optional.
  onDoubleTap(type) {};

  // This function will fire when a contextmenu event occurs (right-click menu).
  // "evt" is the triggering event.  This is optional.
  onContextMenu(evt) {};

  // This function will fire when a POINTERDOWN event occurs.
  // "evt" is the triggering event.  This is optional.
  onButtonDown(evt) {};

  // This function will fire when a POINTERUP event occurs (right-click menu).
  // "evt" is the triggering event.  This is optional.
  onButtonUp(evt) {};

  // This function will fire when the window loses focus (eg. blur event)
  // This is optional.
  onLostFocus() {};
}

这看起来可能有很多代码,但最核心的是onTouch,表示处理单击事件(Pointer)的地方,onMultiTouch是处理至少有两个触摸源的事件的地方。

如果你发现自己在问,“使用这个与从头开始创建自己的相比有什么好处”,这里有一些好处。BaseCameraPointersInput类将自动处理各种输入和基于事件的事情,如preventDefault、pointer捕获和pointer锁定。最重要的是,事件处理由您负责。虽然走这条路的灵活性较小,但它可能更容易使用。

以下是两个版本的示例:

Javascript和 Typescript

你可能感兴趣的:(3D开发,BabylonJS,BabylonJS,Babylon.js,3d,webgl,webgpu)