PointerLockControls.js - TypeError:无法读取 HTMLDivElement 处未定义的属性(读取“锁定”)

问题描述 投票:0回答:1

我对 Javascript 和 Three.js(以及在 stackOverflow 上发帖)非常陌生。我试图通过转换此处找到的 PointerLockControls.js 的示例实现来使用 Three.js 实现第一人称相机: PointerLockControls 示例

我的问题是错误“TypeError:无法读取未定义的属性(读取“lock”) 在 HTMLDivElement 处。被抛出 - 由我的代码的第 204 行(完整代码如下)引起,其内容如下:

 // initialise locks: 
    const blocker = document.getElementById( 'blocker' );
    const instructions = document.getElementById( 'instructions' );
        
    instructions.addEventListener( 'click', function () {
    // LINE 204: ERROR
    this.controls_.lock;
    } );

示例将此锁定义为:

instructions.addEventListener( 'click', function () {

                    controls.lock();

                } );

我知道这个错误源于这样一个事实:在运行时,

this.controls_
未定义。在添加此事件侦听器之前,我创建并分配
this.controls_
。但是当我测试
this.controls_
在调用“this.controls_.lock”时是否未定义时,
this.controls_
未定义。

是否有原因导致

this.controls_
在事件侦听器中未定义?侦听器是否需要独立于定义
FirstPersonCamereaDemo
controls_
类来运行?

之前,我类似地定义了

instructions.addEventListener( 'click', function () {
            this.controls_.lock();
        } );

其中

PointerCamera.lock
定义为

lock() {
       this.controls.lock();
}

我尝试使用

const = function () {...}
重新定义锁定函数,这导致 PointerLockControls.js 文件中出现类型错误。

任何帮助将不胜感激!

完整的Javascript:

import * as THREE from '../three.js-r134/three.js-r134/build/three.module.js';
import { FirstPersonControls } from '../three.js-r134/three.js-r134/examples/jsm/controls/FirstPersonControls.js';
import { PointerLockControls } from '../three.js-r134/three.js-r134/examples/jsm/controls/PointerLockControls.js';

// class for handling the camera

// camera implemented using the example: https://threejs.org/examples/?q=pointerlock#misc_controls_pointerlock


// listener is domElement used to listen for mouse/touch events
class PointerCamera {
    constructor(camera, dElement, objects) {
        this.camera = camera;
        this.dElement = dElement
        this.objects = objects;
        this.raycaster = null;
        this.moveForward = false;
        this.moveBackward = false;
        this.moveLeft = false;
        this.moveRight = false;
        this.canJump = false;
        this.controls = null;

        //minHeight initialised to initial value of y-coordinate of camera
        this.minHeight = camera.position.y;


        // this.prevTime
        // may not need this if we pass it to the update from update in the main render

        this.velocity = new THREE.Vector3();
        this.direction = new THREE.Vector3();
        this.vertex = new THREE.Vector3();
        this.color = new THREE.Color();

        // initialise movement
        this.dElement.addEventListener( 'keydown', this.onKeyDown);
        this.dElement.addEventListener( 'keyup', this.onKeyUp);

        this.initControls();
    }

    initControls() {
        this.controls = new PointerLockControls(this.camera, this.dElement);

        // locks
        this.lock = function () {
            this.controls.lock();
        }

        this.unlock = function () {
            this.controls.unlock();
        }
    }

    changeMinHeight(newHeight) {
        const oldHeight = this.minHeight;
        this.minHeight = newHeight;

        return oldHeight;
    }

    // keydown function
    onKeyDown( event ) {

        switch ( event.code ) {

            case 'ArrowUp':
            case 'KeyW':
                this.moveForward = true;
                break;

            case 'ArrowLeft':
            case 'KeyA':
                this.moveLeft = true;
                break;

            case 'ArrowDown':
            case 'KeyS':
                this.moveBackward = true;
                break;

            case 'ArrowRight':
            case 'KeyD':
                this.moveRight = true;
                break;

            case 'Space':
                if ( canJump === true ) velocity.y += 350;
                this.canJump = false;
                break;

        }
    }
    
    //keyupfunction
    //if domElement doesn't work, try passing full document to the class.

