【Grasshopper进阶】在Grasshopper画布上实现动画效果

本文介绍了如何利用Grasshopper的动画功能制作电池变形和按钮浮现的动画效果。通过重写GH_ComponentAttributes的Layout()和Render()方法,结合ScheduleRegen()函数实现循环刷新,达到动画的视觉效果。详细步骤包括设置动画条件,调整电池高度和按钮透明度,最终呈现出平滑的动画过渡。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

动画电池外观
最近笔者为了能够做到对用户友好,深入地学习了一些前端的知识,等回过神来,发现已经好久没有在优快云上更过东西了… 结合最近看到的某些前端框架的底层,突然想到为什么不在Grasshopper上玩动画呢?比如电池拖出来之后,缓缓变形,然后出现一些UI的元素。

本来已经做好了手动写 Timer 或者写个线程循环来操作,结果人家Grasshopper自带这些个做动画必备的东西,那就简单了,抱着试试看的态度,完成了一个好玩的动画效果。就如同文章最开始的 gif 动画一般。

下面就 “简单” 介绍一下如何在Grasshopper中实现这个效果。

动画即循环

我们需要了解到动画的本质就是个循环绘制,在上图中的展示的效果中,GH_CanvasRefresh()方法会被反复调用,每一次的刷新都会改变电池的大小,当刷新得足够快的时候,看起来就像动画了。随后的按钮的出现也是类似,每一次的刷新都会调整一次可见度,将可见度慢慢调高,快速刷新多次之后,这个按钮看起来就好像是“慢慢浮现”的效果了。

因此,“什么时候刷新”,或者说是“什么时候停止刷新”,这个条件就显得十分重要了。在上面的这个例子中,我们共有两段动画:

  1. 电池的高度不断变高
  2. 电池上按钮的浮现

第一段动画在 “ 电池高度达到指定高度 ” 后完成,第二段动画在 “ 透明度达到指定值 ” 后完成,于是,我们就形成了一个由下面if ... else ...构成的逻辑:

	if (电池高度未达到指定高度)
	{
		// 此时处在第一段动画中
		电池高度++;
		渲染电池;
		刷新画布;
	}
	else if (透明度未达到指定透明度)
	{
		// 此时处在第二段动画中
		透明度++;
		渲染电池;
		渲染按钮;
		刷新画布;
	}
	else
	{
		渲染电池和按钮;
		// 此时不应刷新画布,因两段动画均已结束
	}

按照这个主逻辑,我们就可以配置我们的动画了。

Grasshopper中的渲染

熟悉GH_ComponentAttribute的朋友大概已经知道我们要对其中的Render()方法进行大刀阔斧地重写了。没错,除此之外,我们还需要重写Layout()这个方法。 大家都是进阶教程的代码观察师了,详细的就在注释里写啦。

public class AnimatedAttribute : GH_ComponentAttributes
{
    public AnimatedAttribute(IGH_Component owner) : base(owner) { }
    int addedHeight = 1;
    int buttonFade = 0;

