Browse Source

调整游戏界面样式,引入shinytext

master
hanqin 3 days ago
parent
commit
9ac8f7c6fe
  1. 1
      Build_God_Game/src/assets/images/character.svg
  2. 1
      Build_God_Game/src/assets/images/mission.svg
  3. 1
      Build_God_Game/src/assets/images/scrap.svg
  4. 1
      Build_God_Game/src/assets/images/training.svg
  5. 135
      Build_God_Game/src/components/ShinyText/ShinyText.vue
  6. 94
      Build_God_Game/src/components/StarBorder/StarBorder.vue
  7. 33
      Build_God_Game/src/views/GameView.vue

1
Build_God_Game/src/assets/images/character.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1774669454892" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="24840" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M728.64 577.67a25.92 25.92 0 0 0-12.89-15.52c-2.93-3.53-5.78-7-8.45-10.2 93.14-102.54 90.44-275.69 7.75-388-97.19-132-274.55-133.19-366.92-3.75C290.45 222 257 308.7 269.75 388.8c9.24 58.07 32.18 127.4 75.58 172.94-36.54 77.4-94.83 143.81-137.62 217.8-19.06 33-51.87 83.41-35.8 123.93 35.5 89.57 250 47.27 315.51 41.71 88.27-7.5 174.73-17.66 262.06-32.56 34.43-5.87 76.15-8.32 94.25-42.15 47.89-89.47-51.15-215.58-115.09-292.8z m-233.87 208c-3.66 36.19-9.83 72.49-6.76 108.68q-23.45 1.86-46.76 3.58c-52.48 4-106.86 6.74-159.44 3.77-67.53-3.82-54.18-45.64-31.14-97.1 29.85-66.66 78-132.93 103.56-203.15 5 26 5.13 59.14 11.11 87.33-17 38.38-49.11 71.73-78.21 101.64-18.12 18.62-45.61 43-44.2 71.45 0.29 5.82 9 5.89 9.1 0 0.57-35.41 40.73-63.07 61.88-87.89 10.14-11.91 19.88-24.46 29.37-36.91a260.72 260.72 0 0 0 17.57-25.43c4.54-7.7 3.52-12.42 4-16.24-0.2 1.72 1.3 2.66 2.82 2.74 5.49 19.77 14.78 36.17 32.34 44.29 34.58 16 71.11-2.51 98.27-31.64a713.34 713.34 0 0 1-3.51 74.92z m-133-207.07c0.12-0.41 0.29-0.81 0.41-1.22a153.07 153.07 0 0 0 27 17.6c31.26 15.8 67.92 26.82 105.41 31.31-2.14 43.17-60.29 117.05-99.72 71.7-10.08-11.59-10.78-41.08-13.59-55.38-4.28-21.73-6.44-45.77-19.47-63.97z m44.09-34.2a140.9 140.9 0 0 1-14.46-9.81c14.18-25 26.69-50.69 35.31-78.65 1.4-4.54-5.69-6.48-7.11-2-8.41 26.89-20.67 51.32-34.55 75.13A168.46 168.46 0 0 1 357 497.86c30.66-38.35 57.58-79.88 79-124 2.34-4.82-4.81-9.07-7.24-4.23-21.51 42.91-48.64 81.14-78.11 118.47a243.13 243.13 0 0 1-16.01-31.1c10.81-13.52 16-30.2 23.8-46.53 13.87-29.17 31.45-56.32 49.51-83 2.72-4-3.76-7.74-6.49-3.79-15.55 22.55-30.71 45.38-43.59 69.59-9.28 17.44-15.56 37.81-27 54.11a384.72 384.72 0 0 1-16.59-55.2 192.62 192.62 0 0 1-3-19.31c11.24-30.45 42.29-52.7 62.45-77 24-28.94 45.51-59.87 70.32-88.19 2.8-3.2-1.84-7.91-4.7-4.7-31.88 35.83-58.39 76.24-92 110.53-13.52 13.8-27.67 27-37.23 42.84-1.89-52.87 15.94-107.75 47.16-153.71a17.38 17.38 0 0 0 9.72-7.14c75.56-113 234.55-107.43 310 1.55 78.64 113.56 68.79 296.2-66.77 362.29-61.04 29.78-146.88 19.01-204.33-14.9zM670 590.32c-10 24.39-13.7 51.89-31.77 72.84-23.34 27.06-57.87 41.9-80.06 6.91-7.85-12.37-12.34-27.34-16.14-41.73 33.74-1.21 66.61-8.31 95.23-22.78a246.11 246.11 0 0 0 30.37-18.34c0.72 0.96 1.63 2.11 2.37 3.1z m119.3 259.33c-9.41 12.2-126.28 23.89-150 27.78-27.72 4.56-55.88 7.92-84.12 10.83a29.23 29.23 0 0 0-12.18-21.9c-4.33-40.72 4.93-91.17 3.86-136.12a76.1 76.1 0 0 0 47.55 18.26c65.54 2 90.5-58.08 112.95-110.84C760.19 704.89 828 799.45 789.3 849.65z" fill="#49e356" p-id="24841"></path><path d="M383.91 739.39c-28.26 36.32-64.15 68.21-82.17 111.4-1.2 2.88 1.75 5.34 4.24 5.57l6.24 0.57c4.73 0.43 5.9-7.73 1.13-8.37l-0.79-0.11c18.75-39.74 50.08-70.37 76.45-105.12 2.22-2.91-2.83-6.86-5.1-3.94zM363.25 860.68l-8.83 27.86c-1.64 5.18 6.32 7.27 8.08 2.23l9.67-27.63c1.96-5.6-7.11-8.14-8.92-2.46z" fill="#49e356" p-id="24842"></path></svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

