Sundries.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. import { _decorator, Animation, AnimationState, Collider, Enum, Node, RigidBody, Vec3 } from 'cc';
  2. import { BaseExp } from '../core/base/BaseExp';
  3. import { PoolManager } from '../core/manager/PoolManager';
  4. import { userIns } from '../data/UserData';
  5. import { Logger } from '../extend/Logger';
  6. import { ResUtil } from '../utils/ResUtil';
  7. import { Game } from './Game';
  8. import { audioMgr } from '../core/manager/AudioManager';
  9. import { Constants } from '../data/Constants';
  10. import { Enemy } from './Enemy';
  11. import { GameEnums } from '../data/GameEnums';
  12. const { ccclass, property } = _decorator;
  13. //沙袋堆动画
  14. const sandbge_take = "take"
  15. /**
  16. * 战场障碍物类型
  17. */
  18. export enum ObstacleType {
  19. /** 沙袋堆 */
  20. SANDBAG_PILE = "沙袋堆",
  21. /** 油桶 */
  22. OIL_BARREL = "油桶",
  23. /** 军车 */
  24. MILITARY_TRUCK = "军车",
  25. /** 小沙袋 */
  26. SMALL_SANDBAG = "小沙袋"
  27. }
  28. /**
  29. * 杂物建筑物类
  30. */
  31. @ccclass('Sundries')
  32. export class Sundries extends BaseExp {
  33. @property({ type: Enum(ObstacleType), tooltip: "沙袋堆:SANDBAG_PILE 油桶:OIL_BARREL 军车: MILITARY_TRUCK 小沙袋:SMALL_SANDBAG", displayName: "障碍物类型"})
  34. public obstacleType: ObstacleType = ObstacleType.SANDBAG_PILE;
  35. //浓烟节点 只有军车的时候血量低于30%才会有
  36. public heavySmoke: Node = null;
  37. //添加爆炸队列和标记
  38. private static explosionQueue: {
  39. target: Enemy | Sundries,
  40. damage: number,
  41. source: any,
  42. explosionTarget:any}[] = [];
  43. //标记是否正在处理爆炸队列
  44. private static isProcessingQueue = false;
  45. //杂物信息数据
  46. public data: any = null;
  47. //杂物是否被打报废
  48. private isDead: boolean = false;
  49. //杂物当前的总血量
  50. private totalHp: number = 0;
  51. start() {
  52. //给碰撞体绑定数据
  53. this.setupColliders(this.node);
  54. //根据障碍物类型设置总血量
  55. let sundrie_id: number = -1;
  56. switch (this.obstacleType) {
  57. case ObstacleType.SANDBAG_PILE:
  58. sundrie_id = 11001;
  59. break;
  60. case ObstacleType.OIL_BARREL:
  61. sundrie_id = 11002;
  62. break;
  63. case ObstacleType.MILITARY_TRUCK:
  64. sundrie_id = 11003;
  65. break;
  66. case ObstacleType.SMALL_SANDBAG:
  67. sundrie_id = 11004;
  68. break;
  69. }
  70. if(sundrie_id !== -1){
  71. const data: any = userIns.sundriesTable.find(e=>e.id == sundrie_id);
  72. this.data = data;
  73. this.totalHp = data.hp;
  74. }else{
  75. Logger.log('未找到对应的杂物信息');
  76. }
  77. }
  78. /**
  79. * 杂物被打掉血
  80. * @param hp 血
  81. * @param pData 玩家数据
  82. * @param target 这儿指的是谁打的我
  83. */
  84. public subHP(hp: number, pData:any,target:Node){
  85. if(!this.node
  86. ||Game.I.isPause
  87. ||this.isDead
  88. ||hp == null
  89. ||this.totalHp <= 0){
  90. return;
  91. }
  92. this.totalHp -= hp;
  93. //军车在生命值低于30%时触发动画1黑烟效果 在生命值为0时触发动画 2爆炸效果
  94. this.milityaryTruckHeavySmoke();
  95. //杂物打打死报废
  96. if(this.totalHp <= 0
  97. && !this.isDead){
  98. this.isDead = true;
  99. this.scrap(target);
  100. }
  101. }
  102. /**
  103. * 军车冒浓烟
  104. */
  105. public milityaryTruckHeavySmoke(){
  106. if(this.obstacleType != ObstacleType.MILITARY_TRUCK)return;
  107. if(this.heavySmoke)return;
  108. //血量过低冒浓烟
  109. if(this.totalHp < this.data.hp * 0.3){
  110. ResUtil.playParticle(
  111. `effects/Prefabs/HeavySmoke`,
  112. 0,
  113. new Vec3(0.18,0.18,0.18),
  114. (heavySmoke) => {
  115. heavySmoke.parent = this.node.parent;
  116. heavySmoke.worldPosition = this.node.worldPosition.clone();
  117. heavySmoke.active = true;
  118. this.heavySmoke = heavySmoke;
  119. }
  120. );
  121. }
  122. }
  123. /**
  124. * 杂物被打报废
  125. */
  126. public scrap(target:Node){
  127. switch (this.obstacleType) {
  128. case ObstacleType.SANDBAG_PILE:{//沙袋堆
  129. //沙袋掀开的动画
  130. let anim:Animation = this.node.getComponent(Animation);
  131. if(!anim)return;
  132. //参考物位置坐标 用于计算查沙袋旋开的的方向
  133. let refePos: Vec3 = target.worldPosition.clone();
  134. let selfPos: Vec3 = this.node.worldPosition.clone();
  135. //计算方向向量
  136. let direction = new Vec3();
  137. Vec3.subtract(direction, refePos, selfPos).normalize();
  138. //根据方向调整沙袋的旋转
  139. let relativeAngle = Math.atan2(direction.x, direction.z) * 180 / Math.PI;
  140. const initialRotation = this.node.eulerAngles.y;
  141. this.node.setRotationFromEuler(0, initialRotation + relativeAngle - 85, 0);
  142. //播放动画
  143. let animState:AnimationState = anim.getState(sandbge_take);
  144. animState.speed = 1;
  145. animState.repeatCount = 1;
  146. anim.play(sandbge_take);
  147. }
  148. break;
  149. case ObstacleType.OIL_BARREL:{//油桶
  150. //油漆桶音效
  151. audioMgr.playOneShot(Constants.audios.Oildrum_explosion);
  152. //爆炸后生成爆炸特效 粒子路径、自动回收时间、外部设置回调
  153. ResUtil.playParticle(
  154. `effects/Prefabs/OilBoom`,
  155. 4,
  156. new Vec3(0.1,0.1,0.1),
  157. (particle) => {
  158. particle.parent = this.node.parent;
  159. const targetPos: Vec3 = this.node.worldPosition.clone();
  160. particle.worldPosition = targetPos;
  161. particle.active = true;
  162. this.recycle();
  163. //爆炸后生成爆炸伤害
  164. this.explosionDamage(targetPos);
  165. }
  166. );
  167. }
  168. break;
  169. case ObstacleType.MILITARY_TRUCK:{//军车
  170. //加载报废的军车
  171. ResUtil.loadRes(`enemy/sundries/scrap_car`, this.node)
  172. .then((military: Node | null) => {
  173. if(!military)return;
  174. military.active = true;
  175. military.parent = this.node.parent;
  176. const targetPos: Vec3 = this.node.worldPosition.clone();
  177. military.worldPosition = targetPos;
  178. military.eulerAngles = this.node.eulerAngles.clone();
  179. military.scale = this.node.scale.clone();
  180. //回收原有的军车
  181. this.recycle();
  182. //军车爆炸后生成爆炸特效
  183. ResUtil.playParticle(
  184. `effects/Prefabs/TankBoom`,
  185. 3,
  186. new Vec3(0.1,0.1,0.1),
  187. (particle) => {
  188. particle.parent = this.node.parent;
  189. particle.worldPosition = targetPos;
  190. particle.active = true;
  191. //爆炸后生成爆炸伤害
  192. this.explosionDamage(targetPos);
  193. },
  194. () => {//爆炸完成后 回收浓烟
  195. this.scheduleOnce(()=>{
  196. if(this.heavySmoke && this.heavySmoke.parent){
  197. PoolManager.putNode(this.heavySmoke);
  198. this.heavySmoke = null;
  199. }
  200. },3);
  201. }
  202. );
  203. });
  204. }
  205. break;
  206. case ObstacleType.SMALL_SANDBAG:{//小沙袋
  207. //获取刚体组件
  208. const rigidBody = this.node.getComponent(RigidBody);
  209. if (!rigidBody) return;
  210. //计算抛射方向
  211. const startPos = this.node.worldPosition.clone();
  212. const playerPos = Game.I.player.node.worldPosition.clone();
  213. const direction = startPos.subtract(playerPos).normalize();
  214. //随机抛射参数 水平力1500-2500N
  215. const horizontalForce = 1500 + Math.random() * 1000;
  216. //垂直力800-1500N
  217. const verticalForce = 800 + Math.random() * 700;
  218. //旋转扭矩50-150
  219. const torqueForce = 50 + Math.random() * 100;
  220. //施加作用力
  221. rigidBody.applyForce(new Vec3(
  222. direction.x * horizontalForce,
  223. verticalForce,
  224. direction.z * horizontalForce
  225. ));
  226. //施加旋转扭矩
  227. rigidBody.applyTorque(new Vec3(0, torqueForce, 0));
  228. }
  229. break;
  230. }
  231. }
  232. /**油桶半径的5倍2.24 车5车的半径 10.2
  233. * 爆炸物产生爆炸伤害
  234. * @param targetPos 爆炸物的位置
  235. */
  236. public explosionDamage(targetPos: Vec3){
  237. if(!this.data
  238. || !this.data.explosion_radius
  239. || !this.data.explosion_injury) {
  240. Logger.error('爆炸物数据异常', this.data);
  241. return;
  242. }
  243. //爆炸半径
  244. const eRadius: number = this.data.explosion_radius;
  245. //查找不是自身的所有杂物 然后去看看是否有连续爆炸
  246. const allSundries: Sundries[] = Game.I.map.sundries.children
  247. .filter(node => node.active && node.parent && node != this.node)
  248. .map(node => node.getComponent(Sundries))
  249. .filter(comp =>
  250. comp && !comp.isDead
  251. ) as Sundries[];
  252. const allEnemys = [...Game.I.buildEnemys.allEnemys.filter(e=>!e.isDead && e.node.active),
  253. ...allSundries];
  254. if(allEnemys.length <= 0)return;
  255. //爆炸伤害
  256. const damage: number = this.data.explosion_injury;
  257. allEnemys.forEach((e: any) => {
  258. if(e.node && e.node.parent){
  259. const ePos: Vec3 = e.node.worldPosition;
  260. const hDistance = Vec3.distance(
  261. new Vec3(targetPos.x, 0, targetPos.z),
  262. new Vec3(ePos.x, 0, ePos.z)
  263. );
  264. if(hDistance <= eRadius) {
  265. if(e instanceof Enemy){//敌人直接扣血
  266. e.subHP(damage, this.data);
  267. }else if(e instanceof Sundries){//爆炸物 如果队列未在处理则开始处理 将爆炸目标加入队列而不是立即触发
  268. Sundries.explosionQueue.push({
  269. target: e,
  270. damage: damage,
  271. source: this.data,
  272. explosionTarget: this.node
  273. });
  274. if(!Sundries.isProcessingQueue) {
  275. this.processExplosionQueue();
  276. }
  277. }
  278. }
  279. }
  280. })
  281. }
  282. /**
  283. * 处理爆炸队列
  284. */
  285. private processExplosionQueue() {
  286. if (Sundries.explosionQueue.length == 0) {
  287. Sundries.isProcessingQueue = false;
  288. return;
  289. }
  290. Sundries.isProcessingQueue = true;
  291. const explosion = Sundries.explosionQueue.shift()!;
  292. //触发当前爆炸
  293. explosion.target.subHP(explosion.damage, explosion.source,explosion.explosionTarget);
  294. //添加0.2秒延迟后处理下一个爆炸
  295. this.scheduleOnce(()=>{
  296. this.processExplosionQueue();
  297. },0.2)
  298. }
  299. /**
  300. * 递归设置所有子节点的Collider参数
  301. * @param node 起始节点
  302. */
  303. private setupColliders(node: Node) {
  304. const collider = node.getComponent(Collider);
  305. if(collider){
  306. collider["args"] = [GameEnums.enemyPartType.sundries, this];
  307. }
  308. //递归处理所有子节点
  309. node.children.forEach(child => {
  310. this.setupColliders(child);
  311. });
  312. }
  313. /**
  314. * 杂物回收
  315. */
  316. public recycle(){
  317. this.isDead = true;
  318. this.node.active = false;
  319. /*if(this.node.active){
  320. this.isDead = true;
  321. PoolManager.putNode(this.node);
  322. }*/
  323. }
  324. }