19、利用向量实现变形虫模拟运动

利用向量实现变形虫模拟运动

1. 面向对象编程中的抽象

在面向对象编程里,抽象是一项关键设计理念。我们要决定隐藏哪些细节,通过属性和方法暴露哪些内容。以 Python 为例,汽车对象就是对现实世界汽车的抽象代码表示。为了让车辆在屏幕上移动,无需对每个螺栓、齿轮和电线进行建模,这样做简化了代码。而且, Car 类会把复杂的 Python 指令(如外观、运动等)简化为直观的方法,像 shiftGear() steer() accelerate()

作为程序员,要考虑如何在程序中运用抽象,包括在 Python 里对对象的建模。最佳方法并非总是清晰明确,也没有绝对的对错之分。但要记住,好的抽象能让代码更简洁、清晰、模块化且易于维护。

2. 将 Python 代码拆分为多个文件

随着程序复杂度的提升,代码行数会不断增加。为了更好地组织项目,Python 提供了将代码拆分为多个文件(模块)的机制。

操作步骤如下:
1. 在 Processing 编辑器中,点击 microscopic 标签右侧的箭头,从弹出菜单中选择 New Tab ,并将新文件命名为 amoeba 。Processing 会自动为文件名添加 .py 扩展名,此时 amoeba.py 模块会作为一个标签与 microscopic 标签并列显示。
2. 切换到 microscopic 标签,选中 Amoeba 类的所有代码并剪切,然后切换到 amoeba.py 标签并粘贴代码。
3. 切换回 microscopic 标签,剩余代码从 a1 = Amoeba(400, 200, 100) 开始。
4. 使用 import 关键字导入模块,导入语句必须位于实例化变形虫的代码之前,通常放在文件顶部。以下是 microscopic 标签的完整代码:

from amoeba import Amoeba
a1 = Amoeba(400, 200, 100)

def setup():
    size(800, 400)
    frameRate(120)

def draw():
    background('#004477')
    a1.display()

模块的使用有诸多好处,它能减少主草图的代码行数,隐藏每个模块的内部工作原理,让程序员专注于更高级的逻辑。同时,模块也便于其他程序员浏览项目代码,理解程序结构。

3. 用向量编程实现运动

这里使用的是欧几里得向量来为变形虫的运动建模。欧几里得向量具有大小和方向两个属性,可用于表示推动变形虫的力。

例如,变形虫从位置 A 移动到位置 B,移动的总距离 4 个单位就是向量的大小,它描述了力的强度,但不表明力的方向。向量由多个标量组成,如向量 v = (4, 3) 表示一个力,能使变形虫从先前位置向右移动 4 个单位,向上移动 3 个单位。

通过勾股定理可以计算向量的大小,不过 Processing 提供了内置的 PVector 类来处理向量,其中的 mag() 方法可用于计算向量的大小。

v = PVector(4, 3)
magnitude = v.mag()
print(magnitude)  # 输出 5.0
4. PVector 类的使用

PVector 是 Processing 内置的用于处理欧几里得向量的类,无需导入即可在草图中使用。创建二维向量时, PVector() 类需要 x y 两个参数。

为了让变形虫在显示窗口中移动,我们可以创建一个 PVector 实例来表示推进力。

操作步骤如下:
1. 切换到 amoeba.py 标签,在 __init__() 方法中添加一个新的推进向量:

class Amoeba(object):
    def __init__(self, x, y, diameter, xspeed, yspeed):
        ...
        self.propulsion = PVector(xspeed, yspeed)
  1. 切换到 microscopic 标签,使用 Amoeba() 的第四个和第五个参数将推进向量的 x y 分量分别设置为 3 和 -1,并在 draw() 函数中增加变形虫的 x y 属性:
a1 = Amoeba(400, 200, 100, 3, -1)

def draw():
    background('#004477')
    a1.x += a1.propulsion.x
    a1.y += a1.propulsion.y
    a1.display()

每帧中,变形虫 a1 x 值增加 3 个像素, y 值减少 1 个像素,在默认的 Processing 坐标系中, y 值减少意味着变形虫向上移动。运行草图,变形虫将沿对角线轨迹快速移动,从显示窗口中心开始,很快从右上角下方移出。

