java 保龄球游戏开发_three.js cannon.js物理引擎制作一个保龄球游戏

2023-11-20 12:30

本文主要是介绍java 保龄球游戏开发_three.js cannon.js物理引擎制作一个保龄球游戏,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

关于cannon.js我们已经学习了一些知识,今天郭先生就使用已学的cannon.js物理引擎的知识配合three基础知识来做一个保龄球小游戏,效果如下图,在线案例请点击博客原文。

558d110772ecccc449891f37ef496ad0.png

我们需要掌握的技能点,就是已经学过的cannon.js物理引擎知识、three.js车削几何体、threeBSP和简单的shaderMaterial。下面我们来详细的说一说如何制作这个游戏。

1. 设计游戏

因为我们已经使用过一些物理引擎,所以第一步我们很容易想到要用three做地面网格和墙面网格并为他们生成尺寸相当的刚体数据,这里面要求墙面和地面固定不动,所以刚体质量设为0。然后就是瓶子,瓶子我们可以直接下载模型,但是为了复习之前的知识,我选择使用车削几何体配合着色器来完成。瓶子的刚体我们暂时使用柱体来模拟(虽然和瓶子网格不匹配,但是在物理引擎中其实很少使用外形匹配的刚体,一是因为和实际的效果相差并不大,二是因为简单刚体的计算相对简单),车削几何体所需要的点我们可以通过画图或者ps来算出,让。但是cannon.js的Cylinder默认的up方向和three.js的CylinderGeometry的up方向是不同的,这里要注意。然后就是关于保龄球的设计思路,玩过保龄球的都知道,保龄球上面是有三个洞的(方便手指拿球),我们考虑使用ThreeBSP来绘制网格,相应的刚体我们使用球体即可。关于相机的控制,我们不使用控制器,在投球之前我们使用左右键来控制相机的左右移动,投球后我们让相机跟随球运动,在球发生相撞时,我们固定相机的位置。球的出射方向我们仍然使用鼠标指针控制(使用屏幕坐标转三维坐标),最后使用GUi来重置游戏即可,差不多就是这个思路,下面我们来看代码。

2. 游戏代码

代码比较简洁,有必要的我们在代码中标注。

1. 初始化刚体

initCannon() {//初始化物理世界

world = newCANNON.World();

world.gravity.set(0, -9.8, 0);

world.broadphase= newCANNON.NaiveBroadphase();

world.solver.iterations= 10;//初始化地面刚体

let groundBody = newCANNON.Body({

mass:0,

shape:new CANNON.Box(new CANNON.Vec3(groundSize.x / 2, groundSize.y / 2, groundSize.z / 2)),

position:new CANNON.Vec3(0, -groundSize.y / 2, 0),

material:new CANNON.Material({friction: 1, restitution: 0})

})

world.addBody(groundBody);//初始化墙面刚体

let wallLeftBody = newCANNON.Body({

mass:0,

shape:new CANNON.Box(new CANNON.Vec3(wallSize.x / 2, wallSize.y / 2, wallSize.z / 2)),

position:new CANNON.Vec3(-(wallSize.x + groundSize.x) / 2, wallSize.y / 2, 0),

material:new CANNON.Material({friction: 0, restitution: 0})

})

world.addBody(wallLeftBody);

let wallRightBody= newCANNON.Body({

mass:0,

shape:new CANNON.Box(new CANNON.Vec3(wallSize.x / 2, wallSize.y / 2, wallSize.z / 2)),

position:new CANNON.Vec3((wallSize.x + groundSize.x) / 2, wallSize.y / 2, 0),

material:new CANNON.Material({friction: 0, restitution: 0})

})

world.addBody(wallRightBody);//初始化保龄球刚体

sphereBody = newCANNON.Body({

mass:50,

shape:newCANNON.Sphere(sphereRadius),

position:new CANNON.Vec3(0, sphereRadius, 400),

material:new CANNON.Material({friction: 0.2, restitution: 0})

})

world.addBody(sphereBody);//初始化瓶子刚体

for(let i=0; i

let pingBody= newCANNON.Body({

mass:1,

shape:new CANNON.Cylinder(2.5,2.5,20,18),

quaternion:new CANNON.Quaternion().setFromEuler(Math.PI / 2, 0, 0),//因为柱体的up方向和three的up方向相差90度,这里我们先旋转90度让圆柱体“站起来”。

position: new CANNON.Vec3(pingPositionArray[i][0],pingPositionArray[i][1],pingPositionArray[i][2]),

material:new CANNON.Material({friction: 0.01, restitution: 1})

})

pingBodies.push(pingBody);//将瓶子刚体添加到刚体数组中,这样更容易计算

world.addBody(pingBody);

}

},

