首页 前端知识 Three.js使用ammo.js实现投篮游戏

Three.js使用ammo.js实现投篮游戏

2024-07-20 17:07:34 前端知识 前端哥 418 802 我要收藏

请添加图片描述
实现步骤
1.将Ammo导入 webpack使用Ammo.js - 在react中使用Ammo.js
2. 加载球架和场地模型
3. 针对模型中的每个物体的每个面进行Ammo物理构建
4. 控制相机移动
5. 创建篮球 设置篮球的球体Ammo数据
6. 按住键盘蓄力,抬起键盘投篮

代码仓库

难点:
Ammo.js对不规则形状模型添加检测

体验地址

代码:

/*
 * @Author: hongbin
 * @Date: 2023-03-08 10:59:25
 * @LastEditors: hongbin
 * @LastEditTime: 2023-03-10 20:00:30
 * @Description:篮球游戏
 */
import Layout from "@/src/components/Three/Layout";
import { ThreeHelper } from "@/src/ThreeHelper";
import { KeyBoardListener } from "@/src/ThreeHelper/utils/KeyBoardListener";
import { createRef, FC, Fragment, useImperativeHandle, useState } from "react";
import styled from "styled-components";
import * as THREE from "three";
import { Object3D } from "three";
import Ammo from "../../../../src/ThreeHelper/physics/ammo.wasm";
import { AmmoPhysics } from "../../../../src/ThreeHelper/physics/AmmoPhysics";

interface IProps {}

const percentRef = createRef<{ setPercent: (p: number) => void }>();

const Physics: FC<IProps> = () => {
    const [percent, setPercent] = useState(0);

    useImperativeHandle(
        percentRef,
        () => ({
            setPercent: (percent: number) => {
                setPercent(percent);
            },
        }),
        []
    );

    return (
        <Fragment>
            <Layout
                title={"篮球游戏 物理引擎 Ammo"}
                init={init}
                desc="使用three的Ammo助手-客户端渲染"
            />
            <Bar percent={percent} />
        </Fragment>
    );
};

export default Physics;

const Bar = styled.div<{ percent: number }>`
    width: 40vw;
    height: 1vw;
    position: fixed;
    top: 80vh;
    border: 1px solid ${(props) => (!!props.percent ? "#fff" : "transparent")};
    left: 30vw;
    transition: 0.3s;
    ::after {
        content: "";
        background: #df4b02;
        height: 100%;
        width: ${(props) => props.percent + "%"};
        position: absolute;
    }
`;

const init = (helper: ThreeHelper) => {
    helper.addAxis();
    helper.addStats();
    helper.camera.position.set(0, 1, 2);
    helper.controls.target.y += 1;
    helper.frameByFrame();

    helper.addGUI();

    Ammo().then(async (Ammo: any) => {
        const ammoPhysics = AmmoPhysics(Ammo);

        const floor = createFloor();
        helper.add(floor);
        ammoPhysics.addMesh(floor, 0, { restitution: 0.1 });

        await loadArea(helper, Ammo, ammoPhysics);

        const [keyBoardControl, person] = controlledCamera(helper, ammoPhysics);
        basketball(helper, Ammo, ammoPhysics);

        const diff = new THREE.Vector3();

        helper.animation(() => {
            diff.copy(person.position);
            const deltaTime = helper.clock.getDelta();
            keyBoardControl.update();
            ammoPhysics.stepSimulation(deltaTime, 10);
            diff.sub(person.position);
            helper.camera.position.sub(diff);
            helper.controls.target.sub(diff);
        });
    });
};

class KeyBoardControl {
    private vector = new THREE.Vector3();
    private _call?: (v: Vector3) => void;
    private scaled = 0.1;
    private readonly moveCodeEvent = {
        KeyW: (vec: Vector3) => {
            vec.z = -this.scaled;
        },
        KeyS: (vec: Vector3) => {
            vec.z = this.scaled;
        },
        KeyA: (vec: Vector3) => {
            vec.x = -this.scaled;
        },
        KeyD: (vec: Vector3) => {
            vec.x = this.scaled;
        },
        Space: (vec: Vector3) => {
            vec.y = this.scaled;
        },
    };
    private moveCode = Object.keys(this.moveCodeEvent) as Array<
        keyof typeof this.moveCodeEvent
    >;
    private KeyBoardListener = new KeyBoardListener();

