wenmo8 发布的文章

liwen01 2025.01.05

前言

半导体的前身是真空管,在集成电路被发明前,真空管被广泛应用于收音机、电话、无线电、雷达、电视、电子计算机等电子设备中。

真空管
也叫
电子管
,在不同的资料中称呼可能不一样,这里我们统称为
真空管

因为翻译的原因,
Triode
三极管实际指代的是真空三极管,而不是我们现在使用的晶体三极管。在了解半导体前有必要对真空管有一个简单的了解。

(一)真空二极管

(1)爱迪生效应

1879年爱迪生发明了电灯泡,但是早期的电灯泡使用时间长了之后,外面的玻璃容易变黑,从而导致电灯泡的亮度变低,爱迪生认为是部分灯丝碳化后的烟尘覆盖在了玻璃上。

为解决电灯泡玻璃变黑的问题,爱迪生尝试将一铜片安装在电灯泡里面,以尝试阻挡灯丝高温碳化后的烟尘。在试验的时候却发现了奇怪的现象:在电灯泡亮了一段时间之后,用来阻挡烟尘的铜片产生了单向的电流。而铜片是位于玻璃真空管内部,与带电的灯丝之间应该是绝缘的才对。

爱迪生无法解释铜片产生单向电流的原因,他周边的其它科学家也无法给出合理的解释。专注于新发明和实际应用的爱迪生无暇顾及电灯泡里的这个现象,习惯性地申请了一个专利后,他就将它忘在脑后了。

后来人们称这一现象为
爱迪生效应
,或者是
真空电子发射效应

爱迪生效应原理

后来在其它科学家的探索中我们知道:在真空管中,当电流通过阴极(灯丝)时,它会使灯丝变热。根据热电子发射原理,阴极的温度升高到一定程度时,灯丝表面的电子会获得足够的能量克服金属表面的势垒(也就是
逸出功
),从而被释放到真空中,这个过程称为
热电子发射

可以简单理解为:
灯丝通电受热后,灯丝原子中的电子逃逸出去,飞向了铜片,从而产生了单向电流。

(2)弗莱明阀(真空二极管)

受爱迪生效应的启发,弗莱明在1904年发明了最早的
真空二极管
,也被称为
弗莱明阀

在弗莱明阀中,靠近灯丝端是阴极,在原来铜块的位置是阳极。

当外部加一个反向的电源,如下图(a),铜片处是带负电,与正极间形成一个电场,灯丝加热后,溢出的电子受到电场力的作用,朝外部电源的正极移动,所以外部电源断开,不能导通。

当外部接一个正向的电源时(上图b),铜板与灯丝间的电场反向,灯丝加热后溢出的电子在电场力的作用下,往铜板的方向移动,这时,整个电路导通。

这就是最早的真空二极管,具有单向导通的作用。

(3)二极管检波

在20世纪初真空管刚发明的时候,还没有长途电话、收音机、电视机和计算机这些东西,在赫兹通过试验验证了电磁波的存在后,人们才开始尝试使用无线电磁波进行通信的试验。

无线信号的载波信号一般都是高频信号,因为高频信号传输效果比低频的好。比如现在调幅(AM)广播的频率一般是153 kHz~26.1 MHz,调频(FM)广播的频率是87.5 MHz ~108 MHz,而人耳能听到的声音频率只有 20Hz ~ 20kHz。

无线电在发送的时候,是将声音的低频信号与载波的高频信号进行调制,使发送的无线信号中带有声音信息,这个过程叫信号的调制。

以调幅广播举例,收音机天线接收到高频交变的无线信号之后,先是利用二极管的单向导通性,将信号的负半部分去除,就变成了单向的脉冲直流信号。在这之后,再通过电阻和电容组成的低通滤波电路,就可以得到调幅广播的低频信号了,这个就是声音的信号了。 这个过程实际上也就是无线电的解调过程。

早期人们是使用金属屑检波器进行检波,但它的开关速度很慢,效果不好。而真空二极管里面没有机械部分,电流纯靠电子的流动,因此开关速度比金属屑检波器快很多。

(4)二极管整流

在上面检波的过程中,我们是直接把交变信号中的负半部分去除了,如果是要将交流电转换为直流电,这样其实是把一半的电能丢弃掉了。

为了可以尽可能地完整使用交流电中的所有电能,可以使用二极管桥接成一个整流电路

