clicking, bug avoiding, removing cyclic dependencies, walk randomly

just.... lotsa stuff
This commit is contained in:
Ronja 2020-02-19 23:07:52 +01:00
parent 3259d9545a
commit 93c8bc77b0
31 changed files with 5103 additions and 92 deletions

View file

@ -13,6 +13,7 @@
"typescript": "^3.7.5",
"webpack": "^4.41.5",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.10.2"
"webpack-dev-server": "^3.10.2",
"circular-dependency-plugin": "^5.2.0"
}
}

View file

@ -0,0 +1,10 @@
import { Component } from "ecsy";
import { interaction } from "pixi.js";
export class Clickable extends Component {
actions: { [id: string] : (event: interaction.InteractionEvent) => void } = {}
reset(){
this.actions = {}
}
}

View file

@ -0,0 +1,3 @@
import { TagComponent } from "ecsy";
export class InitializedClickable extends TagComponent {}

View file

@ -4,8 +4,8 @@ import { Path } from "../Datatypes/path";
export class PathWalker extends Component {
path: Path
progress: number
speed: number //speed in pixels per second
progress: number = 0
speed: number = 10 //speed in pixels per second
reset(){
this.path = null

View file

@ -0,0 +1,13 @@
import { Component } from "ecsy";
import { Point } from "../../Datatypes/point";
export class DebugLine extends Component{
color = 0xFF0000
from: Point
to: Point
reset(){
this.color = 0xFF0000
}
}

View file

@ -1,5 +1,5 @@
import { Component } from "ecsy";
import { AABB } from "../Datatypes/aabb";
import { AABB } from "../../Datatypes/aabb";
export class DebugRect extends Component{

View file

@ -1,6 +1,6 @@
import { Texture } from "pixi.js";
import { Component } from "ecsy";
import { Vector } from "../Datatypes/vector";
import { Vector } from "../../Datatypes/vector";
//todo: consider making this a systemstatecomponent so system order isn't as critical for removing sprites
export class SpriteRenderer extends Component{

View file

@ -0,0 +1,4 @@
import { TagComponent } from "ecsy";
export class WalkRandomly extends TagComponent {}

View file

@ -0,0 +1,8 @@
export function clamp(value: number, min:number, max:number) {
return Math.min(Math.max(value, min), max)
};
export function lerp(from: number, to:number, at:number) {
return from + (to - from) * at;
};

View file

@ -1,11 +1,15 @@
import { Point } from "./point";
import { clamp } from "../util";
import { clamp } from "./math";
//todo: consider caching some stuff for performance
export class Path{
points: Point[]
constructor(...points : Point[]){
this.points = points
}
calculateLength(): number{
length = 0
for(let i=1;i<this.points.length;i++){

View file

@ -1,6 +1,6 @@
import { IPoint } from "pixi.js"
import { Vector } from "./vector"
import { lerp } from "../util"
import { lerp } from "./math"
// my own point I can extend however I want to, compatible with pixijs points
export class Point implements IPoint{
@ -17,7 +17,7 @@ export class Point implements IPoint{
}
to(other: Point): Vector{
return new Vector(this.x - other.x, this.y - other.y)
return new Vector(other.x - this.x, other.y - this.y)
}
add(vec: Vector): Point{

View file

@ -0,0 +1,52 @@
import { System, Entity, Not } from "ecsy"
import { PixiRepresentation } from "../Components/rendering/pixiRepresentation";
import { Clickable } from "../Components/clickable";
import { InitializedClickable } from "../Components/initializedClickable";
export class ClickableSystem extends System {
priority = 80
// This method will get called on every frame by default
execute(delta : number) {
// Iterate through all the entities on the query
this.queries.newClickables.results.forEach((entity: Entity) => {
let object = entity.getComponent(PixiRepresentation).value
let clickable = entity.getComponent(Clickable)
object.interactive = true
for(let action in clickable.actions){
object.on(action, clickable.actions[action])
}
entity.addComponent(InitializedClickable)
});
this.queries.changedClickables.changed.forEach((entity: Entity) => {
let object = entity.getComponent(PixiRepresentation).value
let clickable = entity.getComponent(Clickable)
object.removeAllListeners()
for(let action in clickable.actions){
object.on(action, clickable.actions[action])
}
});
this.queries.invisibleClickables.results.forEach((entity: Entity) => {
//remove init clickable tag so it gets initialized again as soon as a pixirepresentation exists again
entity.removeComponent(InitializedClickable)
});
}
static queries = {
newClickables: {
components: [ PixiRepresentation, Clickable, Not(InitializedClickable) ]
},
changedClickables: {
components: [ PixiRepresentation, Clickable, InitializedClickable ],
listen: {
changed: [Clickable]
}
},
invisibleClickables: {
components: [ Clickable, InitializedClickable, Not(PixiRepresentation) ],
}
}
queries: any
}

View file

@ -1,8 +1,8 @@
import { System, Entity, Not } from "ecsy"
import { DebugRect } from "../Components/debugRect";
import { PixiRepresentation } from "../Components/pixiRepresentation";
import { DebugRect } from "../Components/rendering/debugRect";
import { PixiRepresentation } from "../Components/rendering/pixiRepresentation";
import { Graphics, DisplayObject } from "pixi.js";
import { app } from "..";
import globals from "../globals";
// MovableSystem
export class DebugRenderSystem extends System {
@ -16,7 +16,7 @@ export class DebugRenderSystem extends System {
let graphic = new Graphics()
graphic.lineStyle(1, debugRect.color, 0.5)
graphic.drawRect(debugRect.rect.source.x, debugRect.rect.source.y, debugRect.rect.size.x, debugRect.rect.size.y)
app.stage.addChild(graphic)
globals.app.stage.addChild(graphic)
entity.addComponent(PixiRepresentation, <PixiRepresentation>{value: <DisplayObject>graphic})
})

View file

@ -1,21 +1,20 @@
import { System, Entity } from "ecsy"
import { System, Entity, Not } from "ecsy"
import { Position } from "../Components/position";
import { Door } from "../Components/door";
import { SpriteRenderer } from "../Components/spriteRenderer";
import { SpriteRenderer } from "../Components/rendering/spriteRenderer";
import { IPoint, Texture } from "pixi.js";
import { addOrSetComponent } from "../util";
// MovableSystem
export class DoorSystem extends System {
// This method will get called on every frame by default
execute(delta : number) {
// Iterate through all the entities on the query
this.queries.newDoors.added.forEach((entity: Entity) => {
this.queries.newDoors.results.forEach((entity: Entity) => {
let door = entity.getComponent(Door)
let doorOffset: IPoint = door.open ? door.openOffset : door.closedOffset
let doorTex: Texture = door.open ? door.openTex : door.closedTex
addOrSetComponent(entity, SpriteRenderer, <SpriteRenderer>{texture: doorTex, offset: doorOffset})
entity.addComponent(SpriteRenderer, <SpriteRenderer>{texture: doorTex, offset: doorOffset})
})
this.queries.changedDoors.changed.forEach((entity: Entity) => {
@ -29,10 +28,7 @@ export class DoorSystem extends System {
static queries = {
newDoors: {
components: [ Door ],
listen: {
added: true,
}
components: [ Door, Not(SpriteRenderer)]
},
changedDoors: {
components: [ Door, Position, SpriteRenderer ],

View file

@ -5,6 +5,7 @@ import { Position } from "../Components/position";
// MovableSystem
export class PathWalkerSystem extends System {
priority = 50
// This method will get called on every frame by default
execute(delta : number) {
// Iterate through all the entities on the query

View file

@ -1,8 +1,8 @@
import { PixiRepresentation } from "../Components/pixiRepresentation";
import { PixiRepresentation } from "../Components/rendering/pixiRepresentation";
import { Not, Entity, System } from "ecsy";
import { DebugRect } from "../Components/debugRect";
import { SpriteRenderer } from "../Components/spriteRenderer";
import { app } from "..";
import { DebugRect } from "../Components/rendering/debugRect";
import { SpriteRenderer } from "../Components/rendering/spriteRenderer";
import globals from "../globals";
export class PixiCleanupSystem extends System {
@ -12,7 +12,7 @@ export class PixiCleanupSystem extends System {
// This method will get called on every frame by default
execute(delta : number) {
this.queries.oldRepresentations.results.forEach((entity:Entity) => {
app.stage.removeChild(entity.getComponent(PixiRepresentation).value)
globals.app.stage.removeChild(entity.getComponent(PixiRepresentation).value)
entity.removeComponent(PixiRepresentation)
entity.remove()
});

View file

@ -0,0 +1,28 @@
import { System, Entity, Not } from "ecsy"
import { PathWalker } from "../Components/pathWalker"
import { Position } from "../Components/position";
import { WalkRandomly } from "../Components/walkRandomly";
import { roomBounds } from "../constants";
import { Path } from "../Datatypes/path";
export class RandomWalkSystem extends System {
priority = 0
// This method will get called on every frame by default
execute(delta : number) {
// Iterate through all the entities on the query
this.queries.walkers.results.forEach((entity: Entity) => {
var pos = entity.getComponent(Position).value
var target = roomBounds.randomPoint()
var path = new Path(pos, target)
entity.addComponent(PathWalker, <PathWalker>{path: path, speed: 10})
});
}
static queries = {
walkers: {
components: [ WalkRandomly, Position, Not(PathWalker) ]
}
}
queries: any
}

View file

@ -1,5 +1,5 @@
import { System } from "ecsy"
import { app } from "..";
import globals from "../globals";
// MovableSystem
export class RenderSystem extends System {
@ -7,6 +7,6 @@ export class RenderSystem extends System {
// This method will get called on every frame by default
execute(_delta : number) {
app.renderer.render(app.stage);
globals.app.renderer.render(globals.app.stage);
}
}

View file

@ -1,14 +1,14 @@
import { System, Entity } from "ecsy"
import { System, Entity } from "ecsy";
import { Position } from "../Components/position";
import { SpriteRenderer } from "../Components/spriteRenderer";
import { app } from "..";
import { SpriteRenderer } from "../Components/rendering/spriteRenderer";
import { Sprite, DisplayObject } from "pixi.js";
import { PixiRepresentation } from "../Components/pixiRepresentation";
import { PixiRepresentation } from "../Components/rendering/pixiRepresentation";
import { addOrSetComponent } from "../util";
import globals from "../globals";
// MovableSystem
export class SpriteSystem extends System {
priority: 90
priority: 70
// This method will get called on every frame by default
execute(delta : number) {
@ -21,11 +21,13 @@ export class SpriteSystem extends System {
let pos = entity.getComponent(Position)
let sprite = new Sprite(renderer.texture)
sprite.position = pos.value.add(renderer.offset)
app.stage.addChild(sprite)
globals.app.stage.addChild(sprite)
addOrSetComponent(entity, PixiRepresentation, <PixiRepresentation>{value: <DisplayObject>sprite})
})
this.queries.sprites.changed.forEach((entity: Entity) => {
if(!(entity as any).alive)
return
let renderer = entity.getComponent(SpriteRenderer)
let pos = entity.getComponent(Position)
let object = entity.getComponent(PixiRepresentation).value as Sprite

View file

@ -1,29 +1,34 @@
import { System, Entity } from "ecsy"
import { world } from "..";
import { Human } from "../Components/human";
import { Name } from "../Components/name";
import { Appearance } from "../Components/appearance";
import { Loader } from "pixi.js";
import { InCabin } from "../Components/inCabin";
import { Position } from "../Components/position";
import { OrderZ } from "../Components/orderZ";
import { OrderZ } from "../Components/rendering/orderZ";
import { AABB } from "../Datatypes/aabb";
import { System, Entity } from "ecsy";
import globals from "../globals";
// MovableSystem
export class TestSystem extends System {
priority = -100
// This method will get called on every frame by default
execute(delta : number) {
//this.removeHumans(this.queries.humans, 2)
//this.addHumans(2)
}
for(let i=0;i<1;i++){
let ent: Entity = this.queries.humans.results[i]
removeHumans(query: {results: Entity[]}, amount: number): void{
for(let i=0;i<amount;i++){
let ent = query.results[i]
ent.remove()
}
}
addHumans(amount: number): void{
let roomBounds = new AABB(10, 17, 62, 56)
let resources = Loader.shared.resources;
for(let i=0;i<1;i++){
world.createEntity()
let resources = Loader.shared.resources
for(let i=0;i<amount;i++){
globals.world.createEntity()
.addComponent(Human)
.addComponent(Name, <Name>{first: "Sarah", last:"Lee"})
.addComponent(Appearance, <Appearance>{idleTexture: resources["Human"].texture}) //Todo: generate appearance from body traits instead?

View file

@ -3,7 +3,7 @@ import { Human } from "../Components/human"
import { Appearance } from "../Components/appearance"
import { InCabin } from "../Components/inCabin"
import { Position } from "../Components/position"
import { SpriteRenderer } from "../Components/spriteRenderer"
import { SpriteRenderer } from "../Components/rendering/spriteRenderer"
import { Vector } from "../Datatypes/vector"
// MovableSystem

View file

@ -1,7 +1,7 @@
import { PixiRepresentation } from "../Components/pixiRepresentation";
import { PixiRepresentation } from "../Components/rendering/pixiRepresentation";
import { Entity, System } from "ecsy";
import { Position } from "../Components/position";
import { OrderZ } from "../Components/orderZ";
import { OrderZ } from "../Components/rendering/orderZ";
export class ZOrderSystem extends System {
@ -17,6 +17,8 @@ export class ZOrderSystem extends System {
});
this.queries.objects.changed.forEach((entity:Entity) => {
if(!(entity as any).alive)
return
let representation = entity.getComponent(PixiRepresentation).value
let order = entity.getComponent(OrderZ)
let pos = entity.getComponent(Position).value

View file

@ -1,4 +1,45 @@
export const NUM_ELEMENTS = 60
export const SPEED_MULTIPLIER = 1
export const SHAPE_SIZE = 5
export const SHAPE_HALF_SIZE = SHAPE_SIZE / 2
import { AABB } from "./Datatypes/aabb"
export const canvasWidth = 81
export const canvasHeight = 144
export const roomBounds = new AABB(10, 17, 62, 56)
export const FirstNames = [
"Jules",
"Levi",
"Sarah",
"Simon",
"Ronja",
"Marko",
"Wachter"
]
export const LastNames = [
"Adams",
"Baker",
"Clark",
"Davis",
"Evans",
"Frank",
"Ghosh",
"Hills",
"Irwin",
"Jones",
"Klein",
"Lopez",
"Mason",
"Nalty",
"Ochoa",
"Patel",
"Quinn",
"Reily",
"Smith",
"Trott",
"Usman",
"Valdo",
"White",
"Xiang",
"Yakub",
"Zafar",
]

7
Program/src/globals.ts Normal file
View file

@ -0,0 +1,7 @@
import { World } from "ecsy";
import { Application } from "pixi.js";
export default {
world: <World>null,
app: <Application>null,
}

View file

@ -1,5 +1,4 @@
import { Application, Ticker, settings, SCALE_MODES } from "pixi.js"
import { World } from "ecsy"
import { setup } from "./setup"
import { loadResources } from "./Resources"
@ -13,18 +12,22 @@ import { VisibleHumanSystem } from "./Systems/VisibleHumanSystem"
import { DebugRenderSystem } from "./Systems/DebugRenderSystem"
import { PixiCleanupSystem } from "./Systems/PixiCleanupSystem"
import { ZOrderSystem } from "./Systems/ZOrderSystem"
import { PathWalkerSystem } from "./Systems/PathWalkerSystem"
import { RandomWalkSystem } from "./Systems/RandomWalkSystem"
import { World } from "ecsy"
import globals from "./globals"
import { canvasWidth, canvasHeight } from "./constants"
import { ClickableSystem } from "./Systems/ClickableSystem"
// Initialize pixi
export let canvasWidth = 81
export let canvasHeight = 144
export const app = new Application({
globals.app = new Application({
width: canvasWidth,
height: canvasHeight,
backgroundColor: 0x000000,
resolution: 1,
antialias: false,
})
document.body.appendChild(app.view)
document.body.appendChild(globals.app.view)
settings.SCALE_MODE = SCALE_MODES.NEAREST
settings.ROUND_PIXELS = true
@ -35,19 +38,22 @@ window.addEventListener( 'resize', recalculateSize, false )
function recalculateSize(){
let multiplier = Math.min((window.innerWidth/canvasWidth)|0, (window.innerHeight/canvasHeight)|0)
app.view.style.width = canvasWidth * multiplier + "px"
app.view.style.height = canvasHeight * multiplier + "px"
globals.app.view.style.width = canvasWidth * multiplier + "px"
globals.app.view.style.height = canvasHeight * multiplier + "px"
}
// Create world and register the systems on it
//we could also register components here but they should get automatically registered once used for the first time
export let world = new World()
world
globals.world = new World()
globals.world
.registerSystem(TestSystem) //prio -100
.registerSystem(AdventureReturnSystem) //prio -100
.registerSystem(DoorSystem) //prio 0
.registerSystem(RandomWalkSystem) //prio 0
.registerSystem(PathWalkerSystem) //prio 50
.registerSystem(VisibleHumanSystem) //prio 50
.registerSystem(SpriteSystem) //prio 90
.registerSystem(SpriteSystem) //prio 70
.registerSystem(ClickableSystem) //prio 80
.registerSystem(DebugRenderSystem) //prio 90
.registerSystem(ZOrderSystem) //prio 98
.registerSystem(PixiCleanupSystem) //prio 99
@ -63,6 +69,6 @@ function init(){
Ticker.shared.add((delta : number) => {
let time = performance.now()
// Run all the systems
world.execute(delta, time)
globals.world.execute(delta/100, time)
});
}

View file

@ -1,14 +1,11 @@
import { Loader, Sprite } from "pixi.js";
import { app, world } from ".";
import { Door } from "./Components/door";
import { Human } from "./Components/human";
import { Name } from "./Components/name";
import { InCabin } from "./Components/inCabin";
import { Appearance } from "./Components/appearance";
import { DebugRect } from "./Components/rendering/debugRect";
import { roomBounds } from "./constants";
import globals from "./globals";
import { createRandomHuman } from "./util";
import { Position } from "./Components/position";
import { AABB } from "./Datatypes/aabb";
import { DebugRect } from "./Components/debugRect";
import { OrderZ } from "./Components/orderZ";
import { Point } from "./Datatypes/point";
export function setup(){
@ -16,28 +13,23 @@ export function setup(){
//base sprites without entity representation
const bgTex = new Sprite(resources["Background"].texture)
app.stage.addChild(bgTex)
globals.app.stage.addChild(bgTex)
//start entities
//door
world.createEntity()
globals.world.createEntity()
.addComponent(Position, <Position>{value: new Point(38, 2)})
.addComponent(Door, <Door>{open: true,
openOffset: {x:38, y:2}, openTex: resources["Door"].spritesheet.textures[0],
closedOffset: {x:38, y:2}, closedTex: resources["Door"].spritesheet.textures[1]})
openOffset: {x:0, y:0}, openTex: resources["Door"].spritesheet.textures[0],
closedOffset: {x:0, y:0}, closedTex: resources["Door"].spritesheet.textures[1]})
//debug room bounds
globals.world.createEntity()
.addComponent(DebugRect, <DebugRect>{color:0x0000FF, rect: roomBounds})
//example humans
//TODO delete those
let roomBounds = new AABB(10, 17, 62, 56)
world.createEntity()
.addComponent(DebugRect, <DebugRect>{color:0x0000FF, rect: roomBounds})
for(let i=0;i<10;i++)
world.createEntity()
.addComponent(Human)
.addComponent(Name, <Name>{first: "Sarah", last:"Lee"})
.addComponent(Appearance, <Appearance>{idleTexture: resources["Human"].texture}) //Todo: generate appearance from body traits instead?
.addComponent(InCabin)
.addComponent(Position, <Position>{value: roomBounds.randomPoint()})
.addComponent(OrderZ)
createRandomHuman(globals.world)
}

View file

@ -1,4 +1,14 @@
import { Entity, ComponentConstructor, Component } from "ecsy"
import { Entity, ComponentConstructor, Component, World } from "ecsy"
import { Loader, interaction } from "pixi.js"
import { Human } from "./Components/human"
import { Name } from "./Components/name"
import { Appearance } from "./Components/appearance"
import { InCabin } from "./Components/inCabin"
import { OrderZ } from "./Components/rendering/orderZ"
import { WalkRandomly } from "./Components/walkRandomly"
import { Position } from "./Components/position"
import { roomBounds } from "./constants"
import { Clickable } from "./Components/clickable"
export function addOrSetComponent(entity: Entity, Component: ComponentConstructor<Component>, values: any) {
@ -8,7 +18,7 @@ export function addOrSetComponent(entity: Entity, Component: ComponentConstructo
if(component.copy){
component.copy(values)
} else {
for (var name in values) {
for (let name in values) {
component[name]= values[name]
}
}
@ -18,10 +28,22 @@ export function addOrSetComponent(entity: Entity, Component: ComponentConstructo
}
}
export function clamp(value: number, min:number, max:number) {
return Math.min(Math.max(value, min), max)
};
export function randomArrayValue(array: any[]):any{
let randomIndex = Math.random() * array.length
return array[randomIndex]
}
export function lerp(from: number, to:number, at:number) {
return from + (to - from) * at;
};
export function createRandomHuman(world: World){
let resources = Loader.shared.resources;
let entity = world.createEntity()
entity
.addComponent(Human)
.addComponent(Name, <Name>{first: "mary", last:"sue"})
.addComponent(Appearance, <Appearance>{idleTexture: resources["Human"].texture}) //Todo: generate appearance from body traits instead?
.addComponent(InCabin)
.addComponent(Position, <Position>{value: roomBounds.randomPoint()})
.addComponent(OrderZ)
.addComponent(WalkRandomly)
//todo: change this into selecing the human instead of killing it...
.addComponent(Clickable, { actions: { "click": (event: interaction.InteractionEvent) => {entity.remove();} }})
}

View file

@ -1,5 +1,6 @@
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CircularDependencyPlugin = require('circular-dependency-plugin')
module.exports = {
context: __dirname,
@ -22,5 +23,18 @@ module.exports = {
template: "index.html",
title: "Cabin Game",
}),
new CircularDependencyPlugin({
// exclude detection of files based on a RegExp
exclude: /a\.js|node_modules/,
// include specific files based on a RegExp
include: /src/,
// add errors to webpack instead of warnings
failOnError: true,
// allow import cycles that include an asyncronous import,
// e.g. via import(/* webpackMode: "weak" */ './file.js')
allowAsyncCycles: false,
// set the current working directory for displaying module paths
cwd: process.cwd(),
})
],
}

4800
Program/yarn.lock Normal file

File diff suppressed because it is too large Load diff