flow: main entity, actions, active slot
This commit is contained in:
parent
5f139f8960
commit
36607d3f12
|
@ -10,10 +10,3 @@ export const RESOLUTION = {
|
|||
export const SERVER_LATENCY = 0;
|
||||
|
||||
export const TPS = 60;
|
||||
|
||||
export const ACTION_MAP = {
|
||||
w: 'moveUp',
|
||||
d: 'moveRight',
|
||||
s: 'moveDown',
|
||||
a: 'moveLeft',
|
||||
};
|
||||
|
|
10
app/context/main-entity.js
Normal file
10
app/context/main-entity.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import {createContext, useContext} from 'react';
|
||||
|
||||
const context = createContext();
|
||||
|
||||
export default context;
|
||||
|
||||
export function useMainEntity() {
|
||||
return useContext(context);
|
||||
}
|
||||
|
|
@ -3,4 +3,5 @@ export default {
|
|||
moveRight: {type: 'float32'},
|
||||
moveDown: {type: 'float32'},
|
||||
moveLeft: {type: 'float32'},
|
||||
changeSlot: {type: 'int8'},
|
||||
};
|
||||
|
|
3
app/ecs-components/wielder.js
Normal file
3
app/ecs-components/wielder.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
export default {
|
||||
activeSlot: {type: 'uint16'},
|
||||
};
|
17
app/ecs-systems/apply-control-item.js
Normal file
17
app/ecs-systems/apply-control-item.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import {System} from '@/ecs/index.js';
|
||||
|
||||
export default class ApplyControlMovement extends System {
|
||||
|
||||
tick() {
|
||||
const {diff} = this.ecs;
|
||||
for (const id in diff) {
|
||||
if ('changeSlot' in (diff[id].Controlled ?? {})) {
|
||||
if (diff[id].Controlled.changeSlot > 0) {
|
||||
this.ecs.get(id).Wielder.activeSlot = diff[id].Controlled.changeSlot - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -81,6 +81,7 @@ export default class Engine {
|
|||
},
|
||||
});
|
||||
const defaultSystems = [
|
||||
'ApplyControlItem',
|
||||
'ApplyControlMovement',
|
||||
'ApplyMomentum',
|
||||
'ClampPositions',
|
||||
|
@ -104,7 +105,7 @@ export default class Engine {
|
|||
async createPlayer(id) {
|
||||
const player = {
|
||||
Camera: {},
|
||||
Controlled: {moveUp: 0, moveRight: 0, moveDown: 0, moveLeft: 0},
|
||||
Controlled: {},
|
||||
Direction: {direction: 2},
|
||||
Ecs: {path: join('homesteads', `${id}`)},
|
||||
Momentum: {},
|
||||
|
@ -118,6 +119,9 @@ export default class Engine {
|
|||
source: '/assets/dude.json',
|
||||
speed: 0.115,
|
||||
},
|
||||
Wielder: {
|
||||
activeSlot: 0,
|
||||
},
|
||||
};
|
||||
const buffer = (new TextEncoder()).encode(JSON.stringify(player));
|
||||
await this.server.writeData(
|
||||
|
|
|
@ -5,6 +5,8 @@ const WIRE_MAP = {
|
|||
'moveRight': 1,
|
||||
'moveDown': 2,
|
||||
'moveLeft': 3,
|
||||
'use': 4,
|
||||
'changeSlot': 5,
|
||||
};
|
||||
Object.entries(WIRE_MAP)
|
||||
.forEach(([k, v]) => {
|
||||
|
|
|
@ -2,6 +2,7 @@ import {Container} from '@pixi/react';
|
|||
import {useState} from 'react';
|
||||
|
||||
import {RESOLUTION} from '@/constants.js';
|
||||
import {useMainEntity} from '@/context/main-entity.js';
|
||||
import Ecs from '@/ecs/ecs.js';
|
||||
import Components from '@/ecs-components/index.js';
|
||||
import Systems from '@/ecs-systems/index.js';
|
||||
|
@ -14,7 +15,7 @@ import TileLayer from './tile-layer.jsx';
|
|||
export default function EcsComponent() {
|
||||
const [ecs] = useState(new Ecs({Components, Systems}));
|
||||
const [entities, setEntities] = useState({});
|
||||
const [mainEntity, setMainEntity] = useState();
|
||||
const [mainEntity] = useMainEntity();
|
||||
usePacket('Tick', (payload) => {
|
||||
if (0 === Object.keys(payload.ecs).length) {
|
||||
return;
|
||||
|
@ -27,9 +28,6 @@ export default function EcsComponent() {
|
|||
}
|
||||
else {
|
||||
updatedEntities[id] = ecs.get(id);
|
||||
if (updatedEntities[id].MainEntity) {
|
||||
setMainEntity(ecs.get(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
setEntities(updatedEntities);
|
||||
|
@ -37,7 +35,7 @@ export default function EcsComponent() {
|
|||
if (!mainEntity) {
|
||||
return false;
|
||||
}
|
||||
const {Camera} = mainEntity;
|
||||
const {Camera, Position} = ecs.get(mainEntity);
|
||||
const {TileLayers} = ecs.get(1);
|
||||
const [cx, cy] = [
|
||||
Math.round(Camera.x - RESOLUTION.x / 2),
|
||||
|
@ -56,8 +54,8 @@ export default function EcsComponent() {
|
|||
y={-cy}
|
||||
/>
|
||||
<TargetingGhost
|
||||
px={mainEntity.Position.x}
|
||||
py={mainEntity.Position.y}
|
||||
px={Position.x}
|
||||
py={Position.y}
|
||||
cx={cx}
|
||||
cy={cy}
|
||||
/>
|
||||
|
|
|
@ -27,13 +27,14 @@ export default function Entities({entities, x, y}) {
|
|||
continue;
|
||||
}
|
||||
renderables.push(
|
||||
<>
|
||||
<Container
|
||||
key={id}
|
||||
>
|
||||
<Sprite
|
||||
entity={entity}
|
||||
key={id}
|
||||
/>
|
||||
<Crosshair x={entity.Position.x} y={entity.Position.y} />
|
||||
</>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
return (
|
||||
|
|
|
@ -3,29 +3,37 @@ import {
|
|||
} from '@pixi/react';
|
||||
import {SCALE_MODES} from '@pixi/constants';
|
||||
import {BaseTexture} from '@pixi/core';
|
||||
import {createElement, useContext} from 'react';
|
||||
|
||||
import {RESOLUTION} from '@/constants.js';
|
||||
import ClientContext from '@/context/client.js';
|
||||
import MainEntityContext from '@/context/main-entity.js';
|
||||
|
||||
import Ecs from './ecs.jsx';
|
||||
import styles from './pixi.module.css';
|
||||
|
||||
BaseTexture.defaultOptions.scaleMode = SCALE_MODES.NEAREST;
|
||||
|
||||
const ContextBridge = ({ children, Context, render }) => {
|
||||
return (
|
||||
<Context.Consumer>
|
||||
{(value) =>
|
||||
render(<Context.Provider value={value}>{children}</Context.Provider>)
|
||||
const ContextBridge = ({children, Contexts, render}) => {
|
||||
const contexts = Contexts.map(useContext);
|
||||
return render(
|
||||
<>
|
||||
{
|
||||
Contexts.reduce(
|
||||
(children, Context, i) => ([
|
||||
createElement(Context.Provider, {value: contexts[i], key: Context}, children)
|
||||
]),
|
||||
children,
|
||||
)
|
||||
}
|
||||
</Context.Consumer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const Stage = ({children, ...props}) => {
|
||||
return (
|
||||
<ContextBridge
|
||||
Context={ClientContext}
|
||||
Contexts={[ClientContext, MainEntityContext]}
|
||||
render={(children) => <PixiStage {...props}>{children}</PixiStage>}
|
||||
>
|
||||
{children}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import {useContext, useEffect, useState} from 'react';
|
||||
|
||||
import addKeyListener from '@/add-key-listener.js';
|
||||
import {ACTION_MAP, RESOLUTION} from '@/constants.js';
|
||||
import {RESOLUTION} from '@/constants.js';
|
||||
import ClientContext from '@/context/client.js';
|
||||
import {useMainEntity} from '@/context/main-entity.js';
|
||||
import usePacket from '@/hooks/use-packet.js';
|
||||
|
||||
import Disconnected from './disconnected.jsx';
|
||||
import Dom from './dom.jsx';
|
||||
|
@ -12,21 +14,18 @@ import styles from './ui.module.css';
|
|||
|
||||
const ratio = RESOLUTION.x / RESOLUTION.y;
|
||||
|
||||
const KEY_MAP = {
|
||||
keyDown: 1,
|
||||
keyUp: 0,
|
||||
};
|
||||
|
||||
export default function Ui({disconnected}) {
|
||||
// Key input.
|
||||
const client = useContext(ClientContext);
|
||||
const [mainEntity, setMainEntity] = useMainEntity();
|
||||
const [showDisconnected, setShowDisconnected] = useState(false);
|
||||
const [activeSlot, setActiveSlot] = useState(0);
|
||||
useEffect(() => {
|
||||
let handle;
|
||||
if (disconnected) {
|
||||
handle = setTimeout(() => {
|
||||
setShowDisconnected(true);
|
||||
}, 400);
|
||||
}, 1000);
|
||||
}
|
||||
else {
|
||||
setShowDisconnected(false)
|
||||
|
@ -37,17 +36,117 @@ export default function Ui({disconnected}) {
|
|||
}, [disconnected]);
|
||||
useEffect(() => {
|
||||
return addKeyListener(document.body, ({type, payload}) => {
|
||||
if (type in KEY_MAP && payload in ACTION_MAP) {
|
||||
const KEY_MAP = {
|
||||
keyDown: 1,
|
||||
keyUp: 0,
|
||||
};
|
||||
let actionPayload;
|
||||
switch (payload) {
|
||||
case 'w': {
|
||||
actionPayload = {type: 'moveUp', value: KEY_MAP[type]};
|
||||
break;
|
||||
}
|
||||
case 'a': {
|
||||
actionPayload = {type: 'moveLeft', value: KEY_MAP[type]};
|
||||
break;
|
||||
}
|
||||
case 's': {
|
||||
actionPayload = {type: 'moveDown', value: KEY_MAP[type]};
|
||||
break;
|
||||
}
|
||||
case 'd': {
|
||||
actionPayload = {type: 'moveRight', value: KEY_MAP[type]};
|
||||
break;
|
||||
}
|
||||
case ' ': {
|
||||
actionPayload = {type: 'use', value: KEY_MAP[type]};
|
||||
break;
|
||||
}
|
||||
case '1': {
|
||||
if ('keyDown' === type) {
|
||||
actionPayload = {type: 'changeSlot', value: 1};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '2': {
|
||||
if ('keyDown' === type) {
|
||||
actionPayload = {type: 'changeSlot', value: 2};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '3': {
|
||||
if ('keyDown' === type) {
|
||||
actionPayload = {type: 'changeSlot', value: 3};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '4': {
|
||||
if ('keyDown' === type) {
|
||||
actionPayload = {type: 'changeSlot', value: 4};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '5': {
|
||||
if ('keyDown' === type) {
|
||||
actionPayload = {type: 'changeSlot', value: 5};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '6': {
|
||||
if ('keyDown' === type) {
|
||||
actionPayload = {type: 'changeSlot', value: 6};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '7': {
|
||||
if ('keyDown' === type) {
|
||||
actionPayload = {type: 'changeSlot', value: 7};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '8': {
|
||||
if ('keyDown' === type) {
|
||||
actionPayload = {type: 'changeSlot', value: 8};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '9': {
|
||||
if ('keyDown' === type) {
|
||||
actionPayload = {type: 'changeSlot', value: 9};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '0': {
|
||||
if ('keyDown' === type) {
|
||||
actionPayload = {type: 'changeSlot', value: 10};
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (actionPayload) {
|
||||
client.send({
|
||||
type: 'Action',
|
||||
payload: {
|
||||
type: ACTION_MAP[payload],
|
||||
value: KEY_MAP[type],
|
||||
},
|
||||
payload: actionPayload,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
usePacket('Tick', (payload) => {
|
||||
if (0 === Object.keys(payload.ecs).length) {
|
||||
return;
|
||||
}
|
||||
let localMainEntity = mainEntity;
|
||||
for (const id in payload.ecs) {
|
||||
if (payload.ecs[id]?.MainEntity) {
|
||||
setMainEntity(localMainEntity = id);
|
||||
}
|
||||
if (localMainEntity === id) {
|
||||
if (payload.ecs[id].Wielder && 'activeSlot' in payload.ecs[id].Wielder) {
|
||||
setActiveSlot(payload.ecs[id].Wielder.activeSlot);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [mainEntity, setMainEntity]);
|
||||
return (
|
||||
<div className={styles.ui}>
|
||||
<style>
|
||||
|
@ -57,12 +156,23 @@ export default function Ui({disconnected}) {
|
|||
`}
|
||||
</style>
|
||||
<Pixi />
|
||||
<Dom>
|
||||
<HotBar active={1} slots={Array(10).fill(0).map(() => {})} />
|
||||
{showDisconnected && (
|
||||
<Disconnected />
|
||||
)}
|
||||
</Dom>
|
||||
{mainEntity && (
|
||||
<Dom>
|
||||
<HotBar
|
||||
active={activeSlot}
|
||||
onActivate={(i) => {
|
||||
client.send({
|
||||
type: 'Action',
|
||||
payload: {type: 'changeSlot', value: i + 1},
|
||||
});
|
||||
}}
|
||||
slots={Array(10).fill(0).map(() => {})}
|
||||
/>
|
||||
{showDisconnected && (
|
||||
<Disconnected />
|
||||
)}
|
||||
</Dom>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ import {useEffect, useState} from 'react';
|
|||
import {useOutletContext, useParams} from 'react-router-dom';
|
||||
|
||||
import ClientContext from '@/context/client.js';
|
||||
import MainEntityContext from '@/context/main-entity.js';
|
||||
|
||||
import Ui from '@/react-components/ui.jsx';
|
||||
import {juggleSession} from '@/session.server';
|
||||
|
||||
|
@ -14,6 +16,7 @@ export async function loader({request}) {
|
|||
export default function PlaySpecific() {
|
||||
const Client = useOutletContext();
|
||||
const [client, setClient] = useState();
|
||||
const mainEntityTuple = useState();
|
||||
const [disconnected, setDisconnected] = useState(false);
|
||||
const params = useParams();
|
||||
const [type, url] = params['*'].split('/');
|
||||
|
@ -84,7 +87,9 @@ export default function PlaySpecific() {
|
|||
}, [client, disconnected, url]);
|
||||
return (
|
||||
<ClientContext.Provider value={client}>
|
||||
<Ui disconnected={disconnected} />
|
||||
<MainEntityContext.Provider value={mainEntityTuple}>
|
||||
<Ui disconnected={disconnected} />
|
||||
</MainEntityContext.Provider>
|
||||
</ClientContext.Provider>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user