问题

你想创建一个2D菜单界面,让你可以容易地添加新的菜单和指定它们的菜单选项。这个菜单允许用户使用控制器/键盘切换不同的选项和菜单,当用户从一个菜单切换到另一个菜单时还可以定义漂亮的过渡效果。

解决方案

你将创建一个新的类,MenuWindow,这个类保存所有与菜单相关的东西,诸如菜单的当前状态,菜单项,背景图像等。这个类让主程序可以容易地创建多个MenuWindow实例并将菜单项添加到这些实例中。菜单项可以使用你的系统中安装的字体用2D文字的方式显示。

要实现过渡效果,一个窗口将拥有Starting、Ending、Active和Inactive状态。controller/keyboard状态会被传递到Active MenuWindow,可以通过传递选择的菜单让主程序知道用户是否选择了一个菜单项。

让MenuWindow可以存储和显示背景图像可以增强最终效果,这还可以使用后期处理效果加以改进(见教程2-12)。

工作原理

主程序会创建几个MenuWindow对象,每个菜单项都会链接到另一个MenuWindow对象。所以首先定义一个MenuWindow类。 MenuWindow类创建一个叫做MenuWindow的新类。每个菜单能够存储它的菜单项。对每个菜单项,必须存储文字和指向的菜单。所以,在MenuWindow类中定义下述结构:

private struct MenuItem
{public string itemText; public MenuWindow itemLink; public MenuItem(string itemText, MenuWindow itemLink) {this.itemText = itemText; this.itemLink = itemLink; }
} 

每个菜单总是处于下面四个状态之一:

  • Starting:菜单刚刚被选择,正在淡入。
  • Active:此菜单为屏幕上显示的唯一一个菜单并处理用户输入。
  • Ending:此菜单中的一个菜单项被选择,因此它正在淡出。
  • Inactive:如果此菜单不在前面三个状态之中,那么它不被绘制。

所以你需要一个枚举表示状态,枚举应放在类的外部:

public enum WindowState
{Starting, Active, Ending, Inactive
} 

然后是使类正常工作所需的变量:

private TimeSpan changeSpan;
private WindowState windowState;
private List<MenuItem> itemList;
private int selectedItem;
private SpriteFont spriteFont;
private double changeProgress;

changeSpan表示淡入淡出持续的时间。然后你需要一些变量保存菜单的当前状态、菜单项的集合和当前选择的菜单项。变量changeProgress保存一个介于0和1之间的值表示在Starting或Ending状态时淡入淡出处在过程的何处。

构造函数只是简单地初始化这些变量:

public MenuWindow(SpriteFont spriteFont)
{itemList = new List<MenuItem>(); changeSpan = TimeSpan.FromMilliseconds(800); selectedItem = 0; changeProgress = 0; windowState = WindowState.Inactive; this.spriteFont = spriteFont;
} 

你指定了两个菜单间的过渡持续800毫秒的时间,菜单开始时的状态为Inactive。你可以在前一个教程中学到SpriteFont类和如何绘制文字。

然后你需要一个方法添加菜单项:

public void AddMenuItem(string itemText, MenuWindow itemLink)
{MenuItem newItem = new MenuItem(itemText, itemLink); itemList.Add(newItem);
} 

当用户选择菜单项时,菜单项上的文字和菜单需要被激活,被主程序传递。一个新的菜单项被创建并被添加到itemList中。你还需要一个方法激活一个Inactive菜单:

public void WakeUp()
{windowState = WindowState.Starting;
} 

像XNA程序中的大多数组件一样。这个类需要被更新:

public void Update(double timePassedSinceLastFrame)
{if ((windowState == WindowState.Starting) || (windowState == WindowState.Ending)) changeProgress += timePassedSinceLastFrame / changeSpan.TotalMilliseconds; if (changeProgress >= 1.0f) {changeProgress = 0.0f; if (windowState == WindowState.Starting) windowState = WindowState.Active; else if (windowState == WindowState.Ending) windowState = WindowState.Inactive; }
} 

这个方法接受上一个update调用以来经历的毫秒数为参数(通常这个参数为1000/60 毫秒,见教程1-5)。如果菜单正在过渡模式中,变量changeProgress进行更新,导致在经过了存储在changeSpan (800,前面你已经定义了)中的毫秒后,这个值达到1。

当这个值达到1,过渡结束,状态要么从Starting变换到Active,要么从Ending变换为Inactive。

