溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

怎么使用Three.js實(shí)現(xiàn)3D乒乓球小游戲

發(fā)布時(shí)間:2023-03-21 11:33:21 來(lái)源:億速云 閱讀:134 作者:iii 欄目:開(kāi)發(fā)技術(shù)

本篇內(nèi)容介紹了“怎么使用Three.js實(shí)現(xiàn)3D乒乓球小游戲”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

效果

怎么使用Three.js實(shí)現(xiàn)3D乒乓球小游戲

原理

React-Three-Fiber

React Three Fiber 是一個(gè)基于 Three.jsReact 渲染器,簡(jiǎn)稱 R3F。它像是一個(gè)配置器,把 Three.js 的對(duì)象映射為 R3F 中的組件。

特點(diǎn)
  • 使用可重用的組件以聲明方式構(gòu)建動(dòng)態(tài)場(chǎng)景圖,使 Three.js 的處理變得更加輕松,并使代碼庫(kù)更加整潔。這些組件對(duì)狀態(tài)變化做出反應(yīng),具有開(kāi)箱即用的交互性。

  • Three.js 中所有內(nèi)容都能在這里運(yùn)行。它不針對(duì)特定的 Three.js 版本,也不需要更新以修改,添加或刪除上游功能。

  • 渲染性能與 Three.js 和 GPU 相仿。組件參與 React 之外的 render loop 時(shí),沒(méi)有任何額外開(kāi)銷。

寫(xiě) React Three Fiber 比較繁瑣,我們可以寫(xiě)成 R3F 或簡(jiǎn)稱為 Fiber。讓我們從現(xiàn)在開(kāi)始使用 R3F 吧。

生態(tài)系統(tǒng)

R3F 有充滿活力的生態(tài)系統(tǒng),包括各種庫(kù)、輔助工具以及抽象方法:

  • @react-three/drei – 有用的輔助工具,自身就有豐富的生態(tài)

  • @react-three/gltfjsx – 將 GLTFs 轉(zhuǎn)換為 JSX 組件

  • @react-three/postprocessing – 后期處理效果

  • @react-three/test-renderer – 用于在 Node 中進(jìn)行單元測(cè)試

  • @react-three/flex – react-three-fiber 的 flex 盒子布局

  • @react-three/xr – VR/AR 控制器和事件

  • @react-three/csg – 構(gòu)造實(shí)體幾何

  • @react-three/rapier – 使用 Rapier 的 3D 物理引擎

  • @react-three/cannon – 使用 Cannon 的 3D 物理引擎

  • @react-three/p2 – 使用 P2 的 2D 物理引擎

  • @react-three/a11y – 可訪問(wèn)工具

  • @react-three/gpu-pathtracer – 真實(shí)的路徑追蹤

  • create-r3f-app next – nextjs 啟動(dòng)器

  • lamina – 基于 shader materials 的圖層

  • zustand – 基于 flux 的狀態(tài)管理

  • jotai – 基于 atoms 的狀態(tài)管理

  • valtio – 基于 proxy 的狀態(tài)管理

  • react-spring – 一個(gè) spring-physics-based 的動(dòng)畫(huà)庫(kù)

  • framer-motion-3d – framer motion,一個(gè)很受歡迎的動(dòng)畫(huà)庫(kù)

  • use-gesture – 鼠標(biāo)/觸摸手勢(shì)

  • leva – 創(chuàng)建 GUI 控制器

  • maath – 數(shù)學(xué)輔助工具

  • miniplex – ECS 實(shí)體管理系統(tǒng)

  • composer-suite – 合成著色器、粒子、特效和游戲機(jī)制、

安裝
npm install three @react-three/fiber
第一個(gè)場(chǎng)景

在一個(gè)新建的 React 項(xiàng)目中,我們通過(guò)以下的步驟使用 R3F 來(lái)創(chuàng)建第一個(gè)場(chǎng)景。

初始化Canvas

首先,我們從 @react-three/fiber 引入 Canvas 元素,將其放到 React 樹(shù)中:

import ReactDOM from 'react-dom'
import { Canvas } from '@react-three/fiber'

