mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 10:47:04 +02:00
graphics and settings
This commit is contained in:
1 parent
7e2fba35d0
commit
b572ac5ea8
15 files changed
+367
-8
No files matched your search
@@ -1,4 +1,11 @@
|
||||
{
|
||||
"settings": {
|
||||
"quality3": {
|
||||
"low": "Low",
|
||||
"medium": "Medium",
|
||||
"high": "High"
|
||||
}
|
||||
},
|
||||
"price": {
|
||||
"free": "Free"
|
||||
},
|
||||
@@ -159,6 +166,11 @@
|
||||
"name": "Changelogs",
|
||||
"slogan": "BlitzKit patch notes",
|
||||
"action": "Read up"
|
||||
},
|
||||
"settings": {
|
||||
"name": "Settings",
|
||||
"slogan": "",
|
||||
"action": ""
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
@@ -0,0 +1,37 @@
|
||||
import { CaretLeftIcon, CaretRightIcon } from "@radix-ui/react-icons";
|
||||
import { type ReactNode } from "react";
|
||||
|
||||
interface Props {
|
||||
value: number;
|
||||
onChange: (value: number) => void;
|
||||
options: ReactNode[];
|
||||
}
|
||||
|
||||
export function Carousel({ value, onChange, options }: Props) {
|
||||
const isMin = value === 0;
|
||||
const isMax = value === options.length - 1;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
data-disabled={isMin}
|
||||
onClick={() => {
|
||||
if (!isMin) onChange(value - 1);
|
||||
}}
|
||||
>
|
||||
<CaretLeftIcon />
|
||||
</div>
|
||||
|
||||
<div>{options[value]}</div>
|
||||
|
||||
<div
|
||||
data-disabled={isMax}
|
||||
onClick={() => {
|
||||
if (!isMax) onChange(value + 1);
|
||||
}}
|
||||
>
|
||||
<CaretRightIcon />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
import type { ComponentProps } from "react";
|
||||
import { classNames } from "../../ui/classNames";
|
||||
import { Text, type TextProps } from "../Text";
|
||||
import styles from "./index.module.css";
|
||||
|
||||
export interface CodeProps extends ComponentProps<"code"> {
|
||||
export interface CodeProps extends TextProps {
|
||||
lowContrast?: boolean;
|
||||
variant?: "ghost" | "solid";
|
||||
}
|
||||
|
||||
export function Code({ className, variant = "ghost", ...props }: CodeProps) {
|
||||
return (
|
||||
<code
|
||||
<Text
|
||||
data-variant={variant}
|
||||
className={classNames(styles.code, className)}
|
||||
{...props}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
.stats {
|
||||
padding: var(--space-3);
|
||||
gap: var(--space-3);
|
||||
|
||||
z-index: 1;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
.graphs {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
|
||||
.graph {
|
||||
position: static !important;
|
||||
}
|
||||
}
|
||||
|
||||
.settings {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
gap: var(--space-2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Stats } from "@react-three/drei";
|
||||
import { useRef } from "react";
|
||||
import { settingsOrder } from "../../config/settings";
|
||||
import { useSettingDeferred } from "../../hooks/useSetting";
|
||||
import { Settings } from "../../stores/settings";
|
||||
import { Code } from "../Code";
|
||||
import styles from "./index.module.css";
|
||||
|
||||
const stats = [0, 1, 2];
|
||||
|
||||
export function RendererStats() {
|
||||
const renderStatistics = useSettingDeferred("render_statistics");
|
||||
|
||||
if (!renderStatistics) return null;
|
||||
|
||||
return <Inner />;
|
||||
}
|
||||
|
||||
function Inner() {
|
||||
const parent = useRef<HTMLDivElement>(null!);
|
||||
const settings = Settings.use();
|
||||
|
||||
return (
|
||||
<div className={styles.stats}>
|
||||
<div ref={parent} className={styles.graphs}>
|
||||
{stats.map((stat) => (
|
||||
<Stats parent={parent} showPanel={stat} className={styles.graph} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className={styles.settings}>
|
||||
{settingsOrder[0].settings.map((setting) => (
|
||||
<Code lowContrast size="minor">
|
||||
{setting} = {settings[setting]}
|
||||
</Code>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import { useGLTF } from "@react-three/drei";
|
||||
import { useEffect } from "react";
|
||||
import { Mesh, MeshStandardMaterial } from "three";
|
||||
import { SettingQuality3 } from "../config/settings";
|
||||
import { useSetting } from "../hooks/useSetting";
|
||||
|
||||
interface Props {
|
||||
url: string;
|
||||
@@ -8,6 +10,9 @@ interface Props {
|
||||
}
|
||||
|
||||
export function TankModelPart({ url, map }: Props) {
|
||||
const tankQuality = useSetting<SettingQuality3>("tank_quality");
|
||||
const lightingQuality = useSetting<SettingQuality3>("lighting_quality");
|
||||
|
||||
const gltf = useGLTF(url);
|
||||
const [root] = gltf.scene.children;
|
||||
|
||||
@@ -17,16 +22,32 @@ export function TankModelPart({ url, map }: Props) {
|
||||
// TODO: remove this hack and fix the gltf
|
||||
material.vertexColors = false;
|
||||
material.needsUpdate = true;
|
||||
|
||||
if (tankQuality <= SettingQuality3.Medium) {
|
||||
material.normalMap = null;
|
||||
}
|
||||
|
||||
if (tankQuality <= SettingQuality3.Low) {
|
||||
material.metalness = 0;
|
||||
material.roughness = 1;
|
||||
|
||||
material.metalnessMap = null;
|
||||
material.roughnessMap = null;
|
||||
}
|
||||
|
||||
if (tankQuality <= SettingQuality3.Medium) {
|
||||
material.aoMap = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const node of Object.values(gltf.nodes)) {
|
||||
if (node instanceof Mesh) {
|
||||
node.castShadow = true;
|
||||
node.receiveShadow = true;
|
||||
node.castShadow = lightingQuality >= SettingQuality3.Medium;
|
||||
node.receiveShadow = lightingQuality >= SettingQuality3.High;
|
||||
}
|
||||
}
|
||||
}, [url]);
|
||||
}, [url, tankQuality]);
|
||||
|
||||
useEffect(() => {
|
||||
for (const child of root.children) {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
height: 100%;
|
||||
|
||||
.canvas {
|
||||
background-color: black;
|
||||
background-color: black;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { type StageParameters } from "@protos/blitz_static_tank_upgrade_single_stage";
|
||||
import { Canvas } from "@react-three/fiber";
|
||||
import { fov } from "../../config/camera";
|
||||
import { RendererStats } from "../RendererStats";
|
||||
import { TankModelChassis } from "../TankModelChassis";
|
||||
import { TankModelGun } from "../TankModelGun";
|
||||
import { TankModelHull } from "../TankModelHull";
|
||||
@@ -17,7 +18,9 @@ interface TankopediaSandboxProps {
|
||||
export function TankopediaSandbox({ parameters }: TankopediaSandboxProps) {
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.sandbox}>
|
||||
<div className={styles.sandbox} id="canvas-attach">
|
||||
<RendererStats />
|
||||
|
||||
<Canvas
|
||||
camera={{ fov }}
|
||||
shadows
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
export enum SettingType {
|
||||
Boolean,
|
||||
Quality3,
|
||||
}
|
||||
|
||||
interface SettingTypeBoolean {
|
||||
type: SettingType.Boolean;
|
||||
default: SettingBoolean;
|
||||
}
|
||||
|
||||
interface SettingTypeQuality3 {
|
||||
type: SettingType.Quality3;
|
||||
default: SettingQuality3;
|
||||
}
|
||||
|
||||
export enum SettingQuality3 {
|
||||
Low,
|
||||
Medium,
|
||||
High,
|
||||
}
|
||||
|
||||
export enum SettingBoolean {
|
||||
False,
|
||||
True,
|
||||
}
|
||||
|
||||
export type Setting = SettingTypeBoolean | SettingTypeQuality3;
|
||||
|
||||
export const settings = {
|
||||
tank_quality: {
|
||||
type: SettingType.Quality3,
|
||||
default: SettingQuality3.High,
|
||||
},
|
||||
lighting_quality: {
|
||||
type: SettingType.Quality3,
|
||||
default: SettingQuality3.High,
|
||||
},
|
||||
tank_slug_and_id: {
|
||||
type: SettingType.Boolean,
|
||||
default: SettingBoolean.False,
|
||||
},
|
||||
render_statistics: {
|
||||
type: SettingType.Boolean,
|
||||
default: SettingBoolean.False,
|
||||
},
|
||||
} as const satisfies Record<string, Setting>;
|
||||
|
||||
export type SettingName = keyof typeof settings;
|
||||
|
||||
export interface SettingsGroup {
|
||||
name: string;
|
||||
settings: SettingName[];
|
||||
}
|
||||
|
||||
export const settingsOrder: SettingsGroup[] = [
|
||||
{
|
||||
name: "general",
|
||||
settings: ["tank_quality", "lighting_quality"],
|
||||
},
|
||||
{
|
||||
name: "developer",
|
||||
settings: ["tank_slug_and_id", "render_statistics"],
|
||||
},
|
||||
];
|
||||
|
||||
export type SettingTypes = Setting["default"];
|
||||
@@ -91,4 +91,11 @@ export const tools: Record<string, Tool> = {
|
||||
accent: "gray",
|
||||
banner_background_position: "bottom",
|
||||
},
|
||||
|
||||
settings: {
|
||||
id: "settings",
|
||||
|
||||
accent: "gray",
|
||||
banner_background_position: "center",
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
import {
|
||||
settings,
|
||||
type SettingName,
|
||||
type SettingType,
|
||||
type SettingTypes,
|
||||
} from "../config/settings";
|
||||
import { Settings } from "../stores/settings";
|
||||
|
||||
export function useSetting<Type extends SettingTypes>(name: SettingName): Type {
|
||||
const value = Settings.use((state) => state[name]);
|
||||
|
||||
return value as Type;
|
||||
}
|
||||
|
||||
export function useSettingDeferred<Type extends SettingTypes>(
|
||||
name: SettingName,
|
||||
): Type {
|
||||
const value = Settings.useDeferred(
|
||||
(state) => state[name],
|
||||
settings[name].default,
|
||||
);
|
||||
|
||||
return value as Type;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import { Carousel } from "../../../components/Carousel";
|
||||
import { Heading } from "../../../components/Heading";
|
||||
import { Text } from "../../../components/Text";
|
||||
import {
|
||||
SettingBoolean,
|
||||
SettingQuality3,
|
||||
settings,
|
||||
settingsOrder,
|
||||
SettingType,
|
||||
type Setting,
|
||||
type SettingName,
|
||||
type SettingsGroup,
|
||||
} from "../../../config/settings";
|
||||
import { withLocale } from "../../../hocs/withLocale";
|
||||
import { useSetting } from "../../../hooks/useSetting";
|
||||
import { useStrings } from "../../../hooks/useStrings";
|
||||
import { Settings } from "../../../stores/settings";
|
||||
|
||||
export const Page = withLocale(() => {
|
||||
return settingsOrder.map((group) => <Group group={group} />);
|
||||
});
|
||||
|
||||
interface GroupProps {
|
||||
group: SettingsGroup;
|
||||
}
|
||||
|
||||
function Group({ group }: GroupProps) {
|
||||
return (
|
||||
<div>
|
||||
<Heading>{group.name}</Heading>
|
||||
|
||||
<div>
|
||||
{group.settings.map((setting) => (
|
||||
<Setting name={setting} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface SettingProps {
|
||||
name: SettingName;
|
||||
}
|
||||
|
||||
function Setting({ name }: SettingProps) {
|
||||
const setting = settings[name];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Text>{name}</Text>
|
||||
|
||||
{setting.type === SettingType.Boolean && <TypeBoolean name={name} />}
|
||||
{setting.type === SettingType.Quality3 && <TypeQuality3 name={name} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TypeBoolean({ name }: SettingProps) {
|
||||
const value = useSetting<SettingBoolean>(name);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function TypeQuality3({ name }: SettingProps) {
|
||||
const value = useSetting<SettingQuality3>(name);
|
||||
const strings = useStrings();
|
||||
|
||||
return (
|
||||
<Carousel
|
||||
value={value}
|
||||
onChange={(value) => {
|
||||
Settings.mutate((draft) => {
|
||||
draft[name] = value;
|
||||
});
|
||||
}}
|
||||
options={[
|
||||
strings.settings.quality3.low,
|
||||
strings.settings.quality3.medium,
|
||||
strings.settings.quality3.high,
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
import { Section } from "../../../components/Section";
|
||||
import ToolBanner from "../../../components/ToolBanner.astro";
|
||||
import { tools } from "../../../config/tools";
|
||||
import BaseLayout from "../../../layouts/BaseLayout.astro";
|
||||
import { Page } from "./_index";
|
||||
|
||||
export { getStaticPaths } from "../index.astro";
|
||||
|
||||
const { locale } = Astro.props;
|
||||
---
|
||||
|
||||
<BaseLayout accent="amber" {locale}>
|
||||
<ToolBanner {locale} tool={tools.settings} slot="pre-app" />
|
||||
|
||||
<Section>
|
||||
<Page {locale} client:load />
|
||||
</Section>
|
||||
</BaseLayout>
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Varuna } from "varuna";
|
||||
import {
|
||||
settings,
|
||||
type SettingName,
|
||||
type SettingTypes,
|
||||
} from "../config/settings";
|
||||
|
||||
type Settings = Record<SettingName, SettingTypes>;
|
||||
|
||||
const defaults: Partial<Settings> = {};
|
||||
|
||||
for (const key in settings) {
|
||||
const setting = settings[key as SettingName];
|
||||
defaults[key as SettingName] = setting.default;
|
||||
}
|
||||
|
||||
export const Settings = new Varuna<Settings>(defaults as Settings, "settings");
|
||||
Reference in new issue
Block a user