    onKeyUp = function ( event ) {

        switch ( event.code ) {

            case 'ArrowUp':
            case 'KeyW':
                this.moveForward = false;
                break;

            case 'ArrowLeft':
            case 'KeyA':
                this.moveLeft = false;
                break;

            case 'ArrowDown':
            case 'KeyS':
                this.moveBackward = false;
                break;

            case 'ArrowRight':
            case 'KeyD':
                this.moveRight = false;
                break;

        }

    }

    update(timeElapsedS) {

        if (this.controls.isLocked === true) {

            // to adjust vertical acceleration, need to calculate whether on the ground or not.
            const delta = timeElapsedS; // time elapsed in seconds

            // may need to double check this hits 0.
            this.velocity.x -= this.velocity.x * 10 * delta;
            this.velocity.z -= this.velocity.z * 10 * delta;

            // simulate gravity: 
            this.velocity.y -= 9.8 * 100 * delta; // 100.0 = mass of player

            this.direction.z = Number(this.moveForward) - Number(this.moveBackward);
            this.direction.x = Number(this.moveRight) - Number(this.moveLeft);
            this.direction.normalize(); // ensures consistent movement in all directions


            if (this.moveForward || this.moveBackward) this.velocity.z -= this.direction.z * 400 * delta;
            if (this.moveLeft || this.moveRight) this.velocity.x -= this.direction.x * 400 * delta;


            // add object interaction with ray casting here
            
            // update camera position: 
            this.controls.moveRight( - this.velocity.x * delta);
            this.controls.moveForward( - this.velocity.z * delta);

            this.controls.getObject().position.y += ( this.velocity.y * delta ); // new behavior

            // detect if camera falls below minimum: 

            if ( controls.getObject().position.y < 10 ) {
                this.velocity.y = 0;
                this.controls.getObject().position.y = 10;
                this.canJump = true;
            }
        }
    }
}

class FirstPersonCameraDemo {
    constructor() {
        this.initialize_();
    }

    initialize_() {
        this.initializeRenderer_();
        this.initializeLights_();
        this.initializeScene_();
        this.initializeDemo_();

        this.previousRAF_ = null;
        this.raf_();
        this.onWindowResize_();
    }

    initializeDemo_() {
        this.controls_ = new PointerCamera(this.camera_, document.body, []);
        this.controls_.controls.lookSpeed = 0.8;
        this.controls_.controls.movementSpeed = 5;
        this.controls_.controls.heightCoef = 0;
        // const controlLock = function () {this.controls_.lock};

        // initialise locks: 
        const blocker = document.getElementById( 'blocker' );
        const instructions = document.getElementById( 'instructions' );
        
        instructions.addEventListener( 'click', function () {
            this.controls_.lock;
        } );

        this.controls_.controls.addEventListener( 'lock', function () {
            instructions.style.display = 'none';
            blocker.style.display = 'none';
        } );

        this.controls_.controls.addEventListener( 'unlock', function () {
            blocker.style.display = 'block';
            instructions.style.display = '';
        } );
        
        this.scene_.add(this.controls_.controls.getObject());
    }

    initializeRenderer_() {
        this.threejs_ = new THREE.WebGLRenderer({
            antialias: false,
        });
        this.threejs_.shadowMap.enabled = true;
        this.threejs_.shadowMap.type = THREE.PCFSoftShadowMap;
        this.threejs_.setPixelRatio(window.devicePixelRatio);
        this.threejs_.setSize(window.innerWidth, window.innerHeight);
        this.threejs_.physicallyCorrectLights = true;
        this.threejs_.outputEncoding = THREE.sRGBEncoding;
    
        document.body.appendChild(this.threejs_.domElement);

        window.addEventListener('resize', () => {
            this.onWindowResize_();
        }, false);


        // initialise CAMERA
        this.minHeight = 10;
        const fov = 60;
        const aspect = window.innerWidth / window.innerHeight;
        const near = 1.0;
        const far = 1000.0;
        this.camera_ = new THREE.PerspectiveCamera(fov, aspect, near, far);
        // minimum and default height (10 - change to variable later)
        this.camera_.position.set(0, this.minHeight, 0);

        this.scene_ = new THREE.Scene();
    }

