首页/新闻资讯/正文详情

C#推箱子游戏源码解析:WinForms实现与核心逻辑

发布时间:2026/11/5 18:45:45 来源:云帆数科 栏目:资讯中心
C#推箱子游戏源码解析:WinForms实现与核心逻辑
简介这是一份面向C#初学者与游戏编程爱好者的推箱子小游戏完整源码工程基于WinForms实现可用于课程设计参考、C#桌面开发练手或小游戏二次开发。压缩包共76个文件约135KB包含10个cs源文件、4个resx与resources资源文件、11个bmp位图素材、13个dat与15个way关卡及通关记录文件另有sln解决方案、csproj工程文件与可执行程序结构完整可直接编译运行。已有964人学习下载。代码实现了方向键推箱、CtrlZ撤销与CtrlY重做、选关、当前状态存盘读盘通关后自动将最佳步数保存为level.way文件可回放演示整局推箱过程或分享给朋友同时支持自行设计关卡。自动寻路、鼠标操作与通关光荣榜等功能留有扩展空间适合读者在此基础上练习深度优先搜索与栈的应用理解游戏状态管理与文件持久化的实现思路。1. 从一份能跑起来的 C# 推箱子源码说起很多人学 C# 卡在“语法都会、项目不会”这一步窗体拖控件能跑真给一个完整小游戏却不知道从哪读起。这份 C# 推箱子小游戏源代码就是为这个场景准备的——它把地图数据、角色移动、箱子推动判定、关卡切换、胜负判断这些游戏里最基础也最容易写乱的部分压缩在一个 WinForms 工程里。适合刚学完 C# 基础、想找一个能完整跑通并动手改的练手项目的开发者也适合带课的老师拿它当课堂案例。下面按“先看清结构、再跑起来、最后动手改”的顺序拆。2. 工程结构与技术选型一个 WinForms 推箱子是怎么搭起来的2.1 技术栈与运行环境这个项目是典型的 .NET Framework WinForms 桌面程序没有引入任何第三方游戏引擎或 UI 库。选型逻辑很直接推箱子是网格回合制游戏不需要实时渲染循环用 GDI 画格子完全够用窗体控件负责菜单和状态栏省掉引擎学习成本。项目说明语言C#UI 框架WinFormsSystem.Windows.Forms图形绘制GDIGraphics、Bitmap、Pen、Brush目标框架.NET Framework 4.x常见做法是 4.7.2 或 4.8开发工具Visual Studio 2019/2022装“.NET 桌面开发”工作负载数据存储关卡以文本/字符串数组形式内置或读取外部 txt如果你机器上只有 .NET SDK 而没有 Visual Studio用dotnet命令行跑 WinForms 会麻烦一些因为老式 WinForms 工程文件是.csproj的旧格式。我一般建议直接装 Visual Studio 社区版打开.sln就能编译省去折腾。2.2 目录与文件职责一个干净的推箱子工程通常长这样你拿到源码后先对照这个结构认文件Sokoban/ ├── Sokoban.sln ├── Sokoban/ │ ├── Program.cs // 程序入口Application.Run │ ├── MainForm.cs // 主窗体绘制、键盘事件、关卡切换 │ ├── MainForm.Designer.cs// 设计器生成的控件代码 │ ├── GameMap.cs // 地图数据与格子类型定义 │ ├── GameLogic.cs // 移动、推箱、胜负判定 │ ├── Levels.cs // 关卡数据字符串数组或读取文件 │ └── Resources/ // 图标、背景图等职责划分的关键在于把“数据”和“规则”从窗体里拆出来。窗体只负责把地图画出来、把按键转成方向真正的移动和判定放在GameLogic里。这样你改规则时不用碰 UI 代码测试逻辑也方便。2.3 地图数据模型推箱子的地图本质是一个二维字符网格每个格子有固定类型。常见做法是用字符表示读入后转成枚举// GameMap.cs public enum TileType { Floor, // 空地 Wall, // 墙 Goal, // 目标点 Box, // 箱子 BoxOnGoal, // 箱子已在目标点上 Player // 玩家 } public class GameMap { public int Rows { get; private set; } public int Cols { get; private set; } public TileType[,] Tiles { get; private set; } // 用字符数组初始化地图 public void Load(string[] lines) { Rows lines.Length; Cols lines[0].Length; Tiles new TileType[Rows, Cols]; for (int r 0; r Rows; r) { for (int c 0; c Cols; c) { char ch lines[r][c]; switch (ch) { case #: Tiles[r, c] TileType.Wall; break; case .: Tiles[r, c] TileType.Goal; break; case $: Tiles[r, c] TileType.Box; break; case *: Tiles[r, c] TileType.BoxOnGoal; break; case : Tiles[r, c] TileType.Player; break; default: Tiles[r, c] TileType.Floor; break; } } } } }这里#是墙、.是目标点、$是箱子、*是已在目标点上的箱子、是玩家这是推箱子地图的通用字符约定。Load方法把字符逐格翻译成枚举后续所有逻辑都基于Tiles[r, c]判断不再碰原始字符。注意Cols取第一行长度要求所有行等长关卡数据里如果有短行要先用空格补齐否则会数组越界。2.4 窗体绘制把二维数组画到屏幕上WinForms 里画格子最稳的方式是重写OnPaint用Graphics.FillRectangle逐格填充。不要用一堆 Label 控件拼地图格子一多控件刷新会卡而且坐标计算更乱。// MainForm.cs private const int TileSize 40; // 每格像素 protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); Graphics g e.Graphics; g.SmoothingMode System.Drawing.Drawing2D.SmoothingMode.None; for (int r 0; r map.Rows; r) { for (int c 0; c map.Cols; c) { Rectangle rect new Rectangle(c * TileSize, r * TileSize, TileSize, TileSize); switch (map.Tiles[r, c]) { case TileType.Wall: g.FillRectangle(Brushes.DimGray, rect); break; case TileType.Floor: g.FillRectangle(Brushes.WhiteSmoke, rect); break; case TileType.Goal: g.FillRectangle(Brushes.WhiteSmoke, rect); g.DrawEllipse(Pens.Red, rect.X 12, rect.Y 12, 16, 16); break; case TileType.Box: g.FillRectangle(Brushes.SaddleBrown, rect); break; case TileType.BoxOnGoal: g.FillRectangle(Brushes.Green, rect); break; case TileType.Player: g.FillEllipse(Brushes.SteelBlue, rect); break; } } } }TileSize决定格子像素大小改大改小只影响显示不影响逻辑。每种格子用不同画法区分墙用深灰实心块、目标点画红圈、箱子用棕色方块、箱子在目标点上变绿、玩家画蓝圆。OnPaint每次重绘都从map.Tiles重新读所以逻辑层改完数据后只要调Invalidate()就能刷新画面这是 WinForms 里最省心的刷新方式。2.5 键盘输入与移动入口推箱子的操作只有上下左右用KeyDown事件最直接。注意窗体要能接收键盘事件得保证KeyPreview true否则焦点在子控件上时按键收不到。// MainForm.cs private void MainForm_KeyDown(object sender, KeyEventArgs e) { int dr 0, dc 0; switch (e.KeyCode) { case Keys.Up: dr -1; break; case Keys.Down: dr 1; break; case Keys.Left: dc -1; break; case Keys.Right: dc 1; break; default: return; // 其他键不处理 } if (logic.TryMove(dr, dc)) { Invalidate(); // 重绘 if (logic.IsWin()) MessageBox.Show(过关); } }dr、dc是行、列方向增量上移是行减一、左移是列减一这是屏幕坐标系原点在左上角的约定。TryMove返回是否真的移动了没移动就不重绘减少无谓刷新。胜负判断放在移动之后IsWin检查是否所有目标点都被箱子覆盖。3. 核心逻辑拆解一次按键从进入到返回的完整链路3.1 移动与推箱判定推箱子的规则只有三条目标格是墙不能走目标格是空地或目标点直接走目标格是箱子时要看箱子后面那格能不能放箱子。把这三条写成一个方法是整个项目最核心的代码。// GameLogic.cs public bool TryMove(int dr, int dc) { Point player FindPlayer(); int nr player.Row dr; int nc player.Col dc; if (!InBounds(nr, nc)) return false; TileType target map.Tiles[nr, nc]; // 情况一撞墙 if (target TileType.Wall) return false; // 情况二空地或目标点直接走 if (target TileType.Floor || target TileType.Goal) { MovePlayer(player, new Point(nr, nc)); return true; } // 情况三前面是箱子检查箱子后面 if (target TileType.Box || target TileType.BoxOnGoal) { int br nr dr; int bc nc dc; if (!InBounds(br, bc)) return false; TileType behind map.Tiles[br, bc]; // 箱子后面必须是空地或目标点 if (behind ! TileType.Floor behind ! TileType.Goal) return false; // 先推箱子再移动玩家 map.Tiles[br, bc] (behind TileType.Goal) ? TileType.BoxOnGoal : TileType.Box; map.Tiles[nr, nc] TileType.Floor; // 箱子原位置变空地 MovePlayer(player, new Point(nr, nc)); return true; } return false; }逐段看FindPlayer遍历找Player格返回坐标InBounds防越界target是玩家要去的格子。撞墙直接返回 false。空地/目标点直接调用MovePlayer。关键是箱子分支先算箱子后面一格behind只有空地或目标点才能推否则返回 false。推成功时先把箱子写到新位置如果新位置是目标点就记BoxOnGoal再把箱子原位置改成Floor最后移动玩家。顺序不能反否则玩家会覆盖箱子数据。MovePlayer负责把旧位置还原成Floor或Goal这里有个容易翻车的点玩家站过的格子如果原本是目标点离开后要还原成Goal而不是Floor否则目标点会“消失”。常见做法是单独维护一份“原始地形”数组或者移动前记录旧格类型。3.2 胜负判定与关卡切换胜负判定很简单遍历所有格子只要还存在Box没在目标点上的箱子就没赢全是BoxOnGoal或没有箱子就过关。// GameLogic.cs public bool IsWin() { for (int r 0; r map.Rows; r) for (int c 0; c map.Cols; c) if (map.Tiles[r, c] TileType.Box) return false; // 还有箱子没归位 return true; }注意这里只检查Box不检查BoxOnGoal因为BoxOnGoal表示已归位。如果地图里箱子数比目标点多这个判定会误判所以关卡数据要保证箱子数等于目标点数这是设计关卡时的硬约束。关卡切换一般用Levels.cs里的字符串数组CurrentLevel索引加一后重新Load并Invalidate。我一般会在切换前把当前关卡状态存一份方便做“重玩本关”。3.3 撤销功能的数据结构推箱子没有撤销会很难受尤其是复杂关卡。撤销的本质是记录每一步的状态快照。最省事的做法是每次移动前把整个Tiles数组深拷贝一份压栈。// GameLogic.cs private StackTileType[,] history new StackTileType[,](); public void SaveState() { int rows map.Rows, cols map.Cols; var snapshot new TileType[rows, cols]; Array.Copy(map.Tiles, snapshot, map.Tiles.Length); history.Push(snapshot); } public bool Undo() { if (history.Count 0) return false; map.Tiles history.Pop(); return true; }SaveState在每次TryMove成功前调用Array.Copy做浅拷贝对值类型枚举数组来说等价于深拷贝够用。Undo弹栈恢复。这种方案内存占用随步数增长但推箱子单关步数有限完全可接受。如果关卡特别大可以改成只记录“玩家位置 被推动的箱子位置”的增量日志但那是优化不是必需。3.4 关卡数据的组织方式关卡数据建议单独放不要硬编码在窗体里。两种常见做法一是Levels.cs里用string[][]内置二是放levels.txt用空行分隔关卡运行时读文件。// Levels.cs public static class Levels { public static readonly string[][] All new string[][] { new string[] { #####, #$.#, ##### }, new string[] { #######, # . #, # $$ #, # . #, ####### } }; }第一关是最小可玩示例玩家、箱子$、目标点.在同一行按一次右键就过关用来验证逻辑通不通。第二关有四个目标点和两个箱子用来测多箱子场景。注意字符串里的空格代表FloorLoad的default分支会处理成空地所以关卡里可以用空格排版可读性好。4. 跑起来与调试从打开解决方案到第一关通关4.1 编译运行步骤拿到源码后按这个顺序走基本不会卡用 Visual Studio 打开Sokoban.sln不要单独打开.cs文件。确认解决方案资源管理器里项目图标没有黄色感叹号有的话右键“还原 NuGet 包”虽然这个项目通常没有外部包。顶部工具栏把“启动项目”设为Sokoban解决方案平台选Any CPU。按F5调试运行或CtrlF5不调试运行。窗体出来后用方向键移动撞墙不动、推箱有反馈、全部归位弹提示说明跑通了。如果按 F5 报“无法启动程序”多半是输出类型不是 Windows 应用程序。右键项目 → 属性 → 应用程序 → 输出类型选“Windows 应用程序”再检查“启动对象”是不是Sokoban.Program。4.2 常见编译错误对照现象原因解决找不到Main方法启动对象没设或Program.cs被排除项目属性里设启动对象检查文件是否“从项目中排除”MainForm设计器打不开Designer.cs里控件初始化顺序被改乱用“查看代码”手改或从备份恢复 Designer 文件按键没反应窗体KeyPreview为 false 或焦点在按钮上设KeyPreview true或在KeyDown里处理画面不刷新改了数据没调Invalidate()每次逻辑变更后调Invalidate()数组越界关卡行长度不一致补齐短行或Load里按最大行宽补空格4.3 用调试器看一次移动想真正理解逻辑建议在TryMove里下断点按一次方向键单步看player、nr/nc、target、behind四个变量的变化。特别是推箱分支观察map.Tiles[br, bc]和map.Tiles[nr, nc]的赋值顺序。这一步比读十遍代码都管用也是我带人看这个项目时必做的一步。5. 避坑与排查推箱子源码里最容易翻车的几处5.1 玩家离开目标点后目标点消失现象玩家从目标点上走开那个格子变成普通空地红圈没了导致关卡永远无法通关。原因MovePlayer把旧位置一律写成Floor没判断旧格原本是不是Goal。解决移动前记录旧格类型离开时若是Goal就还原成Goal或者维护一份独立的Terrain数组只存墙/空地/目标点Tiles只叠加动态物体。5.2 箱子被推到墙角后关卡死锁现象箱子被推进角落再也推不出来玩家只能重开。原因这是推箱子的固有规则不是 bug但新手常以为是程序问题。解决在TryMove推箱前加一个可选的“死锁检测”——箱子被推到两面墙夹角且不在目标点上时给提示或禁止该步。简单版只检测角落复杂版要检测整行整列按需做。5.3 连续按键导致移动错乱现象快速连按方向键偶尔出现玩家“穿墙”或箱子重叠。原因KeyDown事件在 UI 线程排队如果TryMove里有耗时操作或重绘没完成状态可能不一致。解决保证TryMove是纯同步、无阻塞的重绘用Invalidate()而不是Refresh()让系统合并刷新必要时在移动入口加一个简单的重入保护标志。5.4 关卡文件读取时中文路径或编码问题现象从levels.txt读关卡某些机器上读出来是乱码或空。原因File.ReadAllLines默认按 UTF-8 读文件若是 GBK 保存就会乱。解决统一用 UTF-8 保存关卡文件或显式传Encoding.Default路径用Path.Combine(AppDomain.CurrentDomain.BaseDirectory, levels.txt)不要写死绝对路径。5.5 窗体缩放后格子错位现象拉大窗口后地图还挤在左上角或者格子被拉伸变形。原因OnPaint里格子坐标按固定TileSize算没考虑窗体实际尺寸。解决要么禁止调整窗体大小FormBorderStyle FixedSingle、MaximizeBox false要么在OnPaint里根据ClientSize动态算TileSize Math.Min(width / Cols, height / Rows)并居中偏移。6. 二次开发把练手项目改成你自己的版本这个项目最大的价值不是“能玩”而是结构足够小改起来没有心理负担。我一般会从三个方向动手按难度递增。第一个方向是换皮和加关卡。把OnPaint里的FillRectangle换成DrawImage加载几张 png 当墙、箱子、目标点贴图TileSize调到 48 或 64观感立刻不一样。关卡数据加到Levels.All里或者改成读外部文件配合一个简单的关卡选择列表ListBox绑定关卡名。这一步不改逻辑适合先熟悉工程。第二个方向是加功能。撤销已经讲了再往下可以加步数统计和计时在TryMove成功时stepCount窗体上放个Label显示计时用System.Windows.Forms.Timer每秒Tick一次更新。还可以加“重玩本关”本质是重新Load当前关卡并清空history。这些功能都是围绕现有数据结构加外围不会动核心判定。第三个方向是重构逻辑层让它可测试。把GameLogic从窗体依赖里彻底剥离构造函数只接收GameMap所有方法不碰Graphics和MessageBox。然后新建一个控制台测试项目直接new GameLogic(map)调TryMove断言map.Tiles的变化。这样你改推箱规则时能立刻验证有没有破坏原有行为比每次开窗体手点靠谱得多。// 重构后的逻辑层签名示例 public class GameLogic { private readonly GameMap map; public GameLogic(GameMap map) { this.map map; } public bool TryMove(int dr, int dc) { /* 纯逻辑无 UI 依赖 */ } public bool IsWin() { /* ... */ } public void SaveState() { /* ... */ } public bool Undo() { /* ... */ } }改造时有个习惯我一直保持每加一个功能先在逻辑层写一个最小验证哪怕只是Debug.Assert再接到 UI 上。推箱子这种规则密集的小游戏逻辑错一步整个关卡就废靠肉眼点很容易漏。从那以后我每次改移动或推箱相关代码都强制先跑一遍“最小关卡 单步断言”再开窗体手玩确认。希望这份拆解能帮你把这个小工程真正跑起来、改下去。本文还有配套的精品资源点击获取

