jjzjj

[Unity2D独立/合作开发]实现记录物品在不同场景的存在状态,附:场景的淡入淡出功能和预加载

dangoxiba 2025-02-02 原文

学习目标:

大家好啊我是说的道理,今天来点大家想看的东西,就学习如何实现记录物品在不同场景的存在状态,这句话的意思我想表达的是一个物品如果消失在第一个场景,如果此时你进入第二个场景并回到第一个场景的时候,你会发现消失的物品又会回到原处,这是因为每次加载一个场景的时候运行游戏时的场景又会再实例化一次,所以我今天要做的就是用数据结构的方式来给每一个物品Item独一无二的GUID,在销毁的时候直接将数据彻底删除,话不多说就开始吧。


学习内容:

// 在本节课重点之前,我们先来制作一个场景加载控制器,新建一个空对象

在Enums脚本下新建场景名:

public enum SceneName
{
    Scene1_Farm,
    Scene2_Field,
    Scene3_Cabin,

}

SceneControllerManager并给它同名脚本,内容如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class SceneControllerManager : Singleton<SceneControllerManager>
{
    private bool isFading;
    [SerializeField] private float fadeDuration = 1f;
    [SerializeField] private CanvasGroup fadeCanvasGroup = null;
    [SerializeField] private Image fadeImage = null;
    public SceneName startingSceneName;

    private IEnumerator Start()
    {
        fadeImage.color = new Color(0f, 0f, 0f, 1f);
        fadeCanvasGroup.alpha = 1f;

        yield return StartCoroutine(LoadSceneAndSetActiveCoroutine(startingSceneName.ToString()));

        EventHandler.CallAfterSceneLoadEvent();

        SaveStoreManager.Instance.RestoreCurrentSceneData();

        StartCoroutine(FadeCoroutine(0f));
    }

    public void FadeAndLoadScene(string sceneName, Vector3 spawnPosition)
    {
        if (!isFading)
        {
            StartCoroutine(FadeAndSwitchScenesCoroutine(sceneName, spawnPosition));
        }
    }

    private IEnumerator FadeAndSwitchScenesCoroutine(string sceneName,Vector3 spawnPosition)
    {
        EventHandler.CallBeforeSceneUnloadFadeOutEvent();

        yield return StartCoroutine(FadeCoroutine(1f));

        SaveStoreManager.Instance.StoreCurrentSceneData();

        PlayerController.Instance.gameObject.transform.position = spawnPosition;

        EventHandler.CallBeforeSceneUnloadEvent();

        yield return SceneManager.UnloadSceneAsync(SceneManager.GetActiveScene().buildIndex);

        yield return StartCoroutine(LoadSceneAndSetActiveCoroutine(sceneName));

        EventHandler.CallAfterSceneLoadEvent();

        SaveStoreManager.Instance.RestoreCurrentSceneData();

        yield return StartCoroutine(FadeCoroutine(0f));

        EventHandler.CallAfterSceneLoadFadeInEvent();
    }

    private IEnumerator FadeCoroutine(float finalAlpha)
    {
        isFading = true;

        fadeCanvasGroup.blocksRaycasts = true;

        float fadeSpeed = Mathf.Abs(fadeCanvasGroup.alpha - finalAlpha) / fadeDuration;

        while (!Mathf.Approximately(fadeCanvasGroup.alpha, finalAlpha))
        {
            fadeCanvasGroup.alpha = Mathf.MoveTowards(fadeCanvasGroup.alpha, finalAlpha, fadeSpeed * Time.deltaTime);

            yield return null;
        }


        isFading = false;

        fadeCanvasGroup.blocksRaycasts = false;
    }

    private IEnumerator LoadSceneAndSetActiveCoroutine(string sceneName)
    {
        yield return SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);

        Scene newlyLoadedScene = SceneManager.GetSceneAt(SceneManager.sceneCount - 1);

        SceneManager.SetActiveScene(newlyLoadedScene);
    }
}

 对于EvnetHandler,这是一个事件处理器,适用于我们开发模式:观察者模式,具体可以看上一期文章,那么先把本期要用的事件创建好,分别对应的场景加载完成前淡出的事件,场景加载完成前的事件,场景刚开始加载后淡入的事件,场景刚开始加载后的事件。

