25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

810 lines
18 KiB

  1. define([
  2. 'js/resources',
  3. 'js/system/events',
  4. 'js/misc/physics',
  5. 'js/rendering/effects',
  6. 'js/rendering/tileOpacity',
  7. 'js/rendering/particles',
  8. 'js/rendering/shaders/outline',
  9. 'js/rendering/spritePool'/*,
  10. 'picture'*/
  11. ], function (
  12. resources,
  13. events,
  14. physics,
  15. effects,
  16. tileOpacity,
  17. particles,
  18. shaderOutline,
  19. spritePool/*,
  20. picture*/
  21. ) {
  22. let pixi = PIXI;
  23. let mRandom = Math.random.bind(Math);
  24. return {
  25. stage: null,
  26. layers: {
  27. objects: null,
  28. mobs: null,
  29. characters: null,
  30. attacks: null,
  31. effects: null,
  32. particles: null,
  33. lightPatches: null,
  34. lightBeams: null,
  35. tileSprites: null,
  36. hiders: null
  37. },
  38. titleScreen: false,
  39. width: 0,
  40. height: 0,
  41. showTilesW: 0,
  42. showTilesH: 0,
  43. pos: {
  44. x: 0,
  45. y: 0
  46. },
  47. moveTo: null,
  48. moveSpeed: 0,
  49. moveSpeedMax: 1.50,
  50. moveSpeedInc: 0.5,
  51. lastUpdatePos: {
  52. x: 0,
  53. y: 0
  54. },
  55. zoneId: null,
  56. textures: {},
  57. textureCache: {},
  58. sprites: [],
  59. lastTick: null,
  60. hiddenRooms: null,
  61. init: function () {
  62. PIXI.settings.GC_MODE = PIXI.GC_MODES.AUTO;
  63. PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST;
  64. events.on('onGetMap', this.onGetMap.bind(this));
  65. events.on('onToggleFullscreen', this.toggleScreen.bind(this));
  66. let zoom = isMobile ? 1 : window.devicePixelRatio;
  67. this.width = $('body').width() * zoom;
  68. this.height = $('body').height() * zoom;
  69. this.showTilesW = Math.ceil((this.width / scale) / 2) + 3;
  70. this.showTilesH = Math.ceil((this.height / scale) / 2) + 3;
  71. this.renderer = new PIXI.Renderer({
  72. width: this.width,
  73. height: this.height,
  74. backgroundColor: '0x2d2136'
  75. });
  76. window.addEventListener('resize', this.onResize.bind(this));
  77. $(this.renderer.view)
  78. .appendTo('.canvas-container');
  79. this.stage = new pixi.Container();
  80. let layers = this.layers;
  81. Object.keys(layers).forEach(function (l) {
  82. layers[l] = new pixi.Container();
  83. layers[l].layer = (l === 'tileSprites') ? 'tiles' : l;
  84. this.stage.addChild(layers[l]);
  85. }, this);
  86. let spriteNames = ['tiles', 'mobs', 'bosses', 'animBigObjects', 'bigObjects', 'objects', 'characters', 'attacks', 'auras', 'walls', 'ui', 'animChar', 'animMob', 'animBoss', 'white', 'ray'];
  87. resources.spriteNames.forEach(function (s) {
  88. if (s.indexOf('.png') > -1)
  89. spriteNames.push(s);
  90. });
  91. spriteNames.forEach(function (t) {
  92. this.textures[t] = new pixi.BaseTexture(resources.sprites[t].image);
  93. this.textures[t].scaleMode = pixi.SCALE_MODES.NEAREST;
  94. }, this);
  95. particles.init({
  96. r: this,
  97. renderer: this.renderer,
  98. stage: this.layers.particles
  99. });
  100. this.buildSpritesTexture();
  101. },
  102. buildSpritesTexture: function () {
  103. let container = new pixi.Container();
  104. let totalHeight = 0;
  105. ['tiles', 'walls', 'objects'].forEach(function (t) {
  106. let texture = this.textures[t];
  107. let tile = new pixi.Sprite(new pixi.Texture(texture));
  108. tile.width = texture.width;
  109. tile.height = texture.height;
  110. tile.x = 0;
  111. tile.y = totalHeight;
  112. container.addChild(tile);
  113. totalHeight += tile.height;
  114. }, this);
  115. let renderTexture = pixi.RenderTexture.create(this.textures.tiles.width, totalHeight);
  116. this.renderer.render(container, renderTexture);
  117. this.textures.sprites = renderTexture;
  118. this.textures.scaleMult = pixi.SCALE_MODES.NEAREST;
  119. },
  120. toggleScreen: function () {
  121. let isFullscreen = (window.innerHeight === screen.height);
  122. if (isFullscreen) {
  123. let doc = document;
  124. (doc.cancelFullscreen || doc.msCancelFullscreen || doc.mozCancelFullscreen || doc.webkitCancelFullScreen).call(doc);
  125. return 'Windowed';
  126. }
  127. let el = $('body')[0];
  128. (el.requestFullscreen || el.msRequestFullscreen || el.mozRequestFullscreen || el.webkitRequestFullscreen).call(el);
  129. return 'Fullscreen';
  130. },
  131. buildTitleScreen: function () {
  132. this.titleScreen = true;
  133. this.setPosition({
  134. x: 0,
  135. y: 0
  136. }, true);
  137. let w = Math.ceil(this.width / scale) + 1;
  138. let h = Math.ceil(this.height / scale) + 1;
  139. let container = this.layers.tileSprites;
  140. for (let i = 0; i < w; i++) {
  141. let ii = i / 10;
  142. for (let j = 0; j < h; j++) {
  143. let roll = Math.sin(((j * 0.2) % 5) + Math.cos(ii % 8));
  144. let tile = 5;
  145. if (roll < -0.2)
  146. tile = 3;
  147. else if (roll < 0.2)
  148. tile = 4;
  149. else if (roll < 0.5)
  150. tile = 53;
  151. let alpha = mRandom();
  152. if ([5, 53].indexOf(tile) > -1)
  153. alpha *= 2;
  154. alpha = Math.min(Math.max(0.15, alpha), 0.65);
  155. if (mRandom() < 0.35) {
  156. tile = {
  157. 5: 6,
  158. 3: 0,
  159. 4: 1,
  160. 53: 54
  161. }[tile];
  162. }
  163. let sprite = new pixi.Sprite(this.getTexture('sprites', tile));
  164. sprite.alpha = alpha;
  165. sprite.position.x = i * scale;
  166. sprite.position.y = j * scale;
  167. sprite.width = scale;
  168. sprite.height = scale;
  169. if (mRandom() < 0.5) {
  170. sprite.position.x += scale;
  171. sprite.scale.x = -scaleMult;
  172. }
  173. container.addChild(sprite);
  174. }
  175. }
  176. },
  177. onResize: function () {
  178. if (isMobile)
  179. return;
  180. let zoom = window.devicePixelRatio;
  181. this.width = $('body').width() * zoom;
  182. this.height = $('body').height() * zoom;
  183. this.showTilesW = Math.ceil((this.width / scale) / 2) + 3;
  184. this.showTilesH = Math.ceil((this.height / scale) / 2) + 3;
  185. this.renderer.resize(this.width, this.height);
  186. if (window.player) {
  187. this.setPosition({
  188. x: (window.player.x - (this.width / (scale * 2))) * scale,
  189. y: (window.player.y - (this.height / (scale * 2))) * scale
  190. }, true);
  191. }
  192. if (this.titleScreen) {
  193. this.clean();
  194. this.buildTitleScreen();
  195. }
  196. events.emit('onResize');
  197. },
  198. getTexture: function (baseTex, cell, size) {
  199. size = size || 8;
  200. let textureName = baseTex + '_' + cell;
  201. let textureCache = this.textureCache;
  202. let cached = textureCache[textureName];
  203. if (!cached) {
  204. let y = ~~(cell / 8);
  205. let x = cell - (y * 8);
  206. cached = new pixi.Texture(this.textures[baseTex], new pixi.Rectangle(x * size, y * size, size, size));
  207. textureCache[textureName] = cached;
  208. }
  209. return cached;
  210. },
  211. clean: function () {
  212. this.stage.removeChild(this.layers.hiders);
  213. this.layers.hiders = new pixi.Container();
  214. this.layers.hiders.layer = 'hiders';
  215. this.stage.addChild(this.layers.hiders);
  216. let container = this.layers.tileSprites;
  217. this.stage.removeChild(container);
  218. this.layers.tileSprites = container = new pixi.Container();
  219. container.layer = 'tiles';
  220. this.stage.addChild(container);
  221. this.stage.children.sort(function (a, b) {
  222. if (a.layer === 'hiders')
  223. return 1;
  224. else if (b.layer === 'hiders')
  225. return -1;
  226. else if (a.layer === 'tiles')
  227. return -1;
  228. else if (b.layer === 'tiles')
  229. return 1;
  230. return 0;
  231. });
  232. },
  233. buildTile: function (c, i, j) {
  234. let alpha = tileOpacity.map(c);
  235. let canFlip = tileOpacity.canFlip(c);
  236. let tile = new pixi.Sprite(this.getTexture('sprites', c));
  237. tile.alpha = alpha;
  238. tile.position.x = i * scale;
  239. tile.position.y = j * scale;
  240. tile.width = scale;
  241. tile.height = scale;
  242. if (canFlip && mRandom() < 0.5) {
  243. tile.position.x += scale;
  244. tile.scale.x = -scaleMult;
  245. }
  246. return tile;
  247. },
  248. onGetMap: function (msg) {
  249. this.titleScreen = false;
  250. physics.init(msg.collisionMap);
  251. let map = this.map = msg.map;
  252. let w = this.w = map.length;
  253. let h = this.h = map[0].length;
  254. for (let i = 0; i < w; i++) {
  255. let row = map[i];
  256. for (let j = 0; j < h; j++) {
  257. if (!row[j].split)
  258. row[j] += '';
  259. row[j] = row[j].split(',');
  260. }
  261. }
  262. this.clean();
  263. spritePool.clean();
  264. this.stage.filters = [new PIXI.filters.AlphaFilter()];
  265. this.stage.filterArea = new PIXI.Rectangle(0, 0, w * scale, h * scale);
  266. this.hiddenRooms = msg.hiddenRooms;
  267. this.sprites = _.get2dArray(w, h, 'array');
  268. this.stage.children.sort(function (a, b) {
  269. if (a.layer === 'tiles')
  270. return -1;
  271. else if (b.layer === 'tiles')
  272. return 1;
  273. return 0;
  274. }, this);
  275. if (this.zoneId !== null)
  276. events.emit('onRezone', this.zoneId);
  277. this.zoneId = msg.zoneId;
  278. msg.clientObjects.forEach(function (c) {
  279. c.zoneId = this.zoneId;
  280. events.emit('onGetObject', c);
  281. }, this);
  282. },
  283. setPosition: function (pos, instant) {
  284. pos.x += 16;
  285. pos.y += 16;
  286. let player = window.player;
  287. if (player) {
  288. let px = player.x;
  289. let py = player.y;
  290. let hiddenRooms = this.hiddenRooms || [];
  291. let hLen = hiddenRooms.length;
  292. for (let i = 0; i < hLen; i++) {
  293. let h = hiddenRooms[i];
  294. if (!h.discoverable)
  295. continue;
  296. if (
  297. px < h.x ||
  298. px >= h.x + h.width ||
  299. py < h.y ||
  300. py >= h.y + h.height ||
  301. !physics.isInPolygon(px, py, h.area)
  302. )
  303. continue;
  304. h.discovered = true;
  305. }
  306. }
  307. if (instant) {
  308. this.moveTo = null;
  309. this.pos = pos;
  310. this.stage.x = -~~this.pos.x;
  311. this.stage.y = -~~this.pos.y;
  312. } else
  313. this.moveTo = pos;
  314. this.updateSprites();
  315. },
  316. isVisible: function (x, y) {
  317. let stage = this.stage;
  318. let sx = -stage.x;
  319. let sy = -stage.y;
  320. let sw = this.width;
  321. let sh = this.height;
  322. return (!(x < sx || y < sy || x >= sx + sw || y >= sy + sh));
  323. },
  324. isHidden: function (x, y) {
  325. let hiddenRooms = this.hiddenRooms;
  326. let hLen = hiddenRooms.length;
  327. if (!hLen)
  328. return false;
  329. let player = window.player;
  330. let px = player.x;
  331. let py = player.y;
  332. let hidden = false;
  333. for (let i = 0; i < hLen; i++) {
  334. let h = hiddenRooms[i];
  335. let outsideHider = (
  336. x < h.x ||
  337. x >= h.x + h.width ||
  338. y < h.y ||
  339. y >= h.y + h.height
  340. );
  341. if (outsideHider)
  342. continue;
  343. let inHider = physics.isInPolygon(x, y, h.area);
  344. if (!inHider)
  345. continue;
  346. if (h.discovered)
  347. return false;
  348. outsideHider = (
  349. px < h.x ||
  350. px >= h.x + h.width ||
  351. py < h.y ||
  352. py >= h.y + h.height
  353. );
  354. if (outsideHider) {
  355. hidden = true;
  356. continue;
  357. }
  358. inHider = physics.isInPolygon(px, py, h.area);
  359. if (inHider)
  360. return false;
  361. hidden = true;
  362. }
  363. return hidden;
  364. },
  365. updateSprites: function () {
  366. if (this.titleScreen)
  367. return;
  368. let player = window.player;
  369. if (!player)
  370. return;
  371. let w = this.w;
  372. let h = this.h;
  373. let x = ~~((-this.stage.x / scale) + (this.width / (scale * 2)));
  374. let y = ~~((-this.stage.y / scale) + (this.height / (scale * 2)));
  375. this.lastUpdatePos.x = this.stage.x;
  376. this.lastUpdatePos.y = this.stage.y;
  377. let sprites = this.sprites;
  378. let map = this.map;
  379. let container = this.layers.tileSprites;
  380. let sw = this.showTilesW;
  381. let sh = this.showTilesH;
  382. let lowX = Math.max(0, x - sw + 1);
  383. let lowY = Math.max(0, y - sh + 2);
  384. let highX = Math.min(w, x + sw - 2);
  385. let highY = Math.min(h, y + sh - 2);
  386. let addedSprite = false;
  387. let checkHidden = this.isHidden.bind(this);
  388. let newVisible = [];
  389. let newHidden = [];
  390. for (let i = lowX; i < highX; i++) {
  391. let mapRow = map[i];
  392. let spriteRow = sprites[i];
  393. for (let j = lowY; j < highY; j++) {
  394. let cell = mapRow[j];
  395. if (!cell)
  396. continue;
  397. let cLen = cell.length;
  398. if (!cLen)
  399. return;
  400. let rendered = spriteRow[j];
  401. let isHidden = checkHidden(i, j);
  402. if (rendered.length > 0) {
  403. if (!isHidden)
  404. continue;
  405. else {
  406. newHidden.push({
  407. x: i,
  408. y: j
  409. });
  410. let rLen = rendered.length;
  411. for (let k = 0; k < rLen; k++) {
  412. let sprite = rendered[k];
  413. sprite.visible = false;
  414. spritePool.store(sprite);
  415. }
  416. spriteRow[j] = [];
  417. continue;
  418. }
  419. } else if (isHidden)
  420. continue;
  421. newVisible.push({
  422. x: i,
  423. y: j
  424. });
  425. for (let k = 0; k < cLen; k++) {
  426. let c = cell[k];
  427. if (c === '0' || c === '')
  428. continue;
  429. c--;
  430. let flipped = '';
  431. if (tileOpacity.canFlip(c)) {
  432. if (mRandom() < 0.5)
  433. flipped = 'flip';
  434. }
  435. let tile = spritePool.getSprite(flipped + c);
  436. if (!tile) {
  437. tile = this.buildTile(c, i, j);
  438. container.addChild(tile);
  439. tile.type = c;
  440. tile.sheetNum = tileOpacity.getSheetNum(c);
  441. addedSprite = true;
  442. } else {
  443. tile.position.x = i * scale;
  444. tile.position.y = j * scale;
  445. if (flipped !== '')
  446. tile.position.x += scale;
  447. tile.visible = true;
  448. }
  449. tile.z = k;
  450. rendered.push(tile);
  451. }
  452. }
  453. }
  454. lowX = Math.max(0, lowX - 10);
  455. lowY = Math.max(0, lowY - 10);
  456. highX = Math.min(w - 1, highX + 10);
  457. highY = Math.min(h - 1, highY + 10);
  458. for (let i = lowX; i < highX; i++) {
  459. let spriteRow = sprites[i];
  460. let outside = ((i >= x - sw) && (i < x + sw));
  461. for (let j = lowY; j < highY; j++) {
  462. if ((outside) && (j >= y - sh) && (j < y + sh))
  463. continue;
  464. let list = spriteRow[j];
  465. let lLen = list.length;
  466. for (let k = 0; k < lLen; k++) {
  467. let sprite = list[k];
  468. sprite.visible = false;
  469. spritePool.store(sprite);
  470. }
  471. spriteRow[j] = [];
  472. }
  473. }
  474. events.emit('onTilesVisible', newVisible, true);
  475. events.emit('onTilesVisible', newHidden, false);
  476. if (addedSprite)
  477. container.children.sort((a, b) => a.z - b.z);
  478. },
  479. update: function () {
  480. let time = +new Date();
  481. if (this.moveTo) {
  482. let deltaX = this.moveTo.x - this.pos.x;
  483. let deltaY = this.moveTo.y - this.pos.y;
  484. if (deltaX !== 0 || deltaY !== 0) {
  485. let moveSpeed = this.moveSpeed;
  486. let distance = Math.max(Math.abs(deltaX), Math.abs(deltaY));
  487. let moveSpeedMax = this.moveSpeedMax;
  488. if (distance > 100)
  489. moveSpeedMax *= 1.75;
  490. if (this.moveSpeed < moveSpeedMax)
  491. this.moveSpeed += this.moveSpeedInc;
  492. let elapsed = time - this.lastTick;
  493. moveSpeed *= (elapsed / 16.67);
  494. if (moveSpeed > distance)
  495. moveSpeed = distance;
  496. deltaX = (deltaX / distance) * moveSpeed;
  497. deltaY = (deltaY / distance) * moveSpeed;
  498. this.pos.x = this.pos.x + deltaX;
  499. this.pos.y = this.pos.y + deltaY;
  500. } else {
  501. this.moveSpeed = 0;
  502. this.moveTo = null;
  503. }
  504. let stage = this.stage;
  505. stage.x = -~~this.pos.x;
  506. stage.y = -~~this.pos.y;
  507. let halfScale = scale / 2;
  508. if (Math.abs(stage.x - this.lastUpdatePos.x) > halfScale || Math.abs(stage.y - this.lastUpdatePos.y) > halfScale)
  509. this.updateSprites();
  510. events.emit('onSceneMove');
  511. }
  512. this.lastTick = time;
  513. },
  514. buildContainer: function (obj) {
  515. let container = new pixi.Container();
  516. this.layers[obj.layerName || obj.sheetName].addChild(container);
  517. return container;
  518. },
  519. buildRectangle: function (obj) {
  520. let graphics = new pixi.Graphics();
  521. let alpha = obj.alpha;
  522. if (obj.has('alpha'))
  523. graphics.alpha = alpha;
  524. let fillAlpha = obj.fillAlpha;
  525. if (obj.has('fillAlpha'))
  526. fillAlpha = 1;
  527. graphics.beginFill(obj.color || '0x48edff', fillAlpha);
  528. if (obj.strokeColor)
  529. graphics.lineStyle(scaleMult, obj.strokeColor);
  530. graphics.drawRect(0, 0, obj.w, obj.h);
  531. graphics.endFill();
  532. (obj.parent || this.layers[obj.layerName || obj.sheetName]).addChild(graphics);
  533. graphics.position.x = obj.x;
  534. graphics.position.y = obj.y;
  535. return graphics;
  536. },
  537. moveRectangle: function (obj) {
  538. obj.sprite.position.x = obj.x;
  539. obj.sprite.position.y = obj.y;
  540. obj.sprite.width = obj.w;
  541. obj.sprite.height = obj.h;
  542. },
  543. buildObject: function (obj) {
  544. let w = 8;
  545. let h = 8;
  546. if (obj.w) {
  547. w = obj.w / scaleMult;
  548. h = obj.h / scaleMult;
  549. }
  550. let bigSheets = ['bosses', 'bigObjects', 'animBigObjects'];
  551. if ((bigSheets.indexOf(obj.sheetName) > -1) || (obj.sheetName.indexOf('bosses') > -1)) {
  552. obj.layerName = 'mobs';
  553. w = 24;
  554. h = 24;
  555. obj.w = w * scaleMult;
  556. obj.h = h * scaleMult;
  557. }
  558. let sprite = new pixi.Sprite(this.getTexture(obj.sheetName, obj.cell, w));
  559. sprite.x = obj.x * scale;
  560. sprite.y = obj.y * scale;
  561. sprite.width = obj.w || scale;
  562. sprite.height = obj.h || scale;
  563. sprite.visible = obj.has('visible') ? obj.visible : true;
  564. if ((bigSheets.indexOf(obj.sheetName) > -1) || (obj.sheetName.indexOf('bosses') > -1)) {
  565. sprite.x -= scale;
  566. sprite.y -= (scale * 2);
  567. }
  568. if (obj.flipX) {
  569. sprite.scale.x *= -1;
  570. if (bigSheets.indexOf(obj.sheetName) > -1)
  571. sprite.x += (scale * 2);
  572. else
  573. sprite.x += scale;
  574. }
  575. (obj.parent || this.layers[obj.layerName || obj.sheetName] || this.layers.objects).addChild(sprite);
  576. return sprite;
  577. },
  578. addFilter: function (sprite) {
  579. let thickness = (sprite.width > scale) ? 8 : 16;
  580. let filter = new shaderOutline(this.renderer.width, this.renderer.height, thickness, '0xffffff');
  581. if (!sprite.filters)
  582. sprite.filters = [filter];
  583. else
  584. sprite.filters.push();
  585. return filter;
  586. },
  587. removeFilter: function (sprite, filter) {
  588. if (sprite.filters)
  589. sprite.filters = null;
  590. },
  591. buildText: function (obj) {
  592. let textSprite = new pixi.Text(obj.text, {
  593. fontFamily: 'bitty',
  594. fontSize: (obj.fontSize || 14),
  595. fill: obj.color || 0xF2F5F5,
  596. stroke: 0x2d2136,
  597. strokeThickness: 4,
  598. align: 'center'
  599. });
  600. textSprite.x = obj.x - (textSprite.width / 2);
  601. textSprite.y = obj.y;
  602. let parentSprite = obj.parent || this.layers[obj.layerName];
  603. parentSprite.addChild(textSprite);
  604. return textSprite;
  605. },
  606. buildEmitter: function (config) {
  607. return particles.buildEmitter(config);
  608. },
  609. destroyEmitter: function (emitter) {
  610. particles.destroyEmitter(emitter);
  611. },
  612. setSprite: function (obj) {
  613. obj.sprite.texture = this.getTexture(obj.sheetName, obj.cell, obj.sprite.width / scaleMult);
  614. },
  615. reorder: function (sprite) {
  616. this.layers.mobs.children.sort((a, b) => b.y - a.y);
  617. },
  618. destroyObject: function (obj) {
  619. if (obj.sprite.parent)
  620. obj.sprite.parent.removeChild(obj.sprite);
  621. },
  622. render: function () {
  623. if (!this.stage)
  624. return;
  625. effects.render();
  626. particles.update();
  627. this.renderer.render(this.stage);
  628. }
  629. };
  630. });