首页 » SEO优化 » phparraymapuse技巧_threejs开拓一款塔防游戏

phparraymapuse技巧_threejs开拓一款塔防游戏

访客 2024-12-13 0

扫一扫用手机浏览

文章目录 [+]

gif 图较大,耐心等待,源码见文末

为了上班摸鱼合理的玩游戏,我写了一个3d塔防游戏,个中功能包含动画、仇敌运动、放置武器、升级武器、销毁武器、动态检测等功能。
请动动小手,点赞收藏,这就发车~

phparraymapuse技巧_threejs开拓一款塔防游戏

目录构造

phparraymapuse技巧_threejs开拓一款塔防游戏
(图片来自网络侵删)
思维导图

详细功能和思路如下

有了这个思维导图,就可以按部就班,一步一步的实现游戏功能

技能栈typescriptvitethreejsastarjs

由于项目体系较大,内容覆盖较广,下面挑几个关键内容先容一下

舆图

首先要加载一个舆图,舆图功能包含可放置模块,不可放置模块(仇敌路线,装饰元素),大概思路便是根据floorSize天生一个长和宽相等的舆图,每个舆图都是一个plane。

const createPlane = (texture: THREE.Texture): THREE.Mesh => { const geometry = new THREE.PlaneGeometry(1, 1); const material = new THREE.MeshBasicMaterial({ color: 0xffffff, side: THREE.DoubleSide }); let plane = new THREE.Mesh(geometry, material); plane.rotation.x = Math.PI 0.5; plane.material.map = texture; plane.material.needsUpdate = true; castShadow(plane) return plane}天生轨迹

第一步用for循环,天生一个二维数组存放在mapUseV2中,设定一个出发点const startPoint = new THREE.Vector2(0, 0)和终点const endPoint = new THREE.Vector2(floorSize - 1, floorSize - 1),用于天生仇敌运动轨迹。

天生轨迹的代码

const maps = new (window as any).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即可,(项目目前只有一种仇敌类型),而Enemy_Level也继续了公共类ModelCheck,这个公共类紧张功能是修正位置、旋转角度、获取包围盒等功能。
武器、子弹、仇敌、水晶塔都继续了这个公共类。

天生仇敌

创建仇敌的时候碰着一个问题,本来想着节约渲染本钱,只加载一次仇敌模型,再利用clone()方法,复制出多个仇敌,但是仇敌模型包含了骨骼模型,在克隆模型的时候,骨骼动画和仇敌模型复制了,但是两者的绑定关系没有复制成功,导致骨骼动画和模型不匹配,导致动画不生效,以是每次天生仇敌的时候都重新加载了一下模型。