相关推荐

README.md 文件的核心价值与实战模板分享
README.md 文件的核心价值与实战模板分享

1. 项目概述:README.md 文件的核心价值每个项目都该有个好README——这话我从业十年说了不下百次。上周帮团队Review代码库时又发现三个项目只有空荡荡的README模板,这种场景实在太熟悉了。README.md远不止是个说明文件,它是项目的门面、开发… · 2026/11/5 1:46:54

FastSVDD:MATLAB加速SVDD异常检测训练实战
FastSVDD:MATLAB加速SVDD异常检测训练实战

简介:FastSVDD 是支持向量数据描述(SVDD)算法的一种高效实现,面向从事异常检测、单类分类与故障诊断等方向的研究人员和工程师,尤其适合希望在 MATLAB 环境中快速验证模型、降低训练计算成本的中高级学习者。资源包共 … · 2026/11/5 2:03:54

FastSVDD实战:MATLAB加速单类异常检测与工业质检应用
FastSVDD实战:MATLAB加速单类异常检测与工业质检应用

简介:FastSVDD 是支持向量数据描述(SVDD)算法的一种高效 MATLAB 实现,面向从事异常检测、单类分类与故障诊断等方向的研究人员和工程实践者,帮助其在保持模型性能的前提下降低计算复杂度、提升训练与推理效率。资源包共… · 2026/11/5 11:15:39

