使用最新threejs复刻经典贪吃蛇游戏的3D版,附完整源码

news2025/4/19 16:10:28

基类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缩放为0
      y: 0,                       // 初始Y缩放为0 
      z: 0,                       // 初始Z缩放为0
      ease: `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 = scene
		this.resolution = resolution
		this.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.1
		leftEye.position.x = 0.5
		leftEye.position.y = 0.12
		leftEye.position.z = -0.1

		let 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.05
		leftEye.add(leftEyeHole)

		// 造右眼:同上
		const rightEye = leftEye.clone()

		rightEye.position.x = -0.5
		rightEye.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.07
		mouthMesh.position.z = 0.23
		mouthMesh.position.y = -0.19

		this.mouth = mouthMesh

		// 把零件装到蛇头上
		headMesh.add(rightEye, leftEye, mouthMesh)
		/* 调整头部朝向当前方向 */
		headMesh.lookAt(headMesh.position.clone().add(this.direction))
	}

	init() {
		// 重置方向
		this.direction = RIGHT
		this.iMoving = null

		// 造第一个蛇头节点(放在地图中心)
		const head = new ListNode(new SnakeNode(this.resolution))

		head.data.mesh.position.x = this.resolution.x / 2
		head.data.mesh.position.z = this.resolution.y / 2
		this.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 = UP
				break
			case 'ArrowDown':
			case 'KeyS':
				newDirection = DOWN
				break
			case 'ArrowLeft':
			case 'KeyA':
				newDirection = LEFT
				break
			case 'ArrowRight':
			case 'KeyD':
				newDirection = RIGHT
				break
			default:
				return
		}

		const dot = this.direction.dot(newDirection)
		// 禁止180度掉头(比如正在右走不能直接左转)
		if (dot === 0) {
			this.newDirection = newDirection
		}
	}

	// 每帧判断
	update() {
		// 应用新方向
		if (this.newDirection) {
			this.direction = this.newDirection
			this.newDirection = null
		}
		let currentNode = this.end

		// 处理吃糖果后的尾巴生长
		if (this.end.data.candy) {
			this.end.data.candy = null
			this.end.data.mesh.scale.setScalar(1)

			this.addTailNode()
		}

		// 身体像波浪一样跟随头部移动
		while (currentNode.prev) {
			const candy = currentNode.prev.data.candy
			if (candy) {
				currentNode.data.candy = candy
				currentNode.data.mesh.scale.setScalar(1.15)
				currentNode.prev.data.candy = null
				currentNode.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.position
		headPos.add(this.direction)
		// currentNode.data.mesh.position.add(this.direction)
		const headMesh = this.body.head.data.mesh
		headMesh.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 = loadedFont

  printScore() // 字体加载完成后初始化计分板
})

// 配色方案集合(支持主题切换)
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 = palette

  if (!palette) return
  // 更新场景元素颜色
  const {
    groundColor,
    fogColor,
    rockColor,
    treeColor,
    candyColor,
    snakeColor,
    mouthColor,
  } = palette

  planeMaterial.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 = mouthColor
  snake.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 = candy
    addCandy() // 生成新的糖果
    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.5
  mesh.position.z = -4
  mesh.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 - 1
      let 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 += y
          scale = 0.33
        }
      } else {
        if (-x * scale > y) {
          snake.setDirection('ArrowLeft')
          middle += y
          scale = 0.33
        } else {
          snake.setDirection('ArrowDown')
          scale = 3
        }
      }

      prevTouch.x = x
      prevTouch.y = y // 记录本次触摸位置
    })
  } else {
    // 桌面端键盘事件监听
    window.addEventListener('keydown', function (e) {
      const keyCode = e.code

      snake.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.x
  candy.mesh.position.z = Math.floor(index / resolution.x)

  candies.push(candy) // 加入对象池

  candy.in() // 播放生成动画

  scene.add(candy.mesh)
}

addCandy()

// 随机位置生成算法
function getFreeIndex() {
  let index
  let 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.x
  entity.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).mesh
  clone.position.copy(position)
  clone.scale.set(x, y, z)
  clone.rotation.y = w
  scene.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.volume

btnVolume.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.color
    applyPalette(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.innerWidth
  sizes.height = window.innerHeight

  camera.aspect = sizes.width / sizes.height
  camera.updateProjectionMatrix()

  renderer.setSize(sizes.width, sizes.height)

  const pixelRatio = Math.min(window.devicePixelRatio, 2)
  renderer.setPixelRatio(pixelRatio)
}

游戏 截图如上

游戏源码地址:GitCode - 全球开发者的开源社区,开源代码托管平台

游戏预览地址:3D贪吃蛇

创作不易,点个赞再走吧

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

论坛测试报告

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

zset.

zset 有序集合 zset 保留了 set 不能有重复元素的特点 zset 中的每个元素都有一个唯一的浮点类型的分数&#xff08;score&#xff09;与之关联&#xff0c;使得 zset 内部的元素是可以维护有序性的。但是这个有序不是用下标作为排序依据的&#xff0c;而是根据分数&#xf…

Windows 部署 DeepSeek 详细教程

一、准备工作 系统要求&#xff1a; 建议Windows 10 22H2 或更高版本&#xff0c;家庭版或专业版上网环境&#xff1a; 建议科学上网&#xff0c;国内访问部分网站会很慢设备要求&#xff1a; 内存8G以上、关闭防火墙 二、安装Ollama 官网链接: https://ollama.com/downloadg…

过去十年前端框架演变与技术驱动因素剖析

一、技术演进脉络&#xff08;2013-2023&#xff09; 2013-2015&#xff1a;结构化需求催生框架雏形 早期的jQuery虽然解决了跨浏览器兼容性问题&#xff08;如IE8兼容性处理&#xff09;&#xff0c;但其松散的代码组织方式难以支撑复杂应用开发。Backbone.js的出现首次引入M…

基于微信小程序的中医小妙招系统的设计与实现

hello hello~ &#xff0c;这里是 code袁~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f981;作者简介&#xff1a;一名喜欢分享和记录学习的在校大学生…

css button 点击效果

<!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><title>button点击效果</title><style>#container {display: flex;align-items: center;justify-content: center;}.pushable {position: relat…

Foundation Agent:深度赋能AI4DATA

2025年5月17日&#xff0c;第76期DataFunSummit&#xff1a;AI Agent技术与应用峰会将在DataFun线上社区举办。Manus的爆火并非偶然&#xff0c;随着基础模型效果不断的提升&#xff0c;Agent作为大模型的超级应用备受全世界的关注。为了推动其技术和应用&#xff0c;本次峰会计…

Docker--Docker镜像原理

docker 是操作系统层的虚拟化&#xff0c;所以 docker 镜像的本质是在模拟操作系统。 联合文件系统&#xff08;UnionFS&#xff09; 联合文件系统&#xff08;UnionFS&#xff09; 是Docker镜像实现分层存储的核心技术&#xff0c;它通过将多个只读层&#xff08;Image Laye…

SpringAI+DeepSeek大模型应用开发——2 大模型应用开发架构

目录 2.大模型开发 2.1 模型部署 2.1.1 云服务-开放大模型API 2.1.2 本地部署 搜索模型 运行大模型 2.2 调用大模型 接口说明 提示词角色 ​编辑 会话记忆问题 2.3 大模型应用开发架构 2.3.1 技术架构 纯Prompt模式 FunctionCalling RAG检索增强 Fine-tuning …

2.2/Q2,Charls最新文章解读

文章题目&#xff1a;Association of uric acid to high-density lipoprotein cholesterol ratio with the presence or absence of hypertensive kidney function: results from the China Health and Retirement Longitudinal Study (CHARLS) DOI&#xff1a;10.1186/s12882-…

李飞飞团队新作WorldScore:“世界生成”能力迎来统一评测,3D/4D/视频模型同台PK

从古老神话中对世界起源的幻想&#xff0c;到如今科学家们在实验室里对虚拟世界的构建&#xff0c;人类探索世界生成奥秘的脚步从未停歇。如今&#xff0c;随着人工智能和计算机图形学的深度融合&#xff0c;我们已站在一个全新的起点&#xff0c;能够以前所未有的精度和效率去…

如何在米尔-STM32MP257开发板上部署环境监测系统

本文将介绍基于米尔电子MYD-LD25X开发板&#xff08;米尔基于STM35MP257开发板&#xff09;的环境监测系统方案测试。 摘自优秀创作者-lugl4313820 一、前言 环境监测是当前很多场景需要的项目&#xff0c;刚好我正在论坛参与的一个项目&#xff1a;Thingy:91X 蜂窝物联网原型…

MySQL之SQL优化

目录 1.插入数据 2.大批量插入数据 3.order by优化 4.group by优化 5.limit优化 6.count优化 count用法 7.update优化 1.插入数据 如果我们需要一次性往数据库表中插入多条记录&#xff0c;可以从以下三个方面进行优化 第一个:批量插入数据 Insert into tb_test va…

python_level1.2

目录 一、变量 例如&#xff1a;小正方形——>大正方形 【1】第一次使用这个变量&#xff0c;所以说&#xff1a;定义一个变量length&#xff1b; 【2】&#xff1a;是赋值符号&#xff0c;不是等于符号。&#xff08;只有赋值&#xff0c;该变量才会被创建&#xff09;…

Linux、Kylin OS挂载磁盘,开机自动加载

0.实验环境&#xff1a; 1.确定挂载目录&#xff0c;如果没有使用mkdir 进行创建&#xff1a; mkdir /data 2.查看磁盘 lsblk #列出所有可用的块设备df -T #查看磁盘文件系统类型 3.格式化成xfs文件系统 (这里以xfs为例&#xff0c;ext4类似) mkfs.xfs /dev/vdb 4.挂载到…

FPGA-VGA

目录 前言 一、VGA是什么&#xff1f; 二、物理接口 三、VGA显示原理 四、VGA时序标准 五、VGA显示参数 六、模块设计 七、波形图设计 八、彩条波形数据 前言 VGA的FPGA驱动 一、VGA是什么&#xff1f; VGA&#xff08;Video Graphics Array&#xff09;是IBM于1987年推出的…

【嵌入式】【阿里云服务器】【树莓派】学习守护进程编程、gdb调试原理和内网穿透信息

目录 一. 守护进程的含义及编程实现的主要过程 1.1守护进程 1.2编程实现的主要过程 二、在树莓派中通过三种方式创建守护进程 2.1nohup命令创建 2.2fork()函数创建 2.3daemon()函数创建 三、在阿里云中通过三种方式创建守护进程 3.1nohup命令创建 3.2fork()函数创建 …

前沿篇|CAN XL 与 TSN 深度解读

引言 1. CAN XL 标准演进与设计目标 2. CAN XL 物理层与帧格式详解 3. 时间敏感网络 (TSN) 关键技术解析 4. CAN XL + TSN 在自动驾驶领域的典型应用

AI大模型科普:从零开始理解AI的“超级大脑“,以及如何用好提示词?

大家好&#xff0c;小机又来分享AI了。 今天分享一些新奇的东西&#xff0c; 你有没有试过和ChatGPT聊天时&#xff0c;心里偷偷犯嘀咕&#xff1a;"这AI怎么跟真人一样对答如流&#xff1f;它真的会思考吗&#xff1f;" 或者刷到技术文章里满屏的"Token"…

STM32单片机入门学习——第40节: [11-5] 硬件SPI读写W25Q64

写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难&#xff0c;但我还是想去做&#xff01; 本文写于&#xff1a;2025.04.18 STM32开发板学习——第一节&#xff1a; [1-1]课程简介第40节: [11-5] 硬件SPI读…