您现在访问的是微软AZURE全球版技术文档网站,若需要访问由世纪互联运营的MICROSOFT AZURE中国区技术文档网站,请访问 https://docs.azure.cn.

教程:操作模型Tutorial: Manipulating models

在本教程中,你将了解如何执行以下操作:In this tutorial, you learn how to:

  • 添加围绕远程渲染模型的视觉和操作边界Add visual and manipulation bounds around remotely rendered models
  • 移动、旋转和缩放Move, rotate, and scale
  • 使用空间查询实现光线投射Raycast with spatial queries
  • 为远程渲染对象添加简单动画Add simple animations for remotely rendered objects

先决条件Prerequisites

查询远程对象边界并应用于本地边界Query remote object bounds and apply to local bounds

要与远程对象进行交互,我们首先需要一个要用于进行交互的本地表示形式。To interact with remote objects, we need a local representation to interact with first. 对象边界对于快速操作远程对象非常有用。The objects bounds are useful for quick manipulation of a remote object. 可以使用本地实体作为参考,从 ARR 查询远程边界。The remote bounds can be queried from ARR, using the local Entity as a reference. 在将模型加载到远程会话中后查询边界。The bounds are queried after the model has been loaded into the remote session.

模型的边界由包含整个模型的框定义 - 就像 Unity 的 BoxCollider 一样,为 x、y 和 z 轴分别定义了中心和大小。The bounds of a model are defined by the box that contains the entire model - just like Unity's BoxCollider, which has a center and size defined for the x, y, z axes. 事实上,我们将使用 Unity 的 BoxCollider 来表示远程模型的边界。In fact, we'll use Unity's BoxCollider to represent the bounds of the remote model.

  1. 在与 RemoteRenderedModel 相同的目录中创建新的脚本,并将其命名为 RemoteBounds 。Create a new script in the same directory as RemoteRenderedModel and name it RemoteBounds.

  2. 将脚本的内容替换为以下代码:Replace the contents of the script with the following code:

    // Copyright (c) Microsoft Corporation. All rights reserved.
    // Licensed under the MIT License. See LICENSE in the project root for license information.
    
    using Microsoft.Azure.RemoteRendering;
    using Microsoft.Azure.RemoteRendering.Unity;
    using System;
    using UnityEngine;
    
    [RequireComponent(typeof(BaseRemoteRenderedModel))]
    public class RemoteBounds : BaseRemoteBounds
    {
        //Remote bounds works with a specific remotely rendered model
        private BaseRemoteRenderedModel targetModel = null;
    
        private RemoteBoundsState currentBoundsState = RemoteBoundsState.NotReady;
    
        public override RemoteBoundsState CurrentBoundsState
        {
            get => currentBoundsState;
            protected set
            {
                if (currentBoundsState != value)
                {
                    currentBoundsState = value;
                    BoundsStateChange?.Invoke(value);
                }
            }
        }
    
        public override event Action<RemoteBoundsState> BoundsStateChange;
    
        public void Awake()
        {
            BoundsStateChange += HandleUnityEvents;
            targetModel = GetComponent<BaseRemoteRenderedModel>();
    
            targetModel.ModelStateChange += TargetModel_OnModelStateChange;
            TargetModel_OnModelStateChange(targetModel.CurrentModelState);
        }
    
        private void TargetModel_OnModelStateChange(ModelState state)
        {
            switch (state)
            {
                case ModelState.Loaded:
                    QueryBounds();
                    break;
                default:
                    BoundsBoxCollider.enabled = false;
                    CurrentBoundsState = RemoteBoundsState.NotReady;
                    break;
            }
        }
    
        // Create an async query using the model entity
        async private void QueryBounds()
        {
            //Implement me
        }
    }
    

    备注

    如果你在 Visual Studio 中看到一个错误,声明“功能‘X’在 C# 6 中不可用。请使用语言版本 7.0 或更高版本”,可以忽略这些错误。If you see an error in Visual Studio claiming Feature 'X' is not available in C# 6. Please use language version 7.0 or greater, these error can be safely ignored. 这与 Unity 的解决方案和项目生成有关。This is related to Unity's Solution and Project generation.

    应将此脚本添加到与实现 BaseRemoteRenderedModel 的脚本相同的 GameObject 中。This script should be added to the same GameObject as the script that implements BaseRemoteRenderedModel. 在这种情况下,即为 RemoteRenderedModel。In this case, that means RemoteRenderedModel. 与之前的脚本类似,此初始代码将处理与远程边界相关的所有状态更改、事件和数据。Similar to previous scripts, this initial code will handle all the state changes, events, and data related to remote bounds.

    只剩一个方法有待实现:QueryBounds。There is only one method left to implement: QueryBounds. QueryBounds 以异步方式提取边界,获取查询结果,并将其应用到本地 BoxCollider 。QueryBounds fetches the bounds asynchronously, takes the result of the query and applies it to the local BoxCollider.

    QueryBounds 方法非常直截了当:向远程渲染会话发送查询并等待结果。The QueryBounds method is straightforward: send a query to the remote rendering session and await the result.

  3. 将 QueryBounds 方法替换为以下已完成的方法:Replace the QueryBounds method with the following completed method:

    // Create a query using the model entity
    async private void QueryBounds()
    {
        var remoteBounds = targetModel.ModelEntity.QueryLocalBoundsAsync();
        CurrentBoundsState = RemoteBoundsState.Updating;
        await remoteBounds;
    
        if (remoteBounds.IsCompleted)
        {
            var newBounds = remoteBounds.Result.toUnity();
            BoundsBoxCollider.center = newBounds.center;
            BoundsBoxCollider.size = newBounds.size;
            BoundsBoxCollider.enabled = true;
            CurrentBoundsState = RemoteBoundsState.Ready;
        }
        else
        {
            CurrentBoundsState = RemoteBoundsState.Error;
        }
    }
    

    我们将检查结果,看看是否成功。We'll check the query result to see if it was successful. 如果成功,将返回的边界转换为 BoxCollider 可以接受的格式并应用边界。If yes, convert and apply the returned bounds in a format that the BoxCollider can accept.