上图左边输入的是交变电流

  • 在(a)图中,输入电流从上边正极向右到达D点,由于二极管的单向导通性,它只能通过D2到达A点,经过负载电灯到达B点,再经过D3二极管到达C点后回到电源的负极,形成一个环路。
  • 在(b)图中,输入电流从下边正极到C点,经过D4二极管到达A点,经过电灯负载到达B点再经过D1二极管到达D点回到电源的负极形成一个环路。

对于负载灯泡而言,不管外部输入的是交变电流的正半部分还是负半部分,电流都是从A流向B,从而实现了电能的全部使用

(二) 真空三级管

在二极管的基础上,在铜板与灯丝之间插入一个带电的栅格,也就是一个带孔的网格,这样就变成了一个真空三极管。

第一个三极管

1906年年底,福雷斯特在二极管中多加入了一个栅极,使真空管具有了放大与振荡功能。

(1) 三极管工作原理

当栅格的电极为负的时候,它对灯丝逃逸出来的电子有个反作用力,可以抑制电子往铜板的方向逃逸,当这个电场力足够大的时候,它可以阻断外部电源电流的流通。

当栅格的电极为正的时候,它对灯丝逃逸出来的电子有个吸引的作用力,可以加速灯丝电子的逃逸。

因为电子逃逸的动能很小,所以实际上只需要很小的一个电信号就可以控制外部电源的电流大小,这就实现了真空三极管的放大功能。

因为栅格信号实际影响的是电子,所以它的响应速度与真空二极管一样,都非常快。

(2) 三极管结构

实际使用的真空管,并不是直接使用灯丝加热让灯丝上的电子逃逸,而是在里面设置一个专门用来加热的器件,也叫旁热式真空管。

上面这个是真空三极管的剖面图:

  • 最中间的是一个加热丝,用于加热阴极
  • 加热器外面就是阴极,加热后会逃逸出电子
  • 阴极外面一层是栅极,用于控制电子的运动
  • 栅极外面是阳极,用于接收电子
  • 最外层是玻璃管,将里面的空气抽走,形成真空状态。

低功率三极管

(3) 三极管应用

真空三极管被发明之后,被广泛应用于收音机、电视机、无线电报、音响以及早期的电子计算机中,它主要作用是开关和信号放大。

无线信号或是长途电话,在通过远距离的传输后,信号会衰减,变得很微弱。通过三极管的放大功能,可以将信号进行放大和还原,从而使无线通信和长途电话技术变为可能。

真空管功放

真空管无线电台信号发生器

(三)真空管的局限

随着电子设备功能的逐渐复杂,真空管的缺点也逐渐地变得明显:
体积大、重量重、功耗高、易损坏、寿命短、温度高

1945 年美国建成了第一台电子计算机 ENIAC,它使用了 18000 个真空管,重量达到了 27 吨,占地28平方,功率达到 150KW,它主要用于军方的弹道和其它武器相关的复杂计算。

因为真空管需要预热之后才能被使用,而当时真空管材料的应力不够,导致真空管容易损坏,ENIAC 这一万多个真空管,基本上每天都会有几个被烧坏。结果就是 ENIAC 的一半时间在工作,另外一半时间是在维修。

而这台电子计算机每秒只能够执行 5000 次加法运算或是 357 次乘法运算,脉冲信号频率也只有 100 kHz。现在任意一个物联网设备的计算能力应该都比它强。

真空管的缺点限制了复杂电子设备的发展,二战后美苏进入冷战阶段,开始军备和太空竞赛。军方急需稳定、可靠、质量轻便的器件来代替真空管,这也就给半导体的发展创造了机会。

在20世纪五十年代,人类刚开始太空探索,据说每增加 1Kg 卫星的重量,火箭就要增加1吨的燃料。那时,军方只关注电子器件的体积、功耗、和稳定性,而无关乎价格和成本。这也就为后来的晶体管和集成电路的发展提供了市场需求和资金来源。

结尾

真空管被广泛地使用在早期的无线电、收音机、电视、电子计算机、雷达等电子设备上。在有了美国军方市场和资金的支持下,美国半导体技术也得到了快速的发展,最后真空管逐渐地被晶体管所替代。

下一篇我们将介绍半导体技术的关键
PN结
和晶体二极管。

------------------End------------------
如需获取更多内容
请关注 liwen01 公众号

1. 什么是 Tesseract