1
Build_God_Game/src/assets/images/mission.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1774669383132" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="22425" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M962.56 502.784c-30.72-109.056-73.216-220.16-87.552-247.808-18.432-34.816-44.032-61.44-75.776-79.36-32.768-18.432-73.728-27.648-121.344-27.648h-327.68c-47.616 0-88.064 9.216-121.344 27.648-31.744 17.92-57.344 44.544-75.776 79.36-14.848 27.648-56.832 138.752-88.064 247.808-40.96 145.408-46.592 259.072-16.384 317.44 16.896 32.256 44.544 52.224 80.896 56.32 14.848 2.048 28.672 2.56 41.472 2.56 96.768 0 141.312-49.152 180.736-92.672 23.552-26.112 45.568-50.688 78.336-64h183.808c32.768 13.824 51.712 37.376 73.216 64.512 19.968 25.088 40.448 51.2 72.704 69.12 36.352 20.48 80.896 27.136 137.216 19.968 36.352-4.608 64-24.064 80.896-56.32 31.232-57.856 26.112-171.52-15.36-316.928z m-36.352 288.768c-8.192 16.384-19.968 24.576-38.4 26.624-12.288 1.536-24.064 2.048-34.304 2.048-68.096 0-93.184-31.744-123.904-70.144-24.064-30.208-51.2-64.512-100.352-82.944-3.072-1.024-6.144-1.536-9.728-1.536H426.496c-3.072 0-6.656 0.512-9.728 1.536-47.104 17.92-77.312 50.688-103.424 79.36-42.496 46.592-75.776 83.456-173.056 71.168-17.92-2.048-29.696-10.24-38.4-26.624-22.528-44.032-15.36-147.456 19.968-273.408C152.064 412.16 193.024 306.176 204.8 283.648c28.16-52.224 74.752-76.8 146.944-76.8H675.84c72.192 0 119.296 24.576 146.944 76.8 11.776 22.016 52.736 128 82.432 233.984 36.352 125.952 43.52 229.888 20.992 273.92z" p-id="22426" fill="#d81e06"></path><path d="M610.304 424.448c0 25.6 20.48 46.08 46.08 46.08s46.08-20.48 46.08-46.08-20.48-46.08-46.08-46.08-46.08 20.48-46.08 46.08zM712.704 527.872c0 25.6 20.48 46.08 46.08 46.08s46.08-20.48 46.08-46.08-20.48-46.08-46.08-46.08-46.08 20.992-46.08 46.08zM411.648 447.488H353.28V389.12c0-16.384-13.312-29.696-29.696-29.696s-29.696 13.312-29.696 29.696v58.368H235.52c-16.384 0-29.696 13.312-29.696 29.696S219.136 506.88 235.52 506.88h58.368v58.368c0 16.384 13.312 29.696 29.696 29.696s29.696-13.312 29.696-29.696V506.368h58.368c16.384 0 29.696-13.312 29.696-29.696s-13.312-29.184-29.696-29.184z" p-id="22427" fill="#d81e06"></path></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

1
Build_God_Game/src/assets/images/scrap.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.1 KiB

1
Build_God_Game/src/assets/images/training.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

135
Build_God_Game/src/components/ShinyText/ShinyText.vue