public static class EventHandler
{
   //上一期的事件


    //场景加载事件
    public static event Action BeforeSceneUnloadFadeOutEvent;

    public static void CallBeforeSceneUnloadFadeOutEvent()
    {
        if(BeforeSceneUnloadFadeOutEvent!= null)
        {
            BeforeSceneUnloadFadeOutEvent();
        }
    }

    public static event Action BeforeSceneUnloadEvent;

    public static void CallBeforeSceneUnloadEvent()
    {
        if (BeforeSceneUnloadEvent != null)
        {
            BeforeSceneUnloadEvent();
        }
    }

    public static event Action AfterSceneLoadEvent;

    public static void CallAfterSceneLoadEvent()
    {
        if (AfterSceneLoadEvent != null)
        {
            AfterSceneLoadEvent();
        }
    }

    public static event Action AfterSceneLoadFadeInEvent;

    public static void CallAfterSceneLoadFadeInEvent()
    {
        if (AfterSceneLoadFadeInEvent != null)
        {
            AfterSceneLoadFadeInEvent();
        }
    }

 我们把做好的场景加载持久化场景下面作为Additive,然后去

BuildSettings添加好三个场景 

 

 

 刚开始运行游戏的时候只会加载第一个场景,现在淡入淡出实现,那怎么去第二个场景呢?

这里就用Trigger来写个传送门,创建一个空对象叫SceneTeleport然后同名脚本

 

脚本内容如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(BoxCollider2D))]
public class SceneTeleport : MonoBehaviour
{
    [SerializeField] private SceneName sceneNameToGo = SceneName.Scene1_Farm;
    [SerializeField] private Vector3 scenePositionToGo = new Vector3();

    private void OnTriggerStay2D(Collider2D collision)
    {
        if(collision.TryGetComponent<PlayerController>(out PlayerController player))
        {
            Debug.Log("成功与玩家触发条件");
            float xPosition = Mathf.Approximately(scenePositionToGo.x, 0) ? player.transform.position.x : scenePositionToGo.x;
            float yPosition = Mathf.Approximately(scenePositionToGo.y, 0) ? player.transform.position.y : scenePositionToGo.y;
            float zPosition = Mathf.Approximately(scenePositionToGo.z, 0) ? player.transform.position.z : scenePositionToGo.z;

            SceneControllerManager.Instance.FadeAndLoadScene(sceneNameToGo.ToString(), new Vector3(xPosition,yPosition,zPosition));

        }
    }
}

别忘了把这个对象做成Prefab,就这样我们找到第二个场景要传送的位置,然后添加好,每个场景的传送点都要设置好。

 

搞好了别忘了每次运行游戏的时候要把这三个场景Unload一下只让PersistenceScene加载

 

 

 如果你已经成功了,那么还是得好好恭喜一下,接下来就到了我们的重头戏了

 


实现记录物品在不同场景的存在状态:

进入正题

  首先面对不同场景,他们的物品位置和状态都是各有不同的,因此我们需要先记录下它们的位置和状态,

  先从位置开始写一个脚本

  

[System.Serializable]

public class Vector3Serializable
{
    public float x;
    public float y;
    public float z;

    public Vector3Serializable(float x,float y,float z)
    {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    public Vector3Serializable()
    {

    }
}

然后去Enums脚本新建枚举 

public enum InventoryLocation
{
    player,
    chest,
    count,
}

public enum ToolEffect
{
    none,
    watering,
}

public enum Direction
{
    up,
    down,
    right,
    left,
}

public enum ItemType
{
    Seed,
    Commodity,
    Watering_tool,
    Hoeing_tool,
    Chopping_tool,
    Breaking_tool,
    Reping_tool,
    Collecting_tool,
    Reapable_scenery,
    Furinture,
    None,
    Count,

}

再创建一个SceneItem


[System.Serializable]
public class SceneItem 
{
    public int itemCode;
    public Vector3Serializable position;
    public string itemName;

