vue3+threejs新手从零开发卡牌游戏(十八):己方场上手牌添加画线

手牌上场后,点击己方怪兽区卡牌会跟随鼠标移动画出线条,之后可以通过判断鼠标移动到对方场地的某卡牌进行战斗操作,代码主要改动在game/index.vue文件。

1.添加鼠标移动监听事件(移动端):

window.addEventListener('touchstart', onMousedown)
window.addEventListener('touchmove', onMousemove)
window.addEventListener('touchend', onMouseup)

2.鼠标按下获取选中手牌:

// 鼠标按下事件
const onMousedown = (ev: any) => {
  ...
  // 鼠标选中场上卡牌事件
  onSelectSiteCard(ev)
}

// 鼠标选中场上卡牌事件
const onSelectSiteCard = (point: any) => {
  let p1Cards = scene.children.filter((v: any) => v.userData?.areaType?.indexOf("己方怪兽区") > -1)
  if (p1Cards.length <= 0) {
    return
  }
  let arr = raycaster.intersectObjects(p1Cards, true)
  if (arr.length > 0) {
    selectedCard.value = arr[0].object
    console.log(444, arr[0].object)
    drawLine()
  }

}

3.添加画线逻辑,主要通过CatmullRomCurve3和TubeGeometry管道缓冲几何体配合画线,网上随便找了个箭头的png图做素材:

// 鼠标移动事件
const onMousemove = (ev: any) => {
  // 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
  pointer.x = ev.clientX || ev.changedTouches[0].pageX
  pointer.y = ev.clientY || ev.changedTouches[0].pageY
  // 画线
  if (selectedCard.value) {
    drawLine()
  }
}


// 鼠标抬起事件
const onMouseup = (ev: any) => {
  let lineMesh = scene.getObjectByName("移动线")
  if (lineMesh) {
    scene.remove(lineMesh)
  }
}

// 画线
const drawLine = () => {
  let mesh = scene.getObjectByName("移动线")
  if (mesh) {
    scene.remove(mesh)
  }
  
  let startPos = new THREE.Vector3(0, 0, 0)
  selectedCard.value.getWorldPosition(startPos)
  let point = transPos(pointer.x, pointer.y)
  
  // 通过摄像机和鼠标位置更新射线
  raycaster.setFromCamera( point, camera );

  let plane = scene.getObjectByName("地面")
  let arr = raycaster.intersectObject(plane)
  if (arr.length > 0) {
    // let pos = new THREE.Vector3(0, 0, 0)
    // arr[0].object.getWorldPosition(pos)
    let point = arr[0].point
    let curve = new THREE.CatmullRomCurve3([
      new THREE.Vector3(startPos.x, startPos.y + 0.2, startPos.z),
      new THREE.Vector3(point.x, point.y + 0.2, point.z)
      
    ]);
    let tubeGeometry = new THREE.TubeGeometry(curve, 80, 0.08);

    const material = new THREE.MeshBasicMaterial({
      map: arrowTexture,
      side: THREE.BackSide, //显示背面
      transparent: true,
      // color: new THREE.Color("#ff6347")
    });
    const _mesh = new THREE.Mesh( tubeGeometry, material );
    _mesh.name = "移动线"
    scene.add(_mesh);
  }
}

页面效果如下,后续方便进行战斗逻辑处理:

附:
game/index.vue代码:

<template>
  <div ref="sceneRef" class="scene"></div>
  <!-- 玩家区 -->
  <Player ref="playerRef"/>
  <!-- 手牌 -->
  <Hand ref="handRef"/>
  <!-- 卡组 -->
  <Deck ref="deckRef"/>
  <!-- 战域 -->
  <Site ref="siteRef"/>
  <!-- 抽卡逻辑 -->
  <DrawCard ref="drawCardRef" :handRef="handRef"/>
  <!-- 对话框 -->
  <Dialog ref="dialogRef" @handToSite="handToSite" @onCancel="onCancel"/>
</template>