我们还可以使用 PVector 实例来存储变形虫的 x y 坐标。

操作步骤如下:
1. 切换到 amoeba.py 标签,将 self.x self.y 属性替换为一个名为 self.location 的新向量:

class Amoeba(object):
    def __init__(self, x, y, diameter):
        print('amoeba initialized')
        self.location = PVector(x, y)
        ...
  1. 由于 Amoeba 类中有多处引用了 self.x self.y ,需要将它们全部替换为 self.location.x self.location.y 。可以使用 Processing 菜单栏中的 Edit -> Find 工具进行查找和替换操作。
  2. 切换到 microscopic 标签,将 a1.x a1.y 分别改为 a1.location.x a1.location.y
a1.location.x += a1.propulsion.x
a1.location.y += a1.propulsion.y

使用 PVector 加法可以更高效地实现上述操作,将推进向量和位置向量相加:

def draw():
    background('#004477')
    a1.location += a1.propulsion
    a1.display()
5. 向量的加法和减法
向量加法

PVector 类支持使用 + 运算符进行向量加法, += 作为增强赋值运算符,可将左侧向量操作数更新为自身与右侧操作数的和。

例如,添加一个新的水流向量 current 来模拟对角流动的水流,辅助变形虫的运动:

current = PVector(1, -2)

def draw():
    background('#004477')
    a1.location += a1.propulsion
    a1.location += current
    a1.display()

向量加法通过将一个向量的 x 分量与另一个向量的 x 分量相加, y 分量同理。向量加法是可交换的,即改变操作数的顺序不影响结果。

向量减法

向量减法的结果是两个向量的差。与向量加法不同,向量减法是非交换的,改变操作数的顺序会得到不同的结果。

可以使用 - 运算符进行 PVector 实例的减法操作:

print(current - a1.propulsion)

为了让变形虫向鼠标指针移动,我们可以创建一个 pointer 向量来存储鼠标指针的 x-y 坐标,并计算 pointer location 的差向量:

current = PVector(1, -2)

def draw():
    background('#004477')
    pointer = PVector(mouseX, mouseY)
    difference = pointer - a1.location
    a1.location += difference
    ...

但直接这样做会使变形虫瞬间跳到鼠标指针位置,我们需要限制向量的大小,让变形虫逐步向指针移动。

6. 限制向量大小

PVector 类提供了 limit() 方法来限制向量的大小,而不影响其方向。该方法需要一个标量参数,表示最大大小。

修改 draw() 函数,引导变形虫向鼠标指针移动:

def draw():
    ...
    # 1 #a1.location += difference
    a1.propulsion += difference.limit(0.03)
    a1.location += a1.propulsion.limit(3)
    a1.location += current
    a1.display()

为了让多个变形虫以不同速度移动,我们可以在 Amoeba 类中添加一个 maxpropulsion 属性:

class Amoeba(object):
    def __init__(self, x, y, diameter, xspeed, yspeed):
        ...
        self.maxpropulsion = self.propulsion.mag()

同时调整 microscopic 标签中的代码,使用 maxpropulsion 属性:

a1 = Amoeba(400, 200, 100, 0.3, -0.1)
current = PVector(0.1, -0.2)

def draw():
    ...
    a1.propulsion += difference.limit(a1.maxpropulsion/100)
    a1.location += a1.propulsion.limit(a1.maxpropulsion)
    ...

以下是向量操作的总结表格:
| 操作 | 描述 | 示例代码 |
| — | — | — |
| 向量大小计算 | 使用 mag() 方法计算向量大小 | v = PVector(4, 3); magnitude = v.mag() |
| 向量加法 | 使用 + += 运算符进行向量相加 | a1.location += a1.propulsion |
| 向量减法 | 使用 - 运算符进行向量相减 | difference = pointer - a1.location |
| 限制向量大小 | 使用 limit() 方法限制向量大小 | a1.propulsion += difference.limit(0.03) |

下面是变形虫运动的简单流程图:

graph TD;
    A[初始化变形虫和向量] --> B[进入 draw() 函数];
    B --> C[计算差向量];
    C --> D[更新推进向量];
    D --> E[更新位置向量];
    E --> F[显示变形虫];
    F --> B;

利用向量实现变形虫模拟运动

7. 模拟中添加多个变形虫

之前我们处理的是单个变形虫实例 a1 ,现在要创建一个变形虫群体。可以从同一个类创建任意数量的实例,下面将在同一显示窗口中生成八个变形虫,每个变形虫的大小和起始坐标都不同。

手动添加变形虫的问题

手动定义多个变形虫实例,如:

a1 = Amoeba(400, 200, 100, 0.3, -0.1)
sam = Amoeba(643, 105, 56, 0.4, -0.4)
bob = Amoeba(295, 341, 108, -0.3, -0.1)
lee = Amoeba(97, 182, 198, -0.1, 0.2)

这种方式存在明显的缺点。要显示这些变形虫, draw() 函数需要为每个实例调用 display() 方法:

def draw():
    ...
    a1.display()
    sam.display()
    bob.display()
    lee.display()
    ...

如果要让它们移动, draw() 函数还需要更多代码。当变形虫数量增加时,这种方式效率极低。

使用列表管理变形虫

更好的方法是将变形虫存储在列表中,使用循环生成所需数量的变形虫,并通过另一个循环调用每个变形虫的 display() 方法和移动代码。

以下是修改后的代码:

from amoeba import Amoeba
amoebas = []
for i in range(8):
    diameter = random(50, 200)
    speed = 1000 / (diameter * 50)
    x, y = random(800), random(400)
    amoebas.append(Amoeba(x, y, diameter, speed, speed))

def draw():
    background('#004477')
    pointer = PVector(mouseX, mouseY)
    for a in amoebas:
        difference = pointer - a.location
        a.propulsion += difference.limit(a.maxpropulsion/100)
        a.location += a.propulsion.limit(a.maxpropulsion)
        a.location += current
        a.display()

在上述代码中,第一个 for 循环随机生成每个变形虫的直径、速度和起始坐标,并将其添加到 amoebas 列表中。第二个 for 循环遍历列表,为每个变形虫计算更新后的位置并显示。

处理变形虫越界问题

较大、较慢的变形虫可能会被水流推出显示窗口,为避免这种情况,可以添加边界环绕代码,使变形虫离开窗口后从另一侧重新出现。

for a in amoebas:
    ...
    r = a.d / 2
    if a.location.x - r > width:   
        a.location.x = 0 - r
    if a.location.x + r < 0:       
        a.location.x = width + r
    if a.location.y - r > height:  
        a.location.y = 0 - r
    if a.location.y + r < 0:       
        a.location.y = height + r

这四个 if 语句检查变形虫是否完全离开显示窗口的四条边,如果是,则将其重新定位到窗口的另一侧。

8. 总结与拓展

通过以上步骤,我们实现了多个变形虫在显示窗口中的模拟运动,并且可以让它们向鼠标指针移动。整个过程涉及到面向对象编程、向量运算、代码模块化等多个编程概念。

以下是整个项目的关键步骤总结表格:
| 步骤 | 操作 | 代码示例 |
| — | — | — |
| 代码拆分 | 创建 amoeba.py 模块并导入 | from amoeba import Amoeba |
| 向量使用 | 创建推进向量和位置向量 | self.propulsion = PVector(xspeed, yspeed); self.location = PVector(x, y) |
| 向量运算 | 向量加法、减法和限制大小 | a1.location += a1.propulsion; difference = pointer - a1.location; a1.propulsion += difference.limit(0.03) |
| 多变形虫管理 | 使用列表存储变形虫并循环处理 | amoebas = []; for a in amoebas: ... |
| 边界处理 | 实现边界环绕 | if a.location.x - r > width: a.location.x = 0 - r |

下面是整个变形虫模拟系统的流程图:

graph TD;
    A[初始化模块和向量] --> B[生成变形虫列表];
    B --> C[进入 draw() 函数];
    C --> D[计算鼠标指针与变形虫的差向量];
    D --> E[更新变形虫推进向量];
    E --> F[更新变形虫位置向量];
    F --> G[检查变形虫是否越界并处理];
    G --> H[显示变形虫];
    H --> C;

向量和 PVector 类还有更多的操作,如向量乘法、除法、归一化和处理三维向量等。这些知识可以应用于需要物理模拟的编程项目,如视频游戏等。你可以根据自己的需求进一步拓展这个变形虫模拟系统,例如添加更多的力、改变变形虫的行为等。

这个是完整源码 python实现 Flask,Vue 【python毕业设计】基于Python的Flask+Vue物业管理系统 源码+论文+sql脚本 完整版 数据库是mysql 本文首先实现了基于Python的Flask+Vue物业管理系统技术的发展随后依照传统的软件开发流程,最先为系统挑选适用的言语和软件开发平台,依据需求分析开展控制模块制做和数据库查询构造设计,随后依据系统整体功能模块的设计,制作系统的功能模块图、E-R图。随后,设计框架,依据设计的框架撰写编码,完成系统的每个功能模块。最终,对基本系统开展了检测,包含软件性能测试、单元测试和性能指标。测试结果表明,该系统能够实现所需的功能,运行状况尚可并无明显缺点。本文首先实现了基于Python的Flask+Vue物业管理系统技术的发展随后依照传统的软件开发流程,最先为系统挑选适用的言语和软件开发平台,依据需求分析开展控制模块制做和数据库查询构造设计,随后依据系统整体功能模块的设计,制作系统的功能模块图、E-R图。随后,设计框架,依据设计的框架撰写编码,完成系统的每个功能模块。最终,对基本系统开展了检测,包含软件性能测试、单元测试和性能指标。测试结果表明,该系统能够实现所需的功能,运行状况尚可并无明显缺点。本文首先实现了基于Python的Flask+Vue物业管理系统技术的发展随后依照传统的软件开发流程,最先为系统挑选适用的言语和软件开发平台,依据需求分析开展控制模块制做和数据库查询构造设计,随后依据系统整体功能模块的设计,制作系统的功能模块图、E-R图。随后,设计框架,依据设计的框架撰写编码,完成系统的每个功能模块。最终,对基本系统开展了检测,包含软件性能测试、单元测试和性能指标。测试结果表明,该系统能够实现所需的功能,运行状况尚可并无明显缺点。本文首先实现了基于Python的Flask+Vue物业管理系统技术的发
源码地址: https://pan.quark.cn/s/a4b39357ea24 # SerialAssistant串口助手 下载地址: 本仓库release文件夹 在线下载:http://mculover666.cn/SerialAssistant.zip 功能说明 本项目是使用C# + WinForm框架编写的串口助手。 目前版本为2.0.0版本,拥有以下功能: 未打开串口时,自动扫描可用端口 接收数据支持文本或者HEX方式显示 支持接收数据加入时间戳 支持将当前接收数据保存为文件 支持发送文本数据或HEX数据 支持自动定时发送数据 支持从文件中(.txt, .json)加载数据到发送文本框 支持发送数据记录(不重复记录) ……欢迎加入更多功能 环境说明 VS2019 .NET Framework 4.5 教程 C#上位机开发(一)—— 了解上位机 C#上位机开发(二)—— Hello,World C#上位机开发(三)—— 构建SerialAssistant雏形 C#上位机开发(四)—— SerialAssistant功能完善 C#上位机开发(五)——SerialAssistant界面升级(WinForm界面布局进阶) C#上位机开发(六)——SerialAssistant功能优化(串口自动扫描功能、接收数据保存功能、加载发送文件、发送历史记录、打开浏览器功能、定时发送功能) C#上位机开发(七)—— 修改窗口图标和exe文件图标 C#上位机开发(八)—— 美化界面(给按钮添加背景) 更新日志 2018/6/3 完成串口属性设置,打开与关闭异常处理; 字符串发送功能; 字符串接收功能; 2018/6/4 完善串口扩展功能界面部分 2018/6/6 完善...
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值