    call(back: (v: Vector3) => void) {
        this._call = back;
        this.moveCode.forEach((code) => {
            this.KeyBoardListener.listenKey(code, () => {});
        });
        this.KeyBoardListener.keyBoardListen();
    }

    update() {
        if (this._call) {
            this.vector.set(0, 0, 0);

            this.moveCode.forEach((code) => {
                if (this.KeyBoardListener.listenPool[code].isPress) {
                    this.moveCodeEvent[code](this.vector);
                }
            });
            // if (this.vector.x || this.vector.y || this.vector.z) {
            this._call(this.vector);
            // }
        }
    }
}

function createFloor(): Mesh {
    const width = 50;
    const height = 2;
    const depth = 50;

    const mesh = new THREE.Mesh(
        new THREE.BoxGeometry(width, height, depth),
        new THREE.MeshPhysicalMaterial({
            color: new THREE.Color("#2f029f"),
            metalness: 0.5,
            roughness: 1,
            transparent: true,
            opacity: 0.4,
        })
    );
    mesh.position.y = -2;

    return mesh;
}

/**
 * 控制相机移动
 */
function controlledCamera(
    helper: ThreeHelper,
    ammoPhysics: ReturnType<typeof AmmoPhysics>
): [KeyBoardControl, Mesh] {
    helper.add(helper.camera);
    const keyBoardControl = new KeyBoardControl();

    const person = helper.generateRect({ width: 0.5, height: 1, depth: 0.2 });
    // helper.add(person);
    ammoPhysics.addMesh(person, 10, {
        needMove: true,
    });

    const position = new THREE.Vector3();
    const quaternion = new THREE.Quaternion();

    keyBoardControl.call((v) => {
        if (v.x || v.y || v.z) {
            position.copy(person.position);
            const angle = helper.controls.getAzimuthalAngle();
            v.applyAxisAngle(Object3D.DefaultUp, angle);
            position.add(v);

            ammoPhysics.setMeshPosition(person, position);
            ammoPhysics.setMeshQuaternion(person, quaternion);
        }
    });

    return [keyBoardControl, person];
}

/**
 * 加载场地
 */
async function loadArea(
    helper: ThreeHelper,
    ammo: any,
    ammoPhysics: ReturnType<typeof AmmoPhysics>
) {
    const gltf = await helper.loadGltf("/models/boll.glb");
    helper.add(gltf.scene);

    gltf.scene.traverse((obj) => {
        //@ts-ignore
        if (obj.isMesh) {
            ammoPhysics.addMeshByTriangle(
                obj as Mesh,
                0,
                {
                    restitution: 0.1,
                    friction: 1,
                },
                obj.name.includes("篮筐")
            );
        }
    });
}

/**
 * 篮球
 */
function basketball(
    helper: ThreeHelper,
    ammo: any,
    ammoPhysics: ReturnType<typeof AmmoPhysics>
) {
    const sphere = new THREE.Mesh(
        new THREE.SphereGeometry(0.1, 12, 12),
        new THREE.MeshPhysicalMaterial({ color: 0x4411ff })
    );
    sphere.position.copy(helper.camera.position);
    sphere.position.z -= 1;
    sphere.position.y += 1;

    ammoPhysics.addMesh(sphere, 1, { restitution: 1, needMove: true });

    const dir = new THREE.Vector3();

    let start = 0;
    let duration = 0;
    /**
     * 按下Q键 根据时间决定力度
     */
    const press = () => {
        start == 0 && (start = performance.now());
        duration = Math.min(100, (performance.now() - start) / 10);
        percentRef.current?.setPercent(duration);
    };

    /**
     * 抬起Q键
     */
    const up = () => {
        start = 0;
        percentRef.current?.setPercent(0);
        const p = helper.camera.position.clone();
        ammoPhysics.setMeshPosition(sphere, p);
        helper.camera.getWorldDirection(dir);
        const body = sphere.userData.body;
        dir.multiplyScalar(duration / 4);
        body.setLinearVelocity(new ammo.btVector3(dir.x, dir.y * 1.4, dir.z));
    };

    helper.listenKey("KeyQ", press, up);

    helper.add(sphere);

    return sphere;
}

转载请注明出处或者链接地址:https://www.qianduange.cn//article/14002.html
标签
three.js
评论
发布的文章

JQuery中的load()、$

2024-05-10 08:05:15

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!