EasyU优启通PE维护盘实战指南:制作、启动与核心操作全解析
EasyU优启通PE维护盘实战指南:制作、启动与核心操作全解析

1. 为什么一个PE维护盘值得单独拿出来聊装系统、修引导、救数据、清病毒,这些活儿但凡干过几次的人都知道,真正决定效率的往往不是你手速多快,而是手边那套PE环境靠不靠谱。EasyU(圈内习惯叫优启通)就是在这个场景下被… · 2026/11/5 16:49:35

Rust原来很简单二:所有权系统
Rust原来很简单二:所有权系统

Part 2 核心篇 —— 所有权系统 本篇覆盖 第 6 ~ 10 章:所有权、借用与引用、生命周期、字符串与切片、内存模型。 ⚠️ 这是全书最重要的一篇。 把它吃透,Rust 从此“很简单”;跳过它,你会处处碰壁。请慢下来,动手敲每一段代码。 第 6 章 所有权:Rust 的第一性原理 6… · 2026/11/5 16:26:08

基于Hadoop+Spark的民宿智能推荐系统设计与实现
基于Hadoop+Spark的民宿智能推荐系统设计与实现

1. 项目概述:大数据时代的民宿智能推荐这个毕业设计项目融合了当前大数据领域最主流的技术栈,构建了一个完整的民宿推荐系统解决方案。作为一名长期从事大数据系统开发的工程师,我认为这个选题非常贴合实际应用场景——随着旅游业的复苏和共享… · 2026/11/5 14:59:25

