typescript Vue 3和Pixi.js:事件侦听器无法处理作为类的属性创建或从父类重写的DisplayObjects

edqdpe6u  于 2023-10-22  发布在  TypeScript
关注(0)|答案(1)|浏览(282)

我在vue 3中使用PIXI js(下面的示例代码)因为大多数图形都遵循类似的模式,具有不同的行为和 prop ,我们选择了使用typescript的OOP路线,以避免代码重复。但是这样做,或者我们无法分辨出什么是错误的,会导致像cannot find propagation path to disconnected target这样的错误。
代码中的注解提供了详细信息
在深入研究之前,
当DisplayObject被创建为类的属性时,或者从父类事件侦听器重写时,错误cannot find propagation path to disconnected target 遇到
更改显示对象的属性也不会导致其重新呈现

<script setup lang="ts">
const main = ref<HTMLElement | null>(null)
  
let visualisationEngine: VisualisationEngine

onMounted(() => {
  if (main.value) {
    visualisationEngine = new VisualisationEngine(main.value!!, window)
    visualisationEngine.init()
  }
})
</script>

VisualisationEngine.ts

import { Application, Graphics } from 'pixi.js'
import '@pixi/unsafe-eval' // usage here because it's an electron app 

import { onEscKeyDown, onPointerDown, onPointerMove, onPointerUp } from '@/visualisation_engine/eventHandlers'
import { Viewport } from 'pixi-viewport'

interface GlobalThis {
  __PIXI_APP__: Application<HTMLCanvasElement>
}

export class VisualisationEngine {
  app: Application<HTMLCanvasElement>
  elem: HTMLElement
  window: Window
  viewport: Viewport

  constructor(elem: HTMLElement, window: Window) {
    this.window = window

    this.app = new Application<HTMLCanvasElement>({
      antialias: true,
      autoDensity: true,
      resizeTo: elem,
      hello: true
    })

    this.viewport = new Viewport({
      ...
    })

    // this.viewport.drag().pinch().wheel().decelerate()
    this.elem = elem

    ;(globalThis as any as GlobalThis).__PIXI_APP__ = this.app //for debugging w/ pixi devtools
  }

  init() {
    this.render()
    const background = this.drawBackground() // event listeners on app.stage didn't run an so this was done as a hack
    this.startEventHandlers(background)
  }

  render() {
    this.elem.appendChild(this.app.view)
    this.app.stage.addChild(this.viewport)
  }

  drawBackground() {
    // using background as a property of the class caused eventListeners to not be fired properly, actually at all
    // error regarding propagation occurs
    const background = new Graphics()
    background.beginFill(0x000000)
    background.drawRect(0, 0, this.elem.clientWidth, this.elem.clientHeight)
    background.endFill()
    background.eventMode = 'static'

    // this.app.stage.addChild(background)
    this.viewport.addChild(background)

    return background
  }


  startEventHandlers(graphic: Graphics) {
    document.addEventListener('keydown', (event) => {
      if (event.key === 'Escape') {
        onEscKeyDown()
      }
    })

    graphic
      .on('pointerdown', (event) => {
        onPointerDown(event, (elem) => this.viewport.addChild(elem))
      })
      .on('pointermove', (event) => {
        onPointerMove(event, (elem) => this.viewport.addChild(elem))
      })
      .on('pointerup', (event) => onPointerUp(event))
  }
}

eventListeners.ts

export const onPointerDown = (event: FederatedPointerEvent, callback: (elem: Graphics) => void) => {
  const mainStore = useMainStore()

  if (mainStore.elementToAdd !== null) {
    mainStore.elementToAdd.position.set(Math.round(event.globalX), Math.round(event.globalY))

    const elem = mainStore.elementToAdd.draw() // 
   /*
   draw draws out the graphics and returns it, this was another hack as returning the whole container caused the events launched from the graphics object    to error
   */
    if (callback) {
      callback(elem)
    }
    mainStore.onScreenElements.push(mainStore.elementToAdd)
    mainStore.elementToAdd = null

    return
  }

  ...

  mainStore.currentlySelectedElement = null
}

export const onPointerUp = (event: FederatedPointerEvent) => {
  const mainStore = useMainStore()

  if (useMainStore().draggingElement !== null) {
    useMainStore().draggingElement = null
  }
}

export const onPointerMove = (event: FederatedPointerEvent, callback: (elem: Graphics) => void) => {
if (useMainStore().draggingElement !== null) {
    useMainStore().draggingElement!!.move(event.globalX, event.globalY) // nothing is updated on screen
    console.log(useMainStore().draggingElement!!.position) // but the x, y of the container changes

    /*
    trying to call element.parent yields null, and errors because it has lost reference to it's parent
    */
  }
}

BaseElement.ts

import { defineStore } from 'pinia'
import { Container, Graphics } from 'pixi.js'
import { useMainStore } from '@/store/main'
import { ref } from 'vue'

export abstract class BaseElement extends Container {
  useStore
  nodes: number[]
  abstract readonly type: string

  id: number = Math.floor(Math.random() * 1000000)
  readonly store = defineStore(`${this.constructor.name}-${this.id}`, () => {
    const name = ref(this.name)
    const value = ref<{ [key: string]: { [key: string]: string } }>({})

    return { name, value }
  })

  protected constructor(x: number = 0, y: number = 0, name: string, nodes: number[]) {
    super()
    this.nodes = nodes

    this.x = x
    this.y = y

    this.useStore = this.store()

    this.useStore.$patch({
      name: name
    })
  }

  abstract draw(): Graphics

  initializeEventListeners(graphic: Graphics) {
    // this function is called in the draw method and the graphic object passed as argument
    graphic.hitArea = graphic.getBounds()
    graphic.eventMode = 'static'

    graphic.on(
      'pointerdown',
      () => {
        if (useMainStore().currentAction === 'move') {
          useMainStore().draggingElement = this
        } else {
          useMainStore().currentlySelectedElement = this
        }
      },
      this
    )

    graphic.on('pointerup', () => {
      console.log('pointer up')
    })
  }

  move(x: number, y: number) {
    this.x = x
    this.y = y
  }
}

样本元素

import { BaseElement } from './BaseElement'
import { Graphics, Point } from 'pixi.js'

export class Resistor extends BaseElement {
  override type = 'Resistor'

  constructor(x: number, y: number, nodes: [number, number], name: string = 'R', resistance: number) {
    super(x, y, name, nodes)
  }

  override draw() {
    
    // plan was to make all graphics properties of the parent class, but doing so just prevent all events from being triggerd
    const resistorGraphic = new Graphics()

    const body = new Graphics()

    resistorGraphic
      .lineStyle(DIMENSIONS.CONDUCTOR_WIDTH, COLORS.CONDUCTOR)
      .moveTo(this.x, this.y)
      ...

    body.addChild(resistorGraphic)

    this.addConductor().forEach((conductor) => {
      body.addChild(conductor)
    })

    this.initializeEventListeners(resistorGraphic)

    return body
  }
}

画布上的元素是如何添加的

用户需要从多个元素中进行选择。在选择时,将使用所选元素创建一个新对象,并将其添加到全局pinia存储示例中,如

mainStore.setElementToAdd(new Resistor(...))

setElementToAdd(element: BaseElement) {
...
state.elementToAdd = element
...
}
tjvv9vkg

tjvv9vkg1#

几周后,我们意识到,对象在创建后被垃圾收集,pinia只存储对象的代理,而不是原始对象。(这是一个假设)
结果是重构使VisualisationEngine类成为单例,所有pixijs对象都存储在该类中
底线是,vue的React系统在这里并不好用

相关问题