鱼C论坛

 找回密码
 立即注册
查看: 30|回复: 2

[技术交流] 让Python游戏开发和Scratch一样简单——Scrawl引擎

[复制链接]
发表于 4 小时前 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 streetart 于 2025-8-24 13:59 编辑

经过将近2个月的完善,Scrawl v0.12.1终于和大家见面啦!Scrawl v0.12.x将是v1.1.0稳定版发布前的最后一个主版本,我们将在这一版对Scrawl进行优化,稳定功能。

Github链接:github.com/streetartist/scrawl
QQ交流群:1001578435
文档:cold-coil-43b.notion.site/Scrawl-228c40bc2a0e809585dce4119e52681c?pvs=74
知乎介绍文章:zhuanlan.zhihu.com/p/1931324296887792267

欢迎加入Scrawl库的开发或使用Scrawl库!

那么,什么是Scrawl呢?

简而言之,就是我们希望能够在Python中实现Scratch游戏编程的范式,让Scratch转Python游戏编程更简单。

我们实现了哪些简化功能
  • 让大家可以愉快地写while True
  • 实现事件系统
  • 实现了云变量
  • 甚至实现了简单的GUI功能(基于pygameGUI)感谢@cjjJasonchen

AI生成的介绍:
告别复杂,拥抱创造:用Python轻松打造您的下一个游戏大作!
您是否曾梦想创造属于自己的游戏,却被复杂的游戏引擎和陡峭的学习曲线劝退?您是否熟悉Scratch的积木式编程,并渴望在Python的强大世界中找到同样直观有趣的创作体验?

现在,我们向您隆重介绍一个全新的Python游戏框架——它将Scratch的简洁理念与Python的强大功能完美融合,让游戏开发变得前所未有的简单、快速和充满乐趣!

核心理念:像玩积木一样写代码
我们深知,从图形化编程到文本代码的跨越是许多学习者面临的巨大挑战。因此,我们的框架在设计上处处体现着对初学者的友好:

熟悉的概念,无缝过渡:拥有“角色(Sprite)”、“场景(Scene)”、“造型(Costume)”和“广播(Broadcast)”等核心概念,如果您用过Scratch,就能立刻上手。
装饰器驱动,事件清晰:忘掉冗长的事件循环判断吧!只需一个简单的装饰器,就能让函数响应键盘或鼠标事件。代码直观得就像在说:“当按下空格键时,执行这个动作!”
  1. # 代码就是这么简单!
  2. from pygame.constants import K_SPACE

  3. # 仅供示例,实际使用要在继承Sprite的类中使用。
  4. @on_key(K_SPACE, "pressed")
  5. def jump(self):
  6.     self.play_sound("jump_sound")
  7.     self.velocity.y = -10
复制代码
不止于简单,更有强大功能
简洁的设计之下,是毫不妥协的强大功能。无论您是想快速实现原型,还是打造功能丰富的游戏,这个框架都能满足您的需求。

高级碰撞检测:内置矩形(rect)、圆形(circle)和像素完美(mask)三种碰撞模式。您可以根据性能和精度的需求自由切换,从简单的方块碰撞到不规则图形的精确检测,尽在掌握。
内置物理引擎:通过 PhysicsSprite 类,为您的游戏角色一键添加重力、摩擦力和弹性效果。创建平台跳跃游戏或物理弹球从未如此轻松。
丰富的多媒体支持:轻松加载和播放背景音乐、音效,甚至可以动态生成鼓点和音符。更有开箱即用的粒子系统(ParticleSystem),为您的游戏增添华丽的视觉特效。
真正的“杀手级”功能:云变量(Cloud Variables)
想象一下,您的单机游戏能即时拥有在线功能吗?现在可以了!

我们独创的 CloudVariablesClient 类,让您仅用几行代码就能实现:

在线排行榜:实时记录并显示全球玩家的最高分。
多人数据同步:创建简单的在线协作或对战游戏,同步玩家位置、分数等关键数据。
游戏状态云端保存:让玩家可以跨设备继续他们的游戏进度。
这个功能将为您的创意插上翅膀,轻松实现过去需要复杂后端知识才能完成的在线交互。