<script setup lang="ts">
import { reactive, ref, onMounted, onBeforeUnmount, watch, defineComponent, getCurrentInstance, nextTick } from 'vue'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; // 轨道控制器
import { DragControls } from 'three/addons/controls/DragControls.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { GlitchPass } from 'three/addons/postprocessing/GlitchPass.js';
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
import { GammaCorrectionShader } from 'three/examples/jsm/shaders/GammaCorrectionShader.js';
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass.js';
import { FontLoader } from 'three/addons/loaders/FontLoader.js';
import { useCommonStore } from "@/stores/common.ts"
import { transPos, editDeckCard, renderDeckText, renderSiteCardText } from "@/utils/common.ts"
import { Card } from "./Card.ts"
import { CARD_DICT } from "@/utils/dict/card.ts"
import { p1TestDeck, p2TestDeck} from "@/utils/deck/test.ts"
import Hand from "./hand/index.vue"
import Deck from "./deck/index.vue"
import Site from "./site/index.vue"
import Player from "./player/index.vue"
import DrawCard from "@/components/DrawCard.vue"
import Dialog from "@/components/Dialog.vue"
import { resolve } from 'dns';

// 引入threejs变量
const {proxy} = getCurrentInstance()
const THREE = proxy['THREE']
const scene = proxy['scene']
const camera = proxy['camera']
const renderer = proxy['renderer']
const composer = proxy['composer']
const TWEEN = proxy['TWEEN']

// 后期处理
const renderPass = new RenderPass( scene, camera );
composer.addPass( renderPass );

// 
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();

const commonStore = useCommonStore()


// 场景ref
const sceneRef = ref()
const playerRef = ref()
const siteRef = ref()
const handRef = ref()
const deckRef = ref()
const drawCardRef = ref()
const dialogRef = ref()
const selectedCard = ref() // 选中的场上card

// 坐标轴辅助
const axesHelper = new THREE.AxesHelper(5);
// 创建轨道控制器
// const orbitControls = new OrbitControls( camera, renderer.domElement );
// 字体加载器
const fontLoader = new FontLoader();

onMounted(async () => {
  await initResource()
  initScene()
  initGame()

  // 鼠标按下
  window.addEventListener('touchstart', onMousedown)
  window.addEventListener('touchmove', onMousemove)
  window.addEventListener('touchend', onMouseup)
  // window.addEventListener('click', onMousedown)

  // 监听浏览器窗口变化进行场景自适应
  window.addEventListener('resize', onWindowResize, false);
})

// 资源加载
const initResource = () => {
  // 字体加载
  return new Promise((resolve, reject) => {
    // Microsoft YaHei_Regular
    // fonts/helvetiker_regular.typeface.json
    fontLoader.load('fonts/helvetiker_bold.typeface.json', (font: any) => {
      commonStore.loadFont(font)
      resolve(true)
    });
  })
}

// 初始化场景
const initScene = async () => {
  renderer.setSize( window.innerWidth, window.innerHeight );
  sceneRef.value.appendChild( renderer.domElement );
  scene.add(axesHelper)
  // addSceneBg()

  // camera.position.set( 5, 5, 5 );
  camera.position.set( 0, 6.5, 0 );
  camera.lookAt(0, 0, 0)
  // const glitchPass = new GlitchPass();
  // composer.addPass( glitchPass );

  // const outputPass = new OutputPass();
  // composer.addPass( outputPass );

  addPlane()

  animate();
}

// scene添加动态背景
const addSceneBg = () => {
  const vertices = [];

  for ( let i = 0; i < 5000; i ++ ) {
    const x = THREE.MathUtils.randFloatSpread( 1000 );
    const y = THREE.MathUtils.randFloatSpread( 1000 );
    const z = THREE.MathUtils.randFloatSpread( 1000 );
    vertices.push( x, y, z );
  }

  const particlesGeometry = new THREE.BufferGeometry();
  particlesGeometry.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );

  // 设置点材质
  const pointsMaterial = new THREE.PointsMaterial();
  // pointsMaterial.size = 0.9;
  pointsMaterial.color.set(new THREE.Color("#fff"));
  // 相机深度而衰减
  pointsMaterial.sizeAttenuation = true;

  const points = new THREE.Points(particlesGeometry, pointsMaterial);

  scene.add(points);

  // const texture = new THREE.TextureLoader().load( "textures/bg.jpg" );
  // // const geometry = new THREE.SphereGeometry( 1, 32, 16 );
  // const geometry = new THREE.CircleGeometry( 6, 32 );
  // const material = new THREE.MeshBasicMaterial({
  //   map: texture
  // });
  // const sphere = new THREE.Mesh( geometry, material );
  // sphere.name = "场景背景"
  // sphere.position.set(0, 0, 0)
  // sphere.rotateX(-90 * (Math.PI / 180)) // 弧度
  // scene.add( sphere );

  // texture.wrapS = THREE.RepeatWrapping;
  // texture.wrapT = THREE.RepeatWrapping;
  // scene.background = texture
}

