创建画布,相机

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 = truedirLight.shadow.camera.top = 180dirLight.shadow.camera.bottom = - 100dirLight.shadow.camera.left = - 120dirLight.shadow.camera.right = 120scene.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; //最下面//这两个值决定使用多少像素生成阴影 默认512directionalLight.shadow.mapSize.height = 1024;directionalLight.shadow.mapSize.width = 1024;*/

创建材质和地面

floor = new THREE.MeshPhongMaterial( {color: '#ffffff'
} )/**THREE.MeshPhongMaterial,可以创建一种光亮的材质它可以模拟具有镜面高光的光泽表面(如上漆木材)ambient      这是材质的环境色。它与上一章讲过的环境光源一起使用。这个颜色会与环境光源提供的颜色相乘。默认值为白色emissive     这是该材质发射的颜色。它其实并不想一个光源,只是一种纯粹的、不受其他光照影响的颜色。默认值为黑色。specular   该属性指定该材质的光亮程度及高光部分的颜色。如果将它设置成与color属性相同的颜色,将会得到一个更加类似金属的材质。如果将它设置成灰色(grey),材质将变得更像塑料shininess   该属性指定镜面高光部分的亮度。默认值为30metal  如果此属性设置为true,Three.js会使用稍微不同的方式计算像素的颜色,以使物体看起来更像金属。要注意的是,这个效果非常小wrapAround 如果这个属性设置为true,则启动半lambert光照技术。有了它,光下降得更微妙。如果网格有粗糙、黑暗的地区,启用此属性阴影将变得柔和并且分布更加均匀wrapRGB    当wrapAround属性设置为true时,可以使用THREE.Vector3来控制光下降得速度*/const loaderImage = new THREE.TextureLoader()
/**
loader 图片资源
*/loaderImage.load(paodaoPng, (texture) => {texture.wrapS = THREE.RepeatWrappingtexture.wrapT = THREE.RepeatWrappingtexture.repeat.set(1, 15)floor.map = texturefloorMesh = new THREE.Mesh( new THREE.PlaneGeometry( 150, 21000 ), floor )floorMesh.rotation.x = - Math.PI / 2floorMesh.receiveShadow = truescene.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轴的宽度,默认值为1height:沿着Y轴的高度,默认为1widthSegments :宽度分段数,默认为1heightSegments:高度分段数,默认为1*/

创建左右两边地面

 const loaderImageLeftRight = new THREE.TextureLoader()loaderImageLeftRight.load(`${hrefs.value}${dir.value}/a.png`, (texture2) => {texture2.wrapS = THREE.RepeatWrappingtexture2.wrapT = THREE.RepeatWrappingtexture2.repeat.set(10, 20)floor2.map = texture2floorMesh2 = new THREE.Mesh( new THREE.PlaneGeometry( 1100, 20000), floor2 )floorMesh2.rotation.x = - Math.PI / 2floorMesh2.position.x = -626floorMesh2.name = 'floorMesh2'floorMesh3 = new THREE.Mesh( new THREE.PlaneGeometry( 1100, 20000), floor2 )floorMesh3.rotation.x = - Math.PI / 2// floorMesh2.rotation.y = - Math.PI / 1floorMesh3.position.x = 628floorMesh3.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 = 0gltf.scene.position.x = 0gltf.scene.position.z = -300a = 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 = trueitem.opacity = 0.6})cityMesh2.position.set(-2400, -2, -4500)cityMesh2.scale.setScalar(8.8)// cityMesh2.material.color = new THREE.Color('yellow')cityMesh2.material.transparent = truecityMesh2.material.opacity = 0.7cityGroup.add(cityMesh, cityMesh2)let image = new THREE.TextureLoader()image.load(`${hrefs.value}${dir.value}/static/logo/b.png`, (img) => {img.wrapS = THREE.RepeatWrappingimg.wrapT = THREE.RepeatWrappingimg.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.RepeatWrappingimg.wrapT = THREE.RepeatWrappingimg.matrixAutoUpdate = falselet 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.5cityPaiMaiLogo.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 = 25scene.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 = truecontainer.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 = falseif ((integralsNumber.value <=1 && resurrection.value <= 0) ) {TostSkipOver.value = trueglobGroup.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 = nullglobGroup.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 = truecloseRequestAnimationFrame.value = truesetTimeout(() => {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);elseelement["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);elseelement["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 = trueif (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 = falsetime = setInterval(() => {if (a.position.y >= aY) {y = trueif (a.position.y <= aCY) {clearInterval(time)}a.position.y = a.position.y - 7if (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 + 5if (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 - 18a.position.y = a.position.y - 7if (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 = falseclearInterval(time)atime = setInterval(() => {a.position.z = a.position.z + 5if (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 = 0aBox3d = 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 - 40aBox3d = new THREE.Box3()aBox3d.setFromObject(a)aBoxMove(a)}}}
}

来源小豆包

更多内容请到小豆包》

扫码访问小豆包

扫码关注小豆包公众号

用threejs 技术做游戏跑酷相关推荐

  1. 技术贴:如何简单地做游戏随机生成地图

    转自:http://www.gamelook.com.cn/2015/12/239245 Gamelook报道/对于大多数的游戏来说,内容的消耗都是开发商非常棘手的问题,而随机生成地图的做法则大大增加 ...

  2. 做游戏开发需要具备那些知识或技能?

    做游戏开发需要具备那些知识或技能? 这是我之前在csdn上发的一个贴的名字,今天忽然想起来进行了整理.希望看到关心这方面的人有所启示. 下面是csdner的回复: freezezdj: 游戏分客户端和 ...

  3. python能做游戏吗-python能做游戏吗

    能,但不适合. 用锤子能造汽车吗? 谁也没法说不能吧?地球上也有很多汽车,是用锤子造出来的..但一般来说,还是用工业机器人更合适对吗? 比较大型的,使用Python的游戏有两个,一个是<EVE& ...

  4. 贫穷中透着零基础的单人制作游戏手册之二:做游戏不光靠创意

    构思一个属于自己的.现在就能做的游戏,不光要考虑创意,还有许多客观条件,否则都是空中楼阁. 开发游戏的冲动靠创意,完成游戏的毅力靠计划. 我尝试做过一个简单的自我评估: 把这些条件都列在一起,我在确立 ...

  5. 人人都能做游戏!3D次世代CE云端引擎发布

    全球著名引擎提供商Crytek CEO和欢众科技今日联合发布3D次世代CE云端引擎.该引擎的出现让 "人人都能做游戏"成为可能,其革新之处在于:无论是专业的开发者还是毫无经验的普通 ...

  6. 我很喜欢玩游戏,那么我就适合做游戏程序员吗?

    作者:黄小斜 文章来源:[程序员江湖] 游戏在今天的普及度已经不是端游时代可以比肩的了.如今人手一台手机.平板就可以吃鸡.打农药,不仅是男生,也有很多女生加入了游戏圈.相信现在在看文章的你也玩游戏,虽 ...

  7. java游戏服务器面试_我做游戏开发这八年

    点击上方"CSDN学院精品课",选择"置顶公众号" CSDN学院精品课  IT人的职业提升平台 作者 | kakashi8841 简述这篇文章并不是想教会大家如 ...

  8. 纯做技术是自娱自乐 抛开技术做技术才是出路

    短短一生不过数十载,对于很多人而言,作IT.作技术只是生命中的某一段,并非所有.而无论是换工作还是换行业,只是一种形式而已,最终我们追求的是成功.是荣誉.是收获.于是在年轻的这几年里,作为技术人员理应 ...

  9. 做游戏美术师必须掌握哪些基本知识

    很多正在游戏美术路上前行的新手小伙伴们都有两个疑问:现阶段自己达到了怎样的水品,以及学到怎样的程度才能进入公司胜任岗位.小编整理了一下身为游戏美术师,你必须达到的高度.看看你走到哪一步了? 1.原画师 ...

最新文章

  1. python零基础入门大数据_【资源分享】零基础入门大数据(数据分析)经验分享...
  2. 玩纸牌游戏计算机教案,小班数学好玩的扑克牌教案
  3. Vim高级使用 - CentOS下使用VIM打造C/C++开发环境
  4. “国家科学数据中心”联合专刊征稿
  5. python串口实时读取数据画图_python串口绘图
  6. 不仅仅于 Json和XML ,快来学习Google出品的序列化神器Protocol Buffer
  7. cocos creator android之微信开放平台修改签名 baseResp.errCode=-6
  8. Ajax请求中的Redirect()
  9. 详解AI加速器:为什么说现在是AI加速器的黄金时代?
  10. chrome快速进入扩展页
  11. 想要定制专属AI声音?这是一份来自微软的保姆级攻略
  12. c语言实现一个计算器
  13. 什么是ASIC芯片?与CPU、GPU、FPGA相比如何?
  14. 什么是显示器的分区背光?侧入式背光与直下式背光有什么区别?什么是Mini LED背光?Micro LED又是什么?
  15. OSChina 周三乱弹 ——我们IT工程师会缺女友?
  16. git具体作用_GIT的工作原理、功能特点及其运用
  17. 一小心删除了系统文件NTDETECT.COM怎么办
  18. 淘宝店铺运营,店铺访客增加但是浏览量减少这是为什么,应该怎样解决?
  19. 让子弹再飞一会:游戏中关于碰撞体积的趣闻
  20. mysql 幻读 mvcc_MVCC 能解决幻读吗?

热门文章

  1. think5php的使用,实例分享Thinkphp5行为使用方法
  2. Windows开启监控
  3. 月活被饿了么反超,美团外卖怎么了?
  4. excel 插件禁用
  5. 字符串分割 SDUT
  6. 多台路由器组网之旁路由设置教程
  7. 关于数字孪生技术你需要知道的
  8. C语言顺序结构程序设计——对调数字and判断三角形
  9. 如何理解通道数in_channel和out_channels
  10. Stream排序Map集合