C6ESHRT7FZVXLL3273NDDKISU5LTU24RSPASCLN5VJHZE5VSIMPAC
import React from 'react';
import { IS_DEBUG } from '../environment';
export type DebugFlagsType = {
[key: string]: boolean;
};
const LOCAL_STORAGE_KEY = "debug flags";
export const ReadDebugFlagsFromLocalStorage = <T extends DebugFlagsType>(defaultFlags: T): T => {
if (IS_DEBUG) {
const prevStoredFlags = JSON.parse((window.localStorage.getItem(LOCAL_STORAGE_KEY) || "{}")) as DebugFlagsType;
// delete flags that don't exist
for (const flagName of Object.keys(prevStoredFlags)) {
if (!defaultFlags.hasOwnProperty(flagName)) {
delete prevStoredFlags[flagName];
}
}
return {
...defaultFlags,
...prevStoredFlags,
};
} else {
return defaultFlags;
}
}
const SaveDebugFlagsToLocalStorage = (flags: DebugFlagsType) => {
window.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(flags));
};
type DebugFlagButtonsProps = {
flags: DebugFlagsType;
};
export class DebugFlagButtons extends React.Component<DebugFlagButtonsProps, {}> {
render() {
const flagNames = Object.keys(this.props.flags);
return (
<div>
{
flagNames.map(flagName => {
const flag = this.props.flags[flagName];
return (
<div
key={flagName}
>
<input
type="checkbox"
checked={ flag }
onChange={ () => {
// NOTE: This is TERRIBLE React code. DO NOT LEARN FROM
// THIS. DO NOT IMITATE THIS. IN FACT, RUN FAR AWAY FROM
// THIS!!!
// The reason this works at all is because GameReactWrapper
// does a forceUpdate() on a setInterval to keep everything
// in sync. DebugFlagButtons will be captured in the
// setInterval and forced to update.
// The point being that the value gets synced back into the
// game with no one being the wiser. MAGIC!
this.props.flags[flagName] = !this.props.flags[flagName];
SaveDebugFlagsToLocalStorage(this.props.flags);
}}
/> { flagName }
</div>
);
})
}
</div>
)
}
}
import React from 'react';
import { Container, Graphics } from 'pixi.js';
import { Entity } from '../entity';
import { Debug } from '../debug';
import { IGameState } from 'Library';
type HierarchyProps = {
root: Entity | Container;
setSelected: (obj: Entity | Container) => void;
setMoused: (obj: Entity | Container | null) => void;
gameState: IGameState;
selectedEntity: Entity | Container | null;
};
export class Hierarchy extends React.Component<HierarchyProps, {
hover: boolean;
collapsed: boolean;
}> {
constructor(props: HierarchyProps) {
super(props);
this.state = {
hover: false,
collapsed: true,
};
setInterval(() => {
this.updateDebugGraphics();
}, 200);
}
oldTint: { [key: number]: number } = {};
hoverGraphics: Graphics[] = [];
hoverTarget: Entity | Container | null = null;
updateDebugGraphics = () => {
// clear debug graphics
for (const graphic of this.hoverGraphics) {
graphic.parent.removeChild(graphic);
graphic.destroy();
}
this.hoverGraphics = [];
if (this.hoverTarget !== null) {
this.hoverGraphics = [...Debug.DrawBounds(this.props.root, 0xff0000, true, "stage")];
if (this.props.root instanceof Entity) {
const point = Debug.DrawPoint(this.props.root.position, 0xff0000, true);
this.hoverGraphics = [
...this.hoverGraphics,
point,
];
}
}
if (this.props.selectedEntity === this.props.root) {
this.hoverGraphics = [...this.hoverGraphics, ...Debug.DrawBounds(this.props.selectedEntity, 0xff0000, true, "stage")];
if (this.props.root instanceof Entity) {
const point = Debug.DrawPoint(this.props.selectedEntity.position, 0xff0000, true);
this.hoverGraphics = [
...this.hoverGraphics,
point,
];
}
}
};
mouseOver = () => {
this.setState({ hover: true })
if (this.props.root instanceof Entity) {
this.oldTint[this.props.root.id] = this.props.root.sprite.tint;
this.props.root.sprite.tint = 0x000099;
}
this.hoverTarget = this.props.root;
this.props.setMoused(this.props.root);
};
mouseOut = () => {
this.setState({ hover: false })
if (this.props.root instanceof Entity) {
this.props.root.sprite.tint = this.oldTint[this.props.root.id];
}
this.hoverTarget = null;
this.props.setMoused(null);
};
click = () => {
this.props.setSelected(this.props.root);
console.log(this.props.root);
};
renderLeaf(root: any) {
let active = true;
if (root instanceof Entity) {
active = root.activeModes.includes(this.props.gameState.mode);
}
return (<div style={{
color: active ? "white" : "gray",
}}>
{this.props.selectedEntity === this.props.root ? <strong>{root.name}</strong> : root.name} (depth: { root.zIndex})
</div>)
}
render() {
const root = this.props.root;
let allChildren = (
root instanceof Entity ? root.children() : []
);
let children = allChildren;
let canCollapse = children.length > 20;
let didCollapse = false;
if (canCollapse) {
if (this.state.collapsed) {
children = children.slice(0, 20);
didCollapse = true;
}
}
if (children)
return (
<div
style={{
paddingLeft: "10px",
fontFamily: 'Arial',
fontSize: '14px',
backgroundColor: this.state.hover ? "darkgray" : "black"
}}
>
<div
onMouseEnter={this.mouseOver}
onMouseLeave={this.mouseOut}
onClick={this.click}
>
{this.renderLeaf(root)}
</div>
{
canCollapse
? <div onClick={() => this.setState({ collapsed: !this.state.collapsed })} style={{ padding: "8px 0" }}>
{
didCollapse
? <span>[see {allChildren.length - 20} more]</span>
: <span>[collapse]</span>
}
</div>
: null
}
{
children.map(child => {
return <Hierarchy selectedEntity={this.props.selectedEntity} setMoused={this.props.setMoused} setSelected={this.props.setSelected} root={child} gameState={this.props.gameState} />
})
}
</div>
)
};
}
import React from 'react';
import ReactDOM from 'react-dom';
import { BaseGame } from '../base_game';
import { Hierarchy } from './hierarchy';
import { DebugFlagButtons, DebugFlagsType } from './debug_flag_buttons';
import { IS_DEBUG } from '../environment';
import { Entity } from '../entity';
import { Container } from 'pixi.js';
import { TextEntity } from '../text_entity';
import { Debug } from '../debug';
type ReactWrapperProps = {
game: BaseGame<{}>;
debugFlags: DebugFlagsType;
};
type ReactWrapperState = {
selected: Entity | Container | null;
moused: Entity | Container | null;
};
export class GameReactWrapper extends React.Component<ReactWrapperProps, ReactWrapperState> {
mounted = false;
constructor(props: ReactWrapperProps) {
super(props);
this.state = {
selected: this.props.game.stage,
moused: null,
};
setInterval(() => this.monitorHierarchyUpdates(), 500);
}
componentDidMount() {
this.mounted = true;
}
componentWillUnmount() {
console.error("This should never happen!!!! very bad?!?");
}
monitorHierarchyUpdates = () => {
if (this.mounted) {
this.forceUpdate();
}
};
setSelected = (obj: Entity | Container) => {
this.setState({
selected: obj,
});
};
setMoused = (obj: Entity | Container | null) => {
this.setState({
moused: obj,
});
};
renderSelected = () => {
const target = this.state.moused || this.state.selected;
if (target === null) { return null; }
if (target instanceof Container) {
return (
<div style={{ fontWeight: 600, fontFamily: 'arial', paddingTop: '8px', paddingBottom: '8px', fontSize: '18px' }}>Stage</div>
);
}
let active = true;
if (target instanceof Entity) {
active = target.activeModes.includes(this.props.game.state.mode);
}
return (
<div>
<div style={{ fontWeight: 600, fontFamily: 'arial', paddingTop: '8px', paddingBottom: '8px', fontSize: '18px' }}>{target.name}</div>
<div>
x: {target.x}, y: {target.y}
</div>
<div>
{active ? "Active" : "Inactive"}
</div>
<div>
xAbs: {target.positionAbsolute().x}, yAbs: {target.positionAbsolute().y}
</div>
<div>
width: {target.width}, height: {target.height}
</div>
<div>
visible: {target.visible ? "true" : "false"}
</div>
<div>
scaleX: {target.scale.x.toFixed(2)} scaleY: {target.scale.y.toFixed(2)}
</div>
{
target instanceof TextEntity
? <div>text: {target.html}</div>
: <div></div>
}
</div>
);
};
renderHierarchy() {
return (<div>
<Hierarchy
selectedEntity={this.state.selected}
setMoused={this.setMoused}
setSelected={this.setSelected}
root={this.props.game.stage}
gameState={this.props.game.state}
/>
<Hierarchy
selectedEntity={this.state.selected}
setMoused={this.setMoused}
setSelected={this.setSelected}
root={this.props.game.fixedCameraStage}
gameState={this.props.game.state}
/>
</div>)
}
render() {
return (
<div style={{
display: "flex",
flexDirection: "row",
borderLeft: IS_DEBUG ? "1px solid lightgray" : 0,
marginLeft: '16px',
paddingLeft: '8px',
}}>
<div style={{
overflow: "auto",
height: "90vh",
fontFamily: 'arial',
fontSize: '14px',
}}>
{this.props.game && this.props.game.stage && IS_DEBUG &&
<div style={{ paddingLeft: '8px', }}>
<div style={{ fontFamily: "arial", marginBottom: '8px', fontSize: '14px', width: '300px', padding: '8px' }}>
Note: This debugging panel is only shown in development, or production with ?debug=true.
</div>
<div style={{ fontWeight: 600, fontFamily: 'arial', paddingBottom: '8px', fontSize: '18px' }}>Debug Options</div>
<DebugFlagButtons flags={this.props.debugFlags} />
<div>
Draw Count: {Debug.GetDrawCount()}
</div>
{this.renderSelected()}
<div style={{ fontWeight: 600, fontFamily: 'arial', paddingTop: '8px', paddingBottom: '8px', fontSize: '18px' }}>Debug Hierarchy</div>
{this.renderHierarchy()}
</div>
}
</div>
</div>
);
}
}
export const CreateGame = (game: BaseGame<any, any, any>, debugFlags: DebugFlagsType) => {
ReactDOM.render(
<React.StrictMode>
<GameReactWrapper
game={game}
debugFlags={debugFlags}
/>
</React.StrictMode>,
document.getElementById('root')
);
ReactDOM.render(
<React.StrictMode>
<Log />
</React.StrictMode>,
document.getElementById('log')
);
}
export const originalConsoleLog = console.log;
const allLogs: any[][] = [];
// console.log = (...data: any[]) => {
// allLogs.push(data);
// };
export const Log: React.FC<{}> = (props: {}) => {
const [logs, setLogs] = React.useState<any[][]>([]);
React.useEffect(() => {
const interval = setInterval(() => {
setLogs(allLogs.slice());
}, 400);
return () => clearInterval(interval);
}, []);
return (
<div>
{
logs.map(logItems => {
return (
<div>
{
logItems.map(log => <span>{JSON.stringify(log)}</span>)
}
</div>
);
})
}
</div>
)
}