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

Win2D 中的游戏循环:CanvasAnimatedControl

Win2D 是 DirectX 的一个高层封装,提供了极大 DirectX 性能的同时,又具有很好用的 API 设计。

用 Win2D 除了能做出高性能的视觉效果之外,还可以轻而易举地搭建一个游戏循环出来。使用 Win2D 的游戏循环,你可以直接做出一个简单的游戏出来。


本文内容

      • 使用 Win2D 做出来的游戏
      • 准备工作
      • Win2D 中的画布控件
      • CanvasAnimatedControl
      • CanvasAnimatedControl 在游戏中的使用
      • CanvasAnimatedControl 中 CreateResources 事件
        • 参考资料

使用 Win2D 做出来的游戏

我在 GitHub 上开源了我正在做的一个基于 Win2D 的小游戏 —— GravityMaze,可以翻译为重力迷宫。本意是使用手机的重力感应器借助于自然重力的方式玩这款游戏,不过考虑到 Windows 10 Mobile 的手机太少,用户数量太少,其实我还是直接展示 UWP 桌面版好了。使用方向键可以控制桌面的倾斜角度,以便间接控制小球的运动方向。

当然,我自己是有一部 Lumia 950XL 的,你可以在 使用 Windows 10 中的加速度计(Accelerometer,重力传感器) 一文中看到它的身影。

重力迷宫
▲ 重力迷宫

这张图的红色背景是我自己拍摄的,所以绝不可能存在版权问题。

准备工作

要使用 Win2D 进行简单的游戏开发,你需要先配置好一些 UWP 的开发环境,并且在你的项目中安装 Win2D.uwp 的 NuGet 包。阅读 win10 uwp win2d 入门 看这一篇就够了 - 林德熙 了解如何在你的项目中安装 Win2D,并且了解 Win2D 基本的知识。

Win2D 中的画布控件

Win2D 中的画布有 CanvasControlCanvasVirtualControlCanvasAnimatedControl

  • CanvasControl 用于进行一次性绘制,或者那些不常更新的画面内容。例如进行软件的 UI 绘制,或者软件中所得图形的绘制。
  • CanvasVirtualControl 适用于在一个很大的画面中,只显示一个小部分的情况。例如显示大地图的一部分,或者显示大量超界的笔迹内容。
  • CanvasAnimatedControl 适用于显示频繁更新的画面。典型的例子就是游戏。

CanvasAnimatedControl

我们使用 CanvasAnimatedControl 来做游戏循环,因为这是 Win2D 这几个控件中最适合做游戏循环的控件了。

要在你的项目中使用 CanvasAnimatedControl,你需要在 XAML 中添加 using:Microsoft.Graphics.Canvas.UI.Xaml

<Page x:Class="Walterlv.GravityMaze.Pages.GamePage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:xaml="using:Microsoft.Graphics.Canvas.UI.Xaml">
    <xaml:CanvasAnimatedControl Update="OnUpdate" Draw="OnDraw" />
</Page>

然后,我们订阅 CanvasAnimatedControl 的两个事件:

  • Update
    • 用于更新游戏中的数据,更新参考的是游戏时间线。
  • Draw
    • 用于绘制游戏的内容。

这是游戏循环最必要的两个事件了,其他虽然也是需要的,但也可以不写。

private MazeGame _game;
        
private void OnUpdate(ICanvasAnimatedControl sender, CanvasAnimatedUpdateEventArgs e)
{
    // 根据时间线更新游戏数据。
    _game.Update(e.Timing);
}

private void OnDraw(ICanvasAnimatedControl sender, CanvasAnimatedDrawEventArgs e)
{
    // 绘制游戏画面。
    using (var ds = e.DrawingSession)
    {
        _game.Draw(ds);
    }
}

CanvasAnimatedControl 在游戏中的使用

你在我的 GamePage 中其实看不到对 UpdateDraw 事件的实际使用,因为我把它们都封装到了 MazeGame 中了。