现在,将 RemoteBounds 脚本添加到与 RemoteRenderedModel 相同的游戏对象时,则会在需要时添加 BoxCollider,当模型达到其 Loaded 状态时,会自动查询边界,并将其应用到 BoxCollider 。Now, when the RemoteBounds script is added to the same game object as the RemoteRenderedModel, a BoxCollider will be added if needed and when the model reaches its Loaded state, the bounds will automatically be queried and applied to the BoxCollider.

  1. 使用之前创建的 TestModel GameObject,添加 RemoteBounds 组件 。Using the TestModel GameObject created previously, add the RemoteBounds component.

  2. 确认已添加脚本。Confirm the script is added.

    添加 RemoteBounds 组件

  3. 再次运行应用程序。Run the application again. 模型加载后不久,你会看到远程对象的边界。Shortly after the model loads, you'll see the bounds for the remote object. 你会看到类似以下值的内容:You'll see something like the below values:

    显示远程对象边界示例的屏幕截图。

现在,我们在 Unity 对象上有了一个配置了准确边界的本地 BoxCollider。Now we have a local BoxCollider configured with accurate bounds on the Unity object. 这些边界允许使用与本地渲染对象相同的策略进行可视化和交互。The bounds allow for visualization and interaction using the same strategies we'd use for a locally rendered object. 例如,应用可更改“转换”、物理数据等的脚本。For example, scripts that alter the Transform, physics, and more.

移动、旋转和缩放Move, rotate, and scale

