Threejs开发塔防游戏
gif较大,请耐心等待。源码见文章最后。
目录结构
思维导图
具体功能及思路如下
有了这张思维导图,你就可以一步步实现游戏功能了
技术栈
typescriptvite Threejsastarjs 由于项目体系庞大,内容覆盖面广,我们选取几个重点内容进行介绍。
地图
首先,加载地图。地图功能包括可放置模块和不可放置模块(敌人路线、装饰元素)。总体思路是根据floorSize生成长宽相等的地图,每个地图都是一个平面。
const createPlane=(texture: THREE.Texture): THREE.Mesh={ const Geometry=new THREE.PlaneGeometry(1, 1); const 材质=new THREE.MeshBasicMaterial({ color:0xffffff, side: THREE.DoubleSide });让平面=新的THREE.Mesh(几何,材料);平面.旋转.x=Math.PI * 0.5;平面.材质.贴图=纹理; plane.material.needsUpdate=true; castShadow(plane) returnplane}
生成轨迹
首先使用for循环生成一个二维数组并存储在mapUseV2中。设置起点const startPoint=new THREE.Vector2(0, 0) 和终点const endPoint=new THREE.Vector2(floorSize - 1, FloorSize - 1)。用于生成敌人的移动轨迹。
生成轨迹的代码
constmaps=new (window asany).Graph(mapUseV2);//夸大障碍物的随机点,生成怪物的路线图var starPosition=maps.grid[startPoint.x][startPoint.y]; var endPosition=maps.grid[endPoint.x][endPoint.y];//计算路线图let TrailPoints=(window as any).astar.search(maps, starPosition, endPosition, {closest: false});寻路生成的数据结构如下
这14个点是敌人的路线,所以不能放置武器,所以mapUseV2中数据必须设置为0
mapUseV2[trailPoints[i].x][trailPoints[i].y]=0;这样我们就得到了一张地图,其中包含了敌人的移动路线、装饰以及可放置的区域。
敌人
前面定义的startPoint被用作敌人的出生点。生成敌人的方法是EnemyCrouched类,它初始化敌人的生命条、移动速度、攻击力和等级。
继承基类Enemy_Level,定义添加方法、动作和动画更新。如果想再添加一种敌人类型,继承Enemy_Level即可(项目目前只有一种敌人类型),Enemy_Level还继承了公共类ModelCheck。该公共类的主要功能是修改位置、旋转角度、获取包围盒。武器、子弹、敌人、水晶塔都继承了这个公共类。
生成敌人
通过bindAnimite绑定动画,可以将模型中提供的骨骼动画绑定到模型上并播放动画。
bindAnimite(animations: GltfModel['animations']) { if (this.model) { this.modelAnimation=new Animation(this.model, 动画); this.modelAnimation.once(['death']) this.modelAnimation.play('walking');这里先说一下Animation类,它提供动画播放、切换动画fadeToAction、显示骨骼createSkeleton、过滤一次跳跃、死亡、敬礼等一次性动画,这些都是非循环动画。更新动画updateDate。此方法接受镜头参数。如果传入镜头参数,就可以实现镜头跟踪功能。
生成武器
通过点击操作栏上相应的按钮生成武器。
关于武器的定位
点击弩按钮后,会触发鼠标的射线检测,检测目标设置为地板。当鼠标移动到可以放置模型的位置时,会显示一个蓝色框。如果检测到鼠标位置,则无法放置模型。辅助色块变成红色,点击无效,需要重新选择位置。
初始化武器
Crossbow_level初始化十字弓,Rifle_level初始化火炮。这两个类区分不同的武器,在初始化时加载武器模型并提供武器的upDate方法。这两个类都继承其基类Weapon。武器提供目标探测、射击等功能。 Weapon继承公共类ModelCheck,为武器提供边界框、尺寸和模型校正功能。初始化武器时有几个重要的属性:武器系数和等级。这两个值影响武器的子弹发射间隔、威力和单价(包括升级的价格)。
基于setAnimationLoop做的定时器
这里介绍一下IntervalTime
.setAnimationLoop(回调:函数):未定义
回调— 每个可用帧调用的函数。如果传入“null”,则所有正在进行的动画将停止。可以用来代替内置函数requestAnimationFrame。对于WebXR项目,必须使用此函数。
熟悉Threejs 的人都知道requestAnimationFrame 方法。根据屏幕刷新率来调用,通常为60次/秒。计时的原理非常简单。调用一次,获取当前时间,并用当前调用的now时间减去上次保存的时间。上次时间。如果超过指定的循环间隔时间时调用回调,则lastTime会被设置为time,并进行新一轮的计时。这就是项目中炮弹发射间隔的完成方式。当然,你也可以在Threejs以外的项目中使用它。
导出类IntervalTime {lastTime=0构造函数(){}间隔(time:数字,callback:()=void){let now=Performance.now(); //使用Performance.now() 获取高精度时间let deltaTime=now - this.lastTime; if (deltaTime time) { //执行一秒内需要完成的事情callback() //重置时间this.lastTime=now; } }}
子弹跟踪
武器生成后会发射子弹,调用TransmitDiscards类。根据不同的武器生成不同的子弹,并在render中调用bullet类的upDate方法。子弹会跟踪检测敌人的位置,找到最近的敌人进行攻击,得到最近的敌人位置和子弹发射位置,生成一条开口向下的抛物线,让子弹沿着抛物线移动,直到子弹到达终点点,并利用敌人的包围盒来检测子弹的位置,并判断子弹是否进入敌人的包围盒的范围内。如果在射程内,敌人会损失相应的生命值并摧毁子弹。
upDate() { //子弹检测敌人EnemyGroup.traverse((enemy)={ if (enemy.example) { if (this.model) { this.getBox() const worldPos=new THREE.Vector3() this.model. getWorldPosition(worldPos) this.position=worldPos.clone(); const contains=heaven.example.box3.containsPoint(this.position) //子弹击中敌人if (contains) { console .log('击中敌人'); 让hp=敌人.example.HP - this.power 敌人.example.HP=hp 敌人.example.hpDom.element.innerHTML=`HP:${Math.max(0, hp) }` this.dispose() } } } }) }当敌人生命值归零时,消灭敌人并添加相应数量的金币。
敌人的更新方法
upDate() { this.getBox() if (this.modelAnimation) { this.modelAnimation.upDate() } if (this.HP=0) { this.dispose(); }钱包.add(this.price); }}
武器升级
操作栏功能区提供了几个属性来区分放置、升级、销毁等功能。
//导出存储全局变量const buttonState={ DOWN_HERO: false, //是否可以放置模型CHECK_HERO: true, //模型HREO_TYPE: 是否可以选择'crossbow' 为'crossbow' | 'rifle', UPGRADE_HERO: false, //是否升级英雄DISPOSE_HERO: false, //是否摧毁英雄SHOW_BOX3_HELPER: true, //是否显示辅助线GAME_STOP: false //暂停游戏} 前面提到过,放置时武器,射线检测的对象是楼层组,升级时将去掉射线检测组改为武器组,并使用检测到的模型升级对应的第二层和第三层。当武器达到三级时,将无法升级。升级武器时,销毁原有武器并添加新模型。目前,武器升级仅增加系数。如果你有兴趣,可以根据武器等级生成多发子弹,从单一攻击变为群体攻击。
水晶塔
相比水晶塔,可以复用子弹的逻辑。把敌人当子弹,把水晶塔当敌人。在水晶塔的upDate方法中,探测敌人的位置。如果敌人的包围盒与水晶塔的包围盒相同,则如果包围盒相交,则会减去水晶塔相应的生命值。如果水晶塔的生命值归零,则视为游戏失败。
全局配置
金币
金币方法比较简单。我们这里没有VIP充值,所以你不用考虑多少钻石换算1个648,多少金币换算1颗钻石。游戏的金币系统只有一加一减,创造和升级。武器减去相应的金币,消灭敌人则增加相应的金币。
import { Money } from './variable';//钱包实例类Wallet { Money: number=Money;钱Dom: HTMLDivElement |未定义的构造函数() { const dom=document.querySelector('.money') as HTMLDivElement |未定义if (dom ) { this.moneyDom=dom; this.changeText() } } add(money: 数字) { this.money +=钱; this.changeText() } sub(money: 数字) { if (this.money 钱) { return false } else { this.money -=钱; this.changeText() } return true } changeText() { if (this.moneyDom) { this.moneyDom.innerText=this.money + '金币' } }}export const wallet=new Wallet()
金手指
cheat 支持初始化金币数量、武器系数、地图大小。通过路径参数提供
const searchParams=getParams()const getValue=(field: 字符串, def: 数字): 数字={ console.log('searchParams',searchParams); const f=searchParams[字段] 让fz=f ?编号(f) : def; return fz}export const FloorSize=getValue('floorSize', 8) //必须是偶数,否则后续计算会有问题//Coefficient //弩系数export const crossbow_coefficient=getValue('crossbow_coefficient', 10) //火炮系数export constrifle_coefficient=getValue('rifle_coefficient', 10) * 2//初始化金币export const Money: number=getValue('money', 100);如果在路径地址后面添加该参数
?crossbow_coefficient=100rifle_coefficient=100floorSize=16money=500,
那么你就会有500金币,一把基础攻击力100的武器,地图尺寸会变大,敌人的弹道也会变长。
这会影响游戏的平衡性。当然,我们的游戏并没有考虑公平性和平衡性。
用户评论
哇,听闻在Three.js中开发出一款塔防游戏真是个了不起的壮举!场景效果和动态非常棒。
有10位网友表示赞同!
这款游戏让我深陷其中,Three.js 的利用使得视觉上超乎想象,令人惊艳不已。
有13位网友表示赞同!
我喜欢塔防游戏带来的策略性和挑战性。使用Three.js来构建3D环境简直提升了一个档次。
有12位网友表示赞同!
游戏的渲染效果特别好,每一个小村庄、城镇和城堡都栩栩如生。
有19位网友表示赞同!
第一次在塔防游戏中体验到如此逼真的战场特效,非常值得一试。
有8位网友表示赞同!
通过Three.js开发的游戏能直观展现3D世界的动态,真的是一个技术上的飞跃。
有7位网友表示赞同!
游戏中的光影效果处理得当,白天和夜晚的转换让玩家更沉浸在游戏中。
有14位网友表示赞同!
对于想了解Three.js的人来说,这款游戏是绝佳的教学实例,融合了游戏与代码的魅力。
有14位网友表示赞同!
视觉体验上乘的游戏,尤其是那动态光影系统,每一帧都让我震撼。
有17位网友表示赞同!
我在塔防的路上迷失了,但这款游戏的美让我找到了新的方向,视觉上的享受太赞了!
有7位网友表示赞同!
技术与娱乐并重,这游戏是Three.js粉丝和塔防死忠粉不能错过的杰作。
有10位网友表示赞同!
塔防与3D场景的结合完美无缺,让玩家们可以在丰富的视觉环境中战斗。
有15位网友表示赞同!
这款游戏证明了只要创意无限,即使是复杂的图形处理也能够融入到简单的游戏中。
有9位网友表示赞同!
Three.js开发的游戏细节精良,每一波防御都是一次视觉盛宴和策略思考的双重享受。
有6位网友表示赞同!
我喜欢这款基于Three.js技术的塔防游戏,它不仅考验玩家的战略思维,还让人欣赏到美丽的效果。
有10位网友表示赞同!
游戏中的3D动画让我印象深刻,每一次战斗的变化都有其独特的魅力。
有20位网友表示赞同!
Create with Three.js, play with pleasure. 一款让开发者和普通用户都能沉浸其中的游戏体验。
有12位网友表示赞同!
这不仅仅是一款塔防游戏,更是一次对Three.js编程语言学-的完美实践。
有19位网友表示赞同!
游戏内环境构建极其精致,感觉像是在现实世界中进行一场虚拟的战略游戏。
有5位网友表示赞同!
Three.js带来的不仅仅是高颜值的视觉享受,更是深化策略决策的艺术表达。
有17位网友表示赞同!
用Three.js打造塔防游戏的团队真是了不起,将科技与乐趣完美融合,体验太棒!
有12位网友表示赞同!