mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 12:51:30 +00:00
create topbar for mobile
This commit is contained in:
+177
-135
@@ -22,170 +22,212 @@ import { GameView } from "../../core/game/GameView";
|
||||
import { WinModal } from "./layers/WinModal";
|
||||
import { SpawnTimer } from "./layers/SpawnTimer";
|
||||
import { OptionsMenu } from "./layers/OptionsMenu";
|
||||
import { TopBar } from "./layers/TopBar";
|
||||
|
||||
export function createRenderer(
|
||||
canvas: HTMLCanvasElement,
|
||||
game: GameView,
|
||||
eventBus: EventBus,
|
||||
clientID: ClientID
|
||||
): GameRenderer {
|
||||
const transformHandler = new TransformHandler(game, eventBus, canvas);
|
||||
|
||||
export function createRenderer(canvas: HTMLCanvasElement, game: GameView, eventBus: EventBus, clientID: ClientID): GameRenderer {
|
||||
const transformHandler = new TransformHandler(game, eventBus, canvas)
|
||||
const uiState = { attackRatio: 20 };
|
||||
|
||||
const uiState = { attackRatio: 20 }
|
||||
// TODO maybe append this to dcoument instead of querying for them?
|
||||
const emojiTable = document.querySelector("emoji-table") as EmojiTable;
|
||||
if (!emojiTable || !(emojiTable instanceof EmojiTable)) {
|
||||
consolex.error("EmojiTable element not found in the DOM");
|
||||
}
|
||||
const buildMenu = document.querySelector("build-menu") as BuildMenu;
|
||||
if (!buildMenu || !(buildMenu instanceof BuildMenu)) {
|
||||
consolex.error("BuildMenu element not found in the DOM");
|
||||
}
|
||||
buildMenu.game = game;
|
||||
buildMenu.eventBus = eventBus;
|
||||
|
||||
// TODO maybe append this to dcoument instead of querying for them?
|
||||
const emojiTable = document.querySelector('emoji-table') as EmojiTable;
|
||||
if (!emojiTable || !(emojiTable instanceof EmojiTable)) {
|
||||
consolex.error('EmojiTable element not found in the DOM');
|
||||
}
|
||||
const buildMenu = document.querySelector('build-menu') as BuildMenu;
|
||||
if (!buildMenu || !(buildMenu instanceof BuildMenu)) {
|
||||
consolex.error('BuildMenu element not found in the DOM')
|
||||
}
|
||||
buildMenu.game = game
|
||||
buildMenu.eventBus = eventBus
|
||||
const leaderboard = document.querySelector("leader-board") as Leaderboard;
|
||||
if (!emojiTable || !(leaderboard instanceof Leaderboard)) {
|
||||
consolex.error("EmojiTable element not found in the DOM");
|
||||
}
|
||||
leaderboard.clientID = clientID;
|
||||
leaderboard.eventBus = eventBus;
|
||||
leaderboard.game = game;
|
||||
|
||||
const leaderboard = document.querySelector('leader-board') as Leaderboard;
|
||||
if (!emojiTable || !(leaderboard instanceof Leaderboard)) {
|
||||
consolex.error('EmojiTable element not found in the DOM');
|
||||
}
|
||||
leaderboard.clientID = clientID
|
||||
leaderboard.eventBus = eventBus
|
||||
leaderboard.game = game
|
||||
const controlPanel = document.querySelector("control-panel") as ControlPanel;
|
||||
if (!(controlPanel instanceof ControlPanel)) {
|
||||
consolex.error("ControlPanel element not found in the DOM");
|
||||
}
|
||||
controlPanel.clientID = clientID;
|
||||
controlPanel.eventBus = eventBus;
|
||||
controlPanel.uiState = uiState;
|
||||
controlPanel.game = game;
|
||||
|
||||
const eventsDisplay = document.querySelector(
|
||||
"events-display"
|
||||
) as EventsDisplay;
|
||||
if (!(eventsDisplay instanceof EventsDisplay)) {
|
||||
consolex.error("events display not found");
|
||||
}
|
||||
eventsDisplay.eventBus = eventBus;
|
||||
eventsDisplay.game = game;
|
||||
eventsDisplay.clientID = clientID;
|
||||
|
||||
const controlPanel = document.querySelector('control-panel') as ControlPanel;
|
||||
if (!(controlPanel instanceof ControlPanel)) {
|
||||
consolex.error('ControlPanel element not found in the DOM');
|
||||
}
|
||||
controlPanel.clientID = clientID
|
||||
controlPanel.eventBus = eventBus
|
||||
controlPanel.uiState = uiState
|
||||
controlPanel.game = game
|
||||
const playerInfo = document.querySelector(
|
||||
"player-info-overlay"
|
||||
) as PlayerInfoOverlay;
|
||||
if (!(playerInfo instanceof PlayerInfoOverlay)) {
|
||||
consolex.error("player info overlay not found");
|
||||
}
|
||||
playerInfo.eventBus = eventBus;
|
||||
playerInfo.clientID = clientID;
|
||||
playerInfo.transform = transformHandler;
|
||||
playerInfo.game = game;
|
||||
|
||||
const eventsDisplay = document.querySelector('events-display') as EventsDisplay;
|
||||
if (!(eventsDisplay instanceof EventsDisplay)) {
|
||||
consolex.error('events display not found')
|
||||
}
|
||||
eventsDisplay.eventBus = eventBus
|
||||
eventsDisplay.game = game
|
||||
eventsDisplay.clientID = clientID
|
||||
const winModel = document.querySelector("win-modal") as WinModal;
|
||||
if (!(winModel instanceof WinModal)) {
|
||||
console.error("win modal not found");
|
||||
}
|
||||
winModel.eventBus = eventBus;
|
||||
winModel.game = game;
|
||||
|
||||
const playerInfo = document.querySelector('player-info-overlay') as PlayerInfoOverlay
|
||||
if (!(playerInfo instanceof PlayerInfoOverlay)) {
|
||||
consolex.error('player info overlay not found')
|
||||
}
|
||||
playerInfo.eventBus = eventBus
|
||||
playerInfo.clientID = clientID
|
||||
playerInfo.transform = transformHandler
|
||||
playerInfo.game = game
|
||||
const optionsMenu = document.querySelector("options-menu") as OptionsMenu;
|
||||
if (!(optionsMenu instanceof OptionsMenu)) {
|
||||
console.error("options menu not found");
|
||||
}
|
||||
optionsMenu.eventBus = eventBus;
|
||||
optionsMenu.game = game;
|
||||
|
||||
const topBar = document.querySelector("top-bar") as TopBar;
|
||||
if (!(topBar instanceof TopBar)) {
|
||||
console.error("top bar not found");
|
||||
}
|
||||
topBar.game = game;
|
||||
|
||||
const winModel = document.querySelector('win-modal') as WinModal
|
||||
if (!(winModel instanceof WinModal)) {
|
||||
console.error('win modal not found')
|
||||
}
|
||||
winModel.eventBus = eventBus
|
||||
winModel.game = game
|
||||
const layers: Layer[] = [
|
||||
new TerrainLayer(game),
|
||||
new TerritoryLayer(game, eventBus),
|
||||
new StructureLayer(game, eventBus),
|
||||
new UnitLayer(game, eventBus, clientID),
|
||||
new NameLayer(game, game.config().theme(), transformHandler, clientID),
|
||||
eventsDisplay,
|
||||
new RadialMenu(
|
||||
eventBus,
|
||||
game,
|
||||
transformHandler,
|
||||
clientID,
|
||||
emojiTable as EmojiTable,
|
||||
buildMenu,
|
||||
uiState
|
||||
),
|
||||
new SpawnTimer(game, transformHandler),
|
||||
leaderboard,
|
||||
controlPanel,
|
||||
playerInfo,
|
||||
winModel,
|
||||
optionsMenu,
|
||||
topBar,
|
||||
];
|
||||
|
||||
const optionsMenu = document.querySelector('options-menu') as OptionsMenu
|
||||
if (!(optionsMenu instanceof OptionsMenu)) {
|
||||
console.log('options menu not found')
|
||||
}
|
||||
optionsMenu.eventBus = eventBus
|
||||
optionsMenu.game = game
|
||||
|
||||
|
||||
const layers: Layer[] = [
|
||||
new TerrainLayer(game),
|
||||
new TerritoryLayer(game, eventBus),
|
||||
new StructureLayer(game, eventBus),
|
||||
new UnitLayer(game, eventBus, clientID),
|
||||
new NameLayer(game, game.config().theme(), transformHandler, clientID),
|
||||
eventsDisplay,
|
||||
new RadialMenu(eventBus, game, transformHandler, clientID, emojiTable as EmojiTable, buildMenu, uiState),
|
||||
new SpawnTimer(game, transformHandler),
|
||||
leaderboard,
|
||||
controlPanel,
|
||||
playerInfo,
|
||||
winModel,
|
||||
optionsMenu
|
||||
]
|
||||
|
||||
return new GameRenderer(game, eventBus, canvas, transformHandler, uiState, layers)
|
||||
return new GameRenderer(
|
||||
game,
|
||||
eventBus,
|
||||
canvas,
|
||||
transformHandler,
|
||||
uiState,
|
||||
layers
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export class GameRenderer {
|
||||
private context: CanvasRenderingContext2D;
|
||||
|
||||
private context: CanvasRenderingContext2D
|
||||
constructor(
|
||||
private game: GameView,
|
||||
private eventBus: EventBus,
|
||||
private canvas: HTMLCanvasElement,
|
||||
public transformHandler: TransformHandler,
|
||||
public uiState: UIState,
|
||||
private layers: Layer[]
|
||||
) {
|
||||
this.context = canvas.getContext("2d");
|
||||
}
|
||||
|
||||
constructor(private game: GameView, private eventBus: EventBus, private canvas: HTMLCanvasElement, public transformHandler: TransformHandler, public uiState: UIState, private layers: Layer[]) {
|
||||
this.context = canvas.getContext("2d")
|
||||
}
|
||||
initialize() {
|
||||
this.eventBus.on(RedrawGraphicsEvent, (e) => {
|
||||
this.layers.forEach((l) => {
|
||||
if (l.redraw) {
|
||||
l.redraw();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
initialize() {
|
||||
this.eventBus.on(RedrawGraphicsEvent, (e) => {
|
||||
this.layers.forEach(l => {
|
||||
if (l.redraw) {
|
||||
l.redraw()
|
||||
}
|
||||
})
|
||||
})
|
||||
this.layers.forEach((l) => l.init());
|
||||
|
||||
this.layers.forEach(l => l.init())
|
||||
document.body.appendChild(this.canvas);
|
||||
window.addEventListener("resize", () => this.resizeCanvas());
|
||||
this.resizeCanvas();
|
||||
|
||||
document.body.appendChild(this.canvas);
|
||||
window.addEventListener('resize', () => this.resizeCanvas());
|
||||
this.resizeCanvas();
|
||||
this.transformHandler = new TransformHandler(
|
||||
this.game,
|
||||
this.eventBus,
|
||||
this.canvas
|
||||
);
|
||||
|
||||
this.transformHandler = new TransformHandler(this.game, this.eventBus, this.canvas)
|
||||
requestAnimationFrame(() => this.renderGame());
|
||||
}
|
||||
|
||||
requestAnimationFrame(() => this.renderGame());
|
||||
}
|
||||
resizeCanvas() {
|
||||
this.canvas.width = window.innerWidth;
|
||||
this.canvas.height = window.innerHeight;
|
||||
//this.redraw()
|
||||
}
|
||||
|
||||
resizeCanvas() {
|
||||
this.canvas.width = window.innerWidth;
|
||||
this.canvas.height = window.innerHeight;
|
||||
//this.redraw()
|
||||
}
|
||||
renderGame() {
|
||||
const start = performance.now();
|
||||
// Set background
|
||||
this.context.fillStyle = this.game
|
||||
.config()
|
||||
.theme()
|
||||
.backgroundColor()
|
||||
.toHex();
|
||||
this.context.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
|
||||
renderGame() {
|
||||
const start = performance.now()
|
||||
// Set background
|
||||
this.context.fillStyle = this.game.config().theme().backgroundColor().toHex();
|
||||
this.context.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
// Save the current context state
|
||||
this.context.save();
|
||||
|
||||
// Save the current context state
|
||||
this.context.save();
|
||||
this.transformHandler.handleTransform(this.context);
|
||||
|
||||
this.transformHandler.handleTransform(this.context)
|
||||
this.layers.forEach((l) => {
|
||||
if (l.shouldTransform?.()) {
|
||||
l.renderLayer?.(this.context);
|
||||
}
|
||||
});
|
||||
|
||||
this.layers.forEach(l => {
|
||||
if (l.shouldTransform?.()) {
|
||||
l.renderLayer?.(this.context)
|
||||
}
|
||||
})
|
||||
this.context.restore();
|
||||
|
||||
this.context.restore()
|
||||
this.layers.forEach((l) => {
|
||||
if (!l.shouldTransform?.()) {
|
||||
l.renderLayer?.(this.context);
|
||||
}
|
||||
});
|
||||
|
||||
this.layers.forEach(l => {
|
||||
if (!l.shouldTransform?.()) {
|
||||
l.renderLayer?.(this.context)
|
||||
}
|
||||
})
|
||||
requestAnimationFrame(() => this.renderGame());
|
||||
|
||||
requestAnimationFrame(() => this.renderGame());
|
||||
const duration = performance.now() - start;
|
||||
if (duration > 50) {
|
||||
console.warn(
|
||||
`tick ${this.game.ticks()} took ${duration}ms to render frame`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const duration = performance.now() - start
|
||||
if (duration > 50) {
|
||||
console.warn(`tick ${this.game.ticks()} took ${duration}ms to render frame`)
|
||||
}
|
||||
}
|
||||
tick() {
|
||||
this.layers.forEach((l) => l.tick?.());
|
||||
}
|
||||
|
||||
tick() {
|
||||
this.layers.forEach(l => l.tick?.())
|
||||
}
|
||||
|
||||
resize(width: number, height: number): void {
|
||||
this.canvas.width = Math.ceil(width / window.devicePixelRatio);
|
||||
this.canvas.height = Math.ceil(height / window.devicePixelRatio);
|
||||
}
|
||||
|
||||
}
|
||||
resize(width: number, height: number): void {
|
||||
this.canvas.width = Math.ceil(width / window.devicePixelRatio);
|
||||
this.canvas.height = Math.ceil(height / window.devicePixelRatio);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ export class ControlPanel extends LitElement implements Layer {
|
||||
return html`
|
||||
<div
|
||||
class="${this._isVisible
|
||||
? "w-full lg:w-72 bg-gray-800/70 p-2 pr-3 lg:p-4 shadow-lg rounded-lg backdrop-blur"
|
||||
? "w-full text-sm lg:text-m lg:w-72 bg-gray-800/70 p-2 pr-3 lg:p-4 shadow-lg rounded-lg backdrop-blur"
|
||||
: "hidden"}"
|
||||
>
|
||||
<div class="hidden lg:block bg-black/30 text-white mb-4 p-2 rounded">
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { GameView } from "../../../core/game/GameView";
|
||||
import { Layer } from "./Layer";
|
||||
import { renderNumber, renderTroops } from "../../Utils";
|
||||
|
||||
@customElement("top-bar")
|
||||
export class TopBar extends LitElement implements Layer {
|
||||
public game: GameView;
|
||||
private isVisible = false;
|
||||
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
|
||||
init() {
|
||||
this.isVisible = true;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
tick() {
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.isVisible) {
|
||||
return html``;
|
||||
}
|
||||
const myPlayer = this.game?.myPlayer();
|
||||
if (!myPlayer?.isAlive() || this.game?.inSpawnPhase()) {
|
||||
return html``;
|
||||
}
|
||||
console.log("rendering top bar");
|
||||
const popRate = this.game.config().populationIncreaseRate(myPlayer) * 10;
|
||||
const maxPop = this.game.config().maxPopulation(myPlayer);
|
||||
const goldPerSecond = this.game.config().goldAdditionRate(myPlayer) * 10;
|
||||
return html`
|
||||
<div
|
||||
class="fixed top-0 z-50 bg-black/90 text-white text-sm p-1 rounded grid grid-cols-1 sm:grid-cols-2 w-2/3 sm:w-2/3 md:w-1/2 lg:hidden"
|
||||
>
|
||||
<!-- Pop section (takes 2 columns on desktop) -->
|
||||
<div
|
||||
class="sm:col-span-1 flex items-center space-x-1 overflow-x-auto whitespace-nowrap"
|
||||
>
|
||||
<span class="font-bold shrink-0">Pop:</span>
|
||||
<span
|
||||
>${renderTroops(myPlayer.population())} /
|
||||
${renderTroops(maxPop)}</span
|
||||
>
|
||||
<span>(+${renderTroops(popRate)})</span>
|
||||
</div>
|
||||
|
||||
<!-- Gold section (takes 1 column on desktop) -->
|
||||
<div
|
||||
class="flex items-center space-x-2 overflow-x-auto whitespace-nowrap"
|
||||
>
|
||||
<span class="font-bold shrink-0">Gold:</span>
|
||||
<span
|
||||
>${renderNumber(myPlayer.gold())}
|
||||
(+${renderNumber(goldPerSecond)})</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -179,12 +179,13 @@
|
||||
<options-menu></options-menu>
|
||||
<player-info-overlay></player-info-overlay>
|
||||
<win-modal></win-modal>
|
||||
<top-bar></top-bar>
|
||||
|
||||
<div class="fixed bottom-0 flex w-full flex-col-reverse sm:flex-row z-50">
|
||||
<div class="w-full sm:w-1/3">
|
||||
<control-panel></control-panel>
|
||||
</div>
|
||||
<div class="w-full sm:w-2/3 sm:fixed sm:right-0 sm:bottom-0 sm:flex sm:justify-end">
|
||||
<div class="w-full sm:w-2/3 sm:fixed sm:right-0 sm:bottom-0 sm:flex justify-end">
|
||||
<events-display></events-displayflex >
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user