diff --git a/README.md b/README.md index d5ab2224..999ffe1c 100644 --- a/README.md +++ b/README.md @@ -224,12 +224,18 @@ Here is the structure of a basic JSON for a door: // Physics component (optional) "collider": { "radius": 15, // use "radius" to create a circle. Use "width" and "height" to create a rectangle - "offsetX": -15, // collider offset on X - "offsetY": -30, // collider offset on Y + "x": -15, // collider offset on X + "y": -30, // collider offset on Y "immovable": false, // static or dynamic physic body "dragX": 0.05, // drag on x = "friction"/velocity slowdown speed "dragY": 0.05, // drag on y = "friction"/velocity slowdown speed "collideTilemap": true // enable or disable collision with walls + }, + "trigger": { + "width": 15, // radius is not supported for triggers + "height": 15, + "x": -15, // collider offset on X + "y": -30 // collider offset on Y } } } diff --git a/core/client/components/character.js b/core/client/components/character.js index 42b90021..2b64aea6 100644 --- a/core/client/components/character.js +++ b/core/client/components/character.js @@ -41,6 +41,9 @@ class Character extends Phaser.GameObjects.Container { this.running = false; this.chatCircle = undefined; + this.triggers = {}; + this.lastTriggers = {}; + this.skinPartsContainer = this.scene.add.container(0, 0); this.skinPartsContainer.setScale(3); this.add(this.skinPartsContainer); diff --git a/core/client/entity-manager.js b/core/client/entity-manager.js index a1c41bb9..0aa14783 100644 --- a/core/client/entity-manager.js +++ b/core/client/entity-manager.js @@ -214,6 +214,7 @@ entityManager = { if (nearestEntity) { if (nearestEntity === this.previousNearestEntity) return; + if (nearestEntity.actionType === entityActionType.actionable && nearestEntity.gameObject?.trigger) return; if (!this.previousNearestEntity) { characterPopIns.createOrUpdate( @@ -282,7 +283,7 @@ entityManager = { if (!entity.gameObject) return undefined; let mainSprite; - const { collider, sprite, animations, text } = entity.gameObject; + const { collider, sprite, trigger, animations, text } = entity.gameObject; if (sprite) { mainSprite = this.spawnSpriteFromConfig(sprite); gameObject.add(mainSprite); @@ -296,6 +297,43 @@ entityManager = { // configuration: https://rexrainbow.github.io/phaser3-rex-notes/docs/site/arcade-body/ if (collider) this.spawnColliderFromConfig(gameObject, collider); + + const overlapEnd = () => { + const dbEntitiy = Entities.findOne(gameObject.getData('id')); + if (!dbEntitiy) return; + + if (dbEntitiy.actionType === entityActionType.actionable) { + Entities.update(dbEntitiy._id, { $set: { state: 'off' } }); + } + }; + const overlapStart = user => { + const id = gameObject.getData('id'); + user.triggers[id] = overlapEnd; + + if (user.lastTriggers[id]) return; + + const dbEntitiy = Entities.findOne(id); + if (!dbEntitiy) return; + + if (dbEntitiy.actionType === entityActionType.actionable) { + Entities.update(id, { $set: { state: 'on' } }); + } + }; + + if (trigger && !trigger.radius) { + const user = userManager.getControlledCharacter(); + + // eslint-disable-next-line no-restricted-syntax, guard-for-in + for (const key in trigger) { + trigger[key] *= entity.gameObject.scale ?? 1; + } + + const triggerObject = this.scene.add.rectangle(gameObject.x + trigger.x + trigger.width / 2, gameObject.y + trigger.y + trigger.height / 2, trigger.width, trigger.height); + + this.scene.physics.add.existing(triggerObject); + this.scene.physics.add.overlap(user, triggerObject, overlapStart, null, this); + } + // pickable/loots animations const pickable = entity.actionType === entityActionType.pickable; if (pickable && mainSprite) { diff --git a/core/client/scenes/scene-editor.js b/core/client/scenes/scene-editor.js index 308b3cbd..75157c37 100644 --- a/core/client/scenes/scene-editor.js +++ b/core/client/scenes/scene-editor.js @@ -14,6 +14,34 @@ let isSelecting = false; let selection = {}; let timerResetCopyPaste; +function drawTrigger(collider, gameObject, entity, scale) { + let collision; + if (gameObject.radius) { + // the problem is that the collider is drawn at the center of the entity, but the position is at the top left corner + collision = { + x: gameObject.x + gameObject.radius, + y: gameObject.y + gameObject.radius, + radius: gameObject.radius, + }; + } else { + collision = { x: gameObject.x, y: gameObject.y, w: gameObject.width, h: gameObject.height }; + } + + // eslint-disable-next-line no-restricted-syntax, guard-for-in + for (const key in collision) { + collision[key] *= scale ?? 1; + } + collision.x += entity.x; + collision.y += entity.y; + + if (gameObject.radius) { + collider.strokeCircle(collision.x, collision.y, collision.radius); + collider.fillCircle(collision.x, collision.y, collision.radius); + } else { + collider.strokeRect(collision.x, collision.y, collision.w, collision.h); + collider.fillRect(collision.x, collision.y, collision.w, collision.h); + } +} function compareMouseMovements(currentPosition, lastMousePosition) { return currentPosition.x === lastMousePosition.x && currentPosition.y === lastMousePosition.y; @@ -59,16 +87,16 @@ EditorScene = new Phaser.Class({ Phaser.Scene.call(this, { key: 'EditorScene' }); }, - newEntityCollider() { + newEntityCollider(color = 0xff0000) { const collider = this.add.graphics(); collider.setDefaultStyles({ lineStyle: { width: 2, - color: 0x00ff00, + color, alpha: 1, }, fillStyle: { - color: 0x00ff00, + color, alpha: 0.25, }, }); @@ -98,6 +126,7 @@ EditorScene = new Phaser.Class({ this.marker.setDepth(editorGraphicsDepth); this.entityCollider = {}; + this.entityTrigger = {}; this.areaSelector = this.add.graphics(); this.areaSelector.setDefaultStyles({ @@ -176,6 +205,10 @@ EditorScene = new Phaser.Class({ val.clear(); }); + Object.values(this.entityTrigger).forEach(val => { + val.clear(); + }); + if (this.mode === editorModes.zones) { if (this.input.manager.activePointer.isDown && canvasClicked) this.isMouseDown = true; @@ -477,35 +510,14 @@ EditorScene = new Phaser.Class({ if (entity.gameObject?.collider) { if (!this.entityCollider[entity._id]) this.entityCollider[entity._id] = this.newEntityCollider() const collider = this.entityCollider[entity._id] - if (entity.gameObject.scale < 0) return - - var collision; - if (entity.gameObject.collider.radius) { - // the problem is that the collider is drawn at the center of the entity, but the position is at the top left corner - collision = { - x: entity.gameObject.collider.x + entity.gameObject.collider.radius, - y: entity.gameObject.collider.y + entity.gameObject.collider.radius, - radius: entity.gameObject.collider.radius, - } - } - else { - collision = { x: entity.gameObject.collider.x, y: entity.gameObject.collider.y, w: entity.gameObject.collider.width, h: entity.gameObject.collider.height }; - } - - for (const key in collision) { - collision[key] *= entity.gameObject.scale ?? 1; - } - collision.x += entity.x; - collision.y += entity.y; - - if (entity.gameObject.collider.radius) { - collider.strokeCircle(collision.x, collision.y, collision.radius); - collider.fillCircle(collision.x, collision.y, collision.radius); - } else { - collider.strokeRect(collision.x, collision.y, collision.w, collision.h); - collider.fillRect(collision.x, collision.y, collision.w, collision.h); - } + drawTrigger(collider, entity.gameObject.collider, entity, entity.gameObject.scale); + } + if (entity.gameObject?.trigger && !entity.gameObject.trigger.radius) { + if (!this.entityTrigger[entity._id]) this.entityTrigger[entity._id] = this.newEntityCollider(0x4444aa) + const trigger = this.entityTrigger[entity._id] + if (entity.gameObject.scale < 0) return + drawTrigger(trigger, entity.gameObject.trigger, entity, entity.gameObject.scale); } }); } diff --git a/core/client/user-manager.js b/core/client/user-manager.js index 43290d2e..7e416ba3 100644 --- a/core/client/user-manager.js +++ b/core/client/user-manager.js @@ -269,6 +269,16 @@ userManager = { } else this.controlledCharacter.setAnimationPaused(true); if (moving || this.controlledCharacter.wasMoving) { + const lastTriggersArray = Object.keys(this.controlledCharacter.lastTriggers); + const triggersArray = Object.keys(this.controlledCharacter.triggers); + const outersection = lastTriggersArray.filter(trigger => !triggersArray.includes(trigger)); + for (const trigger of outersection) { + this.controlledCharacter.lastTriggers[trigger](); + } + + this.controlledCharacter.lastTriggers = this.controlledCharacter.triggers; + this.controlledCharacter.triggers = {}; + this.scene.physics.world.update(time, delta); networkManager.sendPlayerNewState(this.controlledCharacter); this.stopInteracting();