Tesseract 是一个强大的字符识别 (OCR) 工具。它最初由 HP 发布,现在由 Google 和学术社区共同维护和开发。 Tesseract 支持多种语言和格式,能够高效地识别图片中的文字。

2. 如何安装 Tesseract

要在 .NET 项目中使用 Tesseract,需要安装相关的 NuGet 包和基础依赖。步骤如下:

  1. 在项目中通过 NuGet 安装 Tesseract 包:

    dotnet add package Tesseract
    
  2. 安装 SkiaSharp 来处理图片:

    dotnet add package SkiaSharp
    
3. 训练数据从哪里下载

Tesseract 需要训练数据文件来识别图片中的文字。这些训练数据文件可以从
Tesseract OCR GitHub
https://github.com/tesseract-ocr/tessdata下载。选择您需要的语言文件,并放置在项目相关目录中。

4. 如何使用 Tesseract

使用 Tesseract 识别图片中的文字,可以通过以下步骤完成:

  1. 创建一个 HttpClient 和 Logger ,用于下载图片和记录日志。
  2. 使用 SkiaSharp 来处理图片。
  3. 使用 Tesseract 完成 OCR 识别。

例子如下:

using SkiaSharp;
using Tesseract;

namespace AutoGetOrder.WebAPI.Services.TesseractService
{
    public class TesseractOCRService
    {
        private readonly HttpClient _httpClient;
        private readonly ILogger<TesseractOCRService> _logger;

        public TesseractOCRService(HttpClient httpClient, ILogger<TesseractOCRService> logger)
        {
            _httpClient = httpClient;
            _logger = logger;
        }