    protected override void Layout()
    {
    	// 调用基类(GH_ComponentAttributes)的 Layout() 方法,用来生成默认大小的电池外框,
    	// 我们再基于这个默认生成的外观进行高度的增加
        base.Layout();
		// 获取电池大小
        var bd = Bounds;
        // 对原电池大小进行一个修改,这个`addedHeight`修改的值是存储在电池实例上的,并且会在
        // Render时,参照我们上文中提到的
        bd.Height += addedHeight;
        Bounds = bd;
        // 之所以在 Layout()函数中修改Bounds,是因为这里是GH框架中完成Bounds测绘的函数,
        // 如果在其他函数中对Bounds进行修改,则很有可能造成奇奇怪怪的后果,比如电池大小很诡异
    }
    protected override void Render(GH_Canvas canvas, Graphics graphics, GH_CanvasChannel channel)
    {
    	// 重写的重头戏
    	// 调用基类的Render方法,由于我们已经针对Bounds调整过其高度了(在Layout函数中)
    	// 此处可以直接使用基类方法而不做额外改变
    	// 另外只需要关心电池下方的按钮的绘制即可
        base.Render(canvas, graphics, channel);
		// 绘制电池相关的内容,只在 channel == GH_CanvasChannel.Objects 时进行操作
        if (channel == GH_CanvasChannel.Objects)
        {
	        // 上文中提到的逻辑
            if (addedHeight <= 20)
            {
                addedHeight++; // 以便下一帧的时候,电池的高度会高亿点点…
                ExpireLayout(); // 声明该电池已过期,会重新call Layout(),并组织相关绘图所需元素

				// 下面这个ScheduleRegen函数就是实现这个动画的重中之重了。
				// 为什么不直接canvas.Refresh()?
				// 因为程序在执行这行代码时,还处在GH_Canvas的OnPaint()函数中,
				// 也就是说,Canvas仍然正在绘制,仍处于上一个Refresh()的调用中。
				// 此时立刻Refresh()则会让Canvas再次执行OnPaint(),类似于递归,这并不能做动画
				
				// 我们需要的是能够在这次的Canvas上的所有绘制都完成之后,
				// 在这次的OnPaint()调用完毕之后再次执行OnPaint()
				
				// 要实现这种操作,本来是需要写个Timer或者后台线程等待本次执行结束再调用UI线程刷新的
				// 不过GH早就给我们准备好了,直接Call这个函数就行,这个函数会等待25毫秒/本次刷新结束之后
				// 立刻让画布刷新,达到动画的效果。
                canvas.ScheduleRegen(25); 
            }
            else if (buttonFade < 10)
            {
            	// 更新一下透明度
                buttonFade++; 
                // 创造一个按钮图案
                GH_Capsule capsule = GH_Capsule.CreateTextCapsule(
                    new RectangleF(Bounds.Left + 2, Bounds.Bottom - 18, Bounds.Width - 4, 16),
                    new RectangleF(Bounds.Left + 3, Bounds.Bottom - 17, Bounds.Width - 6, 14),
                    GH_Palette.Grey,
                    "Button"); 
                // 以特定的透明度渲染这个按钮
                capsule.Render(graphics, System.Drawing.Color.FromArgb(buttonFade * 10, System.Drawing.Color.Black));

				// 常规操作
                ExpireLayout();
                capsule.Dispose();
                canvas.ScheduleRegen(25);
            }
            else
            {
            	// 由于此时动画已经结束,这里就是正常渲染的操作了,
            	// 最后不会进行 ScheduleRegen 函数的调用
	            GH_Capsule capsule = GH_Capsule.CreateTextCapsule(
	                new RectangleF(Bounds.Left + 2, Bounds.Bottom - 18, Bounds.Width - 4, 16),
	                new RectangleF(Bounds.Left + 3, Bounds.Bottom - 17, Bounds.Width - 6, 14),
	                GH_Palette.Grey,
	                "Button");
	            capsule.Render(graphics, System.Drawing.Color.FromArgb(buttonFade * 10, System.Drawing.Color.Black));
	            capsule.Dispose();
            }
        }
    }
}

写完Attributes之后,随便来个电池,挂上我们这个新鲜的Attributes,开始电池动画的表演吧:

public class ComponentWithAnimatedAttributes : GH_Component
{
    public ComponentWithAnimatedAttributes()
      : base("AnimatedAttribute", "AA",
          "Description",
          "Params", "DigitalCrab") {}
    public override void CreateAttributes()
    {
        this.Attributes = new AnimatedAttribute(this);
    }
    protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
    {
        pManager.AddGenericParameter("in", "in", "", GH_ParamAccess.item);
    }
    protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
    {
        pManager.AddGenericParameter("out", "out", "", GH_ParamAccess.item);
    }
    protected override void SolveInstance(IGH_DataAccess DA) {}
    protected override System.Drawing.Bitmap Icon => null;
    public override Guid ComponentGuid => throw new Exception("请自行生成GUID并赋值此处");
}

最后的结果就是文章最开始的GIF图片了。

妙啊!

评论 24
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值