移动、旋转和缩放远程渲染的对象的工作方式与其他 Unity 对象相同。Moving, rotating, and scaling remotely rendered objects works the same as any other Unity object. RemoteRenderingCoordinator 在其 LateUpdate 方法中调用当前活动会话上的 UpdateThe RemoteRenderingCoordinator, in its LateUpdate method, is calling Update on the currently active session. Update 的一部分工作是将本地模型实体的转换与远程对应项进行同步。Part of what Update does is sync local model entity transforms with their remote counterparts. 要移动、旋转或缩放远程渲染的模型,只需移动、旋转或缩放表示远程模型的 GameObject 的转换。To move, rotate, or scale a remotely rendered model, you only need to move, rotate, or scale the transform of the GameObject representing remote model. 这里,我们将修改附加了 RemoteRenderedModel 脚本的父 GameObject 的转换。Here, we're going to modify the transform of the parent GameObject that has the RemoteRenderedModel script attached to it.

本教程使用 MRTK 进行对象交互。This tutorial is using MRTK for object interaction. 大部分用于移动、旋转和缩放对象的特定于 MRTK 的实现不在本教程范围内。Most of the MRTK specific implementation for moving, rotating and scaling an object is outside the scope of this tutorial. 在“模型工具”菜单的“AppMenu”内预配置了一个模型视图控制器 。There is a model view controller that comes pre-configured inside the AppMenu, in the Model Tools menu.

  1. 确保场景中有之前创建的 TestModel GameObject。Ensure the TestModel GameObject created previously is in the scene.
  2. 确保场景中有 AppMenu prefab。Ensure the AppMenu prefab is in the scene.
  3. 按下 Unity 的播放按钮以播放场景,并打开 AppMenu 中的“模型工具”菜单 。Press Unity's Play button to play the scene and open the Model Tools menu inside the AppMenu. 视图控制器View controller

AppMenu 具有一个子菜单“模型工具”,可实现用于与模型绑定的视图控制器 。The AppMenu has a sub menu Model Tools that implements a view controller for binding with the model. 当 GameObject 包含 RemoteBounds 组件时,视图控制器将添加 BoundingBox 组件,该组件是一个 MRTK 组件,使用 BoxCollider 渲染围绕对象的边界框 。When the GameObject contains a RemoteBounds component, the view controller will add a BoundingBox component, which is an MRTK component that renders a bounding box around an object with a BoxCollider. ObjectManipulator,负责手势交互。A ObjectManipulator, which is responsible for hand interactions. 通过组合运用这些脚本,我们可以移动、旋转和缩放远程渲染的模型。These scripts combined will allow us to move, rotate, and scale the remotely rendered model.

  1. 将鼠标移到游戏面板,然后单击游戏面板内部使其拥有焦点。Move your mouse to the Game panel and click inside it to give it focus.

  2. 使用 MRTK 的手势模拟,按住左 Shift 键。Using MRTK's hand simulation, press and hold the left Shift key.

  3. 操纵模拟手,使手部射线指向测试模型。Steer the simulated hand so the hand ray is pointing to the test model.

    指向性的手部射线

  4. 左键单击并按住,然后拖动模型以移动它。Hold left click and drag the model to move it.

你应会看到远程渲染的内容随边界框一起移动。You should see the remotely rendered content move along with the bounding box. 你可能会注意到边界框和远程内容之间存在一些延迟和滞后。You might notice some delay or lag between the bounding box and the remote content. 此延迟将取决于你的 Internet 延迟和带宽。This delay will depend on your internet latency and bandwidth.

远程模型的光线投射和空间查询Ray cast and spatial queries of remote models

模型周围的框碰撞体适合用于与整个模型交互,但不够详细,无法用于与模型的各个部分进行交互。A box collider around models is suitable for interacting with the entire model, but not detailed enough to interact with individual parts of a model. 要解决此问题,可以使用远程光线投射To solve this, we can use remote ray casting. 远程光线投射是 Azure 远程渲染提供的 API,用于将光线投射到远程场景并在本地返回命中结果。Remote ray casting is an API provided by Azure Remote Rendering to cast rays into the remote scene and return hit results locally. 此技术可用于选择大型模型的子实体或获取命中结果信息,如位置、表面法线和距离。This technique can be used for selecting child entities of a large model or getting hit result information like position, surface normal, and distance.

