zoukankan      html  css  js  c++  java
  • 制作3D小汽车游戏(下)

    书接上回,这一节我们分模块说一说怎么写一个这样的游戏

    1. 初始化场景、相机和渲染器

    这几乎是绘制three必须做的事情,我们有两套场景和相机,一个是主场景和相机,另一个是小地图的场景和相机(用来俯视建筑和小汽车),渲染器设置一级曝光,输出编码设置为sRGBEncoding,代码如下。

    scene = new THREE.Scene();
    scene.background = new THREE.Color(0x8FBCD4);
    scene.fog = new THREE.Fog(0x8FBCD4, 3000, 4000);
    
    camera = new THREE.PerspectiveCamera(60, window.innerWidth/window.innerHeight, 0.1, 10000);
    camera.position.set(10,10,10);
                
    scene2 = new THREE.Scene();
    scene2.background = new THREE.Color(0xffffff);
    
    camera2 = new THREE.OrthographicCamera(-400, 400, 400, -400, 1, 1000);
    camera2.position.set(0, 1000, 0);
    camera2.lookAt(0,0,0);
    
    renderer = new THREE.WebGLRenderer({antialias: true});
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.outputEncoding = THREE.sRGBEncoding;
    renderer.toneMapping = THREE.ACESFilmicToneMapping;
    renderer.toneMappingExposure = 1;
    this.$refs.box.appendChild(renderer.domElement);

    2. 设置地面和建筑

    地面很简单,就是一个plane

    initGround() {
        const ground_geom = new THREE.PlaneBufferGeometry(8000, 8000);
        const ground_mate = new THREE.MeshLambertMaterial({color: 0xBCD48F, side: THREE.DoubleSide});
        const ground_mesh = new THREE.Mesh(ground_geom, ground_mate);
        ground_mesh.rotation.x = - Math.PI / 2;
        scene.add(ground_mesh);
    },

    设置建筑,我们需要给每一个建筑设置长宽高、颜色、位置,并把它们放到一个组里,然后然需要给每一个建筑初始化一个OBB,并把这些OBB信息添加到一个数组中,便于我们日后做碰撞检测

    initBuild(num) {
        let color = new THREE.Color();
        let build = new THREE.Group();
        for(let i=0; i<num; i++) {
            let w = Math.random() * 50 + 50;
            let h = Math.random() * 100 + 100;
            let d = Math.random() * 50 + 50;
            let x = Math.random() * 8000 - 4000;
            let z = Math.random() * 8000 - 4000;
            if((x * x + z * z) < Math.pow(140, 2)) {
                //40为车半长的估计值
                x = Math.pow(140, 2) / x;
                z = Math.pow(140, 2) / z;
            }
            let geometry = new THREE.BoxBufferGeometry(w, h, d);
            let material = new THREE.MeshStandardMaterial({color: new THREE.Color().setHSL(Math.random(), 1.0, 0.6)});
            let mesh = new THREE.Mesh(geometry, material);
            mesh.position.set(x, h / 2, z);
            build.add(mesh);
            let obb = new OBB();
            buildObbArray.push(obb.set(new THREE.Vector3(x, h / 2, z), new THREE.Vector3(w/2, h/2, d/2), new THREE.Matrix3()));
        }
        scene.add(build);
        scene2.add(build.clone());
    },

    3. 初始化小汽车

    这里我们要下载好一个小汽车的模型,首先把模型设置成我们想要的大小,这里车高设置成10,其他维度等比例改变,然后找到方向盘,轮子等部分,添加到全局的组中,便于我们控制。

    initCar() {
        const shadowTexture = new THREE.TextureLoader().load('/static/gltf/super_car/super_car_ao.png');
        const loader = new GLTFLoader();
        const dracoLoader = new DRACOLoader();
        dracoLoader.setDecoderPath('/static/gltf/');
        loader.setDRACOLoader(dracoLoader);
        loader.load('/static/gltf/super_car/super_car.glb', gltf => {
            const model = gltf.scene.children[0];
            model.rotation.y = -Math.PI / 2;
            steering_wheel = model.getObjectByName('steering_wheel');[]
            
            const shadow = new THREE.Mesh(
                new THREE.PlaneBufferGeometry( 0.655 * 4, 1.3 * 4 ),
                new THREE.MeshBasicMaterial( {
                    map: shadowTexture, blending: THREE.MultiplyBlending, toneMapped: false, transparent: true
                } )
            );
            shadow.position.y = 0.1;
            shadow.rotation.x = - Math.PI / 2;
            model.add(shadow);
    
            const size = new THREE.Box3().setFromObject(model).getSize(new THREE.Vector3());
            model.scale.copy(new THREE.Vector3().addScalar(carHeight / size.y));
            carHalfSize = new THREE.Box3().setFromObject(model).getSize(new THREE.Vector3()).multiplyScalar(0.4);
            
            car.add(model);
            tyreArray.push(car.getObjectByName('wheel_fl'),car.getObjectByName('wheel_fr'),car.getObjectByName('wheel_rl'),car.getObjectByName('wheel_rr'));
            car.userData.obb = new OBB(new THREE.Vector3(0,5,0), carHalfSize, new THREE.Matrix3());
            scene.add(car);
            orthoCar = new THREE.Mesh(new THREE.SphereBufferGeometry(20, 20), new THREE.MeshBasicMaterial({color: 0xff0000, side: THREE.DoubleSide}));
            orthoCar.rotation.x = - Math.PI / 2;
            scene2.add(orthoCar);
        } );
    },

    4. 添加事件、转弯、增减速和切换视角

    这里我们主要使用q–切换视角,a,d–转弯,w,s–加减速。

    document.addEventListener('keypress', event => {
        if(event.key == 'd') {
            this.turn(0);
        } else if(event.key == 'a') {
            this.turn(1);
        } else if(event.key == 'w') {
            this.speed(1);
        } else if(event.key == 's') {
            this.speed(0)
        } else if(event.key == 'q') {
            view = view == 0 ? 1 : 0;
        }
    })

    对于速度的控制,sp代表左右方向

    speed(sp) {
        if(sp == 0 && speed > 0) {
            speed -= 2;
        } else if(sp == 0 && speed > -10) {
            speed -= 0.5;
        } else if(sp == 1 && speed < 40) {
            speed += 0.5;
        }
    },

    对于转弯的控制,我们用多段控制模拟非线性

    turn(direct) {
        //模拟非线性转向
        if(direct == 0 && rotateTyre > -rotateMax * 0.5) {
            rotateTyre -= 0.02;
        } else if (direct == 0 && rotateTyre > -rotateMax * 0.8) {
            rotateTyre -= 0.04;
        } else if (direct == 0 && rotateTyre > -rotateMax) {
            rotateTyre -= 0.06;
        } else if(direct == 1 && rotateTyre < rotateMax * 0.5) {
            rotateTyre += 0.02;
        } else if(direct == 1 && rotateTyre < rotateMax * 0.8) {
            rotateTyre += 0.04;
        } else if(direct == 1 && rotateTyre < rotateMax) {
            rotateTyre += 0.06;
        }
        tyreArray[0].rotation.y = rotateTyre;
        tyreArray[1].rotation.y = rotateTyre;
        //方向盘
        steering_wheel.rotation.y = - rotateTyre;
    },

    5. 渲染

    因为我们有两个场景要渲染,这里就选择渲染两次

    render() {
        stats.update();
        this.run();
        renderer.setScissor( 0, 0, window.innerWidth, window.innerWidth );
        renderer.setViewport( 0, 0, window.innerWidth, window.innerHeight );
        renderer.setScissorTest(true);
        renderer.render( scene, camera );
        renderer.setScissor( 0, 0, window.innerHeight/4, window.innerHeight/4 );
        renderer.setViewport( 0, 0, window.innerHeight/4, window.innerHeight/4);
        renderer.setScissorTest(true);
        renderer.render( scene2, camera2 );
        this.globalID = requestAnimationFrame(this.render);
    }

    run方法里面控制着车的角度,车子的位置,轮子的传动,相机的位置,相机的lookAt,以及碰撞检测,这里面有我们上一节复习的有向包围盒OBB和欧拉角的使用

    run() {
        let delta = - clock.getDelta();
        //轮胎转动∝速度
        tyreArray.forEach(d => d.rotation.copy(new THREE.Euler(delta * speed + d.rotation.x, d.rotation.y, d.rotation.z, 'ZYX')));
        //rotateOffset 旋转偏移量  rotateTyre轮胎偏转  rotateCorrection偏转系数  speed车速
        let rotateOffset = Math.sin(rotateTyre) * rotateCorrection * speed;
        //rotateRun 旋转偏移总量
        rotateRun += rotateOffset;
        //rotateVector 车前进方向向量(不断乘offset得到)
        rotateVector.applyAxisAngle(new THREE.Vector3(0,1,0), rotateOffset);
        //车x和z方向增加量 ∝车速
        car.position.x += speed * speedCorrection * rotateVector.x;
        car.position.z += speed * speedCorrection * rotateVector.z;
        camera2.position.set(car.position.x, 1000, car.position.z);
        camera2.lookAt(car.position.x, 10, car.position.z);
        orthoCar.position.copy(car.position);
        //车身旋转 使用 旋转偏移总量rotateRun
        car.rotation.y = rotateRun;
        //切换视角
        if(view == 0) {
            camera.position.set(car.position.x - 3 * Math.sin(rotateRun), 8, car.position.z - 3 * Math.cos(rotateRun));
            camera.lookAt(camera.position.x + Math.cos(rotateRun), 8, camera.position.z - Math.sin(rotateRun));
        } else {
            camera.position.set(car.position.x + 50 * Math.cos(rotateRun + Math.PI * 0.9), 20, car.position.z - 50 * Math.sin(rotateRun + Math.PI * 0.9));
            camera.lookAt(camera.position.x + Math.cos(rotateRun), 19.9, camera.position.z - Math.sin(rotateRun));
        }
        //判断是否碰撞
        car.userData.obb.set(car.position, carHalfSize, new THREE.Matrix3().setFromMatrix4(car.matrixWorld));
        const obb = car.userData.obb;
        for(let i=0; i<buildObbArray.length; i++) {
            const obbTest = buildObbArray[i];
            if(obb.intersectsOBB(obbTest) === true) {
                speed = 0;
            }
        }
    },

    这里我们直接遍历建筑的OBB数组然后通过intersectsOBB方法,判断是否相撞就可以了。

    转载请注明地址:郭先生的博客

  • 相关阅读:
    iot 表索引dump《2》
    heap表和iot表排序规则不同
    Cannot complete the install because one or more required items could not be found.
    iot表输出按主键列排序,heap表不是
    iot 表主键存放所有数据,且按数据插入顺序排序
    iot表和heap表排序规则不同
    org.eclipse.graphiti.ui.editor.DiagramEditorInput.
    Oracle 排序规则
    perl 异步超时 打印错误
    14.6.3 Grouping DML Operations with Transactions 组DML操作
  • 原文地址:https://www.cnblogs.com/vadim-web/p/14182942.html
Copyright © 2011-2022 走看看