最后,你需要一些代码绘制菜单。当菜单处于Active状态时,必须显示菜单项,例如从位置(300,300)开始,每个菜单项位于前一个之下30个像素。

当菜单在Starting状态时,菜单项应该淡入(它们的alpha值应该从0增加到1)并且从屏幕的左边移动至最终的位置。如果处于Ending状态,菜单项应该淡出 (它们的alpha值应该减少)并且移动到右方。

public void Draw(SpriteBatch spriteBatch)
{if (windowState == WindowState.Inactive) return; float smoothedProgress = MathHelper.SmoothStep(0,1,(float)changeProgress); int verPosition = 300; float horPosition = 300; float alphaValue; switch (windowState) {case WindowState.Starting: horPosition -= 200 * (1.0f - (float)smoothedProgress); alphaValue = smoothedProgress; break; case WindowState.Ending: horPosition += 200 * (float)smoothedProgress; alphaValue = 1.0f - smoothedProgress; break;default: alphaValue = 1; break; }for (int itemID = 0; itemID < itemList.Count; itemID++) {Vector2 itemPostition = new Vector2(horPosition, verPosition); Color itemColor = Color.White; if (itemID == selectedItem) itemColor = new Color(new Vector4(1,0,0,alphaValue)); else itemColor = new Color(new Vector4(1,1,1,alphaValue)); spriteBatch.DrawString(spriteFont, itemList[itemID].itemText, itemPostition, itemColor, 0, Vector2.Zero, 1, SpriteEffects.None, 0); verPosition += 30; }
} 

当处于Starting或Ending状态时,changeProgress值会线性地从0增加到1,它工作正常但无法在开始或结束时产生平滑的效果。MathHelper. SmoothStep方法平滑曲线,让开始和结束时都能平滑过渡。当菜单处于Starting或Ending状态时,case结构中调整菜单项的水平位置和alpha值。然后,对每个菜单项,其上的文字被正确地绘制到屏幕上。绘制文字更多的信息可见前一个教程。如果菜单项没有被选择,文字是白色的,当选择时变为红色。

以上就是MenuWindow类的基础!

在主程序中,你只需一个集合存储所有的菜单:

List<MenuWindow> menuList; 

在LoadContent方法中,你可以创建菜单并将它们添加到menuList中。然后,你可以将菜单项添加到菜单中,让你可以在用户选择菜单项时指定激活哪个菜单。

MenuWindow menuMain = new MenuWindow(menuFont, "Main Menu", backgroundImage);
MenuWindow menuNewGame = new MenuWindow(menuFont, "Start a New Game", bg);
menuList.Add(menuMain);
menuList.Add(menuNewGame);
menuMain.AddMenuItem("New Game", menuNewGame);
menuNewGame.AddMenuItem("Back to Main menu", menuMain);
menuMain.WakeUp();

以上操作创建了两个菜单,每个菜单包含一个链接到另一个菜单的菜单项。初始化菜单结构后,激活mainMenu,使它处于Starting状态。现在,你需要在程序更新循环中更新所有菜单:

foreach (MenuWindow currentMenu in menuList) currentMenu.Update(gameTime.ElapsedGameTime.TotalMilliseconds); 

并在Draw方法中绘制菜单:

spriteBatch.Begin(); foreach (MenuWindow currentMenu in menuList) currentMenu.Draw(spriteBatch);
spriteBatch.End(); 

当运行代码时,主菜单会从左侧淡入,因为你还没有处理用户输入,所以无法切换到其他菜单。

允许用户通过菜单项导航

你将在MenuWindow类中添加一个方法处理用户输入。注意这个方法只能被当前激活的菜单调用:

public MenuWindow ProcessInput(KeyboardState lastKeybState, KeyboardState currentKeybState)
{if (lastKeybState.IsKeyUp(Keys.Down) && currentKeybState.IsKeyDown(Keys.Down)) selectedItem++; if (lastKeybState.IsKeyUp(Keys.Up) && currentKeybState.IsKeyDown(Keys.Up)) selectedItem--; if (selectedItem < 0) selectedItem = 0; if (selectedItem >= itemList.Count) selectedItem = itemList.Count-1; if (lastKeybState.IsKeyUp(Keys.Enter) && currentKeybState.IsKeyDown(Keys.Enter)) { windowState = WindowState.Ending; return itemList[selectedItem].itemLink; }else if (lastKeybState.IsKeyDown(Keys.Escape)) return null; elsereturn this;
} 