@ -0,0 +1,135 @@
<script setup lang="ts">
import { Motion, useAnimationFrame, useMotionValue, useTransform } from 'motion-v';
import { computed, ref, watch } from 'vue';
interface ShinyTextProps {
text: string;
disabled?: boolean;
speed?: number;
className?: string;
color?: string;
shineColor?: string;
spread?: number;
yoyo?: boolean;
pauseOnHover?: boolean;
direction?: 'left' | 'right';
delay?: number;
}
const props = withDefaults(defineProps<ShinyTextProps>(), {
disabled: false,
speed: 2,
className: '',
color: '#b5b5b5',
shineColor: '#ffffff',
spread: 120,
yoyo: false,
pauseOnHover: false,
direction: 'left',
delay: 0
});
const isPaused = ref(false);
const progress = useMotionValue(0);
const elapsedRef = ref(0);
const lastTimeRef = ref<number | null>(null);
const directionRef = ref(props.direction === 'left' ? 1 : -1);
const animationDuration = computed(() => props.speed * 1000);
const delayDuration = computed(() => props.delay * 1000);
useAnimationFrame(time => {
if (props.disabled || isPaused.value) {
lastTimeRef.value = null;
return;
}
if (lastTimeRef.value === null) {
lastTimeRef.value = time;
return;
}
const deltaTime = time - lastTimeRef.value;
lastTimeRef.value = time;
elapsedRef.value += deltaTime;
// Animation goes from 0 to 100
if (props.yoyo) {
const cycleDuration = animationDuration.value + delayDuration.value;
const fullCycle = cycleDuration * 2;
const cycleTime = elapsedRef.value % fullCycle;
if (cycleTime < animationDuration.value) {
// Forward animation: 0 -> 100
const p = (cycleTime / animationDuration.value) * 100;
progress.set(directionRef.value === 1 ? p : 100 - p);
} else if (cycleTime < cycleDuration) {
// Delay at end
progress.set(directionRef.value === 1 ? 100 : 0);
} else if (cycleTime < cycleDuration + animationDuration.value) {
// Reverse animation: 100 -> 0
const reverseTime = cycleTime - cycleDuration;
const p = 100 - (reverseTime / animationDuration.value) * 100;
progress.set(directionRef.value === 1 ? p : 100 - p);
} else {
// Delay at start
progress.set(directionRef.value === 1 ? 0 : 100);
}
} else {
const cycleDuration = animationDuration.value + delayDuration.value;
const cycleTime = elapsedRef.value % cycleDuration;
if (cycleTime < animationDuration.value) {
// Animation phase: 0 -> 100
const p = (cycleTime / animationDuration.value) * 100;
progress.set(directionRef.value === 1 ? p : 100 - p);
} else {
// Delay phase - hold at end (shine off-screen)
progress.set(directionRef.value === 1 ? 100 : 0);
}
}
});
watch(
() => props.direction,
() => {
directionRef.value = props.direction === 'left' ? 1 : -1;
elapsedRef.value = 0;
progress.set(0);
},
{
immediate: true
}
);
const backgroundPosition = useTransform(progress, p => `${150 - p * 2}% center`);
const handleMouseEnter = () => {
if (props.pauseOnHover) isPaused.value = true;
};
const handleMouseLeave = () => {
if (props.pauseOnHover) isPaused.value = false;
};
const gradientStyle = computed(() => ({
backgroundImage: `linear-gradient(${props.spread}deg, ${props.color} 0%, ${props.color} 35%, ${props.shineColor} 50%, ${props.color} 65%, ${props.color} 100%)`,
backgroundSize: '200% auto',
WebkitBackgroundClip: 'text',
backgroundClip: 'text',
WebkitTextFillColor: 'transparent'
}));
</script>
<template>
<Motion
tag="span"
:class="['inline-block', className]"
:style="{ ...gradientStyle, backgroundPosition }"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
>
{{ text }}
</Motion>
</template>

94
Build_God_Game/src/components/StarBorder/StarBorder.vue

@ -0,0 +1,94 @@
<template>
<component
:is="as"
:class="['relative inline-block overflow-hidden !bg-transparent !border-none !rounded-[20px]', customClass]"
v-bind="restAttrs"
:style="componentStyle"
>
<div
class="absolute w-[300%] h-[50%] opacity-70 bottom-[-11px] right-[-250%] rounded-full animate-star-movement-bottom z-0"
:style="{
background: `radial-gradient(circle, ${color}, transparent 10%)`,
animationDuration: speed
}"
></div>
<div
class="absolute w-[300%] h-[50%] opacity-70 top-[-10px] left-[-250%] rounded-full animate-star-movement-top z-0"
:style="{
background: `radial-gradient(circle, ${color}, transparent 10%)`,
animationDuration: speed
}"
></div>
<div
class="relative z-10 border border-[#333] bg-[#0b0b0b] text-white text-[16px] text-center px-[64px] py-[24px] rounded-[20px]"
>
<slot />
</div>
</component>
</template>
<script setup lang="ts">
import { computed, defineProps, useAttrs } from 'vue';
interface StarBorderProps {
as?: string;
customClass?: string;
color?: string;
speed?: string;
thickness?: number;
}
const props = withDefaults(defineProps<StarBorderProps>(), {
as: 'button',
customClass: '',
color: 'white',
speed: '6s',
thickness: 1
});
const restAttrs = useAttrs();
const componentStyle = computed(() => {
const base = {
padding: `${props.thickness}px 0`
};
const userStyle = (restAttrs.style as Record<string, string>) || {};
return { ...base, ...userStyle };
});
</script>
<style scoped>
@keyframes star-movement-bottom {
0% {
transform: translate(0%, 0%);
opacity: 1;
}
100% {
transform: translate(-100%, 0%);
opacity: 0;
}
}
@keyframes star-movement-top {
0% {
transform: translate(0%, 0%);
opacity: 1;
}
100% {
transform: translate(100%, 0%);
opacity: 0;
}
}
.animate-star-movement-bottom {
animation: star-movement-bottom linear infinite alternate;
}
.animate-star-movement-top {
animation: star-movement-top linear infinite alternate;
}
</style>

