找回密码
 立即注册
查看: 1103|回复: 2

Virt a mate(Vam)插件编写指南(基础篇)

[复制链接]
发表于 2021-12-17 12:12 | 显示全部楼层 |阅读模式
前言:最近发现了一款黄油界扛把子游戏,效果可以说吊打i社所有游戏。画面摈弃了i社黄油的一贯的卡通脸。布料毛发等都是实时解算,效果非常不错。除了画面,它的功能也极其强大。并且它还是原生支持VR的(它的开发团队是Meshed VR,从名字就能看出来)!
不过从严格意义上来说,我个人觉得不能把它作为当作一款游戏,而更应该把它当作一个渲染仿真引擎。毕竟它基本没有剧情和游戏玩法。
虽然它的定位是一款黄油,它最吸引我的却是它强大的扩展能力。首先它有着强大的社区支持和生态:社区里不仅有着各种模型,动画,衣服,还有很多大神写的非常牛逼的插件,具有很强的可玩性和学习价值。导入自己做的衣服也比较简单。而且这几天使用下来,个人感觉这游戏插件编写是比较人性化的,一些较繁琐的操作(比如UI)官方已经帮我们做了较好的封装。
如题,这篇文章我主要介绍的是Vam插件的编写,具体游戏使用教程请自行百度哦。相信看完我这篇文章,你也轻松的编写自己的插件了(Wei Suo Yu Wei)。
好了,废话不多说,进入正题:
反编译它的源代码可以看出,它原生就是支持插件扩展的。并且已经封装好了接口。



框起来的脚本就是官方封装的接口,编写插件只需要继承它就可以开始了

界面里也很明确的给定了插件加载的窗口,并且支持热重载(这就使得我们调试插件的时候非常方便,为MeshVR点赞)



先上一段简单代码以及效果方便讲解:

using System;
using UnityEngine;
using UnityEngine.XR;

//当VR右手柄按下扳机键时,发射子弹。
public class testdefault1 : MVRScript
{
    //子弹速度
    private JSONStorableFloat bulletSpeed;
    //字段半径
    private JSONStorableFloat bulletRadius;
    //子弹颜色
    private JSONStorableColor bulletColor;
    //子弹发射频率
    private JSONStorableFloat shotFreq;
    private JSONStorableBool isSingleShot;
    private Transform lo, ro;
    private bool gameIsVR;
    private float dur;


    public override void Init()
    {
        //SuperController.LogError("hello world1");
        try
        {
            gameIsVR = XRSettings.isDeviceActive && !string.IsNullOrEmpty(XRSettings.loadedDeviceName);
            if (gameIsVR)
            {
                //左手的物体与右手物体
                ro = SuperController.singleton.rightHand;
                lo = SuperController.singleton.leftHand;

                shotFreq = SetupSliderFloat("Freq", 0f, 0f, 5f, false);
                bulletRadius = SetupSliderFloat("Radius", 0.05f, 0.01f, 0.2f, false);
                bulletSpeed = SetupSliderFloat("Speed", 10f, 2f, 20f, false);
                bulletColor = SetUpColor("Bullet Color", new HSVColor() { H = 0.1f, S=0.1f, V=0.1f });

                isSingleShot = SetupToggle("SingleShot", false);
                shotFreq.setCallbackFunction += (q) => { dur = 1 / q; };
            }
            else
            {

            }
        }
        catch (Exception e)
        {
            SuperController.LogError("初始化错误啦" + e.Message);
            throw;
        }
    }


    bool isFired;
    float timer;
    private void Update()
    {
        if (ro != null)
        {
            //单发模式
            if (isSingleShot.val)
            {
                if (GetRightTriggerVal() > 0.3f)
                {
                    if (!isFired)
                    {
                        isFired = true;
                        Fashe();
                    }
                }
                else
                {
                    isFired = false;
                }
               
            }
            else
            {
                //连续发射模式
                if (GetRightTriggerVal() > 0.8)
                {
                    timer += Time.deltaTime;
                    if (timer > dur)
                    {
                        timer = 0;
                        Fashe();
                    }
                }
            }
        }
    }

    private void Fashe()
    {
        SuperController.LogMessage("发射");
        var bullet = GameObject.CreatePrimitive(PrimitiveType.Sphere);
        bullet.transform.localScale = Vector3.one * bulletRadius.val;
        bullet.GetComponent<MeshRenderer>().material.color = HSVColorPicker.HSVToRGB(bulletColor.val);
        bullet.AddComponent<SphereCollider>();
        bullet.transform.position = ro.transform.position;
        var rgd = bullet.AddComponent<Rigidbody>();
        rgd.velocity = ro.transform.forward * bulletSpeed.val;

        Destroy(bullet, 6f);
    }

    //创建一个颜色拾取UI
    private JSONStorableColor SetUpColor(string name, HSVColor color)
    {
        JSONStorableColor storable = new JSONStorableColor(name,color);
        storable.storeType = JSONStorableParam.StoreType.Full;
        CreateColorPicker(storable);
        RegisterColor(storable);
        return storable;
    }

