当前位置: 首页 > news >正文

BrotatoLike游戏制作(拆解人物Player脚本)

以下是人物的脚本,实现了死亡动画播放完毕后显示“菜”字,且“菜”字自适应屏幕居中(无论窗口大小如何变化,都会居中显示)。

# 1. 继承 CharacterBody2D 节点 # 作用:让当前脚本挂载的节点拥有2D角色移动、碰撞、物理处理的基础能力 extends CharacterBody2D # ====================== 可编辑导出变量(编辑器可见) ====================== # 2. 导出移动速度变量,默认300,可在编辑器直接修改 @export var speed: float = 300.0 # 3. 导出子弹场景变量,用于在编辑器拖拽绑定子弹预制体 @export var bullet_scene: PackedScene # 4. 导出射击冷却时间,控制射速,默认0.15秒/发 @export var shoot_cooldown: float = 0.15 # ====================== 节点引用(自动获取子节点) ====================== # 5. 获取名为 AnimatedSprite2D 的子节点,用于播放动画 @onready var animated_sprite: AnimatedSprite2D = $AnimatedSprite2D # 6. 获取名为 Muzzle 的枪口标记点,子弹从这里生成 @onready var muzzle: Marker2D = $Muzzle # 7. 获取名为 Hurtbox 的受伤区域,用于检测敌人碰撞 @onready var hurtbox: Area2D = $Hurtbox # ====================== 自定义状态变量 ====================== # 8. 声明射击计时器,控制射速 var shoot_timer: Timer # 9. 射击状态:是否正在按住射击键 var is_shooting: bool = false # 10. 死亡状态:玩家是否死亡 var is_dead: bool = false # 11. 防开局自动开枪:游戏启动后延迟允许射击 var can_shoot_after_ready: bool = false # ====================== 初始化函数(节点加载完成执行) ====================== func _ready(): # 12. 将玩家加入 "player" 组,方便全局管理(比如敌人检测玩家) add_to_group("player") # 13. 新建一个计时器节点 shoot_timer = Timer.new() # 14. 设置计时器等待时间 = 射击冷却时间 shoot_timer.wait_time = shoot_cooldown # 15. 非单次计时:循环触发(按住射击键持续开枪) shoot_timer.one_shot = false # 16. 绑定计时器超时信号:时间到执行 _on_shoot_timer_timeout 函数 shoot_timer.timeout.connect(_on_shoot_timer_timeout) # 17. 将计时器添加为当前节点的子节点 add_child(shoot_timer) # 18. 如果受伤区域存在 if hurtbox: # 19. 绑定受伤区域的碰撞信号:碰到物体执行 _on_hurtbox_body_entered hurtbox.body_entered.connect(_on_hurtbox_body_entered) # 20. 等待0.1秒,再开启射击权限 await get_tree().create_timer(0.1).timeout # 21. 允许射击 can_shoot_after_ready = true # ====================== 物理帧更新(固定频率,处理移动/输入) ====================== func _physics_process(delta: float) -> void: # 22. 如果玩家死亡,直接退出函数,不执行后续逻辑 if is_dead: return # 23. 获取鼠标在世界中的全局坐标 var mouse_pos = get_global_mouse_position() # 24. 获取水平输入:左(-1)/右(1)/无(0),对应项目设置的输入映射 var horizontal := Input.get_axis("move_left", "move_right") # 25. 获取垂直输入:上(-1)/下(1)/无(0) var vertical := Input.get_axis("move_up", "move_down") # 26. 组合成方向向量,并标准化(防止斜向移动速度过快) var direction := Vector2(horizontal, vertical).normalized() # 27. 计算速度 = 方向 * 速度 velocity = direction * speed # 28. 执行移动(CharacterBody2D 自带函数,处理碰撞) move_and_slide() # 29. 如果动画节点存在 if animated_sprite: # 30. 如果玩家在移动 if velocity.length() > 0: # 31. 播放走路动画 animated_sprite.play("walk") # 32. 如果水平方向有输入,翻转精灵(向左走就向左看) if horizontal != 0: animated_sprite.flip_h = horizontal < 0 else: # 33. 没移动就播放 idle 待机动画 animated_sprite.play("idle") # 34. 强制角色朝向鼠标:鼠标在左边,精灵向左翻转 if animated_sprite and mouse_pos.x < global_position.x: animated_sprite.flip_h = true # 35. 鼠标在右边,精灵向右 elif animated_sprite: animated_sprite.flip_h = false # 36. 如果按住射击键 if Input.is_action_pressed("shoot"): # 37. 如果没在射击状态 if not is_shooting: # 38. 开始射击 start_shooting() else: # 39. 松开射击键,且正在射击 if is_shooting: # 40. 停止射击 stop_shooting() # ====================== 开始射击函数 ====================== func start_shooting(): # 41. 死亡 或 未到允许射击时间 → 直接退出 if is_dead or not can_shoot_after_ready: return # 42. 标记为正在射击 is_shooting = true # 43. 立即向鼠标位置发射一颗子弹 shoot_at(get_global_mouse_position()) # 44. 启动射速计时器 shoot_timer.start() # ====================== 停止射击函数 ====================== func stop_shooting(): # 45. 取消射击状态 is_shooting = false # 46. 停止计时器 shoot_timer.stop() # ====================== 计时器超时回调(自动连射) ====================== func _on_shoot_timer_timeout(): # 47. 按住射击键 + 玩家存活 → 继续射击 if is_shooting and not is_dead: shoot_at(get_global_mouse_position()) # ====================== 发射子弹核心函数 ====================== func shoot_at(target_pos: Vector2): # 48. 死亡 或 没绑定子弹场景 → 退出 if is_dead or not bullet_scene: return # 49. 计算子弹飞行方向:目标点 - 玩家位置 → 标准化 var direction = (target_pos - global_position).normalized() # 50. 实例化子弹场景 var bullet = bullet_scene.instantiate() # 51. 设置子弹位置:优先用枪口,没有就用玩家自身位置 bullet.global_position = muzzle.global_position if muzzle else global_position # 52. 给子弹赋值飞行方向 bullet.direction = direction # 53. 给子弹赋值飞行速度 bullet.speed = 400 # 54. 将子弹添加到当前场景 get_tree().current_scene.add_child(bullet) # ====================== 受伤区域碰撞回调 ====================== func _on_hurtbox_body_entered(body: Node2D): # 55. 已死亡 → 退出 if is_dead: return # 56. 碰到的物体是敌人 → 执行死亡 if body.is_in_group("enemy"): die() # ====================== 死亡逻辑 ====================== func die(): # 57. 防止重复死亡 if is_dead: return # 58. 标记为死亡 is_dead = true # 59. 关闭物理更新(停止移动) set_physics_process(false) # 60. 如果正在射击,停止射击 if is_shooting: stop_shooting() # 61. 获取玩家碰撞体 var collision_shape = $CollisionShape2D if collision_shape: # 62. 禁用碰撞(死亡后无法被碰撞) collision_shape.disabled = true if hurtbox: # 63. 关闭受伤区域检测 hurtbox.monitoring = false # 64. 如果有死亡动画,播放动画 if animated_sprite and animated_sprite.sprite_frames.has_animation("death"): animated_sprite.play("death") # 65. 等待动画播放完成 await animated_sprite.animation_finished # 66. 隐藏玩家 visible = false # 67. 显示“菜”字提示 show_cai_word() # 68. 暂停游戏 get_tree().paused = true # ====================== 显示死亡提示文字 ====================== func show_cai_word(): # 69. 创建画布层(确保文字显示在最上层) var canvas = CanvasLayer.new() canvas.layer = 10 # 70. 游戏暂停时依然正常运行 canvas.process_mode = Node.PROCESS_MODE_ALWAYS get_tree().current_scene.add_child(canvas) # 71. 创建文字标签 var label = Label.new() label.text = "菜" # 提示文字 label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER # 水平居中 label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER # 垂直居中 label.add_theme_font_size_override("font_size", 120) # 字体大小120 label.add_theme_color_override("font_color", Color.RED) # 红色字体 label.anchor_right = 1.0 # 锚点铺满屏幕 label.anchor_bottom = 1.0 label.offset_left = 0 label.offset_top = 0 label.size = Vector2.ZERO label.process_mode = Node.PROCESS_MODE_ALWAYS # 暂停时显示 canvas.add_child(label)

