Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(GameScreen): add "follow my bot" button #89

Merged
Merged
Changes from 10 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
86d754c
added button to follow bot
Nov 27, 2024
1c24502
use the right bot coordinate for follow
Nov 29, 2024
07b2fb3
refactored zoom functions to be generally used
Nov 30, 2024
17dd145
constraint followBot zoom in to map borders
Nov 30, 2024
ad1d859
refactored zoomIn and zoomOut functions to be just 1 function
Dec 1, 2024
0e7c08f
disable panning and mouse grab function when following bot
Dec 1, 2024
c17db6a
fix typoes and follow coding standards
Dec 2, 2024
e135f84
Merge branch 'main' into followButton
Dec 2, 2024
d53c450
changed button label
Dec 2, 2024
c13bfeb
fixed error of follow function on load
Dec 3, 2024
ff72b58
code cleanup
Dec 3, 2024
c76fed5
more code clean up
Dec 3, 2024
9f501e9
disabled zoom in and out with mouse wheen when following
Dec 3, 2024
0d76284
fixed follow issues, put indication if following or not
Dec 8, 2024
5bc6fa6
Merge branch 'main' into followButton
Dec 8, 2024
06ddebe
code cleanup
Dec 8, 2024
8b909e0
removed username scale adjustment
Dec 8, 2024
e08cd40
moved username check, used @onclick, followBot zoom in is based on bo…
Dec 9, 2024
83c79f9
Merge branch 'main' into followButton
Dec 9, 2024
c560264
change followBot to toggle to toggleFollowMeMode
Dec 10, 2024
6d5100d
change follow function name to zoomInToPlayer
Dec 10, 2024
52161a0
Move zoom function to utils
Dec 11, 2024
d5a232d
hide the "follow my bot" button if the user isn't logged in.
Dec 17, 2024
1bc43bf
feat(login): view current game without login (#92)
arilloid Dec 12, 2024
89f4ea4
fix hiding follow button after merge
Dec 17, 2024
13ddd8d
Merge branch 'main' into followButton
Dec 17, 2024
a64d2c2
clean up
Dec 18, 2024
6003bdf
added tests for getZoomScale function
Dec 19, 2024
7ac1c3a
fix test prompt
aldrin312 Dec 19, 2024
8d98688
Made getZoomScale tests prompts more specific.
Dec 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 129 additions & 75 deletions components/GameScreen.vue
aldrin312 marked this conversation as resolved.
Show resolved Hide resolved
yurijmikhalevich marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@
import { Application, Graphics, Text, FillGradient, Assets, type Texture, Sprite } from "pixi.js";

const refreshIntervalMs = 1000;
const zoomSpeed = 0.05;
const zoomSpeed = 0.1;
const minZoom = 1;
const maxZoom = 3;
let startZoomTime: number;
const zoomDuration = 500;
let targetScale: number;
let targetPos: { x: number; y: number };
let isZooming = false;
let isFollowing = false;
aldrin312 marked this conversation as resolved.
Show resolved Hide resolved

const { data: gameState, refresh } = await useFetch("/api/state");
const intervalRef = ref<number | null>(null);
Expand Down Expand Up @@ -56,6 +62,84 @@ type DrawBotArgs = {
graphics: Graphics;
};

function smoothZoom(Pos: { x: number; y: number }, scale: number) {
aldrin312 marked this conversation as resolved.
Show resolved Hide resolved
if (!appRef.value) {
return;
}

if (isZooming) {
return; // Prevent multiple simultaneous zooms
}
isZooming = true;
targetScale = scale;
targetPos = Pos;
startZoomTime = performance.now();

requestAnimationFrame(animateZoom);
}

function animateZoom(currentTime: number) {
if (!appRef.value || !appRef.value.stage || !startZoomTime) {
return;
}

const elapsedTime = currentTime - startZoomTime;
const progress = Math.min(elapsedTime / zoomDuration, 1);

// Get the current scale and the scale change based on progress
const currentScale = appRef.value.stage.scale.x;
const newScale = currentScale + (targetScale - currentScale) * progress;

// Calculate the world position relative to the current scale
const worldPos = {
x: (targetPos.x - appRef.value.stage.position.x) / appRef.value.stage.scale.x,
y: (targetPos.y - appRef.value.stage.position.y) / appRef.value.stage.scale.y,
};

appRef.value.stage.scale.set(newScale);

const newScreenPos = {
x: worldPos.x * newScale + appRef.value.stage.position.x,
y: worldPos.y * newScale + appRef.value.stage.position.y,
};

// Adjust the stage position to follow the zoom focus point
appRef.value.stage.position.set(
Math.min(0, Math.max(appRef.value.stage.position.x - (newScreenPos.x - targetPos.x), appRef.value.screen.width - appRef.value.screen.width * newScale)),
Math.min(0, Math.max(appRef.value.stage.position.y - (newScreenPos.y - targetPos.y), appRef.value.screen.height - appRef.value.screen.height * newScale)),
);

if (progress < 1) {
requestAnimationFrame(animateZoom);
} else {
isZooming = false;
}
}
function followFirstBot(botx: number, boty: number, username: string) {
if (!appRef.value || !gameState.value) {
return;
aldrin312 marked this conversation as resolved.
Show resolved Hide resolved
}

const firstBot = Object.values(gameState.value.bots)?.[0];
if (!firstBot) {
return;
}
if (username == firstBot.username) {
const currentPos = appRef.value.stage.position;
const targetPos = {
x: -botx * appRef.value.stage.scale.x + appRef.value.screen.width / 2,
y: -boty * appRef.value.stage.scale.y + appRef.value.screen.height / 2,
};
const newPosX = currentPos.x + (targetPos.x - currentPos.x) * 0.1;
const newPosY = currentPos.y + (targetPos.y - currentPos.y) * 0.1;

appRef.value.stage.position.set(
Math.min(0, Math.max(newPosX, appRef.value.screen.width - appRef.value.screen.width * appRef.value.stage.scale.x)),
Math.min(0, Math.max(newPosY, appRef.value.screen.height - appRef.value.screen.height * appRef.value.stage.scale.y)),
);
}
}

function setSpritePositionAndSize({
sprite,
bot: { x, y, radius },
Expand Down Expand Up @@ -133,6 +217,10 @@ async function drawBot({ bot, graphics, botDirection }: DrawBotArgs) {
setUsernamePosition({ username, bot });

graphics.addChild(username);

// Adjust username text scale to prevent zooming effect
const stageScale = appRef.value?.stage.scale.x ?? 1;
username.scale.set(1 / stageScale, 1 / stageScale);
}

function destroyGraphics(graphics: Graphics) {
Expand Down Expand Up @@ -179,53 +267,30 @@ watch(gameState, async (newState, prevState) => {
autoDensity: true,
});

let isZoomingOut = false;
const zoomDuration = 500; // Duration of the zoom-out effect in milliseconds
let startZoomTime: number | null = null;
let startMousePos = { x: 0, y: 0 };

// Functions to handle smooth zoom out
function smoothZoomOut(mousePos: { x: number; y: number }) {
if (!appRef.value) {
function follow() {
yurijmikhalevich marked this conversation as resolved.
Show resolved Hide resolved
isFollowing = !isFollowing;
if (!gameState.value) {
return;
}

isZoomingOut = true;
startZoomTime = performance.now();
startMousePos = mousePos;
requestAnimationFrame(animateZoomOut);
const firstBot = Object.values(gameState.value.bots)[0];
if (firstBot) {
const newScale = Math.max(minZoom, Math.min(maxZoom, app.stage.scale.x + 10));
aldrin312 marked this conversation as resolved.
Show resolved Hide resolved
const botPos = { x: firstBot.x, y: firstBot.y };
if (isFollowing) {
smoothZoom(botPos, newScale);
} else {
smoothZoom(botPos, 1);
}
}
}

function animateZoomOut(currentTime: number) {
if (!appRef.value || !startZoomTime) {
return;
};

const elapsedTime = currentTime - startZoomTime;
const progress = Math.min(elapsedTime / zoomDuration, 1);
const newScale = minZoom + (appRef.value.stage.scale.x - minZoom) * (1 - progress);

const worldPos = {
x: (startMousePos.x - appRef.value.stage.position.x) / appRef.value.stage.scale.x,
y: (startMousePos.y - appRef.value.stage.position.y) / appRef.value.stage.scale.y,
};

appRef.value.stage.scale.set(newScale);

const newScreenPos = {
x: worldPos.x * newScale + appRef.value.stage.position.x,
y: worldPos.y * newScale + appRef.value.stage.position.y,
};

appRef.value.stage.position.set(
Math.min(0, Math.max(appRef.value.stage.position.x - (newScreenPos.x - startMousePos.x), appRef.value.screen.width - appRef.value.screen.width * newScale)),
Math.min(0, Math.max(appRef.value.stage.position.y - (newScreenPos.y - startMousePos.y), appRef.value.screen.height - appRef.value.screen.height * newScale)),
);
if (progress < 1) {
requestAnimationFrame(animateZoomOut);
} else {
isZoomingOut = false;
}
const button = document.getElementById("button");

if (button) {
button.addEventListener("click", function (event) {
event.preventDefault();
follow();
});
}

// Mouse wheel event listener
Expand All @@ -234,32 +299,10 @@ watch(gameState, async (newState, prevState) => {
canvas.value?.addEventListener("wheel", (event) => {
event.preventDefault();
const mousePos = { x: event.offsetX, y: event.offsetY };
if (event.deltaY > 0) {
// Zoom out when scrolling down
if (!isZoomingOut) {
smoothZoomOut(mousePos);
}
} else {
// Existing zoom-in functionality
const zoomFactor = event.deltaY * -zoomSpeed;
const newScale = Math.max(minZoom, Math.min(maxZoom, app.stage.scale.x + zoomFactor));

const worldPos = {
x: (mousePos.x - app.stage.position.x) / app.stage.scale.x,
y: (mousePos.y - app.stage.position.y) / app.stage.scale.y,
};

app.stage.scale.set(newScale);

const newScreenPos = {
x: worldPos.x * newScale + app.stage.position.x,
y: worldPos.y * newScale + app.stage.position.y,
};

app.stage.position.set(
Math.min(0, Math.max(app.stage.position.x - (newScreenPos.x - mousePos.x), app.screen.width - app.screen.width * newScale)),
Math.min(0, Math.max(app.stage.position.y - (newScreenPos.y - mousePos.y), app.screen.height - app.screen.height * newScale)),
);
const zoomFactor = (event.deltaY * -zoomSpeed) * 100;
const newScale = Math.max(minZoom, Math.min(maxZoom, app.stage.scale.x + zoomFactor));
smoothZoom(mousePos, newScale);
if (!isFollowing) {
if (newScale > 1) {
gameScreen.value?.classList.add("cursor-grab");
} else {
Expand All @@ -273,15 +316,17 @@ watch(gameState, async (newState, prevState) => {
let startDragPos = { x: 0, y: 0 };

canvas.value?.addEventListener("mousedown", (event) => {
if (app.stage.scale.x > 1) {
gameScreen.value?.classList.add("cursor-grabbing");
if (!isFollowing) {
if (app.stage.scale.x > 1) {
gameScreen.value?.classList.add("cursor-grabbing");
}
isDragging = true;
startDragPos = { x: event.offsetX, y: event.offsetY };
}
isDragging = true;
startDragPos = { x: event.offsetX, y: event.offsetY };
});

canvas.value?.addEventListener("mousemove", (event) => {
if (isDragging && canvas.value) {
if (isDragging && canvas.value && !isFollowing) {
const dx = event.offsetX - startDragPos.x;
const dy = event.offsetY - startDragPos.y;
const newPosX = app.stage.position.x + dx;
Expand Down Expand Up @@ -417,6 +462,10 @@ watch(gameState, async (newState, prevState) => {
const x = prevBot.x + (bot.x - prevBot.x) * progress;
const y = prevBot.y + (bot.y - prevBot.y) * progress;
const botDirection = getDirection(prevBot, bot);
// follow bot
if (isFollowing) {
followFirstBot(x, y, bot.username);
}

drawBot({ bot: { ...bot, x, y }, graphics: existingBot, botDirection });
}
Expand All @@ -437,6 +486,11 @@ watch(gameState, async (newState, prevState) => {
:style="{ maxWidth: gameState?.width + 'px' }"
>
<div class="flex flex-row justify-end mb-2 mt-1 mx-4 gap-6">
<ButtonLink
id="button"
>
follow my bot
</ButtonLink>
<AnchorLink href="/rating">
rating
</AnchorLink>
Expand Down