    public SceneItem()
    {
        position = new Vector3Serializable();
    }
}

 接下来是能添加独特修饰符的GenerateGUID


using UnityEngine;

[ExecuteAlways]
public class GenerateGUID : MonoBehaviour
{
    [SerializeField] private string _gUID = "";

    public string GUID
    {
        get => _gUID;
        set => _gUID = value;
    }

    private void Awake()
    {
        if (!Application.IsPlaying(gameObject))
        {
            if(_gUID == "")
            {

                //登记GUID
                _gUID = System.Guid.NewGuid().ToString();
            }
        }
    }
}

   这里新学的一个特性
[ExecuteAlways]

意思是即使在Unity编辑器的状态下添加脚本,也会按游戏运行之后的内容来执行,

!Application.IsPlaying(gameObject); 感叹号反义,表示在没有运行游戏时,

对于不同场景的物品,我们用一个类的链表来存储属于某个场景的SceneItem

using System.Collections;
using System.Collections.Generic;

[System.Serializable]
public class SceneSave 
{
    public List<SceneItem> listSceneItem;
}

然后我们把这三个场景的数据即SceneSave,存储在一个字典当中 ,写了一个重载

using System.Collections;
using System.Collections.Generic;

[System.Serializable]
public class GameObjectSave 
{
    public Dictionary<string, SceneSave> sceneData;

    public GameObjectSave()
    {
        sceneData = new Dictionary<string, SceneSave>();
    }

    public GameObjectSave(Dictionary<string,SceneSave> sceneData)
    {
        this.sceneData = sceneData;
    }
}

 

我们还需要一个接口,实现物品的存储,重新存储,物品的GUID,登记场景中的物品,去除登记场景中的物品

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public interface ISaveable 
{
    string ISaveableUniqueID { get; set; }

    GameObjectSave GameObjectSave { get; set; }

    void ISaveableRegister();

    void ISaveableDeregister();

    void ISaveableStoreScene(string sceneName);

    void ISaveableRestoreScene(string sceneName);
}

 准备工作完成,那么我们怎么来实现这些接口来让场景的数据以及场景中的物品Item信息能够存储和取出你?

  就先创建一个空对象叫SceneItemManager并创建同名脚本给它

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(GenerateGUID))] //需要组件GenerateGUID
public class SceneItemsManager : Singleton<SceneItemsManager>, ISaveable //继承ISaveable并实现方法
{
    private Transform parentItem;
    [SerializeField] private GameObject itemPrefab = null;

    private string _iSaveableUniqueId;

    public string ISaveableUniqueID { get => _iSaveableUniqueId; set => _iSaveableUniqueId = value; } //GUID属性,每帧更新

    private GameObjectSave _gameObjectSave;
    public GameObjectSave GameObjectSave { get => _gameObjectSave; set => _gameObjectSave = value; } //scneneData属性,更新

    protected override void Awake()
    {
        base.Awake();

        ISaveableUniqueID = GetComponent<GenerateGUID>().GUID;
        GameObjectSave = new GameObjectSave();
    }

    private void OnEnable()
    {
        ISaveableRegister();
        EventHandler.AfterSceneLoadEvent += AfterSceneLoaded;
    }

    private void OnDisable()
    {
        ISaveableDeregister();
        EventHandler.AfterSceneLoadEvent -= AfterSceneLoaded;
    }

    /// <summary>
    /// 在AfterSceneLoadEvent触发后,才能执行parentItem的实例化
    /// </summary>
    public void AfterSceneLoaded()
    {
        parentItem = GameObject.FindGameObjectWithTag(Tags.ItemsParentTransform).transform;
    }

