diff --git a/EN_README.md b/EN_README.md index ac13dc4..891291a 100644 --- a/EN_README.md +++ b/EN_README.md @@ -186,11 +186,29 @@ If you like this project, consider starring it~ You can also watch intro videos ``` For supported models, refer to [litellm documentation](https://docs.litellm.ai/docs/providers) -4. Run the simulator: +4. Run the simulator (Local Pygame version): ```bash python -m src.run.run ``` +5. Run Web version (WIP): + Start both backend and frontend. + + **Backend:** + ```bash + # In project root + python src/server/main.py + ``` + + **Frontend:** + ```bash + # In a new terminal, inside 'web' directory + cd web + npm run dev + ``` + Then visit http://localhost:5173 + + ## Contributors - Aku, for world design & discussion diff --git a/README.md b/README.md index 10498ab..bf3a061 100644 --- a/README.md +++ b/README.md @@ -189,11 +189,29 @@ ``` 具体支持的模型请参考 [litellm文档](https://docs.litellm.ai/docs/providers) -4. 运行模拟器: +4. 运行模拟器(本地 Pygame 版): ```bash python -m src.run.run ``` +5. 运行 Web 版本(开发中): + 需要同时启动后端和前端。 + + **后端:** + ```bash + # 在项目根目录 + python src/server/main.py + ``` + + **前端:** + ```bash + # 打开新终端,进入 web 目录 + cd web + npm run dev + ``` + 然后访问 http://localhost:5173 + + ## 贡献者 - Aku, 世界观\玩法设计与讨论 diff --git a/assets/females/16.png b/assets/females/16.png new file mode 100644 index 0000000..0290b96 Binary files /dev/null and b/assets/females/16.png differ diff --git a/src/server/main.py b/src/server/main.py index f72e77a..3568ae1 100644 --- a/src/server/main.py +++ b/src/server/main.py @@ -2,8 +2,9 @@ import sys import os import asyncio from contextlib import asynccontextmanager -from fastapi import FastAPI +from fastapi import FastAPI, WebSocket, WebSocketDisconnect from fastapi.middleware.cors import CORSMiddleware +from fastapi.staticfiles import StaticFiles import uvicorn # 确保可以导入 src 模块 @@ -24,6 +25,29 @@ game_instance = { "sim": None } +class ConnectionManager: + def __init__(self): + self.active_connections: list[WebSocket] = [] + + async def connect(self, websocket: WebSocket): + await websocket.accept() + self.active_connections.append(websocket) + + def disconnect(self, websocket: WebSocket): + self.active_connections.remove(websocket) + + async def broadcast(self, message: dict): + import json + try: + # 简单序列化,实际生产可能需要更复杂的 Encoder + txt = json.dumps(message, default=str) + for connection in self.active_connections: + await connection.send_text(txt) + except Exception as e: + print(f"Broadcast error: {e}") + +manager = ConnectionManager() + def init_game(): """初始化游戏世界,逻辑复用自 src/run/run.py""" print("正在初始化游戏世界...") @@ -54,10 +78,47 @@ def init_game(): game_instance["sim"] = sim print("游戏世界初始化完成!") +async def game_loop(): + """后台自动运行游戏循环""" + print("后台游戏循环已启动...") + while True: + # 控制游戏速度,例如每秒 1 次更新 + await asyncio.sleep(1.0) + + try: + sim = game_instance.get("sim") + world = game_instance.get("world") + + if sim and world: + # 执行一步 + events = await sim.step() + + # 构造广播数据包 + state = { + "type": "tick", + "year": int(world.month_stamp.get_year()), + "month": world.month_stamp.get_month().value, + "events": [str(e) for e in events], + # 暂时只发前 50 个角色的位置更新,减少数据量 + "avatars": [ + { + "id": str(a.id), + "x": int(getattr(a, "pos_x", 0)), + "y": int(getattr(a, "pos_y", 0)) + } + for a in list(world.avatar_manager.avatars.values())[:50] + ] + } + await manager.broadcast(state) + except Exception as e: + print(f"Game loop error: {e}") + @asynccontextmanager async def lifespan(app: FastAPI): # 启动时初始化 init_game() + # 启动后台任务 + asyncio.create_task(game_loop()) yield # 关闭时清理(如果需要) @@ -72,10 +133,33 @@ app.add_middleware( allow_headers=["*"], ) +# 挂载静态资源 +ASSETS_PATH = os.path.join(os.path.dirname(__file__), '..', '..', 'assets') +if os.path.exists(ASSETS_PATH): + app.mount("/assets", StaticFiles(directory=ASSETS_PATH), name="assets") +else: + print(f"Warning: Assets path not found: {ASSETS_PATH}") + @app.get("/") def read_root(): return {"status": "online", "app": "Cultivation World Simulator Backend"} +@app.websocket("/ws") +async def websocket_endpoint(websocket: WebSocket): + await manager.connect(websocket) + try: + while True: + # 保持连接活跃,接收客户端指令(目前暂不处理复杂指令) + data = await websocket.receive_text() + # echo test + if data == "ping": + await websocket.send_text('{"type":"pong"}') + except WebSocketDisconnect: + manager.disconnect(websocket) + except Exception as e: + print(f"WS Error: {e}") + manager.disconnect(websocket) + @app.get("/api/state") def get_state(): """获取当前世界的一个快照(调试模式)""" @@ -121,7 +205,9 @@ def get_state(): "name": aname, "x": ax, "y": ay, - "action": str(aaction) + "action": str(aaction), + "gender": str(a.gender.value), + "pic_id": (hash(a.id) % 15) + 1 }) except Exception as e: return {"step": 3, "error": str(e)} @@ -137,6 +223,48 @@ def get_state(): except Exception as e: return {"step": 0, "error": "Fatal: " + str(e)} +@app.get("/api/map") +def get_map(): + """获取静态地图数据(仅需加载一次)""" + world = game_instance.get("world") + if not world or not world.map: + return {"error": "No map"} + + # 构造二维数组 + w, h = world.map.width, world.map.height + map_data = [] + for y in range(h): + row = [] + for x in range(w): + tile = world.map.get_tile(x, y) + row.append(tile.type.name) + map_data.append(row) + + # 构造区域列表 + regions_data = [] + if world.map and hasattr(world.map, 'regions'): + for r in world.map.regions.values(): + # 确保有中心点 + if hasattr(r, 'center_loc') and r.center_loc: + rtype = "unknown" + if hasattr(r, 'get_region_type'): + rtype = r.get_region_type() + + regions_data.append({ + "id": r.id, + "name": r.name, + "type": rtype, + "x": r.center_loc[0], + "y": r.center_loc[1] + }) + + return { + "width": w, + "height": h, + "data": map_data, + "regions": regions_data + } + @app.post("/api/step") async def step_world(): """手动触发一帧(一个月)""" diff --git a/web/package-lock.json b/web/package-lock.json index d2e36cb..cb4297d 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -8,10 +8,13 @@ "name": "web", "version": "0.0.0", "dependencies": { + "@vueuse/core": "^14.0.0", "naive-ui": "^2.43.2", "pinia": "^3.0.4", + "pixi-viewport": "^6.0.3", "pixi.js": "^8.14.2", - "vfonts": "^0.0.3" + "vfonts": "^0.0.3", + "vue3-pixi": "^1.0.0-beta.3" }, "devDependencies": { "@vitejs/plugin-vue": "^5.2.4", @@ -20,6 +23,15 @@ "vite": "^5.4.21" } }, + "node_modules/@antfu/utils": { + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.10.tgz", + "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", @@ -1141,6 +1153,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/gradient-parser": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@types/gradient-parser/-/gradient-parser-0.1.5.tgz", + "integrity": "sha512-r7K3NkJz3A95WkVVmjs0NcchhHstC2C/VIYNX4JC6tieviUNo774FFeOHjThr3Vw/WCeMP9kAT77MKbIRlO/4w==", + "license": "MIT" + }, "node_modules/@types/katex": { "version": "0.16.7", "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz", @@ -1162,6 +1180,12 @@ "@types/lodash": "*" } }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "license": "MIT" + }, "node_modules/@vitejs/plugin-vue": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", @@ -1318,6 +1342,44 @@ "license": "MIT", "peer": true }, + "node_modules/@vueuse/core": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.0.0.tgz", + "integrity": "sha512-d6tKRWkZE8IQElX2aHBxXOMD478fHIYV+Dzm2y9Ag122ICBpNKtGICiXKOhWU3L1kKdttDD9dCMS4bGP3jhCTQ==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "14.0.0", + "@vueuse/shared": "14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/@vueuse/metadata": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.0.0.tgz", + "integrity": "sha512-6yoGqbJcMldVCevkFiHDBTB1V5Hq+G/haPlGIuaFZHpXC0HADB0EN1ryQAAceiW+ryS3niUwvdFbGiqHqBrfVA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.0.0.tgz", + "integrity": "sha512-mTCA0uczBgurRlwVaQHfG0Ja7UdGe4g9mwffiJmvLiTtp1G4AQyIjej6si/k8c8pUwTfVpNufck+23gXptPAkw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, "node_modules/@webgpu/types": { "version": "0.1.66", "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.66.tgz", @@ -1797,6 +1859,27 @@ } } }, + "node_modules/pixi-filters": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/pixi-filters/-/pixi-filters-6.1.4.tgz", + "integrity": "sha512-6QdkhR8hZ/jXyV7GZG8R0UKkRy9jPeZsOnHaQiKSFEe4tGJ4PfUG90vaC9eyi7g+YKxhKLpNOXu6tmO1+R2tpQ==", + "license": "MIT", + "dependencies": { + "@types/gradient-parser": "^0.1.2" + }, + "peerDependencies": { + "pixi.js": ">=8.0.0-0" + } + }, + "node_modules/pixi-viewport": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/pixi-viewport/-/pixi-viewport-6.0.3.tgz", + "integrity": "sha512-2+qPJ0/n+8hQYhWvY+795+x9y3MiUrCOWacK0DY53whowWaGdx9iDocy7z1pBwjkZhC52YvrJQuZKK0sdVLtBw==", + "license": "MIT", + "peerDependencies": { + "pixi.js": ">=8" + } + }, "node_modules/pixi.js": { "version": "8.14.2", "resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-8.14.2.tgz", @@ -2122,6 +2205,106 @@ } } }, + "node_modules/vue3-pixi": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/vue3-pixi/-/vue3-pixi-1.0.0-beta.3.tgz", + "integrity": "sha512-WHDA7/VPwAGTnwPSQTxcwTOOjspjX8vh8P3Lgfrg6o2lI4mcjGmHlKiKIZabwlNYqmyr+PZKdnUtjzN/h/0e0w==", + "license": "MIT", + "dependencies": { + "@antfu/utils": "^0.7.10", + "@vueuse/core": "^10.11.1", + "nanoid": "^4.0.2", + "pixi-filters": "^6.1.3", + "pixi.js": "^8.11.0", + "vue-demi": "^0.14.10" + } + }, + "node_modules/vue3-pixi/node_modules/@types/web-bluetooth": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", + "license": "MIT" + }, + "node_modules/vue3-pixi/node_modules/@vueuse/core": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz", + "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "10.11.1", + "@vueuse/shared": "10.11.1", + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/vue3-pixi/node_modules/@vueuse/metadata": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz", + "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/vue3-pixi/node_modules/@vueuse/shared": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.1.tgz", + "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==", + "license": "MIT", + "dependencies": { + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/vue3-pixi/node_modules/nanoid": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-4.0.2.tgz", + "integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^14 || ^16 || >=18" + } + }, + "node_modules/vue3-pixi/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/vueuc": { "version": "0.4.65", "resolved": "https://registry.npmjs.org/vueuc/-/vueuc-0.4.65.tgz", diff --git a/web/package.json b/web/package.json index 6ecffac..a43e4c9 100644 --- a/web/package.json +++ b/web/package.json @@ -15,9 +15,12 @@ "vite": "^5.4.21" }, "dependencies": { + "@vueuse/core": "^14.0.0", "naive-ui": "^2.43.2", "pinia": "^3.0.4", + "pixi-viewport": "^6.0.3", "pixi.js": "^8.14.2", - "vfonts": "^0.0.3" + "vfonts": "^0.0.3", + "vue3-pixi": "^1.0.0-beta.3" } } diff --git a/web/src/App.vue b/web/src/App.vue index f648688..71ee0b7 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -1,9 +1,16 @@ - diff --git a/web/src/components/game/EntityLayer.vue b/web/src/components/game/EntityLayer.vue new file mode 100644 index 0000000..6dfbef7 --- /dev/null +++ b/web/src/components/game/EntityLayer.vue @@ -0,0 +1,74 @@ + + + + diff --git a/web/src/components/game/GameCanvas.vue b/web/src/components/game/GameCanvas.vue new file mode 100644 index 0000000..4fc5706 --- /dev/null +++ b/web/src/components/game/GameCanvas.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/web/src/components/game/MapLayer.vue b/web/src/components/game/MapLayer.vue new file mode 100644 index 0000000..b3ae348 --- /dev/null +++ b/web/src/components/game/MapLayer.vue @@ -0,0 +1,103 @@ + + + + diff --git a/web/src/components/game/Viewport.vue b/web/src/components/game/Viewport.vue new file mode 100644 index 0000000..aae58c6 --- /dev/null +++ b/web/src/components/game/Viewport.vue @@ -0,0 +1,90 @@ + + + diff --git a/web/src/components/game/composables/useTextures.ts b/web/src/components/game/composables/useTextures.ts new file mode 100644 index 0000000..44e0e4c --- /dev/null +++ b/web/src/components/game/composables/useTextures.ts @@ -0,0 +1,64 @@ +import { ref } from 'vue' +import { Assets, Texture } from 'pixi.js' + +// 全局纹理缓存,避免重复加载 +const textures = ref>({}) +const isLoaded = ref(false) + +export function useTextures() { + const loadTextures = async () => { + if (isLoaded.value) return + + const manifest: Record = { + 'PLAIN': '/assets/tiles/plain.png', + 'WATER': '/assets/tiles/water.png', + 'SEA': '/assets/tiles/sea.png', + 'MOUNTAIN': '/assets/tiles/mountain.png', + 'FOREST': '/assets/tiles/forest.png', + 'CITY': '/assets/tiles/city.png', + 'DESERT': '/assets/tiles/desert.png', + 'RAINFOREST': '/assets/tiles/rainforest.png', + 'GLACIER': '/assets/tiles/glacier.png', + 'SNOW_MOUNTAIN': '/assets/tiles/snow_mountain.png', + 'VOLCANO': '/assets/tiles/volcano.png', + 'GRASSLAND': '/assets/tiles/grassland.png', + 'SWAMP': '/assets/tiles/swamp.png', + 'CAVE': '/assets/tiles/cave.png', + 'RUINS': '/assets/tiles/ruins.png', + 'FARM': '/assets/tiles/farm.png' + } + + // 加载地图纹理 + for (const [key, url] of Object.entries(manifest)) { + try { + textures.value[key] = await Assets.load(url) + } catch (e) { + console.error(`Failed to load texture: ${url}`, e) + } + } + + // 加载角色立绘 (1-16) + for (let i = 1; i <= 16; i++) { + const maleUrl = `/assets/males/${i}.png` + const femaleUrl = `/assets/females/${i}.png` + + try { + textures.value[`male_${i}`] = await Assets.load(maleUrl) + } catch (e) { /* ignore */ } + + try { + textures.value[`female_${i}`] = await Assets.load(femaleUrl) + } catch (e) { /* ignore */ } + } + + isLoaded.value = true + console.log('Textures loaded') + } + + return { + textures, + isLoaded, + loadTextures + } +} + diff --git a/web/src/counter.ts b/web/src/counter.ts deleted file mode 100644 index 09e5afd..0000000 --- a/web/src/counter.ts +++ /dev/null @@ -1,9 +0,0 @@ -export function setupCounter(element: HTMLButtonElement) { - let counter = 0 - const setCounter = (count: number) => { - counter = count - element.innerHTML = `count is ${counter}` - } - element.addEventListener('click', () => setCounter(counter + 1)) - setCounter(0) -} diff --git a/web/src/stores/game.ts b/web/src/stores/game.ts index 9e8b267..d1aef61 100644 --- a/web/src/stores/game.ts +++ b/web/src/stores/game.ts @@ -14,6 +14,7 @@ export const useGameStore = defineStore('game', () => { const year = ref(0) const month = ref(0) const avatars = ref>({}) + const events = ref([]) // 添加事件列表状态 // 计算属性:转换为数组以便遍历 const avatarList = computed(() => Object.values(avatars.value)) @@ -36,6 +37,12 @@ export const useGameStore = defineStore('game', () => { year.value = data.year month.value = data.month + // 更新事件日志 + if (data.events && Array.isArray(data.events)) { + // 将新事件追加到开头 + events.value = [...data.events, ...events.value].slice(0, 100) // 只保留最近100条 + } + // 更新 Avatars(增量更新逻辑:这里后端暂发的是全量/部分列表,直接覆盖位置) if (data.avatars && Array.isArray(data.avatars)) { data.avatars.forEach((av: Avatar) => { @@ -87,6 +94,7 @@ export const useGameStore = defineStore('game', () => { month, avatars, avatarList, + events, // 导出 events connect, fetchInitialState } diff --git a/web/src/style.css b/web/src/style.css index 3bcdbd0..9ac1a15 100644 --- a/web/src/style.css +++ b/web/src/style.css @@ -1,96 +1,44 @@ :root { - font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; + font-family: "HarmonyOS Sans", "PingFang SC", "Microsoft YaHei", system-ui, + -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + line-height: 1.4; font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - + color: #f6f6f6; + background-color: #050608; font-synthesis: none; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; +*, +*::before, +*::after { + box-sizing: border-box; } -a:hover { - color: #535bf2; + +html, +body { + width: 100%; + height: 100%; + margin: 0; + padding: 0; + background-color: #050608; + color: #f6f6f6; } body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; + overflow: hidden; } #app { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; + width: 100%; + height: 100%; + margin: 0; + padding: 0; } -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.vanilla:hover { - filter: drop-shadow(0 0 2em #3178c6aa); -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } +a { + color: inherit; + text-decoration: none; } diff --git a/web/src/typescript.svg b/web/src/typescript.svg deleted file mode 100644 index d91c910..0000000 --- a/web/src/typescript.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/vite.config.ts b/web/vite.config.ts index 4190ce8..7436200 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -1,9 +1,16 @@ import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' +import { compilerOptions } from 'vue3-pixi' // https://vite.dev/config/ export default defineConfig({ - plugins: [vue()], + plugins: [ + vue({ + template: { + compilerOptions, + }, + }), + ], server: { proxy: { '/api': { @@ -14,8 +21,11 @@ export default defineConfig({ target: 'ws://localhost:8002', ws: true, changeOrigin: true, + }, + '/assets': { + target: 'http://localhost:8002', + changeOrigin: true, } } } }) -