function App() {
  return (
    <div id="canvas-container">
      <Canvas />
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))

Canvas 組件在幕后做了一些重要的初始化工作:

  • 它初始化了一個(gè)場(chǎng)景 Scene 和一個(gè)相機(jī) Camera,它們都是渲染所需的基本模塊。

  • 它在頁(yè)面每一幀更新中都渲染場(chǎng)景,我們不需要再到頁(yè)面重繪方法中循環(huán)調(diào)用渲染方法。

Canvas 大小響應(yīng)式自適應(yīng)于父節(jié)點(diǎn),我們可以通過(guò)改變父節(jié)點(diǎn)的寬度和高度來(lái)控制渲染場(chǎng)景的尺寸大小。

添加一個(gè)Mesh組件

為了真正能夠在場(chǎng)景中看到一些物體,現(xiàn)在我們添加一個(gè)小寫(xiě)的 <mesh /> 元素,它直接等效于 new THREE.Mesh()。

<Canvas>
  <mesh />

可以看到我們沒(méi)有特地去額外引入mesh組件,我們不需要引入任何元素,所有Three.js中的對(duì)象都將被當(dāng)作原生的JSX元素,就像在 ReactDom 中寫(xiě) <div /><span /> 元素一樣。R3F Fiber組件的通用規(guī)則是將Three.js中的它們的名字寫(xiě)成駝峰式的DOM元素即可。

一個(gè) MeshThree.js 中的基礎(chǔ)場(chǎng)景對(duì)象,需要給它提供一個(gè)幾何對(duì)象 geometry 以及一個(gè)材質(zhì) material 來(lái)代表一個(gè)三維空間的幾何形狀,我們將使用一個(gè) BoxGeometryMeshStandardMaterial 來(lái)創(chuàng)建一個(gè)新的網(wǎng)格 Mesh,它們會(huì)自動(dòng)關(guān)聯(lián)到它們的父節(jié)點(diǎn)。

<Canvas>
  <mesh>
    <boxGeometry />
    <meshStandardMaterial />
  </mesh>

上述代碼和以下 Three.js 代碼是等價(jià)的:

const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000)

const renderer = new THREE.WebGLRenderer()
renderer.setSize(width, height)
document.querySelector('#canvas-container').appendChild(renderer.domElement)

const mesh = new THREE.Mesh()
mesh.geometry = new THREE.BoxGeometry()
mesh.material = new THREE.MeshStandardMaterial()

scene.add(mesh)

function animate() {
  requestAnimationFrame(animate)
  renderer.render(scene, camera)
}

animate()

構(gòu)造函數(shù)參數(shù):

根據(jù) BoxGeometry 的文檔,我們可以選擇給它傳遞三個(gè)參數(shù):widthlengthdepth

new THREE.BoxGeometry(2, 2, 2)

為了實(shí)現(xiàn)相同的功能,我們可以在 R3F 中使用 args 屬性,它總是接受一個(gè)數(shù)組,其項(xiàng)目表示構(gòu)造函數(shù)參數(shù):

<boxGeometry args={[2, 2, 2]} />
添加光源

接著,我們通過(guò)像下面這樣添加光源組件來(lái)為我們的場(chǎng)景添加一些光線。

<Canvas>
  <ambientLight intensity={0.1} />
  <directionalLight color="red" position={[0, 0, 5]} />

屬性:

這里介紹關(guān)于 R3F 的最后一個(gè)概念,即 React 屬性是如何在 Three.js 對(duì)象中工作的。當(dāng)你給一個(gè) Fiber 組件設(shè)置任意屬性時(shí),它將對(duì) Three.js 設(shè)置一個(gè)相同名字的屬性。我們關(guān)注到 ambientLight 上,由它的文檔可知,我們可以選擇 colorintensity 屬性來(lái)初始化它:

<ambientLight intensity={0.1} />

等價(jià)于

const light = new THREE.AmbientLight()
light.intensity = 0.1

快捷方法:

Three.js 中對(duì)于很多屬性的設(shè)置如 colorsvectors 等都可以使用 set() 方法進(jìn)行快捷設(shè)置:

const light = new THREE.DirectionalLight()
light.position.set(0, 0, 5)
light.color.set('red')

