diff --git a/package-lock.json b/package-lock.json index 6cad3d0..418b4d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,9 +12,11 @@ "@angular/compiler": "^19.2.0", "@angular/core": "^19.2.0", "@angular/forms": "^19.2.0", + "@angular/material": "^19.2.11", "@angular/platform-browser": "^19.2.0", "@angular/platform-browser-dynamic": "^19.2.0", "@angular/router": "^19.2.0", + "phaser": "^3.88.2", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.15.0" @@ -493,6 +495,22 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/@angular/cdk": { + "version": "19.2.11", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-19.2.11.tgz", + "integrity": "sha512-G568yWIJlnsuS563WxvCofmxc1405+wRQvDGQ32+qWOblJScFkHgr4jeDkZGcyt/r8OudaW0H0/rNeg1dzdnIQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "parse5": "^7.1.2", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": "^19.0.0 || ^20.0.0", + "@angular/core": "^19.0.0 || ^20.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/@angular/cli": { "version": "19.2.9", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-19.2.9.tgz", @@ -666,6 +684,23 @@ "rxjs": "^6.5.3 || ^7.4.0" } }, + "node_modules/@angular/material": { + "version": "19.2.11", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-19.2.11.tgz", + "integrity": "sha512-0OWwv55Il25mit7oGTloMeKVi0v/q1tr13wUJj0KJOcvICA6JCEW6VEc9zqYmkMPstDCx96cSJgPKxkHjKYyqg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/cdk": "19.2.11", + "@angular/common": "^19.0.0 || ^20.0.0", + "@angular/core": "^19.0.0 || ^20.0.0", + "@angular/forms": "^19.0.0 || ^20.0.0", + "@angular/platform-browser": "^19.0.0 || ^20.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/@angular/platform-browser": { "version": "19.2.8", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-19.2.8.tgz", @@ -11161,7 +11196,6 @@ "version": "7.3.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "dev": true, "license": "MIT", "dependencies": { "entities": "^6.0.0" @@ -11202,7 +11236,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz", "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -11302,6 +11335,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/phaser": { + "version": "3.88.2", + "resolved": "https://registry.npmjs.org/phaser/-/phaser-3.88.2.tgz", + "integrity": "sha512-UBgd2sAFuRJbF2xKaQ5jpMWB8oETncChLnymLGHcrnT53vaqiGrQWbUKUDBawKLm24sghjKo4Bf+/xfv8espZQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1" + } + }, + "node_modules/phaser/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", diff --git a/package.json b/package.json index 594e898..6287245 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,11 @@ "@angular/compiler": "^19.2.0", "@angular/core": "^19.2.0", "@angular/forms": "^19.2.0", + "@angular/material": "^19.2.11", "@angular/platform-browser": "^19.2.0", "@angular/platform-browser-dynamic": "^19.2.0", "@angular/router": "^19.2.0", + "phaser": "^3.88.2", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.15.0" diff --git a/public/sprites/ships/01-starter.png b/public/sprites/ships/01-starter.png new file mode 100644 index 0000000..7a7f36d Binary files /dev/null and b/public/sprites/ships/01-starter.png differ diff --git a/src/app/app.component.html b/src/app/app.component.html index 36093e1..7a56999 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,336 +1 @@ - - - - - - - - - - - -
-
-
- -

Hello, {{ title }}

-

Congratulations! Your app is running. 🎉

