EnemyInstance.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. import { _decorator, Component, Node, ProgressBar, Label, Vec3, find, UITransform, Collider2D, Contact2DType, IPhysics2DContact } from 'cc';
  2. const { ccclass, property } = _decorator;
  3. // 前向声明EnemyController接口,避免循环引用
  4. interface EnemyControllerType {
  5. gameBounds: {
  6. left: number;
  7. right: number;
  8. top: number;
  9. bottom: number;
  10. };
  11. damageWall: (damage: number) => void;
  12. }
  13. // 敌人状态枚举
  14. enum EnemyState {
  15. MOVING, // 移动中
  16. ATTACKING // 攻击中
  17. }
  18. // 单个敌人实例的组件
  19. @ccclass('EnemyInstance')
  20. export class EnemyInstance extends Component {
  21. // 敌人属性
  22. public health: number = 30;
  23. public maxHealth: number = 30;
  24. public speed: number = 50;
  25. public attackPower: number = 10;
  26. // 移动属性
  27. public movingDirection: number = 1; // 1: 向右, -1: 向左
  28. public targetY: number = 0; // 目标Y位置
  29. public changeDirectionTime: number = 0; // 下次改变方向的时间
  30. // 攻击属性
  31. public attackInterval: number = 2; // 攻击间隔(秒)
  32. private attackTimer: number = 0;
  33. // 对控制器的引用
  34. public controller: EnemyControllerType = null;
  35. // 敌人当前状态
  36. private state: EnemyState = EnemyState.MOVING;
  37. // 游戏区域中心
  38. private gameAreaCenter: Vec3 = new Vec3();
  39. // 碰撞的墙体
  40. private collidedWall: Node = null;
  41. start() {
  42. // 计算游戏区域中心
  43. this.calculateGameAreaCenter();
  44. // 初始化攻击计时器
  45. this.attackTimer = this.attackInterval;
  46. // 添加碰撞检测
  47. this.setupCollider();
  48. }
  49. // 设置碰撞检测
  50. setupCollider() {
  51. // 获取或添加碰撞组件
  52. let collider = this.node.getComponent(Collider2D);
  53. if (!collider) {
  54. console.warn('敌人节点没有碰撞组件,请确保已通过EnemyController添加');
  55. }
  56. if (collider) {
  57. // 启用碰撞检测
  58. collider.enabled = true;
  59. // 设置碰撞回调
  60. collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
  61. console.log('已设置敌人碰撞检测');
  62. }
  63. }
  64. // 碰撞开始事件
  65. onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
  66. // 检查是否碰到墙体
  67. const nodeName = otherCollider.node.name;
  68. const nodePath = this.getNodePath(otherCollider.node);
  69. // 检查是否是四个墙体之一
  70. if (nodeName === 'TopFence' || nodeName === 'BottomFence' ||
  71. nodeName === 'JiguangL' || nodeName === 'JiguangR' ||
  72. nodePath.includes('GameArea/TopFence') || nodePath.includes('GameArea/BottomFence') ||
  73. nodePath.includes('GameArea/JiguangL') || nodePath.includes('GameArea/JiguangR')) {
  74. console.log(`敌人碰到墙体 ${nodeName},停止移动,开始攻击`);
  75. // 记录碰撞的墙体
  76. this.collidedWall = otherCollider.node;
  77. // 切换到攻击状态
  78. this.state = EnemyState.ATTACKING;
  79. // 停止移动(速度设为零)
  80. this.speed = 0;
  81. }
  82. }
  83. // 获取节点路径
  84. getNodePath(node: Node): string {
  85. let path = node.name;
  86. let current = node;
  87. while (current.parent) {
  88. current = current.parent;
  89. path = current.name + '/' + path;
  90. }
  91. return path;
  92. }
  93. // 计算游戏区域中心
  94. calculateGameAreaCenter() {
  95. const gameArea = find('Canvas/GameLevelUI/GameArea');
  96. if (gameArea) {
  97. this.gameAreaCenter = gameArea.worldPosition;
  98. console.log('游戏区域中心:', this.gameAreaCenter);
  99. }
  100. }
  101. // 更新血量显示
  102. updateHealthDisplay() {
  103. // 更新血条
  104. const hpBar = this.node.getChildByName('HPBar');
  105. if (hpBar) {
  106. const progressBar = hpBar.getComponent(ProgressBar);
  107. if (progressBar) {
  108. progressBar.progress = this.health / this.maxHealth;
  109. }
  110. }
  111. // 更新血量数字
  112. const hpLabel = this.node.getChildByName('HPLabel');
  113. if (hpLabel) {
  114. const label = hpLabel.getComponent(Label);
  115. if (label) {
  116. label.string = this.health.toString();
  117. }
  118. }
  119. }
  120. // 受到伤害
  121. takeDamage(damage: number) {
  122. this.health -= damage;
  123. // 更新血量显示
  124. this.updateHealthDisplay();
  125. }
  126. update(dt: number) {
  127. switch (this.state) {
  128. case EnemyState.MOVING:
  129. this.updateMovement(dt);
  130. break;
  131. case EnemyState.ATTACKING:
  132. this.updateAttack(dt);
  133. break;
  134. }
  135. }
  136. // 更新移动
  137. updateMovement(dt: number) {
  138. // 更新改变方向的计时器
  139. this.changeDirectionTime -= dt;
  140. // 随机改变水平方向
  141. if (this.changeDirectionTime <= 0) {
  142. this.movingDirection = Math.random() > 0.5 ? 1 : -1;
  143. this.changeDirectionTime = 1 + Math.random() * 2; // 1-3秒后再次改变方向
  144. }
  145. // 计算朝向游戏区域中心的方向
  146. const directionToCenter = new Vec3(
  147. this.gameAreaCenter.x - this.node.worldPosition.x,
  148. this.gameAreaCenter.y - this.node.worldPosition.y,
  149. 0
  150. );
  151. directionToCenter.normalize();
  152. // 检查是否即将进入游戏区域
  153. if (this.isApproachingGameArea()) {
  154. // 如果即将进入游戏区域,停止移动并开始攻击
  155. this.state = EnemyState.ATTACKING;
  156. this.speed = 0;
  157. console.log('敌人即将进入游戏区域,停止移动并开始攻击');
  158. return;
  159. }
  160. // 水平移动(随机左右)+ 垂直移动(朝向中心)
  161. const newX = this.node.position.x + this.movingDirection * this.speed * 0.5 * dt; // 水平速度减半
  162. const newY = this.node.position.y + directionToCenter.y * this.speed * dt;
  163. // 获取游戏区域边界
  164. let leftBound = -500;
  165. let rightBound = 500;
  166. if (this.controller) {
  167. const bounds = this.controller.gameBounds;
  168. if (bounds) {
  169. const halfWidth = 20; // 估计敌人宽度的一半
  170. leftBound = bounds.left - bounds.left + halfWidth;
  171. rightBound = bounds.right - bounds.left - halfWidth;
  172. }
  173. }
  174. // 检查是否超出边界
  175. let finalX = newX;
  176. if (newX < leftBound) {
  177. finalX = leftBound;
  178. this.movingDirection *= -1; // 碰到左边界,改变方向
  179. } else if (newX > rightBound) {
  180. finalX = rightBound;
  181. this.movingDirection *= -1; // 碰到右边界,改变方向
  182. }
  183. // 更新敌人位置
  184. this.node.position = new Vec3(finalX, newY, 0);
  185. // 手动检测是否接近墙体
  186. this.checkWallProximity();
  187. }
  188. // 检查是否即将进入游戏区域
  189. isApproachingGameArea() {
  190. // 获取游戏区域节点
  191. const gameArea = find('Canvas/GameLevelUI/GameArea');
  192. if (!gameArea) return false;
  193. // 获取游戏区域的UITransform组件
  194. const gameAreaTransform = gameArea.getComponent(UITransform);
  195. if (!gameAreaTransform) return false;
  196. // 获取游戏区域的世界坐标和尺寸
  197. const gameAreaPos = gameArea.worldPosition;
  198. const gameAreaWidth = gameAreaTransform.width;
  199. const gameAreaHeight = gameAreaTransform.height;
  200. // 计算游戏区域的边界
  201. const gameAreaLeft = gameAreaPos.x - gameAreaWidth / 2;
  202. const gameAreaRight = gameAreaPos.x + gameAreaWidth / 2;
  203. const gameAreaTop = gameAreaPos.y + gameAreaHeight / 2;
  204. const gameAreaBottom = gameAreaPos.y - gameAreaHeight / 2;
  205. // 获取敌人的世界坐标
  206. const enemyPos = this.node.worldPosition;
  207. // 设置安全距离,防止敌人进入游戏区域
  208. const safeDistance = 60; // 可以根据实际情况调整
  209. // 检查敌人是否即将进入游戏区域
  210. const isNearLeft = enemyPos.x > gameAreaLeft - safeDistance && enemyPos.x < gameAreaLeft;
  211. const isNearRight = enemyPos.x < gameAreaRight + safeDistance && enemyPos.x > gameAreaRight;
  212. const isNearTop = enemyPos.y < gameAreaTop + safeDistance && enemyPos.y > gameAreaTop;
  213. const isNearBottom = enemyPos.y > gameAreaBottom - safeDistance && enemyPos.y < gameAreaBottom;
  214. // 检查敌人是否在游戏区域内部
  215. const isInside = enemyPos.x > gameAreaLeft && enemyPos.x < gameAreaRight &&
  216. enemyPos.y < gameAreaTop && enemyPos.y > gameAreaBottom;
  217. // 如果敌人已经在游戏区域内部,立即停止
  218. if (isInside) {
  219. console.log('敌人已进入游戏区域内部,立即停止');
  220. return true;
  221. }
  222. // 如果敌人接近游戏区域的任一边界,返回true
  223. if (isNearLeft || isNearRight || isNearTop || isNearBottom) {
  224. // 记录接近的方向
  225. let direction = '';
  226. if (isNearLeft) direction += '左边界 ';
  227. if (isNearRight) direction += '右边界 ';
  228. if (isNearTop) direction += '上边界 ';
  229. if (isNearBottom) direction += '下边界 ';
  230. console.log(`敌人接近游戏区域${direction},距离小于${safeDistance}`);
  231. return true;
  232. }
  233. return false;
  234. }
  235. // 检测是否接近墙体
  236. checkWallProximity() {
  237. // 查找四个墙体
  238. const gameArea = find('Canvas/GameLevelUI/GameArea');
  239. if (!gameArea) return;
  240. const walls = [
  241. gameArea.getChildByName('TopFence'),
  242. gameArea.getChildByName('BottomFence'),
  243. gameArea.getChildByName('JiguangL'),
  244. gameArea.getChildByName('JiguangR')
  245. ];
  246. // 检查与每个墙体的距离
  247. for (const wall of walls) {
  248. if (!wall) continue;
  249. // 获取墙体的UITransform组件
  250. const wallTransform = wall.getComponent(UITransform);
  251. if (!wallTransform) continue;
  252. // 计算敌人与墙体的距离
  253. const distance = Vec3.distance(this.node.worldPosition, wall.worldPosition);
  254. // 根据墙体的尺寸调整阈值
  255. let proximityThreshold = 100; // 默认阈值
  256. // 如果是左右墙体,使用更大的阈值
  257. if (wall.name === 'JiguangL' || wall.name === 'JiguangR') {
  258. proximityThreshold = 80;
  259. }
  260. // 如果距离小于阈值,认为已接近墙体
  261. if (distance < proximityThreshold) {
  262. console.log(`敌人接近墙体 ${wall.name},距离: ${distance}`);
  263. // 记录接近的墙体
  264. this.collidedWall = wall;
  265. // 切换到攻击状态
  266. this.state = EnemyState.ATTACKING;
  267. // 停止移动(速度设为零)
  268. this.speed = 0;
  269. // 退出循环
  270. break;
  271. }
  272. }
  273. }
  274. // 更新攻击
  275. updateAttack(dt: number) {
  276. // 敌人在攻击状态下不移动
  277. // 更新攻击计时器
  278. this.attackTimer -= dt;
  279. // 攻击间隔到达,进行攻击
  280. if (this.attackTimer <= 0) {
  281. this.attackTimer = this.attackInterval;
  282. this.attackWall();
  283. }
  284. }
  285. // 攻击墙体
  286. attackWall() {
  287. if (!this.controller) {
  288. console.warn('敌人没有controller引用,无法攻击墙体');
  289. return;
  290. }
  291. // 对墙体造成伤害
  292. this.controller.damageWall(this.attackPower);
  293. // 添加攻击动画效果(可选)
  294. this.playAttackAnimation();
  295. console.log(`敌人攻击墙体,造成 ${this.attackPower} 点伤害`);
  296. }
  297. // 播放攻击动画
  298. playAttackAnimation() {
  299. // 简单的缩放动画模拟攻击效果
  300. const originalScale = this.node.scale.clone();
  301. // 放大
  302. this.node.setScale(originalScale.x * 1.2, originalScale.y * 1.2, originalScale.z);
  303. // 0.1秒后恢复原始大小
  304. this.scheduleOnce(() => {
  305. this.node.setScale(originalScale.x, originalScale.y, originalScale.z);
  306. }, 0.1);
  307. }
  308. }