JSX 中也是相同的:

<directionalLight position={[0, 0, 5]} color="red" />
結(jié)果
<Canvas>
  <mesh>
    <boxBufferGeometry />
    <meshBasicMaterial color="#03c03c" />
  </mesh>
  <ambientLight args={[0xff0000]} intensity={0.1} />
  <directionalLight position={[0, 0, 5]} intensity={0.5} />
</Canvas>

怎么使用Three.js實(shí)現(xiàn)3D乒乓球小游戲

查看React Three Fiber完整API文檔

實(shí)現(xiàn)

〇 搭建頁(yè)面基本結(jié)構(gòu)

首先,我們創(chuàng)建一個(gè) Experience 文件作為渲染三維場(chǎng)景的組件,并在其中添加 Canvas 組件搭建基本頁(yè)面結(jié)構(gòu)。

import { Canvas } from "@react-three/fiber";

export default function Experience() {
  return (
    <>
      <Canvas></Canvas>
    </>
  );
}

怎么使用Three.js實(shí)現(xiàn)3D乒乓球小游戲

① 場(chǎng)景初始化

接著我們開(kāi)啟 Canvas 的陰影并設(shè)置相機(jī)參數(shù),然后添加環(huán)境光 ambientLight 和點(diǎn)光源 pointLight 兩種光源:

<Canvas
  shadows
  camera={{ fov: 50, position: [0, 5, 12] }}
>
  <ambientLight intensity={.5} />
  <pointLight position={[-10, -10, -10]} />
</Canvas>

如果需要修改 Canvas 的背景色,可以在其中添加一個(gè) color 標(biāo)簽并設(shè)置參數(shù) attachbackground,在 args 參數(shù)中設(shè)置顏色即可。

<Canvas>
  <color attach="background" args={["lightgreen"]} />
</Canvas>

怎么使用Three.js實(shí)現(xiàn)3D乒乓球小游戲

② 添加輔助工具

接著,我們?cè)陧?yè)面頂部引入 Perf,它是 R3F 生態(tài)中查看頁(yè)面性能的組件,它的功能和 Three.jsstats.js 是類似的,像下面這樣添加到代碼中設(shè)置它的顯示位置,頁(yè)面對(duì)應(yīng)區(qū)域就會(huì)出現(xiàn)可視化的查看工具,在上面可以查看 GPUCPU、FPS 等性能參數(shù)。

如果想使用網(wǎng)格作為輔助線或用作裝飾,可以使用 gridHelper 組件,它支持配置 positionrotation、args 等參數(shù)。

import { Perf } from "r3f-perf";

export default function Experience() {
  return (
    <>
      <Canvas>
        <Perf position="top-right" />
        <gridHelper args={[50, 50, '#11f1ff', '#0b50aa']} position={[0, -1.1, -4]} rotation={[Math.PI / 2.68, 0, 0]} />
      </Canvas>
    </>
  );
}

怎么使用Three.js實(shí)現(xiàn)3D乒乓球小游戲

③ 創(chuàng)建乒乓球和球拍

我們創(chuàng)建一個(gè)名為 PingPong.jsx 的乒乓球組件文件,然后在文件頂部引入以下依賴,其中 PhysicsuseBox、usePlane、useSphere 用于創(chuàng)建物理世界;useFrame 是用來(lái)進(jìn)行頁(yè)面動(dòng)畫(huà)更新的 hook,它將在頁(yè)面每幀重繪時(shí)執(zhí)行,我們可以在它里面執(zhí)行一些動(dòng)畫(huà)函數(shù)和更新控制器,相當(dāng)于 Three.js 中用原生實(shí)現(xiàn)的 requestAnimationFrame;useLoader 用于加載器的管理,使用它更方便進(jìn)行加載錯(cuò)誤管理和回調(diào)方法執(zhí)行;lerp 是一個(gè)插值運(yùn)算函數(shù),它可以計(jì)算某一數(shù)值到另一數(shù)值的百分比,從而得出一個(gè)新的數(shù)值,常用于移動(dòng)物體、修改透明度、顏色、大小、模擬動(dòng)畫(huà)等。