ADS122U04IPWR+STM32F427ZI工业级温度测量信号链设计
ADS122U04IPWR+STM32F427ZI工业级温度测量信号链设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 14:10:41

UART接口24位ADC搭配RS485,解决远距离温度监控难题
UART接口24位ADC搭配RS485,解决远距离温度监控难题

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 14:10:41

UART接口24位ADC搭配PIC18F46K22的RTD与热电偶测温实践
UART接口24位ADC搭配PIC18F46K22的RTD与热电偶测温实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 14:10:41

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:27:59

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:28:03

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:28:07

多智能体协作系统架构设计与实践优化
多智能体协作系统架构设计与实践优化

1. 多智能体协作系统的时代背景2017年Transformer架构的诞生彻底改变了人工智能的发展轨迹。当行业还沉浸在单一大模型带来的性能突破时,Anthropic的研究团队已经将目光投向了更前沿的领域——如何让多个智能体通过协作产生超越单体能力的系统智能。这就像交响乐团中… · 2026/11/5 0:00:39

大模型系统性入门实战指南:从原理到部署微调的全链路路线
大模型系统性入门实战指南:从原理到部署微调的全链路路线

提到大模型的系统性入门,我先说个结论:这事真不难,难的是资料太散、路线太多,今天这个框架明天那个工具,新人很容易在“收藏夹吃灰”和“教程看到一半放弃”之间反复横跳。市面上关于大模型的内容少说有几十万篇&#… · 2026/11/5 0:00:39

鸿蒙文件系统沙箱机制解析与开发实践
鸿蒙文件系统沙箱机制解析与开发实践

1. 鸿蒙文件系统沙箱机制深度解析作为一名从Android转向鸿蒙开发的工程师,我深刻体会到文件系统访问是迁移过程中最先遇到的"拦路虎"。在Android上运行良好的文件读写代码,在鸿蒙真机环境却频频出现文件写入后神秘消失或直接抛出权限拒绝错误的… · 2026/11/5 0:00:39

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:27:59

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:28:03

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:28:07

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码