创建画布,相机
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)
}
}
}
}
扫码访问小豆包