import { Physics, useBox, usePlane, useSphere } from "@react-three/cannon";
import { useFrame, useLoader } from "@react-three/fiber";
import { Mesh, TextureLoader } from "three";
import { GLTFLoader } from "three-stdlib/loaders/GLTFLoader";
import lerp from "lerp";
創(chuàng)建物理世界

然后創(chuàng)建一個(gè) PingPong 類,在其中添加 <Physics> 組件來(lái)創(chuàng)建物理世界,像直接使用 Cannon.js 一樣,可以給它設(shè)置 iterations、tolerancegravity、allowSleep 等參數(shù)來(lái)分別設(shè)置物理世界的迭代次數(shù)、容錯(cuò)性、引力以及是否支持進(jìn)入休眠狀態(tài)等,然后在其中添加一個(gè)平面幾何體和一個(gè)平面剛體 ContactGround。

function ContactGround() {
  const [ref] = usePlane(
    () => ({
      position: [0, -10, 0],
      rotation: [-Math.PI / 2, 0, 0],
      type: "Static",
    }),
    useRef < Mesh > null
  );
  return <mesh ref={ref} />;
}

export default function PingPong() {
  return (
    <>
      <Physics
        iterations={20}
        tolerance={0.0001}
        defaultContactMaterial={{
          contactEquationRelaxation: 1,
          contactEquationStiffness: 1e7,
          friction: 0.9,
          frictionEquationRelaxation: 2,
          frictionEquationStiffness: 1e7,
          restitution: 0.7,
        }}
        gravity={[0, -40, 0]}
        allowSleep={false}
      >
        <mesh position={[0, 0, -10]} receiveShadow>
          <planeGeometry args={[1000, 1000]} />
          <meshPhongMaterial color="#5081ca" />
        </mesh>
        <ContactGround />
      </Physics>
    </>
  );
}

怎么使用Three.js實(shí)現(xiàn)3D乒乓球小游戲

創(chuàng)建乒乓球

接著,我們創(chuàng)建一個(gè)球體類 Ball,在其中添加球體 ???? ,可以使用前面介紹的 useLoader 來(lái)管理它的貼圖加載,為了方便觀察到乒乓球的轉(zhuǎn)動(dòng)情況,貼圖中央加了一個(gè)十字交叉圖案 ?。然后將其放在 <Physics> 標(biāo)簽下。

function Ball() {
  const map = useLoader(TextureLoader, earthImg);
  const [ref] = useSphere(
    () => ({ args: [0.5], mass: 1, position: [0, 5, 0] }),
    useRef < Mesh > null
  );
  return (
    <mesh castShadow ref={ref}>
      <sphereGeometry args={[0.5, 64, 64]} />
      <meshStandardMaterial map={map} />
    </mesh>
  );
}

export default function PingPong() {
  return (
    <>
      <Physics>
        { /* ... */ }
        <Ball />
      </Physics>
    </>
  );
}

怎么使用Three.js實(shí)現(xiàn)3D乒乓球小游戲

創(chuàng)建球拍

球拍采用的是一個(gè) glb 格式的模型,在 Blender 中我們可以看到模型的樣式和詳細(xì)的骨骼結(jié)構(gòu),對(duì)于模型的加載,我們同樣使用 useLoader 來(lái)管理,此時(shí)的加載器需要使用 GLTFLoader。

怎么使用Three.js實(shí)現(xiàn)3D乒乓球小游戲

我們創(chuàng)建一個(gè) Paddle 類并將其添加到 <Physics> 標(biāo)簽中,在這個(gè)類中我們實(shí)現(xiàn)模型加載,模型加載完成后綁定骨骼,并在 useFrame 頁(yè)面重繪方法中,根據(jù)鼠標(biāo)所在位置更新乒乓球拍模型的位置 position,并根據(jù)是否一開(kāi)始游戲狀態(tài)以及鼠標(biāo)的位置來(lái)更新球拍的 x軸y軸 方向的 rotation 值。

