Unity DOTS纯ECS的UI分辨率适配(写一个ECS的CanvasScaler)

UnityProjectTiny包是一个纯ECS写游戏代码的环境,目前已停止更新很久了,Entities包都更新到1.0去了,Tiny还是用的0.17的,不知后面还会不会更新。不过基本的游戏开发功能都有了,可以用来写些小Demo体验下纯ECS写代码的‘乐趣’…单单Entities包是无法使用纯ECS来写代码的,有很多GameObject组件,像Camera、Mesh、Material都是无法转成Entity。

Tiny包里带有UI模块,但是功能很有限,只有很基础的Button、Slider、Toggle、Image、Text等功能,跟UGUI的功能相比,差距极大,但是可以用。有很多限制,目前所有UI都还不支持旋转跟缩放。只支持一个Root Canvas,其他所有Root Canvas内的UI节点都不能挂Canvas了,挂了也不生效。Canvas的渲染模式只有ScreenSpaceOverlay能用,其他模式或是更不好用,或是还不支持。UI渲染的相机可以是一个单独的Camera,但是Tiny没提供UI在不同分辨率下的自适应功能!也就是Root Canvas上挂的CanvasScaler是不生效的。也就是如果UI是在1920*1080的分辨率下设计的大小,在小于这个分辨率下渲染会变大,在大于这个分辨率下渲染会变小。

不过问题不大,可以自己写一个类似CanvasScaler的组件。代码中的scaleFactor的计算扒了UGUI中的,只支持按宽高来缩放UI大小和计算位置,这种模式的UI在不同分辨率下也不会变形,像那种铺满屏幕的方式是会变形的,感觉没必要,就没加代码了。宽高比写死了0.5,可根据自己的需要进行更改。这里提供的代码只是做演示,可以写更完善一点。把代码丢到项目就中可以了,不用进行其他操作。

using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;
using Unity.Tiny.UI;
using Unity.Tiny;

namespace TinyUI {
    internal struct CanvasScaler : IComponentData {
        public float PreviousFactor;

        public float ScaleFactor;

        public int ScreenWidth;

        public int ScreenHeight;

        public CanvasScaler(int width, int height) {
            PreviousFactor = ScaleFactor = 1;
            ScreenWidth = width;
            ScreenHeight = height;
        }
    }

    [UpdateInGroup(typeof(TransformSystemGroup))]
    [UpdateAfter(typeof(RectangleTransformSystem))]
    [UpdateAfter(typeof(RectCanvas))]
    [UpdateBefore(typeof(CanvasScaler))]
    [UpdateBefore(typeof(UpdateRectTransformSystem))]
    internal class AddScalerSystem : SystemBase
    {
        protected override void OnUpdate()
        {
            var di = GetSingleton<DisplayInfo>();

            var ecb = new EntityCommandBuffer(Allocator.TempJob);
            Entities.WithNone<CanvasScaler>().WithAll<RectCanvas>().ForEach((Entity entity) => {
                ecb.AddComponent<CanvasScaler>(entity, new CanvasScaler(di.screenWidth, di.screenHeight));
            }).WithoutBurst().Run();
            ecb.Playback(EntityManager);
            ecb.Dispose();

            Enabled = false;
        }
    }

    [UpdateInGroup(typeof(TransformSystemGroup))]
    [UpdateAfter(typeof(RectangleTransformSystem))]
    [UpdateAfter(typeof(RectCanvas))]
    [UpdateBefore(typeof(UpdateRectTransformSystem))]
    internal class CanvasScalerSystem : SystemBase
    {
        protected override void OnCreate()
        {
            base.OnCreate();
            RequireSingletonForUpdate<CanvasScaler>();
        }

        protected override void OnUpdate()
        {
            var di = GetSingleton<DisplayInfo>();
            int referenceWidth = di.screenWidth;
            int referenceHeight = di.screenHeight;
            int screenWidth = di.frameWidth;
            int screenHeight = di.frameHeight;

            Entities.ForEach((ref CanvasScaler canvasScaler) => {
                if (canvasScaler.ScreenWidth != screenWidth || canvasScaler.ScreenHeight != screenHeight) {
                    float logWidth = math.log2((float)screenWidth / referenceWidth);
                    float logHeight = math.log2((float)screenHeight / referenceHeight);
                    float logWeightedAverage = math.lerp(logWidth, logHeight, 0.5f);
                    float scaleFactor = math.pow(2, logWeightedAverage);

                    canvasScaler.PreviousFactor = canvasScaler.ScaleFactor;
                    canvasScaler.ScaleFactor = scaleFactor;
                    canvasScaler.ScreenWidth = (int)screenWidth;
                    canvasScaler.ScreenHeight = (int)screenHeight;
                }
            }).WithoutBurst().Run();
        }
    }

    [UpdateInGroup(typeof(TransformSystemGroup))]
    [UpdateAfter(typeof(RectangleTransformSystem))]
    [UpdateAfter(typeof(RectCanvas))]
    [UpdateAfter(typeof(CanvasScalerSystem))]
    internal class UpdateRectTransformSystem : SystemBase
    {
        protected override void OnCreate()
        {
            base.OnCreate();
            RequireSingletonForUpdate<CanvasScaler>();
        }

        protected override void OnUpdate()
        {
            var cs = GetSingleton<CanvasScaler>();
            if (cs.ScaleFactor != cs.PreviousFactor) {
                Entities.WithNone<RectCanvas>().WithAll<RectParent>().ForEach((ref RectTransform rectTransform) => {
                    rectTransform.SizeDelta /= cs.PreviousFactor;
                    rectTransform.SizeDelta *= cs.ScaleFactor;

                    rectTransform.AnchoredPosition /= cs.PreviousFactor;
                    rectTransform.AnchoredPosition *= cs.ScaleFactor;
                }).Run();
                cs.PreviousFactor = cs.ScaleFactor;
                SetSingleton<CanvasScaler>(cs);
            }
        }
    }
}

这是打包wasm版在浏览器运行的效果,也可以打包到Android上运行,不过改变不了分辨率。界面上的几个按钮会根据不同分辨率改变大小,并且重新计算在屏幕上的相对位置。如果不加这些代码,UI在屏幕上是固定大小和位置的,和设计分辨率相差太大,显示就会出问题。可以去Unity的GitHub上下载ProjectTinySample,然后把代码丢到工程里边,创建几个UI试试。

你可能感兴趣的:(unity,ui,ProjectTiny,ECS,DOTS)