2. 初始化three.js

initThree() {//创建地面

this.initGround();//创建墙体

this.initWall();//创建瓶子 并引用

let pingMesh = this.createPing();//pingPositionArray是瓶子位置数组

for(let i=0; i

let pingMeshCopy=pingMesh.clone();

pingMeshCopy.position.set(pingPositionArray[i][0],pingPositionArray[i][1],pingPositionArray[i][2]);

pingMeshes.push(pingMeshCopy);

scene.add(pingMeshCopy);

}//创建保龄球并引用

sphereMesh = this.createSphere();

sphereMesh.position.set(0, sphereRadius, 400);

sphereMesh.rotation.set(Math.PI/ 6, 0, - Math.PI / 12);

scene.add(sphereMesh);

},

createPing() {

let points=[];//latheArray是瓶子车削几何体所需点的数组

for(let i=0; i

points.push(new THREE.Vector2(latheArray[i][0]/10, latheArray[i][1]/10))

}

let geometry= new THREE.LatheGeometry(points, 30);

geometry.computeVertexNormals();//着色器材质

let material = newTHREE.ShaderMaterial({

vertexShader: `

varying vec3 vPosition;

varying vec3 vNormal;voidmain() {

vNormal=normal;

vPosition=position;

gl_Position= projectionMatrix * modelViewMatrix * vec4( position, 1.0);

}

`,

fragmentShader: `

varying vec3 vPosition;

varying vec3 vNormal;voidmain() {//光线向量

vec3 light = vec3(10.0, 10.0, 10.0);float strength = dot(light, vNormal) /length(light);float y =vPosition.y;//在 [3.1, 3.7]和[4.2, 4.8]之间被渲染成红色并根据光线向量和法向量模拟光照

if(y < 4.8 && y > 4.2 || y < 3.7 && y > 3.1) {

gl_FragColor=vec4(1.0, 0.4 * pow(strength, 2.0), 0.4 * pow(strength, 2.0), 1.0);

}else{

gl_FragColor=vec4( 0.6 + 0.4 * pow(strength, 2.0), 0.6 + 0.4 * pow(strength, 2.0), 0.6 + 0.4 * pow(strength, 2.0), 1.0);

}

}

`,

side: THREE.DoubleSide

});

let mesh= newTHREE.Mesh(geometry, material);

mesh.quaternion.copy(new THREE.Quaternion().setFromEuler(new THREE.Euler(-Math.PI / 2, 0, 0)));//这里将柱体网格添加到group中,为的是group的旋转

let group = newTHREE.Group();

group.add(mesh);returngroup;

},

createSphere() {

let material= new THREE.MeshPhongMaterial({color: 0xEE100F, shininess: 60, specular: 0x2C85E1, side: THREE.DoubleSide});

let sphereGeometry= new THREE.SphereGeometry(sphereRadius, 40, 24);

let cylinderGeometry= new THREE.CylinderGeometry(sphereRadius/10,sphereRadius/10,sphereRadius,30);

let sphereMesh= newTHREE.Mesh(sphereGeometry, material);

let cMesh1= newTHREE.Mesh(cylinderGeometry, material);

let cMesh2=cMesh1.clone();

let cMesh3=cMesh1.clone();

cMesh1.position.set(1.14, sphereRadius, 0.67);

cMesh2.position.set(-1.14, sphereRadius, 0.67);

cMesh3.position.set(0, sphereRadius, -1.33);//构造BSP

let bsp1 = newThreeBSP(sphereMesh);

let bsp2= newThreeBSP(cMesh1);

let bsp3= newThreeBSP(cMesh2);

let bsp4= newThreeBSP(cMesh3);//用球形几何体,减去三个小的圆柱体

let resultBsp =bsp1.subtract(bsp2).subtract(bsp3).subtract(bsp4);

let resultGeom= resultBsp.toGeometry();//这里我们只需要导出几何体

resultGeom.mergeVertices();//注意这两步,不然保龄球不会计算法向量,也就不会平滑着色

resultGeom.computeVertexNormals();return newTHREE.Mesh(resultGeom, material);

},