接下来开始拆解

一、@onready

@onreadyGodot 4 专门用来获取子节点的语法糖,作用只有一个:等节点准备好之后,再自动获取子节点,不用自己写在 _ready () 里。

场景节点只有进入场景树、触发_ready()生命周期后,才能用get_node()找到子节点; 普通变量在脚本最顶部定义时,执行时机早于节点加载,直接赋值找不到节点,所以要写在_ready()里;@onready修饰的变量,会自动把赋值语句推迟到_ready()执行阶段运行,简化代码。另外说一句在GDscript中$ =get_node()

$节点名就是get_node("节点名")的简写

# 两种写法完全等同 @onready var my_label = get_node("MyLabel") @onready var my_label = $MyLabel

我们可以用@onready来对全局变量声明,_ready()函数会在所有@onready 变量赋值全部完成后,才运行函数里的自定义代码,这样就可以解决下述问题:

  • 全局变量先创建,此时节点还没加载进场景,不能直接=get_node()
  • 必须等到_ready()函数运行(节点全部就绪),再在函数内部获取节点存入变量;
  • 节点多了,_ready里会堆满一堆xxx=get_node(),代码臃肿。

二、拆解@export var bullet_scene: PackedScene

PackedScene=保存成资源的场景模板(对标 Unity 预制体 Prefab),属于Resource资源类型,存着一整棵节点树的配置数据,不能直接显示,必须调用.instantiate()生成真实节点实体

