【grasshopper自定义电池开发】使用Visual Studio 2022借助官方扩展插件开发一个贪吃蛇电池

一、 为什么使用Visual Studio 2022和C#?

Visual Studio中拥有官方的grasshopper扩展插件,可以提供一个模板快速开发。
C#是grasshopper的原生语言,该使用语言开发扩展性,兼容性最好。

二、.如何创建一个空grasshopper项目?

a.下载并安装Visual Studio

  1. 如果是第一次使用Visual Studio那选用Community版本(社区版本)就可以,这三个版本中只有Community版本是可以免费使用的;
  2. 确保在Visual Studio安装时,在“工作负载”选项卡下选择了“桌面应用和移动应用”下的“.NET桌面开发”组件包,其组件包描述中有 .NET Framework 这个词的出现,因为这个是GH电池的依赖框架;
  3. 如果在之前安装Visual Studio时未选择安装上述组件包,随时可以使用Visual Studio Installer额外加装上述组件包;

b.安装扩展并使用

  1. 打开VS后,先点击“继续但无需代码”。进入编辑界面。
    在这里插入图片描述
  2. 在最顶端的菜单栏中找到“扩展(X)”-“管理扩展(M)”,搜索Grasshopper,安装该扩展。然后需要重启VS来应用扩展。
  3. 新建一个项目,使用项目模板,搜索Grasshopper的模板并使用,此处使用的C#语言版本的。
    在这里插入图片描述
    4.添加新项目后,会要求输入一些电池相关的信息。如下:
    在这里插入图片描述

1.Add-on display name :决定了这个电池在GH插件加载的时候显示的名字。
2.Component class :是C#的类的名字,与GH显示无关,这里可以使用默认值,任何不能作为C#的类名的字符都不可以出现在这里。
3.Name :是该电池在GH里会显示的名字。
4.Nickname :是该电池在GH里选择显示简称时的名字。
5.Category :是该电池在GH里会被放在的大类的名字,比如Params、Math、Sets等。GH如果本身不存在输入的大类的名字的话,GH会为这个电池单独创建一个大类。在这里我们可以填入MyComp,并且以后所有的电池都可以填相同的大类,这样方便我们找到自己创建的电池。
6.Subcategory:是该电池在GH里会被放在大类下的哪个子类里。
7.Description :是该电池在GH里显示的额外描述性信息。

c. 添加依赖

  1. 项目创建完成后,可以发现,引入的包Grasshopper.Kernel、Rhino.Geometry;变红,说明没有依赖上。
    在这里插入图片描述
  2. 我们需要手动修改依赖:点击项目的顶级,会弹出项目的配置文件
    在这里插入图片描述
    进行修改。
//删除该段,因为无法找到该包
  <ItemGroup>
    <PackageReference Include="Grasshopper" Version="7.13.21348.13001" IncludeAssets="compile;build" />
  </ItemGroup>

//替换为以下两段:若Rhino是默认安装在C盘,则可以直接使用
    <ItemGroup>
    <Reference Include="Grasshopper">
      <HintPath>C:\Program Files\Rhino 7\Plug-ins\Grasshopper\Grasshopper.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="RhinoCommon">
      <HintPath>C:\Program Files\Rhino 7\System\RhinoCommon.dll</HintPath>
      <Private>False</Private>
    </Reference>
  </ItemGroup>

修改并保存后,可以发现,错误已经消失了。此时空项目便做好了,可以正常编译。

d. 添加GH电池目录

GH的电池需要放置在特定目录才能被加载,我们将工程目录添加到GH电池目录,GH就可以自动加载最新的电池使用。
首先打开Rhino,在命令栏里输入GrasshopperDeveloperSettings

1.把 Memory load *.GHA assemblies using COFF byte arrays 这个复选框前的钩去掉,该选项会影响我们代码的断点调试
2.点击 Add Folder
3.点击右侧的 … , 将我们刚刚的代码的存储位置下的 bin 文件夹选中,这样GH就知道去这个文件夹找到刚刚我们生成的 gha 文件了。

  1. 设置完成后,打开GH应该就可以找到我们刚刚创建的空插件了。