// scene中添加plane几何体
const addPlane = () => {
  const geometry = new THREE.PlaneGeometry( 20, 20);
  const material = new THREE.MeshBasicMaterial( {
    color: new THREE.Color("gray"), 
    side: THREE.FrontSide, 
    alphaHash: true,
    // alphaTest: 0,
    opacity: 0
  } );
  const plane = new THREE.Mesh( geometry, material );
  plane.rotateX(-90 * (Math.PI / 180)) // 弧度
  plane.name = "地面"
  scene.add( plane );
}

// 用requestAnimationFrame进行渲染循环
// let bgMesh = scene.getObjectByName("场景背景")
// console.log(123, bgMesh)

let arrowTexture = new THREE.TextureLoader().load('textures/arrow.png');
arrowTexture.wrapT = THREE.RepeatWrapping;
arrowTexture.repeat.set(1, 4);
arrowTexture.center = new THREE.Vector2(0.5, 0.5)
arrowTexture.rotation = (-90 * (Math.PI / 180))
arrowTexture.needsUpdate = true;

const animate = () => {
  requestAnimationFrame( animate );
  if (arrowTexture) {
    arrowTexture.offset.y -= 0.02; //更新箭头纹理偏移量
  }
  TWEEN.update()
  // let bgMesh = scene.getObjectByName("场景背景")
  // if (bgMesh) {
  //   bgMesh.rotation.z += 0.0002
  // }
  // renderer.render( scene, camera );
  composer.render()
}

// 场景跟随浏览器窗口大小自适应
const onWindowResize = () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
}

// 初始化游戏
const initGame = async () => {
  // 初始化玩家
  initPlayer()
  // 初始化场地
  initSite()
  // 初始化卡组
  await initDeck()
  // 初始化手牌
  await initHand()
  // 绑定手牌事件
  onHandEvent()

  // 
  await p2DrawCardEvent(1)
  let p2_handGroup = scene.getObjectByName("p2_handGroup")
  let cards = p2_handGroup.children.filter((v: any) => v.userData.type === "怪兽")
  console.log(123, cards)
  p2HandToSite(cards[0])
}

// 初始化玩家
const initPlayer = () => {
  playerRef.value.init()
}

// 初始化场地
const initSite = () => {
  siteRef.value.init()
}

// 初始化卡组
const initDeck = () => {
  return new Promise((resolve, reject) => {
    let p1Deck: any = []
    let p2Deck: any = []
    // 洗牌
    p1TestDeck.sort(() => {
      return Math.random() - 0.5
    })
    p2TestDeck.sort(() => {
      return Math.random() - 0.5
    })
    p1TestDeck.forEach((v: any, i: any) => {
      let obj = CARD_DICT.find((b: any) => b.card_id === v)
      if (obj) {
        p1Deck.push({
          card_id: v,
          name: `${obj.name}_${i}`
        })
      }
    })
    p2TestDeck.forEach((v: any, i: any) => {
      let obj = CARD_DICT.find((b: any) => b.card_id === v)
      if (obj) {
        p2Deck.push({
          card_id: v,
          name: `${obj.name}_${i}`
        })
      }
    })
    // console.log("p1Deck", newDeck)
    commonStore.updateP1Deck(p1Deck)
    commonStore.updateP2Deck(p2Deck)
    
    nextTick(() => {
      handRef.value.init()
      deckRef.value.init()
      resolve(true)
    })
  })
}