initGround() {

let texture= new THREE.TextureLoader().load('/static/images/base/ground.jpg');

texture.wrapS= texture.wrapT =THREE.RepeatWrapping;

texture.repeat.set(1, 4);

let geometry= newTHREE.BoxBufferGeometry(groundSize.x, groundSize.y, groundSize.z);

let material= newTHREE.MeshPhongMaterial({map: texture});

let mesh= newTHREE.Mesh(geometry, material);

mesh.position.y= -groundSize.y / 2;

scene.add(mesh);

},

initWall() {

let material= new THREE.MeshLambertMaterial({color: 0x77dddd});

let geometry= newTHREE.BoxBufferGeometry(wallSize.x, wallSize.y, wallSize.z);

let leftMesh= newTHREE.Mesh(geometry, material);

let rightMesh=leftMesh.clone();

leftMesh.position.set(-(wallSize.x + groundSize.x) / 2, wallSize.y / 2, 0);

rightMesh.position.set((wallSize.x+ groundSize.x) / 2, wallSize.y / 2, 0);

scene.add(leftMesh);

scene.add(rightMesh);

},

3. 定义事件

这里我们需要鼠标mousemove事件和onkeydown,onkeyup事件

document.onkeydown = this.handler;

document.οnkeyup= this.handler;this.$refs.box.addEventListener('mousemove', event =>{//鼠标移动,屏幕二维向量转三维向量

let x = (event.clientX / window.innerWidth) * 2 - 1;

let y= - (event.clientY / window.innerHeight) * 2 + 1;

direction= new THREE.Vector3(x,y,-1).applyQuaternion(camera.getWorldQuaternion(newTHREE.Quaternion())).normalize();

})

handler(event) {var down = (event.type == 'keydown');switch(event.keyCode){case 32: {if(down && time >event.timeStamp) {

time= event.timeStamp;//time默认值为Infinity,第一次按下空格,给time赋值

} else if(down) {

relaxation= event.timeStamp - time;//持续按下,计算累积时间

} else{//根据持续时间给球初始化速度

let t = relaxation > 5000 ? 500 : relaxation / 10;

sphereBody.velocity.set(direction.x* t, direction.y * t, direction.z *t);

sphereBody.angularVelocity.set(-1,0,0);

time=Infinity;

}

}break;case 37:

camera.position.x--;

sphereBody.position.x--;break;case 39:

camera.position.x++;

sphereBody.position.x++;break;

}

},

主要代码大致就是这样,下一节还会继续cannon.js的学习。

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

这篇关于java 保龄球游戏开发_three.js cannon.js物理引擎制作一个保龄球游戏的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/394600

相关文章

Java实现检查多个时间段是否有重合

《Java实现检查多个时间段是否有重合》这篇文章主要为大家详细介绍了如何使用Java实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Java判断多个时间段是否重合的方法小结

《Java判断多个时间段是否重合的方法小结》这篇文章主要为大家详细介绍了Java中判断多个时间段是否重合的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录判断多个时间段是否有间隔判断时间段集合是否与某时间段重合判断多个时间段是否有间隔实体类内容public class D

IDEA编译报错“java: 常量字符串过长”的原因及解决方法

《IDEA编译报错“java:常量字符串过长”的原因及解决方法》今天在开发过程中,由于尝试将一个文件的Base64字符串设置为常量,结果导致IDEA编译的时候出现了如下报错java:常量字符串过长,... 目录一、问题描述二、问题原因2.1 理论角度2.2 源码角度三、解决方案解决方案①:StringBui

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

Java中ArrayList和LinkedList有什么区别举例详解

《Java中ArrayList和LinkedList有什么区别举例详解》:本文主要介绍Java中ArrayList和LinkedList区别的相关资料,包括数据结构特性、核心操作性能、内存与GC影... 目录一、底层数据结构二、核心操作性能对比三、内存与 GC 影响四、扩容机制五、线程安全与并发方案六、工程

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.

Spring AI集成DeepSeek的详细步骤

《SpringAI集成DeepSeek的详细步骤》DeepSeek作为一款卓越的国产AI模型,越来越多的公司考虑在自己的应用中集成,对于Java应用来说,我们可以借助SpringAI集成DeepSe... 目录DeepSeek 介绍Spring AI 是什么?1、环境准备2、构建项目2.1、pom依赖2.2