Skip to content

Commit

Permalink
feat: various tweaks for mudwar (#151)
Browse files Browse the repository at this point in the history
* feat(ecs-browser): view selected entity, only show entity id in entity title

* feat(phaserx): allow centering and scrolling in camera api

* fix(phaserx): increase default chunk size

* feat(phaserx): add hasComponent to embodied entities

* feat(phaserx): disable keyboard input when all input is disabled

* refactor(std-client): rename getAddressColor -> getStringColor

* feat(ecs-browser): allow prototype spawning by name

* feat(phaserx): add setZoom function to exported camera

* fix(phaserx): hue tint shader now shades pixels with alpha < 1

* fix(phaserx): revert off screen padding increase (was causing performance issues)

* fix(phaserx): remove unnecessary color conversion in hue tint shader

* feat(std-client): add new TxReduced action state that happens before Complete

* fix: allow skipping awaiting tx confirmation while still handling errors

Co-authored-by: alvrs <alvarius@lattice.xyz>
  • Loading branch information
Kooshaba and alvrs authored Sep 19, 2022
1 parent dbdeade commit 53bc3cc
Show file tree
Hide file tree
Showing 14 changed files with 82 additions and 32 deletions.
7 changes: 5 additions & 2 deletions packages/ecs-browser/src/Browser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const Browser = observer(
devHighlightComponent,
hoverHighlightComponent,
prototypeComponent,
nameComponent,
spawnPrototypeAt,
world,
}: {
Expand All @@ -29,7 +30,8 @@ export const Browser = observer(
devHighlightComponent: Component<{ value: Type.OptionalNumber }>;
hoverHighlightComponent: Component<{ x: Type.OptionalNumber; y: Type.OptionalNumber }>;
prototypeComponent?: Component<{ value: Type.StringArray }>;
spawnPrototypeAt?: (prototypeId: EntityID, position: Coord) => void;
nameComponent: Component<{ value: Type.String }>;
spawnPrototypeAt: (prototypeId: EntityID, position: Coord) => void;
world: World;
}) => {
const [filteredEntities, setFilteredEntities] = useState<EntityID[]>([]);
Expand All @@ -48,11 +50,12 @@ export const Browser = observer(
clearDevHighlights={clearDevHighlights}
setOverflow={setOverflow}
/>
{hoverHighlightComponent && prototypeComponent && spawnPrototypeAt && (
{hoverHighlightComponent && prototypeComponent && spawnPrototypeAt && nameComponent && (
<PrototypeCreator
layers={layers}
hoverHighlightComponent={hoverHighlightComponent}
prototypeComponent={prototypeComponent}
nameComponent={nameComponent}
spawnPrototypeAt={spawnPrototypeAt}
/>
)}
Expand Down
10 changes: 9 additions & 1 deletion packages/ecs-browser/src/EntityEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,15 @@ export const EntityEditor = observer(
onMouseLeave={() => clearDevHighlights()}
>
<div onClick={() => setOpened(!opened)} style={{ cursor: "pointer" }}>
<h3 style={{ color: "white" }}>{world.entities[entity]}</h3>
<div style={{ display: "flex", flexDirection: "row", justifyContent: 'space-between' }}>
<h3 style={{ color: "white" }}>Entity {entity}</h3>
<ComponentBrowserButton onClick={(e) => {
e.stopPropagation();
navigator.clipboard.writeText(world.entities[entity]);
}}>
Click to copy Entity ID
</ComponentBrowserButton>
</div>
<ComponentBrowserButton onClick={() => setOpened(!opened)}>
{opened ? <>&#9660;</> : <>&#9654;</>}
</ComponentBrowserButton>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, EntityID, Has, Layers, Type } from "@latticexyz/recs";
import { Component, EntityID, getComponentValue, Has, Layers, Type } from "@latticexyz/recs";
import React, { useEffect, useState } from "react";
import { ComponentBrowserButton, ComponentBrowserSelect } from "../StyledComponents";
import { Coord } from "@latticexyz/phaserx";
Expand All @@ -8,8 +8,9 @@ export const PrototypeCreator: React.FC<{
layers: Layers;
spawnPrototypeAt: (prototypeId: EntityID, position: Coord) => void;
prototypeComponent: Component<{ value: Type.StringArray }>;
nameComponent: Component<{ value: Type.String }>;
hoverHighlightComponent: Component<{ x: Type.OptionalNumber; y: Type.OptionalNumber }>;
}> = ({ layers, prototypeComponent, hoverHighlightComponent, spawnPrototypeAt }) => {
}> = ({ layers, prototypeComponent, nameComponent, hoverHighlightComponent, spawnPrototypeAt }) => {
const [selectingPosition, setSelectingPosition] = useState(false);
const [selectedPrototype, setSelectedPrototype] = useState<string | undefined>(undefined);
const hoverHighlight = useComponentValueStream(hoverHighlightComponent);
Expand Down Expand Up @@ -52,9 +53,10 @@ export const PrototypeCreator: React.FC<{
<option value="">None</option>
{[...prototypes].map((entityIndex) => {
const entityId = layers.network.world.entities[entityIndex];
const name = getComponentValue(nameComponent, entityIndex)?.value;
return (
<option key={entityId} value={entityId}>
{entityId}
{name || entityId}
</option>
);
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const PositionFilterButton: React.FC<{
setSelectingPosition(true);
}}
>
{selectingPosition ? `Click on tile to select position` : "Select Entities at a position"}
{selectingPosition ? `Click on tile to select position` : "View Entities at a position"}
</ComponentBrowserButton>
);
};
6 changes: 6 additions & 0 deletions packages/ecs-browser/src/QueryBuilder/QueryBuilder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,12 @@ export const QueryBuilder = ({
queryInputRef={queryInputRef}
input={(layers.phaser as any)?.scenes.Main.phaserScene.input}
/>
<ComponentBrowserButton onClick={() => {
queryInputRef.current?.focus();
editQuery('[Has(Selected)]');
}}>
View Selected Entity
</ComponentBrowserButton>
<h3>Filter by Component</h3>
<QueryShortcutContainer style={{ margin: "8px auto" }}>
{orderBy(allComponents, (c) => c.id)
Expand Down
28 changes: 22 additions & 6 deletions packages/phaserx/src/createCamera.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ export function createCamera(phaserCamera: Phaser.Cameras.Scene2D.Camera, option
// return Math.pow(2, Math.floor(Math.log(currentZoom * 2) / Math.log(2))) / 2;
// }

function setZoom(zoom: number) {
phaserCamera.setZoom(zoom);
worldView$.next(phaserCamera.worldView);
zoom$.next(zoom);
}

const pinchSub = pinchStream$
.pipe(
throttleTime(10),
Expand All @@ -41,9 +47,7 @@ export function createCamera(phaserCamera: Phaser.Cameras.Scene2D.Camera, option
.subscribe(([, zoom]) => {
// Set the gesture zoom state to the current zoom value to avoid zooming beyond the max values
if (gesture._ctrl.state.pinch) gesture._ctrl.state.pinch.offset[0] = zoom;
phaserCamera.setZoom(zoom);
worldView$.next(phaserCamera.worldView);
zoom$.next(zoom);
setZoom(zoom);
});

const wheelSub = wheelStream$
Expand All @@ -62,9 +66,18 @@ export function createCamera(phaserCamera: Phaser.Cameras.Scene2D.Camera, option
objectPool.ignoreCamera(phaserCamera.id, ignore);
}

function centerCameraOnCoord(tileCoord: Coord, tileWidth: number, tileHeight: number) {
function centerOnCoord(tileCoord: Coord, tileWidth: number, tileHeight: number) {
const pixelCoord = tileCoordToPixelCoord(tileCoord, tileWidth, tileHeight);
phaserCamera.centerOn(pixelCoord.x, pixelCoord.y);
centerOn(pixelCoord.x, pixelCoord.y);
}

function centerOn(x: number, y: number) {
phaserCamera.centerOn(x, y);
requestAnimationFrame(() => worldView$.next(phaserCamera.worldView));
}

function setScroll(x: number, y: number) {
phaserCamera.setScroll(x, y);
requestAnimationFrame(() => worldView$.next(phaserCamera.worldView));
}

Expand All @@ -78,6 +91,9 @@ export function createCamera(phaserCamera: Phaser.Cameras.Scene2D.Camera, option
wheelSub.unsubscribe();
gesture.destroy();
},
centerCameraOnCoord,
centerOnCoord,
centerOn,
setScroll,
setZoom,
};
}
7 changes: 6 additions & 1 deletion packages/phaserx/src/createEmbodiedEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ export function createEmbodiedEntity<Type extends keyof GameObjectTypes>(
if (activeGameObject && once) once(activeGameObject);
}

function hasComponent(id: string): boolean {
return onOnce.has(id) || onUpdate.has(id);
}

function removeComponent(id: string, stop?: boolean) {
onOnce.delete(id);
onUpdate.delete(id);
Expand All @@ -85,6 +89,7 @@ export function createEmbodiedEntity<Type extends keyof GameObjectTypes>(
gameObject.setScale(1, 1);
gameObject.setOrigin(0, 0);
gameObject.setAlpha(1);
gameObject.setScrollFactor(1);
if (isSprite(gameObject, type)) {
gameObject.clearTint();
gameObject.setTexture("");
Expand Down Expand Up @@ -123,7 +128,7 @@ export function createEmbodiedEntity<Type extends keyof GameObjectTypes>(
activeGameObject = undefined;
}

return { setComponent, removeComponent, spawn, despawn, position, id, setCameraFilter, type };
return { setComponent, hasComponent, removeComponent, spawn, despawn, position, id, setCameraFilter, type };
}

function executeGameObjectFunctions<Type extends keyof GameObjectTypes>(
Expand Down
2 changes: 1 addition & 1 deletion packages/phaserx/src/createInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export function createInput(inputPlugin: Phaser.Input.InputPlugin) {
for (const key of Object.keys(Phaser.Input.Keyboard.KeyCodes)) addKey(key);

// Subscriptions
const keySub = keyboard$.subscribe((key) => {
const keySub = keyboard$.pipe(filter(() => enabled.current)).subscribe((key) => {
const keyName = codeToKey.get(key.keyCode);
if (!keyName) return;
runInAction(() => {
Expand Down
14 changes: 4 additions & 10 deletions packages/phaserx/src/pipelines/HueTintAndOutlineFXPipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,7 @@ export class HueTintAndOutlineFXPipeline extends SpritePipeline {
float e = 1.0e-10;
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
vec3 hsv2rgb(vec3 c)
{
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
void main(void)
{
vec4 srcColor;
Expand All @@ -44,12 +39,11 @@ export class HueTintAndOutlineFXPipeline extends SpritePipeline {
vec3 rgbColor;
srcColor = texture2D(uMainSampler, outTexCoord);
hsvColor = rgb2hsv(srcColor.rgb);
if (hsvColor.g == 0.0 && srcColor.a == 1. && !(tintColor.r == 0.0 && tintColor.g == 0.0 && tintColor.b == 0.0))
if (hsvColor.g == 0.0 && !(tintColor.r == 0.0 && tintColor.g == 0.0 && tintColor.b == 0.0))
{
vec3 color = hsv2rgb(hsvColor);
rgbColor = color * tintColor;
rgbColor = srcColor.rgb * tintColor;
} else {
rgbColor = hsv2rgb(hsvColor);
rgbColor = srcColor.rgb;
}
outColor = vec4(rgbColor.r, rgbColor.g, rgbColor.b, srcColor.a);
if(outline == 1) {
Expand Down
6 changes: 5 additions & 1 deletion packages/phaserx/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ export type Camera = {
zoom$: Observable<number>;
ignore: (objectPool: ObjectPool, ignore: boolean) => void;
dispose: () => void;
centerCameraOnCoord: (tileCoord: Coord, tileWidth: number, tileHeight: number) => void;
centerOnCoord: (tileCoord: Coord, tileWidth: number, tileHeight: number) => void;
centerOn: (x: number, y: number) => void;
setScroll: (x: number, y: number) => void;
setZoom: (zoom: number) => void;
};

export type GameObjectTypes = typeof GameObjectClasses;
Expand Down Expand Up @@ -64,6 +67,7 @@ export type Input = ReturnType<typeof createInput>;

export type EmbodiedEntity<Type extends keyof GameObjectTypes> = {
setComponent: (component: GameObjectComponent<Type>) => void;
hasComponent: (id: string) => boolean;
removeComponent: (id: string, stop?: boolean) => void;
spawn: () => void;
despawn: () => void;
Expand Down
2 changes: 2 additions & 0 deletions packages/std-client/src/systems/ActionSystem/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ export enum ActionState {
Complete,
Failed,
Cancelled,
TxReduced,
}

export const ActionStateString = {
[ActionState.TxReduced]: "TxReduced",
[ActionState.Requested]: "Requested",
[ActionState.Executing]: "Executing",
[ActionState.WaitingForTxEvents]: "WaitingForTxEvents",
Expand Down
17 changes: 12 additions & 5 deletions packages/std-client/src/systems/ActionSystem/createActionSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export function createActionSystem(world: World, txReduced$: Observable<string>)

/**
* Maps all components in a given components map to the respective components including pending updates
* @param components Components to be mapped to components including pending updates
* @param component Component to be mapped to components including pending updates
* @returns Components including pending updates
*/
function withOptimisticUpdates<C extends Component>(component: C): C {
Expand Down Expand Up @@ -110,7 +110,7 @@ export function createActionSystem(world: World, txReduced$: Observable<string>)

/**
* Checks the requirement of a given action and executes the action if the requirement is fulfilled
* @param actionId ID of the action to check the requirement of
* @param action Action to check the requirement of
* @returns void
*/
function checkRequirement(action: ActionData) {
Expand Down Expand Up @@ -153,19 +153,26 @@ export function createActionSystem(world: World, txReduced$: Observable<string>)
if (tx) {
// Wait for all tx events to be reduced
updateComponent(Action, action.entityIndex, { state: ActionState.WaitingForTxEvents });
const txReduced = awaitStreamValue(txReduced$, (v) => v === tx.hash);
await Promise.all([tx.wait(), txReduced]);
const txConfirmed = tx.wait().catch(() => handleError(action)); // Also catch the error if not awaiting
await awaitStreamValue(txReduced$, (v) => v === tx.hash);
updateComponent(Action, action.entityIndex, { state: ActionState.TxReduced });
if (action.awaitConfirmation) await txConfirmed;
}

updateComponent(Action, action.entityIndex, { state: ActionState.Complete });
} catch (e) {
updateComponent(Action, action.entityIndex, { state: ActionState.Failed });
handleError(action);
}

// After the action is done executing (failed or completed), remove its actionData and remove the Action component
remove(action.id);
}

// Set the action's state to ActionState.Failed
function handleError(action: ActionData) {
updateComponent(Action, action.entityIndex, { state: ActionState.Failed });
}

/**
* Cancels the action with the given ID if it is in the "Requested" state.
* @param actionId ID of the action to be cancelled
Expand Down
3 changes: 3 additions & 0 deletions packages/std-client/src/systems/ActionSystem/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ export interface ActionRequest<C extends Components, T> {
// If txHashes are returned from the txQueue, the action will only be completed (and pending updates removed)
// once all events from the given txHashes have been received and reduced.
execute: (data: T) => Promise<ContractTransaction> | Promise<void> | void | undefined;

// Flag to set if the queue should wait for the underlying transaction to be confirmed (in addition to being reduced)
awaitConfirmation?: boolean;
}

export type ActionData = ActionRequest<Components, unknown> & {
Expand Down
2 changes: 1 addition & 1 deletion packages/std-client/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ export function randomColor(id: string): number {
return Phaser.Display.Color.HSLToColor(h, s, l).color;
}

export function getAddressColor(address: string) {
export function getStringColor(address: string) {
return randomColor(keccak256(address).substring(2));
}

Expand Down

0 comments on commit 53bc3cc

Please sign in to comment.