function Paddle() {
  const { nodes, materials } = useLoader(
    GLTFLoader,
    '/models/pingpong.glb',
  );
  const model = useRef();
  const [ref, api] = useBox(() => ({
    type: 'Kinematic',
    args: [3.4, 1, 3.5],
  }));
  const values = useRef([0, 0]);
  useFrame((state) => {
    values.current[0] = lerp(
      values.current[0],
      (state.mouse.x * Math.PI) / 5,
      0.2
    );
    values.current[1] = lerp(
      values.current[1],
      (state.mouse.x * Math.PI) / 5,
      0.2
    );
    api.position.set(state.mouse.x * 10, state.mouse.y * 5, 0);
    api.rotation.set(0, 0, values.current[1]);
    if (!model.current) return;
    model.current.rotation.x = lerp(
      model.current.rotation.x,
      started ? Math.PI / 2 : 0,
      0.2
    );
    model.current.rotation.y = values.current[0];
  });

  return (
    <mesh ref={ref} dispose={null}>
      <group
        ref={model}
        position={[-0.05, 0.37, 0.3]}
        scale={[0.15, 0.15, 0.15]}
      >
        <group rotation={[1.88, -0.35, 2.32]} scale={[2.97, 2.97, 2.97]}>
          <primitive object={nodes.Bone} />
          <primitive object={nodes.Bone003} />
          { /* ... */ }
          <skinnedMesh
            castShadow
            receiveShadow
            material={materials.glove}
            material-roughness={1}
            geometry={nodes.arm.geometry}
            skeleton={nodes.arm.skeleton}
          />
        </group>
        <group rotation={[0, -0.04, 0]} scale={[141.94, 141.94, 141.94]}>
          <mesh
            castShadow
            receiveShadow
            material={materials.wood}
            geometry={nodes.mesh.geometry}
          />
          { /* ... */ }
        </group>
      </group>
    </mesh>
  );
}

到這里,我們已經(jīng)實(shí)現(xiàn)乒乓球顛球的基本功能了 ????

怎么使用Three.js實(shí)現(xiàn)3D乒乓球小游戲

顛球計(jì)數(shù)

為了顯示每次游戲可以顛球的次數(shù),現(xiàn)在我們?cè)谄古仪蚺闹醒爰由蠑?shù)字顯示 5?? 。我們可以像下面這樣創(chuàng)建一個(gè) Text 類,在文件頂部引入 TextGeometry、FontLoader、fontJson 作為字體幾何體、字體加載器以及字體文件,添加一個(gè) geom 作為創(chuàng)建字體幾何體的方法,當(dāng) count 狀態(tài)值發(fā)生變化時(shí),實(shí)時(shí)更新創(chuàng)建字體幾何體模型。

import { useMemo } from "react";
import { TextGeometry } from "three/examples/jsm/geometries/TextGeometry";
import { FontLoader } from "three/examples/jsm/loaders/FontLoader";
import fontJson from "../public/fonts/firasans_regular.json";

const font = new FontLoader().parse(fontJson);
const geom = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'].map(
  (number) => new TextGeometry(number, { font, height: 0.1, size: 5 })
);

export default function Text({ color = 0xffffff, count, ...props }) {
  const array = useMemo(() => [...count], [count]);
  return (
    <group {...props} dispose={null}>
      {array.map((char, index) => (
        <mesh
          position={[-(array.length / 2) * 3.5 + index * 3.5, 0, 0]}
          key={index}
          geometry={geom[parseInt(char)]}
        >
          <meshBasicMaterial color={color} transparent opacity={0.5} />
        </mesh>
      ))}
    </group>
  );
}

然后將 Text 字體類放入球拍幾何體中,其中 count 字段需要在物理世界中剛體發(fā)生碰撞時(shí)進(jìn)行更新,該方法加載下節(jié)內(nèi)容添加碰撞音效時(shí)一起實(shí)現(xiàn)。

function Paddle() {
  return (
    <mesh ref={ref} dispose={null}>
      <group ref={model}>
        { /* ... */ }
        <Text
          rotation={[-Math.PI / 2, 0, 0]}
          position={[0, 1, 2]}
          count={count.toString()}
        />
      </group>
    </mesh>
  );
}

怎么使用Three.js實(shí)現(xiàn)3D乒乓球小游戲