// 初始化手牌
const initHand = () => {
  let p1 = new Promise((resolve, reject) => {
    let cardNumber = 4
    let _number = 0
    let p1Deck = JSON.parse(JSON.stringify(commonStore.$state.p1Deck))
    let deckGroup = scene.getObjectByName("p1_deckGroup")
    let position = new THREE.Vector3(0, 0.005 * p1Deck.length, 0)
    let _interval = setInterval(async() => {
      // console.log(123, p1Deck)
      if (_number < cardNumber) {
        let obj = p1Deck[p1Deck.length - 1]
        p1Deck.splice(p1Deck.length-1, 1)
        commonStore.updateP1Deck(p1Deck)
        // 修改卡组
        await editDeckCard(deckGroup, obj, "remove")
        await renderDeckText(deckGroup, `${commonStore.$state.p1Deck.length}`, commonStore.$state._font, position)
        // 手牌区添加手牌
        handRef.value.addP1HandCard(obj, deckGroup)
      } else {
        clearInterval(_interval)
        resolve(true)
      }
      _number++
    }, 200)
  })

  let p2 = new Promise((resolve, reject) => {
    let cardNumber = 4
    let _number = 0
    let p2Deck = JSON.parse(JSON.stringify(commonStore.$state.p2Deck))
    let deckGroup = scene.getObjectByName("p2_deckGroup")
    let position = new THREE.Vector3(0, 0.005 * p2Deck.length, 0)
    let _interval = setInterval(async() => {
      // console.log(123, p1Deck)
      if (_number < cardNumber) {
        let obj = p2Deck[p2Deck.length - 1]
        p2Deck.splice(p2Deck.length-1, 1)
        commonStore.updateP2Deck(p2Deck)
        // 修改卡组
        await editDeckCard(deckGroup, obj, "remove")
        await renderDeckText(deckGroup, `${commonStore.$state.p2Deck.length}`, commonStore.$state._font, position)
        // 手牌区添加手牌
        handRef.value.addP2HandCard(obj, deckGroup)
      } else {
        clearInterval(_interval)
        resolve(true)
      }
      _number++
    }, 200)
  })
  return new Promise((resolve, reject) => {
    Promise.all([p1, p2]).then((res: any) => {
      resolve(true)
    })
  })
}

// p2抽牌事件
const p2DrawCardEvent = (cardNumber: any) => {
  return new Promise((resolve, reject) => {
    let _number = 0
    let p2Deck = JSON.parse(JSON.stringify(commonStore.$state.p2Deck))
    let deckGroup = scene.getObjectByName("p2_deckGroup")
    let position = new THREE.Vector3(0, 0.005 * p2Deck.length, 0)
    let _interval = setInterval(async() => {
      // console.log(123, p1Deck)
      if (_number < cardNumber) {
        let obj = p2Deck[p2Deck.length - 1]
        p2Deck.splice(p2Deck.length-1, 1)
        commonStore.updateP2Deck(p2Deck)
        // 修改卡组
        await editDeckCard(deckGroup, obj, "remove")
        await renderDeckText(deckGroup, `${commonStore.$state.p2Deck.length}`, commonStore.$state._font, position)
        // 手牌区添加手牌
        handRef.value.addP2HandCard(obj, deckGroup)
      } else {
        clearInterval(_interval)
        resolve(true)
      }
      _number++
    }, 200)
  })
}

// p2卡牌上场事件
const p2HandToSite = (card: any) => {
  let sitePlane = scene.getObjectByName("对方战域Plane")
  let userData = card.userData
  if (userData.type === "怪兽") {
    let meshes = sitePlane.children.filter((v: any) => v.name.indexOf("对方怪兽区") > -1 && v.userData.empty === true)
    if (meshes.length > 0) {
      let _mesh = null
      let m1 = meshes.find((v: any) => v.name.indexOf(1) > -1)
      let m2 = meshes.find((v: any) => v.name.indexOf(2) > -1)
      let m3 = meshes.find((v: any) => v.name.indexOf(3) > -1)
      if (m2) {
        _mesh = m2
      } else if (m1) {
        _mesh = m1
      } else if (m3) {
        _mesh = m3
      }
      let p2_handGroup = scene.getObjectByName("p2_handGroup")
      // card.rotateX(180 * (Math.PI / 180)) // 弧度
      renderSiteCard(p2_handGroup, card, _mesh)
    }
  }
}