    initializeScene_() {
        const loader = new THREE.CubeTextureLoader();
        const texture = loader.load([
            './bkg/bkg/red/bkg1_right1.png',
            './bkg/bkg/red/bkg1_left2.png',
            './bkg/bkg/red/bkg1_top3.png',
            './bkg/bkg/red/bkg1_bottom4.png',
            './bkg/bkg/red/bkg1_front5.png',
            './bkg/bkg/red/bkg1_back6.png'
        ]);

        this.scene_.background = texture;

        const planegeo = new THREE.PlaneGeometry(100, 100, 10, 10);
        // plane.castShadow = false;
        // plane.receiveShadow = true;
        planegeo.rotateX(-Math.PI / 2);
        const material = new THREE.MeshBasicMaterial( {color: 0xffff00, side: THREE.DoubleSide} );
        const plane = new THREE.Mesh( planegeo, material );
        this.scene_.add(plane);
    }

    initializeLights_() { 

        // his light: 
    const distance = 50.0;
    const angle = Math.PI / 4.0;
    const penumbra = 0.5;
    const decay = 1.0;

    let light = new THREE.SpotLight(
        0xFFFFFF, 100.0, distance, angle, penumbra, decay);
    light.castShadow = true;
    light.shadow.bias = -0.00001;
    light.shadow.mapSize.width = 4096;
    light.shadow.mapSize.height = 4096;
    light.shadow.camera.near = 1;
    light.shadow.camera.far = 100;

    light.position.set(25, 25, 0);
    light.lookAt(0, 0, 0);
    this.scene_.add(light);

    const upColour = 0xFFFF80;
    const downColour = 0x808080;
    light = new THREE.HemisphereLight(upColour, downColour, 0.5);
    light.color.setHSL( 0.6, 1, 0.6 );
    light.groundColor.setHSL( 0.095, 1, 0.75 );
    light.position.set(0, 4, 0);
    this.scene_.add(light);
    }

    onWindowResize_() {
        this.camera_.aspect = window.innerWidth / window.innerHeight;
        this.camera_.updateProjectionMatrix();

        this.threejs_.setSize(window.innerWidth, window.innerHeight);
    }

    raf_() {
        requestAnimationFrame((t) => {
            if (this.previousRAF_ === null) {
              this.previousRAF_ = t;
            }
      
            this.step_(t - this.previousRAF_);
            this.threejs_.autoClear = true;
            this.threejs_.render(this.scene_, this.camera_);
            console.log("just tried to render");
            this.threejs_.autoClear = false;
            this.previousRAF_ = t;
            this.raf_();
          }); 
    }

    step_(timeElapsed) {
        // console.log("in demo step");
        const timeElapsedS = timeElapsed * 0.01;
        // may need to change above to 0.001

        this.controls_.update(timeElapsedS);
    }
}

// run
let _APP = null;

window.addEventListener('DOMContentLoaded', () => {
    console.log("successful print");
    _APP = new FirstPersonCameraDemo();
});

HTML:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Trying to get basic FPS control</title>
        <style>
            body { margin: 0; }

            #blocker {
                position: absolute;
                width: 100%;
                height: 100%;
                background-color: rgba(0, 0, 0, 0.5);
            }

            #instructions {
                width: 100%;
                height: 100%;

                display: flex;
                flex-direction: column;
                justify-content: center;
                align-items: center;

                text-align: center;
                font-size: 14px;
                cursor: pointer;
            }
        </style>
    </head>
    <body>
        <div id="blocker">
            <div id="instructions">
                <p style="font-size:36px">
                    Click to play
                </p>
                <p>
                    Move: WASD<br/>
                    Jump: SPACE<br/>
                    Look: MOUSE
                </p>
            </div>
        </div>
        <script type="module" src="./fpsbasic.js"></script>
    </body>
</html>
javascript three.js pointerlock
1个回答
0
投票

解决方案是将正确的“this”“绑定”到每个新的“lock”函数,以便每个调用都有适当的范围来适当地调用“this.controls_”和“this.controls”。

此处的讨论 addEventListener 中处理函数的范围对于理解事件处理函数无法访问属于类的正确“this”非常有用,而是“this”将引用指令。

更正的定义是:

在“FirstPersonCameraDemo”中:

        instructions.addEventListener( 'click', this.controls_.lock2().bind(this));

在“PointerCamear”中:

initControls() {
    this.controls = new PointerLockControls(this.camera, this.dElement);

    // locks
    this.lock = function (e) {
        return this.controls.lock(e);
    }

    this.unlock = function () {
        this.controls.unlock();
    }
}

lock2() {return this.lock.bind(this)}
unlock2() {return this.unlock.bind(this)}
© www.soinside.com 2019 - 2024. All rights reserved.