Python 物理引擎pymunk最完整教程(下)

上一篇文章:Python 物理引擎pymunk最完整教程(中)
总目录:https://gitee.com/python_zzy/csdn-articles/blob/master/pymunk/README.md

8 空间管理

8.1 管理身体、形状、约束

身体、形状、以及约束对象都要被添加到空间中才能起作用。添加的方法是Space.add方法。有一些功能只有在物体被加入空间后才能使用,例如查询。

space.add(body1, shape2, constraint1, constraint2, body2, ...)

身体、形状、约束被加入空间后,以列表形式被储存在Space.bodies, shapes, constraints几个属性中。

与add相反,Space.remove方法可以用于删除被加入空间的身体、形状、或约束。但是需要注意:如果要删除一个物体,那么需要同时删除这个物体的Body, Shape, 以及附加的Constraint对象。只删除一个Body对象或是只删除一个Shape都无法彻底地删除这个物体。
Space.remove方法主要用于删除“飞出边界”的物体。例如一个小球从一个斜坡上落下去,如果不加以处理,它会一直下坠而不会自动删除,拖慢了程序的速度。

for shape in space.shapes: # 遍历空间内所有形状
    if shape.body.position.y > ...: # 如果飞出了边界
        space.remove(shape, shape.body) # 删除形状和对应身体

8.2 static_body

Space对象在创建时自带一个static_body属性,表示一个静态的Body对象,位于(0,0)。通常不需要直接将这个属性和Shape绑定,也不需要将它添加到空间,但是使用它能方便一些操作,比如添加固定支点的约束。

import pymunk

space = pymunk.Space()
space.gravity = (0, 500)

b = pymunk.Body()
b.position = (100, 100)
s = pymunk.Segment(b, (0, 0), (50, 50), 5)
s.mass = 1
space.add(b, s, pymunk.PivotJoint(b, space.static_body, b.position))

if __name__ == "__main__":
    import util
    util.run(space)

在这里插入图片描述
这段代码实现了一个钟摆的效果。前文已经介绍过,PivotJoint创建一个单点作为约束点的关节,使用世界坐标表示约束点。这段代码直接将static_body和线段的body进行了约束。

8.3 复制和转储空间

pymunk.Space对象可以被复制或用pickle转储。通过Space.copy方法可以复制Space对象,得到一个新的空间。

>>> space = pymunk.Space()
>>> space2 = space.copy()

Space也可以被pickle模块进行转储和载入。pickle模块可以直接将一些python对象转换为字符串一样的数据,可以将pickle后的数据转换为python对象。关于pickle详细用法参见:https://blog.csdn.net/Hardworking666/article/details/112754839

import pymunk
import random # 随机数模块
import pickle # python对象序列化与反序列化模块

def add_circle():
    b = pymunk.Body()
    b.position = (random.randint(100, 900), 100) # 随机位置
    s = pymunk.Circle(b, random.randint(1, 50)) # 创建半径随机大小的圆形
    s.mass = 1
    s.elasticity = 0.8 # 设置弹性系数
    space.add(b, s)

try:
    space = pickle.load(open("savefile.p", "rb")) # 载入空间,以二进制形式读取
except FileNotFoundError: # 没找到储存的空间
    space = pymunk.Space() # 创建新的空间
    space.gravity = (0, 500)

    body = pymunk.Body(body_type=pymunk.Body.STATIC) # 创建静态地面
    body.position = (0, 550)
    shape = pymunk.Segment(body, (0, 10), (1000, 10), 3)
    shape.elasticity = 0.8
    space.add(body, shape)

    for i in range(random.randint(2, 6)): # 添加随机2-6个圆形
        add_circle()

count = 0 # 计数器
def update():
    global count
    count += 1
    if count > 120: # 每过2秒保存一次pymunk空间
        count = 0
        
        pickle.dump(space, open("savefile.p", "wb")) # 以二进制形式将pymunk空间储存到文件中
        print("Saved!")

if __name__ == "__main__":
    import util
    util.run(space, update)

这段代码首先在空间中添加随机数量和随机大小的几个圆形,每过2s就会储存一次空间。打印出"Saved!"后,重新运行程序,发现运行结果和上一次储存时的空间内容一样,说明pickle成功转储了空间。目录下有一个savefile.p文件,表示的就是pickle储存空间的内容。

