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方法,判断是否相撞就可以了。

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

  • 相关阅读:
    2.SpringBoot之返回json数据
    1.SpringBoot之Helloword 快速搭建一个web项目
    Jquery获取radio单选按钮的value与后面的文字
    JS点击事件的重叠处理(多个点击事件出现冲突)
    table中的td等长(不随内容大小变化)
    使用ocupload和POI一键上传Excel并解析导入数据库
    将博客搬至CSDN
    【解决方案】SSL证书报错:X509_check_private_key:key values mismatch
    如何设置开机自动重启脚本
    [Linux命令] Top命令详解
  • 原文地址:https://www.cnblogs.com/vadim-web/p/14182942.html
Copyright © 2011-2022 走看看