使用最新threejs复刻经典贪吃蛇游戏的3D版,附完整源码
基类Entity
建立基类Entity,实现投影能力、动画入场效果(从小变大的弹性动画)、计算自己在地图格位置的方法。
// 导入gsap动画库(用于创建补间动画)
import gsap from 'gsap'// 定义Entity基类
export default class Entity {constructor(mesh, resolution, option = { size: 1.5, number: 0.5 }) {// 保存传入的3D网格对象this.mesh = mesh// 配置网格的阴影属性mesh.castShadow = true // 允许投射阴影mesh.receiveShadow = true // 允许接收阴影// 保存分辨率参数和相关配置选项this.resolution = resolution // 可能包含屏幕/布局分辨率信息this.option = option // 动画参数,默认size=1.5, number=0.5}// 位置属性的getter,代理访问mesh的位置get position() {return this.mesh.position // 返回THREE.Vector3对象}// 根据坐标计算索引的方法(可能用于网格布局)getIndexByCoord() {const { x, y } = this.resolution // 解构分辨率,可能代表网格列/行数// 计算索引公式:z坐标 * x轴分辨率 + x坐标// 注意:此处可能存在问题,通常3D空间索引需要考虑y坐标return this.position.z * x + this.position.x}// 进入动画方法in() {// 使用gsap创建缩放动画gsap.from(this.mesh.scale, { // 从指定状态动画到当前状态duration: 1, // 动画时长1秒x: 0, // 初始X缩放为0y: 0, // 初始Y缩放为0 z: 0, // 初始Z缩放为0ease: `elastic.out(${this.option.size}, ${this.option.number})`, // 弹性缓动函数})}// 离开动画方法(暂未实现)out() {}
}
开发糖果类Candy
Candy
这个类继承了之前Entity,
这里用threejs内置的SphereGeometry(0.3, 20, 20)
做了个小圆球模型。然后用MeshStandardMaterial做材质,并可以传入颜色
,所有糖果都会用这个模具和颜色来制作。并且可以随机生成大小不一的糖果,代表不同的分数。
// 导入Three.js的3D对象相关模块
import {Mesh, // 网格对象(几何体+材质的组合)MeshNormalMaterial, // 显示法向量的标准材质MeshStandardMaterial, // PBR标准材质SphereGeometry, // 球体几何体
} from 'three'// 导入自定义的Entity基类
import Entity from './Entity'// 创建球体几何体(半径0.3,经纬分段数各20)
const GEOMETRY = new SphereGeometry(0.3, 20, 20)
// 创建标准材质并设置基础颜色为紫色(#614bdd)
const MATERIAL = new MeshStandardMaterial({color: 0x614bdd,
})// 定义Candy类,继承自Entity基类
export default class Candy extends Entity {constructor(resolution, color) {// 创建网格对象(使用共享的几何体和材质)const mesh = new Mesh(GEOMETRY, MATERIAL)// 调用父类Entity的构造函数super(mesh, resolution) // 假设Entity处理了场景添加、位置初始化等// 如果有传入颜色参数,则覆盖材质的默认颜色// 注意:这里会修改共享材质,影响所有Candy实例!if (color) {MATERIAL.color.set(color)}// 生成随机点数(1-3点)this.points = Math.floor(Math.random() * 3) + 1// 根据点数设置缩放比例:点数越多,糖果越大// 基础缩放0.5 + (点数*0.5)/3 → 范围在0.5~1.0之间this.mesh.scale.setScalar(0.5 + (this.points * 0.5) / 3)}
}
创建LinkedKList与ListNode类,实现链表结构,来模拟贪吃蛇相连的每节节点。
// 链表容器类(管理整个链)
export default class LinkedList {constructor(head) { // 初始化必须传入头节点this.head = head // 存储链表头部this.end = head // 存储链表尾部(初始头就是尾)}// 添加新节点方法(注意:只能向后追加)addNode(node) {this.end.linkTo(node) // 让当前尾部连接新节点this.end = node // 更新尾部为新节点}
}
// 链表节点类(每个节点像一节火车车厢)
export default class ListNode {next = null // 指向下一个车厢的钩子prev = null // 指向前一个车厢的钩子constructor(data) {this.data = data // 当前车厢}// 连接节点方法(像车厢挂钩的动作)linkTo(node) {this.next = node // 当前节点钩住下一个node.prev = this // 下一个节点反钩回来(形成双向连接)}
}
Snake类
创建贪吃蛇本体,实现转向,移动,吃糖果加尾部节点,碰到障碍物检测的逻辑。需要用到之前的LinkedList、ListNode、Entity类。
import {EventDispatcher,Mesh,MeshNormalMaterial,MeshStandardMaterial,SphereGeometry,Vector2,Vector3,
} from 'three'
import LinkedList from './LinkedList'
import { RoundedBoxGeometry } from 'three/examples/jsm/geometries/RoundedBoxGeometry'
import ListNode from './ListNode'
import Entity from './Entity'const NODE_GEOMETRY = new RoundedBoxGeometry(0.9, 0.9, 0.9, 5, 0.1) // 用threejs内置函数创建圆角四方块
const NODE_MATERIAL = new MeshStandardMaterial({color: 0xff470a,
}) // 创建材质并给初始颜色const UP = new Vector3(0, 0, -1)
const DOWN = new Vector3(0, 0, 1)
const LEFT = new Vector3(-1, 0, 0)
const RIGHT = new Vector3(1, 0, 0)
export default class Snake extends EventDispatcher {direction = RIGHT // 初始方向朝右indexes = [] // 存储蛇身占用的格子坐标speedInterval = 240 // 移动速度(数值越大越慢)constructor({ scene, resolution = new Vector2(10, 10), color, mouthColor }) {super()// 把游戏场景、地图尺寸、颜色存起来this.scene = scenethis.resolution = resolutionthis.mouthColor = mouthColor// 如果有颜色参数,给所有蛇节点刷颜色if (color) {NODE_MATERIAL.color.set(color)}this.init() // 开始组装蛇}get head() {return this.body.head}get end() {return this.body.end}//组装蛇头createHeadMesh() {const headMesh = this.body.head.data.mesh // 获取蛇头模型// 造左眼:白眼球+黑眼珠const leftEye = new Mesh(new SphereGeometry(0.2, 10, 10),new MeshStandardMaterial({ color: 0xffffff }))leftEye.scale.x = 0.1leftEye.position.x = 0.5leftEye.position.y = 0.12leftEye.position.z = -0.1let leftEyeHole = new Mesh(new SphereGeometry(0.22, 10, 10),new MeshStandardMaterial({ color: 0x333333 }))leftEyeHole.scale.set(1, 0.6, 0.6)leftEyeHole.position.x += 0.05leftEye.add(leftEyeHole)// 造右眼:同上const rightEye = leftEye.clone()rightEye.position.x = -0.5rightEye.rotation.y = Math.PI// 造嘴巴:紫色圆角矩形const mouthMesh = new Mesh(new RoundedBoxGeometry(1.05, 0.1, 0.6, 5, 0.1),new MeshStandardMaterial({color: this.mouthColor, //0x614bdd,}))mouthMesh.rotation.x = -Math.PI * 0.07mouthMesh.position.z = 0.23mouthMesh.position.y = -0.19this.mouth = mouthMesh// 把零件装到蛇头上headMesh.add(rightEye, leftEye, mouthMesh)/* 调整头部朝向当前方向 */headMesh.lookAt(headMesh.position.clone().add(this.direction))}init() {// 重置方向this.direction = RIGHTthis.iMoving = null// 造第一个蛇头节点(放在地图中心)const head = new ListNode(new SnakeNode(this.resolution))head.data.mesh.position.x = this.resolution.x / 2head.data.mesh.position.z = this.resolution.y / 2this.body = new LinkedList(head)this.createHeadMesh()this.indexes.push(this.head.data.getIndexByCoord())// 添加初始三节身体(类似火车车厢)for (let i = 0; i < 3; i++) {const position = this.end.data.mesh.position.clone()position.sub(this.direction) // 每节身体往后挪一格this.addTailNode() // 挂载到链表末尾this.end.data.mesh.position.copy(position)this.indexes.push(this.end.data.getIndexByCoord())}head.data.in()this.scene.add(head.data.mesh)}setDirection(keyCode) {let newDirection// 根据按键计算新方向(上下左右)switch (keyCode) {case 'ArrowUp':case 'KeyW':newDirection = UPbreakcase 'ArrowDown':case 'KeyS':newDirection = DOWNbreakcase 'ArrowLeft':case 'KeyA':newDirection = LEFTbreakcase 'ArrowRight':case 'KeyD':newDirection = RIGHTbreakdefault:return}const dot = this.direction.dot(newDirection)// 禁止180度掉头(比如正在右走不能直接左转)if (dot === 0) {this.newDirection = newDirection}}// 每帧判断update() {// 应用新方向if (this.newDirection) {this.direction = this.newDirectionthis.newDirection = null}let currentNode = this.end// 处理吃糖果后的尾巴生长if (this.end.data.candy) {this.end.data.candy = nullthis.end.data.mesh.scale.setScalar(1)this.addTailNode()}// 身体像波浪一样跟随头部移动while (currentNode.prev) {const candy = currentNode.prev.data.candyif (candy) {currentNode.data.candy = candycurrentNode.data.mesh.scale.setScalar(1.15)currentNode.prev.data.candy = nullcurrentNode.prev.data.mesh.scale.setScalar(1)}const position = currentNode.prev.data.mesh.position// 每节身体移动到前一节的位置currentNode.data.mesh.position.copy(position)currentNode = currentNode.prev // 往前遍历}const headPos = currentNode.data.mesh.positionheadPos.add(this.direction)// currentNode.data.mesh.position.add(this.direction)const headMesh = this.body.head.data.meshheadMesh.lookAt(headMesh.position.clone().add(this.direction))// 移动头部if (headPos.z < 0) {headPos.z = this.resolution.y - 1} else if (headPos.z > this.resolution.y - 1) {headPos.z = 0}// 边界穿越处理(从地图左边消失就从地图右边出来...其他边界类似)if (headPos.x < 0) {headPos.x = this.resolution.x - 1} else if (headPos.x > this.resolution.x - 1) {headPos.x = 0}this.updateIndexes()// 向上抛出事件this.dispatchEvent({ type: 'updated' })}// 死亡die() {let node = this.body.head// 移除所有身体部件do {this.scene.remove(node.data.mesh) // 从场景删除模型node = node.next} while (node)this.init() // 重新初始化this.addEventListener({ type: 'die' }) // 向上抛出死亡事件}checkSelfCollision() {// 检查头部坐标是否和身体坐标重叠const headIndex = this.indexes.pop()const collide = this.indexes.includes(headIndex)this.indexes.push(headIndex)return collide // 撞到自己返回true}checkEntitiesCollision(entities) {// 检查头部坐标是否和障碍物坐标重叠const headIndex = this.indexes.at(-1)const entity = entities.find((entity) => entity.getIndexByCoord() === headIndex)return !!entity // 撞到障碍物返回true}// 更新蛇身所有节点的网格坐标索引,用于碰撞检测等需要知道蛇身位置的功能updateIndexes() {// 清空旧索引(相当于擦除之前的身体痕迹)this.indexes = []// 从蛇尾开始遍历(相当于从火车最后一节车厢开始检查)let node = this.body.end// 循环向前遍历所有节点(直到没有前一节车厢)while (node) {// 获取当前节点的网格坐标(比如把3D坐标转换为地图网格的[x,y])// 假设地图是10x10网格,坐标(5.3, 0, 5.7)会被转换为索引[5,5]// 将索引推入数组(记录这节身体占据的格子)this.indexes.push(node.data.getIndexByCoord())// 移动到前一节身体(向蛇头方向移动)node = node.prev// 最终得到的indexes数组示例:// [[3,5], [4,5], [5,5]] 表示蛇身占据这三个网格// 其中最后一个元素[5,5]是蛇头位置}}// 添加尾部节点addTailNode(position) {const node = new ListNode(new SnakeNode(this.resolution))if (position) {node.data.mesh.position.copy(position)} else {node.data.mesh.position.copy(this.end.data.mesh.position)}this.body.addNode(node)node.data.in()this.scene.add(node.data.mesh)}
}
// 蛇身体节点类
class SnakeNode extends Entity {constructor(resolution) {const mesh = new Mesh(NODE_GEOMETRY, NODE_MATERIAL)super(mesh, resolution)}
}
实现障碍物,树Tree与石头Rock的类
// 导入Three.js相关模块
import {IcosahedronGeometry, // 二十面体几何体(适合制作复杂形状)Mesh, // 网格对象(用于组合几何体与材质)MeshNormalMaterial, // 法线材质(调试用,显示表面朝向)MeshStandardMaterial, // PBR标准材质(支持金属/粗糙度等特性)
} from 'three'
import Entity from './Entity' // 基础实体类// 创建共享几何体(优化性能,所有树实例共用同一个几何体)
const GEOMETRY = new IcosahedronGeometry(0.3) // 基础半径为0.3的二十面体
GEOMETRY.rotateX(Math.random() * Math.PI * 2) // 随机绕X轴旋转(避免重复感)
GEOMETRY.scale(1, 6, 1) // Y轴拉伸6倍,形成细长形状(类似树干)// 创建共享材质(所有树实例默认使用相同材质)
const MATERIAL = new MeshStandardMaterial({flatShading: true, // 平面着色(增强低多边形风格)color: 0xa2d109, // 默认黄绿色(类似树叶颜色)
})// 定义树类(继承自基础实体类)
export default class Tree extends Entity {constructor(resolution, color) {// 创建网格实例(组合几何体与材质)const mesh = new Mesh(GEOMETRY, MATERIAL)// 随机缩放(0.6~1.2倍原始尺寸,制造大小差异)mesh.scale.setScalar(0.6 + Math.random() * 0.6)// 随机Y轴旋转(让树木朝向不同方向)mesh.rotation.y = Math.random() * Math.PI * 2// 如果指定颜色,覆盖默认材质颜色if (color) {MATERIAL.color.set(color)}// 调用父类构造函数(处理坐标转换等)super(mesh, resolution)}
}
// 导入Three.js相关模块
import {IcosahedronGeometry, // 二十面体几何体(用于创建复杂形状)Mesh, // 网格对象(组合几何体与材质)MeshNormalMaterial, // 法线材质(调试用)MeshStandardMaterial, // PBR标准材质(支持金属/粗糙度)
} from 'three'
import Entity from './Entity' // 基础实体类// 创建共享几何体(所有岩石实例共用)
const GEOMETRY = new IcosahedronGeometry(0.5) // 基础半径0.5的二十面体// 创建共享材质(所有岩石实例默认使用相同材质)
const MATERIAL = new MeshStandardMaterial({color: 0xacacac, // 默认岩石灰色flatShading: true, // 平面着色(增强低多边形风格)
})// 岩石类(继承基础实体类)
export default class Rock extends Entity {constructor(resolution, color) {// 创建网格实例(几何体+材质)const mesh = new Mesh(GEOMETRY, MATERIAL)// X轴:0.5~1倍随机缩放(横向随机宽度)// Y轴:0.5~2.4倍缩放(使用平方让更多岩石较矮)// Z轴:保持1倍(前后方向不变形)mesh.scale.set(Math.random() * 0.5 + 0.5, 0.5 + Math.random() ** 2 * 1.9, 1)mesh.rotation.y = Math.random() * Math.PI * 2 // 随机Y轴旋转(0-360度)mesh.rotation.x = Math.random() * Math.PI * 0.1 // 轻微X轴倾斜(最大18度)mesh.rotation.order = 'YXZ' // 旋转顺序:先Y后X最后Z(避免万向锁问题)// 下沉位置(使岩石看起来半埋在地面)mesh.position.y = -0.5// 如果指定颜色,覆盖默认材质颜色if (color) {MATERIAL.color.set(color)}// 调用父类构造函数(处理坐标转换等)super(mesh, resolution)}
}
最后,实现地图尺寸配置Params.js,光照Lights等环境变量
// 导入Three.js二维向量模块
import { Vector2 } from 'three'// 定义场景地图尺寸参数 20*20的格子
const resolution = new Vector2(20, 20)
/*** 参数说明:* x: 场景横向尺* y: 场景纵向尺寸*/// 定义颜色配置集合
const colors = {groundColor: '#ff7438', // 地面基础色(暖橙色)fogColor: '#d68a4c' // 雾效颜色(与地面色协调的浅棕色)
}// 导出配置参数(供其他模块统一访问)
export { resolution, colors }
// 导入Three.js光源相关模块
import { AmbientLight, DirectionalLight } from 'three'
import { resolution } from './Params' // 场景尺寸参数// 创建环境光(提供基础照明,无方向性)
const ambLight = new AmbientLight(0xffffff, // 白光(十六进制颜色值)0.6 // 光照强度(范围0-1,相当于60%亮度)
)// 创建平行光(模拟太阳光,产生方向性阴影)
const dirLight = new DirectionalLight(0xffffff, // 白光0.7 // 主光源强度(70%亮度)
)// 设置平行光参数
dirLight.position.set(20, 20, 18) // 光源三维坐标(模拟高空太阳位置)
dirLight.target.position.set( // 光照焦点位置(场景中心点)resolution.x / 2, // X轴中心(地图宽度的一半)0, // Y轴保持地面高度resolution.y / 2 // Z轴中心(地图深度的一半)
)// 阴影质量配置
dirLight.shadow.mapSize.set(1024, 1024) // 阴影贴图分辨率(值越大越清晰,但更耗性能)
dirLight.shadow.radius = 7 // 阴影模糊半径(软化阴影边缘)
dirLight.shadow.blurSamples = 20 // 模糊采样次数(提升阴影边缘平滑度)// 设置阴影相机的视锥范围(控制产生阴影的区域)
dirLight.shadow.camera.top = 30 // 可见区域顶部边界
dirLight.shadow.camera.bottom = -30 // 可见区域底部边界
dirLight.shadow.camera.left = -30 // 可见区域左边界
dirLight.shadow.camera.right = 30 // 可见区域右边界dirLight.castShadow = true // 启用该光源的阴影投射// 组合光源(通常场景需要多个光源配合)
const lights = [dirLight, ambLight]export default lights // 导出光源配置集合
最后是入口文件main.js的主逻辑,包含游戏循环,按键监听,主题切换,游戏开始与失败逻辑
import './style.css'// 导入Three.js核心库和扩展模块
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' // 摄像机轨道控制器
import Snake from './Snake' // 贪吃蛇游戏角色
import Candy from './Candy' // 糖果道具
import Rock from './Rock' // 岩石障碍物
import Tree from './Tree' // 树木装饰
import lights from './Lights' // 光照系统
import { resolution } from './Params' // 场景分辨率参数
import gsap from 'gsap' // 动画库(用于平滑过渡)
import { FontLoader } from 'three/examples/jsm/loaders/FontLoader' // 字体加载器
import fontSrc from 'three/examples/fonts/helvetiker_bold.typeface.json?url' // 3D字体文件
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry' // 3D文字几何体
import Entity from './Entity' // 基础实体类// 设备检测(移动端适配)
const isMobile = window.innerWidth <= 768
const loader = new FontLoader()
let font
// 字体加载系统
loader.load(fontSrc, function (loadedFont) {font = loadedFontprintScore() // 字体加载完成后初始化计分板
})// 配色方案集合(支持主题切换)
const palettes = {green: { // 绿色主题groundColor: 0x56f854,fogColor: 0x39c09f,rockColor: 0xebebeb,treeColor: 0x639541,candyColor: 0x1d5846,snakeColor: 0x1d5846,mouthColor: 0x39c09f,},orange: { // 橙色主题groundColor: 0xd68a4c,fogColor: 0xffac38,rockColor: 0xacacac,treeColor: 0xa2d109,candyColor: 0x614bdd,snakeColor: 0xff470a,mouthColor: 0x614bdd,},lilac: { // 紫色主题groundColor: 0xd199ff,fogColor: 0xb04ce6,rockColor: 0xebebeb,treeColor: 0x53d0c1,candyColor: 0x9900ff,snakeColor: 0xff2ed2,mouthColor: 0x614bdd,},
}// 主题管理系统
let paletteName = localStorage.getItem('paletteName') || 'green'
let selectedPalette = palettes[paletteName]const params = {...selectedPalette, // 当前生效的配色参数
}// 应用配色方案(支持运行时切换)
function applyPalette(paletteName) {const palette = palettes[paletteName]localStorage.setItem('paletteName', paletteName)selectedPalette = paletteif (!palette) return// 更新场景元素颜色const {groundColor,fogColor,rockColor,treeColor,candyColor,snakeColor,mouthColor,} = paletteplaneMaterial.color.set(groundColor) // 地面scene.fog.color.set(fogColor) // 雾效scene.background.set(fogColor) // 背景// 更新实体颜色(岩石、树木等)entities.find((entity) => entity instanceof Rock)?.mesh.material.color.set(rockColor)entities.find((entity) => entity instanceof Tree)?.mesh.material.color.set(treeColor)// 更新游戏元素candies[0].mesh.material.color.set(candyColor)snake.body.head.data.mesh.material.color.set(snakeColor)snake.body.head.data.mesh.material.color.set(snakeColor)snake.mouthColor = mouthColorsnake.mouth.material.color.set(mouthColor)// 更新UI按钮btnPlayImg.src = `/btn-play-bg-${paletteName}.png`
}
// 游戏核心参数
let score = 0 // 得分统计// 网格辅助线(可视化场景坐标系)
const gridHelper = new THREE.GridHelper(resolution.x, // 横向分割数resolution.y, // 纵向分割数0xffffff, // 主网格颜色0xffffff // 次级网格颜色
)
gridHelper.position.set(resolution.x / 2 - 0.5, -0.49, resolution.y / 2 - 0.5) // 居中定位
gridHelper.material.transparent = true
gridHelper.material.opacity = isMobile ? 0.75 : 0.3 // 移动端降低透明度// 场景初始化
const scene = new THREE.Scene()
scene.background = new THREE.Color(params.fogColor) // 背景scene.fog = new THREE.Fog(params.fogColor, 5, 40) // 添加雾效scene.add(gridHelper) // 添加辅助网格// 视窗尺寸管理
const sizes = {width: window.innerWidth,height: window.innerHeight,
}// 摄像机系统
const fov = 60 // 正交相机(类似人眼)
const camera = new THREE.PerspectiveCamera(fov, sizes.width / sizes.height, 0.1)// 摄像机位置(移动端与PC不同)
const finalPosition = isMobile? new THREE.Vector3(resolution.x / 2 - 0.5, resolution.x + 15, resolution.y): new THREE.Vector3(-8 + resolution.x / 2,resolution.x / 2 + 4,resolution.y + 6)
const initialPosition = new THREE.Vector3(resolution.x / 2 + 5,4,resolution.y / 2 + 4
)
camera.position.copy(initialPosition)// 渲染器配置
const renderer = new THREE.WebGLRenderer({antialias: window.devicePixelRatio < 2, // 抗锯齿(高清屏关闭)logarithmicDepthBuffer: true, // 解决远距离渲染问题
})
document.body.appendChild(renderer.domElement)
handleResize() // 初始自适应// 高级渲染特性
renderer.toneMapping = THREE.ACESFilmicToneMapping // 电影级色调映射
renderer.toneMappingExposure = 1.2 // 曝光强度
renderer.shadowMap.enabled = true // 启用阴影
renderer.shadowMap.type = THREE.VSMShadowMap // 柔和阴影算法// 摄像机控制器(限制移动方式)
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true // 惯性滑动
controls.enableZoom = false // 禁用缩放
controls.enablePan = false // 禁用平移
controls.enableRotate = false // 禁用旋转
controls.target.set(resolution.x/2 -2, 0, resolution.y/2 + (isMobile ? 0 : 2))// 地面
const planeGeometry = new THREE.PlaneGeometry(resolution.x*50, resolution.y*50)
planeGeometry.rotateX(-Math.PI * 0.5) // 创建水平面为地面
const planeMaterial = new THREE.MeshStandardMaterial({color: params.groundColor
})
const plane = new THREE.Mesh(planeGeometry, planeMaterial)
plane.position.set(resolution.x/2-0.5, -0.5, resolution.y/2-0.5) // 对齐网格
plane.receiveShadow = true // 接收阴影
scene.add(plane)// 游戏角色初始化
const snake = new Snake({scene,resolution,color: selectedPalette.snakeColor,mouthColor: selectedPalette.mouthColor,
})// 游戏事件系统
snake.addEventListener('updated', function () {// 碰撞检测,碰撞上障碍就死亡,重置游戏if (snake.checkSelfCollision() || snake.checkEntitiesCollision(entities)) {snake.die()resetGame()}// 糖果收集检测const headIndex = snake.indexes.at(-1)const candyIndex = candies.findIndex((candy) => candy.getIndexByCoord() === headIndex)// 吃到糖果if (candyIndex >= 0) {const candy = candies[candyIndex]scene.remove(candy.mesh) // 从场景移除被吃掉的糖果candies.splice(candyIndex, 1)snake.body.head.data.candy = candyaddCandy() // 生成新的糖果score += candy.points // 加分printScore() // 更新分数板}
})let scoreEntity // 当前分数板function printScore() {// 等待字体加载完成if (!font) {return}if (!score) {score = 0}// 清理旧分数对象(避免内存泄漏)if (scoreEntity) {scene.remove(scoreEntity.mesh)scoreEntity.mesh.geometry.dispose() // 释放几何体内存scoreEntity.mesh.material.dispose() // 释放材质内存}// 创建带倒角的3D文字几何体const geometry = new TextGeometry(`${score}`, {font: font,size: 1,depth: 0.1,curveSegments: 12,bevelEnabled: true, // 启用倒角bevelThickness: 0.1, // 倒角深度bevelSize: 0.1, // 倒角宽度bevelOffset: 0,bevelSegments: 5,})geometry.center() // 几何体居中if (isMobile) {geometry.rotateX(-Math.PI * 0.5) // 移动端纵向旋转90度}// 复用蛇头材质创建网格const mesh = new THREE.Mesh(geometry, snake.body.head.data.mesh.material)// 定位在场景上方mesh.position.x = resolution.x / 2 - 0.5mesh.position.z = -4mesh.position.y = 1.8// 启用阴影投射mesh.castShadow = true// 创建分数实体并加入场景scoreEntity = new Entity(mesh, resolution, { size: 0.8, number: 0.3 })// 播放入场动画scoreEntity.in()scene.add(scoreEntity.mesh)
}// 移动端触摸控制系统 虚拟方向键DOM
const mobileArrows = document.getElementById('mobile-arrows')function registerEventListener() {if (isMobile) {// 触摸逻辑核心参数const prevTouch = new THREE.Vector2() // 记录上次触摸位置let middle = 1.55 // 屏幕中心参考线let scale = 1 // 方向判断灵敏度系数// 触摸事件监听window.addEventListener('touchstart', (event) => {const touch = event.targetTouches[0]middle = THREE.MathUtils.clamp(middle, 1.45, 1.65)// 将屏幕坐标转换为归一化坐标(-1到1范围)let x = (2 * touch.clientX) / window.innerWidth - 1let y = (2 * touch.clientY) / window.innerHeight - middle// 游戏启动检测if (!isRunning) startGame()// 方向判断算法(根据坐标象限)if (x * scale > y) {if (x * scale < -y) {snake.setDirection('ArrowUp')scale = 3} else {snake.setDirection('ArrowRight')middle += yscale = 0.33}} else {if (-x * scale > y) {snake.setDirection('ArrowLeft')middle += yscale = 0.33} else {snake.setDirection('ArrowDown')scale = 3}}prevTouch.x = xprevTouch.y = y // 记录本次触摸位置})} else {// 桌面端键盘事件监听window.addEventListener('keydown', function (e) {const keyCode = e.codesnake.setDirection(keyCode) // 方向键控制if (keyCode === 'Space') { // 空格键暂停/继续!isRunning ? startGame() : stopGame()} else if (!isRunning) {startGame()}})}
}let isRunning // 游戏运行状态标记function startGame() {if (!snake.isMoving) {// 设置240ms间隔的蛇移动循环(约4帧/秒)isRunning = setInterval(() => {snake.update()}, 240)}
}// 暂停游戏
function stopGame() {clearInterval(isRunning) // 清除计时器isRunning = null // 重置状态
}// 重置游戏
function resetGame() {stopGame() // 停止游戏循环score = 0 // 重置积分// 清理所有糖果对象let candy = candies.pop()while (candy) {scene.remove(candy.mesh)candy = candies.pop()}// 清理所有实体对象(岩石/树木)let entity = entities.pop()while (entity) {scene.remove(entity.mesh)entity = entities.pop()}// 重新初始化游戏元素addCandy()generateEntities()
}const candies = [] // 糖果对象池
const entities = [] // 实体对象池(障碍物)// 糖果生成逻辑
function addCandy() {const candy = new Candy(resolution, selectedPalette.candyColor)let index = getFreeIndex() // 获取可用位置索引// 设置三维坐标(基于索引的网格布局)candy.mesh.position.x = index % resolution.xcandy.mesh.position.z = Math.floor(index / resolution.x)candies.push(candy) // 加入对象池candy.in() // 播放生成动画scene.add(candy.mesh)
}addCandy()// 随机位置生成算法
function getFreeIndex() {let indexlet candyIndexes = candies.map((candy) => candy.getIndexByCoord())let entityIndexes = entities.map((entity) => entity.getIndexByCoord())// 生成随机索引(范围:0到场景总网格数)do {index = Math.floor(Math.random() * resolution.x * resolution.y)} while (snake.indexes.includes(index) || // 不与蛇身重叠candyIndexes.includes(index) || // 不与现有糖果重叠entityIndexes.includes(index) // 不与障碍物重叠)return index
}// 障碍物生成逻辑
function addEntity() {// 随机生成岩石或树木(50%概率)const entity =Math.random() > 0.5? new Rock(resolution, selectedPalette.rockColor): new Tree(resolution, selectedPalette.treeColor)let index = getFreeIndex() // 获取安全位置// 设置坐标(与糖果生成逻辑相同)entity.mesh.position.x = index % resolution.xentity.mesh.position.z = Math.floor(index / resolution.x)entities.push(entity) // 加入对象池scene.add(entity.mesh) // 加入场景
}// 初始实体生成器
function generateEntities() {// 生成20个障碍物for (let i = 0; i < 20; i++) {addEntity()}// 按距离场景中心排序(优化渲染顺序)entities.sort((a, b) => {const c = new THREE.Vector3(resolution.x / 2 - 0.5,0,resolution.y / 2 - 0.5)const distanceA = a.position.clone().sub(c).length()const distanceB = b.position.clone().sub(c).length()return distanceA - distanceB})// 使用GSAP实现弹性入场动画gsap.from(entities.map((entity) => entity.mesh.scale),{x: 0,y: 0,z: 0,duration: 1,ease: 'elastic.out(1.5, 0.5)',stagger: {grid: [20, 20],amount: 0.7,},})
}generateEntities()// 添加光照
scene.add(...lights)// 生成装饰性树,地图外的
const treeData = [new THREE.Vector4(-5, 0, 10, 1),new THREE.Vector4(-6, 0, 15, 1.2),new THREE.Vector4(-5, 0, 16, 0.8),new THREE.Vector4(-10, 0, 4, 1.3),new THREE.Vector4(-5, 0, -3, 2),new THREE.Vector4(-4, 0, -4, 1.5),new THREE.Vector4(-2, 0, -15, 1),new THREE.Vector4(5, 0, -20, 1.2),new THREE.Vector4(24, 0, -12, 1.2),new THREE.Vector4(2, 0, -6, 1.2),new THREE.Vector4(3, 0, -7, 1.8),new THREE.Vector4(1, 0, -9, 1.0),new THREE.Vector4(15, 0, -8, 1.8),new THREE.Vector4(17, 0, -9, 1.1),new THREE.Vector4(18, 0, -7, 1.3),new THREE.Vector4(24, 0, -1, 1.3),new THREE.Vector4(26, 0, 0, 1.8),new THREE.Vector4(32, 0, 0, 1),new THREE.Vector4(28, 0, 6, 1.7),new THREE.Vector4(24, 0, 15, 1.1),new THREE.Vector4(16, 0, 23, 1.1),new THREE.Vector4(12, 0, 24, 0.9),new THREE.Vector4(-13, 0, -13, 0.7),new THREE.Vector4(35, 0, 10, 0.7),
]
const tree = new Tree(resolution)treeData.forEach(({ x, y, z, w }) => {let clone = tree.mesh.clone()clone.position.set(x, y, z)clone.scale.setScalar(w)scene.add(clone)
})const rock = new Rock(resolution)
const resX = resolution.x
const rexY = resolution.y// 生成装饰性石头,地图外的
const rockData = [[new THREE.Vector3(-7, -0.5, 2), new THREE.Vector4(2, 8, 3, 2.8)],[new THREE.Vector3(-3, -0.5, -10), new THREE.Vector4(3, 2, 2.5, 1.5)],[new THREE.Vector3(-5, -0.5, 3), new THREE.Vector4(1, 1.5, 2, 0.8)],[new THREE.Vector3(resX + 5, -0.5, 3), new THREE.Vector4(4, 1, 3, 1)],[new THREE.Vector3(resX + 4, -0.5, 2), new THREE.Vector4(2, 2, 1, 1)],[new THREE.Vector3(resX + 8, -0.5, 16), new THREE.Vector4(6, 2, 4, 4)],[new THREE.Vector3(resX + 6, -0.5, 13), new THREE.Vector4(3, 2, 2.5, 3.2)],[new THREE.Vector3(resX + 5, -0.5, -8), new THREE.Vector4(1, 1, 1, 0)],[new THREE.Vector3(resX + 6, -0.5, -7), new THREE.Vector4(2, 4, 1.5, 0.5)],[new THREE.Vector3(-5, -0.5, 14), new THREE.Vector4(1, 3, 2, 0)],[new THREE.Vector3(-4, -0.5, 15), new THREE.Vector4(0.8, 0.6, 0.7, 0)],[new THREE.Vector3(resX / 2 + 5, -0.5, 25),new THREE.Vector4(2.5, 0.8, 4, 2),],[new THREE.Vector3(resX / 2 + 9, -0.5, 22),new THREE.Vector4(1.2, 2, 1.2, 1),],[new THREE.Vector3(resX / 2 + 8, -0.5, 21.5),new THREE.Vector4(0.8, 1, 0.8, 2),],
]rockData.forEach(([position, { x, y, z, w }]) => {let clone = new Rock(resolution).meshclone.position.copy(position)clone.scale.set(x, y, z)clone.rotation.y = wscene.add(clone)
})// 音效
const audio = document.getElementById('audio')
const btnVolume = document.getElementById('btn-volume')
const btnPlay = document.getElementById('btn-play')
const btnPlayImg = document.getElementById('btn-play-img')
// 音效按钮效果
gsap.fromTo(btnPlay,{ autoAlpha: 0, scale: 0, yPercent: -50, xPercent: -50 },{duration: 0.8,autoAlpha: 1,scale: 1,yPercent: -50,xPercent: -50,delay: 0.3,ease: `elastic.out(1.2, 0.7)`,}
)// 开始游戏
btnPlay.addEventListener('click', function () {audio.play()gsap.to(camera.position, { ...finalPosition, duration: 2 })if (isMobile) {gsap.to(controls.target, {x: resolution.x / 2 - 0.5,y: 0,z: resolution.y / 2 - 0.5,})}gsap.to(scene.fog, { duration: 2, near: isMobile ? 30 : 20, far: 55 })gsap.to(this, {duration: 1,scale: 0,ease: `elastic.in(1.2, 0.7)`,onComplete: () => {this.style.visibility = 'hidden'},})registerEventListener()
})const userVolume = localStorage.getItem('volume')
if (userVolume === 'off') {muteVolume()
}// 音量
const initialVolume = audio.volumebtnVolume.addEventListener('click', function () {if (audio.volume === 0) {unmuteVolume()} else {muteVolume()}
})// 静音
function muteVolume() {localStorage.setItem('volume', 'off')gsap.to(audio, { volume: 0, duration: 1 })btnVolume.classList.remove('after:hidden')btnVolume.querySelector(':first-child').classList.remove('animate-ping')btnVolume.classList.add('after:block')
}// 解除静音
function unmuteVolume() {localStorage.setItem('volume', 'on')btnVolume.classList.add('after:hidden')btnVolume.querySelector(':first-child').classList.add('animate-ping')btnVolume.classList.remove('after:block')gsap.to(audio, { volume: initialVolume, duration: 1 })
}// 主题选择
const topBar = document.querySelector('.top-bar')
const paletteSelectors = document.querySelectorAll('[data-color]')
gsap.to(topBar, {opacity: 1,delay: 0.5,onComplete: () => {gsap.to(paletteSelectors, {duration: 1,x: 0,autoAlpha: 1,ease: `elastic.out(1.2, 0.9)`,stagger: {amount: 0.2,},})},
})paletteSelectors.forEach((selector) =>selector.addEventListener('click', function () {const paletteName = this.dataset.colorapplyPalette(paletteName)})
)// 加载器
const manager = new THREE.LoadingManager()
const textureLoader = new THREE.TextureLoader(manager)// 按键新手引导
const wasd = textureLoader.load('/wasd.png')
const arrows = textureLoader.load('/arrows.png')const wasdGeometry = new THREE.PlaneGeometry(3.5, 2)
wasdGeometry.rotateX(-Math.PI * 0.5)const planeWasd = new THREE.Mesh(wasdGeometry,new THREE.MeshStandardMaterial({transparent: true,map: wasd,opacity: isMobile ? 0 : 0.5,})
)const planeArrows = new THREE.Mesh(wasdGeometry,new THREE.MeshStandardMaterial({transparent: true,map: arrows,opacity: isMobile ? 0 : 0.5,})
)planeArrows.position.set(8.7, 0, 21)
planeWasd.position.set(13, 0, 21)// 添加按键新手引导
scene.add(planeArrows, planeWasd)// 使用主题
applyPalette(paletteName)// 游戏主循环
function tic() {controls.update()renderer.render(scene, camera)requestAnimationFrame(tic)
}requestAnimationFrame(tic)// 监听屏幕尺寸变化
window.addEventListener('resize', handleResize)function handleResize() {sizes.width = window.innerWidthsizes.height = window.innerHeightcamera.aspect = sizes.width / sizes.heightcamera.updateProjectionMatrix()renderer.setSize(sizes.width, sizes.height)const pixelRatio = Math.min(window.devicePixelRatio, 2)renderer.setPixelRatio(pixelRatio)
}
游戏 截图如上
游戏源码地址:GitCode - 全球开发者的开源社区,开源代码托管平台
游戏预览地址:3D贪吃蛇
创作不易,点个赞再走吧
相关文章:
使用最新threejs复刻经典贪吃蛇游戏的3D版,附完整源码
基类Entity 建立基类Entity,实现投影能力、动画入场效果(从小变大的弹性动画)、计算自己在地图格位置的方法。 // 导入gsap动画库(用于创建补间动画) import gsap from gsap// 定义Entity基类 export default class …...
Google优化搜索体验:全新动态摘要功能(Beta)为欧洲用户带来更丰富的结果
Google持续推动搜索体验的创新,最新推出的动态摘要(Dynamic Snippets)功能(Beta版)为欧洲经济区(EEA)的用户和企业带来了全新的交互方式。2025年4月,Google更新了动态摘要的文档&…...
[苍穹外卖 | 项目日记] 第三天
前言 实现了新增菜品接口实现了菜品分页查询接口实现了删除菜品接口实现了根据id查询菜品接口实现了修改菜品接口 今日收获: 今日的这几个接口其实和之前写的对员工的操作是一样的,都是一整套Curd操作,所以今天在技术层面上并没有…...
【Python爬虫基础篇】--2.模块解析
目录 1.urllib库 1.1.request模块 1.1.1、urllib.request.urlopen() 函数 1.1.2.urllib.request.urlretrieve() 函数 1.2. error模块 1.3. parse 模块 2. BeautifulSoup4库 2.1.对象种类 2.2.对象属性 2.2.1.子节点 2.2.2.父节点 2.2.3.兄弟节点 2.2.4.回退和前进 …...
LabVIEW技巧——获取文件版本信息
获取可执行文件(exe)版本信息的几种方法 方法1. LabVIEW自带函数 labview自带了获取文件版本号的VI,但是没有开放到程序框图的函数选板中,在该目录下可以找到:...\LabVIEW 20xx\vi.lib\Platform\fileVersionInfo.llb…...
【软件工程】用飞书画各种图(流程图,架构图···)
笔者在做服务外包大赛的时候被文档内容的编写反复折磨,网上的工程图绘画工具要么是展示效果不佳,要么要收大几百的VIP费,最后发现飞书竟然可以直接绘画并插入示意图。 一、为什么选择飞书文档画流程图? 完全免费,无广…...
RFID图书管理系统如何重构数字化仓储管理新生态
引言 在图书馆与出版行业数字化转型进程中,RFID图书管理系统正打破传统人工管理的效率瓶颈,通过与数字化仓储管理系统的深度融合,实现从图书采购、入库到借阅的全链路智能化。本文结合RFID固定资产管理软件的应用逻辑,解析这一技…...
如何校验一个字符串是否是可以正确序列化的JSON字符串呢?
方法1:先给一个比较暴力的方法 try {JSONObject o new JSONObject(yourString); } catch (JSONException e) {LOGGER.error("No valid json"); } 方法2: Object json new cn.hutool.json.JSONTokener("[{\"name\":\"t…...
操作系统-PV
🧠 背景:为什么会有 PV? 类比:内存(生产者) 和 CPU(消费者) 内存 / IO / 磁盘 / 网络下载 → 不断“生产数据” 例如:读取文件、下载视频、从数据库加载信息 CPU → 负…...
工厂方法模式详解及c++代码实现(以自动驾驶感知模块中的应用为例)
模式定义 工厂方法模式(Factory Method Pattern)是一种创建型设计模式,通过定义抽象工厂接口将对象创建过程延迟到子类实现,实现对象创建与使用的解耦。该模式特别适合需要动态扩展产品类型的场景。 自动驾驶感知场景分析 自动驾…...
Jsp技术入门指南【五】详细讲解jsp结构页面
Jsp技术入门指南【五】详细讲解jsp结构页面 前言一、JSP页面的结构二、JSP页面的部件1. 指令(核心控制部件)2. 动作(页面交互部件,了解即可)3. 脚本(Java逻辑嵌入部件) 三、JSP指令详解1.1 JSP指…...
游戏APP如何抵御DDoS攻击与黑客勒索?实战防护全攻略
一、游戏行业安全挑战与攻击危害 游戏APP因高实时性、高并发及虚拟资产交易特性,成为DDoS攻击和勒索的重灾区,典型威胁包括: DDoS攻击瘫痪服务: UDP Flood:针对游戏服务器端口(如UDP 7777)发起…...
Mac 选择下载安装工具 x86 还是 arm64 ?
要确定你的 Mac 电脑应该选择下载安装工具的 x86 还是 arm64 版本,关键是判断你的 Mac 使用的是 Intel 处理器(x86 架构)还是 Apple Silicon(如 M1、M2 等,arm64 架构)。具体方法如下: 方法 1&…...
string函数的应用
字符串查找 find 方法 实例 string s "Hello World,C is awesome!";//查找子串 size_t pos1 s.find("World"); //pos16 size_t pos2 s.find("Python"); //pos2string::npos//查找字符 size_tpos3s.find(c); //pos313//从指定位置开始查找 size…...
使用Trae CN分析项目架构
架构分析后的截图 A区是打开的项目、B区是源码区、C区是AI给出当前项目的架构分析结果。 如何用 Trae CN 快速学习 STM32 嵌入式项目架构 在嵌入式开发领域,快速理解现有项目的架构是一项关键技能。Trae CN 作为一款强大的分析工具,能帮助开发者高效剖…...
每日一题(小白)暴力娱乐篇33
由题意可知我们今天要解决的问题是在1~2025中去找合适的数字,这里要找出一些特殊的数字,这些数字要满足以下条件,是三的倍数,是8的倍数,是38的倍数,老板就给要多给一个红包,我们循环然后相应条件…...
MyBatis框架
前言: MyBatis框架相比JDBC来说大大提升了我们写代码的效率,但是对初学者来说框架还是有点难以理解,所以本篇博客会十分详细的讲解MyBatis框架 目录 一.MyBatis概述 1.什么是映射? 2.什么是XML? 二.MyBatis框架搭建 1.创建一张表和表对…...
基于SpringBoot的新闻小程序开发与设计
概述 在信息爆炸的时代,高效获取新闻资讯成为现代人的刚需。幽络源平台今日分享一款基于SpringBoot框架开发的微信小程序新闻资讯系统,该系统采用前后端分离架构,实现了新闻分类管理、个性化推荐、用户收藏等功能,为新闻传播提供…...
IE之路专题12.BGP专题
BGP协议有哪些特点 BGP时边界网关协议(EGP),是一种用在自治系统之间传递路由信息的路由协议; 提供了丰富的路由属性以及强大的路由过滤和路由策略,实现灵活选路和路由控制; 策略方式更改属性或根据更新信息中属性实现过滤和策略 BGP是工作在传输层TCP之上的,使用TCP的端口号…...
文件包含(详解)
文件包含漏洞是一种常见的Web安全漏洞,其核心在于应用程序未对用户控制的文件路径或文件名进行严格过滤,导致攻击者能够包含并执行任意文件(包括本地或远程恶意文件)。 1. 文件包含原理 动态文件包含机制 开发者使用动态包含函数…...
智慧养老照护实训室:推动养老服务数字化转型实践
在人口老龄化加速与数字化浪潮席卷的当下,传统养老服务模式在效率、精准度及个性化服务上的局限日益明显。智慧养老成为行业转型的必然方向,但专业人才短缺与技术应用落地困难制约着发展。智慧养老照护实训室通过整合虚拟仿真等前沿技术,构建…...
NOIP2015提高组.信息传递
目录 题目算法标签: 并查集, T a r j a n Tarjan Tarjan算法, s c c scc scc强连通分量思路 T a r j a n Tarjan Tarjan算法求解代码 题目 517. 信息传递 算法标签: 并查集, T a r j a n Tarjan Tarjan算法, s c c scc scc强连通分量 思路 使用强连通分量算法求环上点…...
Wireshark 搜索组合速查表
文章目录 Wirshark使用记录基本语法规则搜索条件符号速查表捕获过滤器组合指令速查表筛选过滤器组合命令速查表Wireshark Frame 协议字段解析 Wirshark使用记录 官网地址:https://www.wireshark.org/ 基本语法规则 字段描述示例说明type用于指定数据包的类型&…...
AI当前状态:有哪些新技术
一、到目前为址AI领域出现的新技术 到目前为止,AI领域涌现了许多令人兴奋的新技术。以下是一些关键的进展,涵盖了从基础模型到实际应用的多个方面: 1. 更强大的大型语言模型 (LLMs): 性能提升: 新一代LLM,例如OpenAI的GPT-4o和…...
我的gittee仓库
日常代码: 日常代码提交https://gitee.com/xinxin-pingping/daily-code 有需要的宝子们可自行读取。...
RT-Thread开发文档合集
瑞萨VisionBoard开发实践指南 RT-Thread 文档中心 RT-Thread-【RA8D1-Vision Board】 RA8D1 Vision Board上的USB实践RT-Thread问答社区 - RT-Thread 【开发板】环境篇:05烧录工具介绍_哔哩哔哩_bilibili 【RA8D1-Vision Board】基于OpenMV 实现图像分类_哔哩哔哩_…...
SPWM-H桥逆变器工作原理
SPWM-H桥逆变器(Sinusoidal Pulse Width Modulation H-Bridge Inverter)是一种基于正弦脉宽调制(SPWM)技术的电力电子装置,用于将直流(DC)转换为交流(AC)。它广泛应用于光…...
【数据结构_10】二叉树(2)
一、根据树的遍历结果还原树 紧接着(1),我们继续来讨论:如果给了树的遍历结果,我们能否把这个树给还原出来呢? 如果只给一种遍历结果,那么我们无法对树进行还原。 只有给了我们先序中序&…...
day1-小白学习JAVA(mac版)---(jdk安装和环境变量配置)
JDK安装和环境变量配置 我的电脑系统一、下载JDK1、oracle官网下载适合的JDK安装包,选择Mac OS对应的版本。 二、安装三、配置环境变量1、终端输入/usr/libexec/java_home -V查询所在的路径,复制备用2、输入ls -a3、检查文件目录中是否有.bash_profile文…...
Muduo网络库实现 [十六] - HttpServer模块
设计思路 本模块就是设计一个HttpServer模块,提供便携的搭建http协议的服务器的方法。那么这个模块需要如何设计呢? 这还需要从Http请求说起。 首先从http请求的请求行开始分析,请求行里面有个方法。分为静态资源请求和功能性请求的。 静态…...
工业触摸显示器助力智慧工业实验室发展
工业触摸显示器作为智慧工业实验室的核心人机交互设备,凭借其卓越的性能和灵活性,为实验室的智能化、自动化发展提供了强有力的支持。以下从多个方面阐述工业触摸显示器如何助力智慧工业实验室的发展: 一、提升操作便捷性与效率 直观操作&a…...
k8s介绍与实践
第一节 理论 基础介绍,部署实践,操作实践,点击这里学习 第二节 dashboard操作 查看安装的dashboard服务信息 kubectl get pod,svc -n kubernetes-dashboard 网页登录地址:https://server_ip:30976/#/login 创建token kube…...
ATEngin开发记录_5_C++日志打印引发的崩溃?一次虚函数调用引发的内存错误排查记录
该系列只做记录 不做教程 所以文章简洁直接 会列出碰到的问题和解决方案 只适合C萌新 在使用 C 进行事件系统开发时,我遇到了一次由于调用虚函数 GetName() 输出日志而引发的崩溃问题。通过逐步排查、使用防御性编程和类型检查,最终定位到了隐藏的生命…...
Yocto项目实战教程 · 第4章:4.2小节-菜谱
🔍 B站相应的视频教程: 📌 Yocto项目实战教程-第4章-4.2小节-菜谱 记得三连,标为原始粉丝。 在 Yocto 项目中,**菜谱(Recipe)**承载了包的配置信息、源码获取方式、编译与安装步骤,是…...
7.Rust+Axum:打造高效 RESTful API 的最佳实践
摘要 深入探讨 RustAxum 开发 RESTful API 的关键要点,涵盖资源路由设计、HATEOAS 实现、参数处理及 DTO 序列化与 JSON 处理案例。 一、引言 在现代 Web 开发中,RESTful API 是构建分布式系统的重要组成部分。Rust 作为一种高性能、安全的系统编程语…...
CAN总线嵌入式开发实战:从入门到精通
CAN总线嵌入式开发实战:从入门到精通 一、CAN总线基础概念 CAN(Controller Area Network)是一种广泛应用于汽车电子和工业控制领域的串行通信协议,由Bosch公司于1986年开发。它具有以下核心特点: 多主架构:所有节点地位平等&am…...
从头学 | 目标函数、梯度下降相关知识笔记(一)
很多基本的概念最近忘的有点多,简单回顾一些 文章目录 1 目标函数、梯度下降1.1 回归模型中的目标函数1.1.1 回归任务目标函数(1) 均方误差(MSE)(2) Huber损失 1.1.2 分类任务目标函数(1) 交叉熵损失(Cross-Entropy)(2…...
欣佰特携数十款机器人相关前沿产品,亮相第二届人形机器人和具身智能行业盛会
2025年4月15日至16日,备受关注的第二届中国人形机器人与具身智能产业大会已在北京成功举行。作为国内前沿科技及产品服务领域的重要参与者,欣佰特科技携众多前沿产品精彩亮相,全方位展示了其在人形机器人与具身智能领域的创新产品。 在本次大…...
QT常见显示类控件及其属性
Label QLabel可用用来显示文本和图片 核心属性如下 文本格式---textFormat 例: 在ui界面创建3个label,分别用不同的显示格式 在构造函数进行文本格式和文内容设置 此时运行后三种显示格式无区别 可以给富文本加标签 如<B>表示加粗 如果将<…...
基于领域知识的A型主动脉夹层综合分割及面向临床的评估|文献速递-深度学习医疗AI最新文献
Title 题目 Domain knowledge based comprehensive segmentation of Type-A aortic dissection with clinically-oriented evaluation 基于领域知识的A型主动脉夹层综合分割及面向临床的评估 01 文献速递介绍 A型主动脉夹层(TAAD)是一种医疗急症&a…...
守护进程编程、GDB调试以及外网连接树莓派
目录 一、什么是守护进程以及如何创建守护进程1. 什么是守护进程?2. 如何创建守护进程? 二、什么是GDB调试以及如何用GDB命令调试C程序1. 什么是GDB?2. 如何用GDB命令调试C程序? 三、外网访问树莓派 一、什么是守护进程以及如何创…...
HTML理论题
1.什么是HTML? 超文本标记语言(英语:HyperText Markup Language,简称:HTML)是一种用于创建网页的标准标记语言。 2.DOCTYPE 的作用是什么?标准与兼容模式(混杂模式)各有什么区别? DOCTYPE 的作用是告知浏览器的解析器用什么文档标准解析这个文档。 标准模式:用于…...
Cables为链上社区树立标杆:专注于实用性、用户主权与全球流动性
在 Web3 世界,“社区”这个词已逐渐沦为炒作、虚高数据与短暂参与的代名词。Cables 正在颠覆这一现状,以真实贡献与长期可持续发展为核心,走出了一条独特的路径。 最近,Cables 推出了其核心长期战略之一——积分计划(…...
Clickhouse 配置参考
Clickhouse 配置参考 适用版本 21.3.9.84 config.xml 配置 <?xml version"1.0"?> <!--NOTE: User and query level settings are set up in "users.xml" file. --> <yandex><access_control_path>/data/clickhouse/clickhous…...
thinkphp实现图像验证码
示例 服务类 app\common\lib\captcha <?php namespace app\common\lib\captcha;use think\facade\Cache; use think\facade\Config; use Exception;class Captcha {private $im null; // 验证码图片实例private $color null; // 验证码字体颜色// 默认配置protected $co…...
【Pandas】pandas DataFrame where
Pandas2.2 DataFrame Indexing, iteration 方法描述DataFrame.head([n])用于返回 DataFrame 的前几行DataFrame.at快速访问和修改 DataFrame 中单个值的方法DataFrame.iat快速访问和修改 DataFrame 中单个值的方法DataFrame.loc用于基于标签(行标签和列标签&#…...
redis利用备忘录
fofa: icon_hash"864611937" 防护: redis的安全设置:设置完毕,需要重加载配置文件启动redis 1.绑定内网ip地址进行访问 2. requirepass设置redis密码 3.保护模式开启protected-mode开启(默认开启) 4.最好把…...
【dify实战】chatflow结合deepseek实现基于自然语言的数据库问答、Echarts可视化展示、Excel报表下载
dify结合deepseek实现基于自然语言的数据库问答、Echarts可视化展示、Excel报表下载 观看视频,您将学会 在dify下如何快速的构建一个chatflow,来完成数据分析工作;如何在AI的回复中展示可视化的图表;如何在AI 的回复中加入Excel报…...
医疗行业如何构建合成数据平台?——技术、合规与实践全景
目录 医疗行业如何构建合成数据平台?——技术、合规与实践全景 一、为什么医疗领域尤需合成数据? 二、平台功能全景图 ✅ 模块划分: 三、典型合成数据生成方式 1. 结构化病例合成 2. 医学图像生成 3. 多轮医生-患者问答合成 四、数据…...
6.8.最小生成树
一.复习: 1.生成树: 对于一个连通的无向图,假设图中有n个顶点,如果能找到一个符合以下要求的子图: 子图中包含图中所有的顶点,同时各个顶点保持连通, 而且子图的边的数量只有n-1条࿰…...