为谁而生?
编程初学者和学生:这是从Scratch等图形化编程过渡到Python的最佳桥梁。在熟悉的理念下学习“真实世界”的编程范式。
游戏开发爱好者:无需学习庞大的商业引擎,用您钟爱的Python语言快速将创意变为现实。
教育工作者:一套完美的教学工具,能够生动地向学生展示事件驱动、面向对象和异步编程等核心计算机科学概念。
原型设计师:在投入大型项目前,用它快速验证您的游戏玩法和核心机制。
立即开始您的创作之旅!
停止观望,立即行动!这个框架已经为您铺平了通往游戏世界的所有道路。从一个简单的想法开始,添加角色,编写交互,部署特效,甚至连接云端。您的下一个游戏杰作,只差一个 import 的距离。

释放您的想象力,用Python和我们一起,将创造的乐趣带回编程!

多说无益,给大家看两个成品项目,只需要如此通俗的代码,即可实现炫酷小游戏:
1. 奇幻幽林:
(基于:scratch.mit.edu/projects/239626199/editor/ Scratch项目修改而来)
效果视频:s3plus.meituan.net/opapisdk/op_ticket_885190757_1756014248118_qdqqd_6uuzxy.mp4
代码与资源文件:github.com/streetartist/scrawl_demo_witch
主程序源码:
  1. from scrawl import *
  2. import pygame
  3. import time

  4. # svg files from scratch.mit.edu/projects/239626199/editor/
  5. # 游戏说明:
  6. # 通过左右方向键控制女巫旋转;
  7. # 通过空格键控制火球发射(点一次发射一个);
  8. # 碰到敌人就Game Over;
  9. # 按a键使用屏障,屏障显示3秒,10秒可用一次。


  10. # 创建游戏实例
  11. game = Game()


  12. class Bat1(Sprite):

  13.     def __init__(self):
  14.         super().__init__()
  15.         self.name = "Bat1"

  16.         self.add_costume("costume1",
  17.                          pygame.image.load("bat1-b.svg").convert_alpha())
  18.         self.add_costume("costume2",
  19.                          pygame.image.load("bat1-a.svg").convert_alpha())
  20.         self.visible = False
  21.         self.set_size(0.5)

  22.     @as_clones
  23.     def clones1(self):
  24.         self.pos = pygame.Vector2(400, 300)
  25.         self.face_random_direction()
  26.         self.move(400)
  27.         self.face_towards("Witch")
  28.         self.visible = True

  29.         while True:
  30.             self.next_costume()
  31.             yield 300

  32.     @as_clones
  33.     def clones2(self):
  34.         while True:
  35.             self.move(8) # 快速蝙蝠
  36.             yield 200

  37.     @as_main
  38.     def main1(self):
  39.         while True:
  40.             yield 3000
  41.             # 添加蝙蝠
  42.             self.clone()

  43.     @handle_sprite_collision("FireBall")
  44.     @handle_sprite_collision("Wall")
  45.     def die(self, other):
  46.         self.delete_self()

  47.     @handle_sprite_collision("Witch")
  48.     def hit_witch(self, other):
  49.         self.delete_self()

  50. class Bat2(Sprite):

  51.     def __init__(self):
  52.         super().__init__()
  53.         self.name = "Bat2"

  54.         self.add_costume("costume1",
  55.                          pygame.image.load("bat2-b.svg").convert_alpha())
  56.         self.add_costume("costume2",
  57.                          pygame.image.load("bat2-a.svg").convert_alpha())
  58.         self.visible = False
  59.         self.set_size(0.5)

  60.     @as_clones
  61.     def clones1(self):
  62.         self.pos = pygame.Vector2(400, 300)
  63.         self.face_random_direction()
  64.         self.move(400)
  65.         self.face_towards("Witch")
  66.         self.visible = True

  67.         while True:
  68.             self.next_costume()
  69.             yield 300

  70.     @as_clones
  71.     def clones2(self):
  72.         while True:
  73.             self.move(5)
  74.             yield 200

  75.     @as_main
  76.     def main1(self):
  77.         while True:
  78.             yield 3000
  79.             # 添加蝙蝠
  80.             self.clone()

  81.     @handle_sprite_collision("Witch")
  82.     def hit_witch(self, other):
  83.         self.delete_self()
  84.         
  85.     @handle_sprite_collision("FireBall")
  86.     @handle_sprite_collision("Wall")
  87.     def die(self, other):
  88.         self.delete_self()


  89. class FireBall(Sprite):

  90.     def __init__(self):
  91.         super().__init__()
  92.         self.name = "FireBall"
  93.         self.add_costume("costume1",
  94.                          pygame.image.load("ball-a.svg").convert_alpha())
  95.         self.visible = False
  96.         self.set_size(0.2)

  97.     @as_clones
  98.     def clones1(self):
  99.         self.visible = True

  100.         while True:
  101.             self.move(10)
  102.             yield 100

  103.     @handle_edge_collision()
  104.     def finish(self):
  105.         self.delete_self()

  106. class Wall(Sprite):
  107.     def __init__(self):
  108.         super().__init__()
  109.         self.name = "Wall"
  110.         self.add_costume("costume1",
  111.                          pygame.image.load("wall.png").convert_alpha())
  112.         self.set_size(0.5)
  113.         self.last_use = time.time() # 记录上一次使用
  114.         self.visible = False

  115.     @on_key(pygame.K_a, "pressed")
  116.     def use_wall(self):
  117.         if time.time() - self.last_use >= 10: # 最多10秒用一次屏障
  118.             self.visible = True
  119.             yield 3000 # 屏障显示3秒
  120.             self.visible = False
  121.             self.last_use = time.time()
  122.             

  123. class Gameover(Sprite):
  124.     def __init__(self):
  125.         super().__init__()
  126.         self.add_costume("costume1",
  127.                          pygame.image.load("gameover.png").convert_alpha())
  128.         self.visible = False

  129.     @handle_broadcast("gameover")
  130.     def gameover(self):
  131.         self.visible = True

  132. class Witch(Sprite):

  133.     def __init__(self):
  134.         super().__init__()
  135.         self.name = "Witch"

  136.         self.add_costume("costume1",
  137.                          pygame.image.load("witch.svg").convert_alpha())

  138.         self.fireball = FireBall()
  139.         self.set_size(0.7)

  140.     @on_key(pygame.K_RIGHT, "held")
  141.     def right_held(self):
  142.         self.turn_right(2)

  143.     @on_key(pygame.K_LEFT, "held")
  144.     def left_held(self):
  145.         self.turn_left(2)

  146.     @on_key(pygame.K_SPACE, "pressed")
  147.     def space_pressed(self):
  148.         self.fireball.direction = self.direction
  149.         self.clone(self.fireball)

  150.     @handle_sprite_collision("Bat1")
  151.     @handle_sprite_collision("Bat2")
  152.     def die(self):
  153.         self.broadcast("gameover")


  154. # 定义场景
  155. class MyScene(Scene):

  156.     def __init__(self):
  157.         super().__init__()

  158.         bat1 = Bat1()
  159.         self.add_sprite(bat1)

  160.         bat2 = Bat2()
  161.         self.add_sprite(bat2)

  162.         witch = Witch()
  163.         self.add_sprite(witch)

  164.         wall = Wall()
  165.         self.add_sprite(wall)

  166.         gameover = Gameover()
  167.         self.add_sprite(gameover)


  168. # 运行游戏
  169. game.set_scene(MyScene())
  170. game.run(fps=60)
