diff --git a/package-lock.json b/package-lock.json index 694e3bc..3531042 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "react-feather": "^2.0.9", "stylus": "^0.54.7", "stylus-loader": "^6.0.0", + "tslib": "^2.4.0", "typescript": "^4.1.3", "webpack": "^5.37.1" }, @@ -8338,9 +8339,9 @@ } }, "node_modules/tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" }, "node_modules/tsutils": { "version": "3.21.0", @@ -15157,9 +15158,9 @@ } }, "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" }, "tsutils": { "version": "3.21.0", diff --git a/package.json b/package.json index 093f97f..27e0599 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "react-feather": "^2.0.9", "stylus": "^0.54.7", "stylus-loader": "^6.0.0", + "tslib": "^2.4.0", "typescript": "^4.1.3", "webpack": "^5.37.1" }, diff --git a/public/assets/city/background.png b/public/assets/city/background.png new file mode 100644 index 0000000..e5589b7 Binary files /dev/null and b/public/assets/city/background.png differ diff --git a/public/assets/fonts/aileron/Aileron-UltraLightItalic.woff b/public/assets/fonts/aileron/Aileron-UltraLightItalic.woff deleted file mode 100644 index aee19d3..0000000 Binary files a/public/assets/fonts/aileron/Aileron-UltraLightItalic.woff and /dev/null differ diff --git a/src/GameEngine/2D/Debug/PointDebugger.ts b/src/GameEngine/2D/Debug/PointDebugger.ts index 8ad2bce..4f4dcfb 100644 --- a/src/GameEngine/2D/Debug/PointDebugger.ts +++ b/src/GameEngine/2D/Debug/PointDebugger.ts @@ -5,9 +5,9 @@ import Vector2D from '../Vector2D' export default class PointDebugger extends Component2D { public constructor(point: Vector2D, color = 'red') { super() - this.scale = new Vector2D(.1, .1) + this.scale = new Vector2D(1, 1) this.position = point - console.log('Debugging point at location', point) + // console.log('Debugging point at location', point) // this.origin = component.origin this.renderer = new RectRenderer(this, {material: color}) } diff --git a/src/GameEngine/2D/Vector2D.ts b/src/GameEngine/2D/Vector2D.ts index 6f2af76..9ef136d 100644 --- a/src/GameEngine/2D/Vector2D.ts +++ b/src/GameEngine/2D/Vector2D.ts @@ -25,10 +25,24 @@ export default class Vector2D { ) } + public div(v: number): Vector2D { + return new Vector2D( + this.x / v, + this.y / v + ) + } + public isIn(topLeft: Vector2D, bottomRight: Vector2D): boolean { return this.x >= topLeft.x && this.y >= topLeft.y && this.x <= bottomRight.x && this.y <= bottomRight.y } + + public decimalCount(nDecimal: number) { + return new Vector2D( + parseFloat(this.x.toFixed(nDecimal)), + parseFloat(this.y.toFixed(nDecimal)) + ) + } } diff --git a/src/GameEngine/Renderer/RectRenderer.ts b/src/GameEngine/Renderer/RectRenderer.ts index c92e404..3126ed6 100644 --- a/src/GameEngine/Renderer/RectRenderer.ts +++ b/src/GameEngine/Renderer/RectRenderer.ts @@ -6,13 +6,13 @@ import Renderer from '.' interface Params { material?: string | Asset - stroke?: string + stroke?: string | {color: string, width: number} } export default class RectRenderer extends Renderer implements Params { public material?: string | Asset - public stroke?: string + public stroke?: string | {color: string, width: number} public constructor(component: Component2D, params?: Params) { super(component) @@ -45,7 +45,12 @@ export default class RectRenderer extends Renderer implements Params { ctx.fillRect(...item) } if (this.stroke) { - ctx.strokeStyle = this.stroke + if (typeof this.stroke === 'string') { + ctx.strokeStyle = this.stroke + } else { + ctx.strokeStyle = this.stroke.color + ctx.lineWidth = this.stroke.width + } ctx.strokeRect(...item) } } diff --git a/src/GameEngine/Renderer/TextRenderer.ts b/src/GameEngine/Renderer/TextRenderer.ts index 16a0e6e..539951a 100644 --- a/src/GameEngine/Renderer/TextRenderer.ts +++ b/src/GameEngine/Renderer/TextRenderer.ts @@ -11,6 +11,8 @@ export default class TextRenderer extends Renderer { public text?: string public size?: number + public weight?: 'bold' + public color?: string public constructor(component: Component2D, params?: Params) { super(component) @@ -31,10 +33,11 @@ export default class TextRenderer extends Renderer { // console.log if (this.text) { - ctx.fillStyle = 'black' - ctx.textBaseline = 'top' + ctx.fillStyle = this.color ?? 'black' + ctx.textBaseline = 'middle' + ctx.textAlign = 'center' - ctx.font = `${size}px sans-serif` + ctx.font = `${this.weight ? `${this.weight} ` : ''}${size + (this.size ?? 0)}px sans-serif` ctx.fillText(this.text, ...item) } } diff --git a/src/GameEngine/Renderer/index.ts b/src/GameEngine/Renderer/index.ts index 2dabad6..54cd108 100644 --- a/src/GameEngine/Renderer/index.ts +++ b/src/GameEngine/Renderer/index.ts @@ -10,7 +10,11 @@ export default abstract class Renderer { protected getPosition(): Vector2D { const ge = GameEngine.getGameEngine() - const realPosition = ge.currentScene!.camera.topLeft.sum(this.component.position) + const realPosition = ge.currentScene?.camera.topLeft.sum(this.component.position) + if (!realPosition) { + console.error('no camera?!?') + return this.component.position + } return new Vector2D( realPosition.x - this.component.scale.x / 2 - this.component.origin.x, realPosition.y - this.component.scale.y / 2 - this.component.origin.y diff --git a/src/GameEngine/Scene.ts b/src/GameEngine/Scene.ts index f4b6e81..70d5ad7 100644 --- a/src/GameEngine/Scene.ts +++ b/src/GameEngine/Scene.ts @@ -1,5 +1,4 @@ import GameEngine from 'GameEngine' -import AssetsManager from './Asset' import Camera from './Components/Camera' import Component2D, { ComponentState } from './Component2D' @@ -13,6 +12,7 @@ export default class Scene { private components: Array = [] private ge!: GameEngine + private hasClickedComponent: number | undefined public constructor(sceneId: string) { @@ -37,8 +37,9 @@ export default class Scene { } public async update() { - for (const component of this.components) { - await this.updateComponent(component) + for (let index = 0; index < this.components.length; index++) { + const component = this.components[index]; + await this.updateComponent(component, index) } } @@ -48,7 +49,7 @@ export default class Scene { } } - private async updateComponent(v: Component2D) { + private async updateComponent(v: Component2D, index: number) { const debug = v.debug if (debug) { console.log('Processing Component', v) @@ -56,11 +57,20 @@ export default class Scene { const state: Partial = {} // const width = (v.width() ?? 1) * this.ge.caseSize[0] // const height = (v.height() ?? 1) * this.ge.caseSize[1] - if (v.collider && v.collider.type === 'click' && this.ge.cursor.isDown && !this.ge.cursor.wasDown) { + if (v.collider && v.collider.type === 'click' && (this.hasClickedComponent === index || !this.hasClickedComponent)) { if (v.collider.pointColliding(this.ge.cursor.position, 'click')) { - state.isColliding = 'click' + if (this.ge.cursor.isDown && !this.ge.cursor.wasDown) { + state.isColliding = 'click' + this.hasClickedComponent = index + } else if (this.ge.cursor.isDown) { + state.isColliding = 'down' + this.hasClickedComponent = index + } } } + if (this.hasClickedComponent === index && !state.isColliding) { + this.hasClickedComponent = undefined + } // if (v.pos) { // const ax = v.pos.x * this.ge.caseSize[0] // const ay = v.pos.y * this.ge.caseSize[1] diff --git a/src/GameEngine/index.ts b/src/GameEngine/index.ts index 5f890a4..e593201 100644 --- a/src/GameEngine/index.ts +++ b/src/GameEngine/index.ts @@ -93,9 +93,11 @@ export default class GameEngine { } }) document.addEventListener('mousedown', () => { + console.log('cursor down') this.cursor.isDown = true }) document.addEventListener('mouseup', () => { + console.log('cursor up') this.cursor.isDown = false this.cursor.wasDown = false }) diff --git a/src/games/city/Cursor.ts b/src/games/city/Cursor.ts new file mode 100644 index 0000000..b07b92d --- /dev/null +++ b/src/games/city/Cursor.ts @@ -0,0 +1,27 @@ +import { stat } from 'fs' +import GameEngine from 'GameEngine' +import BoxCollider2D from 'GameEngine/2D/Collision/BoxCollider2D' +import Vector2D from 'GameEngine/2D/Vector2D' +import Component2D, { ComponentState } from 'GameEngine/Component2D' +import Renderer from 'GameEngine/Renderer' +import RectRenderer from 'GameEngine/Renderer/RectRenderer' + +export default class Cursor extends Component2D { + + public renderer: RectRenderer = new RectRenderer(this) + public collider: BoxCollider2D = new BoxCollider2D(this, 'click') + + public position: Vector2D = new Vector2D(0, 0) + + public scale: Vector2D = new Vector2D(1, 1) + + // public origin: Vector2D = new Vector2D(0.5, 0.5) + + public update(state: ComponentState): void | Promise { + // state.mouseHovering + + this.renderer.material = 'blue' + const cursor = GameEngine.getGameEngine().cursor + this.position = cursor.position + } +} diff --git a/src/games/city/Space.ts b/src/games/city/Space.ts new file mode 100644 index 0000000..a8ea516 --- /dev/null +++ b/src/games/city/Space.ts @@ -0,0 +1,86 @@ +import { stat } from 'fs' +import GameEngine from 'GameEngine' +import BoxCollider2D from 'GameEngine/2D/Collision/BoxCollider2D' +import ColliderDebugger from 'GameEngine/2D/Debug/ColliderDebugger' +import PointDebugger from 'GameEngine/2D/Debug/PointDebugger' +import Vector2D from 'GameEngine/2D/Vector2D' +import Component2D, { ComponentState } from 'GameEngine/Component2D' +import Renderer from 'GameEngine/Renderer' +import RectRenderer from 'GameEngine/Renderer/RectRenderer' +import Cursor from './Cursor' +import TextComponent from './TextComponent' + +export default class Space extends Component2D { + private static isOnCursor = false + + public size = { + width: 3, height: 1 + } + + + + public renderer: RectRenderer = new RectRenderer(this) + public collider: BoxCollider2D = new BoxCollider2D(this, 'click') + + public position: Vector2D = new Vector2D(0, 0) + + public scale: Vector2D = new Vector2D(30, 10) + + private posBeforeCursor: Vector2D | null = null + public constructor( + size: Vector2D, + private cursor: Cursor, + private placeableRects: Array<[Vector2D, Vector2D]>, + private log: boolean = false + ) { + super() + // this.debug = true + this.scale = size + + const text = `${size.x}x${size.y}` + this.childs = [ + new TextComponent(this, text, 'bold', 16, 'blue') + ] + } + + public update(state: ComponentState): void | Promise { + // state.mouseHovering + const point = this.scale.div(2).sub(this.position) + + this.renderer.material = 'white' + this.renderer.stroke = {color: 'black', width: 3} + const cursor = GameEngine.getGameEngine().cursor + // if (this.log) console.log(point) + if (state.isColliding === 'click' || state.isColliding === 'down') { + this.renderer.stroke = {color: 'green', width: 3} + if (!this.posBeforeCursor) { + this.posBeforeCursor = this.position + } + Space.isOnCursor = true + // console.log('follow cursor', cursor.position, this.position) + this.position = cursor.position.decimalCount(0) + let canPlace = false + for (const placeableRect of this.placeableRects) { + if (point.isIn(placeableRect[0], this.scale.sub(placeableRect[1]))) { + canPlace = true + break + } + } + if (!canPlace) this.renderer.stroke = {color: 'red', width: 3} + else this.posBeforeCursor = this.position + } else if (this.posBeforeCursor) { + let canPlace = false + for (const placeableRect of this.placeableRects) { + if (point.isIn(placeableRect[0], this.scale.sub(placeableRect[1]))) { + canPlace = true + break + } + } + if (!canPlace) { + this.position = this.posBeforeCursor + } + Space.isOnCursor = false + this.posBeforeCursor = null + } + } +} diff --git a/src/games/city/TextComponent.ts b/src/games/city/TextComponent.ts new file mode 100644 index 0000000..328a343 --- /dev/null +++ b/src/games/city/TextComponent.ts @@ -0,0 +1,31 @@ +import { stat } from 'fs' +import GameEngine from 'GameEngine' +import BoxCollider2D from 'GameEngine/2D/Collision/BoxCollider2D' +import ColliderDebugger from 'GameEngine/2D/Debug/ColliderDebugger' +import PointDebugger from 'GameEngine/2D/Debug/PointDebugger' +import Vector2D from 'GameEngine/2D/Vector2D' +import Component2D, { ComponentState } from 'GameEngine/Component2D' +import Renderer from 'GameEngine/Renderer' +import RectRenderer from 'GameEngine/Renderer/RectRenderer' +import TextRenderer from 'GameEngine/Renderer/TextRenderer' +import Cursor from './Cursor' + +export default class TextComponent extends Component2D { + + + public renderer: TextRenderer = new TextRenderer(this) + + // public position: Vector2D = new Vector2D(0, 0) + + public constructor(private parent: Component2D, text: string, weight?: 'bold', size?: number, color?: string) { + super() + this.renderer.text = text + this.renderer.weight = weight + this.renderer.size = size + this.renderer.color = color + } + + public update(state: ComponentState): void | Promise { + this.position = this.parent.position + } +} diff --git a/src/pages/city/index.tsx b/src/pages/city/index.tsx new file mode 100644 index 0000000..a0552df --- /dev/null +++ b/src/pages/city/index.tsx @@ -0,0 +1,91 @@ +/* eslint-disable max-classes-per-file */ +import React from 'react' +import { Text, Link } from '@dzeio/components' +import GameEngine from 'GameEngine' +import Scene from 'GameEngine/Scene' +import Item from 'games/tictactoe/Item' +import Line from 'games/tictactoe/Line' +import Start from 'games/tictactoe/Menu/Start' +import FPSCounter from 'GameEngine/Components/FPSCounter' +import ComponentDebug from 'GameEngine/2D/Debug/ComponentDebug' +import Tile from 'games/city/Tile' +import Space from 'games/city/Space' +import Vector2D from 'GameEngine/2D/Vector2D' +import Cursor from 'games/city/Cursor' + +export default class Snake extends React.PureComponent { + + public async componentDidMount() { + const ge = new GameEngine('#test', { + caseCount: [208,104], + debugColliders: true, + goalFramerate: 60 + }) + const mainScene = new Scene('Menu') + mainScene.camera.topLeft.x = 0 + mainScene.camera.topLeft.y = 0 + const cursor = new Cursor() + + // for (let ch = 0; ch < 104; ch++) { + // for (let cw = 0; cw < 208; cw++) { + // mainScene.addComponent( + // new Tile(cw, ch) + // ) + // } + // } + + const placeableRects: Array<[Vector2D, Vector2D]> = [ + // top rect + [new Vector2D(0, 0), new Vector2D(208, 18)], + + // left recet + [new Vector2D(20, 28), new Vector2D(167, 79)], + + // right rect + [new Vector2D(178,28), new Vector2D(208, 79)], + // bottom rect + [new Vector2D(0, 89), new Vector2D(208, 104)], + + + ] + + for (let i = 0; i < 10; i++) { + const width = i % 2 === 0 ? 20 : 10 + const height = i % 2 === 0 ? 10 : 20 + const baseX = width / 2 + 1 + const baseY = i % 2 === 0 ? 50 : 70 + + const it = new Space(new Vector2D(width, height), cursor, placeableRects) + it.position.x = baseX + it.position.y = baseY + // it.debug = i === 0 + mainScene.addComponent( + it + ) + } + + // mainScene.addComponent( + // // cursor, + // new Space(new Vector2D(20, 10), cursor, placeableRects, true) + // ) + + await ge.setScene(mainScene) + ge.start() + } + + + public render = () => ( + <> + + + + + + ) + + private randomInt(min: number, max: number): number { + min = Math.ceil(min) + max = Math.floor(max) + return Math.floor(Math.random() * (max - min + 1)) + min + } +}