这里有许多有趣的东西。首先,你检查up或down键是否被按下。当用户按下一个键时,只要这个键一直被按着,那么这个键的IsKeyUp为true!因此,你需要检查上一帧这个键是否已经被按下。

如果up或down键被按下,你需要对应地改变selectedItem变量。如果超出边界,需要将它返回到一个合理的位置。

接下去的代码包含整个导航逻辑。你应该注意到这个方法需要返回一个MenuWindow对象到主程序中。因为这个方法只会被当前激活的菜单调用,这允许当前菜单将新选择的菜单返回到主程序。如果用户没有选择任何菜单项,当前菜单会保持激活并返回自己,这就是最后一行代码的操作。通过这种方式,主菜单在处理输入后知道哪个菜单是激活菜单。

当用户按下Enter键后,当前激活菜单会从Active转到Ending状态,被选择菜单项链接的菜单被返回到主程序。如果用户按下Escape键,返回null,这回被后面的退出程序捕捉到。如果什么都没选,返回当前菜单自身,告知主程序当前菜单仍保持激活。

这个方法需要从主程序调用,主程序需要两个变量:

MenuWindow activeMenu;
KeyboardState lastKeybState; 

第一个变量保存当前激活的菜单,在LoadContent 方法中进行初始化。lastKeybState 变量在Initialize方法中进行初始化。

private void MenuInput(KeyboardState currentKeybState)
{MenuWindow newActive = activeMenu.ProcessInput(lastKeybState, currentKeybState); if (newActive == null) this.Exit(); else if (newActive != activeMenu) newActive.WakeUp(); activeMenu = newActive;
} 

这个方法调用当前激活菜单的ProcessInput方法,并传递前一个和当前的键盘状态。如前所述,如果用户按下Escape键这个方法会返回null,所以在这种情况中,应用程序会退出。否则,如果这个方法返回一个不同于当前菜单的菜单,说明用户做出了一个选择。在这种情况中,新选择的菜单会通过调用它的WakeUp方法从Inactive变化到Starting状态。如果两种情况都不是,则返回当前菜单,存储在activeMenu变量中。

请确保在Update方法内部调用这个方法。运行这个代码让你可以在两个菜单间自由切换。

在菜单中添加标题和背景图像

菜单现在已经可以正常工作了,但没有背景图片菜单还不够完美。在MenuWindow类中添加两个变量:

private string menuTitle;
private Texture2D backgroundImage; 

这两个变量需要在Initialize方法中赋值:

public MenuWindow(SpriteFont spriteFont, string menuTitle, Texture2D backgroundImage)
{//... this.menuTitle = menuTitle; this.backgroundImage = backgroundImage;
}

显示标题很简单。但是,如果菜单使用不同的背景图片,那么在绘制背景图片时会遇到些麻烦。你想要的是在Active和Ending状态下显示图片。当处于Starting状态时,新的背景会混合在前一个的上面。当在第一张图片上混合第二张图片时,你想要保证第一张图像首先被绘制!这并不容易,因为这涉及到改变菜单的绘制顺序。

一个简单的方法是使用SpriteBatch . Draw方法的layerDepth参数(见教程3-3)。当处于Active或Ending状态时,背景图像会在距离1绘制,即“最深的”层。在Starting模式中,图像会在距离0.5绘制,所有文字会在距离0绘制。当使用SpriteSortMode. BackToFront时,首先在深度1的Active或Ending菜单会被绘制。然后,Starting菜单被绘制(混合在已经存在的图像上),最后绘制所有文字。

在MenuWindow的Draw方法中,创建两个变量:

float bgLayerDepth;
Color bgColor;

这两个变量保存背景图像的layerDepth和透明颜色值,在switch中设置这两个变量:

switch (windowState)
{case WindowState.Starting: horPosition -= 200 * (1.0f - (float)smoothedProgress); alphaValue = smoothedProgress; bgLayerDepth = 0.5f; bgColor = new Color(new Vector4(1, 1, 1, alphaValue)); break; case WindowState.Ending: horPosition += 200 * (float)smoothedProgress; alphaValue = 1.0f - smoothedProgress; bgLayerDepth = 1; bgColor = Color.White; break; default: alphaValue = 1; bgLayerDepth = 1; bgColor = Color.White; break;
} 

Color. White与Color(new Vector4(1, 1, 1, 1))相同,表示完全alpha值。如果一个菜单处于Starting或Ending状态,会计算alphaValue。然后,使用透明颜色值绘制标题和背景图像。

