Skip to content

Commit

Permalink
Merge pull request #104 from traP-jp/client/display_items
Browse files Browse the repository at this point in the history
フィールドにいろいろを表示
  • Loading branch information
ikura-hamu authored Jan 25, 2025
2 parents 209459a + 9fcc3a5 commit 5fa3ee2
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 70 deletions.
49 changes: 31 additions & 18 deletions client/src/pixi/Canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
} from "../model/position";
import Explorer from "./components/Explorer";
import PIXI from "pixi.js";
import useExplorerDispatcher from "../api/explorer";

const mountHandler = import.meta.env.DEV
? (app: PIXI.Application) => {
Expand All @@ -40,6 +41,7 @@ const Canvas: React.FC<Props> = (props) => {
} | null>(null);
const [intervalID, setIntervalID] = useState<number | null>(null);
const stageRef = useRef<HTMLDivElement>(null);
const dispatcher = useExplorerDispatcher();

useEffect(() => {
const width = (window.innerWidth * 3) / 5;
Expand All @@ -66,25 +68,36 @@ const Canvas: React.FC<Props> = (props) => {
};
}, [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,
const updateUserPosition = useCallback(
(targetPosition: Position) => {
setUserPosition((position) => {
if (position === null || fieldSize === 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) {
dispatcher({
position: targetPosition,
size: fieldSize,
});
return targetPosition;
}
const nextPosition = calcNewPosition(position, {
x: diff.x / 10,
y: diff.y / 10,
});
dispatcher({
position: nextPosition,
size: fieldSize,
});
return nextPosition;
});
return nextPosition;
});
}, []);
},
[dispatcher, fieldSize],
);