核心:它是 Resource(资源),不是 Node(节点)

  • Node:能放进场景树、能渲染、有坐标、能运行_process(玩家、子弹、Sprite 都属于 Node)
  • PackedScene:只是硬盘上.tscn文件加载到内存的数据包,没有坐标、不在场景树、画面看不见,只负责「复制生成整套节点」

1.三种在代码获取 PackedScene 的写法

①. @export 拖拽赋值

@export var bullet_scene: PackedScene

选中玩家节点,右侧检查器面板直接把Bullet.tscn拖入变量框,编辑器自动加载资源,运行前就绑定好,新手最常用。

②. preload(编译时预加载,推荐固定资源)

const BULLET_RES: PackedScene = preload("res://scenes/Bullet.tscn")

引擎编译脚本时直接读取 tscn,运行零加载卡顿,写死路径的子弹 / 敌人首选。

③. load(运行时动态加载,路径可变)

var res_path = "res://scenes/Bullet.tscn" var bullet_res: PackedScene = load(res_path)

游戏运行中按需读取,适合动态换皮肤、动态加载关卡资源。

2、核心方法:.instantiate()(生成子弹的关键)

#1. 数据包(PackedScene,看不见) var bullet_res: PackedScene = bullet_scene #2. instantiate:根据数据包,在内存生成完整节点树(子弹实体,不在场景) var bullet_node = bullet_res.instantiate() #3. add_child:加入场景树,引擎渲染、物理生效、屏幕出现子弹 get_tree().current_scene.add_child(bullet_node)

.instantiate()关键特性

①.每次 instantiate 都是全新独立个体:改 A 子弹的速度,不会改 B 子弹、不会改原 tscn 模板;

②.生成瞬间只执行子弹脚本_init()add_child 进场景后才触发 @onready、_ready ()

3.PackedScene VS 普通节点 new () 巨大区别

#方式1:PackedScene实例化(推荐子弹/怪物) var bullet = bullet_scene.instantiate() #自动生成:根节点+Sprite2D+碰撞形状+绑定好的脚本+编辑器调好的参数 #方式2:手动new节点(极麻烦,不适合大量生成) var bullet = CharacterBody2D.new() var spr = Sprite2D.new() bullet.add_child(spr) #所有贴图、碰撞、参数全部代码手动写,效率极低

