Full rewrite of game + extra features
Full rewrite close to MVP
23
bsrc/main.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import * as PIXI from "pixi.js";
|
||||||
|
import Game from "./base/Game";
|
||||||
|
import Assets from "./base/Assets";
|
||||||
|
(async () => {
|
||||||
|
const app = new PIXI.Application();
|
||||||
|
await app.init({
|
||||||
|
width: 640,
|
||||||
|
height: 360,
|
||||||
|
resizeTo: document.body,
|
||||||
|
backgroundColor: "white",
|
||||||
|
sharedTicker: true,
|
||||||
|
preference: "webgl",
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.appendChild(app.canvas);
|
||||||
|
await Assets.LoadAssets();
|
||||||
|
const game = new Game(app.screen);
|
||||||
|
app.stage.addChild(game.container);
|
||||||
|
window.addEventListener("resize", () => {
|
||||||
|
app.renderer.resize(window.innerWidth, window.innerHeight);
|
||||||
|
game.setBounds(0, 0, app.screen.width, app.screen.height);
|
||||||
|
});
|
||||||
|
})();
|
1
bsrc/typescript.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="32" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path fill="#007ACC" d="M0 128v128h256V0H0z"></path><path fill="#FFF" d="m56.612 128.85l-.081 10.483h33.32v94.68h23.568v-94.68h33.321v-10.28c0-5.69-.122-10.444-.284-10.566c-.122-.162-20.4-.244-44.983-.203l-44.74.122l-.121 10.443Zm149.955-10.742c6.501 1.625 11.459 4.51 16.01 9.224c2.357 2.52 5.851 7.111 6.136 8.208c.08.325-11.053 7.802-17.798 11.988c-.244.162-1.22-.894-2.317-2.52c-3.291-4.795-6.745-6.867-12.028-7.233c-7.76-.528-12.759 3.535-12.718 10.321c0 1.992.284 3.17 1.097 4.795c1.707 3.536 4.876 5.649 14.832 9.956c18.326 7.883 26.168 13.084 31.045 20.48c5.445 8.249 6.664 21.415 2.966 31.208c-4.063 10.646-14.14 17.879-28.323 20.276c-4.388.772-14.79.65-19.504-.203c-10.28-1.828-20.033-6.908-26.047-13.572c-2.357-2.6-6.949-9.387-6.664-9.874c.122-.163 1.178-.813 2.356-1.504c1.138-.65 5.446-3.129 9.509-5.485l7.355-4.267l1.544 2.276c2.154 3.29 6.867 7.801 9.712 9.305c8.167 4.307 19.383 3.698 24.909-1.26c2.357-2.153 3.332-4.388 3.332-7.68c0-2.966-.366-4.266-1.91-6.501c-1.99-2.845-6.054-5.242-17.595-10.24c-13.206-5.69-18.895-9.224-24.096-14.832c-3.007-3.25-5.852-8.452-7.03-12.8c-.975-3.617-1.22-12.678-.447-16.335c2.723-12.76 12.353-21.659 26.25-24.3c4.51-.853 14.994-.528 19.424.569Z"></path></svg>
|
After Width: | Height: | Size: 1.4 KiB |
1
bsrc/vite-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
10
package-lock.json
generated
@ -8,6 +8,7 @@
|
|||||||
"name": "bastion",
|
"name": "bastion",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"pixi-viewport": "^6.0.3",
|
||||||
"pixi.js": "^8.4.0"
|
"pixi.js": "^8.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -774,6 +775,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/pixi-viewport": {
|
||||||
|
"version": "6.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/pixi-viewport/-/pixi-viewport-6.0.3.tgz",
|
||||||
|
"integrity": "sha512-2+qPJ0/n+8hQYhWvY+795+x9y3MiUrCOWacK0DY53whowWaGdx9iDocy7z1pBwjkZhC52YvrJQuZKK0sdVLtBw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"pixi.js": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pixi.js": {
|
"node_modules/pixi.js": {
|
||||||
"version": "8.4.0",
|
"version": "8.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-8.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-8.4.0.tgz",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"vite": "^5.4.1"
|
"vite": "^5.4.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"pixi-viewport": "^6.0.3",
|
||||||
"pixi.js": "^8.4.0"
|
"pixi.js": "^8.4.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
48
public/Mission011
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
{ "compressionlevel":-1,
|
||||||
|
"height":17,
|
||||||
|
"infinite":false,
|
||||||
|
"layers":[
|
||||||
|
{
|
||||||
|
"data":[15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
|
||||||
|
15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
|
||||||
|
15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
|
||||||
|
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
|
||||||
|
18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 15, 15, 15, 15, 15, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
||||||
|
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 7, 18, 19, 15, 15, 15, 15, 15, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
|
||||||
|
15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 17, 18, 19, 15, 15, 15, 15, 15, 17, 18, 6, 32, 32, 32, 32, 32, 32, 32, 32,
|
||||||
|
15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 17, 18, 19, 15, 15, 15, 15, 15, 17, 18, 19, 15, 15, 15, 15, 15, 15, 15, 15,
|
||||||
|
15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 17, 18, 19, 15, 15, 15, 15, 15, 17, 18, 19, 15, 15, 15, 15, 15, 15, 15, 15,
|
||||||
|
15, 15, 3, 4, 4, 4, 4, 4, 4, 4, 4, 21, 18, 19, 15, 15, 15, 15, 15, 17, 18, 19, 15, 15, 15, 15, 15, 15, 15, 15,
|
||||||
|
15, 15, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 15, 15, 15, 15, 15, 17, 18, 19, 15, 15, 15, 15, 15, 15, 15, 15,
|
||||||
|
15, 15, 17, 18, 6, 32, 32, 32, 32, 32, 32, 32, 32, 33, 15, 15, 15, 15, 15, 17, 18, 19, 15, 15, 15, 15, 15, 15, 15, 15,
|
||||||
|
15, 15, 17, 18, 19, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 17, 18, 19, 15, 15, 15, 15, 15, 15, 15, 15,
|
||||||
|
15, 15, 17, 18, 20, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 21, 18, 19, 15, 15, 15, 15, 15, 15, 15, 15,
|
||||||
|
15, 15, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 15, 15, 15, 15, 15, 15, 15, 15,
|
||||||
|
15, 15, 31, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 33, 15, 15, 15, 15, 15, 15, 15, 15,
|
||||||
|
15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15],
|
||||||
|
"height":17,
|
||||||
|
"id":1,
|
||||||
|
"name":"Tile Layer 1",
|
||||||
|
"opacity":1,
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":30,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
}],
|
||||||
|
"nextlayerid":2,
|
||||||
|
"nextobjectid":1,
|
||||||
|
"orientation":"orthogonal",
|
||||||
|
"renderorder":"right-down",
|
||||||
|
"tiledversion":"1.11.0",
|
||||||
|
"tileheight":64,
|
||||||
|
"tilesets":[
|
||||||
|
{
|
||||||
|
"firstgid":1,
|
||||||
|
"source":"Tileset.tsx"
|
||||||
|
}],
|
||||||
|
"tilewidth":64,
|
||||||
|
"type":"map",
|
||||||
|
"version":"1.10",
|
||||||
|
"width":30
|
||||||
|
}
|
25
public/Mission011.tmx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<map version="1.10" tiledversion="1.11.0" orientation="orthogonal" renderorder="right-down" width="30" height="17" tilewidth="64" tileheight="64" infinite="0" nextlayerid="2" nextobjectid="1">
|
||||||
|
<tileset firstgid="1" source="Tileset.tsx"/>
|
||||||
|
<layer id="1" name="Tile Layer 1" width="30" height="17">
|
||||||
|
<data encoding="csv">
|
||||||
|
15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,
|
||||||
|
15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,
|
||||||
|
15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,
|
||||||
|
4,4,4,4,4,4,4,4,4,4,4,4,4,5,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,
|
||||||
|
18,18,18,18,18,18,18,18,18,18,18,18,18,19,15,15,15,15,15,3,4,4,4,4,4,4,4,4,4,4,
|
||||||
|
32,32,32,32,32,32,32,32,32,32,32,7,18,19,15,15,15,15,15,17,18,18,18,18,18,18,18,18,18,18,
|
||||||
|
15,15,15,15,15,15,15,15,15,15,15,17,18,19,15,15,15,15,15,17,18,6,32,32,32,32,32,32,32,32,
|
||||||
|
15,15,15,15,15,15,15,15,15,15,15,17,18,19,15,15,15,15,15,17,18,19,15,15,15,15,15,15,15,15,
|
||||||
|
15,15,15,15,15,15,15,15,15,15,15,17,18,19,15,15,15,15,15,17,18,19,15,15,15,15,15,15,15,15,
|
||||||
|
15,15,3,4,4,4,4,4,4,4,4,21,18,19,15,15,15,15,15,17,18,19,15,15,15,15,15,15,15,15,
|
||||||
|
15,15,17,18,18,18,18,18,18,18,18,18,18,19,15,15,15,15,15,17,18,19,15,15,15,15,15,15,15,15,
|
||||||
|
15,15,17,18,6,32,32,32,32,32,32,32,32,33,15,15,15,15,15,17,18,19,15,15,15,15,15,15,15,15,
|
||||||
|
15,15,17,18,19,15,15,15,15,15,15,15,15,15,15,15,15,15,15,17,18,19,15,15,15,15,15,15,15,15,
|
||||||
|
15,15,17,18,20,4,4,4,4,4,4,4,4,4,4,4,4,4,4,21,18,19,15,15,15,15,15,15,15,15,
|
||||||
|
15,15,17,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,19,15,15,15,15,15,15,15,15,
|
||||||
|
15,15,31,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,33,15,15,15,15,15,15,15,15,
|
||||||
|
15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15
|
||||||
|
</data>
|
||||||
|
</layer>
|
||||||
|
</map>
|
4
public/Tileset.tsx
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<tileset version="1.10" tiledversion="1.11.0" name="TiledTDThree64" tilewidth="64" tileheight="64" tilecount="140" columns="14">
|
||||||
|
<image source="../../../../dumping/tiles/TiledTDThree64.png" width="896" height="640"/>
|
||||||
|
</tileset>
|
@ -1,7 +1,7 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"health": 2,
|
"health": 2,
|
||||||
"speed": 0.0005,
|
"speed": 0.04,
|
||||||
"special": null,
|
"special": null,
|
||||||
"resistance": {
|
"resistance": {
|
||||||
"physical": 0,
|
"physical": 0,
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"name": "Basic Tower",
|
"name": "Basic Tower",
|
||||||
|
"behaviour": "BasicTowerBehaviour",
|
||||||
|
"sprite": "basic_tower",
|
||||||
|
"description": "The building block of society, nothing more basic exists.",
|
||||||
"stats": {
|
"stats": {
|
||||||
"damage": 2,
|
"damage": 2,
|
||||||
"cooldown": 2,
|
"cooldown": 120,
|
||||||
"gemSlotsAmount": 2,
|
"gemSlotsAmount": 2,
|
||||||
"cost": 100,
|
"cost": 100,
|
||||||
"range": 7
|
"range": 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
BIN
public/assets/gui/background_01.png
Executable file
After Width: | Height: | Size: 281 KiB |
BIN
public/assets/gui/background_02.png
Executable file
After Width: | Height: | Size: 296 KiB |
BIN
public/assets/gui/banner_01.png
Executable file
After Width: | Height: | Size: 244 KiB |
BIN
public/assets/gui/banner_02.png
Executable file
After Width: | Height: | Size: 61 KiB |
BIN
public/assets/gui/button_01.png
Executable file
After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 8.3 KiB |
BIN
public/assets/gui/frame_01.png
Executable file
After Width: | Height: | Size: 11 KiB |
BIN
public/assets/gui/frame_green.png
Executable file
After Width: | Height: | Size: 46 KiB |
BIN
public/assets/gui/frame_red.png
Executable file
After Width: | Height: | Size: 36 KiB |
BIN
public/assets/gui/frame_violet.png
Executable file
After Width: | Height: | Size: 40 KiB |
BIN
public/assets/gui/gems.png
Executable file
After Width: | Height: | Size: 112 KiB |
BIN
public/assets/gui/gui_01_button_01.png
Executable file
After Width: | Height: | Size: 49 KiB |
BIN
public/assets/gui/gui_01_checkbox_01_bg01.png
Executable file
After Width: | Height: | Size: 42 KiB |
BIN
public/assets/gui/money.png
Executable file
After Width: | Height: | Size: 222 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
BIN
public/assets/gui/shield_02.png
Executable file
After Width: | Height: | Size: 226 KiB |
BIN
public/assets/gui/w_anvil.png
Executable file
After Width: | Height: | Size: 210 KiB |
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 120 KiB |
@ -2,58 +2,94 @@
|
|||||||
"name": "Mission 1",
|
"name": "Mission 1",
|
||||||
"description": "This is the first mission",
|
"description": "This is the first mission",
|
||||||
"mapImage": {
|
"mapImage": {
|
||||||
"url": "/assets/maps/mission_01.png",
|
"url": "/assets/maps/mission_01.png"
|
||||||
"width": 1200,
|
|
||||||
"height": 900
|
|
||||||
},
|
},
|
||||||
"gameMap": {
|
"gameMap": {
|
||||||
"rows": 15,
|
"rows": 17,
|
||||||
"columns": 20,
|
"columns": 25,
|
||||||
"cells": [
|
"cells": [
|
||||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||||
|
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||||
|
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
||||||
],
|
],
|
||||||
"paths": [
|
"paths": [
|
||||||
[
|
[
|
||||||
[8, 0],
|
[4, 0],
|
||||||
[8, 1],
|
[4, 1],
|
||||||
[9, 2],
|
[4, 2],
|
||||||
[10, 2],
|
[4, 3],
|
||||||
[11, 2],
|
[4, 4],
|
||||||
[12, 3],
|
[4, 5],
|
||||||
[12, 4],
|
[4, 6],
|
||||||
[12, 5],
|
[4, 7],
|
||||||
[12, 6],
|
[4, 8],
|
||||||
[12, 7],
|
[4, 9],
|
||||||
[12, 8],
|
[4, 10],
|
||||||
[12, 9],
|
[4, 11],
|
||||||
[11, 10],
|
[4, 12],
|
||||||
[10, 10],
|
[5, 12],
|
||||||
[9, 10],
|
[6, 12],
|
||||||
[8, 11],
|
[7, 12],
|
||||||
[8, 12],
|
[8, 12],
|
||||||
[8, 13],
|
[9, 12],
|
||||||
[7, 14],
|
[10, 12],
|
||||||
[6, 14],
|
[10, 11],
|
||||||
[5, 14],
|
[10, 10],
|
||||||
[4, 14],
|
[10, 9],
|
||||||
[3, 14],
|
[10, 8],
|
||||||
[2, 14],
|
[10, 7],
|
||||||
[1, 14],
|
[10, 6],
|
||||||
[0, 15]
|
[10, 5],
|
||||||
|
[10, 4],
|
||||||
|
[10, 3],
|
||||||
|
[11, 3],
|
||||||
|
[12, 3],
|
||||||
|
[13, 3],
|
||||||
|
[14, 3],
|
||||||
|
[14, 4],
|
||||||
|
[14, 5],
|
||||||
|
[14, 6],
|
||||||
|
[14, 7],
|
||||||
|
[14, 8],
|
||||||
|
[14, 9],
|
||||||
|
[14, 10],
|
||||||
|
[14, 11],
|
||||||
|
[14, 12],
|
||||||
|
[14, 13],
|
||||||
|
[14, 14],
|
||||||
|
[14, 15],
|
||||||
|
[14, 16],
|
||||||
|
[14, 17],
|
||||||
|
[14, 18],
|
||||||
|
[14, 19],
|
||||||
|
[14, 20],
|
||||||
|
[13, 20],
|
||||||
|
[12, 20],
|
||||||
|
[11, 20],
|
||||||
|
[10, 20],
|
||||||
|
[9, 20],
|
||||||
|
[8, 20],
|
||||||
|
[7, 20],
|
||||||
|
[6, 20],
|
||||||
|
[5, 20],
|
||||||
|
[5, 21],
|
||||||
|
[5, 22],
|
||||||
|
[5, 23],
|
||||||
|
[5, 24]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
BIN
public/assets/projectiles/basic_tower.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
public/assets/towers/basic_tower.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
14
public/maps.tiled-project
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"automappingRulesFile": "",
|
||||||
|
"commands": [
|
||||||
|
],
|
||||||
|
"compatibilityVersion": 1100,
|
||||||
|
"extensionsPath": "extensions",
|
||||||
|
"folders": [
|
||||||
|
"."
|
||||||
|
],
|
||||||
|
"properties": [
|
||||||
|
],
|
||||||
|
"propertyTypes": [
|
||||||
|
]
|
||||||
|
}
|
66
public/maps.tiled-session
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
{
|
||||||
|
"Map/SizeTest": {
|
||||||
|
"height": 4300,
|
||||||
|
"width": 2
|
||||||
|
},
|
||||||
|
"activeFile": "Mission011.tmx",
|
||||||
|
"expandedProjectPaths": [
|
||||||
|
"."
|
||||||
|
],
|
||||||
|
"file.lastUsedOpenFilter": "All Files (*)",
|
||||||
|
"fileStates": {
|
||||||
|
"": {
|
||||||
|
"scaleInDock": 0.33
|
||||||
|
},
|
||||||
|
"#TiledTDThree128": {
|
||||||
|
"scaleInDock": 0.33,
|
||||||
|
"scaleInEditor": 1
|
||||||
|
},
|
||||||
|
"/home/koneko/dumping/tiles/TiledTDThree64.tmx": {
|
||||||
|
"scale": 0.187625,
|
||||||
|
"selectedLayer": 0,
|
||||||
|
"viewCenter": {
|
||||||
|
"x": 3197.8680879413732,
|
||||||
|
"y": 3197.8680879413732
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Mission01.tmx": {
|
||||||
|
"scale": 0.6739062499999999,
|
||||||
|
"selectedLayer": 0,
|
||||||
|
"viewCenter": {
|
||||||
|
"x": 795.3628564804081,
|
||||||
|
"y": 639.5548342221192
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Mission011.tmx": {
|
||||||
|
"scale": 0.5,
|
||||||
|
"selectedLayer": 0,
|
||||||
|
"viewCenter": {
|
||||||
|
"x": 1070,
|
||||||
|
"y": 448
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Tileset.tsx": {
|
||||||
|
"scaleInDock": 0.5,
|
||||||
|
"scaleInEditor": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last.externalTilesetPath": "/home/koneko/Programing/js/towerdefense/public",
|
||||||
|
"map.height": 17,
|
||||||
|
"map.lastUsedFormat": "tmx",
|
||||||
|
"map.tileHeight": 64,
|
||||||
|
"map.tileWidth": 64,
|
||||||
|
"map.width": 30,
|
||||||
|
"openFiles": [
|
||||||
|
"Tileset.tsx",
|
||||||
|
"Mission011.tmx"
|
||||||
|
],
|
||||||
|
"project": "maps.tiled-project",
|
||||||
|
"recentFiles": [
|
||||||
|
"Tileset.tsx",
|
||||||
|
"Mission011.tmx",
|
||||||
|
"Mission01.tmx",
|
||||||
|
"/home/koneko/dumping/tiles/TiledTDThree64.tmx"
|
||||||
|
],
|
||||||
|
"tileset.lastUsedFilter": "Tiled tileset files (*.tsx *.xml)"
|
||||||
|
}
|
@ -1,10 +1,12 @@
|
|||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
body,
|
body,
|
||||||
html {
|
html {
|
||||||
width: 100vw;
|
margin: 0;
|
||||||
height: 100vh;
|
padding: 0;
|
||||||
overflow: hidden;
|
overflow: hidden; /* Prevent scrollbars */
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: black; /* Letterbox background color */
|
||||||
|
}
|
||||||
|
canvas {
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
|
133
src/classes/Assets.ts
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
import * as PIXI from 'pixi.js';
|
||||||
|
import { CreepStatsDefinition, MissionDefinition, TowerDefinition } from './Definitions';
|
||||||
|
import { Globals } from './Bastion';
|
||||||
|
|
||||||
|
export default class GameAssets {
|
||||||
|
public static async LoadAssets() {
|
||||||
|
console.log('Loading Texture Assets');
|
||||||
|
const text = new PIXI.Text({
|
||||||
|
text: 'Loading textures. This might take a while.',
|
||||||
|
style: new PIXI.TextStyle({
|
||||||
|
fill: 0x333333,
|
||||||
|
fontSize: 50,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
text.x = Globals.app.canvas.width / 2;
|
||||||
|
text.y = Globals.app.canvas.height / 2;
|
||||||
|
text.anchor.set(0.5, 0.5);
|
||||||
|
Globals.app.stage.addChild(text);
|
||||||
|
GameAssets.Button01Texture = await PIXI.Assets.load({
|
||||||
|
src: '/assets/gui/button_01.png',
|
||||||
|
});
|
||||||
|
GameAssets.Button02Texture = await PIXI.Assets.load({
|
||||||
|
src: '/assets/gui/button_02.png',
|
||||||
|
});
|
||||||
|
GameAssets.Frame01Texture = await PIXI.Assets.load({
|
||||||
|
src: '/assets/gui/frame_01.png',
|
||||||
|
});
|
||||||
|
GameAssets.Frame02Texture = await PIXI.Assets.load({
|
||||||
|
src: '/assets/gui/frame_02.png',
|
||||||
|
});
|
||||||
|
GameAssets.FrameBackground = await PIXI.Assets.load({
|
||||||
|
src: '/assets/gui/background_01.png',
|
||||||
|
});
|
||||||
|
GameAssets.FrameTowerTab = await PIXI.Assets.load({
|
||||||
|
src: '/assets/gui/background_02.png',
|
||||||
|
});
|
||||||
|
GameAssets.VioletBackground = await PIXI.Assets.load({
|
||||||
|
src: '/assets/gui/frame_violet.png',
|
||||||
|
});
|
||||||
|
GameAssets.RedBackground = await PIXI.Assets.load({
|
||||||
|
src: '/assets/gui/frame_red.png',
|
||||||
|
});
|
||||||
|
GameAssets.GreenBackground = await PIXI.Assets.load({
|
||||||
|
src: '/assets/gui/frame_green.png',
|
||||||
|
});
|
||||||
|
GameAssets.HealthTexture = await PIXI.Assets.load({
|
||||||
|
src: '/assets/gui/heart.png',
|
||||||
|
});
|
||||||
|
GameAssets.GoldTexture = await PIXI.Assets.load({
|
||||||
|
src: '/assets/gui/money.png',
|
||||||
|
});
|
||||||
|
|
||||||
|
GameAssets.BasicCreepTexture = await PIXI.Assets.load({
|
||||||
|
src: '/assets/creeps/basic.jpg',
|
||||||
|
});
|
||||||
|
|
||||||
|
GameAssets.BasicTowerTexture = await PIXI.Assets.load({
|
||||||
|
src: '/assets/towers/basic_tower.png',
|
||||||
|
});
|
||||||
|
|
||||||
|
GameAssets.BasicProjectileTexture = await PIXI.Assets.load({
|
||||||
|
src: '/assets/projectiles/basic_tower.png',
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.LoadMissions();
|
||||||
|
await this.LoadTowers();
|
||||||
|
await this.LoadCreepStats();
|
||||||
|
text.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async LoadCreepStats() {
|
||||||
|
const res = await fetch('/assets/CreepStats.json');
|
||||||
|
const stats = await res.json();
|
||||||
|
this.CreepStats = stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async LoadMissions() {
|
||||||
|
GameAssets.Missions = [await this.LoadMission('/assets/missions/mission_01.json')];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async LoadTowers() {
|
||||||
|
const res = await fetch('/assets/Towers.json');
|
||||||
|
const towers = await res.json();
|
||||||
|
GameAssets.Towers = towers;
|
||||||
|
towers.forEach(async (tower) => {
|
||||||
|
let index = this.TowerSprites.length - 1;
|
||||||
|
if (index == -1) index = 0;
|
||||||
|
this.TowerSprites[index] = await PIXI.Assets.load({
|
||||||
|
src: `/assets/towers/${tower.sprite}.png`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async LoadMission(missionUrl: string) {
|
||||||
|
const res = await fetch(missionUrl);
|
||||||
|
const mission = await res.json();
|
||||||
|
await this.LoadBackground(mission.mapImage.url);
|
||||||
|
return mission;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async LoadBackground(backgroundUrl: string) {
|
||||||
|
let index = this.MissionBackgrounds.length - 1;
|
||||||
|
if (index == -1) index = 0;
|
||||||
|
this.MissionBackgrounds[index] = await PIXI.Assets.load({
|
||||||
|
src: backgroundUrl,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BasicCreepTexture: PIXI.Texture;
|
||||||
|
|
||||||
|
public static BasicTowerTexture: PIXI.Texture;
|
||||||
|
|
||||||
|
public static BasicProjectileTexture: PIXI.Texture;
|
||||||
|
|
||||||
|
public static Frame01Texture: PIXI.Texture;
|
||||||
|
public static Frame02Texture: PIXI.Texture;
|
||||||
|
public static FrameBackground: PIXI.Texture;
|
||||||
|
public static FrameTowerTab: PIXI.Texture;
|
||||||
|
public static VioletBackground: PIXI.Texture;
|
||||||
|
public static RedBackground: PIXI.Texture;
|
||||||
|
public static GreenBackground: PIXI.Texture;
|
||||||
|
public static Button01Texture: PIXI.Texture;
|
||||||
|
public static Button02Texture: PIXI.Texture;
|
||||||
|
public static HealthTexture: PIXI.Texture;
|
||||||
|
public static GoldTexture: PIXI.Texture;
|
||||||
|
|
||||||
|
public static MissionBackgrounds: PIXI.Texture[] = [];
|
||||||
|
public static TowerSprites: PIXI.Texture[] = [];
|
||||||
|
public static Missions: MissionDefinition[];
|
||||||
|
public static Towers: TowerDefinition[];
|
||||||
|
public static CreepStats: CreepStatsDefinition[];
|
||||||
|
public static DebuggingEnabled: boolean = false;
|
||||||
|
}
|
51
src/classes/Bastion.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import * as PIXI from 'pixi.js';
|
||||||
|
import GameObject from './GameObject';
|
||||||
|
import GuiObject from './GuiObject';
|
||||||
|
import Scene from '../scenes/Scene';
|
||||||
|
import { Grid } from './game/Grid';
|
||||||
|
import WaveManager from './game/WaveManager';
|
||||||
|
import TowerManager from './game/TowerManager';
|
||||||
|
import { GameScene } from '../scenes/Game';
|
||||||
|
|
||||||
|
export class Globals {
|
||||||
|
public static app: PIXI.Application;
|
||||||
|
public static GameMaster: GameMaster;
|
||||||
|
public static WindowHeight: number;
|
||||||
|
public static WindowWidth: number;
|
||||||
|
public static AspectRatio: number = 16 / 9;
|
||||||
|
public static Grid: Grid;
|
||||||
|
public static WaveManager: WaveManager;
|
||||||
|
public static TowerManager: TowerManager;
|
||||||
|
public static GameScene: GameScene;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class GameMaster {
|
||||||
|
public currentScene: Scene;
|
||||||
|
private gameScene: GameScene;
|
||||||
|
private GameObjects: GameObject[] = [];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
Globals.GameMaster = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public _CreateGuiObject(object: GuiObject) {
|
||||||
|
this.currentScene.gui.push(object);
|
||||||
|
Globals.app.stage.addChild(object.container);
|
||||||
|
}
|
||||||
|
|
||||||
|
public _RemoveGuiObject(object: GuiObject) {
|
||||||
|
this.currentScene.gui.splice(this.currentScene.gui.indexOf(object), 1);
|
||||||
|
Globals.app.stage.removeChild(object.container);
|
||||||
|
}
|
||||||
|
|
||||||
|
public changeScene(newScene: Scene) {
|
||||||
|
if (this.currentScene) {
|
||||||
|
this.currentScene.destroy();
|
||||||
|
}
|
||||||
|
this.GameObjects.forEach((element) => {
|
||||||
|
element.destroy();
|
||||||
|
});
|
||||||
|
this.currentScene = newScene;
|
||||||
|
this.currentScene.init();
|
||||||
|
}
|
||||||
|
}
|
85
src/classes/Definitions.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
export type MissionDefinition = {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
mapImage: MapImageDefinition;
|
||||||
|
gameMap: GameMapDefinition;
|
||||||
|
rounds: MissionRoundDefinition[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MapImageDefinition = {
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GameMapDefinition = {
|
||||||
|
rows: number;
|
||||||
|
columns: number;
|
||||||
|
cells: TerrainType[][];
|
||||||
|
paths: PathDefinition[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MissionRoundDefinition = {
|
||||||
|
waves: WaveDefinition[];
|
||||||
|
offeredGems: GemType[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WaveDefinition = {
|
||||||
|
firstCreepSpawnTick: number;
|
||||||
|
spawnIntervalTicks: number;
|
||||||
|
creeps: CreepType[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CreepStatsDefinition = {
|
||||||
|
health: number;
|
||||||
|
speed: number;
|
||||||
|
special: Function;
|
||||||
|
resistance: CreepResistancesDefinition;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CreepResistancesDefinition = {
|
||||||
|
physical: number;
|
||||||
|
divine: number;
|
||||||
|
fire: number;
|
||||||
|
ice: number;
|
||||||
|
frostfire: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TowerDefinition = {
|
||||||
|
name: string;
|
||||||
|
behaviour: string;
|
||||||
|
sprite: string;
|
||||||
|
description: string;
|
||||||
|
stats: TowerStatsDefinition;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TowerStatsDefinition = {
|
||||||
|
damage: number;
|
||||||
|
cooldown: number;
|
||||||
|
gemSlotsAmount: number;
|
||||||
|
cost: number;
|
||||||
|
range: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PathDefinition = [[row: number, column: number]];
|
||||||
|
|
||||||
|
export enum CreepType {
|
||||||
|
Basic = 0,
|
||||||
|
Fast = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum TerrainType {
|
||||||
|
Restricted = 0,
|
||||||
|
Buildable = 1,
|
||||||
|
Path = 9,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum GemType {
|
||||||
|
Fire = 0,
|
||||||
|
Yeti = 1,
|
||||||
|
Titalium = 2,
|
||||||
|
Soulforge = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum TowerType {
|
||||||
|
Shooting = 0,
|
||||||
|
Circle = 1,
|
||||||
|
}
|
27
src/classes/GameObject.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import * as PIXI from 'pixi.js';
|
||||||
|
|
||||||
|
export default abstract class GameObject {
|
||||||
|
public readonly name: string = this.constructor.name;
|
||||||
|
|
||||||
|
protected _container: PIXI.Container = new PIXI.Container();
|
||||||
|
|
||||||
|
protected bb: PIXI.Rectangle = new PIXI.Rectangle();
|
||||||
|
|
||||||
|
protected _events: PIXI.EventEmitter = new PIXI.EventEmitter();
|
||||||
|
|
||||||
|
public destroy() {
|
||||||
|
this._events.removeAllListeners();
|
||||||
|
if (this._container.parent) this._container.parent.removeChild(this._container);
|
||||||
|
this._container.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get container(): PIXI.Container {
|
||||||
|
return this._container;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get events(): PIXI.EventEmitter {
|
||||||
|
return this._events;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract update(elapsedMS): void;
|
||||||
|
}
|
50
src/classes/GuiObject.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import * as PIXI from 'pixi.js';
|
||||||
|
import { Globals } from './Bastion';
|
||||||
|
|
||||||
|
export default abstract class GuiObject {
|
||||||
|
public readonly name: string = this.constructor.name;
|
||||||
|
|
||||||
|
protected _container: PIXI.Container = new PIXI.Container();
|
||||||
|
|
||||||
|
protected _events: PIXI.EventEmitter = new PIXI.EventEmitter();
|
||||||
|
|
||||||
|
protected enabled: boolean = true;
|
||||||
|
|
||||||
|
public destroy() {
|
||||||
|
this._events.removeAllListeners();
|
||||||
|
if (this._container.parent) this._container.parent.removeChild(this._container);
|
||||||
|
this._container.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get container(): PIXI.Container {
|
||||||
|
return this._container;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get events(): PIXI.EventEmitter {
|
||||||
|
return this._events;
|
||||||
|
}
|
||||||
|
|
||||||
|
public onClick(e: PIXI.FederatedPointerEvent) {
|
||||||
|
console.warn(`[${this.name} does not implement GuiObject.onClick()]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public onWheel(e: PIXI.FederatedWheelEvent) {
|
||||||
|
console.warn(`[${this.name} does not implement GuiObject.onWheel()]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public setEnabled(enabled: boolean) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(interactive?: boolean) {
|
||||||
|
Globals.GameMaster._CreateGuiObject(this);
|
||||||
|
if (!interactive) return;
|
||||||
|
this._container.interactive = true;
|
||||||
|
this._container.onwheel = (e) => {
|
||||||
|
if (this.enabled) this.onWheel(e);
|
||||||
|
};
|
||||||
|
this._container.onclick = (e) => {
|
||||||
|
if (this.enabled) this.onClick(e);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
113
src/classes/game/Creep.ts
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import GameAssets from '../Assets';
|
||||||
|
import Assets from '../Assets';
|
||||||
|
import { Globals } from '../Bastion';
|
||||||
|
import { CreepStatsDefinition, CreepType, PathDefinition } from '../Definitions';
|
||||||
|
import GameObject from '../GameObject';
|
||||||
|
import * as PIXI from 'pixi.js';
|
||||||
|
|
||||||
|
export enum CreepEvents {
|
||||||
|
Died = 'died',
|
||||||
|
TakenDamage = 'takenDamage',
|
||||||
|
Escaped = 'escaped',
|
||||||
|
Moved = 'moved',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Creep extends GameObject {
|
||||||
|
public creepType: CreepType;
|
||||||
|
private sprite: PIXI.Sprite;
|
||||||
|
private path: PathDefinition;
|
||||||
|
private stats: CreepStatsDefinition;
|
||||||
|
private pathIndex: number = 0;
|
||||||
|
private speed: number;
|
||||||
|
public health: number;
|
||||||
|
public maxHealth: number;
|
||||||
|
public escaped: boolean = false;
|
||||||
|
public died: boolean = false;
|
||||||
|
public x: number;
|
||||||
|
public y: number;
|
||||||
|
constructor(creepType: CreepType, path: PathDefinition) {
|
||||||
|
super();
|
||||||
|
this.creepType = creepType;
|
||||||
|
this.stats = structuredClone(Assets.CreepStats[this.creepType]);
|
||||||
|
this.sprite = new PIXI.Sprite({
|
||||||
|
texture: GameAssets.BasicCreepTexture,
|
||||||
|
});
|
||||||
|
this.container.label = 'creep-' + creepType.toString();
|
||||||
|
// because wave manager spawns all instantly and i dont want
|
||||||
|
// it to look like a shit game (they all spawn in top left corner)
|
||||||
|
// i want to hide minion - mario
|
||||||
|
this.container.x = -70;
|
||||||
|
this.container.y = -50;
|
||||||
|
this.sprite.width = 64;
|
||||||
|
this.sprite.height = 64;
|
||||||
|
this.speed = this.stats.speed;
|
||||||
|
this.health = this.stats.health;
|
||||||
|
this.maxHealth = this.stats.health;
|
||||||
|
this.path = path;
|
||||||
|
this.x = path[0][1] * 64 + 32; // centered
|
||||||
|
this.y = path[0][0] * 64 + 32;
|
||||||
|
Globals.Grid.container.addChild(this.container);
|
||||||
|
this.container.addChild(this.sprite);
|
||||||
|
}
|
||||||
|
public update(elapsedMS: number) {
|
||||||
|
if (this.pathIndex + 1 == this.path.length) {
|
||||||
|
if (this.escaped) return;
|
||||||
|
this.events.emit(CreepEvents.Escaped, this);
|
||||||
|
this.escaped = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const currentCell = this.path[this.pathIndex];
|
||||||
|
const targetCell = this.path[this.pathIndex + 1];
|
||||||
|
|
||||||
|
const targetX = targetCell[1] * 64 + 32;
|
||||||
|
const targetY = targetCell[0] * 64 + 32;
|
||||||
|
const directionX = targetCell[1] - currentCell[1];
|
||||||
|
const directionY = targetCell[0] - currentCell[0];
|
||||||
|
let deltaX = this.speed * elapsedMS * directionX;
|
||||||
|
let deltaY = this.speed * elapsedMS * directionY;
|
||||||
|
let increaseIndex = false;
|
||||||
|
|
||||||
|
if (deltaX > 0 && this.x + deltaX > targetX) {
|
||||||
|
// limit to center of target cell
|
||||||
|
deltaX = targetX - this.x;
|
||||||
|
increaseIndex = true;
|
||||||
|
}
|
||||||
|
if (deltaX < 0 && this.x + deltaX < targetX) {
|
||||||
|
// limit to center of target cell
|
||||||
|
deltaX = targetX - this.x;
|
||||||
|
increaseIndex = true;
|
||||||
|
}
|
||||||
|
if (deltaY > 0 && this.y + deltaY > targetY) {
|
||||||
|
// limit to center of target cell
|
||||||
|
deltaY = targetY - this.y;
|
||||||
|
increaseIndex = true;
|
||||||
|
}
|
||||||
|
if (deltaY < 0 && this.y + deltaY < targetY) {
|
||||||
|
// limit to center of target cell
|
||||||
|
deltaY = targetY - this.y;
|
||||||
|
increaseIndex = true;
|
||||||
|
}
|
||||||
|
this.x += deltaX;
|
||||||
|
this.y += deltaY;
|
||||||
|
if (increaseIndex) this.pathIndex++;
|
||||||
|
this.draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
public takeDamage(amount: number) {
|
||||||
|
this.health -= amount;
|
||||||
|
if (this.health < 0 && !this.died) {
|
||||||
|
this.died = true;
|
||||||
|
this.events.emit(CreepEvents.Died, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override destroy() {
|
||||||
|
super.destroy();
|
||||||
|
this.container.removeChildren();
|
||||||
|
}
|
||||||
|
protected draw() {
|
||||||
|
this.sprite.anchor.set(0.5, 0.5);
|
||||||
|
this.container.x = this.x;
|
||||||
|
this.container.y = this.y;
|
||||||
|
}
|
||||||
|
}
|
155
src/classes/game/Grid.ts
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
import * as PIXI from 'pixi.js';
|
||||||
|
import GameObject from '../GameObject';
|
||||||
|
import { GameMapDefinition, TerrainType } from '../Definitions';
|
||||||
|
import GameAssets from '../Assets';
|
||||||
|
import { Globals } from '../Bastion';
|
||||||
|
import Creep, { CreepEvents } from './Creep';
|
||||||
|
|
||||||
|
export class Cell extends GameObject {
|
||||||
|
public type: TerrainType;
|
||||||
|
public row: number;
|
||||||
|
public column: number;
|
||||||
|
public isPath: boolean = false;
|
||||||
|
private g: PIXI.Graphics;
|
||||||
|
public clickDetector: PIXI.Graphics;
|
||||||
|
|
||||||
|
constructor(type: TerrainType, row: number, column: number, isPath: boolean) {
|
||||||
|
super();
|
||||||
|
this.type = type;
|
||||||
|
this.row = row;
|
||||||
|
this.column = column;
|
||||||
|
this.isPath = isPath;
|
||||||
|
this.bb.x = this.column * 64;
|
||||||
|
this.bb.y = this.row * 64;
|
||||||
|
this.bb.width = 64;
|
||||||
|
this.bb.height = 64;
|
||||||
|
Globals.Grid.container.addChild(this.container);
|
||||||
|
this.container.x = this.bb.x;
|
||||||
|
this.container.y = this.bb.y;
|
||||||
|
|
||||||
|
this.clickDetector = new PIXI.Graphics({
|
||||||
|
zIndex: 99,
|
||||||
|
interactive: true,
|
||||||
|
});
|
||||||
|
this.clickDetector.rect(0, 0, this.bb.width, this.bb.height);
|
||||||
|
this.clickDetector.fill({ color: 0xff0000, alpha: 0 });
|
||||||
|
this.container.addChild(this.clickDetector);
|
||||||
|
this.clickDetector.onclick = (e) => {
|
||||||
|
Globals.Grid._gridCellClicked(row, column);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!GameAssets.DebuggingEnabled) return;
|
||||||
|
const text = new PIXI.Text({
|
||||||
|
text: `${this.row}|${this.column}`,
|
||||||
|
style: new PIXI.TextStyle({
|
||||||
|
fill: 0xffffff,
|
||||||
|
dropShadow: true,
|
||||||
|
fontSize: 16,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
this.container.addChild(text);
|
||||||
|
text.anchor.set(0.5, 0.5);
|
||||||
|
text.x = this.bb.width / 2;
|
||||||
|
text.y = this.bb.height / 2;
|
||||||
|
if (isPath) text.text += 'p';
|
||||||
|
}
|
||||||
|
public gDraw() {
|
||||||
|
this.g = new PIXI.Graphics({
|
||||||
|
zIndex: 5,
|
||||||
|
});
|
||||||
|
this.g.rect(0, 0, this.bb.width, this.bb.height);
|
||||||
|
switch (this.type) {
|
||||||
|
case TerrainType.Restricted:
|
||||||
|
this.g.fill({ color: 0x222222, alpha: 0.5 });
|
||||||
|
break;
|
||||||
|
case TerrainType.Path:
|
||||||
|
this.g.fill({ color: 0x222222, alpha: 0.5 });
|
||||||
|
break;
|
||||||
|
case TerrainType.Buildable:
|
||||||
|
this.g.stroke({ color: 0x00ff00, alpha: 0.9 });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.container.addChild(this.g);
|
||||||
|
}
|
||||||
|
public gClear() {
|
||||||
|
if (this.g != null) {
|
||||||
|
this.container.removeChild(this.g);
|
||||||
|
this.g.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public update() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Grid extends GameObject {
|
||||||
|
private gameMap: GameMapDefinition;
|
||||||
|
private cells: Cell[] = [];
|
||||||
|
public creeps: Creep[] = [];
|
||||||
|
public gridShown: boolean = false;
|
||||||
|
|
||||||
|
constructor(map: GameMapDefinition, missionIndex) {
|
||||||
|
super();
|
||||||
|
this.gameMap = map;
|
||||||
|
Globals.Grid = this;
|
||||||
|
this.bb.x = 0;
|
||||||
|
this.bb.y = 0;
|
||||||
|
this.bb.width = 64 * 30;
|
||||||
|
this.bb.height = 64 * 17;
|
||||||
|
Globals.app.stage.addChild(this.container);
|
||||||
|
|
||||||
|
let background = new PIXI.Sprite(GameAssets.MissionBackgrounds[missionIndex]);
|
||||||
|
background.x = 0;
|
||||||
|
background.y = 0;
|
||||||
|
background.width = this.bb.width;
|
||||||
|
background.height = this.bb.height;
|
||||||
|
|
||||||
|
this.container.addChild(background);
|
||||||
|
|
||||||
|
for (let y = 0; y < this.gameMap.columns; y++) {
|
||||||
|
for (let x = 0; x < this.gameMap.rows; x++) {
|
||||||
|
let type = this.gameMap.cells[x][y];
|
||||||
|
if (!type) type = 1;
|
||||||
|
const isPath = this.gameMap.paths.some((path) => path.some((p) => p[0] === x && p[1] === y));
|
||||||
|
if (isPath) type = TerrainType.Path;
|
||||||
|
let cell = new Cell(type, x, y, isPath);
|
||||||
|
this.cells.push(cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public toggleGrid() {
|
||||||
|
this.cells.forEach((cell) => {
|
||||||
|
if (this.gridShown) {
|
||||||
|
cell.gClear();
|
||||||
|
} else {
|
||||||
|
cell.gDraw();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.gridShown = !this.gridShown;
|
||||||
|
}
|
||||||
|
public addCreep(creep: Creep) {
|
||||||
|
console.log('ADD CREEP');
|
||||||
|
this.creeps.push(creep);
|
||||||
|
creep.events.on(CreepEvents.Died, (diedCreep) => {
|
||||||
|
this.onCreepDiedOrEscaped(diedCreep);
|
||||||
|
});
|
||||||
|
creep.events.on(CreepEvents.Escaped, (escapedCreep) => {
|
||||||
|
this.onCreepDiedOrEscaped(escapedCreep);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
private onCreepDiedOrEscaped(creep: Creep) {
|
||||||
|
this.creeps.splice(this.creeps.indexOf(creep), 1);
|
||||||
|
creep.destroy();
|
||||||
|
}
|
||||||
|
public update(elapsedMS) {
|
||||||
|
this.creeps.forEach((creep) => {
|
||||||
|
creep.update(elapsedMS);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public getCellByRowAndCol(row, column) {
|
||||||
|
return this.cells.filter((item) => item.row == row && item.column == column)[0];
|
||||||
|
}
|
||||||
|
public _gridCellClicked(row, column) {
|
||||||
|
// function will be assigned by GameScene, but must be predefined here.
|
||||||
|
this.onGridCellClicked(row, column);
|
||||||
|
}
|
||||||
|
public onGridCellClicked(row, column) {}
|
||||||
|
}
|
94
src/classes/game/MissionStats.ts
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import Assets from '../Assets';
|
||||||
|
import { Globals } from '../Bastion';
|
||||||
|
import GameObject from '../GameObject';
|
||||||
|
import * as PIXI from 'pixi.js';
|
||||||
|
|
||||||
|
export default class MissionStats extends GameObject {
|
||||||
|
private hp: number = 100;
|
||||||
|
private gold: number = 0;
|
||||||
|
private goldText: PIXI.Text;
|
||||||
|
private healthText: PIXI.Text;
|
||||||
|
|
||||||
|
public getHP() {
|
||||||
|
return this.hp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasEnoughGold(amount) {
|
||||||
|
return amount <= this.gold;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setHP(hp: number) {
|
||||||
|
this.hp = hp;
|
||||||
|
this.healthText.text = this.hp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public takeDamage(damage: number) {
|
||||||
|
this.hp -= damage;
|
||||||
|
this.healthText.text = this.hp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setGold(gold: number) {
|
||||||
|
this.gold = gold;
|
||||||
|
this.goldText.text = this.gold;
|
||||||
|
}
|
||||||
|
|
||||||
|
public earnGold(gold: number) {
|
||||||
|
this.gold += gold;
|
||||||
|
this.goldText.text = this.gold;
|
||||||
|
}
|
||||||
|
|
||||||
|
public spendGold(amount: number) {
|
||||||
|
this.gold -= amount;
|
||||||
|
this.goldText.text = this.gold;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(initialHP: number, initialGold: number) {
|
||||||
|
super();
|
||||||
|
this.hp = initialHP;
|
||||||
|
this.gold = initialGold;
|
||||||
|
this.container.x = 0;
|
||||||
|
this.container.y = 20;
|
||||||
|
Globals.app.stage.addChild(this.container);
|
||||||
|
this.healthText = new PIXI.Text({
|
||||||
|
text: `${this.hp}`,
|
||||||
|
style: new PIXI.TextStyle({
|
||||||
|
fill: 'red',
|
||||||
|
fontSize: 36,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
dropShadow: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
this.goldText = new PIXI.Text({
|
||||||
|
text: `${this.gold}`,
|
||||||
|
style: new PIXI.TextStyle({
|
||||||
|
fill: 'gold',
|
||||||
|
fontSize: 36,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
dropShadow: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const healthSprite = new PIXI.Sprite(Assets.HealthTexture);
|
||||||
|
const goldSprite = new PIXI.Sprite(Assets.GoldTexture);
|
||||||
|
|
||||||
|
this.healthText.x = 200;
|
||||||
|
this.healthText.y = -15;
|
||||||
|
healthSprite.x = 160;
|
||||||
|
healthSprite.width = 36;
|
||||||
|
healthSprite.height = 32;
|
||||||
|
healthSprite.y = -10;
|
||||||
|
|
||||||
|
this.goldText.x = 200;
|
||||||
|
this.goldText.y = 20;
|
||||||
|
goldSprite.x = 150;
|
||||||
|
goldSprite.width = 56;
|
||||||
|
goldSprite.height = 56;
|
||||||
|
goldSprite.y = 15;
|
||||||
|
|
||||||
|
this.container.addChild(this.healthText);
|
||||||
|
this.container.addChild(this.goldText);
|
||||||
|
this.container.addChild(healthSprite);
|
||||||
|
this.container.addChild(goldSprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
public update() {}
|
||||||
|
}
|
57
src/classes/game/Projectile.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import * as PIXI from 'pixi.js';
|
||||||
|
import GameObject from '../GameObject';
|
||||||
|
import { Globals } from '../Bastion';
|
||||||
|
|
||||||
|
export function calculateAngleToPoint(x, y, targetX, targetY) {
|
||||||
|
const dx = targetX - x;
|
||||||
|
const dy = targetY - y;
|
||||||
|
return Math.atan2(dy, dx);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Projectile extends GameObject {
|
||||||
|
public sprite: PIXI.Sprite;
|
||||||
|
public x: number;
|
||||||
|
public y: number;
|
||||||
|
public angle: number;
|
||||||
|
public speed: number;
|
||||||
|
constructor(x, y, spriteTexture, angle) {
|
||||||
|
super();
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
|
||||||
|
this.sprite = new PIXI.Sprite({ texture: spriteTexture, scale: 0.5, rotation: angle });
|
||||||
|
this.sprite.anchor.set(0.5);
|
||||||
|
this.container.x = this.x;
|
||||||
|
this.container.y = this.y;
|
||||||
|
this.container.addChild(this.sprite);
|
||||||
|
Globals.app.stage.addChild(this.container);
|
||||||
|
|
||||||
|
this.angle = angle;
|
||||||
|
|
||||||
|
this.speed = 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
public update(elapsedMS) {
|
||||||
|
if (this.x > 2000 || this.x < 0 || this.y > 2000 || this.y < 0) return this.destroy();
|
||||||
|
this.x += Math.cos(this.angle) * this.speed * elapsedMS;
|
||||||
|
this.y += Math.sin(this.angle) * this.speed * elapsedMS;
|
||||||
|
|
||||||
|
this.container.x = this.x;
|
||||||
|
this.container.y = this.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public onCollide(otherSprite) {
|
||||||
|
console.log(`Collision detected with`, otherSprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
public checkCollision(otherSprite) {
|
||||||
|
const boundsA = this.sprite.getBounds();
|
||||||
|
const boundsB = otherSprite.getBounds();
|
||||||
|
return (
|
||||||
|
boundsA.x < boundsB.x + boundsB.width &&
|
||||||
|
boundsA.x + boundsA.width > boundsB.x &&
|
||||||
|
boundsA.y < boundsB.y + boundsB.height &&
|
||||||
|
boundsA.y + boundsA.height > boundsB.y
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
96
src/classes/game/Tower.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import { Globals } from '../Bastion';
|
||||||
|
import * as PIXI from 'pixi.js';
|
||||||
|
import GameObject from '../GameObject';
|
||||||
|
import { TowerDefinition } from '../Definitions';
|
||||||
|
import { Cell } from './Grid';
|
||||||
|
import { TowerBehaviours } from './TowerManager';
|
||||||
|
import Projectile, { calculateAngleToPoint } from './Projectile';
|
||||||
|
import GameAssets from '../Assets';
|
||||||
|
import Creep from './Creep';
|
||||||
|
|
||||||
|
function distance(x1, y1, x2, y2) {
|
||||||
|
return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TowerInstance = {
|
||||||
|
row: number;
|
||||||
|
column: number;
|
||||||
|
sprite: PIXI.Sprite;
|
||||||
|
projectiles: Array<any>;
|
||||||
|
baseDamage: number;
|
||||||
|
damage: number;
|
||||||
|
cooldown: number;
|
||||||
|
ticksToFireAt: number;
|
||||||
|
slottedGems: Array<any>;
|
||||||
|
cost: number;
|
||||||
|
baseRange: number;
|
||||||
|
range: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum TowerEvents {
|
||||||
|
TowerPlacedEvent = 'towerPlacedEvent',
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Tower extends GameObject {
|
||||||
|
public row: number;
|
||||||
|
public column: number;
|
||||||
|
private projectiles: Projectile[] = [];
|
||||||
|
private behaviour: string;
|
||||||
|
private definition: TowerDefinition;
|
||||||
|
private sprite: PIXI.Sprite;
|
||||||
|
private graphics: PIXI.Graphics = new PIXI.Graphics();
|
||||||
|
constructor(row, column, texture, definition, behaviour) {
|
||||||
|
super();
|
||||||
|
this.row = row;
|
||||||
|
this.column = column;
|
||||||
|
this.behaviour = behaviour;
|
||||||
|
this.definition = definition;
|
||||||
|
let parent: Cell = Globals.Grid.getCellByRowAndCol(row, column);
|
||||||
|
this.sprite = new PIXI.Sprite({
|
||||||
|
texture: texture,
|
||||||
|
height: 64,
|
||||||
|
width: 64,
|
||||||
|
zIndex: 10,
|
||||||
|
});
|
||||||
|
this.container.addChild(this.sprite);
|
||||||
|
parent.container.addChild(this.container);
|
||||||
|
parent.clickDetector.onmouseenter = (e) => {
|
||||||
|
this.graphics.circle(this.column * 64 + 32, this.row * 64 + 32, this.definition.stats.range * 64);
|
||||||
|
this.graphics.fill({ color: 0xff0000, alpha: 0.5 });
|
||||||
|
};
|
||||||
|
parent.clickDetector.onmouseleave = (e) => {
|
||||||
|
this.graphics.clear();
|
||||||
|
};
|
||||||
|
Globals.app.stage.addChild(this.graphics);
|
||||||
|
}
|
||||||
|
public GetCreepsInRange() {
|
||||||
|
let creeps = Globals.Grid.creeps;
|
||||||
|
return creeps.filter((creep) => {
|
||||||
|
const x = creep.x;
|
||||||
|
const y = creep.y;
|
||||||
|
const towerX = this.column * 64 + 32;
|
||||||
|
const towerY = this.row * 64 + 32;
|
||||||
|
const radius = this.definition.stats.range * 64;
|
||||||
|
const d = distance(towerX, towerY, x, y);
|
||||||
|
return d < radius;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public Shoot(creep: Creep) {
|
||||||
|
let x = this.column * 64 + 32;
|
||||||
|
let y = this.row * 64 + 32;
|
||||||
|
let angle = calculateAngleToPoint(x, y, creep.x, creep.y);
|
||||||
|
this.projectiles.push(new Projectile(x, y, GameAssets.BasicProjectileTexture, angle));
|
||||||
|
}
|
||||||
|
public update(elapsedMS: any): void {
|
||||||
|
this.projectiles.forEach((proj) => {
|
||||||
|
proj.update(elapsedMS);
|
||||||
|
});
|
||||||
|
if (this.behaviour == TowerBehaviours.BasicTowerBehaviour) {
|
||||||
|
let creepsInRange = this.GetCreepsInRange();
|
||||||
|
if (creepsInRange.length > 0) {
|
||||||
|
let focus = creepsInRange[0];
|
||||||
|
this.Shoot(focus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
81
src/classes/game/TowerManager.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import * as PIXI from 'pixi.js';
|
||||||
|
import { Globals } from '../Bastion';
|
||||||
|
import { TerrainType, TowerDefinition } from '../Definitions';
|
||||||
|
import GameAssets from '../Assets';
|
||||||
|
import GameObject from '../GameObject';
|
||||||
|
import { Tower, TowerEvents } from './Tower';
|
||||||
|
|
||||||
|
export enum TowerBehaviours {
|
||||||
|
BasicTowerBehaviour = 'BasicTowerBehaviour',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class TowerManager {
|
||||||
|
public isPlacingTower: boolean = false;
|
||||||
|
public canPlaceTowers: boolean = true;
|
||||||
|
private selectedTower: TowerDefinition | null = null;
|
||||||
|
private towers: Tower[] = [];
|
||||||
|
constructor() {
|
||||||
|
Globals.TowerManager = this;
|
||||||
|
}
|
||||||
|
public ToggleChoosingTowerLocation(towerName: string) {
|
||||||
|
if (!this.canPlaceTowers) return;
|
||||||
|
Globals.Grid.toggleGrid();
|
||||||
|
if (!this.isPlacingTower) {
|
||||||
|
GameAssets.Towers.forEach((item) => {
|
||||||
|
if (item.name == towerName) {
|
||||||
|
this.selectedTower = item;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.selectedTower = null;
|
||||||
|
}
|
||||||
|
this.isPlacingTower = !this.isPlacingTower;
|
||||||
|
}
|
||||||
|
public PlayerClickOnGrid(row, column) {
|
||||||
|
if (!this.canPlaceTowers) return;
|
||||||
|
if (this.isPlacingTower) {
|
||||||
|
if (!this.selectedTower)
|
||||||
|
throw console.warn('TowerManager.selectedTower is null when trying to place tower.');
|
||||||
|
this.PlaceTower(this.selectedTower, row, column, this.selectedTower.behaviour);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public GetTowerByRowAndCol(row, col) {
|
||||||
|
// i just want to say that this is really stupid, why do i need to define a type for a local
|
||||||
|
// variable i will temporarily use? TS is weird
|
||||||
|
let returnTower: Tower | null = null;
|
||||||
|
this.towers.forEach((tower) => {
|
||||||
|
if (tower.row == row && tower.column == col) returnTower = tower;
|
||||||
|
});
|
||||||
|
return returnTower;
|
||||||
|
}
|
||||||
|
public PlaceTower(definition: TowerDefinition, row, column, behaviour: string, ignoreCost?) {
|
||||||
|
let idx = 0;
|
||||||
|
GameAssets.Towers.forEach((item, index) => {
|
||||||
|
if (item.sprite == definition.sprite) idx = index;
|
||||||
|
});
|
||||||
|
const sprite = GameAssets.TowerSprites[idx];
|
||||||
|
if (!Globals.GameScene.MissionStats.hasEnoughGold(definition.stats.cost) && !ignoreCost)
|
||||||
|
return console.warn('Does not have enough gold.');
|
||||||
|
if (
|
||||||
|
!this.GetTowerByRowAndCol(row, column) &&
|
||||||
|
Globals.Grid.getCellByRowAndCol(row, column).type != TerrainType.Path &&
|
||||||
|
Globals.Grid.getCellByRowAndCol(row, column).type != TerrainType.Restricted
|
||||||
|
) {
|
||||||
|
Globals.GameScene.MissionStats.spendGold(definition.stats.cost);
|
||||||
|
let tower = new Tower(row, column, sprite, definition, behaviour);
|
||||||
|
this.towers.push(tower);
|
||||||
|
this.ToggleChoosingTowerLocation('RESET');
|
||||||
|
console.log('SHOULDVE PLACED TOWER');
|
||||||
|
console.log(this.selectedTower);
|
||||||
|
this.selectedTower = null;
|
||||||
|
Globals.GameScene.events.emit(TowerEvents.TowerPlacedEvent, definition.name);
|
||||||
|
} else {
|
||||||
|
console.warn('Can not place tower on occupied spot or path. Try again.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public update(elapsedMS) {
|
||||||
|
this.towers.forEach((twr) => {
|
||||||
|
twr.update(elapsedMS);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
74
src/classes/game/WaveManager.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { CreepType, MissionRoundDefinition, PathDefinition } from '../Definitions';
|
||||||
|
import * as PIXI from 'pixi.js';
|
||||||
|
import Creep, { CreepEvents } from './Creep';
|
||||||
|
import { Globals } from '../Bastion';
|
||||||
|
|
||||||
|
export enum WaveManagerEvents {
|
||||||
|
CreepSpawned = 'creepSpawned',
|
||||||
|
Finished = 'finished',
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreepInstance = {
|
||||||
|
creep: Creep;
|
||||||
|
tickToSpawnAt: number;
|
||||||
|
spawned: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class WaveManager {
|
||||||
|
// Doesn't need to extend GameObject since it does not render
|
||||||
|
private creeps: CreepInstance[] = [];
|
||||||
|
private rounds: MissionRoundDefinition[];
|
||||||
|
private paths: PathDefinition[];
|
||||||
|
private ticks: number = 0;
|
||||||
|
private started: boolean = false;
|
||||||
|
public finished: boolean = false;
|
||||||
|
public events = new PIXI.EventEmitter();
|
||||||
|
constructor(rounds: MissionRoundDefinition[], paths: PathDefinition[]) {
|
||||||
|
Globals.WaveManager = this;
|
||||||
|
this.rounds = rounds;
|
||||||
|
this.paths = paths;
|
||||||
|
}
|
||||||
|
public start(roundIndex) {
|
||||||
|
this.started = true;
|
||||||
|
this.ticks = 0;
|
||||||
|
this.creeps = [];
|
||||||
|
this.finished = false;
|
||||||
|
let tickToSpawnAt = 0;
|
||||||
|
this.rounds[roundIndex].waves.forEach((wave) => {
|
||||||
|
tickToSpawnAt += wave.firstCreepSpawnTick;
|
||||||
|
wave.creeps.forEach((creep) => {
|
||||||
|
const creepObj = new Creep(creep, this.paths[0]);
|
||||||
|
const creepInstance = {
|
||||||
|
creep: creepObj,
|
||||||
|
tickToSpawnAt,
|
||||||
|
spawned: false,
|
||||||
|
};
|
||||||
|
console.log('CREAWTASEDASD');
|
||||||
|
tickToSpawnAt += wave.spawnIntervalTicks;
|
||||||
|
this.creeps.push(creepInstance);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
console.log(this.creeps);
|
||||||
|
}
|
||||||
|
public end() {
|
||||||
|
this.started = false;
|
||||||
|
}
|
||||||
|
public update(elapsedMS: number): void {
|
||||||
|
if (this.started == false) return;
|
||||||
|
this.ticks += elapsedMS;
|
||||||
|
this.creeps.forEach((creep) => {
|
||||||
|
if (!creep.spawned && creep.tickToSpawnAt <= this.ticks) {
|
||||||
|
creep.spawned = true;
|
||||||
|
this.events.emit(WaveManagerEvents.CreepSpawned, creep.creep);
|
||||||
|
console.log('Wave manager creep spawned, ', creep, this.ticks);
|
||||||
|
if (!this.finished && this.creeps.every((creep) => creep.spawned)) {
|
||||||
|
this.finished = true;
|
||||||
|
console.log('wave maanger finisehd');
|
||||||
|
this.events.emit(WaveManagerEvents.Finished);
|
||||||
|
}
|
||||||
|
} else if (creep.spawned) {
|
||||||
|
creep.creep.update(elapsedMS);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
60
src/classes/gui/Button.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import * as PIXI from 'pixi.js';
|
||||||
|
import GuiObject from '../GuiObject';
|
||||||
|
import Assets from '../Assets';
|
||||||
|
|
||||||
|
export enum ButtonTexture {
|
||||||
|
Button01 = 0,
|
||||||
|
Button02 = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Button extends GuiObject {
|
||||||
|
private caption: string;
|
||||||
|
private bounds: PIXI.Rectangle;
|
||||||
|
private buttonTexture: PIXI.Texture;
|
||||||
|
|
||||||
|
private buttonSprite: PIXI.NineSliceSprite;
|
||||||
|
private buttonText: PIXI.Text;
|
||||||
|
|
||||||
|
setCaption(caption: string) {
|
||||||
|
this.caption = caption;
|
||||||
|
this.buttonText.text = caption;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(bounds: PIXI.Rectangle, caption: string, buttonTexture: ButtonTexture, enabled: boolean = true) {
|
||||||
|
super(true);
|
||||||
|
if (buttonTexture == ButtonTexture.Button01) this.buttonTexture = Assets.Button01Texture;
|
||||||
|
if (buttonTexture == ButtonTexture.Button02) this.buttonTexture = Assets.Button02Texture;
|
||||||
|
this.caption = caption;
|
||||||
|
this.enabled = enabled;
|
||||||
|
this.bounds = bounds;
|
||||||
|
this.container.x = this.bounds.x;
|
||||||
|
this.container.y = this.bounds.y;
|
||||||
|
this.container.width = this.bounds.width;
|
||||||
|
this.container.height = this.bounds.height;
|
||||||
|
this.buttonSprite = new PIXI.NineSliceSprite({
|
||||||
|
texture: this.buttonTexture,
|
||||||
|
leftWidth: 100,
|
||||||
|
topHeight: 100,
|
||||||
|
rightWidth: 100,
|
||||||
|
bottomHeight: 100,
|
||||||
|
});
|
||||||
|
this.buttonSprite.x = 0;
|
||||||
|
this.buttonSprite.y = 0;
|
||||||
|
this.buttonSprite.width = this.bounds.width;
|
||||||
|
this.buttonSprite.height = this.bounds.height;
|
||||||
|
this.container.addChild(this.buttonSprite);
|
||||||
|
this.buttonText = new PIXI.Text({
|
||||||
|
text: this.caption,
|
||||||
|
style: new PIXI.TextStyle({
|
||||||
|
fill: 0xffffff,
|
||||||
|
fontSize: 24,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
this.container.addChild(this.buttonText);
|
||||||
|
this.buttonText.anchor.set(0.5, 0.5);
|
||||||
|
this.buttonText.x = this.bounds.width / 2;
|
||||||
|
this.buttonText.y = this.bounds.height / 2;
|
||||||
|
this.container.x = this.bounds.x;
|
||||||
|
this.container.y = this.bounds.y;
|
||||||
|
}
|
||||||
|
}
|
31
src/classes/gui/GemTab.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import * as PIXI from 'pixi.js';
|
||||||
|
import GuiObject from '../GuiObject';
|
||||||
|
import GameAssets from '../Assets';
|
||||||
|
|
||||||
|
export default class GemTab extends GuiObject {
|
||||||
|
private bounds: PIXI.Rectangle;
|
||||||
|
private gemTabSprite: PIXI.NineSliceSprite;
|
||||||
|
|
||||||
|
constructor(bounds: PIXI.Rectangle) {
|
||||||
|
super(false);
|
||||||
|
this.bounds = bounds;
|
||||||
|
this.container.x = this.bounds.x;
|
||||||
|
this.container.y = this.bounds.y;
|
||||||
|
this.gemTabSprite = new PIXI.NineSliceSprite({
|
||||||
|
texture: GameAssets.FrameTowerTab,
|
||||||
|
leftWidth: 1000,
|
||||||
|
topHeight: 1000,
|
||||||
|
rightWidth: 1000,
|
||||||
|
bottomHeight: 1000,
|
||||||
|
});
|
||||||
|
// this.towerTabSprite = new PIXI.Sprite({
|
||||||
|
// texture: GameAssets.FrameBackground,
|
||||||
|
// });
|
||||||
|
this.gemTabSprite.x = 0;
|
||||||
|
this.gemTabSprite.y = 0;
|
||||||
|
this.gemTabSprite.width = this.bounds.width;
|
||||||
|
this.gemTabSprite.height = this.bounds.height;
|
||||||
|
|
||||||
|
this.container.addChild(this.gemTabSprite);
|
||||||
|
}
|
||||||
|
}
|
40
src/classes/gui/Sidebar.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import * as PIXI from 'pixi.js';
|
||||||
|
import GuiObject from '../GuiObject';
|
||||||
|
import GameAssets from '../Assets';
|
||||||
|
import TowerTab from './TowerTab';
|
||||||
|
import GemTab from './GemTab';
|
||||||
|
|
||||||
|
export default class Sidebar extends GuiObject {
|
||||||
|
private bounds: PIXI.Rectangle;
|
||||||
|
private sidebarSprite: PIXI.NineSliceSprite;
|
||||||
|
private towerTab: TowerTab;
|
||||||
|
private gemTab: GemTab;
|
||||||
|
|
||||||
|
constructor(bounds: PIXI.Rectangle) {
|
||||||
|
super(false);
|
||||||
|
this.bounds = bounds;
|
||||||
|
this.container.x = this.bounds.x;
|
||||||
|
this.container.y = this.bounds.y;
|
||||||
|
this.sidebarSprite = new PIXI.NineSliceSprite({
|
||||||
|
texture: GameAssets.Frame01Texture,
|
||||||
|
leftWidth: 100,
|
||||||
|
topHeight: 100,
|
||||||
|
rightWidth: 100,
|
||||||
|
bottomHeight: 100,
|
||||||
|
});
|
||||||
|
this.sidebarSprite.x = 40;
|
||||||
|
this.sidebarSprite.y = -40;
|
||||||
|
this.sidebarSprite.width = this.bounds.width + 40;
|
||||||
|
this.sidebarSprite.height = this.bounds.height + 80;
|
||||||
|
|
||||||
|
this.container.addChild(this.sidebarSprite);
|
||||||
|
|
||||||
|
const towerTabRect = new PIXI.Rectangle(60, 20, this.bounds.width - 65, 150);
|
||||||
|
this.towerTab = new TowerTab(towerTabRect);
|
||||||
|
this.container.addChild(this.towerTab.container);
|
||||||
|
|
||||||
|
const gemTabRect = new PIXI.Rectangle(60, 180, this.bounds.width - 65, this.bounds.height - 280);
|
||||||
|
this.gemTab = new GemTab(gemTabRect);
|
||||||
|
this.container.addChild(this.gemTab.container);
|
||||||
|
}
|
||||||
|
}
|
81
src/classes/gui/TowerTab.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import * as PIXI from 'pixi.js';
|
||||||
|
import GuiObject from '../GuiObject';
|
||||||
|
import GameAssets from '../Assets';
|
||||||
|
import { Globals } from '../Bastion';
|
||||||
|
import { TowerEvents } from '../game/Tower';
|
||||||
|
|
||||||
|
class TowerButton extends GuiObject {
|
||||||
|
private frameSprite: PIXI.NineSliceSprite;
|
||||||
|
private background: PIXI.Sprite;
|
||||||
|
private towerName: string;
|
||||||
|
constructor(index: number, row, width, height, parent: PIXI.Container, backgroundTexture, towerName) {
|
||||||
|
if (index > 3 || row > 2 || index < 0 || row < 0) throw 'Index/row out of bounds for TowerButton.';
|
||||||
|
super(true);
|
||||||
|
this.towerName = towerName;
|
||||||
|
this.container.x = index * width + 5;
|
||||||
|
this.container.y = row * height + 5; // 5 is padding, looks better
|
||||||
|
this.background = new PIXI.Sprite({
|
||||||
|
texture: backgroundTexture,
|
||||||
|
});
|
||||||
|
this.background.width = width;
|
||||||
|
this.background.height = height;
|
||||||
|
this.container.addChild(this.background);
|
||||||
|
|
||||||
|
this.frameSprite = new PIXI.NineSliceSprite({
|
||||||
|
texture: GameAssets.Frame02Texture,
|
||||||
|
leftWidth: 100,
|
||||||
|
topHeight: 100,
|
||||||
|
rightWidth: 100,
|
||||||
|
bottomHeight: 100,
|
||||||
|
roundPixels: true,
|
||||||
|
height: height,
|
||||||
|
width: width,
|
||||||
|
});
|
||||||
|
this.container.addChild(this.frameSprite);
|
||||||
|
parent.addChild(this.container);
|
||||||
|
Globals.GameScene.events.on(TowerEvents.TowerPlacedEvent, (name) => {
|
||||||
|
this.frameSprite.tint = 0xffffff; // reset the tint after a tower has been placed
|
||||||
|
});
|
||||||
|
this.container.onmouseenter = (e) => {
|
||||||
|
// add on mouse over info (banner next to sidebar)
|
||||||
|
};
|
||||||
|
|
||||||
|
this.container.onmouseleave = (e) => {};
|
||||||
|
}
|
||||||
|
public onClick(e: PIXI.FederatedPointerEvent): void {
|
||||||
|
if (this.frameSprite.tint == 0x00ff00) this.frameSprite.tint = 0xffffff;
|
||||||
|
else this.frameSprite.tint = 0x00ff00;
|
||||||
|
Globals.TowerManager.ToggleChoosingTowerLocation(this.towerName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class TowerTab extends GuiObject {
|
||||||
|
private bounds: PIXI.Rectangle;
|
||||||
|
private towerTabSprite: PIXI.NineSliceSprite;
|
||||||
|
private towerList: Array<any> = [];
|
||||||
|
|
||||||
|
constructor(bounds: PIXI.Rectangle) {
|
||||||
|
super(false);
|
||||||
|
this.bounds = bounds;
|
||||||
|
GameAssets.Towers.forEach((twr) => {
|
||||||
|
let obj = { name: twr.name, description: twr.description, cost: twr.stats.cost };
|
||||||
|
this.towerList.push(obj);
|
||||||
|
});
|
||||||
|
this.container.x = this.bounds.x;
|
||||||
|
this.container.y = this.bounds.y;
|
||||||
|
this.towerTabSprite = new PIXI.NineSliceSprite({
|
||||||
|
texture: GameAssets.FrameTowerTab,
|
||||||
|
leftWidth: 500,
|
||||||
|
topHeight: 500,
|
||||||
|
rightWidth: 500,
|
||||||
|
bottomHeight: 500,
|
||||||
|
roundPixels: true,
|
||||||
|
});
|
||||||
|
this.towerTabSprite.width = this.bounds.width;
|
||||||
|
this.towerTabSprite.height = this.bounds.height;
|
||||||
|
this.container.addChild(this.towerTabSprite);
|
||||||
|
|
||||||
|
new TowerButton(0, 0, 70, 70, this.container, GameAssets.RedBackground, 'Basic Tower');
|
||||||
|
new TowerButton(0, 1, 70, 70, this.container, GameAssets.GreenBackground, 'Basic Tower');
|
||||||
|
}
|
||||||
|
}
|
64
src/main.ts
@ -1,23 +1,57 @@
|
|||||||
import * as PIXI from "pixi.js";
|
import * as PIXI from 'pixi.js';
|
||||||
import Game from "./base/Game";
|
import GameMaster, { Globals } from './classes/Bastion';
|
||||||
import Assets from "./base/Assets";
|
import Assets from './classes/Assets';
|
||||||
|
import { MainScene } from './scenes/Main';
|
||||||
|
import { GameScene } from './scenes/Game';
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
const app = new PIXI.Application();
|
const app = new PIXI.Application();
|
||||||
|
Globals.app = app;
|
||||||
await app.init({
|
await app.init({
|
||||||
width: 640,
|
width: 1920, // Base width
|
||||||
height: 360,
|
height: 1080, // Base height
|
||||||
resizeTo: document.body,
|
resolution: window.devicePixelRatio || 1,
|
||||||
backgroundColor: "white",
|
autoDensity: true,
|
||||||
|
backgroundColor: 0xffffff,
|
||||||
sharedTicker: true,
|
sharedTicker: true,
|
||||||
preference: "webgl",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
document.body.appendChild(app.canvas);
|
document.body.appendChild(app.canvas);
|
||||||
|
|
||||||
|
function resize() {
|
||||||
|
const windowWidth = window.innerWidth;
|
||||||
|
const windowHeight = window.innerHeight;
|
||||||
|
Globals.WindowHeight = windowHeight;
|
||||||
|
Globals.WindowWidth = windowWidth;
|
||||||
|
|
||||||
|
// Calculate scale factor to maintain aspect ratio
|
||||||
|
const scaleX = windowWidth / app.screen.width;
|
||||||
|
const scaleY = windowHeight / app.screen.height;
|
||||||
|
const scale = Math.min(scaleX, scaleY); // Use the smaller scale to fit
|
||||||
|
|
||||||
|
// Calculate new canvas size
|
||||||
|
const gameWidth = Math.round(app.screen.width * scale);
|
||||||
|
const gameHeight = Math.round(app.screen.height * scale);
|
||||||
|
|
||||||
|
// Center the canvas
|
||||||
|
const marginHorizontal = (windowWidth - gameWidth) / 2;
|
||||||
|
const marginVertical = (windowHeight - gameHeight) / 2;
|
||||||
|
|
||||||
|
// Apply styles to canvas
|
||||||
|
app.canvas.style.width = `${gameWidth}px`;
|
||||||
|
app.canvas.style.height = `${gameHeight}px`;
|
||||||
|
app.canvas.style.marginLeft = `${marginHorizontal}px`;
|
||||||
|
app.canvas.style.marginTop = `${marginVertical}px`;
|
||||||
|
app.canvas.style.marginRight = `0`; // Prevent unnecessary margin
|
||||||
|
app.canvas.style.marginBottom = `0`; // Prevent unnecessary margin
|
||||||
|
app.canvas.style.display = 'block'; // Prevent inline-block spacing issues
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('resize', resize);
|
||||||
|
resize();
|
||||||
await Assets.LoadAssets();
|
await Assets.LoadAssets();
|
||||||
const game = new Game(app.screen);
|
new GameMaster();
|
||||||
app.stage.addChild(game.container);
|
globalThis.Globals = Globals;
|
||||||
window.addEventListener("resize", () => {
|
Globals.GameMaster.changeScene(new MainScene());
|
||||||
app.renderer.resize(window.innerWidth, window.innerHeight);
|
let params = new URLSearchParams(location.href);
|
||||||
game.setBounds(0, 0, app.screen.width, app.screen.height);
|
if (params.entries().next().value[1] == 'game') Globals.GameMaster.changeScene(new GameScene('Mission 1'));
|
||||||
});
|
|
||||||
})();
|
})();
|
||||||
|
94
src/scenes/Game.ts
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import GameAssets from '../classes/Assets';
|
||||||
|
import { Globals } from '../classes/Bastion';
|
||||||
|
import { MissionDefinition } from '../classes/Definitions';
|
||||||
|
import Creep, { CreepEvents } from '../classes/game/Creep';
|
||||||
|
import { Grid } from '../classes/game/Grid';
|
||||||
|
import WaveManager, { WaveManagerEvents } from '../classes/game/WaveManager';
|
||||||
|
import Sidebar from '../classes/gui/Sidebar';
|
||||||
|
import Button, { ButtonTexture } from '../classes/gui/Button';
|
||||||
|
import Scene from './Scene';
|
||||||
|
import * as PIXI from 'pixi.js';
|
||||||
|
import MissionStats from '../classes/game/MissionStats';
|
||||||
|
import TowerManager from '../classes/game/TowerManager';
|
||||||
|
|
||||||
|
enum RoundMode {
|
||||||
|
Purchase = 0,
|
||||||
|
Combat = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GameScene extends Scene {
|
||||||
|
public mission: MissionDefinition;
|
||||||
|
public missionIndex: number;
|
||||||
|
public MissionStats: MissionStats;
|
||||||
|
public roundMode: RoundMode;
|
||||||
|
public ticker: PIXI.Ticker;
|
||||||
|
public changeRoundButton: Button;
|
||||||
|
public sidebar: Sidebar;
|
||||||
|
private currentRound: number = 0;
|
||||||
|
|
||||||
|
constructor(name: string) {
|
||||||
|
super();
|
||||||
|
Globals.GameScene = this;
|
||||||
|
GameAssets.Missions.forEach((mission, index) => {
|
||||||
|
if (mission.name == name) {
|
||||||
|
this.mission = mission;
|
||||||
|
this.missionIndex = index;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public init() {
|
||||||
|
this.ticker = new PIXI.Ticker();
|
||||||
|
this.ticker.maxFPS = 60;
|
||||||
|
this.ticker.minFPS = 30;
|
||||||
|
this.ticker.add(() => this.update(this.ticker.elapsedMS)); // bruh
|
||||||
|
this.ticker.start();
|
||||||
|
const SidebarRect = new PIXI.Rectangle(64 * 30 - 360, 0, 360, Globals.app.canvas.height);
|
||||||
|
const changeRoundButtonRect = new PIXI.Rectangle(50, Globals.app.canvas.height - 100, 310, 100);
|
||||||
|
new Grid(this.mission.gameMap, this.missionIndex);
|
||||||
|
new TowerManager();
|
||||||
|
new WaveManager(this.mission.rounds, this.mission.gameMap.paths);
|
||||||
|
Globals.Grid.onGridCellClicked = (row, column) => {
|
||||||
|
if (Globals.TowerManager.isPlacingTower) {
|
||||||
|
Globals.TowerManager.PlayerClickOnGrid(row, column);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Globals.WaveManager.events.on(WaveManagerEvents.CreepSpawned, (creep: Creep) => {
|
||||||
|
Globals.Grid.addCreep(creep);
|
||||||
|
creep.events.on(CreepEvents.Escaped, () => {
|
||||||
|
this.onCreepEscaped(creep);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.sidebar = new Sidebar(SidebarRect);
|
||||||
|
this.changeRoundButton = new Button(changeRoundButtonRect, 'Start', ButtonTexture.Button01, true);
|
||||||
|
this.changeRoundButton.container.removeFromParent();
|
||||||
|
this.sidebar.container.addChild(this.changeRoundButton.container);
|
||||||
|
this.changeRoundButton.onClick = () => {
|
||||||
|
console.log('clicked');
|
||||||
|
this.changeRoundButton.setEnabled(false);
|
||||||
|
this.changeRoundButton.setCaption('[X]');
|
||||||
|
this.setRoundMode(RoundMode.Combat);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.MissionStats = new MissionStats(100, 200);
|
||||||
|
}
|
||||||
|
public update(elapsedMS) {
|
||||||
|
Globals.WaveManager.update(elapsedMS);
|
||||||
|
Globals.Grid.update(elapsedMS);
|
||||||
|
Globals.TowerManager.update(elapsedMS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public onCreepEscaped(creep: Creep) {
|
||||||
|
this.MissionStats.takeDamage(creep.health);
|
||||||
|
}
|
||||||
|
|
||||||
|
private setRoundMode(roundMode: RoundMode) {
|
||||||
|
this.roundMode = roundMode;
|
||||||
|
if (this.roundMode == RoundMode.Combat) {
|
||||||
|
Globals.WaveManager.start(this.currentRound);
|
||||||
|
} else {
|
||||||
|
Globals.WaveManager.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public onTowerPlaced() {}
|
||||||
|
}
|
28
src/scenes/Main.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { Globals } from '../classes/Bastion';
|
||||||
|
import Button, { ButtonTexture } from '../classes/gui/Button';
|
||||||
|
import { MissionPickerScene } from './MissionPicker';
|
||||||
|
import Scene from './Scene';
|
||||||
|
import * as PIXI from 'pixi.js';
|
||||||
|
|
||||||
|
export class MainScene extends Scene {
|
||||||
|
public init() {
|
||||||
|
const NewGameButton = {
|
||||||
|
caption: 'New Game',
|
||||||
|
rect: new PIXI.Rectangle(Globals.WindowWidth / 2 - 300 / 2, Globals.WindowHeight / 2 - 80, 300, 60),
|
||||||
|
texture: ButtonTexture.Button01,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SettingsButton = {
|
||||||
|
caption: 'Settings',
|
||||||
|
rect: new PIXI.Rectangle(Globals.WindowWidth / 2 - 300 / 2, Globals.WindowHeight / 2 + 20, 300, 60),
|
||||||
|
texture: ButtonTexture.Button01,
|
||||||
|
};
|
||||||
|
|
||||||
|
const button01 = new Button(NewGameButton.rect, NewGameButton.caption, NewGameButton.texture, true);
|
||||||
|
button01.onClick = (e) => {
|
||||||
|
Globals.GameMaster.changeScene(new MissionPickerScene());
|
||||||
|
};
|
||||||
|
|
||||||
|
new Button(SettingsButton.rect, SettingsButton.caption, SettingsButton.texture, true);
|
||||||
|
}
|
||||||
|
}
|
26
src/scenes/MissionPicker.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import Assets from '../classes/Assets';
|
||||||
|
import { Globals } from '../classes/Bastion';
|
||||||
|
import Button, { ButtonTexture } from '../classes/gui/Button';
|
||||||
|
import { GameScene } from './Game';
|
||||||
|
import Scene from './Scene';
|
||||||
|
import * as PIXI from 'pixi.js';
|
||||||
|
|
||||||
|
export class MissionPickerScene extends Scene {
|
||||||
|
public init() {
|
||||||
|
Assets.Missions.forEach((mission, index) => {
|
||||||
|
const button = new Button(
|
||||||
|
new PIXI.Rectangle(
|
||||||
|
Globals.app.canvas.width / 2 - 300 / 2,
|
||||||
|
Globals.app.canvas.height / 5 + index * 80,
|
||||||
|
300,
|
||||||
|
60
|
||||||
|
),
|
||||||
|
mission.name,
|
||||||
|
ButtonTexture.Button01
|
||||||
|
);
|
||||||
|
button.onClick = (e) => {
|
||||||
|
Globals.GameMaster.changeScene(new GameScene(mission.name));
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
27
src/scenes/Scene.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import GuiObject from '../classes/GuiObject';
|
||||||
|
import * as PIXI from 'pixi.js';
|
||||||
|
|
||||||
|
export default class Scene {
|
||||||
|
public gui: GuiObject[] = [];
|
||||||
|
private _events: PIXI.EventEmitter = new PIXI.EventEmitter();
|
||||||
|
|
||||||
|
public destroy() {
|
||||||
|
this.gui.forEach((element) => {
|
||||||
|
element.destroy();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public GetGuiObject(object: GuiObject) {
|
||||||
|
return this.gui.find((obj) => obj == object);
|
||||||
|
}
|
||||||
|
public GetGuiObjectByName(name: string) {
|
||||||
|
return this.gui.filter((obj) => obj.name == name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get events(): PIXI.EventEmitter {
|
||||||
|
return this._events;
|
||||||
|
}
|
||||||
|
|
||||||
|
public init() {
|
||||||
|
// Definitions for scene elements.
|
||||||
|
}
|
||||||
|
}
|
@ -18,6 +18,6 @@
|
|||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"checkJs": false
|
"checkJs": false
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"],
|
"include": ["bsrc/**/*"],
|
||||||
"exclude": ["node_modules", "build"]
|
"exclude": ["node_modules", "build"]
|
||||||
}
|
}
|
||||||
|