三、贪吃蛇电池

a. 功能

  1. 蛇吃食物能增长身体
  2. 设定蛇的运动平面,包括边界
  3. 设置x与y轴的格数
  4. 蛇能穿越边界并从另一端回归
  5. 蛇不能原地掉头

b.具体代码

类成员变量:

public sealed class Dir
        {
    
    
            public static readonly Dir Up = new Dir("Up");
            public static readonly Dir Down = new Dir("Down");
            public static readonly Dir Left = new Dir("Left");
            public static readonly Dir Right = new Dir("Right");

            private Dir(string value)
            {
    
    
                Name = value;

            }

            public string Name {
    
     get; private set; }
        }


        Point3d headPoint = new Point3d(0, 0, 0);

        double headXinPlane = 0;
        double headYinPlane = 0;

        List<Point3d> bodyPoints = new List<Point3d>() {
    
     new Point3d(0, 0, 0) };

        int seed = 0;

        Circle lastFood = new Circle(1);
        List<Curve> lastSnake = new List<Curve>();

        int nowdirection = 0;

定义输入:

protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
        {
    
    
            pManager.AddRectangleParameter("rectangle", "rec", "蛇运动平面", GH_ParamAccess.item);
            pManager.AddIntegerParameter("xDivideNumber", "xDiv", "x轴分割的块数", GH_ParamAccess.item);
            pManager.AddIntegerParameter("yDivideNumber", "yDiv", "y轴分割的块数", GH_ParamAccess.item);
            pManager.AddBooleanParameter(Dir.Up.Name, Dir.Up.Name, "连接一个按钮来让蛇向+y方向转", GH_ParamAccess.item);
            pManager.AddBooleanParameter(Dir.Down.Name, Dir.Down.Name, "连接一个按钮来让-y方向转", GH_ParamAccess.item);
            pManager.AddBooleanParameter(Dir.Left.Name, Dir.Left.Name, "连接一个按钮来让蛇向+x方向转", GH_ParamAccess.item);
            pManager.AddBooleanParameter(Dir.Right.Name, Dir.Right.Name, "连接一个按钮来让蛇向-x方向转", GH_ParamAccess.item);
            pManager.AddBooleanParameter("Reset", "Reset", "连接一个按钮来重置游戏.", GH_ParamAccess.item);
        }

定义输出:

        protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
        {
    
    
            pManager.AddCurveParameter("Snake", "Snake", "蛇身体的曲线", GH_ParamAccess.list);
            pManager.AddCircleParameter("Food", "Food", "事物的圆", GH_ParamAccess.item);
        }

逻辑:

protected override void SolveInstance(IGH_DataAccess DA)
        {
    
    
            //Input
            bool[] dirs = {
    
     false, false, false, false };
            bool zReset = false;
            int xMax = 0;
            int yMax = 0;

            Rectangle3d rec = new Rectangle3d();

            //为了避免方便设计避免掉头逻辑,使用上左下右的排列顺序
            bool judge = DA.GetData(Dir.Up.Name, ref dirs[0]);
            judge &= DA.GetData(Dir.Down.Name, ref dirs[2]);
            judge &= DA.GetData(Dir.Left.Name, ref dirs[1]);
            judge &= DA.GetData(Dir.Right.Name, ref dirs[3]);

            bool judge2 = DA.GetData("Reset", ref zReset);

            DA.GetData("xDivideNumber", ref xMax);
            DA.GetData("yDivideNumber", ref yMax);

            DA.GetData("rectangle", ref rec);
            Plane plane = rec.Plane;

            //rec.pointAt()使用的是百分数,故直接除以1
            double xStep = 1d / xMax;
            double yStep = 1d / yMax;
            //间隔
            double step = 0;
            //半径
            double r = 1;
            if (xStep > yStep)
            {
    
    
                step = yStep;
                r = yStep * rec.Height / 2;
            }
            else
            {
    
    
                step = xStep;
                r = xStep * rec.Width / 2;
            }

            AddRuntimeMessage(GH_RuntimeMessageLevel.Remark, r + "  " + xStep + " " + yStep + " " + step);

            if (!judge) AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "请连接一个按钮来控制方向");

            if (!judge2) AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "请连接一个按钮来复原");

            AddRuntimeMessage(GH_RuntimeMessageLevel.Remark, "记得加上Trigger. 建议间隔:500ms.");



            List<Curve> bodies = new List<Curve>();

            Random foodRandomNumber = new Random(seed);


            //Reset

            if (zReset)
            {
    
    
                headXinPlane = 0;
                headYinPlane = 0;
                headPoint = rec.PointAt((headXinPlane + 0.5) * xStep, (headYinPlane + 0.5) * yStep);

                bodyPoints = new List<Point3d>() {
    
     headPoint };
                return;

            }


            //Direction
            for (int i = 0; i < dirs.Length; i++)
            {
    
    
                if (dirs[i])
                {
    
    
                    if (nowdirection != i && (nowdirection + 2) % dirs.Length != i)
                    {
    
    
                        //只有在往另外一个轴进行移动,才算有效移动
                        nowdirection = i;
                    }
                    //避免在多次输入的情况下快速刷新,只更新当前方向就返回
                    DA.SetData("Food", lastFood);
                    DA.SetDataList("Snake", lastSnake);

                    //return;
                }
            }


            bodyPoints.Add(headPoint);

            if (nowdirection % 2 == 0)
            {
    
    
                //此时是上或下
                if (nowdirection == 0)
                {
    
    
                    headYinPlane += 1;
                }
                else
                {
    
    
                    headYinPlane -= 1;
                }

                //超出边界,从另一边回归
                if (headYinPlane >= yMax)
                {
    
    
                    headYinPlane = 0;
                }
                else if (headYinPlane < 0)
                {
    
    
                    headYinPlane = yMax - 1;
                }
            }
            else
            {
    
    
                if (nowdirection == 1)
                {
    
    
                    headXinPlane += 1;
                }
                else
                {
    
    
                    headXinPlane -= 1;
                }

                if (headXinPlane >= xMax)
                {
    
    
                    headXinPlane = 0;
                }
                else if (headXinPlane < 0)
                {
    
    
                    headXinPlane = xMax - 1;
                }
            }

            headPoint = rec.PointAt((headXinPlane + 0.5) * xStep, (headYinPlane + 0.5) * yStep);


            //Food section below
            double foodRandomX = foodRandomNumber.Next(0, xMax - 1);
            double foodRandomY = foodRandomNumber.Next(0, yMax - 1);


            Point3d foodPoint = rec.PointAt((foodRandomX + 0.5) * xStep, (foodRandomY + 0.5) * yStep);
            Circle FoodCircle = new Circle(plane, foodPoint, r);


            //Eating judge
            if (foodPoint.DistanceTo(headPoint) <= 2 * r) {
    
     seed++; }

            else
            {
    
    
                bodyPoints.RemoveRange(0, 1);
            }



            foreach (Point3d point in bodyPoints)
            {
    
    
                Circle body = new Circle(plane, point, r);

                bodies.Add(body.ToNurbsCurve());

            }

            Curve[] zSnake = Curve.CreateBooleanUnion(bodies, 1);

            List<Curve> output = new List<Curve>(zSnake);
            //Output


            DA.SetData("Food", FoodCircle);
            DA.SetDataList("Snake", output);

            lastFood = FoodCircle;
            lastSnake = output;


        }

c.使用方法及与效果

  1. 使用方法
    在这里插入图片描述

调整Trigger的间隔可以改变蛇运动的快慢

  1. 效果
    请添加图片描述

参考资料

  1. 制作Grasshopper电池/二次开发基础【上篇】

猜你喜欢

转载自blog.csdn.net/Reven_L/article/details/125658152
今日推荐