PackedScene 一次性还原整套节点层级 + 所有编辑器配置,这是它最大价值

4.配套常用内置 API

①.can_instantiate():判断 PackedScene 资源完好、可以实例化,防止资源丢失报错
if bullet_scene.can_instantiate(): bullet_scene.instantiate()
②.pack(node:Node):运行时把场景树上现成节点打包成新 PackedScene,动态生成预制
var new_pack = PackedScene.new() new_pack.pack($Enemy) ResourceSaver.save(new_pack,"res://new_enemy.tscn")
③.总结PackedScene数据类型

PackedScene 是 Godot 中代表场景模板(.tscn)的数据类型,它本身只是一份资源数据,不显示在场景中。通过调用它的内置方法.instantiate(),可以将编辑器里制作好的完整场景,一次性实例化为可运行的节点对象,并自动带入该场景的:节点结构、精灵、碰撞体、脚本、变量、信号、动画等所有配置,无需在代码里重复创建节点、绑定功能,极大简化动态生成对象的逻辑。

三、Marker2D节点

讲实话我也根本不大明白这到底是什么意思,从定义出发:

1.根据定义来说

Marker2D 是一个通用的 2D 位置标记节点,用于编辑时可视化标记一个点。它继承自Node2D和普通 Node2D 功能几乎一样,只有一个区别:在编辑器里永远显示一个绿色十字(方便你看见它在哪),运行游戏时完全隐形、不渲染、不画任何东西。

Marker2D = 一个 “看得见的空点”。

  • 没有图片、没有碰撞、不显示在游戏画面里
  • 它只是一个坐标点(位置 + 旋转)
  • 编辑器里:绿色十字,一眼能找到
  • 运行时:完全消失,不影响画面和性能

而Marker2D的唯一属性gizmo_extents(默认 10.0)此属性其实就是调整编辑器中绿色十字的大小而已,只影响编辑器的显示,不影响游戏,Marker2D 就是个轻量 Node2D,成百上千个也不卡。这就是为什么这里的子弹选择使用Marker2D节点

2.总结:

将Marker2D这个点放在场景的任意一个点 Marker2D这个点绑定上含有它的脚本的场景比如人脚本里面含有@onready var muzzle: Marker2D = $Muzzle ,这样Marker2D就会和人物场景绑定,然后我在编辑器里面放置Marker2D的位置,在游戏运行中 Marker2D节点都只是会进行相对于人物的运动

四、为什么使用Area2D来发挥Hurtbox作用?

依旧从定义入手,来看看官方文档

可以看见Area2D作用主要是检测其他CollisionObject2D的进入或退出,也就是检测碰撞体

那么对于我们用来作为Hurtbox来说是一个绝佳的节点,Hurtbox只需要检测,它的核心需求就一个:检测重叠,不影响物理移动,不产生阻挡 / 反弹

1.那么我们为什么不适用碰撞体呢?

所有碰撞体都会受力,也有的会被阻挡,我们的目的是在特定的区域内检测受伤,

只有 Area2D 满足所有条件

  • 只检测重叠,不产生物理阻挡(子弹穿过你,你只掉血,不会被推)
  • 不影响角色移动、跳跃、碰撞
  • 可以单独画形状(头、身体、四肢分开,做暴击判定)
  • 有信号:area_entered /body_entered,代码好写
  • 性能轻量,无数个也不卡
  • 层与掩码,精准过滤,Hurtbox 层设为「受伤层」,Hitbox(子弹 / 武器)掩码只扫「受伤层」,不会误触发墙、地面、队友

  • 信号驱动,代码干净
    # Hurtbox 脚本 func _ready(): area_entered.connect(_on_hit) func _on_hit(area): if area.is_in_group("bullet"): take_damage(area.damage)

且主要原因是Area2D有着只“感知”不干涉的优势,也是其可以作为Hurtbox最重要的一点!