// 鼠标按下事件
const onMousedown = (ev: any) => {
  // console.log(222, ev.target)
  // 判断是否点击到canvas上
  if(!(ev.target instanceof HTMLCanvasElement)){
    return;
  }
  // 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
  let clientX = ev.clientX || ev.changedTouches[0].pageX
  let clientY = ev.clientY || ev.changedTouches[0].pageY

  let point = transPos(clientX, clientY)

  // 通过摄像机和鼠标位置更新射线
  raycaster.setFromCamera( point, camera );

  // 点击卡组事件
  // onP1DeckEvent()
  // 鼠标选中场上卡牌事件
  onSelectSiteCard(ev)
}

// 鼠标移动事件
const onMousemove = (ev: any) => {
  // 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
  pointer.x = ev.clientX || ev.changedTouches[0].pageX
  pointer.y = ev.clientY || ev.changedTouches[0].pageY
  // 画线
  if (selectedCard.value) {
    drawLine()
  }
}

// 鼠标抬起事件
const onMouseup = (ev: any) => {
  let lineMesh = scene.getObjectByName("移动线")
  if (lineMesh) {
    scene.remove(lineMesh)
  }
}

// 鼠标选中场上卡牌事件
const onSelectSiteCard = (point: any) => {
  let p1Cards = scene.children.filter((v: any) => v.userData?.areaType?.indexOf("己方怪兽区") > -1)
  if (p1Cards.length <= 0) {
    return
  }
  let arr = raycaster.intersectObjects(p1Cards, true)
  if (arr.length > 0) {
    selectedCard.value = arr[0].object
    console.log(444, arr[0].object)
    drawLine()
  }

}

// 画线
const drawLine = () => {
  let mesh = scene.getObjectByName("移动线")
  if (mesh) {
    scene.remove(mesh)
  }
  
  let startPos = new THREE.Vector3(0, 0, 0)
  selectedCard.value.getWorldPosition(startPos)
  let point = transPos(pointer.x, pointer.y)
  
  // 通过摄像机和鼠标位置更新射线
  raycaster.setFromCamera( point, camera );

  let plane = scene.getObjectByName("地面")
  let arr = raycaster.intersectObject(plane)
  if (arr.length > 0) {
    // let pos = new THREE.Vector3(0, 0, 0)
    // arr[0].object.getWorldPosition(pos)
    let point = arr[0].point
    let curve = new THREE.CatmullRomCurve3([
      new THREE.Vector3(startPos.x, startPos.y + 0.2, startPos.z),
      new THREE.Vector3(point.x, point.y + 0.2, point.z)
      
    ]);
    let tubeGeometry = new THREE.TubeGeometry(curve, 80, 0.08);

    const material = new THREE.MeshBasicMaterial({
      map: arrowTexture,
      side: THREE.BackSide, //显示背面
      transparent: true,
      // color: new THREE.Color("#ff6347")
    });
    const _mesh = new THREE.Mesh( tubeGeometry, material );
    _mesh.name = "移动线"
    scene.add(_mesh);
  }
}

// 点击卡组事件
const onP1DeckEvent = () => {
  if (commonStore.$state.p1Deck.length <= 0) {
    return
  }
  let p1_deckGroup = scene.getObjectByName("p1_deckGroup")
  let arr = raycaster.intersectObject(p1_deckGroup, true)
  if (arr.length <= 0) {
    return
  }
  let pos1 = p1_deckGroup.userData.position
  let pos2 = new THREE.Vector3(0, 2, 0)
  // console.log(444, pos1, pos2)
  if (p1_deckGroup.position.x !== pos2.x) {
    drawCardRef.value.drawCardAnimate1(p1_deckGroup, pos1, pos2)
  }
}