有些信息需要注意:

  1. UpdateDraw 运行于相同的线程,但都不是主线程;所以你不可以从这里去获取主线程中的 UI 资源。
  2. 正常情况下 Update 调用一次之后,Draw 就会调用一次;但如果当前运行缓慢,那么多次 Update 调用之后才会调用一次 Draw
  3. 如果 UWP 窗口最小化了,那么只会调用 Update 方法,而不会调用 Draw 方法。

线程
▲ 线程

在 GravityMaze 重力迷宫中,主要是 Player 也就是你在上面动图中看到的那个小球需要在 Update 中更新数据,其他其实只需要画就好了。Update 中我需要计算速度、加速度以及进行碰撞检测。

private void OnUpdate(ICanvasAnimatedControl sender, CanvasAnimatedUpdateEventArgs e)
{
    var seconds = timing.ElapsedTime.TotalSeconds;
    
    // 1. 根据重力感应器或者键盘计算这一帧桌面的倾斜角度。
    // 2. 计算这一倾角带来的加速度。
    // 3. 计算是否跌入黑洞。
    // 4. 将加速度叠加阻力。
    // 5. 计算此速度和加速度下的位置。
    // 6. 进行边缘检测和碰撞检测。
}

而在 Draw 中,只绘制了那个球:

private void OnDraw(ICanvasAnimatedControl sender, CanvasAnimatedDrawEventArgs e)
{
    // 绘制游戏画面。
    using (var ds = e.DrawingSession)
    {
        ds.FillEllipse(_xPosition, _yPosition, _radius, _radius, Colors.Gray);
    }
}

事实上你在上面动图看到的球并不是一个毫无生机的灰球,而是一个具有特效的半透明塑料弹球。你可以阅读 使用 Win2D 绘制带图片纹理的圆(或椭圆) 了解如何绘制这样的塑料弹球。

CanvasAnimatedControl 中 CreateResources 事件

CanvasAnimatedControl 中还有 CreateResources 事件,对更复杂的游戏循环有所帮助。当需要创建资源的时候会引发此事件。

第一次使用的时候就需要创建资源;除此之外,如果设备丢失,也需要创建资源。阅读 Win2D 官方文章系列翻译 - 处理设备丢失 - void² - 博客园 了解更多关于设备丢失的内容。

private CanvasBitmap _boardMaterial;

private async void OnCreateResources(CanvasAnimatedControl sender, CanvasCreateResourcesEventArgs e)
{
    // 其中,GameCanvas 是 XAML 中 CanvasAnimatedControl 的名称。
    _boardMaterial = await CanvasBitmap.LoadAsync(GameCanvas, new Uri("{ms-appx:///Assets/Game/Boards/table.jpg}"));
}

这里的 _boardMaterial 就是你在上面动图中看到的后面那张红色背景。

这样,便可以在需要的时候创建资源。

不过,这时你需要在 Draw 中先判空再绘制。

private void OnDraw(ICanvasAnimatedControl sender, CanvasAnimatedDrawEventArgs e)
{
    using (var ds = e.DrawingSession)
    {
        // 其中,FullBounds 是 Rect 类型,我在 Page 的 SizeChanged 中给它赋的值。
        if (_boardMaterial != null)
        {
            ds.DrawImage(_boardMaterial, FullBounds);
        }
        else
        {
            ds.FillRectangle(FullBounds, Colors.White);
        }
    }
}

你也可以使用事件参数 CanvasCreateResourcesEventArgs 来追踪这个异步加载任务,这样能够在绘制之前确保资源被加载完毕。

private async void OnCreateResources(CanvasAnimatedControl sender, CanvasCreateResourcesEventArgs e)
{
    e.TrackAsyncAction(CreateResourcesAsync().AsAsyncAction());

    async Task CreateResourcesAsync()
    {
        _boardMaterial = await CanvasBitmap.LoadAsync(GameCanvas, new Uri("{ms-appx:///Assets/Game/Boards/table.jpg}"));
    }
}

参考资料

  • win10 uwp win2d 入门 看这一篇就够了 - 林德熙
  • win10 uwp win2d CanvasVirtualControl 与 CanvasAnimatedControl - 林德熙
  • win10 uwp 萤火虫效果 - 林德熙

相关文章:

  • 使用 Windows 10 中的加速度计(Accelerometer,重力传感器)
  • 用 dotTrace 进行性能分析时,各种不同性能分析选项的含义和用途
  • 如何创建一个基于 .NET Core 3 的 WPF 项目
  • 将基于 .NET Framework 的 WPF 项目迁移到基于 .NET Core 3
  • 了解 .NET 的默认 TaskScheduler 和线程池(ThreadPool)设置,避免让 Task.Run 的性能急剧降低
  • .NET 中小心嵌套等待的 Task,它可能会耗尽你线程池的现有资源,出现类似死锁的情况
  • 在有 UI 线程参与的同步锁(如 AutoResetEvent)内部使用 await 可能导致死锁
  • 不要使用 Dispatcher.Invoke,因为它可能在你的延迟初始化 LazyT 中导致死锁
  • 定义一组抽象的 Awaiter 的实现接口,你下次写自己的 await 可等待对象时将更加方便
  • .NET 除了用 Task 之外,如何自己写一个可以 await 的对象?
  • .NET 中什么样的类是可使用 await 异步等待的?
  • Visual Studio 2017 以前的旧格式的 csproj Import 进来的 targets 文件有时不能正确计算属性(PropertyGroup)和集合(ItemGroup)
  • 使用 ReSharper,输入即遵循 StyleCop 的代码格式化规范
  • StyleCop 是什么,可以帮助团队带来什么价值?
  • 文件和文件夹不存在的时候,FileSystemWatcher 监听不到文件的改变?如果递归地监听就可以了
  • [js高手之路]搞清楚面向对象,必须要理解对象在创建过程中的内存表示
  • 8年软件测试工程师感悟——写给还在迷茫中的朋友
  • Javascript基础之Array数组API
  • log4j2输出到kafka
  • Python - 闭包Closure
  • SpriteKit 技巧之添加背景图片
  • SSH 免密登录
  • v-if和v-for连用出现的问题
  • vue 个人积累(使用工具,组件)
  • 编写符合Python风格的对象
  • 关于springcloud Gateway中的限流
  • 机器学习中为什么要做归一化normalization
  • 开年巨制!千人千面回放技术让你“看到”Flutter用户侧问题
  • 全栈开发——Linux
  • 我的业余项目总结
  • 写代码的正确姿势
  • 正则表达式
  • ionic异常记录
  • Nginx实现动静分离
  • shell使用lftp连接ftp和sftp,并可以指定私钥
  • 关于Android全面屏虚拟导航栏的适配总结
  • 教程:使用iPhone相机和openCV来完成3D重建(第一部分) ...
  • ​力扣解法汇总946-验证栈序列
  • #微信小程序:微信小程序常见的配置传值
  • (Redis使用系列) Springboot 使用Redis+Session实现Session共享 ,简单的单点登录 五
  • (附源码)计算机毕业设计SSM保险客户管理系统
  • (七)微服务分布式云架构spring cloud - common-service 项目构建过程
  • (收藏)Git和Repo扫盲——如何取得Android源代码
  • (原創) 如何使用ISO C++讀寫BMP圖檔? (C/C++) (Image Processing)
  • (转)Android学习系列(31)--App自动化之使用Ant编译项目多渠道打包
  • (转)Sublime Text3配置Lua运行环境
  • (转)平衡树
  • .bat批处理(九):替换带有等号=的字符串的子串
  • .NET CLR Hosting 简介
  • .NET Core、DNX、DNU、DNVM、MVC6学习资料
  • .NET Project Open Day(2011.11.13)
  • .NET WebClient 类下载部分文件会错误?可能是解压缩的锅
  • .pop ----remove 删除
  • /deep/和 >>>以及 ::v-deep 三者的区别
  • [ 数据结构 - C++]红黑树RBTree