测试模型具有许多可查询和选择的子实体。The test model has a number of sub-entities that can be queried and selected. 现在,所选内容将所选实体的名称输出到 Unity 控制台。For now, the selection will output the name of the selected Entity to the Unity Console. 查看材料、照明和效果一章,了解如何突出显示所选实体。Check the Materials, lighting and effects chapter for highlighting the selected Entity.

首先,围绕远程光线投射查询创建一个静态包装器。First, let's create a static wrapper around the remote ray cast queries. 此脚本将接受 Unity 空间中的位置和方向,将其转换为远程光线投射接受的数据类型,并返回结果。This script will accept a position and direction in Unity space, convert it to the data types accepted by the remote ray cast, and return the results. 此脚本将使用 RayCastQueryAsync API。The script will make use of the RayCastQueryAsync API.

  1. 创建名为 RemoteRayCaster 的新脚本,并将其内容替换为以下代码:Create a new script called RemoteRayCaster and replace its contents with the following code:

    // Copyright (c) Microsoft Corporation. All rights reserved.
    // Licensed under the MIT License. See LICENSE in the project root for license information.
    
    using Microsoft.Azure.RemoteRendering;
    using Microsoft.Azure.RemoteRendering.Unity;
    using System.Linq;
    using System.Threading.Tasks;
    using UnityEngine;
    
    /// <summary>
    /// Wraps the Azure Remote Rendering RayCast queries to easily send requests using Unity data types
    /// </summary>
    public class RemoteRayCaster
    {
        public static double maxDistance = 30.0;
    
        public static async Task<RayCastHit[]> RemoteRayCast(Vector3 origin, Vector3 dir, HitCollectionPolicy hitPolicy = HitCollectionPolicy.ClosestHit)
        {
            if(RemoteRenderingCoordinator.instance.CurrentCoordinatorState == RemoteRenderingCoordinator.RemoteRenderingState.RuntimeConnected)
            {
                var rayCast = new RayCast(origin.toRemotePos(), dir.toRemoteDir(), maxDistance, hitPolicy);
                var result = await RemoteRenderingCoordinator.CurrentSession.Connection.RayCastQueryAsync(rayCast);
                return result.Hits;
            }
            else
            {
                return new RayCastHit[0];
            }
        }
    
        public static async Task<Entity[]> RemoteRayCastEntities(Vector3 origin, Vector3 dir, HitCollectionPolicy hitPolicy = HitCollectionPolicy.ClosestHit)
        {
            var hits = await RemoteRayCast(origin, dir, hitPolicy);
            return hits.Select(hit => hit.HitEntity).Where(entity => entity != null).ToArray();
        }
    }
    

    备注

    Unity 有一个名为 RaycastHit 的类,Azure 远程渲染有一个名为 RayCastHit 的类 。Unity has a class named RaycastHit, and Azure Remote Rendering has a class named RayCastHit. 大写 C 是用于避免编译错误的一个重要区分信息。The uppercase C is an important difference to avoid compile errors.

    RemoteRayCaster 提供了用于将远程光线投射到当前会话的通用访问点。RemoteRayCaster provides a common access point for casting remote rays into the current session. 具体而言,我们接下来将实现 MRTK 指针处理程序。To be more specific, we'll implement an MRTK pointer handler next. 该脚本将实现 IMixedRealityPointerHandler 接口,该接口会告诉 MRTK 我们想要该脚本侦听混合现实指针事件。The script will implement the IMixedRealityPointerHandler interface, which will tell MRTK that we want this script to listen for Mixed Reality Pointer events.

  2. 创建名为 RemoteRayCastPointerHandler 的新脚本,并将该代码替换为以下代码:Create a new script called RemoteRayCastPointerHandler and replace the code with the following code:

    // Copyright (c) Microsoft Corporation. All rights reserved.
    // Licensed under the MIT License. See LICENSE in the project root for license information.
    
    using Microsoft.Azure.RemoteRendering;
    using Microsoft.MixedReality.Toolkit.Input;
    using System;
    using System.Linq;
    using System.Threading.Tasks;
    using UnityEngine;
    
    public class RemoteRayCastPointerHandler : BaseRemoteRayCastPointerHandler, IMixedRealityPointerHandler
    {
        public UnityRemoteEntityEvent OnRemoteEntityClicked = new UnityRemoteEntityEvent();
    
        public override event Action<Entity> RemoteEntityClicked;
    
        public void Awake()
        {
            // Forward events to Unity events
            RemoteEntityClicked += (entity) => OnRemoteEntityClicked?.Invoke(entity);
        }
    
        public async void OnPointerClicked(MixedRealityPointerEventData eventData)
        {
            if (RemoteEntityClicked != null) //Ensure someone is listening before we do the work
            {
                var firstHit = await PointerDataToRemoteRayCast(eventData.Pointer);
                if (firstHit.success)
                    RemoteEntityClicked.Invoke(firstHit.hit.HitEntity);
            }
        }
    
        public void OnPointerDown(MixedRealityPointerEventData eventData) { }
    
        public void OnPointerDragged(MixedRealityPointerEventData eventData) { }
    
        public void OnPointerUp(MixedRealityPointerEventData eventData) { }
    
        private async Task<(bool success, RayCastHit hit)> PointerDataToRemoteRayCast(IMixedRealityPointer pointer, HitCollectionPolicy hitPolicy = HitCollectionPolicy.ClosestHit)
        {
            RayCastHit hit;
            var result = pointer.Result;
            if (result != null)
            {
                var endPoint = result.Details.Point;
                var direction = pointer.Rays[pointer.Result.RayStepIndex].Direction;
                Debug.DrawRay(endPoint, direction, Color.green, 0);
                hit = (await RemoteRayCaster.RemoteRayCast(endPoint, direction, hitPolicy)).FirstOrDefault();
            }
            else
            {
                hit = new RayCastHit();
            }
            return (hit.HitEntity != null, hit);
        }
    }
    