复制代码


2. Scrawl版的Flappybird:
完整源码:github.com/streetartist/scrawl_demo_flappybird
视频:s3plus.meituan.net/opapisdk/op_ticket_885190757_1756014484001_qdqqd_5eh880.mp4
主程序:
  1. import pygame
  2. from pygame import K_SPACE
  3. import random
  4. from scrawl import Game, Scene, PhysicsSprite, Sprite, on_key, as_main, on_mouse_event, handle_edge_collision, handle_sprite_collision, CloudVariablesClient
  5. import time

  6. # 游戏常量
  7. SCREEN_WIDTH = 288
  8. SCREEN_HEIGHT = 512
  9. GRAVITY = 0.5
  10. FLAP_FORCE = -7
  11. PIPE_SPEED = 2.5
  12. PIPE_GAP = 120
  13. PIPE_FREQUENCY = 1500  # 每1500ms生成一个新管道

  14. client = CloudVariablesClient(project_id="0a668321-a7f0-4663-8d68-27c515142875")

  15. class Bird(PhysicsSprite):
  16.     def __init__(self):
  17.         super().__init__()
  18.         self.name = "小鸟"
  19.         self.set_collision_type("mask")
  20.         
  21.         # 创建小鸟图像
  22.         bird_width = 24
  23.         bird_height = 17
  24.         surface = pygame.Surface((bird_width, bird_height), pygame.SRCALPHA)
  25.         pygame.draw.ellipse(surface, (255, 200, 0), (0, 0, bird_width, bird_height))  # 鸟身体
  26.         pygame.draw.circle(surface, (255, 100, 0), (bird_width - 9, 6), 3)  # 鸟头
  27.         pygame.draw.polygon(surface, (255, 0, 0), [
  28.             (bird_width - 6, 6),
  29.             (bird_width + 4, 6),
  30.             (bird_width - 1, 8)
  31.         ])  # 鸟嘴
  32.         pygame.draw.ellipse(surface, (0, 0, 0), (bird_width - 12, 4, 3, 3))  # 眼睛
  33.         
  34.         self.add_costume("default", surface)
  35.         self.pos.x = SCREEN_WIDTH // 3  # 初始位置在屏幕1/3处
  36.         self.pos.y = SCREEN_HEIGHT // 2
  37.         
  38.         # 设置物理属性
  39.         self.set_gravity(0, GRAVITY)
  40.         self.set_elasticity(0.2)
  41.         self.set_friction(0.99)
  42.         
  43.         # 游戏状态
  44.         self.game_over = False
  45.         self.score = 0
  46.    
  47.     @on_key(K_SPACE, "pressed")
  48.     @on_mouse_event(button=1, mode="pressed")
  49.     def flap(self):
  50.         """按下空格键或鼠标使小鸟上升"""
  51.         if not self.game_over:
  52.             self.velocity.y = FLAP_FORCE
  53.             self.play_sound("flap")  # 播放拍翅膀音效
  54.    
  55.     @handle_edge_collision("top")
  56.     def hit_top(self):
  57.         """碰到上边界"""
  58.         if not self.game_over:
  59.             self.velocity.y = 0
  60.             self.pos.y = 10
  61.    
  62.     @handle_edge_collision("bottom")
  63.     @handle_sprite_collision("管道")
  64.     def hit_bottom(self):
  65.         """碰到下边界或管道 - 游戏结束"""
  66.         if not self.game_over:
  67.             self.game.log_debug("!!!!!")
  68.             self.game_over = True
  69.             self.say("Ouch!", 3000)
  70.             self.play_sound("hit")  # 播放撞击音效
  71.             
  72.             # 从云变量中获取当前最高分,如果不存在则默认为0
  73.             current_highest = client.get_variable("highest_score", 0)
  74.             
  75.             # 如果当前分数高于最高分,则更新云变量
  76.             if self.score > current_highest:
  77.                 client.set_variable("highest_score", self.score)
  78.    
  79.     @as_main
  80.     def bird_physics(self):
  81.         """处理小鸟物理状态"""
  82.         while True:
  83.             # 旋转小鸟基于下落速度
  84.             self.direction = max(-30, min(self.velocity.y * 2, 90))
  85.             yield 0

  86. class Pipe(Sprite):
  87.     def __init__(self, x, gap_y):
  88.         super().__init__()
  89.         
  90.         self.name = "管道"
  91.         self.set_collision_type("mask")
  92.         
  93.         # 随机管道高度
  94.         top_height = gap_y - PIPE_GAP // 2
  95.         bottom_height = SCREEN_HEIGHT - (gap_y + PIPE_GAP // 2)
  96.         
  97.         # 管道宽度
  98.         pipe_width = 52 * (SCREEN_WIDTH / 400)
  99.         
  100.         # 直接在一个surface上绘制管道
  101.         surface = pygame.Surface((pipe_width, SCREEN_HEIGHT), pygame.SRCALPHA)
  102.         
  103.         # 绘制上管道(从顶部向下绘制)
  104.         pygame.draw.rect(surface, (0, 180, 0), (0, 0, pipe_width, top_height))
  105.         # 上管道顶部装饰
  106.         pygame.draw.rect(surface, (0, 140, 0), (0, top_height - 15, pipe_width, 15))
  107.         
  108.         # 绘制下管道(从间隙底部开始绘制)
  109.         bottom_pipe_y = gap_y + PIPE_GAP // 2
  110.         pygame.draw.rect(surface, (0, 180, 0), (0, bottom_pipe_y, pipe_width, bottom_height))
  111.         # 下管道顶部装饰
  112.         pygame.draw.rect(surface, (0, 140, 0), (0, bottom_pipe_y, pipe_width, 15))
  113.         
  114.         self.add_costume("default", surface)
  115.         self.collision_mask = pygame.mask.from_surface(self.image)
  116.         self.pos.x = x
  117.         self.pos.y = SCREEN_HEIGHT // 2
  118.         
  119.         # 管道属性
  120.         self.passed = False  # 小鸟是否已通过该管道
  121.    
  122.     @as_main
  123.     def move_pipe(self):
  124.         """移动管道"""
  125.         while True:
  126.             if not self.scene.bird.game_over:
  127.                 self.pos.x -= PIPE_SPEED
  128.                
  129.                 # 检测小鸟是否通过管道
  130.                 if not self.passed and self.pos.x < self.scene.bird.pos.x:
  131.                     self.passed = True
  132.                     self.scene.bird.score += 1
  133.                     self.play_sound("point")  # 播放得分音效
  134.                
  135.                 # 管道移出屏幕后删除
  136.                 if self.pos.x < -100:
  137.                     self.delete_self()
  138.             
  139.             yield 0

  140. class Ground(Sprite):
  141.     def __init__(self):
  142.         super().__init__()
  143.         self.name = "地面"
  144.         
  145.         # 创建地面图像
  146.         ground_height = 40
  147.         surface = pygame.Surface((SCREEN_WIDTH, ground_height))
  148.         surface.fill((222, 184, 135))  # 浅棕色地面
  149.         
  150.         # 添加草地纹理
  151.         pygame.draw.rect(surface, (0, 180, 0), (0, 0, SCREEN_WIDTH, 8))
  152.         for i in range(0, SCREEN_WIDTH, 15):
  153.             pygame.draw.line(surface, (0, 140, 0), (i, 8), (i+8, 8), 2)
  154.         
  155.         self.add_costume("default", surface)
  156.         self.pos.x = SCREEN_WIDTH // 2
  157.         self.pos.y = SCREEN_HEIGHT - ground_height // 2

  158. class ScoreSprite(Sprite):
  159.     def __init__(self, bird):
  160.         super().__init__()
  161.         self.name = "得分显示"
  162.         self.bird = bird  # 引用小鸟对象以获取分数
  163.         
  164.         self.font = pygame.font.SysFont(None, 28)
  165.         
  166.         # 初始分数显示
  167.         self.update_score()
  168.         
  169.         # 设置精灵位置 (居中靠上)
  170.         self.pos.x = SCREEN_WIDTH // 2
  171.         self.pos.y = 40
  172.    
  173.     def update_score(self):
  174.         """更新分数显示"""
  175.         # 渲染分数文本
  176.         score_text = f"Score: {self.bird.score}"
  177.         text_surface = self.font.render(score_text, True, (255, 255, 255))
  178.         
  179.         # 添加阴影效果
  180.         shadow_surface = self.font.render(score_text, True, (0, 0, 0))
  181.         
  182.         # 创建最终表面(稍大一点以容纳阴影)
  183.         self.surface = pygame.Surface((text_surface.get_width() + 4, text_surface.get_height() + 4), pygame.SRCALPHA)
  184.         
  185.         # 绘制阴影(偏移2像素)
  186.         self.surface.blit(shadow_surface, (2, 2))
  187.         # 绘制主文本
  188.         self.surface.blit(text_surface, (0, 0))
  189.         
  190.         # 设置精灵造型
  191.         self.add_costume("default", self.surface)
  192.    
  193.     @as_main
  194.     def update_loop(self):
  195.         """持续更新分数显示"""
  196.         last_score = -1  # 初始值确保第一次会更新
  197.         while True:
  198.             # 当分数变化时更新显示
  199.             if self.bird.score != last_score:
  200.                 self.update_score()
  201.                 last_score = self.bird.score
  202.             yield 0
  203.             
  204. # 最高分显示精灵
  205. class HighestScoreDisplay(Sprite):
  206.     def __init__(self):
  207.         super().__init__()
  208.         self.name = "最高分显示"
  209.         
  210.         self.font = pygame.font.SysFont(None, 24) # 字体稍小
  211.         
  212.         self.highest_score = 0
  213.         self.update_display()
  214.         
  215.         # 设置位置,在当前分数下方
  216.         self.pos.x = SCREEN_WIDTH // 2
  217.         self.pos.y = 70
  218.    
  219.     def update_display(self):
  220.         """更新最高分显示"""
  221.         self.highest_score = client.get_variable("highest_score", 0) # 从云端获取,默认为0
  222.         
  223.         score_text = f"Highest: {self.highest_score}"
  224.         text_surface = self.font.render(score_text, True, (255, 255, 255))
  225.         shadow_surface = self.font.render(score_text, True, (0, 0, 0))
  226.         
  227.         self.surface = pygame.Surface((text_surface.get_width() + 4, text_surface.get_height() + 4), pygame.SRCALPHA)
  228.         self.surface.blit(shadow_surface, (2, 2))
  229.         self.surface.blit(text_surface, (0, 0))
  230.         
  231.         self.add_costume("default", self.surface)
  232.    
  233.     @as_main
  234.     def update_loop(self):
  235.         """持续更新最高分显示"""
  236.         last_highest_score = -1
  237.         while True:
  238.             current_highest = client.get_variable("highest_score", 0)
  239.             if current_highest != last_highest_score:
  240.                 self.update_display()
  241.                 last_highest_score = current_highest
  242.             # 每0.1秒更新一次最高分
  243.             yield 100

  244. class FlappyScene(Scene):
  245.     def __init__(self):
  246.         super().__init__()
  247.         self.name = "Flappy Bird"
  248.         self.background_color = (135, 206, 235)  # 天蓝色背景
  249.         
  250.         # 添加云朵装饰
  251.         for _ in range(4):
  252.             cloud = Sprite()
  253.             cloud_size = random.randint(20, 50)
  254.             surface = pygame.Surface((cloud_size*2, cloud_size), pygame.SRCALPHA)
  255.             pygame.draw.ellipse(surface, (255, 255, 255), (0, 0, cloud_size*2, cloud_size))
  256.             cloud.add_costume("default", surface)
  257.             cloud.pos.x = random.randint(0, SCREEN_WIDTH)
  258.             cloud.pos.y = random.randint(40, 150)  # 降低云朵高度
  259.             self.add_sprite(cloud)
  260.         
  261.         # 添加游戏元素
  262.         self.bird = Bird()
  263.         self.add_sprite(self.bird)
  264.         
  265.         self.ground = Ground()
  266.         self.add_sprite(self.ground)
  267.         
  268.         # 添加得分显示精灵
  269.         self.score_display = ScoreSprite(self.bird)
  270.         self.add_sprite(self.score_display)

  271.         # 添加最高分显示精灵
  272.         self.highest_score_display = HighestScoreDisplay()
  273.         self.add_sprite(self.highest_score_display)
  274.    
  275.     @as_main
  276.     def generate_pipes(self):
  277.         # 加载音效
  278.         self.game.load_sound("flap", "sounds/flap.ogg")
  279.         self.game.load_sound("hit", "sounds/hit.ogg")
  280.         self.game.load_sound("point", "sounds/point.ogg")
  281.         
  282.         """定时生成新管道"""
  283.         while True:
  284.             if not self.bird.game_over:
  285.                 # 随机管道位置 (确保间隙在屏幕内)
  286.                 gap_y = random.randint(120, SCREEN_HEIGHT - 120)
  287.                 new_pipe = Pipe(SCREEN_WIDTH + 30, gap_y)
  288.                 self.add_sprite(new_pipe)
  289.             
  290.             yield PIPE_FREQUENCY  # 等待一段时间后生成新管道
  291.             
  292. time.sleep(2)

  293. # 创建并运行游戏
  294. game = Game(width=SCREEN_WIDTH, height=SCREEN_HEIGHT, title="Flappy Bird - Scrawl 复刻版", fullscreen=True)
  295. game.set_scene(FlappyScene())
  296. game.run(fps=60)
复制代码

小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 4 小时前 | 显示全部楼层
牛逼
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 半小时前 | 显示全部楼层
哇哦,小陈的朋友吗
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-8-24 18:57

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表