五、层与掩码

层(Layer)= 我是谁;掩码(Mask)= 我能看到谁。适用于所有 2D 物理节点(Area2D、CharacterBody2D、RigidBody2D…)。

1.Hurtbox(Area2D)举(最实用)

场景结构:

  • Player(CharacterBody2D)
    • Hurtbox(Area2D)← 受伤判定区

1)Player 设置

  • Layer:1(Player)
  • Mask:2,5(只和敌人、墙壁碰撞,不检测子弹

2)Hurtbox(Area2D)设置

  • Layer:4(Hurtbox)← 我是受伤区
  • Mask:3(Bullet)← 我只检测子弹

3)Bullet(Area2D)设置

  • Layer:3(Bullet)← 我是子弹
  • Mask:4(Hurtbox)← 我只检测受伤区

效果

  • 子弹碰到玩家身体(Layer1)→忽略(Mask 不包含 1)
  • 子弹碰到 Hurtbox(Layer4)→触发受伤信号(Mask 包含 4)
  • 玩家不会被子弹挡住(因为 Player 的 Mask 不含 Bullet)

总结一下,其实就是:当layer = mask 即可触发检测 / 碰撞

六、Timer节点

与其他语言不同的是 GDscript的Timer是倒计时器而非计时器

1.定义 Timer = 倒计时器

  • 从设定时间开始倒数
  • 到 0 时发出timeout 信号
  • 可以只跑一次循环重复
  • 不依赖帧率、不卡主线程,最适合做技能 CD、攻击间隔、生成怪物、无敌时间

2.核心属性(Inspector 面板)

①. Wait Time(最常用)
  • 类型:float(秒)
  • 含义:每次倒计时的总时间
  • 例子:1.0= 1 秒后触发 timeout
②.One Shot(一次 / 循环)
  • true只执行一次,到 0 就停(适合 “3 秒后开门”)
  • false循环执行,到 0 自动重置再倒数(适合 “每 2 秒发一颗子弹”)
③. Autostart(自动启动)
  • true:进入场景就自动开始倒计时
  • false:必须手动start()才开始(常用)
④. Ignore Time Scale(忽略慢动作)
  • false:受Engine.time_scale影响(慢动作时倒计时变慢)
  • true真实时间,不受慢动作影响(适合 UI、剧情倒计时)