    /// <summary>
    /// 销毁所有场景中的物品
    /// </summary>
    private void DestorySceneItems()
    {
        Item[] itemInScenes = GameObject.FindObjectsOfType<Item>();

        for (int i = itemInScenes.Length -1; i > -1; i--)
        {
            Destroy(itemInScenes[i].gameObject);
        }
    }

    /// <summary>
    /// 重新加载场景中的所有物品,先销毁所有物品游戏对象再初始化
    /// </summary>
    /// <param name="sceneName"></param>
    public void ISaveableRestoreScene(string sceneName)
    {
        if(GameObjectSave.sceneData.TryGetValue(sceneName,out SceneSave sceneSave))//找到特定的sceneName字符串,如果找到了就销毁该场景的
        {
            if(sceneSave.listSceneItem!= null )
            {
                DestorySceneItems();

                InstantiateSceneItems(sceneSave.listSceneItem);
            }
        }
    }

    /// <summary>
    /// 初始化SceneItem链表,并给所有Item重新赋值
    /// </summary>
    /// <param name="sceneItemList"></param>
    private void InstantiateSceneItems(List<SceneItem> sceneItemList)
    {
        GameObject itemGameObject;
        foreach (SceneItem sceneItem in sceneItemList)
        {
            itemGameObject = Instantiate(itemPrefab, new Vector3(sceneItem.position.x, sceneItem.position.y, sceneItem.position.z), Quaternion.identity, parentItem);

            Item item = itemGameObject.GetComponent<Item>();

            item.ItemCode = sceneItem.itemCode;
            item.name = sceneItem.itemName;

        }
    }

    public void InstantiateSceneItem(int itemCode,Vector3 itemPosition)
    {
        GameObject itemGameObject = Instantiate(itemPrefab, itemPosition, Quaternion.identity, parentItem);
        Item item = itemGameObject.GetComponent<Item>();
        item.Init(itemCode);        
    }

    /// <summary>
    /// 将场景中的所有Item存储起来
    /// </summary>
    /// <param name="sceneName"></param>
    public void ISaveableStoreScene(string sceneName)
    {
        GameObjectSave.sceneData.Remove(sceneName); //先清空该场景

        List<SceneItem> sceneItemLists = new List<SceneItem>();
        Item[] itemsInScene = FindObjectsOfType<Item>();

        foreach (Item item in itemsInScene)
        {
            SceneItem sceneItem = new SceneItem();
            sceneItem.itemCode = item.ItemCode;
            sceneItem.position = new Vector3Serializable(item.transform.position.x, item.transform.position.y, item.transform.position.z);
            sceneItem.itemName = item.name;

            sceneItemLists.Add(sceneItem);
        }

        SceneSave sceneSave = new SceneSave();
        sceneSave.listSceneItem = sceneItemLists;

        GameObjectSave.sceneData.Add(sceneName, sceneSave);
    }

    /// <summary>
    /// 移除SaveStoreManager.Instance.iSaveableObjectLists链表的该数据
    /// </summary>
    public void ISaveableDeregister()
    {
        SaveStoreManager.Instance.iSaveableObjectLists.Remove(this);
    }


    /// <summary>
    /// 添加SaveStoreManager.Instance.iSaveableObjectLists链表的该数据
    /// </summary>
    public void ISaveableRegister()
    {
        SaveStoreManager.Instance.iSaveableObjectLists.Add(this);
    }
}