8.4 Space对象属性参考

class pymunk.Space(threaded: bool = False)
创建一个pymunk空间。所有身体、形状、约束都必须被添加到一个正确的空间中才能起作用。
threaded表示是否使用线程来运行物理求解器,这在空间数量较多时可能有用(Windows系统不支持)。
空间可以被复制或用pickle转储。
add(*objs: Body | Shape | Constraint) → None
将身体、形状、约束加入空间
add_collision_handler(collision_type_a: int, collision_type_b: int) → CollisionHandler
添加碰撞处理器,处理collison_type_a和collision_type_b发生的碰撞(详见7.1)
add_default_collision_handler() → CollisionHandler
添加默认碰撞处理器,用于处理那些没有通过普通碰撞处理器和通配符碰撞处理器进行处理的碰撞(详见7.5)
add_wildcard_collision_handler(collision_type_a: int) → CollisionHandler
添加通配符碰撞处理器,用于处理所有与collision_type_a发生的碰撞(详见7.5)
add_post_step_callback(callback_function: Callable[[…], None], key: Hashable, *args: Any, **kwargs: Any) → bool
注册一个回调函数,将在下一次Space.step结束后调用。
func(space : Space, key, *args, **kwargs)
bb_query(bb: BB, shape_filter: ShapeFilter) → List[Shape]
查询边界框附近的形状列表(详见7.6)
property bodies: List[Body]
所有加入空间的身体对象
property constraints: List[Constraint]
所有加入空间的约束对象
property shapes: List[Shape]
所有加入空间的形状对象
property collision_bias: float
决定空间以多快的速度将两个重叠的物体推开,值在0-1之间,但一般不需要改变这个值。pymunk允许快速移动的物体重叠,然后随着时间的推移修复重叠(把重叠的物体推开);例如两个小球相撞,小球的形状之间会发生重叠,然后通过修复而弹开。
默认值约为0.2%,表示经过1s后剩余0.2%的重叠没有修复。
property collision_persistence: float
空间保持碰撞解决方案的帧数,一般不用改变这个值。
property collision_slop: float
允许的形状之间的重叠量。为了提高稳定性,将这个值设置得尽可能高,就不会出现明显的重叠。默认值为0.1。
copy() → T
复制空间(深层复制)
property current_time_step: float
上一次调用(或当前调用)Space.step所指定的时间步长。
property damping: float
空间的阻尼系数(详见2.5)
property gravity: Vec2d
空间的重力加速度(详见2.5)
debug_draw(options: SpaceDebugDrawOptions) → None
绘制当前pymunk空间中的内容,options是绘制选项(详见后文)
property idle_speed_threshold: float
一个物体被认为是进入了“空闲状态”的速度。如果一个物体的速度很小,基本可以被认为是静止了,这个速度小于idle_speed_threshold,就会认为这个物体进入了空闲状态。默认值为0,表示根据重力加速度估计一个合适的空闲速度。

“空闲”和“睡眠”不是一个概念,空闲是指这个身体静止或几乎静止了,而睡眠是指让这个物体的各属性不再刷新。