    //创建一个ToggleUI
    private JSONStorableBool SetupToggle(string name,  bool singleShot)
    {
        JSONStorableBool storable = new JSONStorableBool(name,false);
        storable.storeType = JSONStorableParam.StoreType.Full;
        CreateToggle(storable, singleShot);
        RegisterBool(storable);
        return storable;
    }

    //创建一个滑块UI
    private JSONStorableFloat SetupSliderFloat(string name, float defaultValue, float minValue, float maxValue, bool rightSide)
    {
        JSONStorableFloat storable = new JSONStorableFloat(name, defaultValue, minValue, maxValue, true, true);
        storable.storeType = JSONStorableParam.StoreType.Full;
        CreateSlider(storable, rightSide);
        RegisterFloat(storable);
        return storable;
    }

}

简单效果:

Vam插件编写及软体物理
https://www.zhihu.com/video/1454054959931691008

  • 从上面的代码可以看到:
我们的脚本首先继承了MVRScript这个类


在Vam中,我们编写的插件都会继承的这个类。它实际上是一个monobehaviour。它里面封装了插件初始化,一些常用的元素获取以及UI动态创建的方法。如:GetAtomById(string uid)——从ID获取原子;GetRightTriggerVal()——获取VR手柄扳机的值;CreateColorPicker(storable)——创建颜色拾取器等。在重写的初始化方法中我们需要对我们的插件进行初始化。


SuperController.cs:这个可以说是除了MVRScript外最重要的类了,代码也是超级多。里面包含了如:
场景中各种物体的引用如:各种相机,vr手柄物体,UI物体等。
各种map映射:场景中的各种原子,动画等map。
各种游戏全局型的变量以及事件,Log等。
上面只是列了一些,总的来说我们插件编写过程需要用到的变量和物体基本在里面都可以拿到。我们平时编写插件时如果需要某些引用或变量,去里面翻就对了。
有了这两个类,我们就可以进行插件编写了。
主要讲讲两个方面:

  • UI实现
从上面的代码我们可以看到,我们声明变量使用了
private JSONStorableColor bulletColor;的形式,从类型的名称就容易想到,这是vam内部封装的一种可以用Json序列化的类型,在我们创建UI时就需要用到它,这样当我们修改UI的内容时,内容能被实时保存和应用。从这个类型我们可以很容易动态的创建出对应的UI,而不需要我们去考虑UI的具体生成过程:
//动态创建一个颜色拾取UI
    private JSONStorableColor SetUpColor(string name, HSVColor color)
    {
        //创建一个Json可序列化颜色变量
        JSONStorableColor storable = new JSONStorableColor(name,color);
        storable.storeType = JSONStorableParam.StoreType.Full;
        //调用MVRScript中封装的创建ColorPicker的方法
        CreateColorPicker(storable);
        //注册
        RegisterColor(storable);
        return storable;
    }
此外JSONStorableXXX 中还封装有回调方法,当它的值在UI中被修改时,会自动调用相关回调,相当方便。
private JSONStorableFloat shotFreq;
shotFreq = SetupSliderFloat("Freq", 0f, 0f, 5f, false);
shotFreq.setCallbackFunction += (q) => { dur = 1 / q; };
这样几行简要的代码就实现了我们要的ColorPicker面板:


可以看出,UI方面vam已经帮我们封装的非常好了(点赞)。

  • 发射子弹
逻辑方面没什么好讲的,看下前面的代码就清楚了。主要讲下前面提到的一些vam内部方法的应用。
//判断是否为VR模式
gameIsVR = XRSettings.isDeviceActive && !string.IsNullOrEmpty(XRSettings.loadedDeviceName);
            if (gameIsVR)
            {
                //获取左手的物体与右手物体
                ro = SuperController.singleton.rightHand;
                lo = SuperController.singleton.leftHand;
如上,我们就从SuperController中取到了VR中左右手的Transform。此外SuperController中的调试方法也是非常重要的:
SuperController.LogError("hello world1");
SuperController.LogMessage("hello world");
这样可以方便我们对插件进行调试。





  • 最后
插件基本结构写好之后,我们就可以在vam中加载插件了并启用了(具体流程和其他插件加载流程完全一致),配合调试窗口和热重载特性我们可以很方便的对代码进行调试。

  • 后面我会结合一些大神开发的具体插件来稍微深入的讲讲vam插件的相关开发,敬请期待哦!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2021-12-17 12:19 | 显示全部楼层
技术流分析 vam成也自由度 败也自由度 门槛过高导致劝退很多人 不像i社直接玩 人少也导致没有大神来做资源 而且作者跳票了两年2x版本 不知道未来会怎么样
发表于 2021-12-17 12:26 | 显示全部楼层
[干杯]
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-5-9 10:12 , Processed in 0.199930 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表