// 手牌事件
const onHandEvent = () => {
  let handGroup = scene.getObjectByName("p1_handGroup")
  const dragControls = new DragControls( handGroup.children, camera, renderer.domElement );

  dragControls.addEventListener( 'dragstart', function ( event: any ) {
    event.object.position.y += 0.04
  });

  dragControls.addEventListener( 'drag', function ( event: any ) {
    event.object.position.y += 0.04
    
  });

  dragControls.addEventListener( 'dragend', function ( event: any ) {
    event.object.position.y -= 0.04
    let p1SitePlane = scene.getObjectByName("己方战域Plane")
    let point = transPos(pointer.x, pointer.y)
    // 通过摄像机和鼠标位置更新射线
    raycaster.setFromCamera( point, camera );
    const intersects = raycaster.intersectObjects(p1SitePlane.children);
    if (intersects.length > 0) {
      dialogRef.value.init({
        type: "handToSite",
        obj: event.object,
        message: "是否上场该卡牌"
      })
    } else {
      handRef.value.backPosition(event.object)
    }
  });
}

// 手牌移入己方战域
const handToSite = (data: any) => {
  let sitePlane = scene.getObjectByName("己方战域Plane")
  console.log(data)
  let userData = data.userData
  if (userData.type === "怪兽") {
    let meshes = sitePlane.children.filter((v: any) => v.name.indexOf("己方怪兽区") > -1 && v.userData.empty === true)
    if (meshes.length > 0) {
      let _mesh = null
      let m1 = meshes.find((v: any) => v.name.indexOf(1) > -1)
      let m2 = meshes.find((v: any) => v.name.indexOf(2) > -1)
      let m3 = meshes.find((v: any) => v.name.indexOf(3) > -1)
      if (m2) {
        _mesh = m2
      } else if (m1) {
        _mesh = m1
      } else if (m3) {
        _mesh = m3
      }
      let p1_handGroup = scene.getObjectByName("p1_handGroup")
      renderSiteCard(p1_handGroup, data, _mesh)
    }
  }
}

// 绘制场上卡牌
const renderSiteCard = async (handGroup: any, data: any, mesh: any) => {
  let position = new THREE.Vector3(0, 0, 0)
  mesh.getWorldPosition(position)
  mesh.userData.empty = false
  let oldMesh = handGroup.children.find((v: any) => v.name === data.name)
  let newMesh = oldMesh.clone()
  newMesh.userData.areaType = mesh.name // 用来记录卡牌在哪个区域,怪兽区、墓地、手牌、卡组、场地等
  newMesh.scale.set(0.8, 0.8, 0.8)
  if (handGroup.name === "p1_handGroup") {
    handRef.value.removeP1HandCard(oldMesh)
  } else if (handGroup.name === "p2_handGroup") {
    handRef.value.removeP2HandCard(oldMesh)
    newMesh.rotateX(180 * (Math.PI / 180)) // 弧度
    newMesh.rotateY(180 * (Math.PI / 180)) // 弧度
  }
  scene.add(newMesh)
  newMesh.position.set(position.x, position.y, position.z)
  await renderSiteCardText(handGroup, newMesh, commonStore.$state._font)

  // 创建伽马校正通道
  // const gammaPass= new ShaderPass(GammaCorrectionShader)
  // composer.addPass( gammaPass );
  
  // const outlinePass = new OutlinePass(new THREE.Vector2( window.innerWidth, window.innerHeight ), scene, camera); // 模糊
  // outlinePass.edgeStrength = 1.0; // 调整边缘强度
  // outlinePass.edgeGlow = 1.0; // 边缘发光强度
  // outlinePass.usePatternTexture = false; // 是否使用纹理
  // outlinePass.visibleEdgeColor.set(0xffffff); // 设置边缘颜色
  // outlinePass.hiddenEdgeColor.set(0x000000); // 设置隐藏边缘的颜色

  // outlinePass.selectedObjects = [newMesh.children[0]]
  // composer.addPass( outlinePass );
  console.log(123, newMesh)
}

// 取消
const onCancel = (data: any) => {
  handRef.value.backPosition(data)
}
</script>

