当前位置:网站首页>用threejs 技术做游戏跑酷
用threejs 技术做游戏跑酷
2022-07-29 05:09:00 【小豆包3D世界】
创建画布,相机
container = document.getElementById('threejs')
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 300000 )
camera.position.set( 0, 150, 400 )
scene = new THREE.Scene()
// 设置画布背景透明
scene.background = null
创建灯光
const hemiLight = new THREE.HemisphereLight( 0xffffff, 0x444444 )
hemiLight.position.set( 0, 200, 0 )
scene.add( hemiLight )
# new THREE.HemisphereLight 可以创建更加贴近自然的户外光照效果
# color:从天空发出的光线的颜色
# groundColor:从地面发出的光线的颜色
# intensity:光源照射的强度。默认值为:1。
# position:光源在场景中的位置。默认值为:(0, 100, 0)
# visible:设为 ture(默认值),光源就会打开。设为 false,光源就会关闭。
const dirLight = new THREE.DirectionalLight( 0xffffff )
dirLight.position.set( 0, 200, 100 )
// dirLight.castShadow = true
dirLight.shadow.camera.top = 180
dirLight.shadow.camera.bottom = - 100
dirLight.shadow.camera.left = - 120
dirLight.shadow.camera.right = 120
scene.add( dirLight )
/** new THREE.DirectionalLight 平行光 被平行光照亮的整个区域接收到的光强是一样的。光是平行的 directionalLight.shadow.camera.near = 20; //产生阴影的最近距离 directionalLight.shadow.camera.far = 200; //产生阴影的最远距离 directionalLight.shadow.camera.left = -50; //产生阴影距离位置的最左边位置 directionalLight.shadow.camera.right = 50; //最右边 directionalLight.shadow.camera.top = 50; //最上边 directionalLight.shadow.camera.bottom = -50; //最下面 //这两个值决定使用多少像素生成阴影 默认512 directionalLight.shadow.mapSize.height = 1024; directionalLight.shadow.mapSize.width = 1024; */
创建材质和地面
floor = new THREE.MeshPhongMaterial( {
color: '#ffffff'
} )
/** THREE.MeshPhongMaterial,可以创建一种光亮的材质 它可以模拟具有镜面高光的光泽表面(如上漆木材) ambient 这是材质的环境色。它与上一章讲过的环境光源一起使用。这个颜色会与环境光源提供的颜色相乘。默认值为白色 emissive 这是该材质发射的颜色。它其实并不想一个光源,只是一种纯粹的、不受其他光照影响的颜色。默认值为黑色。 specular 该属性指定该材质的光亮程度及高光部分的颜色。如果将它设置成与color属性相同的颜色,将会得到一个更加类似金属的材质。如果将它设置成灰色(grey),材质将变得更像塑料 shininess 该属性指定镜面高光部分的亮度。默认值为30 metal 如果此属性设置为true,Three.js会使用稍微不同的方式计算像素的颜色,以使物体看起来更像金属。要注意的是,这个效果非常小 wrapAround 如果这个属性设置为true,则启动半lambert光照技术。有了它,光下降得更微妙。如果网格有粗糙、黑暗的地区,启用此属性阴影将变得柔和并且分布更加均匀 wrapRGB 当wrapAround属性设置为true时,可以使用THREE.Vector3来控制光下降得速度 */
const loaderImage = new THREE.TextureLoader()
/** loader 图片资源 */
loaderImage.load(paodaoPng, (texture) => {
texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.RepeatWrapping
texture.repeat.set(1, 15)
floor.map = texture
floorMesh = new THREE.Mesh( new THREE.PlaneGeometry( 150, 21000 ), floor )
floorMesh.rotation.x = - Math.PI / 2
floorMesh.receiveShadow = true
scene.add( floorMesh )
},
(xhr) => {
// console.log( (xhr.loaded / xhr.total * 100) + '% loaded' )
},
(error) => {
console.log( 'error' )
},
)
/** PlaneGeometry 是二维平面几何体,看上去是扁平的,因为它只有两个维度,给定宽高,即可创建这种几何体 PlaneGeometry(width : Float, height : Float, widthSegments : Integer, heightSegments : Integer) width:沿着X轴的宽度,默认值为1 height:沿着Y轴的高度,默认为1 widthSegments :宽度分段数,默认为1 heightSegments:高度分段数,默认为1 */
创建左右两边地面
const loaderImageLeftRight = new THREE.TextureLoader()
loaderImageLeftRight.load(`${
hrefs.value}${
dir.value}/a.png`, (texture2) => {
texture2.wrapS = THREE.RepeatWrapping
texture2.wrapT = THREE.RepeatWrapping
texture2.repeat.set(10, 20)
floor2.map = texture2
floorMesh2 = new THREE.Mesh( new THREE.PlaneGeometry( 1100, 20000), floor2 )
floorMesh2.rotation.x = - Math.PI / 2
floorMesh2.position.x = -626
floorMesh2.name = 'floorMesh2'
floorMesh3 = new THREE.Mesh( new THREE.PlaneGeometry( 1100, 20000), floor2 )
floorMesh3.rotation.x = - Math.PI / 2
// floorMesh2.rotation.y = - Math.PI / 1
floorMesh3.position.x = 628
floorMesh3.name = 'floorMesh3'
scene.add( floorMesh2, floorMesh3)
}, (xhr) => {
console.log( (xhr.loaded / xhr.total * 100) + '% loaded' )
}, (error) => {
console.log('error')
})
创建路灯
importGltfLoader('Light.gltf', `${
hrefs.value}${
dir.value}/d`, 'Light')
const importGltfLoader = (gltfurl: string, setPath: string, type?: string) => {
let gltfLoader = new GLTFLoader()
gltfLoader.setPath(setPath)
gltfLoader.load(gltfurl, (gltf) => {
if (gltf) {
if (gltf.scene && gltf.scene.children && gltf.scene.children.length > 0) {
if (type === 'light') {
gltf.scene.children.map((item) => {
// item.scale.setScalar(200000000000)
// item.position.set(0, 0, 0)
})
gltf.scene.scale.setScalar(0.9)
gltf.scene.name = 'dcw-glob'
gltf.scene.position.y = 0
gltf.scene.position.x = 0
gltf.scene.position.z = -300
a = gltf.scene.clone()
}
}
}, (xhr) => {
// called while loading is progressing
// console.log(`${(xhr.loaded / xhr.total * 100)}% loaded`)
},
(error) => {
// called when loading has errors
// console.error('An error happened', error)
})
}
// new GLTFLoader() loading 路灯模型
创建业务logo 模型 建筑模型
const importObj = () => {
let mtlLoader = new MTLLoader()
mtlLoader.setPath(`${
hrefs.value}${
dir.value}b/`)
//加载mtl文件
mtlLoader.load('city.mtl', function (material) {
let objLoader = new OBJLoader()
//设置当前加载的纹理
objLoader.setMaterials(material)
objLoader.setPath(`${
hrefs.value}${
dir.value}/b`)
objLoader.load('a.obj', function (object) {
if (object.children && object.children.length > 0) {
for(let i=0; i< object.children.length; i++) {
if(i === 2) {
cityMesh = object.children[185].clone()
cityMesh.name = 'city-dcw'
}
if(i === 1) {
cityMesh2 = object.children[185].clone()
cityMesh.name = 'city-dcw'
}
}
// let scale = new colorsFn().chroma.scale(['yellow', '008ae5'])
// let color = scale(Math.random()).hex()
cityMesh.position.set(-1682, -2, -2000)
cityMesh.scale.setScalar(8.8)
cityMesh.material.map((item) => {
item.color = new THREE.Color('#1890ff')
item.transparent = true
item.opacity = 0.6
})
cityMesh2.position.set(-2400, -2, -4500)
cityMesh2.scale.setScalar(8.8)
// cityMesh2.material.color = new THREE.Color('yellow')
cityMesh2.material.transparent = true
cityMesh2.material.opacity = 0.7
cityGroup.add(cityMesh, cityMesh2)
let image = new THREE.TextureLoader()
image.load(`${
hrefs.value}${
dir.value}/static/logo/b.png`, (img) => {
img.wrapS = THREE.RepeatWrapping
img.wrapT = THREE.RepeatWrapping
img.matrixAutoUpdate = false
// img.repeat.set(1, 1)
let imgM = new THREE.MeshBasicMaterial({
map: img,
color: '#ffffff',
transparent: true,
opacity: 0.9
})
image.load(`${
hrefs.value}${
dir.value}logo/xiaodoubao.png`, (img) => {
img.wrapS = THREE.RepeatWrapping
img.wrapT = THREE.RepeatWrapping
img.matrixAutoUpdate = false
let imgM = new THREE.MeshBasicMaterial({
map: img,
color: '#ffffff',
transparent: true,
opacity: 0.8,
// wireframe: true
})
const geometry = new THREE.BoxGeometry( 185.1, 49.0, 49.1)
cityPaiMaiLogo = new THREE.Mesh(geometry, imgM)
cityPaiMaiLogo.rotation.x = Math.PI * 1.5
cityPaiMaiLogo.position.set(-590, 20, -3300)
cityGroup.add(cityPaiMaiLogo)
})
})
});
}
// let mtlLoader = new MTLLoader() obj 模型loding
添加游戏主角
const loader = new FBXLoader()
loader.load( `${
hrefs.value}${
dir.value}/d/a.fbx`, function ( object ) {
object.name = 'a-dcw'
mixer = new THREE.AnimationMixer( object )
object.scale.setScalar(0.08)
object.rotateY(Math.PI * 3)
// object.traverse( function ( child ) {
// if ( (child as any).isMesh ) {
// child.castShadow = true
// child.receiveShadow = true
// }
// } )
object.position.y = 25
scene.add(object)
aBox3d = new THREE.Box3()
aBox3d.setFromObject(object)
aBox3d.name = 'a-dcw-box3'
aBoxHelper = new THREE.BoxHelper(object, 0xff0000)
scene.add(aBoxHelper)
})
// const loader = new FBXLoader() // loding 游戏主角模型
// 添加游戏动态及动画 mixer = new THREE.AnimationMixer( object )
渲染画布设置画布大小
renderer = new THREE.WebGLRenderer( {
alpha: true, antialias: true } )
renderer.setPixelRatio( window.devicePixelRatio )
renderer.setSize( window.innerWidth, window.innerHeight )
// renderer.shadowMap.enabled = true
container.appendChild( renderer.domElement )
renderer.setClearColor(0xEEEEEE, 0.0)
// 设置背景透明
scene.background = null
模型动起来
animate()
floor.map.offset.y += (0.0009 * globSpeed.value) // 跑道走动
// 创建金币及障碍物
const runIf = randomInt(0, 3)
const numberGlob = randomInt(1, 15)
const Y = [20, 45, 65]
const numberY = randomInt(0, 3)
const difficulty = randomInt(0, 3)
const numberZ = randomInt(1000, 1500)
const time = randomInt(1200, 2800)
numberGlobNumber.value ++
if (numberGlobNumber.value % 2 === 1) {
globs(runIf, numberGlob, Y[numberY], difficulty, numberZ, time)
}
// 金币Z 位置
const meshPosition = (cloneMesh, numberX, numberY, numberZ, Z, i, type) => {
const mesh = cloneMesh.clone()
mesh.position.set(numberX, numberY, -i * numberZ - zNumber.value - Z)
mesh.name = `glob${
i}`
globGroup.add(mesh)
}
// 障碍物Z位置
const meshPositionObstacles = (cloneMesh, numberX, numberY, numberZ, Z, i, type) => {
const mesh = cloneMesh.clone()
mesh.position.set(numberX, numberY, -i * numberZ - zNumber.value - Z)
mesh.name = `obstacle`
globGroup.add(mesh)
}
// 更新模型动画
const delta = clock.getDelta()
if ( mixer ) mixer.update( delta )
//
撞击金币及障碍物逻辑
for(const item of globGroup.children) {
// 创建撞击盒子
let itemBox3 = new THREE.Box3()
itemBox3.setFromObject(item)
// 看看是否碰撞到
if (aBox3d) {
if (aBox3d.intersectsBox(itemBox3)) {
// 障碍物撞击
if (item.name === "obstacle") {
item.name = 'start-on'
// 没积分没复活卡 吐司后跳转结束页面
if (resurrectionSkp.value <= 0) {
const gameNoHref = encodeURIComponent(window.btoa(gameNo.value))
window.location.replace(`${
hrefs.value}/b.html`)
start.value = false
} else {
start.value = false
if ((integralsNumber.value <=1 && resurrection.value <= 0) ) {
TostSkipOver.value = true
globGroup.traverse(function(obj) {
if (obj.type === 'Mesh') {
(obj as any).geometry.dispose();
(obj as any).material.dispose();
}
})
window.location.replace(`${
hrefs.value}/over.html`)
} else {
itemBox3.makeEmpty()
itemBox3 = null
globGroup.remove(item)
globGroup.traverse(function(obj) {
if (obj.type === 'Mesh') {
(obj as any).geometry.dispose();
(obj as any).material.dispose();
}
})
scene.remove(globGroup)
processTimeFn()
closeHelp.value = true
closeRequestAnimationFrame.value = true
setTimeout(() => {
globGroup = new THREE.Group()
scene.add(globGroup)
}, 11000)
}
}
}
// 金币撞击
if (item.name !== "obstacle") {
if (globNumber.value >= 1000) {
} else {
if (!A.value) {
} else {
globNumber.value++
globNumberCopy.value++
}
}
globGroup.remove(item)
}
}
} else {
if (item.position.z - 210 >= -zNumber.value) {
globGroup.remove(item)
}
}
}
}
操控 手机端上下左右滑动
const EventUtil = {
addHandler: function (element, type, handler) {
if (element.addEventListener)
element.addEventListener(type, handler, false);
else if (element.attachEvent)
element.attachEvent("on" + type, handler);
else
element["on" + type] = handler;
},
removeHandler: function (element, type, handler) {
if(element.removeEventListener)
element.removeEventListener(type, handler, false);
else if(element.detachEvent)
element.detachEvent("on" + type, handler);
else
element["on" + type] = handler;
},
/** * 监听触摸的方向 * @param target 要绑定监听的目标元素 * @param isPreventDefault 是否屏蔽掉触摸滑动的默认行为(例如页面的上下滚动,缩放等) * @param upCallback 向上滑动的监听回调(若不关心,可以不传,或传false) * @param rightCallback 向右滑动的监听回调(若不关心,可以不传,或传false) * @param downCallback 向下滑动的监听回调(若不关心,可以不传,或传false) * @param leftCallback 向左滑动的监听回调(若不关心,可以不传,或传false) */
listenTouchDirection: function (target, isPreventDefault, upCallback, rightCallback, downCallback, leftCallback) {
this.addHandler(target, "touchstart", handleTouchEvent, {
passive: false });
this.addHandler(target, "touchend", handleTouchEvent);
this.addHandler(target, "touchmove", handleTouchEvent);
var startX;
var startY;
function handleTouchEvent(event) {
switch (event.type){
case "touchstart":
startX = event.touches[0].pageX;
startY = event.touches[0].pageY;
break;
case "touchend":
var spanX = event.changedTouches[0].pageX - startX;
var spanY = event.changedTouches[0].pageY - startY;
if(Math.abs(spanX) > Math.abs(spanY)){
//认定为水平方向滑动
if(spanX > 0){
//向右
if(rightCallback)
rightCallback()
} else if(spanX < 0){
//向左
if(leftCallback)
leftCallback()
}
} else {
//认定为垂直方向滑动
if(spanY > 0){
//向下
if(downCallback)
downCallback();
} else if (spanY < 0) {
//向上
if(upCallback)
upCallback();
}
}
break;
case "touchmove":
//阻止默认行为
if(isPreventDefault)
event.preventDefault();
break;
}
}
}
};
export default EventUtil
// 实例化操控组件
EventUtil.listenTouchDirection(document, true, up, right, down, left)
const up = () => {
if(start.value) {
if (!upStop.value) {
upStop.value = true
if (scene && scene.getObjectByName) {
let a = scene.getObjectByName('a-dcw')
const aY = JSON.stringify(a.position.y + 120)
const aCY = JSON.stringify(a.position.y)
let y = false
time = setInterval(() => {
if (a.position.y >= aY) {
y = true
if (a.position.y <= aCY) {
clearInterval(time)
}
a.position.y = a.position.y - 7
if (a.position.z > 0) {
a.position.z = 100
}
if (a.position.z < -4500) {
a.position.z = -4500
}
aBox3d = new THREE.Box3()
aBox3d.setFromObject(a)
aBoxMove(a)
} else {
if (!y) {
a.position.y = a.position.y + 5
if (a.position.z > 0) {
a.position.z = 100
}
if (a.position.z < -4500) {
a.position.z = -4500
}
aBox3d = new THREE.Box3()
aBox3d.setFromObject(a)
aBoxMove(a)
} else if (y){
a.position.z = a.position.z - 18
a.position.y = a.position.y - 7
if (a.position.z > 0) {
a.position.z = 100
}
if (a.position.z < -4500) {
a.position.z = -4500
}
aBox3d = new THREE.Box3()
aBox3d.setFromObject(a)
if (a.position.y <= 0) {
upStop.value = false
clearInterval(time)
atime = setInterval(() => {
a.position.z = a.position.z + 5
if (a.position.z > 0) {
a.position.z = 100
}
if (a.position.z < -4500) {
a.position.z = -4500
}
aBox3d = new THREE.Box3()
aBox3d.setFromObject(a)
if (a.position.z >= -20 || !start.value) {
a.position.z = 0
aBox3d = new THREE.Box3()
aBox3d.setFromObject(a)
clearInterval(atime)
}
}, 3)
}
aBoxMove(a)
}
}
}, 12)
}
}
}
}
const right = () => {
if (start.value) {
if (scene && scene.getObjectByName) {
const a = scene.getObjectByName('a-dcw')
if (a.position.x > 30) {
} else {
a.position.x = a.position.x + 40
// aBox3d = new THREE.Box3()
// aBoxHelper = new THREE.BoxHelper(a, 0xff0000)
// scene.add(aBoxHelper)
aBox3d.setFromObject(a)
aBoxMove(a)
}
}
}
}
const down = () => {
// console.log("action:down");
}
const left = () => {
if (start.value) {
if (scene && scene.getObjectByName) {
const a = scene.getObjectByName('a-dcw')
if (a.position.x < -35) {
} else {
a.position.x = a.position.x - 40
aBox3d = new THREE.Box3()
aBox3d.setFromObject(a)
aBoxMove(a)
}
}
}
}
扫码访问小豆包
扫码关注小豆包公众号
边栏推荐
- 小程序中的DOM对象元素块动态排序
- Abstract classes and interfaces
- 整数溢出和打印
- B - identify floating point constant problems
- 题解:在一个排序数组中查找元素第一个和最后一个的位置 (个人笔记)
- ClickHouse学习(二)ClickHouse单机安装
- ClickHouse学习(十)监控运行指标
- 关于局部变量
- Solution: find the position of the first and last element in a sorted array (personal notes)
- 【C语言系列】—深度解剖数据在内存中的存储(二)-浮点型
猜你喜欢
C语言 一级指针
Day 1
Alibaba cloud architect Liang Xu: MES on cloud box helps customers quickly build digital factories
【C语言系列】— 打印100~200之间的素数
Alibaba cloud architect details nine trends in the game industry
省市区三级联动(简单又完美)
shell基本操作(上)
浅谈范式
ClickHouse学习(八)物化视图
Best practices of JD cloud Distributed Link Tracking in financial scenarios
随机推荐
副作用和序列点
【剑指offer】— 详解库函数atoi以及模拟实现atoi函数
全局components组件注册
With cloud simulation platform, Shichuang technology supports the upgrading of "China smart manufacturing"
AD常用快捷键
无重复字符的最长字串
一维数组练习
Cryengine3 debugging shader method
Realize simple database query (incomplete)
·来一篇编程之路的自我介绍吧·
哈夫曼树以及哈夫曼编码在文件压缩上的应用
【C语言系列】— 一道递归小题目
Database operation day 6
力扣994:腐烂的橘子(BFS)
ClickHouse学习(二)ClickHouse单机安装
适创科技以云仿真平台,支持“中国智造”升级
Best practices for elastic computing in the game industry
Abstract classes and interfaces
In depth analysis of common cross end technology stacks of app
ClickHouse学习(四)SQL操作