当指针“点击”碰撞体(像框碰撞体那样),MRTK 即调用 RemoteRayCastPointerHandler 的 OnPointerClicked 方法。RemoteRayCastPointerHandler's OnPointerClicked method is called by MRTK when a Pointer 'clicks' on a collider, like our box collider. 之后,调用 PointerDataToRemoteRayCast,将指针的结果转换为点和方向。After that, PointerDataToRemoteRayCast is called to convert the pointer's result into a point and direction. 然后,使用该点和方向在远程会话中投射远程射线。That point and direction are then used to cast a remote ray in the remote session.

边界已更新

在单击时发送光线投射请求是查询远程对象的有效策略。Sending requests for ray casting on click is an efficient strategy for querying remote objects. 但是,这不是理想的用户体验,因为是光标与框碰撞体发生碰撞,而不是模型本身。However, it's not an ideal user experience because the cursor collides with the box collider, not the model itself.

还可以创建一个新的 MRTK 指针,该指针在远程会话中更频繁地投射光线。You could also create a new MRTK pointer that casts its rays in the remote session more frequently. 虽然这是一种更复杂的方法,但用户体验会更好。Although this is a more complex approach, the user experience would be better. 此策略超出了本教程的范围,但可以在“展示”应用中查看此方法的示例,可通过 ARR 示例存储库获取。This strategy is outside the scope of this tutorial, but an example of this approach can be seen in the Showcase App, found in the ARR samples repository.