Color titleColor = new Color(new Vector4(1, 1, 1, alphaValue)); spriteBatch.Draw(backgroundImage, new Vector2(), null, bgColor, 0, Vector2.Zero, 1, SpriteEffects.None, bgLayerDepth);
spriteBatch.DrawString(spriteFont, menuTitle, new Vector2(horPosition, 200), titleColor,0,Vector2.Zero, 1.5f, SpriteEffects.None, 0); 

标题文字被缩放到1.5f,比菜单项的文字大。

最后,你需要确保在主程序的Draw方法中将SpriteSortMode设置为BackToFront:

spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.BackToFront, SaveStateMode.None); 
从菜单移动到游戏

至此你创建了一些漂亮的菜单,但如何创建一些菜单项开始游戏?这可以使用dummy 菜单,这个菜单应该存储在主程序中。例如,如果你想在Start New Game菜单中包含start an Easy,a Normal或a Hard game的菜单项,应该将下列代码添加到菜单中:

MenuWindow startGameEasy;
MenuWindow startGameNormal;
MenuWindow startGameHard;
bool menusRunning; 

在LoadContent方法中,你可以使用null参数实例化这些变量并将它们链接到menuNewGame中的菜单项上:

startGameEasy = new MenuWindow(null, null, null);
startGameNormal = new MenuWindow(null, null, null);
startGameHard = new MenuWindow(null, null, null);
menuNewGame.AddMenuItem("Easy", startGameEasy);
menuNewGame.AddMenuItem("Normal", startGameNormal);
menuNewGame.AddMenuItem("Hard", startGameHard);
menuNewGame.AddMenuItem("Back to Main menu", menuMain);

这会在New Game菜单中添加四个菜单项。接下去你要做的就是检测哪一个dummy菜单被选择。因此,需要扩展一下MenuInput方法:

private void MenuInput(KeyboardState currentKeybState)
{MenuWindow newActive = activeMenu.ProcessInput(lastKeybState, currentKeybState); if (newActive == startGameEasy) {//set level to easy menusRunning = false; }else if (newActive == startGameNormal) {//set level to normal menusRunning = false; }else if (newActive == startGameHard) {//set level to hard menusRunning = false; }else if (newActive == null) this.Exit(); else if (newActive != activeMenu) newActive.WakeUp(); activeMenu = newActive;
} 

你可以使用menusRunning变量保证当用户在游戏中时不更新/绘制菜单:

if (menusRunning)
{spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.BackToFront, SaveStateMode.None); foreach (MenuWindow currentMenu in menuList) currentMenu.Draw(spriteBatch); spriteBatch.End(); Window.Title = "Menu running ...";
}
else
{Window.Title = "Game running...";
}
代码
MenuWindow类

下面是简单、必须的方法:

public MenuWindow(SpriteFont spriteFont, string menuTitle, Texture2D backgroundImage)
{itemList = new List<MenuItem>(); changeSpan = TimeSpan.FromMilliseconds(800); selectedItem = 0; changeProgress = 0; windowState = WindowState.Inactive; this.spriteFont = spriteFont; this.menuTitle = menuTitle; this.backgroundImage = backgroundImage;
}public void AddMenuItem(string itemText, MenuWindow itemLink)
{MenuItem newItem = new MenuItem(itemText, itemLink); itemList.Add(newItem);
}public void WakeUp()
{windowState = WindowState.Starting;
}

然后是更新菜单的方法。注意只有当前激活的菜单才会调用ProcessInput方法。

public void Update(double timePassedSinceLastFrame)
{if ((windowState == WindowState.Starting) || (windowState == WindowState.Ending)) changeProgress += timePassedSinceLastFrame / changeSpan.TotalMilliseconds; if (changeProgress >= 1.0f) {changeProgress = 0.0f; if (windowState == WindowState.Starting) windowState = WindowState.Active; else if (windowState == WindowState.Ending) windowState = WindowState.Inactive; }
}public MenuWindow ProcessInput(KeyboardState lastKeybState, KeyboardState currentKeybState)
{if (lastKeybState.IsKeyUp(Keys.Down) && currentKeybState.IsKeyDown(Keys.Down)) selectedItem++; if (lastKeybState.IsKeyUp(Keys.Up) && currentKeybState.IsKeyDown(Keys.Up)) selectedItem--; if (selectedItem < 0) selectedItem = 0; if (selectedItem >= itemList.Count) selectedItem = itemList.Count-1; if (lastKeybState.IsKeyUp(Keys.Enter) && currentKeybState.IsKeyDown(Keys.Enter)) {windowState = WindowState.Ending; return itemList[selectedItem].itemLink; }else if (lastKeybState.IsKeyDown(Keys.Escape)) return null; else return this;
} 