④ 頁(yè)面裝飾

到這里,整個(gè)小游戲的全部流程都開(kāi)發(fā)完畢了,現(xiàn)在我們來(lái)加一些頁(yè)面提示語(yǔ)、顛球時(shí)的碰撞音效,頁(yè)面的光照效果等,使 3D 場(chǎng)景看起來(lái)更加真實(shí)。

音效

實(shí)現(xiàn)音效前,我們先像下面這樣添加一個(gè)狀態(tài)管理器,來(lái)進(jìn)行頁(yè)面全局狀態(tài)的管理。zustand 是一個(gè)輕量級(jí)的狀態(tài)管理庫(kù);_.clamp(number, [lower], upper) 用于返回限制在 lowerupper 之間的值;pingSound 是需要播放的音頻文件。我們?cè)谄渲刑砑右粋€(gè) pong 方法用來(lái)更新音效和顛球計(jì)數(shù),添加一個(gè) reset 方法重置顛球數(shù)字。count 字段表示每次的顛球次數(shù),welcome 表示是否在歡迎界面。

import create from "zustand";
import clamp from "lodash-es/clamp";
import pingSound from "/medias/ping.mp3";

const ping = new Audio(pingSound);

export const useStore = create((set) => ({
  api: {
    pong(velocity) {
      ping.currentTime = 0;
      ping.volume = clamp(velocity / 20, 0, 1);
      ping.play();
      if (velocity > 4) set((state) => ({ count: state.count + 1 }));
    },
    reset: (welcome) =>
      set((state) => ({ count: welcome ? state.count : 0, welcome })),
  },
  count: 0,
  welcome: true,
}));

然后我們可以在上述 Paddle 乒乓球拍類中像這樣在物體發(fā)生碰撞時(shí)觸發(fā) pong 方法:

function Paddle() {
  {/* ... */}
  const [ref, api] = useBox(() => ({
    type: "Kinematic",
    args: [3.4, 1, 3.5],
    onCollide: (e) => pong(e.contact.impactVelocity),
  }));
}
光照

為了是場(chǎng)景更加真實(shí),我們可以開(kāi)啟 Canvas 的陰影,然后添加多種光源 ???? 來(lái)優(yōu)化場(chǎng)景,如 spotLight 就能起到視覺(jué)聚焦的作用。

<Canvas
  shadows
  camera={{ fov: 50, position: [0, 5, 12] }}
>
  <ambientLight intensity={.5} />
  <pointLight position={[-10, -10, -10]} />
  <spotLight
    position={[10, 10, 10]}
    angle={0.3}
    penumbra={1}
    intensity={1}
    castShadow
    shadow-mapSize-width={2048}
    shadow-mapSize-height={2048}
    shadow-bias={-0.0001}
  />
  <PingPong />
</Canvas>
提示語(yǔ)

為了提升小游戲的用戶體驗(yàn),我們可以添加一些頁(yè)面文字提示來(lái)指引使用者和提升頁(yè)面視覺(jué)效果,需要注意的是,這些額外的元素不能添加到 <Canvas /> 標(biāo)簽內(nèi)哦 ????。

const style = (welcome) => ({
  color: '#000000',
  display: welcome ? 'block' : 'none',
  fontSize: '1.8em',
  left: '50%',
  position: "absolute",
  top: 40,
  transform: 'translateX(-50%)',
  background: 'rgba(255, 255, 255, .2)',
  backdropFilter: 'blur(4px)',
  padding: '16px',
  borderRadius: '12px',
  boxShadow: '1px 1px 2px rgba(0, 0, 0, .2)',
  border: '1px groove rgba(255, 255, 255, .2)',
  textShadow: '0px 1px 2px rgba(255, 255, 255, .2), 0px 2px 2px rgba(255, 255, 255, .8), 0px 2px 4px rgba(0, 0, 0, .5)'
});

<div style={style(welcome)}>???? 點(diǎn)擊任意區(qū)域開(kāi)始顛球</div>

怎么使用Three.js實(shí)現(xiàn)3D乒乓球小游戲

“怎么使用Three.js實(shí)現(xiàn)3D乒乓球小游戲”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI