feat: interactions
This commit is contained in:
parent
bb87f553fc
commit
43a6b12488
37
app/ecs-components/interactive.js
Normal file
37
app/ecs-components/interactive.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
import Component from '@/ecs/component.js';
|
||||
|
||||
export default class Interactive extends Component {
|
||||
instanceFromSchema() {
|
||||
const {ecs} = this;
|
||||
return class ControlledInstance extends super.instanceFromSchema() {
|
||||
interact(initiator) {
|
||||
this.interactScriptInstance.context.initiator = initiator;
|
||||
const {Ticking} = ecs.get(this.entity);
|
||||
Ticking.addTickingPromise(this.interactScriptInstance.tickingPromise());
|
||||
}
|
||||
get interacting() {
|
||||
return !!this.$$interacting;
|
||||
}
|
||||
set interacting(interacting) {
|
||||
this.$$interacting = interacting ? 1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
async load(instance) {
|
||||
// heavy handed...
|
||||
if ('undefined' !== typeof window) {
|
||||
return;
|
||||
}
|
||||
instance.interactScriptInstance = await this.ecs.readScript(
|
||||
instance.interactScript,
|
||||
{
|
||||
ecs: this.ecs,
|
||||
subject: this.ecs.get(instance.entity),
|
||||
},
|
||||
);
|
||||
}
|
||||
static properties = {
|
||||
interacting: {type: 'uint8'},
|
||||
interactScript: {type: 'string'},
|
||||
};
|
||||
}
|
14
app/ecs-components/interacts.js
Normal file
14
app/ecs-components/interacts.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
import Component from '@/ecs/component.js';
|
||||
|
||||
export default class Interacts extends Component {
|
||||
instanceFromSchema() {
|
||||
return class ControlledInstance extends super.instanceFromSchema() {
|
||||
toJSON() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
static properties = {
|
||||
willInteractWith: {type: 'uint32'},
|
||||
};
|
||||
}
|
47
app/ecs-systems/interactions.js
Normal file
47
app/ecs-systems/interactions.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
import {System} from '@/ecs/index.js';
|
||||
|
||||
export default class PlantGrowth extends System {
|
||||
|
||||
static queries() {
|
||||
return {
|
||||
default: ['Interacts'],
|
||||
};
|
||||
}
|
||||
|
||||
tick() {
|
||||
for (const {Direction, Interacts, Position} of this.select('default')) {
|
||||
Interacts.willInteractWith = 0
|
||||
const box = {
|
||||
x: Position.x - 8,
|
||||
y: Position.y - 8,
|
||||
w: 16,
|
||||
h: 16,
|
||||
}
|
||||
if (0 === Direction.direction) {
|
||||
box.y -= 16
|
||||
}
|
||||
if (1 === Direction.direction) {
|
||||
box.x += 16
|
||||
}
|
||||
if (2 === Direction.direction) {
|
||||
box.y += 16
|
||||
}
|
||||
if (3 === Direction.direction) {
|
||||
box.x -= 16
|
||||
}
|
||||
// todo sort
|
||||
const entities = Array.from(this.ecs.system('UpdateSpatialHash').within(
|
||||
box.x,
|
||||
box.y,
|
||||
box.w,
|
||||
box.h,
|
||||
));
|
||||
for (let j = 0; j < entities.length; ++j) {
|
||||
if (entities[j].Interactive && entities[j].Interactive.interacting) {
|
||||
Interacts.willInteractWith = entities[0].id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -58,7 +58,7 @@ export default class Engine {
|
|||
entity,
|
||||
payload,
|
||||
] of this.incomingActions) {
|
||||
const {Controlled, Inventory, Wielder} = entity;
|
||||
const {Controlled, Ecs, Interacts, Inventory, Wielder} = entity;
|
||||
switch (payload.type) {
|
||||
case 'changeSlot': {
|
||||
if (!Controlled.locked) {
|
||||
|
@ -85,6 +85,18 @@ export default class Engine {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case 'interact': {
|
||||
if (!Controlled.locked) {
|
||||
if (payload.value) {
|
||||
if (Interacts.willInteractWith) {
|
||||
const ecs = this.ecses[Ecs.path];
|
||||
const subject = ecs.get(Interacts.willInteractWith);
|
||||
subject.Interactive.interact(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.incomingActions = [];
|
||||
|
@ -153,6 +165,7 @@ export default class Engine {
|
|||
'RunAnimations',
|
||||
'RunTickingPromises',
|
||||
'Water',
|
||||
'Interactions',
|
||||
];
|
||||
defaultSystems.forEach((defaultSystem) => {
|
||||
const System = ecs.system(defaultSystem);
|
||||
|
@ -171,6 +184,7 @@ export default class Engine {
|
|||
Ecs: {path: join('homesteads', `${id}`)},
|
||||
Emitter: {},
|
||||
Forces: {},
|
||||
Interacts: {},
|
||||
Inventory: {
|
||||
slots: {
|
||||
// 1: {
|
||||
|
|
|
@ -7,6 +7,7 @@ const WIRE_MAP = {
|
|||
'moveLeft': 3,
|
||||
'use': 4,
|
||||
'changeSlot': 5,
|
||||
'interact': 6,
|
||||
};
|
||||
Object.entries(WIRE_MAP)
|
||||
.forEach(([k, v]) => {
|
||||
|
|
|
@ -1,12 +1,43 @@
|
|||
import {AdjustmentFilter} from '@pixi/filter-adjustment';
|
||||
import {GlowFilter} from '@pixi/filter-glow';
|
||||
import {Container} from '@pixi/react';
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
import {useEcs} from '@/context/ecs.js';
|
||||
import {useMainEntity} from '@/context/main-entity.js';
|
||||
|
||||
import Entity from './entity.jsx';
|
||||
|
||||
export default function Entities({entities}) {
|
||||
const [ecs] = useEcs();
|
||||
const [mainEntity] = useMainEntity();
|
||||
const [radians, setRadians] = useState(0);
|
||||
const [willInteractWith, setWillInteractWith] = useState(0);
|
||||
const [filters] = useState([new AdjustmentFilter(), new GlowFilter({color: 0x0})]);
|
||||
const pulse = (Math.cos(radians) + 1) * 0.5;
|
||||
filters[0].brightness = (pulse * 0.75) + 1;
|
||||
filters[1].outerStrength = pulse * 0.5;
|
||||
useEffect(() => {
|
||||
setRadians(0);
|
||||
const handle = setInterval(() => {
|
||||
setRadians((radians) => (radians + 0.1) % (Math.PI * 2))
|
||||
}, 50);
|
||||
return () => {
|
||||
clearInterval(handle);
|
||||
};
|
||||
}, [willInteractWith]);
|
||||
useEffect(() => {
|
||||
if (!mainEntity) {
|
||||
return;
|
||||
}
|
||||
setWillInteractWith(ecs.get(mainEntity).Interacts.willInteractWith);
|
||||
}, [entities, ecs, mainEntity]);
|
||||
const renderables = [];
|
||||
for (const id in entities) {
|
||||
const isHighlightedInteraction = id == willInteractWith;
|
||||
renderables.push(
|
||||
<Entity
|
||||
filters={isHighlightedInteraction ? filters : []}
|
||||
entity={entities[id]}
|
||||
key={id}
|
||||
/>
|
||||
|
|
|
@ -29,7 +29,7 @@ function Crosshair({x, y}) {
|
|||
);
|
||||
}
|
||||
|
||||
function Entities({entity}) {
|
||||
function Entity({entity, ...rest}) {
|
||||
const [debug] = useDebug();
|
||||
if (!entity) {
|
||||
return false;
|
||||
|
@ -41,6 +41,7 @@ function Entities({entity}) {
|
|||
{entity.Sprite && (
|
||||
<Sprite
|
||||
entity={entity}
|
||||
{...rest}
|
||||
/>
|
||||
)}
|
||||
{entity.Emitter && (
|
||||
|
@ -55,4 +56,4 @@ function Entities({entity}) {
|
|||
);
|
||||
}
|
||||
|
||||
export default memo(Entities);
|
||||
export default memo(Entity);
|
||||
|
|
|
@ -2,7 +2,7 @@ import {Sprite as PixiSprite} from '@pixi/react';
|
|||
|
||||
import {useAsset} from '@/context/assets.js';
|
||||
|
||||
export default function Sprite({entity}) {
|
||||
export default function Sprite({entity, ...rest}) {
|
||||
const asset = useAsset(entity.Sprite.source);
|
||||
if (!asset) {
|
||||
return false;
|
||||
|
@ -21,6 +21,7 @@ export default function Sprite({entity}) {
|
|||
texture={texture}
|
||||
x={Math.round(entity.Position.x)}
|
||||
y={Math.round(entity.Position.y)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -92,6 +92,10 @@ export default function Ui({disconnected}) {
|
|||
actionPayload = {type: 'use', value: KEY_MAP[type]};
|
||||
break;
|
||||
}
|
||||
case 'e': {
|
||||
actionPayload = {type: 'interact', value: KEY_MAP[type]};
|
||||
break;
|
||||
}
|
||||
case '1': {
|
||||
if ('keyDown' === type) {
|
||||
actionPayload = {type: 'changeSlot', value: 1};
|
||||
|
|
18
package-lock.json
generated
18
package-lock.json
generated
|
@ -7,6 +7,8 @@
|
|||
"name": "silphius-next",
|
||||
"dependencies": {
|
||||
"@msgpack/msgpack": "^3.0.0-beta2",
|
||||
"@pixi/filter-adjustment": "^5.1.1",
|
||||
"@pixi/filter-glow": "^5.2.1",
|
||||
"@pixi/particle-emitter": "^5.0.8",
|
||||
"@pixi/react": "^7.1.2",
|
||||
"@pixi/spritesheet": "^7.4.2",
|
||||
|
@ -3302,6 +3304,14 @@
|
|||
"@pixi/core": "7.4.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@pixi/filter-adjustment": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@pixi/filter-adjustment/-/filter-adjustment-5.1.1.tgz",
|
||||
"integrity": "sha512-AUHe03rmqXwV1ylAHq62t19AolPWOOYomCcL+Qycb1tf+LbM8FWpGXC6wmU1PkUrhgNc958uM9TrA9nRpplViA==",
|
||||
"peerDependencies": {
|
||||
"@pixi/core": "^7.0.0-X"
|
||||
}
|
||||
},
|
||||
"node_modules/@pixi/filter-alpha": {
|
||||
"version": "7.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@pixi/filter-alpha/-/filter-alpha-7.4.2.tgz",
|
||||
|
@ -3342,6 +3352,14 @@
|
|||
"@pixi/core": "7.4.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@pixi/filter-glow": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@pixi/filter-glow/-/filter-glow-5.2.1.tgz",
|
||||
"integrity": "sha512-94I4XePDF9yqqA6KQuhPSphEHPJ2lXfqJLn0Bes8VVdwft0Ianj1wALqjoSUeBWqiJbhjBEXGDNkRZhPHvY3Xg==",
|
||||
"peerDependencies": {
|
||||
"@pixi/core": "^7.0.0-X"
|
||||
}
|
||||
},
|
||||
"node_modules/@pixi/filter-noise": {
|
||||
"version": "7.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@pixi/filter-noise/-/filter-noise-7.4.2.tgz",
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@msgpack/msgpack": "^3.0.0-beta2",
|
||||
"@pixi/filter-adjustment": "^5.1.1",
|
||||
"@pixi/filter-glow": "^5.2.1",
|
||||
"@pixi/particle-emitter": "^5.0.8",
|
||||
"@pixi/react": "^7.1.2",
|
||||
"@pixi/spritesheet": "^7.4.2",
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
const {Sprite} = ecs.get(plant.entity);
|
||||
const {Interactive, Sprite} = ecs.get(plant.entity);
|
||||
|
||||
plant.growth = 0
|
||||
|
||||
if (plant.stage < 3) {
|
||||
plant.stage += 1
|
||||
|
@ -6,8 +8,8 @@ if (plant.stage < 3) {
|
|||
if (4 === plant.stage) {
|
||||
plant.stage = 3
|
||||
}
|
||||
if (3 !== plant.stage) {
|
||||
plant.growth = 0
|
||||
if (3 === plant.stage) {
|
||||
Interactive.interacting = true;
|
||||
}
|
||||
|
||||
Sprite.animation = ['stage', plant.stage].join('/')
|
||||
|
|
4
public/assets/tomato-plant/interact.js
Normal file
4
public/assets/tomato-plant/interact.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
const {Interactive, Plant, Sprite} = subject;
|
||||
Interactive.interacting = false;
|
||||
Plant.stage = 4;
|
||||
Sprite.animation = ['stage', Plant.stage].join('/')
|
|
@ -9,10 +9,13 @@ if (projected?.length > 0) {
|
|||
const [, direction] = Sprite.animation.split(':')
|
||||
|
||||
const plant = {
|
||||
Interactive: {
|
||||
interactScript: '/assets/tomato-plant/interact.js',
|
||||
},
|
||||
Plant: {
|
||||
growScript: '/assets/tomato-plant/grow.js',
|
||||
mayGrowScript: '/assets/tomato-plant/may-grow.js',
|
||||
stages: Array(5).fill(60),
|
||||
stages: Array(5).fill(5),
|
||||
},
|
||||
Sprite: {
|
||||
anchor: {x: 0.5, y: 0.75},
|
||||
|
|
Loading…
Reference in New Issue
Block a user