-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #15 from traP-jp/feat/move
ユーザーの移動を実装
- Loading branch information
Showing
7 changed files
with
293 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |