Walt 发布的文章

导航

如果你要在Blazor WebAssembly应用中使用Babylonjs,请参考:https://www.hnbc.info/index.php/archives/40/

准备阶段

本文案中用到的Hnbc.Lib.Blazor.BabylonJS.Server项目,是基于Babylonjs官方javascript生成的供blazor使用的引用库。可通过https://github.com/canhorn/EventHorizon.Blazor.TypeScript.Interop.Generator 该工程进行生成。
也可使用我已经生成好的项目直接使用:
下载一:https://pan.baidu.com/s/19GgK3b09cIPAG4t-8sBw-g 提取码: fvxg
下载二:https://download.csdn.net/download/yuexingchen2/19666177

Blazor Server应用配置

生成基于Blazor的BabylonJs引用库

参考:https://github.com/canhorn/EventHorizon.Blazor.TypeScript.Interop.Generator
可直接clone该项目,然后生成,或者下载我已经处理好的:

创建Blazor项目

创建新项目,选择Blazor Server应用

添加Hnbc.Lib.Blazor.BabylonJS.Server项目

将项目文Hnbc.Lib.Blazor.BabylonJS.Server拷贝到解决方案目录下,在解决方案上右键,添加现有项目,选择Hnbc.Lib.Blazor.BabylonJS.Server

添加项目引用

Blazor Server应用项目上添加项目引用,将Hnbc.Lib.Blazor.BabylonJS.Server引用到当前项目。

修改App.razor

App.razor文件末尾,添加如下代码:


@code {
    [Inject]
    public IJSRuntime JSRuntime { get; set; }

    protected override void OnInitialized()
    {
        EventHorizon.Blazor.Server.Interop.EventHorizonBlazorInterop.JSRuntime = JSRuntime;
    }
}

添加Js引用

Pages/_Host.cshtml文件中,添加Babylon的js引用和server-interop-bridge.js引用

        <!-- blazor基础框架js -->
        <script src="_framework/blazor.server.js"></script>
        <script src="_content/EventHorizon.Blazor.Server.Interop/server-interop-bridge.js"></script>

    <!-- Babylon.js -->
    <script src="https://code.jquery.com/pep/0.4.2/pep.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.2/dat.gui.min.js"></script>
    <script src="https://cdn.babylonjs.com/ammo.js"></script>
    <script src="https://cdn.babylonjs.com/cannon.js"></script>
    <script src="https://cdn.babylonjs.com/Oimo.js"></script>
    <script src="https://cdn.babylonjs.com/libktx.js"></script>
    <script src="https://cdn.babylonjs.com/earcut.min.js"></script>
    <script src="https://cdn.babylonjs.com/babylon.js"></script>
    <script src="https://cdn.babylonjs.com/materialsLibrary/babylonjs.materials.min.js"></script>
    <script src="https://cdn.babylonjs.com/proceduralTexturesLibrary/babylonjs.proceduralTextures.min.js"></script>
    <script src="https://cdn.babylonjs.com/postProcessesLibrary/babylonjs.postProcess.min.js"></script>
    <script src="https://cdn.babylonjs.com/loaders/babylonjs.loaders.js"></script>
    <script src="https://cdn.babylonjs.com/serializers/babylonjs.serializers.min.js"></script>
    <script src="https://cdn.babylonjs.com/gui/babylon.gui.min.js"></script>

创建Babylon组件

为了测试是否可以正常使用,在项目下新建Controls文件夹,在此文件夹下新建-Razor组件。命名为TestComp。内容为:

<div>
    <canvas id="test-window" style="width:100%"></canvas>
</div>

添加TestComp的类文件,在Controls下添加名为TestComp.razor.cs的类文件,内容为:

using BABYLON;
using Hnbc.Lib.Blazor.BabylonJS.Server.Model;
using EventHorizon.Blazor.Server.Interop.Callbacks;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Blazor.Test.Server.Controls
{
    public partial class TestComp : IDisposable
    {
        private IDictionary<string, AnimationGroup> _animationMap = new Dictionary<string, AnimationGroup>();
        private AnimationGroup _runningAnimation = null;
        private Engine _engine;
        public Mesh Obj; 

        protected override async Task OnAfterRenderAsync(bool firstRender)
        {
            if (firstRender)
            {
                await CreateScene();
            }
        }

        public void Dispose()
        {
            _engine?.dispose();
        }

        public async ValueTask CreateScene()
        {
            var canvas = await Canvas.GetElementById("test-window");
            var engine = await Engine.NewEngine(canvas,true);
            var scene = await Scene.NewScene(engine);
            var cameraTarget = await Vector3.NewVector3(0, 0, 5);

            var camera = await ArcRotateCamera.NewArcRotateCamera(
                "ArcRotateCamera",
                (decimal)(System.Math.PI / 2),
                (decimal)(System.Math.PI / 2),
                2,
                cameraTarget,
                scene
            );
            var hemisphericLightDirection = await Vector3.NewVector3(1, 1, 0);
            var light1 = await HemisphericLight.NewHemisphericLight("light1", hemisphericLightDirection, scene);
            var pointLightDirection = await Vector3.NewVector3(0, 1, -1);

            var light2 = await PointLight.NewPointLight("Omni", pointLightDirection,scene);

            await Mesh.CreateSphere("sphere1",50,2, scene);

            await scene.set_activeCamera(camera);
            await camera.attachControl(false);

            await engine.runRenderLoop(new ActionCallback(
                            () => Task.Run(() => scene.render(true, false))
                        ));

            _engine = engine;
        }

        public async ValueTask RunAnimation(string name)
        {
            if (_runningAnimation != null)
            {
                await _runningAnimation.stop();
                _runningAnimation = null;
            }

            if (_animationMap.ContainsKey(name))
            {
                await _animationMap[name].play(true);
                _runningAnimation = _animationMap[name];
            }
        }
    }
}

测试

修改Pages/Index.razor文件,添加TestComp组件显示。

@page "/"

<h1>Babylon测试!</h1>

<Blazor.Test.Server.Controls.TestComp/>

启动,如果界面显示了模型,则说明可以正常使用了。

修改网站设置

如果上面的测试例子中,使用了glb模型,为了能在网站加载此模型,需要在StartUp.cs文件的Configure中添加如下代码,如果网站部署在IIS下,也可在IIS中手动配置MIME。


var provider = new FileExtensionContentTypeProvider();
// Add new mappings
provider.Mappings[".fx"] = "application/fx";
provider.Mappings[".gltf"] = "model/vnd.gltf+json";
provider.Mappings[".glb"] = "application/octet-stream";

导航

如果你要在Blazor Server应用中使用Babylonjs,请参考:https://www.hnbc.info/index.php/archives/41/

准备阶段

本文案中用到的Hnbc.Lib.Blazor.BabylonJS.Wasm项目,是基于Babylonjs官方javascript生成的供blazor使用的引用库。可通过https://github.com/canhorn/EventHorizon.Blazor.TypeScript.Interop.Generator 该工程进行生成
也可使用我已经生成好的项目:
下载一:https://pan.baidu.com/s/19GgK3b09cIPAG4t-8sBw-g 提取码: fvxg
下载二:https://download.csdn.net/download/yuexingchen2/19666120

Blazor WebAssembly应用配置

创建Blazor项目

创建新项目,选择Blazor WebAssembly应用

添加Hnbc.Lib.Blazor.BabylonJS.Wasm项目

将项目文Hnbc.Lib.Blazor.BabylonJS.Wasm拷贝到解决方案目录下,在解决方案上右键,添加现有项目,选择Hnbc.Lib.Blazor.BabylonJS.Wasm

添加项目引用

Blazor WebAssembly应用上添加项目引用,将Hnbc.Lib.Blazor.BabylonJS.Wasm引用到当前项目。

修改App.razor

App.razor文件末尾,添加如下代码:


@code { 
    [Inject]
    public IJSRuntime JSRuntime { get; set; }

    protected override void OnInitialized()
    {
        EventHorizon.Blazor.Interop.EventHorizonBlazorInterop.JSRuntime = JSRuntime;
    }
}

添加Js引用

wwwroot/index.html文件中,添加Babylon的js引用和interop-bridge.js引用

添加interop-bridge.js引用,通常和blazor.webassembly.js在一起。

        <!-- blazor基础框架js -->
    <script src="_framework/blazor.webassembly.js"></script>
    <script src="_content/EventHorizon.Blazor.Interop/interop-bridge.js"></script>

添加BabylonJs引用,通常放在head中。

    <!-- Babylon.js -->
    <script src="https://code.jquery.com/pep/0.4.2/pep.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.2/dat.gui.min.js"></script>
    <script src="https://cdn.babylonjs.com/ammo.js"></script>
    <script src="https://cdn.babylonjs.com/cannon.js"></script>
    <script src="https://cdn.babylonjs.com/Oimo.js"></script>
    <script src="https://cdn.babylonjs.com/libktx.js"></script>
    <script src="https://cdn.babylonjs.com/earcut.min.js"></script>
    <script src="https://cdn.babylonjs.com/babylon.js"></script>
    <script src="https://cdn.babylonjs.com/materialsLibrary/babylonjs.materials.min.js"></script>
    <script src="https://cdn.babylonjs.com/proceduralTexturesLibrary/babylonjs.proceduralTextures.min.js"></script>
    <script src="https://cdn.babylonjs.com/postProcessesLibrary/babylonjs.postProcess.min.js"></script>
    <script src="https://cdn.babylonjs.com/loaders/babylonjs.loaders.js"></script>
    <script src="https://cdn.babylonjs.com/serializers/babylonjs.serializers.min.js"></script>
    <script src="https://cdn.babylonjs.com/gui/babylon.gui.min.js"></script>

创建Babylon组件

为了测试是否可以正常使用,在项目下新建Controls文件夹,在此文件夹下新建-Razor组件。命名为TestComp。内容为:

<div>
    <canvas id="test-window" style="width:100%"></canvas>
</div>

添加TestComp的类文件,在Controls下添加名为TestComp.razor.cs的类文件,内容为:

using BabylonJS;
using Hnbc.Lib.Blazor.BabylonJS.Wasm.Model;
using EventHorizon.Blazor.Interop.Callbacks;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Blazor.Test.Client.Controls
{
    public partial class TestComp : IDisposable
    {
        private IDictionary<string, AnimationGroup> _animationMap = new Dictionary<string, AnimationGroup>();
        private AnimationGroup _runningAnimation = null;
        private Engine _engine;
        public Mesh Obj;

        protected override void OnAfterRender(bool firstRender)
        {
            if (firstRender)
            {
                CreateScene();
            }
        }

        public void Dispose()
        {
            _engine?.dispose();
        }

        public void CreateScene()
        {
            var canvas = Canvas.GetElementById("test-window");
            var engine = new Engine(canvas, true);
            var scene = new Scene(engine);
            var cameraTarget = new BabylonJS.Vector3(0, 0, 5);

            var camera =new ArcRotateCamera(
                "ArcRotateCamera",
                (decimal)(System.Math.PI / 2),
                (decimal)(System.Math.PI / 2),
                2,
                cameraTarget,
                scene
            );
            var hemisphericLightDirection = new BabylonJS.Vector3(1, 1, 0);
            var light1 = new HemisphericLight("light1", hemisphericLightDirection, scene);
            var pointLightDirection = new BabylonJS.Vector3(0, 1, -1);

            var light2 = new PointLight("Omni", pointLightDirection, scene);
             
            Mesh.CreateSphere("sphere1", 50, 2, scene);

            scene.activeCamera= camera;
            camera.attachControl(false);

            engine.runRenderLoop(new ActionCallback(
                            () => Task.Run(() => scene.render(true, false))
                        ));

            _engine = engine;
        }

        public void RunAnimation(string name)
        {
            if (_runningAnimation != null)
            {
                _runningAnimation.stop();
                _runningAnimation = null;
            }

            if (_animationMap.ContainsKey(name))
            {
                _animationMap[name].play(true);
                _runningAnimation = _animationMap[name];
            }
        }
    }
}

测试

修改Pages/Index.razor文件,添加TestComp组件显示。

@page "/"

<h1>Hello, Babylon!</h1>

<Blazor.Test.Client.Controls.TestComp/>

启动,如果界面显示了模型,则说明可以正常使用了。

零散知识点

Unit,Unity里面的单位,设计界面为100像素。

Time.deltaTime 表示执行的时间间隔,放在Update中,可查看每帧更新的时间间隔

Application.targetFrameRate=50,配置FPS帧率

脚本中使用this表示当前组件。如:

this.transform.Translate(0,0.5f,0);表示将当前游戏对象Y方向上移动0.5个Unit;

匀速运动的一种实现方式:

f1oat step = 0.8f *Time. de1taTime;
this.transform.Trans1ate(o,step,0);

Unity中一般使用float

获取游戏对象的方法

  • 在脚本中使用this获取当前组件
  • this.gameObject获取当前节点(游戏对象)

获取当前节点的组件

  • this.gameObject.GetComponent<组件类型>();
  • 因为Unity内部做了封装,上述代码可以简化成this.GetComponent<组件类型>();
  • 又因为this一般可省略,再次简化GetComponent<组件类型>();

获取其他节点

在当前节点中获取其他节点,使用GameObject.Find

//获取其他节点
GameObject obj = GameObject.Find("/Other/22");//获取节点,通过名字或路径查找
SpriteRenderer comp = obj.GetComponent<SpriteRenderer>();//获取节点下组件
comp.flipY = true;//修改组件属性

Transform组件作用

  • 维护节点的Position、Rotation和Scale属性
  • 维护节点的父子关系
    示例:
//获取父级节点
GameObject parent = this.transform.parent.gameObject;

//获取所有子节点可直接遍历transform
foreach(var child in transform)
{
        //...
}

//将A节点移动到B节点下
GameObject objA= GameObject.Find("节点A的名称");
GameObject objB= GameObject.Find("节点B的名称");
objA.transform.SetParent(objB.tansform);

//转移到场景根节点下,可将parent设置为null
objA.transform.SetParent(null);

组件的属性

脚本中,直接添加全局Public属性,会自动转换为组件的属性,在Unity设计页面的Inspector窗口中可直接看到并修改。
支持的类型:

  • 基本类型,int、float、string
  • 图片、游戏对象、组件等

Vector3

三维向量,用于移动、旋转等操作。

//指定坐标位置
transform.position= new Vector3(1,1,1);

//通过设置欧拉角的值,逆时针旋转45度。也可以通过Rotation设置,但不使用Vector3
transform.eulerAngles= new Vector3(0,0,45f);

世界坐标和本地坐标

本地坐标对应的属性:localEulerAngles、localPosition等。

Space.Self表示自己的坐标系。

以下代码表示沿着自己的方向运动

void Update()
{
        transform.Translate(0,0.2f,0,Space.Self);
}

Space

  • Self:自己的坐标系
  • World:世界坐标系

向量 Vector

相关属性

  • magnitude:向量长度
  • normalized:标准化,返回长度为1的向量。

相关方法:

  • ToString();序列化,默认只保留一位小数,可加参数:参数为"F3"表示保留三位小数。

几个标准向量(常量)

  • Vector3.right=>new Vector3(1,0,0);
  • Vector3.up=>new Vector3(0,1,0);
  • Vector3.forward=>new Vector3(0,0,1);

向量间的运算及使用

常用运算:

  • 加法
  • 减法

常用的使用场景:

  • 求两个物体间的距离:

      GameObject obj1 = GameObject.Find("obj1");
      GameObject obj2 = GameObject.Find("obj2");
      
      Vector3 v1 = obj1.transform.position;
      Vector3 v2 = obj2.transform.position;
      
      Vector3 direction = v2-v1;
      
      Debug.Log("obj1和obj2之间的距离为:" + direction.magnitude);

向量夹角

使用SignedAngle方法进行计算。

Vector3 a = new Vector3(2,2,0);
Vector3 b = new Vector3(-1,3,0);
float angle = Vector3.SignedAngle(b, a, Vector3.forward);//会有正负号
Debug.Log ("夹角为:" + angle) ;

//打印内容:夹角为:63.43495

注意:Vector3.SignedAngle返回的角度有正负号,有顺时针和逆时针区别。Vector3.Angle方法可返回没有方向(正负)的角度。

物体的三个坐标向量

  • transform.right:代表X轴的指向
  • transform.up:代表Y轴的指向
  • transform.forward:代表Z轴指向
    这三个都是标准向量

物体指向

转向,将物体A转向物体B


    GameObject objA = GameObject.Find("objA");
    GameObject objB = GameObject.Find("objB");

    Vector3 v1 = objA.transform.position;
    Vector3 v2 = objB.transform.position;

    Vector3 direction = v2 - v1;
    float angle = Vector3.SignedAngle(objA.transform.up, direction, Vector3.forward);
    objA.transform.Rotate(0, 0, angle);

屏幕坐标

获取一个物体的屏幕坐标

Vector3 pos = transform.position;
Vector3 screenPos = Camera.main.WorldToScreenPoint(pos);

屏幕边界

判断一个物体是否超出屏幕,使用屏幕坐标判断

Vector3 sp = Camera.main.WorldToScreenPoint(transform.position);
if(sp.x>Screen.width)
{
    //超出屏幕右边界
}

if(sp.x<0)
{
    //超出屏幕左边界
}

Unity中,高度是固定的,不同屏幕分辨率下,只是宽度不同。所以Y方向是否超出边界可以直接使用物体的游戏坐标去判断,而X轴方向是否超出边界需要使用屏幕坐标判断。

鼠标事件的处理

在Update()中,探测鼠标事件

if(Input.GetMouseButtonDown(0))
{
    //鼠标左键按下
}
if(Input.GetMouseButtonUp(0))
{
    //鼠标左键抬起
}
if(Input.GetMouseButton(0))
{
    //检查鼠标左键是否按下
}
  • 参数说明:0表示左键,1表示右键,2表示中键。
  • Input.mousePosition代表此时鼠标的位置(屏幕坐标)
  • 所有输入事件均由Input获取
  • GetMouseButtonDownGetMouseButtonUp只能在Update中调用。
  • GetMouseButtonDownGetMouseButtonUp代表事件,所以即使一直按着鼠标不松开,也只会被监测出一次按下。而GetMouseButton为获取鼠标状态,所以每次执行Update都会触发一次。
  • 鼠标点击后,所有物体的脚本Update里都可以捕获到。

键盘事件

  • Input.GetKeyDown(key) :键盘按下事件
  • Input.GetKeyUp(key) :键盘抬起事件
  • Input.GetKey(key) :查看状态,某键是否被按下

Unity内置枚举KeyCode来表示键盘的按键。


if(Input.GetKeyDown(KeyCode.LeftArrow))
{
    //向左的方向键被按下
}

脚本执行顺序

可通过设置进行修改。具体设置在:
Edit-Project Settings-Script Execution Order
值越小,越先执行。

脚本参数

直接在脚本中添加Public的属性即可。

预制体Prefab

通过预制体动态创建物体。

GameObject bullet = Instantiate(myPrefab);

点击鼠标发射子弹的实现:

//定义一个属性,用来存放预制体
public GameObject myPrefab;
// 在Unity界面中,将该属性设置为一个预制体。

void Update()
{
  if(Input.GetMouseButtonDown(0))
  {
    //通过预制体创建实例
    GameObject bullet = Instantiate(myPrefab);
    
  }
}

实例销毁

GameObject.Destroy(obj);

注意,销毁的是游戏对象,不要写成销毁当前组件。

Destroy(this.gameObject);//正确
Destroy(this);//错误

物理系统

刚体(RigidBody),物理系统中的物体。

通过对物体添加刚体组件可将该物体设置为刚体。

刚体的类型:

  • 普通刚体(Dynamic),有质量有速度的。
  • 静态刚体(Static),有质量,无穷大,但没有速度。适用于建筑物、地面等固定不动的物体。
  • 运动学刚体(Kinematic),无质量。忽略物理规律的刚体,一般用于碰撞监测。

碰撞体(Collider)

通过对物体添加碰撞体组件可将该物体设置为碰撞体。
两个碰撞体相遇会产生碰撞。
通过碰撞体组件属性中的 Edit Collider,可以修改碰撞体的碰撞范围

碰撞体的类型:

  • 方形(Box Collider)
  • 圆形(Circle Collider)
  • 不规则边缘(Edge Collider)
  • 胶囊形状(Capsule Collider)

背景

最近项目使用到了Unity WebGL。在加载资源包时,提示了内存不足问题。Unity版本:5.6.3

问题描述

后在Player Settings中进行配置,修改了WebGL Memory Size 的值,但在修改后出现了如下错误:

unity WebAssembly.instantiate(): memory import  has a larger maximum size

WebAssembly.instantiate(): memory import 419 is smaller than maximum 9600

原因

多次排查后,发现这个值不能随意设置,推荐使用64、128、256、512等这类整数值。

解决

知道了问题,自然好处理了,根据自己的需要,设置为512后,问题解决。