最后是绘制菜单的方法:

public void Draw(SpriteBatch spriteBatch)
{if (windowState == WindowState.Inactive) return; float smoothedProgress = MathHelper.SmoothStep(0,1,(float)changeProgress); int verPosition = 300; float horPosition = 300; float alphaValue; float bgLayerDepth; Color bgColor; switch (windowState) {case WindowState.Starting: horPosition -= 200 * (1.0f - (float)smoothedProgress); alphaValue = smoothedProgress; bgLayerDepth = 0.5f; bgColor = new Color(new Vector4(1, 1, 1, alphaValue)); break; case WindowState.Ending: horPosition += 200 * (float)smoothedProgress; alphaValue = 1.0f - smoothedProgress; bgLayerDepth = 1; bgColor = Color.White; break; default: alphaValue = 1; bgLayerDepth = 1; bgColor = Color.White; break; }Color titleColor = new Color(new Vector4(1, 1, 1, alphaValue));spriteBatch.Draw(backgroundImage, new Vector2(), null, bgColor, 0, Vector2.Zero, 1, SpriteEffects.None, bgLayerDepth); spriteBatch.DrawString(spriteFont, menuTitle, new Vector2(horPosition, 200), titleColor,0,Vector2.Zero, 1.5f, SpriteEffects.None, 0); for (int itemID = 0; itemID < itemList.Count; itemID++) {Vector2 itemPostition = new Vector2(horPosition, verPosition); Color itemColor = Color.White; if (itemID == selectedItem) itemColor = new Color(new Vector4(1,0,0,alphaValue)); else itemColor = new Color(new Vector4(1,1,1,alphaValue)); spriteBatch.DrawString(spriteFont, itemList[itemID].itemText, itemPostition, itemColor, 0, Vector2.Zero, 1, SpriteEffects.None, 0); verPosition += 30; }
} 
主程序

你可以在LoadContent方法中创建菜单结构。更新方法必须调用每个菜单的更新方法和MenuInput方法:

protected override void Update(GameTime gameTime)
{if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); KeyboardState keybState = Keyboard.GetState(); if (menusRunning) {foreach (MenuWindow currentMenu in menuList) currentMenu.Update(gameTime.ElapsedGameTime.TotalMilliseconds); MenuInput(keybState); }else{}lastKeybState = keybState; base.Update(gameTime);
} 

MenuInput方法将用户输入传递到当前激活的菜单,当输入处理后接受返回的激活菜单:

private void MenuInput(KeyboardState currentKeybState)
{MenuWindow newActive = activeMenu.ProcessInput(lastKeybState, currentKeybState); if (newActive == startGameEasy) {//set level to easy menusRunning = false; }else if (newActive == startGameNormal) {//set level to normal menusRunning = false; }else if (newActive == startGameHard) {//set level to hard menusRunning = false; }else if (newActive == null) this.Exit(); else if (newActive != activeMenu) newActive.WakeUp(); activeMenu = newActive;
} 
扩展阅读

虽然对一个基础菜单系统这个方法已经足够了,但对一个完整的游戏来说,MenuWindow类应该是一个抽象类,这样菜单不能作为这个类的实例。作为替代,你应该为菜单和游戏各创建一个新类,这两个类都从MenuWindow类继承。通过这种方式,键盘处理和绘制完全被这个方法处理,无需丑陋的menuRunning变量了。这也是http ://creators . xna . com网站上菜单示例的基础(译者注:这应该是指示例Game State Management)。

转载于:https://www.cnblogs.com/AlexCheng/archive/2010/11/26/2120153.html

处理2D图像和纹理——创建2D菜单界面相关推荐

  1. 处理2D图像和纹理——投影纹理

    创建一面镜子:投影纹理 问题 你想在场景中创建一面镜子.例如,在一个赛车游戏中创建一面后视镜.你也可以使用这个技术创建一个反射贴图. 解决方案 首先需要将镜子中看到的场景绘制到一张纹理中.然后,绘制相 ...

  2. 处理2D图像和纹理——显示文字

    问题 你想绘制一些文字,例如,显示一些操作说明或当前得分. 解决方案 本章前四个教程中学习的SpriteBatch类也可以绘制文字.做法和绘制纹理几乎是一样的,只不过不是导入一个Texture2D,这 ...

  3. 数字媒体概论——2D图像图形

    一:色彩基础 1.1:色彩认知 色彩是能引起我们共同的审美愉悦的.最为敏感的形式要素.色彩是最有表现力的要素之一,因为它的性质直接影响人们的感情. 丰富多样的颜色可以分成两个大类:无彩色系和有彩色系. ...

  4. 通过.obj生成2d图像_自动生成 凹凸法线灯贴图 插件

    CrazyBump疯狂凹凸自动生成凹凸法线灯贴图 CrazyBump是一款专业的法线贴图制作软件,人们一般称之为超级法线凹凸生成软件,用来做材质中贴图中的凹凸和法线贴图是非常不错的,CrazyBump ...

  5. [SheRO]用D3D绘制2D图像

    置顶声明:本文版权归shallway所有,如有转载,请按如下方式于文章明显位置标明原创作者及原文出处,以示尊重!! ========================================== ...

  6. Unity学习4:如何实现2D图像跟踪(涂色类AR项目实践1)

    Unity2D图像检测追踪 AR tracked image manager(2D图像检测追踪管理器) 准备:创建项目 第一步:创建参考图像库 第二步:挂载组件 小插曲:如何显示整个地球仪 第三步:为 ...

  7. 英伟达这篇CVPR 2022 Oral火了!2D图像秒变逼真3D物体!虚拟爵士乐队来了!

    点击下方卡片,关注"CVer"公众号 AI/CV重磅干货,第一时间送达 你见过乐器自己演奏么?看看这个: 图1. "活灵活现"的虚拟乐器还是在 NVIDIA 服 ...

  8. 二维纹理 Texture 2D

    二维纹理 Texture 2D 原文链接http://www.ceeger.com/Manual/Textures.html Textures bring your Meshes, Particles ...

  9. 2D图像转3D仅需5秒,特斯拉的自动驾驶技术有救了?

    来源:科技智谷 编译:徐浩 75年前,宝丽来相机拍摄出第一张即时照片,是人类第一次以逼真的二维图像快速捕捉三维世界,具有划时代的意义.今天,人工智能的研究人员正在进行相反的工作,力求在几秒钟的时间内将 ...

最新文章

  1. 初学者css常见问题_5分钟内学习CSS Grid-初学者教程
  2. windows环境下,mysql的root密码丢失后重置方法
  3. Ecshop实现仿Taobao地区运费模板
  4. ubuntu四个屏幕设置_Linux_从9个方面来立体式地美化Ubuntu 桌面,总结了一下桌面美化的设置。 - phpStudy...
  5. Spark学习之路 (五)Spark伪分布式安装
  6. PAT1048 数字加密 (20 分)
  7. DataTime转Varchar
  8. 百度的一道 java 高频面试题的多种解法
  9. 日本語の勉強の日記 十七回
  10. 归类问题:简单的代价函数和梯度下降----吴恩达机器学习
  11. 尚硅谷大数据开发Day03
  12. 【python】matplotlib绘图显示不了中文,且没有SimHei、FangSong等字体
  13. 三星 android驱动安装失败,三星USB手机驱动安装失败怎么办?三星USB手机驱动安装失败解决方法...
  14. 沪深300股指破冰金融期权衍生品市场“基建”再进一步
  15. hexo(sakura)仿gitee添加文章贡献度日历图(echarts)
  16. Flash Player 更新后视频卡问题
  17. antd源码-spin解析
  18. 苹果手机投影_智能手机投屏到投影仪
  19. java nas smb_FreeNAS 02:深入 SMB 服务使用
  20. 使用打码平台登录B站

热门文章

  1. ControlStyles(枚举)
  2. php 使用curl模拟登录人人(校内)网
  3. 【转载】WinCE6.0 Camera驱动源码分析(二)
  4. 关于flex布局学习分享
  5. franz ubuntu_重新审视Unix理念,持续测试,Franz,Gitbase,Python,Linux等
  6. 调试器原理_调试器的工作原理
  7. (18)Vue DevTools插件
  8. Keba常用硬件模块
  9. 视觉SLAM笔记(23) 图像
  10. 深度学习笔记(5) 深层神经网络