-
- -
-
- @for (item of [ - { title: 'Explore the Docs', link: 'https://angular.dev' }, - { title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials' }, - { title: 'CLI Docs', link: 'https://angular.dev/tools/cli' }, - { title: 'Angular Language Service', link: 'https://angular.dev/tools/language-service' }, - { title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools' }, - ]; track item.title) { - - {{ item.title }} - - - - - } -
- -
-
-
- - - - - - - - - - - +
\ No newline at end of file diff --git a/src/app/app.component.scss b/src/app/app.component.scss index e69de29..1fa9853 100644 --- a/src/app/app.component.scss +++ b/src/app/app.component.scss @@ -0,0 +1,8 @@ +.game-container { + position: absolute; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + overflow: hidden; +} \ No newline at end of file diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 54fadbf..c738617 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,12 +1,54 @@ -import { Component } from '@angular/core'; -import { RouterOutlet } from '@angular/router'; +import { Component, ElementRef, inject, ViewChild } from '@angular/core'; +import Phaser from 'phaser'; +import { MapScene } from './scene/map.scene'; +import { MatDialog, MatDialogModule } from '@angular/material/dialog'; +import { GameService } from './service/game.service'; @Component({ selector: 'app-root', - imports: [RouterOutlet], + imports: [MatDialogModule], templateUrl: './app.component.html', styleUrl: './app.component.scss' }) export class AppComponent { - title = 'stellar-lines'; + + public dialog = inject(MatDialog); + public gameService = inject(GameService); + + @ViewChild('gameContainer', { static: true }) gameContainer!: ElementRef; + + ngAfterViewInit(): void { + this.gameService.dialog = this.dialog; + const scene = new MapScene(); + scene.gameService = this.gameService; + + const config: Phaser.Types.Core.GameConfig = { + type: Phaser.AUTO, + width: window.innerWidth, + height: window.innerHeight, + disableContextMenu: true, + scale: { + mode: Phaser.Scale.EXPAND, + autoCenter: Phaser.Scale.CENTER_BOTH, + + }, + parent: this.gameContainer.nativeElement, + backgroundColor: '#0c0e1a', + scene: [scene], + render: { + pixelArt: false, + antialias: true, + }, + + physics: { + default: 'arcade', + arcade: { + debug: true, + timeScale: 1 + } + } + }; + + const game = new Phaser.Game(config); + } } diff --git a/src/app/components/dialog/planet-dialog/planet-dialog.component.html b/src/app/components/dialog/planet-dialog/planet-dialog.component.html new file mode 100644 index 0000000..62630a3 --- /dev/null +++ b/src/app/components/dialog/planet-dialog/planet-dialog.component.html @@ -0,0 +1,24 @@ +@if (planet) { +

{{planet.name}}

+ +
Bevölkerung: {{ planet.population | number:'0.0-0' }}
+ + + +
+

Güter:

+ @for (item of planet.getAllGoods(); track $index) { +
+ {{ item.type }}: {{ item.amount }} +
+ } +
+ +
+

Angebotene Güter:

+ @for (item of offeredItems(); track $index) { + {{ item.type }}: {{ item.amount | number }} / {{ item.productionStorage | number }} + } +
+
+} \ No newline at end of file diff --git a/src/app/components/dialog/planet-dialog/planet-dialog.component.scss b/src/app/components/dialog/planet-dialog/planet-dialog.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/components/dialog/planet-dialog/planet-dialog.component.spec.ts b/src/app/components/dialog/planet-dialog/planet-dialog.component.spec.ts new file mode 100644 index 0000000..ba06a1d --- /dev/null +++ b/src/app/components/dialog/planet-dialog/planet-dialog.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PlanetDialogComponent } from './planet-dialog.component'; + +describe('PlanetDialogComponent', () => { + let component: PlanetDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [PlanetDialogComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(PlanetDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/dialog/planet-dialog/planet-dialog.component.ts b/src/app/components/dialog/planet-dialog/planet-dialog.component.ts new file mode 100644 index 0000000..f3ae318 --- /dev/null +++ b/src/app/components/dialog/planet-dialog/planet-dialog.component.ts @@ -0,0 +1,25 @@ +import { Component, inject, InjectionToken } from '@angular/core'; +import { Planet } from '../../../model/planet.model'; +import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; +import { CommonModule } from '@angular/common'; +import { Good } from '../../../model/goods/good.interface'; + +@Component({ + selector: 'app-planet-dialog', + imports: [CommonModule, MatDialogModule], + templateUrl: './planet-dialog.component.html', + styleUrl: './planet-dialog.component.scss' +}) +export class PlanetDialogComponent { + readonly planet = inject(MAT_DIALOG_DATA); + + ngOnInit() { + console.log(this.planet) + } + + + offeredItems(): Good[] { + return this.planet.getAllGoods().filter(g => g.productionRate) + } +} + diff --git a/src/app/model/goods/good-config.ts b/src/app/model/goods/good-config.ts new file mode 100644 index 0000000..3f2357a --- /dev/null +++ b/src/app/model/goods/good-config.ts @@ -0,0 +1,18 @@ +import { GoodType } from "./good-type.enum"; + +export interface GoodConfig { + baseProduction: number; // pro Sekunde + baseDemand: number; // pro Sekunde + storageLimit: number; + isRawResource: boolean; +} + +export const GOODS_DATA: Record = { + [GoodType.Erz]: { baseProduction: 1.0, baseDemand: 0, storageLimit: 500, isRawResource: true }, + [GoodType.Wasser]: { baseProduction: 0.8, baseDemand: 0.2, storageLimit: 300, isRawResource: true }, + [GoodType.Nahrung]: { baseProduction: 5, baseDemand: 0.5, storageLimit: 200, isRawResource: true }, + [GoodType.Metall]: { baseProduction: 0, baseDemand: 0.6, storageLimit: 300, isRawResource: false }, + [GoodType.Treibstoff]: { baseProduction: 0, baseDemand: 0.3, storageLimit: 150, isRawResource: false }, + [GoodType.Elektronik]: { baseProduction: 0, baseDemand: 0.4, storageLimit: 100, isRawResource: false }, + [GoodType.Bauteile]: { baseProduction: 0, baseDemand: 0.2, storageLimit: 100, isRawResource: false } +}; \ No newline at end of file diff --git a/src/app/model/goods/good-type.enum.ts b/src/app/model/goods/good-type.enum.ts new file mode 100644 index 0000000..60c7299 --- /dev/null +++ b/src/app/model/goods/good-type.enum.ts @@ -0,0 +1,9 @@ +export enum GoodType { + Erz = 'Erz', + Wasser = 'Wasser', + Nahrung = 'Nahrung', + Metall = 'Metall', + Treibstoff = 'Treibstoff', + Elektronik = 'Elektronik', + Bauteile = 'Bauteile' +} \ No newline at end of file diff --git a/src/app/model/goods/good.interface.ts b/src/app/model/goods/good.interface.ts new file mode 100644 index 0000000..71fad34 --- /dev/null +++ b/src/app/model/goods/good.interface.ts @@ -0,0 +1,9 @@ +import { GoodType } from "./good-type.enum"; + +export interface Good { + type: GoodType, + amount: number; + productionRate: number; + demandRate: number; + productionStorage: number; +} \ No newline at end of file diff --git a/src/app/model/planet.model.ts b/src/app/model/planet.model.ts new file mode 100644 index 0000000..b927082 --- /dev/null +++ b/src/app/model/planet.model.ts @@ -0,0 +1,131 @@ +import { interval, Subscription } from "rxjs"; +import { GoodType } from "./goods/good-type.enum"; +import { GOODS_DATA } from "./goods/good-config"; +import { Good } from "./goods/good.interface"; +import { ShipUi } from "./ship"; +import { Ship } from "./ships/ship.model"; + +export class Planet { + public population: number = 0; + public name: string = ""; + + private goods: Map = new Map(); + private updateSubscription = interval(5000).subscribe(() => {this.update(5)}); // alle 5s + + public isGrowing: boolean = false; + + + constructor(config: PlanetInit) { + this.name = config.name; + config.initialGoods.forEach(good => { + const base = GOODS_DATA[good.type]; + + this.goods.set(good.type, { + type: good.type, + amount: good.amount, + demandRate: base.baseDemand, + productionRate: base.baseProduction * (good.productionBonus ?? 0), + productionStorage: base.storageLimit + }) + }); + this.setIsGrowing(); + } + + + private update(seconds: number): void { + this.goods.forEach((good: Good, key: string) => { + good.amount += good.productionRate * seconds; + good.amount -= good.demandRate * seconds; + + // Min 0 + good.amount = Math.max(0, good.amount); + if (good.productionRate && good.productionStorage) { + good.amount = Math.min(good.amount, good.productionStorage) + } + }); + + this.setIsGrowing(); + + return; + + // Debug-Ausgabe (später ersetzen durch Events/Callback/Service) + console.log(`[${this.name}] Wirtschaft aktualisiert:`); + this.goods.forEach(good => { + console.log(` ${good.type}: ${good.amount.toFixed(2)}`); + }); + + console.log("Angeboten:") + for (let good of this.offeredGoods) { + console.log(` ${good.type}: ${good.amount.toFixed(2)}`) + } + + console.log("Nachgefragt:") + for (let good of this.requestedGoods) { + console.log(` ${good.type}: ${good.amount.toFixed(2)}`) + } + + + + } + + private setIsGrowing(): void { + this.isGrowing = !this.getAllGoods().some( g => g.amount == 0); + } + + + getGood(type: GoodType): Good | undefined { + return this.goods.get(type); + } + + getAllGoods(): Good[] { + return Array.from(this.goods.values()); + } + + addGood(good: Good): void { + this.goods.set(good.type, { ...good }); + } + + + deliver(ship: Ship) { + if (!ship || ship.cargoSpace.length == 0) { return; } + + const requests = this.requestedGoods; + if (requests.length == 0) { return; } + for (let request of requests) { + const offer = ship.cargoSpace.find(cargo => cargo.type == request?.type); + if (!offer) { continue; } + const transfer = Math.min(offer.amount, request.amount); + const good = this.getGood(request.type); + if (!good) { continue; } + good.amount += transfer; + offer.amount -= transfer; + } + } + + + get requestedGoods(): {type: GoodType, amount: number}[] { + return this.getAllGoods().filter(g => !g.productionRate && g.amount < g.demandRate * 30).map(g => { return { type: g.type, amount: g.demandRate * 100 }}) + } + + get offeredGoods(): {type: GoodType, amount: number}[] { + return this.getAllGoods().filter(g => g.productionRate && g.amount > g.demandRate * 10).map(g => { return { type: g.type, amount: g.amount - g.demandRate * 20}}) + } + + request(ship: ShipUi) { + const offers = this.offeredGoods; + if (offers.length == 0) { return; } + const loaded = ship.loadCargo(offers[0]); + offers[0].amount -= loaded; + + } + +} + +export interface PlanetInit { + name: string; + initialGoods: { + type: GoodType; + amount: number; + productionBonus?: number; // optional: z. B. 1.5 = 150% der Basis + }[]; +} \ No newline at end of file diff --git a/src/app/model/ship.ts b/src/app/model/ship.ts new file mode 100644 index 0000000..b2ce9ae --- /dev/null +++ b/src/app/model/ship.ts @@ -0,0 +1,100 @@ +import { PlanetUi } from "../ui/planet.ui"; +import { GoodType } from "./goods/good-type.enum"; +import { Ship } from "./ships/ship.model"; + +export class ShipUi extends Phaser.Physics.Arcade.Sprite { + private velocity = new Phaser.Math.Vector2(0, 0); + private target: PlanetUi | null = null; + private targetVector: Phaser.Math.Vector2 | null = null; // Später Planet! + public model: Ship = new Ship(); + + + constructor(scene: Phaser.Scene, x: number, y: number) { + super(scene, x, y, 'ship'); + + scene.add.existing(this); + scene.physics.add.existing(this); + this.setOrigin(0.5); + this.setScale(1); // oder z. B. 0.5 je nach Bildgröße + this.setDisplaySize(32, 32) + this.setCollideWorldBounds(true); + const conf: Phaser.Types.Input.InputConfiguration = { + useHandCursor: true + } + this.setInteractive(conf) + } + + moveTo(target: PlanetUi) { + this.target = target; + this.targetVector = new Phaser.Math.Vector2(target.getWorldPoint().x, target.getWorldPoint().y); + const angle = Phaser.Math.Angle.Between(this.getWorldPoint().x, this.getWorldPoint().y, target.getWorldPoint().x, target.getWorldPoint().y); + // this.setRotation(angle); // Zeigt Schiff zur Zielrichtung + if (!this.body) { return; } + } + + override update(time: number, delta: number): void { + if (!this.targetVector) return; + if (!(this.body instanceof Phaser.Physics.Arcade.Body)) { return; } + const dt = delta / 1000; // ms → Sekunden + + const toTarget = new Phaser.Math.Vector2(this.targetVector.x - this.getWorldPoint().x, this.targetVector.y - this.getWorldPoint().y); + const distance = toTarget.length(); + + this.setRotation(this.velocity.angle() + Phaser.Math.DegToRad(180)); // Zeigt Schiff zur Zielrichtung + + if (distance < 4) { + this.body.setVelocity(0, 0); + this.velocity.set(0, 0); + if (this.targetVector && this.target) { + this.targetReached(this.target); + } + this.targetVector = null; + return; + } + + // Richtung zum Ziel + const desiredDirection = toTarget.normalize(); + + // Zielgeschwindigkeit abhängig von Entfernung (langsamer bremsen) + const targetSpeed = (distance < this.model.slowDownRadius) + ? this.model.maxSpeed * (distance / this.model.slowDownRadius) + : this.model.maxSpeed; + + const desiredVelocity = desiredDirection.scale(targetSpeed); + + // Steuerung per "Lerp" zur Zielgeschwindigkeit (weiches Beschleunigen) + this.velocity.lerp(desiredVelocity, this.model.acceleration * dt / this.model.maxSpeed); + + this.body.setVelocity(this.velocity.x, this.velocity.y); + + } + + targetReached(target: PlanetUi) { + console.log(target) + + target.model.deliver(this.model); + target.model.request(this); + } + + loadCargo(cargo: {type: GoodType, amount: number}): number { + const loaded = this.model.cargoSpace.reduce((acc, succ) => acc + succ.amount, 0); + if (loaded >= this.model.cargoSize) { return 0; } + + const remaining = this.model.cargoSize - loaded; + const load = Math.min(remaining, cargo.amount); + + const exists = this.model.cargoSpace.find(c => c.type == cargo.type); + if (exists) { + exists.amount += cargo.amount + } else { + this.model.cargoSpace.push({ + amount: load, + type: cargo.type + }) + } + + console.log(this.model.cargoSpace) + return load; + } + +} \ No newline at end of file diff --git a/src/app/model/ships/ship.model.ts b/src/app/model/ships/ship.model.ts new file mode 100644 index 0000000..7ffa765 --- /dev/null +++ b/src/app/model/ships/ship.model.ts @@ -0,0 +1,9 @@ +import { GoodType } from "../goods/good-type.enum"; + +export class Ship { + public acceleration = 100; // Pixel pro Sekunde² + public maxSpeed = 200; + public slowDownRadius = 1000; // Startet Bremsen, wenn Ziel nahe ist + public cargoSize = 20; + cargoSpace: {type: GoodType, amount: number}[] = []; +} \ No newline at end of file diff --git a/src/app/scene/map.scene.ts b/src/app/scene/map.scene.ts new file mode 100644 index 0000000..e714184 --- /dev/null +++ b/src/app/scene/map.scene.ts @@ -0,0 +1,140 @@ +import { GoodType } from "../model/goods/good-type.enum"; +import { ShipUi } from "../model/ship"; +import { GameService } from "../service/game.service"; +import { PlanetUi } from "../ui/planet.ui"; + +export class MapScene extends Phaser.Scene { + public gameService: GameService = new GameService(); + private camera!: Phaser.Cameras.Scene2D.Camera; + private isDragging = false; + private dragStart = new Phaser.Math.Vector2(); + private ship!: ShipUi; // Das Raumschiff + + + private lastZoomTime = 0; + + constructor() { + super({ key: 'MapScene '}) + } + + preload() { + this.load.image('ship', 'sprites/ships/01-starter.png'); + } + + create() { + this.camera = this.cameras.main; + + this.createPlaceHolderGraphic(); + // this.camera.setBackgroundColor('0x99ccff') + + // Weltgröße groß setzen, z. B. 5000x5000 Pixel + this.camera.setBounds(0, 0, 10000, 10000); + this.physics.world.setBounds(0, 0, 10000, 10000); + this.cameras.main.setBounds(0, 0, 10000, 10000); + + + /* Sterne */ + for (let i = 0; i< 1000; i++) { + const x = Phaser.Math.Between(0, 10000); + const y = Phaser.Math.Between(0, 10000); + this.add.circle(x, y, Phaser.Math.Between(1, 3), 0x88ccff) + } + + // Beispiel: ein paar Kreise (Planeten) zeichnen + this.buildPlanets(); + + // Events für Panning + this.input.on('pointerdown', (pointer: Phaser.Input.Pointer) => { + this.isDragging = true; + this.dragStart.set(pointer.x, pointer.y); + }); + + this.input.on('pointerup', () => { + this.isDragging = false; + }); + + this.input.on('pointermove', (pointer: Phaser.Input.Pointer) => { + if (!this.isDragging) return; + const dragX = pointer.x - this.dragStart.x; + const dragY = pointer.y - this.dragStart.y; + this.camera.scrollX -= dragX / this.camera.zoom; + this.camera.scrollY -= dragY / this.camera.zoom; + this.dragStart.set(pointer.x, pointer.y); + }); + + // Zoom via Mausrad + this.input.on('wheel', (_: any, __: number, ___: number, deltaY: number) => { + const now = Date.now(); + if (now - this.lastZoomTime < 50) return; // nur alle 100ms + this.lastZoomTime = now; + const factor = 1.05; + if (deltaY > 0) { + this.camera.setZoom(this.camera.zoom / factor); + } else { + this.camera.setZoom(this.camera.zoom * factor); + } + + const minScale = Math.max(window.innerWidth / 10000, window.innerHeight / 10000) + // Begrenze Zoom + this.camera.setZoom(Phaser.Math.Clamp(this.camera.zoom, minScale, 5)); + }); + + this.ship = new ShipUi(this, 100, 100); + this.physics.world.enable(this.ship); + + + } + + override update(time: number, delta: number): void { + if (this.ship) { + + this.ship.update(time, delta); + } + } + private buildPlanets() { + for (let i = 0; i < 1; i++) { + const x = Phaser.Math.Between(200, 300); + const y = Phaser.Math.Between(200, 300); + + const conf = { + name: 'Terra Nova', + initialGoods: [ + {type: GoodType.Nahrung, amount: 10, productionBonus: 1 }, + {type: GoodType.Wasser, amount: 10 }, + ] + } + const p = new PlanetUi(this, x, y, conf); + p.rightClick.subscribe(n => { + this.ship.moveTo(p) + }) + + p.leftClick.subscribe(n => { + this.gameService.showDialog(p.model); + }) + } + + for (let i = 0; i < 1; i++) { + const conf = { + name: 'Waterloo', + initialGoods: [ + {type: GoodType.Nahrung, amount: 10 }, + {type: GoodType.Wasser, amount: 10, productionBonus: 1 }, + ] + } + const x = Phaser.Math.Between(400, 500); + const y = Phaser.Math.Between(400, 500); + const p = new PlanetUi(this, x, y, conf); + p.rightClick.subscribe(n => { + this.ship.moveTo(p) + }) + } + } + + private createPlaceHolderGraphic() { + const graphics = this.add.graphics(); + graphics.fillStyle(0x88ccff, 1); + graphics.fillCircle(100, 100, 100); + graphics.generateTexture('planet', 200, 200); + graphics.destroy(); + } +} \ No newline at end of file diff --git a/src/app/service/game.service.ts b/src/app/service/game.service.ts new file mode 100644 index 0000000..dbb8441 --- /dev/null +++ b/src/app/service/game.service.ts @@ -0,0 +1,19 @@ +import { inject, Injectable } from "@angular/core"; +import { MatDialog } from "@angular/material/dialog"; +import { PlanetDialogComponent } from "../components/dialog/planet-dialog/planet-dialog.component"; +import { Planet } from "../model/planet.model"; + +@Injectable({ + providedIn: 'root', +}) +export class GameService { + public dialog!: MatDialog; + + constructor() {} + + showDialog(planet: Planet) { + this.dialog.open(PlanetDialogComponent, { + data: planet + }) + } +} \ No newline at end of file diff --git a/src/app/ui/planet.ui.ts b/src/app/ui/planet.ui.ts new file mode 100644 index 0000000..c7e9f7c --- /dev/null +++ b/src/app/ui/planet.ui.ts @@ -0,0 +1,70 @@ +import { EventEmitter } from "@angular/core"; +import { interval } from "rxjs"; +import { Planet } from "../model/planet.model"; + +export class PlanetUi extends Phaser.Physics.Arcade.Sprite { + + public leftClick: EventEmitter = new EventEmitter(); + public rightClick: EventEmitter = new EventEmitter(); + public model: Planet; + private growingIndicator!: Phaser.GameObjects.Text; + + private updateInterval = interval(1000) + + constructor(scene: Phaser.Scene, x: number, y: number, config: any) { + super(scene, x, y, 'planet'); + + this.model = new Planet(config); + + scene.add.existing(this); + this.setInteractive({ useHandCursor: true }); + + this.leftClick.emit(); + + + this.on('pointerdown', (pointer: Phaser.Input.Pointer) => { + if (pointer.button == Phaser.Input.MOUSE_DOWN) { + this.leftClick.emit(this) + } else if (pointer.button == Phaser.Input.MOUSE_UP) { + this.rightClick.emit(this); + } + }); + + this.growingIndicator = scene.add.text(x, y - 100, this.model.name, { + fontSize: '20px', + color: '#ffffff', + fontFamily: 'Arial', + backgroundColor: '#00000080', + padding: { x: 10, y: 5 }, + }).setOrigin(0.5, 1); + + this.updateInterval.subscribe(() => this.update()) + } + + + override update(...args: any[]): void { + + const offers = this.model.offeredGoods; + + + let text = `${this.model.name}`; + if (offers.length > 0) { + text += `\nAngebot: ${offers[0].type}: ${offers[0].amount.toFixed(2)}` + } + + + const request = this.model.requestedGoods; + if (request.length > 0) { + text += `\nNachgefragt: ${request[0].type}: ${request[0].amount.toFixed(2)}` + } + + this.growingIndicator.setText(text); + + if (this.model.isGrowing) { + this.growingIndicator.setColor('green') + } else { + this.growingIndicator.setColor('red') + } + } + +} \ No newline at end of file diff --git a/src/styles.scss b/src/styles.scss index 90d4ee0..07156ea 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -1 +1,11 @@ /* You can add global styles to this file, and also import other style files */ +@use '@angular/material' as mat; + +html { + color-scheme: light dark; + @include mat.theme(( + color: mat.$violet-palette, + typography: Roboto, + density: 0 + )); +} \ No newline at end of file