⑤. Process Mode(更新时机)
  • Idle(默认):在_process更新,和帧率同步(适合 UI、动画)
  • Physics:在_physics_process更新,固定 60 次 / 秒(适合攻击、移动、物理相关冷却

3.唯一信号:timeout

  • Timer 到 0 时自动发出
  • 你要做的:把这个信号连接到你的函数

编辑器连接(推荐)

  1. 选中 Timer → 右侧「节点」→ 信号 → 双击timeout
  2. 选目标节点(如玩家)→ 选脚本函数(如_on_attack_cd
  3. 自动生成代码:
func _on_attack_cd(): print("技能冷却结束!")
代码连接
timer.timeout.connect(_on_attack_cd)
④、常用方法(代码控制)
timer.start() # 开始倒计时(重置并启动) timer.stop() # 停止并归零 timer.paused = true # 暂停(保留剩余时间) timer.paused = false # 继续 timer.time_left # 剩余时间(只读) timer.is_active() # 是否正在运行

4.实战场景(直接抄)

①. 玩家攻击冷却(核心!)
  • 需求:每 0.5 秒才能攻击一次
  • Timer 设置:
    • Wait Time = 0.5
    • One Shot = false(循环)
    • Autostart = true
    • Process Mode = Physics
func _input(event): if event is InputEventMouseButton and event.pressed: if timer.is_active() == false: # 冷却结束 attack() timer.start() # 重新开始冷却
②. 3 秒后生成敌人(一次性)
  • Timer:Wait Time=3,One Shot=true,Autostart=true
func _on_timer_timeout(): spawn_enemy()
③. 无敌时间(受伤后 1 秒无敌)
func take_damage(): if not invincible: health -= 1 invincible = true invincible_timer.start() # 1秒后解除无敌 func _on_invincible_timer_timeout(): invincible = false

5.避坑要点

  1. 不要用 _process 做倒计时:帧率不稳、代码乱、容易累积误差。
  2. 技能冷却一定要用 Physics 模式:和物理步同步,不会因为掉帧导致攻击变快。
  3. One Shot =true 时,timeout 后不会自动重启,必须手动start()
  4. 不要重复连接信号,否则一次 timeout 触发多次函数。

七、组(group)

1.定义

Godot 的Group = 全局标签 / 分类

  • 一个节点可以加多个组
  • 不依赖父子层级、不依赖类型
  • 全场景、跨场景都能查到

玩家加入"player"组 = 告诉引擎:这个节点是玩家,全世界都能快速找到我

2.为什么一定要把玩家加入组?

①. 快速 “找到玩家”

任何敌人、陷阱、系统都需要知道玩家在哪、玩家是谁

不用组:

# 硬找、容易错、换场景就崩 var player = get_node("/root/Player")

用组:

# 安全、简单、跨场景也能用 var players = get_tree().get_nodes_in_group("player") if players.size() > 0: var player = players[0]

一句话:组 = 全局快速索引

②. 批量发消息(全局事件)

比如:全屏震动、所有敌人发现玩家、游戏暂停

# 给所有玩家发“受伤” get_tree().call_group("player", "take_damage", 1) # 给所有玩家发“暂停” get_tree().call_group("player", "set_paused", true)

一句话:组 = 广播器,不用一个个连信号

③. 解耦合(代码不写死、好维护)

代码敌人脚本不需要硬引用玩家节点,只认组:

# 敌人AI:找玩家 func _find_player(): var players = get_tree().get_nodes_in_group("player") if players: target = players[0]

玩家删了、换了、改名了,敌人代码完全不用改

④. 多玩家 / 多角色兼容(扩展性强)

以后做多人游戏、分身、队友,都加入"player"组,现有逻辑全部自动兼容,不用大改代码。

⑤.身份判断(Area2D、碰撞、触发器必用)

比如你有一个伤害区 / 触发区(Area2D)

不用组:

# 写死节点名,改名字就崩 if body.name == "Player": take_damage()

用组:

if body.is_in_group("player"): take_damage()

一句话:组 = 安全的身份识别标签

八、解析初始化函数

# ====================== 初始化函数(节点加载完成执行) ====================== func _ready(): # 12. 将玩家加入 "player" 组,方便全局管理(比如敌人检测玩家) add_to_group("player") # 13. 新建一个计时器节点 shoot_timer = Timer.new() # 14. 设置计时器等待时间 = 射击冷却时间 shoot_timer.wait_time = shoot_cooldown # 15. 非单次计时:循环触发(按住射击键持续开枪) shoot_timer.one_shot = false # 16. 绑定计时器超时信号:时间到执行 _on_shoot_timer_timeout 函数 shoot_timer.timeout.connect(_on_shoot_timer_timeout) # 17. 将计时器添加为当前节点的子节点 add_child(shoot_timer) # 18. 如果受伤区域存在 if hurtbox: # 19. 绑定受伤区域的碰撞信号:碰到物体执行 _on_hurtbox_body_entered hurtbox.body_entered.connect(_on_hurtbox_body_entered) # 20. 等待0.1秒,再开启射击权限 await get_tree().create_timer(0.1).timeout # 21. 允许射击 can_shoot_after_ready = true

1. .new( )方法

Timer.new () = 创建一个全新的计时器对象

  • Label.new()→ 创建新文字
  • Sprite2D.new()→ 创建新图片
  • Marker2D.new()→ 创建新标记点

也就是创建一个全新的节点对象

下面的add_child(shoot_timer)是必须加的,此行代码表示把shoot_timer这个计时器添加卫当前节点的子节点,如果不添加,Timer就没有被添加,就不会起作用

2.为什么玩家开枪,非要新建一个 Timer 节点?

1.因为需要【按住鼠标 → 自动连续开枪】

必须用计时器来控制【每多少秒打一枪】, 不用计时器,你根本做不出稳定、不卡、不依赖帧率的连射!

如果不用计时器,只能这样写:

func _process(delta): if Input.get_mouse_button_mask() & MOUSE_BUTTON_LEFT: shoot() # 一直开枪

这样就会导致:

  • 电脑性能好 →开枪快到飞起
  • 电脑卡顿 →开枪变得巨慢
  • 一秒钟能射 100 发,完全失控

2.每个独立功能,都需要自己独立的计时器

  • 开枪冷却 → 1 个 Timer
  • 技能冷却 → 1 个 Timer
  • 无敌时间 → 1 个 Timer
  • 换弹时间 → 1 个 Timer

它们时间不一样、逻辑不一样必须分开,不能共用!

3.计时器是 “工具节点”,专门管时间

Godot 里:

  • 想计时 →必须用 Timer
  • 想稳定间隔 →必须用 Timer
  • 想做 CD →必须用 Timer

Timer 就是专门用来干这个的标准工具

4.代码创建 Timer = 更干净、更自动

  • 不在编辑器里占位置
  • 脚本复制到任何角色都能直接用
  • 不需要手动拖节点、设置参数
  • 完全自动化,不易出错
http://www.jsqmd.com/news/983489/

相关文章:

  • 2026 湖州装修公司推荐:靠谱口碑、性价比、环保整装、排屋别墅装修与报价指南 - GrowthUME
  • [实战] 2026年数字化环境下的QC七大工具应用:从工程图纸到检验计划优化
  • 源头厂家直供|伺服电动缸、伺服压力机、安全光栅 一站式自动化设备解决方案 - GrowthUME
  • sqli-labs解题思路(Less-1到Less-11)
  • Pulseaudio进阶开发之ALSA两种播放方案(二十九)
  • 对比实测|湘潭好吃的麻辣烫推荐,老牌vs新晋,谁才是真顶流? - 信息热点
  • “给钱都不坐!”训练特斯拉FSD的人曝内幕:9人受访7人拒乘,“千万别信马斯克”
  • 终极指南:如何免费激活Beyond Compare 5 - 完整密钥生成教程
  • WeChatMsg深度解析:从数据提取到个人AI记忆库的技术实现
  • 苏州鑫鑫迷你仓|苏州短期仓库灵活租赁,日租月租按需寄存 - GrowthUME
  • MIT Cheetah 3 的 MPC 控制器实战:如何用凸优化搞定四足机器人的复杂步态?
  • 1-17串锂电池保护板设计包:SH367309+STM32F030硬件方案+RS485上位机调试工具
  • NXP Kinetis K40系列MCU实战解析:Cortex-M4内核、低功耗与高集成度设计
  • SLAM 岗位 C++ 面试速查手册
  • CSP-J 2022 初赛补全代码题解析
  • 终极指南:让macOS原生支持MKV、AVI等视频格式预览
  • NJU OS 调试 C 标准库
  • 团队邮件协作效率低?你可能忽略了这三个关键功能
  • ppt模板_0082_灰绿圆圈
  • 从电商实时数仓到风控预警:3个真实案例拆解Flink在事件驱动场景下的落地实践
  • 智科 深度学习毕业设计选题技巧
  • 毕业季-为什么别人的文档长那样,我复制过来样式就全乱了?
  • 光学实验室必备技能:离线环境下用MetroPro和命令行生成Zemax兼容的zxg文件
  • SSHFS-Win完整指南:Windows与Linux服务器间无缝文件访问的终极解决方案
  • 计算机网络(4) -- http协议
  • 用树莓派4B搭建Matter智能家居中枢:从刷写Ubuntu Server到运行chip-tool全记录
  • 护网必学日志分析
  • C++学习实例:杨辉三角
  • MD5哈希算法:原理、应用与安全性解析
  • Kinetis K64引脚配置与选型实战:从数据手册到硬件设计