property iterations: int
物理求解器的迭代数量,默认为10。这个值设置的越大,物理效果越真实,但是也会增加CPU占用。
point_query(point: Tuple[float, float], max_distance: float, shape_filter: ShapeFilter) → List[PointQueryInfo]
查询与point这个点距离为max_distance以内的所有形状,包含传感器(详见7.6)
point_query_nearest(point: Tuple[float, float], max_distance: float, shape_filter: ShapeFilter) → PointQueryInfo | None
查询与point这个点距离为max_distance以内的最近的形状,不包含传感器(详见7.6)
reindex_shape(shape: Shape) → None
当更改了某个身体、形状的一些属性(如:位置)后,如果立即进行形状查询可能不准。此时可以调用这个方法来刷新给定shape的碰撞信息来避免这一点。
reindex_shapes_for_body(body: Body) → None
刷新给定身体对应的所有形状的碰撞信息。
reindex_static() → None
刷新空间中静态形状的碰撞检测信息。只有在移动静态形状时才需要调用这个函数。
remove(*objs: Body | Shape | Constraint) → None
从空间中删除身体、形状、约束。
segment_query(start: Tuple[float, float], end: Tuple[float, float], radius: float, shape_filter: ShapeFilter) → List[SegmentQueryInfo]
沿着start到end,以radius为线段半径,查询与此线段相交的所有形状,包含传感器(详见7.6)
segment_query_first(start: Tuple[float, float], end: Tuple[float, float], radius: float, shape_filter: ShapeFilter) → SegmentQueryInfo | None
沿着start到end,以radius为线段半径,查询与此线段相交的所有形状,不包含传感器(详见7.6)
shape_query(shape: Shape) → List[ShapeQueryInfo]
查询与给定形状相交的所有形状,包含传感器(详见7.6)。
property sleep_time_threshold: float
物体进入空闲状态多少秒后,进入睡眠状态。默认为float(“inf”)表示禁用睡眠。如果不设置这个值,那么无法使用Body.sleep方法。(详见3.3)
property static_body: Body
表示一个位于(0,0)的静态身体对象(详见8.2)。
step(dt: float) → None
使物理空间执行一段时间
property threads: int
如果创建空间时启用了线程,则表示用于通过线程运行step的数量,最大数量为2,默认为1。(Windows系统不支持)
use_spatial_hash(dim: float, count: int) → None
将空间切换为使用空间散列而不是边界框树。dim是哈希单元的大小,count是哈希表中建议的最小单元格数

9 其他功能

9.1 自动几何图形转换

pymunk.autogeometry模块用于将图像类的信息转换成几何图形,从而创建符合形状的多边形。

pymunk.autogeometry.march_hard(bb: BB, x_samples: int, y_samples: int, threshold: float, sample_func: Callable[[Tuple[float, float]], float]) → PolylineSet

autogeometry.march_hard方法对需要转换为几何图形的内容进行采样并返回多边形点集对象autogeometry.PolylineSet。首先需要提供几何图形的边界框作为参数,然后决定x方向上的采样和y方向上的采样数量,还需要提供threshold表示算法的容错率。采样时,pymunk会按照采样数量遍历边界框内的点的坐标,然后将坐标传递给sample_func回调函数。sample_func回调函数接受一个2D向量作为参数,需要返回一个布尔值,决定是否对当前坐标的点进行标记。march_hard会将所有标记的点储存到PolylineSet对象中,并进行一些删减、分类之类的处理,从而就有了创建多边形的每一个点。
示例如下:

import pymunk
from pymunk import autogeometry
from pprint import pprint # 漂亮打印,只是为了输出的结果更好看,与pymunk无关

def sample_func(point):
    x = int(point[0])
    y = int(point[1]) # 采样点不是整数,需要转换为整数后进行列表切片
    marked = img[y][x]

    if marked == "x": # 判断当前采样点是否被标记
        return True # 被标记的采样点会被储存到PolylineSet中
    else:
        return False

img = [
    "...xx...",
    "...xx...",
    "...xx...",
    "...xxxx."
    ] # .表示空像素点区域,x表示这个位置有像素
w = len(img[0]) # 图像的宽度(x方向上的长度)
h = len(img) # 图像的高度(y方向上的长度)

pl_set = autogeometry.march_hard(pymunk.BB(0,0,w-1,h-1), w, h, 0.1, sample_func)
pprint(list(pl_set)) # 打印出采样的结果

运行后输出:

[[Vec2d(2.5, 3.0), Vec2d(2.5, 2.0), Vec2d(2.5, 1.0), Vec2d(2.5, 0.0)],
 [Vec2d(5.5, 0.0),
  Vec2d(5.5, 1.0),
  Vec2d(5.5, 2.0),
  Vec2d(5.5, 2.5),
  Vec2d(6.0, 2.5),
  Vec2d(6.5, 2.5),
  Vec2d(6.5, 3.0)]]

这个示例中,设置了采样范围为:(0,0,w-1,h-1),在x方向上有w个采样点。因此,pymunk在x方向上会从0遍历到w-1,步长为1。采样过程的python代码表示如下(实际算法肯定有所不同,只是下面的写法比文字表述容易理解):

