Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 | 106x 106x 106x 106x 106x 64x 106x 65x 65x 51x 51x 51x 51x 51x 4x 47x 36x 11x 1x 10x 51x 51x 51x 51x 51x 51x 51x 51x 51x 51x 14x 14x 14x 14x 106x | /**
* useCombatLayout Hook - Enhanced Responsive Combat Layout
*
* Custom hook for managing responsive combat screen layout calculations with
* comprehensive support for all screen sizes from mobile to ultra-wide displays.
*
* Enhanced Features:
* - Five screen size categories (mobile, tablet, desktop, large, xlarge)
* - Proportional scaling for consistent sizing across devices
* - Optimized arena sizing for each device category
* - Smooth transitions for resize operations
* - 60fps performance maintained
*
* Uses robust device detection combining user-agent and screen size to ensure
* mobile controls are shown on all mobile devices, including high-resolution phones.
*
* Performance:
* - Reduces recalculations by checking only breakpoint changes, not exact dimensions
* - Memoizes arena bounds to prevent cascading re-renders
* - Targets <1ms execution time for layout calculations
*
* @param width - Screen width
* @param height - Screen height
*
* @returns Layout constants and arena bounds
*
* @example
* ```typescript
* const { layoutConstants, arenaBounds, isMobile, screenSize } = useCombatLayout(1200, 800);
* ```
*/
import { useMemo } from "react";
import { shouldUseMobileControls } from "../../../../utils/deviceDetection";
import { getScreenSize } from "../../../../systems/ResponsiveScaling";
import type { ScreenSize } from "../../../../systems/ResponsiveScaling";
export interface LayoutConstants {
readonly padding: number;
readonly hudHeight: number;
readonly controlsHeight: number;
readonly footerHeight: number;
readonly healthBarHeight: number;
}
export interface ArenaBounds {
readonly x: number;
readonly y: number;
readonly width: number;
readonly height: number;
readonly scale: number; // 3D scale factor for arena (1.0 = desktop, <1.0 = mobile)
}
export interface CombatLayout {
readonly layoutConstants: LayoutConstants;
readonly arenaBounds: ArenaBounds;
readonly isMobile: boolean;
readonly screenSize: ScreenSize;
}
/**
* Custom hook for combat screen layout calculations
* Enhanced with centralized responsive scaling system
* Optimized to reduce recalculations and improve 60fps performance
*/
export function useCombatLayout(width: number, height: number): CombatLayout {
// Determine screen size category using centralized scaling system
const screenSize = useMemo(() => getScreenSize(width), [width]);
// Device detection has its own internal caching based on screen dimensions
// No need for additional React memoization here
const isMobile = shouldUseMobileControls();
// Use screen size category instead of pixel threshold for better organization
const isLargeDesktop = useMemo(() => screenSize === 'xlarge', [screenSize]);
const isTablet = useMemo(() => screenSize === 'tablet', [screenSize]);
// Centralized layout constants for easier tweaking
// Enhanced with tablet-specific values for better responsive support
// Updated mobile controls height for new sizing: D-Pad (140px), buttons (80px+70px)
// Optimized: Depends on breakpoint booleans, not exact width
const layoutConstants = useMemo<LayoutConstants>(
() => ({
padding: 10,
hudHeight: isMobile ? 95 : isTablet ? 100 : isLargeDesktop ? 90 : 120,
controlsHeight: isMobile ? 160 : isTablet ? 140 : isLargeDesktop ? 120 : 160,
footerHeight: isMobile ? 34 : isTablet ? 30 : isLargeDesktop ? 20 : 28,
healthBarHeight: isMobile ? 48 : isTablet ? 50 : isLargeDesktop ? 45 : 55,
}),
[isMobile, isTablet, isLargeDesktop]
);
// Fixed player positions for 2-player combat with proper bounds
// Arena bounds should account for HUD at top and controls at bottom
// Mobile arena sizing with 4:3 aspect ratio adapts to device resolution:
// - Standard phones (< 768px): up to 400px width
// - Large phones (768-1199px): up to 500px width
// - 2K devices (1200-1439px): up to 600px width
// - 4K devices (≥1440px): up to 800px width
// Optimized: Separate calculation dependencies to reduce recalculation frequency
const arenaBounds = useMemo<ArenaBounds>(() => {
const arenaY = layoutConstants.hudHeight + layoutConstants.padding;
// Mobile-specific arena sizing for better screen fit
if (isMobile) {
// Reserve space for HUD (80px min) and controls (120px min)
const minTopClearance = 80;
const minBottomClearance = 120;
const availableHeight = height - minTopClearance - minBottomClearance;
const availableWidth = width - 40; // 20px margins on each side
// Calculate optimal arena size maintaining 4:3 aspect ratio (width:height)
// Target sizing based on device resolution:
// - Standard mobile (375-430px): 350x262px to 400x300px
// - High-res mobile (1200px+): Up to 600x450px (2K Android)
// - Ultra high-res (1440px+): Up to 800x600px (4K Android)
// Determine max width based on screen width for high-end devices
let maxMobileWidth: number;
if (width >= 1440) {
// 4K/QHD+ Android devices (e.g., Galaxy S23 Ultra, Pixel 9 Pro)
maxMobileWidth = Math.min(availableWidth, 800);
} else if (width >= 1200) {
// 2K Android devices
maxMobileWidth = Math.min(availableWidth, 600);
} else if (width >= 768) {
// Large phones (e.g., iPhone 14 Pro Max)
maxMobileWidth = Math.min(availableWidth, 500);
} else {
// Standard phones (e.g., iPhone SE, standard Android)
maxMobileWidth = Math.min(availableWidth, 400);
}
const maxMobileHeight = Math.min(availableHeight, 800);
// Maintain 4:3 aspect ratio (width:height = 4:3)
const aspectRatio = 4 / 3;
let arenaWidth = maxMobileWidth;
let arenaHeight = arenaWidth / aspectRatio; // height = width / (4/3) = width * (3/4)
// If height exceeds available, recalculate based on height constraint
Iif (arenaHeight > maxMobileHeight) {
arenaHeight = maxMobileHeight;
arenaWidth = arenaHeight * aspectRatio; // width = height * (4/3)
}
// Ensure minimum size for playability without exceeding available space
arenaWidth = Math.min(Math.max(arenaWidth, 300), availableWidth);
arenaHeight = Math.min(Math.max(arenaHeight, 225), maxMobileHeight); // 300 * 3/4 = 225
// Calculate 3D scale factor (mobile arena is smaller than desktop)
const desktopWidth = 960; // 80% of 1200px
const scale = arenaWidth / desktopWidth;
return {
x: (width - arenaWidth) / 2, // Centered horizontally
y: arenaY,
width: arenaWidth,
height: arenaHeight,
scale,
};
}
// Desktop arena sizing - use full available space
const totalReservedHeight =
layoutConstants.hudHeight +
layoutConstants.controlsHeight +
layoutConstants.footerHeight;
const totalPadding = layoutConstants.padding * 3;
const arenaHeight = height - totalReservedHeight - totalPadding;
return {
x: width * 0.1,
y: arenaY,
width: width * 0.8,
height: arenaHeight,
scale: 1.0, // Desktop uses full scale
};
}, [width, height, layoutConstants, isMobile]);
return {
layoutConstants,
arenaBounds,
isMobile,
screenSize,
};
}
|