如果在 RemoteRayCastPointerHandler 中成功完成光线投射,则会从 OnRemoteEntityClicked Unity 事件发出命中的 EntityWhen a ray cast is completed successfully in the RemoteRayCastPointerHandler, the hit Entity is emitted from the OnRemoteEntityClicked Unity event. 为了响应该事件,我们将创建一个帮助程序脚本,用于接受 Entity 并对其执行操作。To respond to that event, we'll create a helper script that accepts the Entity and performs an action on it. 首先让脚本将 Entity 的名称打印到调试日志。Let's start by getting the script to print the name of the Entity to the debug log.

  1. 创建一个名为 RemoteEntityHelper 的新脚本,并将其内容替换为以下内容:Create a new script named RemoteEntityHelper and replace its contents with the below:

    // Copyright (c) Microsoft Corporation. All rights reserved.
    // Licensed under the MIT License. See LICENSE in the project root for license information.
    
    using Microsoft.Azure.RemoteRendering;
    using Microsoft.Azure.RemoteRendering.Unity;
    using UnityEngine;
    
    public class RemoteEntityHelper : MonoBehaviour
    {
        public void EntityToDebugLog(Entity entity)
        {
            Debug.Log(entity.Name);
        }
    }
    
  2. 在之前创建的 TestModel GameObject 中,添加 RemoteRayCastPointerHandler 组件和 RemoteEntityHelper 组件 。On the TestModel GameObject created previously, add both the RemoteRayCastPointerHandler component and the RemoteEntityHelper component.

  3. EntityToDebugLog 方法分配给 OnRemoteEntityClicked 事件。Assign the EntityToDebugLog method to the OnRemoteEntityClicked event. 当事件的输出类型和方法的输入类型匹配时,我们可以使用 Unity 的动态事件挂钩,该挂钩会自动将事件值传递到方法中。When the event's output type and method's input type match, we can use Unity's dynamic event hookup, that will automatically pass the event value into the method.

    1. 创建新的回调字段 添加回调Create a new callback field Add callback
    2. 将“远程实体帮助程序”组件拖到对象字段,以引用父 GameObject 分配对象Drag Remote Entity Helper component into the Object field, to reference the parent GameObject Assign object
    3. EntityToDebugLog 分配为回调 分配回调Assign the EntityToDebugLog as the callback Assign callback
  4. 按 Unity 编辑器中的“播放”以启动场景、连接到远程会话并加载测试模型。Press play in the Unity Editor to start the scene, connect to a remote session and load the test model.

  5. 使用 MRTK 的手势模拟,按住左 Shift 键。Using MRTK's hand simulation press and hold the left Shift key.

  6. 操纵模拟手,使手部射线指向测试模型。Steer the simulated hand so the hand ray is pointing to the test model.

  7. 单击并长按以模拟隔空敲击,从而执行 OnPointerClicked 事件。Long click to simulate an air-tap, executing the OnPointerClicked event.

  8. 观察 Unity 控制台,看是否显示包含所选子实体名称的日志消息。Observe the Unity Console for a log message with the name of the child entity selected. 例如:子实体示例For example: Child entity example

将远程对象图同步到 Unity 层次结构中Synchronizing the remote object graph into the Unity hierarchy

到此时为止,我们只看到一个表示整个模型的本地 GameObject。Up to this point, we've only seen a single local GameObject representing the entire model. 这适用于整个模型的渲染和操作。This works well for rendering and manipulation of the entire model. 但是,如果我们想要应用效果或操作特定的子实体,需要创建本地 GameObject 来表示这些实体。However, if we want to apply effects or manipulate specific sub-entities, we'll need to create local GameObjects to represent those entities. 首先,可以在测试模型中手动浏览。First, we can explore manually in the test model.

  1. 启动场景并加载测试模型。Start the scene and load the test model.
  2. 在 Unity 层次结构中展开 TestModel GameObject 的子项,并选择 TestModel_Entity GameObject 。Expand the children of the TestModel GameObject in Unity's hierarchy and select the TestModel_Entity GameObject.
  3. 在检查器中,单击“显示子项”按钮。In the Inspector, click the Show Children button. 显示子项Show children
  4. 继续展开层次结构中的子项,然后单击“显示子项”,直到显示一个大的子项列表。Continue to expand children in the hierarchy and clicking Show Children until a large list of children is shown. 所有子项All children