还需要一个SceneLodeManager 游戏对象以及同名脚本,用于场景加载的时候调用


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class SaveStoreManager : Singleton<SaveStoreManager>
{
    public List<ISaveable> iSaveableObjectLists;

    protected override void Awake()
    {
        base.Awake();

        iSaveableObjectLists = new List<ISaveable>();
    }

    public void StoreCurrentSceneData()
    {
        foreach (ISaveable iSaveableObjectList in iSaveableObjectLists)
        {
            iSaveableObjectList.ISaveableStoreScene(SceneManager.GetActiveScene().name);
        }
    }

    public void RestoreCurrentSceneData()
    {
        foreach (ISaveable iSaveableObjectList in iSaveableObjectLists)
        {
            iSaveableObjectList.ISaveableRestoreScene(SceneManager.GetActiveScene().name);
        }
    }
}

回到场景加载控制器脚本,我们可以把报错的内容全部消除了。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class SceneControllerManager : Singleton<SceneControllerManager>
{
    private bool isFading;
    [SerializeField] private float fadeDuration = 1f;
    [SerializeField] private CanvasGroup fadeCanvasGroup = null;
    [SerializeField] private Image fadeImage = null;
    public SceneName startingSceneName;

    private IEnumerator Start()
    {
        fadeImage.color = new Color(0f, 0f, 0f, 1f);
        fadeCanvasGroup.alpha = 1f;

        yield return StartCoroutine(LoadSceneAndSetActiveCoroutine(startingSceneName.ToString()));

        EventHandler.CallAfterSceneLoadEvent();

        SaveStoreManager.Instance.RestoreCurrentSceneData();

        StartCoroutine(FadeCoroutine(0f));
    }

    public void FadeAndLoadScene(string sceneName, Vector3 spawnPosition)
    {
        if (!isFading)
        {
            StartCoroutine(FadeAndSwitchScenesCoroutine(sceneName, spawnPosition));
        }
    }

    private IEnumerator FadeAndSwitchScenesCoroutine(string sceneName,Vector3 spawnPosition)
    {
        EventHandler.CallBeforeSceneUnloadFadeOutEvent();

        yield return StartCoroutine(FadeCoroutine(1f));

        SaveStoreManager.Instance.StoreCurrentSceneData();

        PlayerController.Instance.gameObject.transform.position = spawnPosition;

        EventHandler.CallBeforeSceneUnloadEvent();

        yield return SceneManager.UnloadSceneAsync(SceneManager.GetActiveScene().buildIndex);

        yield return StartCoroutine(LoadSceneAndSetActiveCoroutine(sceneName));

        EventHandler.CallAfterSceneLoadEvent();

        SaveStoreManager.Instance.RestoreCurrentSceneData();

        yield return StartCoroutine(FadeCoroutine(0f));

        EventHandler.CallAfterSceneLoadFadeInEvent();
    }

    private IEnumerator FadeCoroutine(float finalAlpha)
    {
        isFading = true;

        fadeCanvasGroup.blocksRaycasts = true;

        float fadeSpeed = Mathf.Abs(fadeCanvasGroup.alpha - finalAlpha) / fadeDuration;

        while (!Mathf.Approximately(fadeCanvasGroup.alpha, finalAlpha))
        {
            fadeCanvasGroup.alpha = Mathf.MoveTowards(fadeCanvasGroup.alpha, finalAlpha, fadeSpeed * Time.deltaTime);

            yield return null;
        }


        isFading = false;

        fadeCanvasGroup.blocksRaycasts = false;
    }

    private IEnumerator LoadSceneAndSetActiveCoroutine(string sceneName)
    {
        yield return SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);

        Scene newlyLoadedScene = SceneManager.GetSceneAt(SceneManager.sceneCount - 1);

        SceneManager.SetActiveScene(newlyLoadedScene);
    }
}

 检查一下你有没有挂载好脚本呢

 

更改脚本的执行顺序

 

 

 回到UIInventorySlot脚本,我们需要在事件触发之后才能实例化。更改如下:

private void OnEnable()
    {
        EventHandler.AfterSceneLoadEvent += SceneLoaded;
    }

    private void OnDisable()
    {
        EventHandler.AfterSceneLoadEvent -= SceneLoaded;
    }

    public void SceneLoaded()
    {
        itemParent = GameObject.FindGameObjectWithTag(Tags.ItemsParentTransform).transform;
    }