33
Build_God_Game/src/views/GameView.vue

@ -7,6 +7,12 @@ import { useRouter } from 'vue-router'
import Particles from '@/components/Particles/Particles.vue'
import GlareHover from '@/components/GlareHover/GlareHover.vue'
import ChatBox from '@/components/ChatBox.vue'
import ShinyText from '@/components/ShinyText/ShinyText.vue'
import StarBorder from '@/components/StarBorder/StarBorder.vue'
import trainingIcon from '@/assets/images/training.svg'
import missionIcon from '@/assets/images/mission.svg'
import scrapIcon from '@/assets/images/scrap.svg'
import characterIco from '@/assets/images/character.svg'
const authStore = useAuthStore()
const characterStore = useCharacterStore()
@ -33,12 +39,10 @@ const breakthroughMessage = ref('')
const showBreakthroughMessage = ref(false)
const menuItems = computed(() => [
{ label: '任务', icon: '🗺️' },
// { label: '', icon: '' },
// { label: '', icon: '🎒' },
{ label: '角色', icon: '👤' },
{ label: isTraining.value ? '打坐中' : '打坐', icon: isTraining.value ? '🔥' : '🧘', isTraining: isTraining.value },
{ label: '捡垃圾', icon: '🗑️' },
{ label: '任务', icon: missionIcon, useImage: true },
{ label: '角色', icon: characterIco, useImage: true },
{ label: isTraining.value ? '打坐中' : '打坐', icon: trainingIcon, useImage: true, isTraining: isTraining.value },
{ label: '捡垃圾', icon: scrapIcon, useImage: true },
])
const handleLogout = () => {
@ -74,7 +78,6 @@ const handleBreakthrough = async () => {
<template>
<div class="game-page">
<Particles :particle-count="100" :particle-colors="['#ffffff', '#cccccc']" class="particles-bg" />
<div class="game-container">
<div class="character-header" @click="handleSwitchCharacter">
<div class="character-info">
@ -83,8 +86,10 @@ const handleBreakthrough = async () => {
</div>
<span class="switch-btn">切换角色</span>
</div>
<h1 class="welcome-text">欢迎来到CUPOWER</h1>
<div style="text-align: center;">
<ShinyText class="welcome-text" text="✨ 欢迎来到我的世界" :speed="2" :delay="0.5" :disabled="false" :color="'#b5b5b5'"
:shine-color="'#34fef1'" :spread="120" :direction="'left'" :yoyo="false" :pause-on-hover="false" />
</div>
<div class="menu-grid">
<GlareHover v-for="item in menuItems" :key="item.label" width="100%" height="120px"
@ -93,7 +98,8 @@ const handleBreakthrough = async () => {
:glare-color="item.isTraining ? '#ff8844' : '#ffffff'" :glare-opacity="0.1" class="menu-card"
:class="{ 'training-active': item.isTraining }" @click="navigateTo(item)">
<div class="menu-content">
<span class="menu-icon">{{ item.icon }}</span>
<img v-if="item.useImage" :src="item.icon" class="menu-icon-img" />
<span v-else class="menu-icon">{{ item.icon }}</span>
<span class="menu-label">{{ item.label }}</span>
</div>
</GlareHover>
@ -217,6 +223,7 @@ const handleBreakthrough = async () => {
color: #ffffff;
letter-spacing: 0.1em;
margin-bottom: 24px;
font-weight: bold;
}
.menu-grid {
@ -247,6 +254,12 @@ const handleBreakthrough = async () => {
opacity: 0.8;
}
.menu-icon-img {
width: 3rem;
height: 3rem;
object-fit: contain;
}
.menu-label {
color: #cccccc;
font-size: 0.875rem;

Loading…
Cancel
Save