Skip to content

Commit

Permalink
Merge pull request #15 from traP-jp/feat/move
Browse files Browse the repository at this point in the history
ユーザーの移動を実装
  • Loading branch information
ikura-hamu authored Jan 21, 2025
2 parents 36e6f9f + 7e9716d commit a5b50c8
Show file tree
Hide file tree
Showing 7 changed files with 293 additions and 35 deletions.
4 changes: 2 additions & 2 deletions client/src/App.css
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#root {
/* #root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
} */

.logo {
height: 6em;
Expand Down
36 changes: 3 additions & 33 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,11 @@
import "./App.css";
import { useMemo } from "react";

import { BlurFilter, TextStyle } from "pixi.js";
import { Stage, Container, Sprite, Text } from "@pixi/react";
import { Button } from "antd";
import Canvas from "./pixi/Canvas";

const App = () => {
const blurFilter = useMemo(() => new BlurFilter(2), []);
const bunnyUrl = "https://pixijs.io/pixi-react/img/bunny.png";
return (
<div className="flex justify-center items-center h-screen">
<Stage width={800} height={600} options={{ background: 0x1099bb }}>
<Sprite image={bunnyUrl} x={300} y={150} />
<Sprite image={bunnyUrl} x={500} y={150} />
<Sprite image={bunnyUrl} x={400} y={200} />

<Container x={200} y={200}>
<Text
text="Hello World"
anchor={0.5}
x={220}
y={150}
filters={[blurFilter]}
style={
new TextStyle({
align: "center",
fill: "0xffffff",
fontSize: 50,
letterSpacing: 20,
dropShadow: true,
dropShadowColor: "#E72264",
dropShadowDistance: 6,
})
}
/>
</Container>
</Stage>
<div className="flex">
<Canvas />
<div>
タイムライン
<Button type="primary">Primary Button</Button>
Expand Down
146 changes: 146 additions & 0 deletions client/src/pixi/Canvas.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { Stage } from "@pixi/react";
import World from "./World";
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import {
Position,
DisplayPosition,
displayPositionToPosition,
} from "./Position";
import Explorer from "./components/Explorer";

interface Props {
className?: string;
}

const calcNewPosition = (position: Position, diff: Position): Position => {
const x = Math.max(Math.min(position.x + diff.x, 2000), 0);
const y = Math.max(Math.min(position.y + diff.y, 2000), 0);
return { x, y };
};

const Canvas: React.FC<Props> = (props) => {
const [userPosition, setUserPosition] = useState<Position | null>(null);
const [fieldSize, setFieldSize] = useState<{
width: number;
height: number;
} | null>(null);
const [intervalID, setIntervalID] = useState<number | null>(null);
const stageRef = useRef<HTMLDivElement>(null);

useEffect(() => {
const width = (window.innerWidth * 3) / 5;
const height = window.innerHeight;

setFieldSize({
width: width,
height: height,
});
setUserPosition({
x: width / 2,
y: height / 2,
});
// TODO: リサイズオブザーバー入れる
}, []);

const userDisplayPosition = useMemo(() => {
if (fieldSize === null) {
return null;
}
return {
left: fieldSize.width / 2,
top: fieldSize.height / 2,
};
}, [fieldSize]);

const updateUserPosition = useCallback((targetPosition: Position) => {
setUserPosition((position) => {
if (position === null) {
return null;
}
const diff = {
x: targetPosition.x - position.x,
y: targetPosition.y - position.y,
};
if (Math.abs(diff.x) < 3 && Math.abs(diff.y) < 3) {
return targetPosition;
}
const nextPosition = calcNewPosition(position, {
x: diff.x / 10,
y: diff.y / 10,
});
return nextPosition;
});
}, []);

const onFieldClick = useCallback(
(e: React.MouseEvent<HTMLCanvasElement, MouseEvent>) => {
if (stageRef.current === null) {
return;
}
const stage = stageRef.current;
const stageRect = stage.getBoundingClientRect();

// クリックされた位置の、左画面の左上からの座標
const clickDisplayPosition: DisplayPosition = {
left: e.clientX - stageRect.left,
top: e.clientY - stageRect.top,
};

if (userPosition === null || userDisplayPosition === null) {
return;
}

// クリックされた場所に対応するワールド上の座標
const clickPosition = displayPositionToPosition(
clickDisplayPosition,
userPosition,
userDisplayPosition,
);

if (intervalID !== null) {
clearInterval(intervalID);
}
const id = setInterval(() => {
updateUserPosition(clickPosition);
}, 1000 / 60);
setIntervalID(id);
},
[intervalID, updateUserPosition, userDisplayPosition, userPosition],
);

if (
fieldSize === null ||
userPosition === null ||
userDisplayPosition === null
) {
return;
}

return (
<div ref={stageRef}>
<Stage // Field
{...fieldSize}
options={{ background: 0x1099bb }}
className={props.className}
onClick={onFieldClick}
>
<World
userPosition={userPosition}
userDisplayPosition={userDisplayPosition}
/>
<Explorer
imgURL="https://q.trap.jp/api/v3/public/icon/ikura-hamu"
displayPosition={userDisplayPosition}
/>
</Stage>
</div>
);
};

export default Canvas;
20 changes: 20 additions & 0 deletions client/src/pixi/Position.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export interface Position {
x: number;
y: number;
}

export interface DisplayPosition {
left: number;
top: number;
}

export const displayPositionToPosition = (
displayPosition: DisplayPosition,
userPosition: Position,
userDisplayPosition: DisplayPosition,
): Position => {
return {
x: displayPosition.left + userPosition.x - userDisplayPosition.left,
y: displayPosition.top + userPosition.y - userDisplayPosition.top,
};
};
62 changes: 62 additions & 0 deletions client/src/pixi/World.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Container, Sprite } from "@pixi/react";
import Rectangle from "./components/Rectangle";
import "@pixi/events";
import { DisplayPosition, Position } from "./Position";
import React from "react";

interface Props {
userPosition: Position;
userDisplayPosition: DisplayPosition;
}

const World: React.FC<Props> = ({ userPosition, userDisplayPosition }) => {
return (
<Container
width={2000}
height={2000}
x={-userPosition.x + userDisplayPosition.left}
y={-userPosition.y + userDisplayPosition.top}
anchor={{ x: 0, y: 0 }}
interactive={true}
>
<Rectangle
lineWidth={2}
color={0xffffff}
width={2000}
height={2000}
fillColor={0xeeeeee}
fillAlpha={1}
/>
<Sprite
image={"https://q.trap.jp/api/v3/public/icon/ikura-hamu"}
x={0}
y={0}
width={100}
height={100}
/>
<Sprite
image={"https://q.trap.jp/api/v3/public/icon/ikura-hamu"}
x={0}
y={1900}
width={100}
height={100}
/>
<Sprite
image={"https://q.trap.jp/api/v3/public/icon/ikura-hamu"}
x={1900}
y={0}
width={100}
height={100}
/>
<Sprite
image={"https://q.trap.jp/api/v3/public/icon/ikura-hamu"}
x={1900}
y={1900}
width={100}
height={100}
/>
</Container>
);
};

export default World;
22 changes: 22 additions & 0 deletions client/src/pixi/components/Explorer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Sprite } from "@pixi/react";
import { DisplayPosition } from "../Position";

export interface Props {
imgURL: string;
displayPosition: DisplayPosition;
}

const Explorer: React.FC<Props> = ({ displayPosition, imgURL }) => {
return (
<Sprite
image={imgURL}
x={displayPosition.left}
y={displayPosition.top}
anchor={{ x: 0.5, y: 0.5 }}
width={50}
height={50}
/>
);
};

export default Explorer;
38 changes: 38 additions & 0 deletions client/src/pixi/components/Rectangle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Graphics } from "@pixi/react";
import { useCallback } from "react";
import * as PIXI from "pixi.js";

interface Props {
lineWidth: number;
color: number;
width: number;
height: number;
fillColor?: number;
fillAlpha?: number;
}

const Rectangle: React.FC<Props> = (props) => {
const draw = useCallback(
(g: PIXI.Graphics) => {
g.clear();
g.lineStyle(props.lineWidth, props.color);
if (props.fillColor !== undefined) {
g.beginFill(props.fillColor, props.fillAlpha ?? 1);
}
g.drawRect(
props.lineWidth,
props.lineWidth,
props.width - 2 * props.lineWidth,
props.height - 2 * props.lineWidth,
);
if (props.fillColor !== undefined) {
g.endFill();
}
},
[props],
);

return <Graphics draw={draw} />;
};

export default Rectangle;

0 comments on commit a5b50c8

Please sign in to comment.