<style lang="scss" scoped>
.scene {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100vh;
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/493173.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Scikit Learn中的概率校准曲线

概率校准是一种用于将二分类的输出分数转换为概率的技术&#xff0c;以与目标类的实际概率相关联。在本文中&#xff0c;我们将讨论概率校准曲线以及如何使用Scikit-learn绘制它们。 概率校准 概率校准曲线是二分类问题的正类的预测概率和实际观察频率之间的图。它用于检查分…

DLS-42/5-5双位置继电器 柜内安装板前接线 JOSEF约瑟

系列型号&#xff1a; DLS-41/10-2双位置继电器&#xff1b; DLS-41/9-3双位置继电器&#xff1b; DLS-41/8-4双位置继电器&#xff1b; DLS-41/6-6双位置继电器&#xff1b; DLS-42/9-1双位置继电器&#xff1b; DLS-42/8-2双位置继电器&#xff1b; DLS-42/7-3双位置继…

理解CPU与执行指令原理

本文侧重介绍cpu的工作任务&#xff0c;与cpu执行指令的过程是怎么样的&#xff1f; 目录 1.理解CPU 1.1.CPU的功能 1.2.CPU的逻辑构成 2.认识指令 2.1.什么是指令 2.2.CPU执行指令的准备工作(重点) 3.指令的执行过程 前景知识&#xff1a; 什么是计算机 就是遵循冯诺依…

微信小程序使用Vant组件库流程

目前 Vant 官方提供了 Vue 2 版本、Vue 3 版本和微信小程序版本&#xff0c;并由社区团队维护 React 版本和支付宝小程序版本。这样开发原生微信小程序的会方便很多。 官方网址&#xff1a;Vant Weapp - 轻量、可靠的小程序 UI 组件库 步骤一 通过 npm 安装 npm i vant/weap…

RXMA1 RK211 075 AC220V中间继电器 柜内安装板前接线 JOSEF约瑟

系列型号 RXMA1 RK 211 063中间继电器&#xff1b;RXMA1 RK 211 064中间继电器; RXMA1 RK 211 066中间继电器&#xff1b;RXMA1 RK 211 072中间继电器; RXMA1 RK 211 073中间继电器&#xff1b;RXMA1 RK 211 074中间继电器&#xff1b; RXMA1 RK 211 025中间继电器&#xff1b;…

k8s入门到实战(十二)—— pod的深入理解

pod 深入理解 pod 容器生命周期 pod 的几种状态 可以使用命令kubectl get pod -w实时监控查看 pod 的状态 running&#xff1a;正常运行状态Pending&#xff1a;资源分配不对的时候会挂起&#xff0c;出现此状态Terminating&#xff1a;某个节点突然关机&#xff0c;上面的 p…

uniapp微信小程序消息订阅详解

一、微信公众平台申请订阅模板 注意&#xff1a;订阅信息 这个事件 是 当用户 点击的时候触发 或者 是 支付成功后触发&#xff0c; 用户勾选 “总是保持以上选择&#xff0c;不再询问” 之后或长期订阅&#xff0c;下次订阅调用 wx.requestSubscribeMessage 不会弹窗&#xf…

浅析扩散模型与图像生成【应用篇】(十三)——PITI

13. Pretraining is All You Need for Image-to-Image Translation 该文提出一种基于预训练扩散模型的图像转换方法&#xff0c;称为PITI。其思想并不复杂&#xff0c;就是借鉴现有视觉和NLP领域中常见的预训练方法&#xff0c;考虑预先在一个大规模的任务无关数据集上对扩散模…

基于java+springboot+vue实现的校园二手交易系统(文末源码+Lw+ppt)23-336

摘 要 自从新冠疫情爆发以来&#xff0c;各个线下实体越来越难做&#xff0c;线下购物的人也越来越少&#xff0c;随之带来的是一些不必要的浪费&#xff0c;尤其是即将毕业的大学生&#xff0c;各种用品不方便携带走导致被遗弃&#xff0c;造成大量的浪费。本系统目的就是让…

浏览器如何渲染页面

浏览器渲染页面的过程是一个多步骤的复杂流程&#xff0c;下面我将通过一个简单的例子来逐步说明这一过程&#xff1a; 以下是浏览器渲染该页面的主要步骤&#xff1a; 请求HTML文档&#xff1a; 用户在浏览器中输入URL并回车&#xff0c;浏览器向服务器发送HTTP请求。 服务器…

【Java多线程】1——多线程知识回顾

1 多线程知识回顾 ⭐⭐⭐⭐⭐⭐ Github主页&#x1f449;https://github.com/A-BigTree 笔记仓库&#x1f449;https://github.com/A-BigTree/tree-learning-notes 个人主页&#x1f449;https://www.abigtree.top ⭐⭐⭐⭐⭐⭐ 如果可以&#xff0c;麻烦各位看官顺手点个star…

测试开发工程师(QA)职业到底需要干些什么?part6:数据测试工程师QA

常见任务和工作内容 作为数据测试工程师QA&#xff08;Quality Assurance&#xff09;&#xff0c;您的主要职责是确保数据的质量、准确性和一致性。以下是数据测试工程师QA的一些常见任务和工作内容&#xff1a; 数据验证和准确性测试&#xff1a;您将负责验证数据的准确性和…

数论问题代码模板

文章目录 一、质数1.1、质数筛&#xff08;筛1~n中的所有质数&#xff09;1.2、判断一个数是否为质数1.3、对一个数进行质因数分解 二、快速幂2.1、费马小定理——乘法逆元2.2、快速幂 三、约数3.1、N个数的正约数集合3.2、一个数的正约数集合 四、欧拉函数&#xff08;互质数数…

洗地机好用吗?哪款型号值得推荐?看完本文你就知道

在如今社会生活节奏不断加快的情况下&#xff0c;洗地机已经成为众多家庭的必备的清洁设备&#xff0c;面对市面上种类繁多的洗地机&#xff0c;我们常常会发出感叹“洗地机好用吗&#xff1f;洗地机哪个型好用&#xff1f;”等的疑问&#xff0c;今天&#xff0c;为了帮助大家…

一文搞定用python实现终身免费的听书工具

你好&#xff0c;我是 shengjk1&#xff0c;多年大厂经验&#xff0c;努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注&#xff01;你会有如下收益&#xff1a; 了解大厂经验拥有和大厂相匹配的技术等 希望看什么&#xff0c;评论或者私信告诉我&#xff01; 文章目录 一…

C语言程序练习——汉诺塔递归

1. 题目 在终端输入汉诺塔层数n&#xff0c;实现将n层汉诺塔通过三座塔座A、B、C进行排列 2. 代码 #include <stdio.h>int hannuota(int len, int str, int tmp, int dst) {if (1 len){printf("%c -> %c\n", str, dst);}else{hannuota(len-1, str, dst, …

好展位、抢先订!2024第二十二届上海国际涂料展|上海涂料展

致/To: 展会负责人、 市场部、 企划部、 销售部负责人 2024中国国际涂料博览会暨第二十二届中国国际涂料展览会 时间&#xff1a;2024年8月7-9日 地点&#xff1a;上海新国际博览中心 主办方&#xff1a; 中国涂料工业协会 承办方&#xff1a; 北京涂博国际展览有限公司 …

javaSwing坦克大战游戏

在游戏开发领域&#xff0c;坦克大战是一款经典的游戏&#xff0c;其简单而又耐玩的玩法吸引了无数玩家。而今&#xff0c;在Java编程技术的支持下&#xff0c;我们可以用Java Swing技术轻松实现这款经典游戏。本文将介绍如何使用Java Swing技术编写坦克大战游戏&#xff0c;并…

某对象存储元数据集群改造流水账

软件产品&#xff1a;某厂商提供的不便具名的对象存储产品&#xff0c;核心底层技术源自HDFS和Amazon S3&#xff0c;元数据集群采用了基于MongoDB的NOSQL数据库产品和MySQL数据库产品相结合。 该产品的元数据逻辑示意图如下&#xff1a; 业务集群现状&#xff1a;当前第3期建…

Qt 窗口MainWindow(上)

Qt 窗口是通过 QMainWindow 类来实现的。 QMainWindow 是一个为用户提供主窗口程序的类&#xff0c;继承自 QWidget 类&#xff0c;并且提供了⼀个预定义的布局。QMainWindow 包含一个菜单栏&#xff08;menubar&#xff09;、多个工具栏(toolbars)、多个浮动窗口&#xff08;…