Compare commits

12 Commits

Author SHA1 Message Date
Smile Rex
5304120464 fix
All checks were successful
Create and publish a Docker image 🚀 / build-and-push-image (push) Successful in 1m30s
2026-01-22 17:39:41 +03:00
Smile Rex
2459262cef repl
All checks were successful
Create and publish a Docker image 🚀 / build-and-push-image (push) Successful in 1m37s
2026-01-22 17:22:00 +03:00
Smile Rex
a54b315e5d fix msg types
All checks were successful
Create and publish a Docker image 🚀 / build-and-push-image (push) Successful in 1m29s
2026-01-22 16:54:56 +03:00
Smile Rex
bd072c8604 add joystick
All checks were successful
Create and publish a Docker image 🚀 / build-and-push-image (push) Successful in 1m27s
2026-01-22 10:35:02 +03:00
Smile Rex
10a48c4038 on server addr
All checks were successful
Create and publish a Docker image 🚀 / build-and-push-image (push) Successful in 1m27s
2026-01-22 10:02:28 +03:00
Smile Rex
cfa1afe4cb fix
All checks were successful
Create and publish a Docker image 🚀 / build-and-push-image (push) Successful in 1m29s
2026-01-22 09:44:04 +03:00
Smile Rex
480e9d41f8 set to local addr
Some checks failed
Create and publish a Docker image 🚀 / build-and-push-image (push) Failing after 31s
2026-01-22 09:41:05 +03:00
Smile Rex
24ed3afbaf add tma lib
All checks were successful
Create and publish a Docker image 🚀 / build-and-push-image (push) Successful in 1m23s
2026-01-21 19:22:54 +03:00
Smile Rex
ae4471dec9 fix
All checks were successful
Create and publish a Docker image 🚀 / build-and-push-image (push) Successful in 1m20s
2026-01-21 19:17:52 +03:00
Smile Rex
544160f916 select api
All checks were successful
Create and publish a Docker image 🚀 / build-and-push-image (push) Successful in 1m18s
2026-01-21 18:52:02 +03:00
b7d21decaf Merge pull request 'fix dockerfile' (#2) from feauture into main
All checks were successful
Create and publish a Docker image 🚀 / build-and-push-image (push) Successful in 1m31s
Reviewed-on: #2
2026-01-21 18:44:00 +03:00
93ffcb67a7 Merge pull request 'new tma' (#1) from feauture into main
Some checks failed
Create and publish a Docker image 🚀 / build-and-push-image (push) Failing after 1m1s
Reviewed-on: #1
2026-01-21 18:31:03 +03:00
5 changed files with 136 additions and 27 deletions

View File

@@ -1,13 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>tma-front</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>tma-front</title>
<script src="https://telegram.org/js/telegram-web-app.js"></script>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@@ -2,12 +2,29 @@ import Phaser from "phaser";
export class RemotePlayer {
sprite: Phaser.GameObjects.Rectangle;
label: Phaser.GameObjects.Text;
targetX: number;
targetY: number;
constructor(scene: Phaser.Scene, x: number, y: number, color: number) {
constructor(
scene: Phaser.Scene,
x: number,
y: number,
name: string,
color: number,
) {
this.sprite = scene.add.rectangle(x, y, 32, 32, color);
this.label = scene.add
.text(x, y - 26, name, {
fontSize: "12px",
color: "#ffffff",
stroke: "#000000",
strokeThickness: 3,
})
.setOrigin(0.5);
this.targetX = x;
this.targetY = y;
}
@@ -18,12 +35,17 @@ export class RemotePlayer {
}
update() {
// магия сглаживания
// интерполяция позиции
this.sprite.x = Phaser.Math.Linear(this.sprite.x, this.targetX, 0.15);
this.sprite.y = Phaser.Math.Linear(this.sprite.y, this.targetY, 0.15);
// ник всегда над игроком
this.label.x = this.sprite.x;
this.label.y = this.sprite.y - 26;
}
destroy() {
this.sprite.destroy();
this.label.destroy();
}
}

View File

@@ -4,23 +4,63 @@ export class TouchInput {
dx = 0;
dy = 0;
constructor(scene: Phaser.Scene) {
let start: Phaser.Math.Vector2 | null = null;
private base?: Phaser.GameObjects.Arc;
private knob?: Phaser.GameObjects.Arc;
private start?: Phaser.Math.Vector2;
private readonly radius = 50;
constructor(scene: Phaser.Scene) {
scene.input.on("pointerdown", (pointer: Phaser.Input.Pointer) => {
start = new Phaser.Math.Vector2(pointer.x, pointer.y);
this.start = new Phaser.Math.Vector2(pointer.x, pointer.y);
// основание
this.base = scene.add
.circle(pointer.x, pointer.y, this.radius, 0x000000, 0.35)
.setDepth(1000);
// ручка
this.knob = scene.add
.circle(pointer.x, pointer.y, this.radius / 2, 0xffffff, 0.8)
.setDepth(1001);
});
scene.input.on("pointermove", (pointer: Phaser.Input.Pointer) => {
if (!start) return;
this.dx = Phaser.Math.Clamp((pointer.x - start.x) / 50, -1, 1);
this.dy = Phaser.Math.Clamp((pointer.y - start.y) / 50, -1, 1);
if (!this.start || !this.knob) return;
const dx = pointer.x - this.start.x;
const dy = pointer.y - this.start.y;
const distance = Math.min(this.radius, Math.hypot(dx, dy));
const angle = Math.atan2(dy, dx);
const knobX = this.start.x + Math.cos(angle) * distance;
const knobY = this.start.y + Math.sin(angle) * distance;
this.knob.setPosition(knobX, knobY);
this.dx = Phaser.Math.Clamp(dx / this.radius, -1, 1);
this.dy = Phaser.Math.Clamp(dy / this.radius, -1, 1);
});
scene.input.on("pointerup", () => {
start = null;
this.dx = 0;
this.dy = 0;
this.reset();
});
scene.input.on("pointerout", () => {
this.reset();
});
}
private reset() {
this.dx = 0;
this.dy = 0;
this.start = undefined;
this.base?.destroy();
this.knob?.destroy();
this.base = undefined;
this.knob = undefined;
}
}

View File

@@ -1,3 +1,13 @@
declare global {
interface Window {
Telegram?: {
WebApp: {
initData: string;
};
};
}
}
export class GameSocket {
private socket: WebSocket;
playerId: string | null = null;
@@ -6,6 +16,17 @@ export class GameSocket {
constructor(url: string) {
this.socket = new WebSocket(url);
this.socket.onopen = () => {
const initData = window.Telegram?.WebApp?.initData ?? "";
this.socket.send(
JSON.stringify({
type: "auth",
initData,
}),
);
};
this.socket.onmessage = (e) => {
const msg = JSON.parse(e.data);
@@ -13,10 +34,14 @@ export class GameSocket {
this.playerId = msg.payload.id;
}
if (msg.type === "state") {
if (msg.type === "input") {
this.onState?.(msg.payload);
}
};
this.socket.onerror = (e) => {
console.error("WebSocket error", e);
};
}
sendInput(dx: number, dy: number) {
@@ -24,8 +49,8 @@ export class GameSocket {
this.socket.send(
JSON.stringify({
dx,
dy,
type: "input",
payload: { dx, dy },
}),
);
}

View File

@@ -1,7 +1,18 @@
import Phaser from "phaser";
import { RemotePlayer } from "../entities/RemotePlayer";
import { TouchInput } from "../input/TouchInput";
import { GameSocket } from "../net/Socket";
let localAddr: string = "ws://localhost:8080/ws";
let remoteAddr: string = "wss://tma-api.quizer.space/ws";
let isLocal: boolean = false;
type ServerPlayerState = {
x: number;
y: number;
name: string;
};
export class GameScene extends Phaser.Scene {
players = new Map<string, RemotePlayer>();
socket!: GameSocket;
@@ -9,7 +20,7 @@ export class GameScene extends Phaser.Scene {
create() {
this.inputCtrl = new TouchInput(this);
this.socket = new GameSocket("ws://localhost:8080/ws");
this.socket = new GameSocket(isLocal ? localAddr : remoteAddr);
this.socket.onState = (state) => {
this.syncPlayers(state.players);
@@ -24,7 +35,8 @@ export class GameScene extends Phaser.Scene {
}
}
syncPlayers(serverPlayers: Record<string, { x: number; y: number }>) {
syncPlayers(serverPlayers: Record<string, ServerPlayerState>) {
// добавление / обновление
for (const id in serverPlayers) {
const data = serverPlayers[id];
let player = this.players.get(id);
@@ -32,13 +44,22 @@ export class GameScene extends Phaser.Scene {
if (!player) {
const isMe = id === this.socket.playerId;
const color = isMe ? 0x00ff00 : 0xff4444;
player = new RemotePlayer(this, data.x, data.y, color);
player = new RemotePlayer(
this,
data.x,
data.y,
data.name, // 👈 username от сервера
color,
);
this.players.set(id, player);
}
player.setTarget(data.x, data.y);
}
// удаление отключившихся
for (const [id, player] of this.players) {
if (!serverPlayers[id]) {
player.destroy();