学习产出:

淡入淡出 

 

不同场景切换的时候数据结构发挥作用,让被拾取的Item被销毁并且不再实例化 

 

 

 

 

有关[Unity2D独立/合作开发]实现记录物品在不同场景的存在状态,附:场景的淡入淡出功能和预加载的更多相关文章

  1. ruby - 在 Ruby 程序执行时阻止 Windows 7 PC 进入休眠状态 - 2

    我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0

  2. ruby-on-rails - 独立 ruby​​ 脚本的配置文件 - 2

    我有一个在Linux服务器上运行的ruby​​脚本。它不使用rails或任何东西。它基本上是一个命令行ruby​​脚本,可以像这样传递参数:./ruby_script.rbarg1arg2如何将参数抽象到配置文件(例如yaml文件或其他文件)中?您能否举例说明如何做到这一点?提前谢谢你。 最佳答案 首先,您可以运行一个写入YAML配置文件的独立脚本:require"yaml"File.write("path_to_yaml_file",[arg1,arg2].to_yaml)然后,在您的应用中阅读它:require"yaml"arg

  3. ruby - Sinatra:运行 rspec 测试时记录噪音 - 2

    Sinatra新手;我正在运行一些rspec测试,但在日志中收到了一堆不需要的噪音。如何消除日志中过多的噪音?我仔细检查了环境是否设置为:test,这意味着记录器级别应设置为WARN而不是DEBUG。spec_helper:require"./app"require"sinatra"require"rspec"require"rack/test"require"database_cleaner"require"factory_girl"set:environment,:testFactoryGirl.definition_file_paths=%w{./factories./test/

  4. ruby-on-rails - 跳过状态机方法的所有验证 - 2

    当我的预订模型通过rake任务在状态机上转换时,我试图找出如何跳过对ActiveRecord对象的特定实例的验证。我想在reservation.close时跳过所有验证!叫做。希望调用reservation.close!(:validate=>false)之类的东西。仅供引用,我们正在使用https://github.com/pluginaweek/state_machine用于状态机。这是我的预订模型的示例。classReservation["requested","negotiating","approved"])}state_machine:initial=>'requested

  5. ruby-on-rails - Rails 5 Active Record 记录无效错误 - 2

    我有两个Rails模型,即Invoice和Invoice_details。一个Invoice_details属于Invoice,一个Invoice有多个Invoice_details。我无法使用accepts_nested_attributes_forinInvoice通过Invoice模型保存Invoice_details。我收到以下错误:(0.2ms)BEGIN(0.2ms)ROLLBACKCompleted422UnprocessableEntityin25ms(ActiveRecord:4.0ms)ActiveRecord::RecordInvalid(Validationfa

  6. ruby - 字符串文字中的转义状态作为 `String#tr` 的参数 - 2

    对于作为String#tr参数的单引号字符串文字中反斜杠的转义状态,我觉得有些神秘。你能解释一下下面三个例子之间的对比吗?我特别不明白第二个。为了避免复杂化,我在这里使用了'd',在双引号中转义时不会改变含义("\d"="d")。'\\'.tr('\\','x')#=>"x"'\\'.tr('\\d','x')#=>"\\"'\\'.tr('\\\d','x')#=>"x" 最佳答案 在tr中转义tr的第一个参数非常类似于正则表达式中的括号字符分组。您可以在表达式的开头使用^来否定匹配(替换任何不匹配的内容)并使用例如a-f来匹配一

  7. ruby - Net::HTTP 获取源代码和状态 - 2

    我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur

  8. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

  9. FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D) - 2

    本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01  客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02  数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit

  10. unity---接入Admob - 2

    目录1.AdmobSDK下载地址2.将下载好的unityPackagesdk导入到unity里​编辑 3.解析依赖到项目中

随机推荐