#bb: BB, x_samples: int, y_samples: int, sample_func: Callable[[Tuple[float, float]], float]
def march_hard(...):
	for x in range(bb.left, bb.right + 1, int(bb.right - bb.left) // x_samples):
		for y in range(bb.bottom, bb.top + 1, int(bb.top - bb.bottom) // y_samples):
			if sample_func((x, y)):
				... # 对这个点进行标记
			else:
				... # 舍弃

除了march_hard之外,autogeometry还有一个march_soft方法,用法和march_hard完全一样,只是适用范围不同。march_hard适合那种线条横平竖直的图像,march_soft则对采样点进行了抗锯齿的处理,使得采样线条更加平滑。

通过march_hard方法进行采样后,需要对采样后的数据进行处理。需要注意的是,march_hard所返回的PolylineSet对象转换为列表后,里面可能有多个小列表,表示一条条折线。如果要创建多边形Poly的话,首先需要把这些列表拼接起来。直接遍历列表进行拼接效果有时候不是特别理想,如果想要完美复现形状可以将这些小列表中的折线用一条条Segment创建出来。

import pymunk
from pymunk import autogeometry

def sample_func(point):
    x = int(point[0])
    y = int(point[1]) # 采样点不是整数,需要转换为整数后进行列表切片
    marked = img[y][x]

    if marked == "x": # 判断当前采样点是否被标记
        return True # 被标记的采样点会被储存到PolylineSet中
    else:
        return False

img = [
    "...xx...",
    "...xx...",
    "...xx...",
    "...xxxx."
    ] # .表示空像素点区域,x表示这个位置有像素
w = len(img[0]) # 图像的宽度
h = len(img) # 图像的高度

pl_set = autogeometry.march_hard(pymunk.BB(0,0,w-1,h-1), w, h, 0.1, sample_func)
pl = [point for a in pl_set for point in autogeometry.simplify_vertexes(a, 1)] # 将列表拼接起来,调用simplify_curves是为了优化折线效果

space = pymunk.Space()
b = pymunk.Body()
b.position = (100, 100)
shape = pymunk.Poly(b, pl, pymunk.Transform.scaling(10), radius=1) # 创建多边形,放大10倍
shape.mass = 1
space.add(b, shape)

if __name__ == "__main__":
    import util
    util.run(space)

运行效果如下:
在这里插入图片描述
效果并不是很理想,因为拼接列表并不是按照点的顺序将折线连接起来的,需要其他算法进行处理。
代码中还使用了autogeometry.simplify_curves函数。这个函数接受一个折线的点列表,它会根据算法优化这个列表。这个函数还需要一个参数表示算法容错,根据实际情况设定即可。

>>> autogeometry.simplify_vertexes([(0, 0), (10, 0), (20, 0), (20, 20), (10, 20), (0, 20)], 0)
[Vec2d(0.0, 0.0), Vec2d(20.0, 0.0), Vec2d(20.0, 20.0), Vec2d(0.0, 20.0)]

上面这个示例中,需要优化的点列表表示一个长方形,但是其中包含了长方形的非支点。经过优化后,只保留了长方形的四个支点坐标。
与这个方法类似,simplify_curves方法也用于优化点的列表,但是这个函数比较适合平滑或者轻微弯曲的图形,而simplify_vertexes适合边缘直或有角度的图形。

下面这个示例展示了使用Segment连接图形的轮廓,创建出一个和pymunk logo一样的图形。

import pymunk
import pygame as pg
from pymunk import autogeometry

space = pymunk.Space()
space.gravity = (0, 500)

surf = pg.image.load("pymunk_logo.png") # 导入图像
w, h = surf.get_size() # 获取图像的宽和高

def sample_func(point):
    color = surf.get_at(point) # 获取位于point的像素颜色
    return 1 if color[3] else 0 # 像素是否为不透明的

pl_set = autogeometry.march_soft(pymunk.BB(0,0,w-1,h-1), 100, 100, 0.1, sample_func) # 抗锯齿取样
polyline = [b for a in list(pl_set) for b in a]
line = pymunk.autogeometry.simplify_curves(polyline, 1.0) # 优化多边形各点列表

b = pymunk.Body()
b.position = (100, 100)
space.add(b)
    
d = 1
for i in range(len(line) - 1):
    p1 = line[i] * d # 线段的起始点
    p2 = line[i + 1] * d # 线段的终点
    shape = pymunk.Segment(b, p1, p2, 1)
    shape.mass = 1
    shape.elasticity = 0.8
    space.add(shape)

b = pymunk.Body(body_type=pymunk.Body.STATIC) # 创建静态地面
b.position = (0, 550)
shape = pymunk.Segment(b, (0, 10), (1000, 10), 3)
shape.elasticity = 0.8
space.add(b, shape)

if __name__ == "__main__":
    import util
    util.run(space)

请添加图片描述 -> 在这里插入图片描述

关于autogeometry更多方法可参考:https://www.pymunk.org/en/latest/pymunk.autogeometry.html

9.2 调试

通常情况下,pymunk不提供有关绘图的内容,因此需要与pygame, pyglet, matplotlib等图形模块相结合,才能将绘制的内容显示到窗口上。但是为了方便测试,pymunk提供了调试绘图方法Space.debug_draw。
Space.debug_draw方法需要一个参数表示绘制选项。一般的绘图选项类是pymunk.SpaceDebugDrawOptions,但是这个选项并不创建窗口绘制内容,而是输出绘制的内容。

import pymunk

space = pymunk.Space()
body = pymunk.Body()
body.position = (100, 100)
shape = pymunk.Circle(body, 10)
shape.mass = 1
space.add(body, shape)

options = pymunk.SpaceDebugDrawOptions() # 创建绘图选项对象
while True:
    space.step(0.01) # 运行空间一段时间(0.01s)
    space.debug_draw(options) # 绘制空间中的内容

运行后,程序不断输出:

draw_circle (Vec2d(100.0, 100.0), 0.0, 10.0, SpaceDebugColor(r=44.0, g=62.0, b=80.0, a=255.0), SpaceDebugColor(r=52.0, g=152.0, b=219.0, a=255.0))
draw_circle (Vec2d(100.0, 100.0), 0.0, 10.0, SpaceDebugColor(r=44.0, g=62.0, b=80.0, a=255.0), SpaceDebugColor(r=52.0, g=152.0, b=219.0, a=255.0))
...

除此之外,pymunk.pygame_util, pyglet_util, matplotlib_util中各包含一个DrawOptions类表示绘制选项,分别用于在pygame, pyglet, matplotlib模块中的调试绘图。
以pygame为例:

import pygame as pg
import pymunk
from pymunk.pygame_util import DrawOptions # 导入pygame_util中的绘图选项

space = pymunk.Space()
body = pymunk.Body()
body.position = (100, 100)
shape = pymunk.Circle(body, 10)
shape.mass = 1
space.add(body, shape)

pg.init()
screen = pg.display.set_mode((1000, 600))
clock = pg.time.Clock()

options = DrawOptions(screen) # pygame的绘图选项,需要提供pygame窗口表面作为参数
while True:
    screen.fill((255, 255, 255))

    for event in pg.event.get():
        if event.type == pg.QUIT:
            pg.quit()
            exit()

    space.step(1 / 60) # 运行pymunk空间1/60s
    space.debug_draw(options) # 调试绘图

    pg.display.flip()
    clock.tick(60)

在这里插入图片描述
事实上,这正类似于前文用于测试代码的util.py所做的(util.py中还包含了一些附加功能)。
绘图选项也不是一成不变的,形状颜色,绘制哪些东西,怎么绘制,这些都是可以进行修改的,尽管一般不需要。详见:https://www.pymunk.org/en/latest/pymunk.html#pymunk.SpaceDebugDrawOptions
在这里插入图片描述

9.3 其他子模块

batch

batch模块用于对一些操作进行批处理,可以有效提高程序的运行效率。但是batch模块目前是实验性的模块,且涉及到较多内存操作,这里暂不详细介绍。
关于batch模块可以参考pymunk.examples.planet。文档位于:https://www.pymunk.org/en/latest/pymunk.batch.html

examples

pymunk的官方教程并不怎么详细,但是读者可以通过pymunk文件夹下的examples文件中的程序学习pymunk。其中包含一些经典的示例。关于全部示例以及介绍参见:https://www.pymunk.org/en/latest/examples.html

10 总结

关于本教程的任何疏漏之处和建议欢迎提出。

另附一pymunk+pygame游戏实战代码,包含详细注释,可在https://gitee.com/python_zzy/csdn-articles/tree/master/pymunk/2048下载。

请添加图片描述

  • 7
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: pymunk是一个用于物理模拟的Python库,您可以使用它来绘制圆弧。要绘制圆弧,首先需要创建一个空间,然后使用pymunk.Arc()函数创建一个弧形对象。可以通过设置弧形对象的位置、半径和角度来控制弧形的形状。最后,可以使用pymunk.Space.add()函数将弧形对象添加到空间中,并使用pymunk.pygame_util.draw()函数在Pygame窗口中绘制弧形。 示例代码: ``` import pymunk import pymunk.pygame_util import pygame # 创建Pygame窗口 screen = pygame.display.set_mode((600, 600)) # 创建pymunk空间 space = pymunk.Space() # 创建圆弧 arc = pymunk.Arc(space.static_body, 50, 0, 180, (300, 300)) space.add(arc) # 循环渲染 running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # 绘制圆弧 screen.fill((255, 255, 255)) pymunk.pygame_util.draw(screen, space) pygame.display.flip() # 退出 pygame.quit() ``` ### 回答2: pymunk是一个强大的物理引擎库,它允许我们在Python中模拟物理世界和物体的运动。然而,pymunk本身并不提供绘制功能,因此我们需要借助其他库(例如pygame)来进行绘制。 要使用pymunk绘制圆弧,我们可以按照以下步骤进行: 1. 首先,我们需要导入所需的库,包括pymunk和pygame。确保这些库已经安装在你的Python环境中。 2. 创建一个绘制窗口。可以使用pygame的相关函数来创建一个窗口,并设置窗口的大小和标题。 3. 创建一个pymunk的空间(Space)对象。pymunk的空间用于模拟物理环境和物体的运动。 4. 创建一个pymunk的刚体(Body)对象。刚体用于表示物体,并设置刚体的质量、惯性和形状。 5. 创建一个pymunk的形状(Shape)对象。形状用于定义刚体的形状和碰撞特性。 6. 添加刚体和形状到空间中。通过空间的add函数将刚体和形状添加到空间中。 7. 循环绘制。使用pygame的绘制函数在窗口中绘制圆弧。可以使用pymunk的刚体和形状的属性来获取圆弧的位置和大小。 8. 在每次循环结束后更新空间,以便物体的运动会被模拟。 9. 在绘制循环中添加事件处理,以便可以关闭窗口或按下键盘时退出程序。 绘制圆弧的具体代码可以根据实际需求进行调整,但以上步骤提供了一个基本的框架来绘制圆弧。希望这些信息对你有帮助! ### 回答3: Pymunk是一个用于物理模拟和碰撞检测的Python库,它是Chipmunk物理引擎Python绑定。 在pymunk中,要绘制圆弧,可以借助 Shape类,并设置圆弧的半径、起始角度和终止角度。 首先,我们需要创建一个空的空间(Space)来进行物理模拟和碰撞检测,可以通过如下代码创建一个空间: ```python space = pymunk.Space() ``` 接下来,我们创建一个静态的圆弧形状(StaticBody),设置其位置和半径: ```python body = pymunk.Body(body_type=pymunk.Body.STATIC) body.position = (0, 0) # 设置圆弧的位置 radius = 50 # 设置圆弧的半径 ``` 然后,我们通过引入 pymunk.constraints 包来创建一个旋转关节(RotaryLimitJoint)来约束圆弧的运动范围,这样圆弧就不会旋转超出我们指定的角度范围: ```python constraint = pymunk.constraints.RotaryLimitJoint(body, body, start_angle, end_angle) ``` 其中 start_angle 和 end_angle 分别为起始角度和终止角度,可以根据需要设置。 最后,将圆弧形状和旋转关节添加到空间中,用于物理模拟: ```python space.add(body, constraint) ``` 通过这样的步骤,我们就成功创建了一个pymunk绘制的圆弧。你可以根据需要调整圆弧的位置、半径和角度范围。 pymunk提供了许多其他的功能和选项,例如弹簧约束、摩擦力等,在实际应用中可以根据需要进行使用和配置。希望这些信息对你有所帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值