const onFieldClick = useCallback(
(e: React.MouseEvent<HTMLCanvasElement, MouseEvent>) => {
Expand Down
144 changes: 92 additions & 52 deletions client/src/pixi/World.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import messagesAtom from "../state/message";
import MessageIcon from "./components/MessageIcon";
import useMessageExpanded from "./hooks/message";
import { isInsideField } from "../util/field";
import speakerPhonesAtom from "../state/speakerPhone";
import reactionsAtom from "../state/reactions";
import explorersAtom from "../state/explorer";
import OtherExplorer from "./components/OtherExplorer";

interface Props {
userPosition: Position;
Expand All @@ -27,7 +31,11 @@ const World: React.FC<Props> = ({
const { expanded, collapseMessage, expandMessage, message } =
useMessageExpanded();
const messages = useAtomValue(messagesAtom);
const messageNodes = [];
const speakerPhones = useAtomValue(speakerPhonesAtom);
const reactions = useAtomValue(reactionsAtom);
const explorers = useAtomValue(explorersAtom);

const messageNodes: JSX.Element[] = [];
for (const message of messages.values()) {
if (!isInsideField(message.position, fieldSize, userPosition)) {
continue;
Expand All @@ -42,24 +50,88 @@ const World: React.FC<Props> = ({
);
}

//TODO: モック用なので後で消す
for (let i = 1; i <= 3; i++) {
messageNodes.push(
<MessageIcon
currentExpandedMessageId={message?.id}
expander={expandMessage}
key={i}
message={{
id: i.toString(),
position: { x: 100 * i + 10, y: 100 * i },
userId: "ikura-hamu",
content: "Hello, World!".repeat(i * 5),
createdAt: new Date(),
updatedAt: new Date(),
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000), // 1 day later
const speakerPhoneNodes = Array.from(speakerPhones.values())
.filter((speakerPhone) =>
isInsideField(speakerPhone.position, fieldSize, userPosition),
)
.map((speakerPhone) => (
<SpeakerPhone
key={speakerPhone.name}
position={speakerPhone.position}
name={speakerPhone.name}
radius={100}
/>
));

const reactionsNodes = Array.from(reactions.values())
.filter((reaction) =>
isInsideField(reaction.position, fieldSize, userPosition),
)
.map((reaction) => (
<Reaction
key={reaction.id}
position={reaction.position}
reaction={reaction.kind}
user={{
name: reaction.userId,
iconURL: traqIconURL(reaction.userId),
}}
/>,
/>
));

const explorerNodes = Array.from(explorers.values()).map((explorer) => {
return (
<OtherExplorer
key={explorer.userId}
explorer={explorer}
previousPosition={explorer.previousPosition}
/>
);
});

//TODO: モック用なので後で消す
{
for (let i = 1; i <= 3; i++) {
messageNodes.push(
<MessageIcon
currentExpandedMessageId={message?.id}
expander={expandMessage}
key={i}
message={{
id: i.toString(),
position: { x: 100 * i + 10, y: 100 * i },
userId: "ikura-hamu",
content: "Hello, World!".repeat(i * 5),
createdAt: new Date(),
updatedAt: new Date(),
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000), // 1 day later
}}
/>,
);
}
for (let i = 4; i <= 6; i++) {
speakerPhoneNodes.push(
<SpeakerPhone
key={i}
position={{ x: 100 * i + 10, y: 100 * i }}
name="SpeakerPhone"
radius={100}
/>,
);
}
for (let i = 7; i <= 9; i++) {
reactionsNodes.push(
<Reaction
key={i}
position={{ x: 100 * i + 10, y: 100 * i }}
reaction="iine"
user={{
name: "ikura-hamu",
iconURL: traqIconURL("ikura-hamu"),
}}
/>,
);
}
}

return (
Expand All @@ -79,42 +151,10 @@ const World: React.FC<Props> = ({
fillColor={0xeeeeee}
fillAlpha={1}
/>
<SpeakerPhone
position={{ x: 1700, y: 1700 }}
name="#gps/times/ikura-hamu"
radius={100}
/>
<SpeakerPhone
position={{ x: 200, y: 200 }}
name="#gps/times/ikura-hamu"
radius={100}
/>

{speakerPhoneNodes}
{messageNodes}
<Reaction
position={{ x: 300, y: 300 }}
reaction="kusa"
user={{
name: "SSlime",
iconURL: traqIconURL("SSlime"),
}}
/>
<Reaction
position={{ x: 200, y: 500 }}
reaction="iine"
user={{
name: "Ras",
iconURL: traqIconURL("Ras"),
}}
/>
<Reaction
position={{ x: 250, y: 500 }}
reaction="pro"
user={{
name: "H1rono_K",
iconURL: traqIconURL("H1rono_K"),
}}
/>
{explorerNodes}
{reactionsNodes}
<Message
expanded={expanded}
message={message}
Expand Down
60 changes: 60 additions & 0 deletions client/src/pixi/components/OtherExplorer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Position } from "../../model/position";
import ExplorerModel from "../../model/explorer";
import Explorer from "./Explorer";
import { useUser } from "../../api/user";
import { traqIconURL } from "../../util/icon";
import { useTick } from "@pixi/react";
import { useState } from "react";

interface Props {
explorer: ExplorerModel;
previousPosition?: Position;
}

const OtherExplorer: React.FC<Props> = ({ explorer, previousPosition }) => {
// previousPosition: 1つ前の受信時の位置
const { position: targetPosition, userId } = explorer; // 目標の位置
const { data, error, isLoading } = useUser(userId);
const [position, setPosition] = useState(targetPosition); // 実際の現在の位置

useTick(() => {
if (previousPosition) {
if (position.x === targetPosition.x && position.y === targetPosition.y) {
return;
}
setPosition((pos) => {
const diff = {
x: targetPosition.x - pos.x,
y: targetPosition.y - pos.y,
};
if (Math.abs(diff.x) < 3 && Math.abs(diff.y) < 3) {
return targetPosition;
}
const speed = 0.1;
return {
x: position.x + diff.x * speed,
y: position.y + diff.y * speed,
};
});
}
});

if (isLoading || error || !data) {
return null;
}
const user = data.user;
if (!user) {
return null;
}

return (
<Explorer
position={position}
imgURL={traqIconURL(user.name)}
isMe={false}
name={user.name}
/>
);
};

export default OtherExplorer;

0 comments on commit 5fa3ee2

Please sign in to comment.