Entity.java 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. package me.hammerle.supersnuvi.entity;
  2. import me.hammerle.supersnuvi.entity.components.ai.Controller;
  3. import me.hammerle.supersnuvi.entity.components.Energy;
  4. import me.hammerle.supersnuvi.entity.components.Health;
  5. import me.hammerle.supersnuvi.entity.components.ItemCollector;
  6. import me.hammerle.supersnuvi.entity.components.Movement;
  7. import me.hammerle.supersnuvi.tiles.Tile;
  8. import me.hammerle.supersnuvi.util.Face;
  9. import me.hammerle.supersnuvi.util.Utils;
  10. import me.hammerle.supersnuvi.gamelogic.Level;
  11. import me.hammerle.supersnuvi.math.Vector;
  12. import me.hammerle.supersnuvi.entity.components.IDespawn;
  13. public final class Entity
  14. {
  15. public static final float GRAVITY = 8.0f * Tile.SIZE_SCALE;
  16. public static final float STEP = 0.015625f;
  17. // this one is a little bit bigger to prevent wrong calculation
  18. // while going upwards
  19. public static final float UP_STEP = STEP + 0.00390625f;
  20. // the last position is used for interpolation during rendering
  21. private final Vector lastPos = new Vector();
  22. // the current position of the entity
  23. private final Vector pos = new Vector();
  24. // the width of the entity
  25. private final float width;
  26. // the height of the entity
  27. private final float height;
  28. // own force used by the controller component
  29. private final Vector ownForce = new Vector();
  30. // the final motion of the last tick
  31. private final Vector lastMotion = new Vector();
  32. // the motion, reduced by the movement collision check
  33. private final Vector motion = new Vector();
  34. // the friction reducing motion each tick
  35. private final Vector baseFriction = new Vector(0.0f, 1.0f);
  36. private final Vector friction = new Vector(0.0f, 1.0f);
  37. // a flag indicating that the entity is on the ground
  38. private boolean onGround = true;
  39. // entity components
  40. private final Controller controller;
  41. private final Health health;
  42. private final Energy energy;
  43. private final Movement move;
  44. private final ItemCollector itemCollector;
  45. private final IDespawn onDespawn;
  46. // the type of the entity, used by snuvi script
  47. private final String type;
  48. private Face direction = Face.NULL;
  49. protected Entity(float width, float height, Controller c, Health h, Energy e, Movement m, ItemCollector ic, IDespawn d, String type)
  50. {
  51. this.width = width;
  52. this.height = height;
  53. this.controller = c;
  54. this.health = h;
  55. this.energy = e;
  56. this.move = m;
  57. this.itemCollector = ic;
  58. this.onDespawn = d;
  59. this.type = type;
  60. }
  61. public String getType()
  62. {
  63. return type;
  64. }
  65. //--------------------------------------------------------------------------
  66. // components
  67. //--------------------------------------------------------------------------
  68. public boolean isAnimated()
  69. {
  70. return controller.isAnimated();
  71. }
  72. public Controller getController()
  73. {
  74. return controller;
  75. }
  76. public Health getHealth()
  77. {
  78. return health;
  79. }
  80. public Energy getEnergy()
  81. {
  82. return energy;
  83. }
  84. public Movement getMovement()
  85. {
  86. return move;
  87. }
  88. public ItemCollector getItemCollector()
  89. {
  90. return itemCollector;
  91. }
  92. public void onDespawn()
  93. {
  94. onDespawn.onDespawn(this);
  95. }
  96. //--------------------------------------------------------------------------
  97. // basic stuff
  98. //--------------------------------------------------------------------------
  99. public float getSquaredDistance(Entity e)
  100. {
  101. return Utils.getSquaredDistance(getCenterX(), getCenterY(), e.getCenterX(), e.getCenterY());
  102. }
  103. public float getX()
  104. {
  105. return pos.getX();
  106. }
  107. public float getY()
  108. {
  109. return pos.getY();
  110. }
  111. public void setPosition(float x, float y)
  112. {
  113. lastPos.set(x, y);
  114. pos.set(x, y);
  115. }
  116. public float getLastX()
  117. {
  118. return lastPos.getX();
  119. }
  120. public float getLastY()
  121. {
  122. return lastPos.getY();
  123. }
  124. public float getCenterX()
  125. {
  126. return pos.getX() + width * 0.5f;
  127. }
  128. public float getCenterY()
  129. {
  130. return pos.getY() + height * 0.5f;
  131. }
  132. public float getWidth()
  133. {
  134. return width;
  135. }
  136. public float getHeight()
  137. {
  138. return height;
  139. }
  140. public float getMotionX()
  141. {
  142. return motion.getX();
  143. }
  144. public float getMotionY()
  145. {
  146. return motion.getY();
  147. }
  148. public float getOwnForceX()
  149. {
  150. return ownForce.getX();
  151. }
  152. public float getOwnForceY()
  153. {
  154. return ownForce.getY();
  155. }
  156. public void applyOwnForce(float x, float y)
  157. {
  158. ownForce.add(x, y);
  159. }
  160. public void applyForce(float x, float y)
  161. {
  162. motion.add(x, y);
  163. }
  164. public void setBaseFriction(float fx, float fy)
  165. {
  166. baseFriction.set(fx, fy);
  167. }
  168. public Vector getFriction()
  169. {
  170. return friction;
  171. }
  172. public boolean isAt(float x, float y)
  173. {
  174. return Math.abs(x - pos.getX()) < STEP && Math.abs(y - pos.getY()) < STEP;
  175. }
  176. public boolean isColliding(float minX, float minY, float maxX, float maxY)
  177. {
  178. return maxX > getX() && getX() + width > minX && maxY > getY() && getY() + height > minY;
  179. }
  180. public boolean isColliding(Entity ent)
  181. {
  182. return isColliding(ent.getX(), ent.getY(), ent.getX() + ent.width, ent.getY() + ent.height);
  183. }
  184. private Face getCollidingFace(Entity o)
  185. {
  186. return Utils.getCollidingFace(
  187. o.getX(), o.getY(), o.getX() + o.getWidth(), o.getY() + o.getHeight(),
  188. getX(), getY(), getX() + getWidth(), getY() + getHeight());
  189. }
  190. public boolean jump()
  191. {
  192. if(onGround)
  193. {
  194. ownForce.addY(-move.getJumpPower());
  195. return true;
  196. }
  197. return false;
  198. }
  199. public Face getDirection()
  200. {
  201. return direction;
  202. }
  203. //--------------------------------------------------------------------------
  204. // ticking
  205. //--------------------------------------------------------------------------
  206. public void tick(Level level)
  207. {
  208. // last motion is current motion, last motion will be set in move
  209. motion.set(lastMotion);
  210. lastMotion.set(0.0f, 0.0f);
  211. // reset own force
  212. ownForce.set(0.0f, 0.0f);
  213. // update onGround before controller tick
  214. onGround = isOnTileGround(level);
  215. if(onGround)
  216. {
  217. if(!move.canMoveOnTiles())
  218. {
  219. motion.setY(0.0f);
  220. }
  221. }
  222. else
  223. {
  224. friction.set(0.2f, 1.0f);
  225. }
  226. // tick components
  227. controller.tick(this, level);
  228. energy.tick(this);
  229. health.tick();
  230. // apply gravity if needed
  231. if(move.hasGravity(this))
  232. {
  233. motion.addY(GRAVITY * move.getGravityFactor());
  234. }
  235. // apply friction
  236. motion.mul(friction);
  237. if(move.isInWater())
  238. {
  239. motion.mul(friction);
  240. move.setInWater(false);
  241. }
  242. friction.set(baseFriction);
  243. // modify current motion by own force
  244. motion.add(ownForce);
  245. if(ownForce.getX() < 0.0f)
  246. {
  247. direction = Face.LEFT;
  248. }
  249. else if(ownForce.getX() > 0.0f)
  250. {
  251. direction = Face.RIGHT;
  252. }
  253. }
  254. public void tickCollision(Level level)
  255. {
  256. // (entity <-> tile) collision
  257. doTileCollision(level);
  258. // (entity <-> entity) collision
  259. level.forEachCollidingEntity(this, (ent) ->
  260. {
  261. controller.onCollideWithEntity(this, ent, getCollidingFace(ent));
  262. });
  263. slideOnEdge(level);
  264. // remeber last position for rendering
  265. lastPos.set(pos);
  266. }
  267. public int move(Level level)
  268. {
  269. if(motion.getX() == 0.0f && motion.getY() == 0.0f)
  270. {
  271. return 0;
  272. }
  273. Vector maxMotion = new Vector();
  274. maxMotion.set(motion);
  275. if(!move.canMoveOnEntities() || !move.canMoveOnTiles())
  276. {
  277. reduceMotion(level);
  278. }
  279. pos.add(motion);
  280. int r = (motion.getX() == 0.0f && motion.getY() == 0.0f) ? 0 : 1;
  281. lastMotion.add(motion);
  282. maxMotion.sub(motion);
  283. motion.set(maxMotion);
  284. return r;
  285. }
  286. private boolean isOnTileGround(Level level)
  287. {
  288. float minX = pos.getX();
  289. float minY = pos.getY() + height;
  290. float maxX = pos.getX() + width;
  291. float maxY = pos.getY() + height + STEP * 2;
  292. int startX = Utils.toBlock(minX);
  293. int startY = Utils.toBlock(minY);
  294. int endX = Utils.toBlock(maxX);
  295. int endY = Utils.toBlock(maxY);
  296. for(int x = startX; x <= endX; x++)
  297. {
  298. for(int y = startY; y <= endY; y++)
  299. {
  300. Tile t = level.getInteractionTile(x, y);
  301. if(t.isMoveColliding(minX, minY, maxX, maxY, x, y, level))
  302. {
  303. return true;
  304. }
  305. }
  306. }
  307. for(Entity ent : level.getEntities())
  308. {
  309. if(ent == this)
  310. {
  311. continue;
  312. }
  313. if(ent.getMovement().isSolid() && ent.isColliding(minX, minY, maxX, maxY))
  314. {
  315. return true;
  316. }
  317. }
  318. return false;
  319. }
  320. private void doTileCollision(Level level)
  321. {
  322. float minX = pos.getX() - STEP;
  323. float minY = pos.getY() - STEP;
  324. float maxX = pos.getX() + width + STEP;
  325. float maxY = pos.getY() + height + STEP;
  326. int startX = Utils.toBlock(minX);
  327. int startY = Utils.toBlock(minY);
  328. int endX = Utils.toBlock(maxX);
  329. int endY = Utils.toBlock(maxY);
  330. for(int x = startX; x <= endX; x++)
  331. {
  332. for(int y = startY; y <= endY; y++)
  333. {
  334. Tile t = level.getInteractionTile(x, y);
  335. if(t.isColliding(minX, minY, maxX, maxY, x, y, level))
  336. {
  337. Face f = t.getCollidingFace(minX, minY, maxX, maxY, x, y, level);
  338. controller.onCollideWithTile(this, x, y, level, t, f);
  339. t.onEntityCollide(this, x, y, f.getOpposite(), level);
  340. }
  341. }
  342. }
  343. }
  344. private boolean isCollidingWithTiles(int startX, int startY, int endX, int endY, Level level)
  345. {
  346. if(move.canMoveOnTiles())
  347. {
  348. return false;
  349. }
  350. float minX = getX();
  351. float minY = getY();
  352. float maxX = minX + width;
  353. float maxY = minY + height;
  354. for(int x = startX; x <= endX; x++)
  355. {
  356. for(int y = startY; y <= endY; y++)
  357. {
  358. if(level.getInteractionTile(x, y).isMoveColliding(minX, minY, maxX, maxY, x, y, level))
  359. {
  360. return true;
  361. }
  362. }
  363. }
  364. return false;
  365. }
  366. private boolean isCollidingWithEntities(Level level)
  367. {
  368. if(move.canMoveOnEntities())
  369. {
  370. return false;
  371. }
  372. float minX = pos.getX();
  373. float minY = pos.getY();
  374. float maxX = pos.getX() + width;
  375. float maxY = pos.getY() + height;
  376. for(Entity ent : level.getEntities())
  377. {
  378. if(ent == this)
  379. {
  380. continue;
  381. }
  382. if(ent.isColliding(minX, minY, maxX, maxY) && (move.isSolid() || ent.move.isSolid()))
  383. {
  384. return true;
  385. }
  386. }
  387. for(Entity ent : level.getEntitiesInQueue())
  388. {
  389. if(ent == this)
  390. {
  391. continue;
  392. }
  393. if(ent.isColliding(minX, minY, maxX, maxY) && (move.isSolid() || ent.move.isSolid()))
  394. {
  395. return true;
  396. }
  397. }
  398. return false;
  399. }
  400. public void reduceMotion(Level level)
  401. {
  402. if(motion.getX() == 0.0f && motion.getY() == 0.0f)
  403. {
  404. return;
  405. }
  406. float mx = motion.getX();
  407. float my = motion.getY();
  408. int startX;
  409. int endX;
  410. if(mx < 0.0f)
  411. {
  412. startX = Utils.toBlock(pos.getX() + mx);
  413. endX = Utils.toBlock(pos.getX() + width);
  414. }
  415. else
  416. {
  417. startX = Utils.toBlock(pos.getX());
  418. endX = Utils.toBlock(pos.getX() + width + mx);
  419. }
  420. int startY;
  421. int endY;
  422. if(my < 0.0f)
  423. {
  424. startY = Utils.toBlock(pos.getY() + my);
  425. endY = Utils.toBlock(pos.getY() + height);
  426. }
  427. else
  428. {
  429. startY = Utils.toBlock(pos.getY());
  430. endY = Utils.toBlock(pos.getY() + height + my);
  431. }
  432. float oldPosX = pos.getX();
  433. float oldPosY = pos.getY();
  434. while(mx != 0.0 || my != 0.0)
  435. {
  436. if(mx != 0.0f)
  437. {
  438. float oldX = pos.getX();
  439. if(mx < 0.0)
  440. {
  441. if(mx > -STEP)
  442. {
  443. pos.addX(mx);
  444. mx = 0.0f;
  445. }
  446. else
  447. {
  448. pos.addX(-STEP);
  449. mx += STEP;
  450. }
  451. }
  452. else if(mx > 0.0)
  453. {
  454. if(mx < STEP)
  455. {
  456. pos.addX(mx);
  457. mx = 0.0f;
  458. }
  459. else
  460. {
  461. pos.addX(STEP);
  462. mx -= STEP;
  463. }
  464. }
  465. if(isCollidingWithTiles(startX, startY, endX, endY, level) || isCollidingWithEntities(level))
  466. {
  467. pos.addY(-UP_STEP);
  468. if(isCollidingWithTiles(startX, startY, endX, endY, level) || isCollidingWithEntities(level))
  469. {
  470. pos.addY(UP_STEP);
  471. pos.setX(oldX);
  472. mx = 0.0f;
  473. }
  474. }
  475. }
  476. if(my != 0.0f)
  477. {
  478. float oldY = pos.getY();
  479. if(my < 0.0)
  480. {
  481. if(my > -STEP)
  482. {
  483. pos.addY(my);
  484. my = 0.0f;
  485. }
  486. else
  487. {
  488. pos.addY(-STEP);
  489. my += STEP;
  490. }
  491. }
  492. else if(my > 0.0)
  493. {
  494. if(my < STEP)
  495. {
  496. pos.addY(my);
  497. my = 0.0f;
  498. }
  499. else
  500. {
  501. pos.addY(STEP);
  502. my -= STEP;
  503. }
  504. }
  505. if(isCollidingWithTiles(startX, startY, endX, endY, level) || isCollidingWithEntities(level))
  506. {
  507. my = 0.0f;
  508. pos.setY(oldY);
  509. }
  510. }
  511. }
  512. motion.set(pos.getX() - oldPosX, pos.getY() - oldPosY);
  513. pos.set(oldPosX, oldPosY);
  514. }
  515. private void slideOnEdge(Level level)
  516. {
  517. float minY = pos.getY() + height;
  518. float maxY = pos.getY() + height + STEP * 2;
  519. float minX = pos.getX();
  520. float midX = pos.getX() + width * 0.5f;
  521. float maxX = pos.getX() + width;
  522. int startX = Utils.toBlock(minX);
  523. int startY = Utils.toBlock(minY);
  524. int endX = Utils.toBlock(maxX);
  525. int endY = Utils.toBlock(maxY);
  526. int leftEdges = 0;
  527. int rightEdges = 0;
  528. for(int x = startX; x <= endX; x++)
  529. {
  530. for(int y = startY; y <= endY; y++)
  531. {
  532. Tile t = level.getInteractionTile(x, y);
  533. if(t.isMoveColliding(minX, minY, midX, maxY, x, y, level))
  534. {
  535. rightEdges++;
  536. }
  537. if(t.isMoveColliding(midX, minY, maxX, maxY, x, y, level))
  538. {
  539. leftEdges++;
  540. }
  541. }
  542. }
  543. if(leftEdges == 0)
  544. {
  545. applyForce(STEP * 25, 0.0f);
  546. }
  547. if(rightEdges == 0)
  548. {
  549. applyForce(-STEP * 25, 0.0f);
  550. }
  551. }
  552. public void renderTick(float lag)
  553. {
  554. controller.renderTick(this, lag);
  555. }
  556. //--------------------------------------------------------------------------
  557. // gravity, friction
  558. //--------------------------------------------------------------------------
  559. public boolean isOnGround()
  560. {
  561. return onGround;
  562. }
  563. @Override
  564. public String toString()
  565. {
  566. return String.format("(%f, %f, %f, %f)", pos.getX(), pos.getY(), pos.getX() + width, pos.getY() + height);
  567. }
  568. }