Draggable object and drop zone on different scenes interacting with each other?

I have a scene which represents the inventory and then another one which represents the actual game world.

I’d like to drag an item from the inventory zone and drop it onto the game world. The drop zone in the game world should change tint on dragenter/dragleave.

I looked at the examples, like sprite drop zone, but they use this.input which is scene-specific AFAIK.

How can I achieve this in Phaser?

The only solution which comes to my mind is copying the item from the inventory scene to the world scene and then making the player drag the object on the world scene somehow, but perhaps there’s an easier way to implement this. :thinking: Any pointers appreciated!

I think all the input would have to happen in the inventory scene. You could use a dummy drop zone there and emit events for the world scene.

1 Like

Thanks! This is the API that I’ve come up with:

In the scene where I need the player to drop a certain item into a certain area I do this:

this.scene.get('Inventory').makeDropZoneForItem('ironBar', ironBarOutline, {
  dragenter: () => {
    ironBarOutline.setTintFill(0xffffff)
  },
  dragleave: () => {
    ironBarOutline.setTintFill(0xffd700)
  },
  drop: () => {
    this.scene.start('Scene01')
  },
})

then the implementation in the Inventory scene looks like this:

makeDropZoneForItem(itemId, sourceForDropZone, eventHandlers) {
  const sourceCenter = sourceForDropZone.getCenter()
  const zone = this.add.zone(sourceCenter.x, sourceCenter.y, sourceForDropZone.width, sourceForDropZone.height)
    .setInteractive({dropZone: true})
    .setData('itemId', itemId)
    .setScale(1.3) // Make it easier to drop an item on the zone.

  for (const [eventName, eventHandler] of Object.entries(eventHandlers)) {
    const wrappedEventHandler = function(pointer, gameObject, dropZone) {
      // The drop zone is coupled to a specific inventory item.
      const gameObjectMatchesDropZone = gameObject.data.get('itemId') == itemId && dropZone.data &&
        gameObject.data.get('itemId') == dropZone.data.get('itemId')
      if (gameObjectMatchesDropZone) {
        eventHandler(pointer, gameObject, dropZone)
      }
    }

    this.input.on(eventName, wrappedEventHandler)
  }
}

One thing this code doesn’t handle is the fact that sourceForDropZone can be destroyed, because the scene changes on drop. Fortunately, handling this is quite straightforward:

 makeDropZoneForItem(itemId, sourceForDropZone, eventHandlers) {
   const sourceCenter = sourceForDropZone.getCenter()
   const zone = this.add.zone(sourceCenter.x, sourceCenter.y, sourceForDropZone.width, sourceForDropZone.height)
     .setInteractive({dropZone: true})
     .setData('itemId', itemId)
     .setScale(1.3) // Make it easier to drop an item on the zone.
 
   for (const [eventName, eventHandler] of Object.entries(eventHandlers)) {
     const wrappedEventHandler = function(pointer, gameObject, dropZone) {
       // The drop zone is coupled to a specific inventory item.
       const gameObjectMatchesDropZone = gameObject.data.get('itemId') == itemId && dropZone.data &&
         gameObject.data.get('itemId') == dropZone.data.get('itemId')
       if (gameObjectMatchesDropZone) {
         eventHandler(pointer, gameObject, dropZone)
       }
     }
 
     this.input.on(eventName, wrappedEventHandler)
 
+    sourceForDropZone.on('destroy', () => {
+      this.input.off(eventName, wrappedEventHandler)
+    })
   }
 
+  sourceForDropZone.on('destroy', () => {
+    zone.destroy()
+  })
 }

Is there anything I forgot about?

1 Like