        public async Task<string> Do(string imageUrl)
        {
            try
            {
                byte[] imageBytes = await DownloadImageAsync(imageUrl);
                using (var skBitmap = SKBitmap.Decode(imageBytes))
                {
                    var processedBitmap = PreprocessImage(skBitmap);
                    using (var pix = ConvertSKBitmapToPix(processedBitmap))
                    {
                        string captchaText = RecognizeText(pix);
                        return captchaText;
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.ToString());
                return string.Empty;
            }
        }

        private async Task<byte[]> DownloadImageAsync(string url)
        {
            return await _httpClient.GetByteArrayAsync(url);
        }

        private SKBitmap PreprocessImage(SKBitmap bitmap)
        {
            return bitmap;
        }

        private Pix ConvertSKBitmapToPix(SKBitmap skBitmap)
        {
            using (var image = SKImage.FromBitmap(skBitmap))
            using (var data = image.Encode(SKEncodedImageFormat.Png, 100))
            {
                return Pix.LoadFromMemory(data.ToArray());
            }
        }

        private string RecognizeText(Pix pix)
        {
            using (var engine = new TesseractEngine(@"./Services/TesseractService", "eng", EngineMode.Default))
            {
                using (var page = engine.Process(pix))
                {
                    return page.GetText();
                }
            }
        }
    }
}
5. Docker 环境中安装依赖

如果您在 Docker 环境中使用 Tesseract,需要确保基础依赖已经安装。参考PR https://github.com/charlesw/tesseract/issues/675 可以参考下面的 Dockerfile:

RUN apt-get update && apt-get install -y \
    libfontconfig1 \
    libfreetype6 \
    libpng16-16 \
    libjpeg62-turbo \
    libx11-6 \
    libxext6 \
    libxrender1 \
    tesseract-ocr \
    libtesseract-dev \
    libleptonica-dev

RUN ln -s /usr/lib/x86_64-linux-gnu/libdl.so.2 /usr/lib/x86_64-linux-gnu/libdl.so
WORKDIR /app/x64
RUN ln -s /usr/lib/x86_64-linux-gnu/liblept.so.5 /app/x64/libleptonica-1.82.0.so
RUN ln -s /usr/lib/x86_64-linux-gnu/libtesseract.so.5 /app/x64/libtesseract50.so

这些依赖确保 Tesseract 和其依赖库在 Docker 中正常运行。

6.一个例子

识别结果

一.three框架材质原理与应用

其实three里面所有的材质不管是基础还是点等等之类最终都会编译为shader然后执行,所以如果拿到一个材质我想对某个地方小改一下,那么也是可以用shader来进行修改的

image-20241228172314925

这里用基础材质创建一个平面

image-20241228173407666

通过材质的这个回调就可以拿到他的shader并且里面也有顶点着色器和片段着色器

image-20241228173729164

image-20241228173737369

当然这里面这些定义都不是目前学的,因为这是three自己封装的,要看其源码都可以在这个目录下面找到

像commen就是封装了一些可以快速使用的变量函数等

image-20241228174051337

还比如这个导入就是对position做出了一个封装,可以快速修改位置通过transformed变量

image-20241228174248517

当然如果我们要修改一些参数

因为glsl是字符串,所以需要通过字符串的方法来修改

比如偏移位置

image-20241228174719387

image-20241228174726468

动起来

image-20241228175328680

GIF

二.修改物理光照材质制作人物被打效果

先把模型加载进来

image-20241228181005889

image-20241228181014266

继续加载纹理和法相纹理

image-20241228181406442

image-20241228181414568

修改被打效果,核心就是脸扭曲变形

比如让连续旋转180度,PI就是3.14就是180度,
注意旋转角度是x轴和z轴同时转

image-20241228183022563

image-20241228183031723

当然要捏造被打的形象,扭曲应该围绕着y轴为角度

image-20241228183141961

image-20241228183149772

开启阴影后发现阴影有点问题

image-20241228192835134

这时候需要调整法相的旋转角度跟刚才保持一致

注意几个问题:法相在前,此时还没有transformed用原始的position

还有就是法相的位置是objectNormal

第三个就是法相前面已经声明了angle这些后面不用在声明直接用

image-20241228193413906

image-20241228193346518

image-20241228193551800

这个问题解决后但是投射到后面的还是不对

image-20241228193612738

对于3D物体有一个方法

image-20241228193759085

首先需要设置一个自定义深度材质

image-20241228194210279

有了这个材质后,跟刚才的材质一样也要去设置旋转角度,只是没有了法相的设置

image-20241228194350942

image-20241228194358175

最后添加来回被打的效果,加上时间器

注意来回要加sin函数

image-20241228195516669

GIF

扩展是将较短的文本,例如一组提示或主题列表,输入到大型语言模型中,让模型生成更长的文本。我们可以利用这个特性让大语言模型生成基于某个主题的电子邮件或小论文。通过这种方式使用大语言模型,可以为工作与生活提供诸多便利,而即便不能获得满意的结果,大型语言模型也将是一名优秀的头脑风暴的伙伴。当然,如果以不负责任的目的使用这种功能,也可以快速产生大量垃圾内容,所以我们在使用大语言模型时须谨记我们的社会责任。
继续以本系列文章前面曾使用过的网购客户评价为例,利用扩展可自动生成对评价的回复,提升客户购物的良好体验。
你是一位电商客服的AI助手。你的任务是根据客户的评价对该评价进行回复。
以下由“```”包含的内容是客户评价,请生成回复以感谢客户。为使回复更贴近该评价,在回复中应提及评价中的具体细节,且回复应简明而专业。回复时请使用“积极”的评论情感。
```XX上的东西质量有保证,我拿少2百左右,价格便宜,方法闺蜜说的,买着放心用着舒心。客服也很好,消费者权益有保障,在XX上买的东西耐心,质量经的起考验。大瓶用的持久,小样出门旅行带着很方便,总之就是好。韩国后天气丹已收到,物流快,客服服务好,有诚信,收到货,包装完好回头客。快递送货及时,五星好评。产品包装:包装高端大气上档次。吸收效果:吸收效果很好!保湿程度:夜间使用非常保湿。适合肤质:敏感肌也可以用,味道很好闻。我真的非常喜欢它,非常支持它,质量非常好,和卖家描述的一模一样,我非常满意。我真的很喜欢它,它完全超出了预期的价值,交货速度非常快,包装非常仔细和紧凑,物流公司有一个良好的服务态度,交货速度非常快,我非常满意购物,包装很好,水和乳液两大瓶,可以用很久,和以前买的小样香味儿一样的,用后不油腻,我是敏感肌,用后暂时没有出现问题,再使用一阵再追评!```


让大语言模型进行文字创作通常也可以得到不错的效果,整体而言,大语言模型的文字功底超过了大多数普通人。比如我们可以写一个开头,让模型继续完成后继内容。

在上例中,我们只是给出了第一句,模型不仅能够续写,而且它猜测出了我们可能是想要一首诗。我们也可以指定文章中的若干个语句,然后让模型完成整篇文章。例如我们输入以下内容,要求模型写一封情书。
现在你正和一位女孩子谈恋爱,你现在的任务给女孩子写一封情书,情书至少应有五段,其中情书第二到第四段请分别以“你是我的太阳”“你是我的月亮”“你是我的星星”开头。

上述回应虽然没有能够完全符合提示的要求,但已经是很不错的一封情书了。
大语言模型不仅能实现续写任务,也能够实现一些特定的文字任务,例如藏头诗这种特殊的文字。

在使用大语言模型时,有一个称为“温度”的参数会影响模型回复的多样性,温度越高,模型的回复将越具有创造性,对同一提示回复的灵活性与变化越大。遗憾的是部分大语言模型提供的聊天对话框中并不支持这个参数的设定,但如果通过API使用大模型则可以调整这个参数。
微软的Copilot提供了三种对话样式的选择,这三种对话样式实际上就是指定以不同的温度回复我们的提示。

以下是选择“精确”对话样式(即温度较低)连续两次提交相同内容时Copilot给出的回复,可以看到,两次回复相似度非常高。我们在询问诸如规则制度、原理、历史事实等这些确定性非常高的事实时,应使用这种模式。


将Copilot的对话样式调整为“有创造力”后(即温度较高)连续两次提交相同内容时Copilot给出的回复如下两图,可以看到,两次回复的内容与形式均有明显的差异。


可见,扩展可以帮助我们完成多种不同任务。据说国外有学生使用ChatGPT利用这种特性甚至完成了一篇学术论文。

动画组合类的作用是将多个动画组合起来,以实现更复杂的动画效果。

Manim
中有
4个
用于动画组合的类:

  1. AnimationGroup
    :将多个动画组合在一起同时播放,能一次性呈现多个对象的不同变化
  2. LaggedStart
    :按照添加顺序依次启动多个动画,每个动画间有延迟,使动画呈现出清晰的先后顺序
  3. LaggedStartMap
    :对一组对象应用相同动画,并按顺序逐个启动且有延迟,快速实现批量相似动画效果
  4. Succession
    :严格按照添加顺序依次播放多个动画,前一个完成后下一个才开始,确保动画按特定流程进行

1. 动画概述

1.1. AnimationGroup

当需要同时播放多个动画时,
AnimationGroup
非常有用。

例如,在一个场景中,需要同时移动多个图形、改变它们的颜色或执行其他操作。

AnimationGroup
中包含的所有动画会同时开始和结束,可以通过设置
lag_ratio
参数来调整动画之间的延迟比例。

它的参数主要有:

参数名称 类型 说明
animations [Animation] 要播放的动画对象序列
group Group 包含多个Mobject的组
run_time float 动画持续时间
rate_func func 基于相对运行时间定义动画进度的函数
lag_ratio float 定义动画应用于子对象的延迟

参数
lag_ratio
默认为
0
,表示所有动画同时播放。


lag_ratio=0.5
,表示下一个动画将在前一个动画播放到 50% 时开始。

1.2. LaggedStart

当需要按顺序播放多个动画,但希望它们之间有一定的延迟时,
LaggedStart
是一个很好的选择。

例如,依次显示一系列的文本内容或逐个移动多个对象。

LaggedStart
用于创建具有节奏感的动画序列,使观众能够清晰地看到每个动画的发生过程。

动画会按照它们被添加的顺序依次启动,每个动画之间有一定的延迟。

它的参数主要有:

参数名称 类型 说明
animations [Animation] 要播放的动画对象序列
lag_ratio float 定义动画应用于子对象的延迟
run_time float 动画的持续时间

1.3. LaggedStartMap

当需要对一组对象应用相同的动画,但希望它们之间有一定的延迟时,
LaggedStartMap
非常实用。

例如,逐个显示一组文本对象或逐个移动一组图形。

LaggedStartMap
适用于处理大量相似对象的动画,通过简洁的方式实现逐个动画的延迟效果。

它的参数主要有:

参数名称 类型 说明
AnimationClass func 要应用于mobject的动画类
mobject Mobject 动画要应用到的Mobject
arg_creator func 要应用于Mobject的函数
run_time float 动画的持续时间
lag_ratio float 定义动画应用于子对象的延迟

1.4. Succession

当需要按顺序依次播放多个动画,且每个动画必须在前一个动画完成后才能开始时,
Succession
是首选。

例如,先移动一个对象,然后再对其进行旋转或缩放等操作。

Succession
用于创建复杂的动画流程,确保动画按照特定的顺序执行,动画会按照它们被添加的顺序依次播放,前一个动画完成后,下一个动画才会开始。

它的参数主要有:

参数名称 类型 说明
animations [Animation] 要播放的动画对象序列
lag_ratio float 定义动画应用于子对象的延迟
run_time float 动画的持续时间

2. 使用示例

下面通过一些示例演示这4个动画组合类的使用。

2.1. 同时移动和变色多个图形

此示例中,展示了
AnimationGroup
并行处理多个动画的能力。

示例中同时启动圆形的移动动画和正方形的变色动画,让多个对象的不同动画效果同时呈现,且所有动画在同一时刻开始和结束。

# 创建一个圆形和一个正方形
c = Circle().shift(LEFT * 2)
s = Square().shift(RIGHT * 2)

# 定义圆形的移动动画和正方形的变色动画
anim_circle = c.animate.shift(RIGHT * 4)
anim_square = s.animate.set_color(RED)

# 使用AnimationGroup同时播放这两个动画
self.play(AnimationGroup(anim_circle, anim_square))

2.2. 逐个显示文本内容

在这个示例中,
LaggedStart
的特点是按照添加动画的顺序依次启动,每个动画之间根据
lag_ratio
(这里设置为
0.3
)产生延迟。

动画中可以清晰地看到每个文本依次淡入,营造出一种有序的节奏感,适合用于展示一系列相关元素的逐个出现效果。

# 创建三个文本对象
text1 = Text("死去元知万事空").shift(UP * 2)
text2 = Text("但悲不见九州同").next_to(
    text1, DOWN, buff=0.5
)
text3 = Text("王师北定中原日").next_to(
    text2, DOWN, buff=0.5
)
text4 = Text("家祭无忘告乃翁").next_to(
    text3, DOWN, buff=0.5
)

# 定义每个文本对象的淡入动画
anim_text1 = FadeIn(text1)
anim_text2 = FadeIn(text2)
anim_text3 = FadeIn(text3)
anim_text4 = FadeIn(text4)

# 使用LaggedStart按顺序逐个启动动画,每个动画之间有一定延迟
self.play(
    LaggedStart(
        anim_text1,
        anim_text2,
        anim_text3,
        anim_text4,
        lag_ratio=0.3,
    ),
    run_time=3,
)

2.3. 逐个移动一组点并变色

此例中,
LaggedStartMap
的优势在于可以方便地对一组对象应用相同的动画操作,并且通过
lag_ratio
(设置为
0.2
)实现逐个对象动画的延迟效果。

这使得一组点能够逐个向下移动并变色,展示了批量处理相似对象动画且控制延迟的能力,适用于处理多个相同类型元素的动画序列。

# 创建一组点
dots = VGroup(*[Dot() for _ in range(5)]).arrange_in_grid(
    rows=1, cols=5, buff=1
)
dots.shift(UP)

# 定义一个动画函数,用于移动点并变色
def move_and_color_dot(dot):
    return dot.animate.shift(DOWN * 2).set_color(YELLOW)

# 使用LaggedStartMap对每个点应用动画函数,逐个启动动画且有延迟
self.play(LaggedStartMap(move_and_color_dot, dots, lag_ratio=0.2))

2.4. 先旋转后移动图形

在该示例中,Succession严格按照顺序依次执行动画。

先旋转五角星图形,然后向上下左右四个方向分别射出不同颜色的点。

# 创建一个三角形
star = Star()
dot1 = Dot(color=RED)
dot2 = Dot(color=GREEN)
dot3 = Dot(color=YELLOW)
dot4 = Dot(color=PURPLE)
self.add(star, dot1, dot2, dot3, dot4)

# 定义三角形的移动、旋转和缩放动画
rotate_star = Rotate(star, angle=PI * 2)
send_dot1 = dot1.animate.shift(UP * 2)
send_dot2 = dot2.animate.shift(RIGHT * 2)
send_dot3 = dot3.animate.shift(DOWN * 2)
send_dot4 = dot4.animate.shift(LEFT * 2)

# 使用Succession按顺序依次播放动画,前一个动画完成后下一个才开始
self.play(
    Succession(
        rotate_star,
        send_dot1,
        send_dot2,
        send_dot3,
        send_dot4,
    ),
    run_time=3,
)

3. 附件

文中的代码只是关键部分的截取,完整的代码共享在网盘中(
anim_group.py
),

下载地址:
完整代码
(访问密码: 6872)