export class EnemyCrouched extends Enemy_Level { coefficient: number = crouched_coefficient constructor(level: number) { super(ENEMY_CROUCHED_NAME) this.speed = this.coefficient 60 this.price = this.coefficient this.HP = this.coefficient level this.level = level // 骨骼在实行clone()时候,骨骼之间的位置和动画发生错位,以是每次重新拉一次模型,很不可取。
// 但是可以重新克隆一个骨骼动画,并重新绑定骨骼动画和新的模型之间的关系 loadGltf(import.meta.env.VITE_ASSETS_URL + '/assets/models/snowgolem/scene.gltf').then((gltf: any) => { this.model = gltf.scene this.bindAnimite(gltf.animations) this.addModel() this.group.example = this this.handleModel(this.group) this.run() this.runStart() }); }}
仇敌动画

通过bindAnimite绑定动画可以将模型中供应的骨骼动画绑定到模型上并播放动画。

bindAnimite(animations: GltfModel['animations']) { if (this.model) { this.modelAnimation = new Animation(this.model, animations); this.modelAnimation.once(['death']) this.modelAnimation.play('walking'); }}

这里说一下Animation类,供应播放动画play、切换动画fadeToAction、显示骨骼createSkeleton,过滤一次性动画once比如跳跃,去世亡,敬礼这种非循环播放的动画。
更新动画upDate,这个方法接管一个镜头参数,如果将镜头参数传进来,可以实现镜头跟踪功能。

天生武器

天生武器通过点击操作栏对应的按钮进行。

关于武器的定位

在点击十字弓按钮后,将触发鼠标的射线检测,并将检测目标设置为地板,当鼠标移动至可放置模型的位置时,显示蓝色的框,如果检测到鼠标位置不可放置模型,则赞助色块变为赤色,点击无效,需重新选择位置。

初始化武器

Crossbow_level初始化十字弓,Rifle_level初始化火炮。
这两个类是区分不同的武器,并在初始化的时候加载武器模型并供应武器的upDate方法,这两个类都继续他们的基类Weapon。
Weapon供应目标检测、射击等功能。
Weapon又继续公共类ModelCheck为武器供应包围盒,尺寸和模型纠正的功能。
初始化武器时有几个属性比较主要:武器系数coefficient、等级level这两个数值影响武器的子弹发射间隔,威力,和单价(包括升级的价格)。

基于setAnimationLoop做的定时器

这里先容一下IntervalTime

.setAnimationLoop ( callback : Function ) : undefined

callback — 每个可用帧都会调用的函数。
如果传入‘null’,所有正在进行的动画都会停滞。
可用来代替requestAnimationFrame的内置函数. 对付WebXR项目,必须利用此函数。

requestAnimationFrame方法熟习threejs的都理解,基于屏幕刷新率调用的,一样平常60次/秒,实现定时的事理很大略,调用一次的时候获取一次当前韶光,用当前调用时的韶光now减去上次存的韶光lastTime,如果超过规定的循环间隔韶光time调用一次回调,这时会将lastTime置为time,并进行新一轮的计时。
项目中炮弹发射间隔便是这么做的。
当然,你也可以在threejs以外的项目利用。

export class IntervalTime { lastTime = 0 constructor() { } interval(time: number, callback: () => void) { let now = performance.now(); // 利用 performance.now() 获取高精度韶光 let deltaTime = now - this.lastTime; if (deltaTime > time) { // 实行一秒内须要做的事情 callback() // 重置韶光 this.lastTime = now; } }}子弹跟踪

武器在天生后会发射子弹,调用TransmitDiscards类。
根据不同的武器天生不同的子弹,并在render中调用子弹类的upDate方法。
子弹会跟踪检测仇敌的位置,并找到最近的仇敌进行攻击,得到最近仇敌的位置和子弹发射位置,天生一条抛物线,开口向下,并让子弹沿着抛物线进走运动,直到子弹运动到终点,并利用仇敌的包围盒检测子弹的位置,判断子弹是否进入仇敌包围盒的范围。
如果在范围内,仇敌HP掉对应的血量,并销毁子弹。

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(); enemy.example.getBox(); const contains = enemy.example.box3.containsPoint(this.position) // 子弹撞击仇敌 if (contains) { console.log('击中仇敌'); let hp = enemy.example.HP - this.power enemy.example.HP = hp enemy.example.hpDom.element.innerHTML = `HP:${Math.max(0, hp)}` this.dispose() } } } }) }

当仇敌的hp归零时,则销毁仇敌并增加对应数量的金币

仇敌的update方法

upDate() { this.getBox() if (this.modelAnimation) { this.modelAnimation.upDate() } if (this.HP <= 0) { this.dispose(); wallet.add(this.price); }}武器升级

操作栏的功能区,供应了几个属性,区分放置、升级、销毁等功能

// 存放全局变量的export const buttonState = { DOWN_HERO: false, // 是否可以放置模型 CHECK_HERO: true, // 是否可以选中模型 HREO_TYPE: 'crossbow' as "crossbow" | "rifle", UPGRADE_HERO: false, // 是否升级英雄 DISPOSE_HERO: false, // 是否销毁英雄 SHOW_BOX3_HELPER: true, // 是否显示赞助线 GAME_STOP: false // 停息游戏}

前面提过,放置武器的时候,射线检测的工具为floor组,而升级则会将射线检测的组改为武器组,通过检测到的模型去升级对应的二级和三级,投军器达到三级的时候则不可再升级,升级武器时将原有武器销毁,添加一个新的模型,目前对付武器升级只是将系数调高,如感兴趣可根据武器等级,天生多个子弹,从单一攻击改为群攻。

水晶塔

相对付水晶塔,则复用子弹的逻辑即可,将仇仇视作子弹,水晶塔视作仇敌,在水晶塔的upDate方法,检测仇敌的位置,如果仇敌的包围盒与水晶塔的包围盒相交,则水晶塔减去对应的hp,如果水晶塔的hp归零,则视为游戏失落败

全局配置金币

金币方法相对大略一些,咱们这里没vip充值,以是不必考虑一个648换算几个钻石,1个钻石换算几个金币,游戏的金币系统,只有一个加一个减,创建和升级武器减去相对应的金币,消灭仇敌增加对应的金币

import { Money } from "./variable";// 钱包实例class Wallet { money: number = Money; moneyDom: HTMLDivElement | undefined constructor() { const dom = document.querySelector('.money') as HTMLDivElement | undefined if (dom) { this.moneyDom = dom; this.changeText() } } add(money: number) { this.money += money; this.changeText() } sub(money: number) { if (this.money < money) { return false } else { this.money -= money; this.changeText() } return true } changeText() { if (this.moneyDom) { this.moneyDom.innerText = this.money + '金币' } }}export const wallet = new Wallet()金手指

金手指支持初始化金币数量、武器系数、舆图尺寸。
通过路径参数供应

const searchParams = getParams()const getValue = (field: string, def: number): number => { console.log('searchParams',searchParams); const f = searchParams[field] let fz = f ? Number(f) : def; return fz}export const floorSize = getValue('floorSize', 8) // 必须双数,不然后面的打算有问题// 系数// 十字弩系数export const crossbow_coefficient = getValue('crossbow_coefficient', 10)// 火炮系数export const rifle_coefficient = getValue('rifle_coefficient', 10) 2// 初始化金币export const Money: number = getValue('money', 100);

如果将路径地址后面拼上这段参数

?crossbow_coefficient=100&rifle_coefficient=100&floorSize=16&money=500,

那么你将拥有500个金币,攻击力100打底的武器,并且舆图尺寸变大,仇敌行动轨迹变长

这会影响游戏的平衡性,当然,我们的游戏也没做公正性和平衡性的考虑。

链接:https://juejin.cn/post/7355745761370505231

标签:

相关文章

phppow类似技巧_PoWPoSPoC孰优孰劣

说到“Proof of X”,一样平常想起来的自然便是目前圈内著名的PoW、PoS和PoC这三大共识——当然目前而言2014年出身...

SEO优化 2024-12-14 阅读0 评论0

phppdo_fetch技巧_PHP PDO 简单教程

PHP 5.5 版本之前,我们有用于访问 MySQL 数据库的 mysql_ 命令,但由于安全性不敷,它们终极被弃用。mysql_...

SEO优化 2024-12-14 阅读0 评论0

phpredis分库技巧_redis 大年夜开大年夜合

一.背景随着互联网+大数据时期的来临,传统的关系型数据库已经不能知足中大型网站日益增长的访问量和数据量,这个时候就须要一种能够快速...

SEO优化 2024-12-14 阅读0 评论0