现在,层次结构中填充了数十个实体。A list of dozens of entities will now populate the hierarchy. 选择其中一个,随即会在检查器中显示 TransformRemoteEntitySyncObject 组件。Selecting one of them will show the Transform and RemoteEntitySyncObject components in the Inspector. 默认情况下,每个实体不会自动同步每个帧,因此对 Transform 的本地更改不会同步到服务器。By default, each entity isn't automatically synced every frame, so local changes to the Transform aren't synced to the server. 你可以查看“同步每个帧”,然后在场景视图中移动、缩放或旋转转换,你不会在场景视图中看到渲染的模型,查看“游戏”视图,从视觉上体验模型位置和旋转效果的更新。You can check Sync Every Frame and then move, scale, or rotate the transform in the Scene view, you will not see the rendered model in the scene view, watch the Game view to see the model's position and rotation visually update.

上述过程可以编程方式实现,这是修改特定远程实体的第一步。The same process can be done programmatically and is the first step in modifying specific remote entities.

  1. 修改 RemoteEntityHelper 脚本以同时包含以下方法:Modify the RemoteEntityHelper script to also contain the following method:

    public void MakeSyncedGameObject(Entity entity)
    {
        var entityGameObject = entity.GetOrCreateGameObject(UnityCreationMode.DoNotCreateUnityComponents);
        var sync = entityGameObject.GetComponent<RemoteEntitySyncObject>();
        sync.SyncEveryFrame = true;
    }
    
  2. 将另一个回调添加到 RemoteRayCastPointerHandler 事件 OnRemoteEntityClicked 中,并将其设置为 MakeSyncedGameObjectAdd an additional callback to the RemoteRayCastPointerHandler event OnRemoteEntityClicked, setting it to MakeSyncedGameObject. 其他回调Additional callback

  3. 使用 MRTK 的手势模拟,按住左 Shift 键。Using MRTK's hand simulation press and hold the left Shift key.

  4. 操纵模拟手,使手部射线指向测试模型。Steer the simulated hand so the hand ray is pointing to the test model.

  5. 单击并长按以模拟隔空敲击,从而执行 OnPointerClicked 事件。Long click to simulate an air-tap, executing the OnPointerClicked event.

  6. 选中并展开层次结构以查看表示被单击的实体的新子对象。Check and expand the Hierarchy to see a new child object, representing the clicked entity. GameObject 表示形式GameObject representation

  7. 测试后,删除对 MakeSyncedGameObject 的回调,因为稍后要将其作为其他效果的一部分包含。After testing, remove the callback for MakeSyncedGameObject, since we'll incorporate this as part of other effects later.

备注

仅当需要同步转换数据时,才需要同步每个帧。Syncing every frame is only required when you need to sync the transform data. 同步转换会产生一些开销,因此应谨慎使用。There is some overhead to syncing transforms, so it should be used sparingly.

创建本地实例并使其自动同步是操作子实体的第一步。Creating a local instance and making it automatically sync is the first step in manipulating sub-entities. 操作整体模型时所使用的技术也适用于子实体。The same techniques we've used to manipulate the model as a whole can be used on the sub-entities as well. 例如,在创建实体的同步本地实例后,可以查询其边界并添加操作处理程序,使用户可通过手部射线移动它。For example, after creating a synced local instance of an entity, you could query its bounds and add manipulation handlers to allow it to be moved around by the user's hand rays.

后续步骤Next steps

你现在已可以操作远程渲染模型并与之交互了!You can now manipulate and interact with your remotely rendered models! 在下一教程中,我们将介绍如何修改材料、更改照明以及将效果应用于远程渲染模型。In the next tutorial, we'll cover modifying materials, altering the lighting, and applying effects to remotely rendered models.