注册
web

基于英雄联盟人物的加载动画,奇怪的需求又增加了!

1、背景


前两天老板找到我说有一个需求,要求使用英雄联盟的人物动画制作一个加载中的组件,类似于下面这样:


iShot_2024-06-06_18.09.55.gif


我定眼一看:这个可以实现,但是需要UI妹子给切图。


老板:UI? 咱们啥时候招的UI !


我:老板,那不中呀,不切图弄不成呀。


老板:下个月绩效给你A。


我:那中,管管管。


2、调研


发动我聪明的秃头,实现这个需求有以下几种方案:



  • 切动画帧,没有UI不中❎。
  • 去lol客户端看看能不能搞到什么美术素材,3D模型啥的,可能行❓
  • 问下 gpt4o,有没有哪个老表收集的有lol英雄的美术素材,如果有那就更得劲了✅。

经过我一番搜索,发现了这个网站:model-viewer,收集了很多英雄联盟的人物模型,模型里面还有各种动画,还给下载。老表,这个需求稳了50%了!


image-20240606182312802.png


接下来有几种选择:



  • 将模型动画转成动画帧,搞成雪碧图,较为麻烦,且动画不支持切换。
  • 直接加载模型,将模型放在进度条上,较为简单,支持切换不同动画,而且可以自由过渡。就是模型文件有点大,初始化加载可能耗时较长。但是后续缓存一下就好了。

聪明的我肯定先选第二个方案呀,你糊弄我啊,我糊弄你。


3、实现


web中加载模型可以使用谷歌基于threejs封装的 model-viewer, 使用现代的 web component 技术。简单易用。


先初始化一个vue工程


 npm create vue@latest

然后将里面的初始化的组件和app.vue里面的内容都删除。


安装model-viewer依赖:


npm i three // 前置依赖
npm i @google/model-viewer

修改vite.config.js,将model-viewer视为自定义元素,不进行编译


import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
 plugins: [
   vue({
     template: {
       // 添加以下内容
       compilerOptions: {
         isCustomElement: (tag) => ['model-viewer'].includes(tag)
      }
    }
  })
],
 resolve: {
   alias: {
     '@': fileURLToPath(new URL('./src', import.meta.url))
  }
},
 assetsInclude: ['./src/assets/heros/*.glb']
})


新建 src/components/LolProgress.vue


<template>
 <div class="progress-container">
   <model-viewer
     :src="hero.src"
     disable-zoom
     shadow-intensity="1"
     :camera-orbit="hero.cameraOrbit"
     class="model-viewer"
     :style="heroPosition"
     :animation-name="animationName"
     :camera-target="hero.cameraTarget"
     autoplay
     ref="modelViewer"
   >
</model-viewer>
   <div
     class="progress-bar"
     :style="{ height: strokeWidth + 'px', borderRadius: strokeWidth / 2 + 'px' }"
   >

     <div class="progress-percent" :style="currentPercentStyle"></div>
   </div>
 </div>

</template>

<script setup lang="ts">
import { computed, onMounted, ref, watch, type PropType } from 'vue'
/** 类型 */
interface Hero {
 src: string
 cameraOrbit: string
 progressAnimation: string
 finishAnimation: string
 finishAnimationIn: string
 cameraTarget: string
 finishDelay: number
}
type HeroName = 'yasuo' | 'yi'

type Heros = {
[key in HeroName]: Hero
}
const props = defineProps({
 hero: {
   type: String as PropType<HeroName>,
   default: 'yasuo'
},
 percentage: {
   type: Number,
   default: 100
},
 strokeWidth: {
   type: Number,
   default: 10
},
 heroSize: {
   type: Number,
   default: 150
}
})

const modelViewer = ref(null)

const heros: Heros = {
 yasuo: {
   src: '/src/components/yasuo.glb',
   cameraOrbit: '-90deg 90deg',
   progressAnimation: 'Run2',
   finishAnimationIn: 'yasuo_skin02_dance_in',
   finishAnimation: 'yasuo_skin02_dance_loop',
   cameraTarget: 'auto auto 0m',
   finishDelay: 2000
},
 yi: {
   src: '/src/components/yi.glb',
   cameraOrbit: '-90deg 90deg',
   progressAnimation: 'Run',
   finishAnimationIn: 'Dance',
   finishAnimation: 'Dance',
   cameraTarget: 'auto auto 0m',
   finishDelay: 500
}
}

const heroPosition = computed(() => {
 const percentage = props.percentage > 100 ? 100 : props.percentage
 return {
   left: `calc(${percentage + '%'} - ${props.heroSize / 2}px)`,
   bottom: -props.heroSize / 10 + 'px',
   height: props.heroSize + 'px',
   width: props.heroSize + 'px'
}
})

const currentPercentStyle = computed(() => {
 const percentage = props.percentage > 100 ? 100 : props.percentage
 return { borderRadius: `calc(${props.strokeWidth / 2}px - 1px)`, width: percentage + '%' }
})

const hero = computed(() => {
 return heros[props.hero]
})

const animationName = ref('')

watch(
() => props.percentage,
(percentage) => {
   if (percentage < 100) {
     animationName.value = hero.value.progressAnimation
  } else if (percentage === 100) {
     animationName.value = hero.value.finishAnimationIn
     setTimeout(() => {
       animationName.value = hero.value.finishAnimation
    }, hero.value.finishDelay)
  }
}
)
onMounted(() => {
 setTimeout(() => {
   console.log(modelViewer.value.availableAnimations)
}, 2000)
})
</script>
<style scoped>
.progress-container {
 position: relative;
 width: 100%;
}
.model-viewer {
 position: relative;
 background: transparent;
}
.progress-bar {
 border: 1px solid #fff;
 background-color: #666;
 width: 100%;
}
.progress-percent {
 background-color: aqua;
 height: 100%;
 transition: width 100ms ease;
}
</style>


组件非常简单,核心逻辑如下:



  • 根据传入的英雄名称加载模型
  • 指定每个英雄的加载中的动画,
  • 加载100%,切换完成动作进入动画和完成动画即可。
  • 额外的细节处理。

    最后修改 app.vue:


    <script setup lang="ts">
    import { ref } from 'vue'
    import LolProgress from './components/LolProgress.vue'
    const percentage = ref(0)
    setInterval(() => {
     percentage.value = percentage.value + 1
    }, 100)
    </script>

    <template>
     <main>
       <LolProgress
         :style="{ width: '200px' }"
         :percentage="percentage"
         :heroSize="200"
         hero="yasuo"
       />

     </main>
    </template>

    <style scoped></style>




这不就完成了吗,先拿给老板看看。


老板:换个女枪的看看。


我:好嘞。


iShot_2024-06-06_19.08.49.gif


老板:弄类不赖啊小伙,换个俄洛伊的看看。


4、总结


通过本次需求,了解到了 model-viewer组件。


老板招个UI妹子吧。


在线体验:github-pages


作者:盖伦大王
来源:juejin.cn/post/7377217883305279526

0 个评论

要回复文章请先登录注册