|
马上注册,结交更多好友,享用更多功能^_^
您需要 登录 才可以下载或查看,没有账号?立即注册
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,就能立刻上手。
装饰器驱动,事件清晰:忘掉冗长的事件循环判断吧!只需一个简单的装饰器,就能让函数响应键盘或鼠标事件。代码直观得就像在说:“当按下空格键时,执行这个动作!”
- # 代码就是这么简单!
- from pygame.constants import K_SPACE
- # 仅供示例,实际使用要在继承Sprite的类中使用。
- @on_key(K_SPACE, "pressed")
- def jump(self):
- self.play_sound("jump_sound")
- 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
主程序源码:
- from scrawl import *
- import pygame
- import time
- # svg files from scratch.mit.edu/projects/239626199/editor/
- # 游戏说明:
- # 通过左右方向键控制女巫旋转;
- # 通过空格键控制火球发射(点一次发射一个);
- # 碰到敌人就Game Over;
- # 按a键使用屏障,屏障显示3秒,10秒可用一次。
- # 创建游戏实例
- game = Game()
- class Bat1(Sprite):
- def __init__(self):
- super().__init__()
- self.name = "Bat1"
- self.add_costume("costume1",
- pygame.image.load("bat1-b.svg").convert_alpha())
- self.add_costume("costume2",
- pygame.image.load("bat1-a.svg").convert_alpha())
- self.visible = False
- self.set_size(0.5)
- @as_clones
- def clones1(self):
- self.pos = pygame.Vector2(400, 300)
- self.face_random_direction()
- self.move(400)
- self.face_towards("Witch")
- self.visible = True
- while True:
- self.next_costume()
- yield 300
- @as_clones
- def clones2(self):
- while True:
- self.move(8) # 快速蝙蝠
- yield 200
- @as_main
- def main1(self):
- while True:
- yield 3000
- # 添加蝙蝠
- self.clone()
- @handle_sprite_collision("FireBall")
- @handle_sprite_collision("Wall")
- def die(self, other):
- self.delete_self()
- @handle_sprite_collision("Witch")
- def hit_witch(self, other):
- self.delete_self()
- class Bat2(Sprite):
- def __init__(self):
- super().__init__()
- self.name = "Bat2"
- self.add_costume("costume1",
- pygame.image.load("bat2-b.svg").convert_alpha())
- self.add_costume("costume2",
- pygame.image.load("bat2-a.svg").convert_alpha())
- self.visible = False
- self.set_size(0.5)
- @as_clones
- def clones1(self):
- self.pos = pygame.Vector2(400, 300)
- self.face_random_direction()
- self.move(400)
- self.face_towards("Witch")
- self.visible = True
- while True:
- self.next_costume()
- yield 300
- @as_clones
- def clones2(self):
- while True:
- self.move(5)
- yield 200
- @as_main
- def main1(self):
- while True:
- yield 3000
- # 添加蝙蝠
- self.clone()
- @handle_sprite_collision("Witch")
- def hit_witch(self, other):
- self.delete_self()
-
- @handle_sprite_collision("FireBall")
- @handle_sprite_collision("Wall")
- def die(self, other):
- self.delete_self()
- class FireBall(Sprite):
- def __init__(self):
- super().__init__()
- self.name = "FireBall"
- self.add_costume("costume1",
- pygame.image.load("ball-a.svg").convert_alpha())
- self.visible = False
- self.set_size(0.2)
- @as_clones
- def clones1(self):
- self.visible = True
- while True:
- self.move(10)
- yield 100
- @handle_edge_collision()
- def finish(self):
- self.delete_self()
- class Wall(Sprite):
- def __init__(self):
- super().__init__()
- self.name = "Wall"
- self.add_costume("costume1",
- pygame.image.load("wall.png").convert_alpha())
- self.set_size(0.5)
- self.last_use = time.time() # 记录上一次使用
- self.visible = False
- @on_key(pygame.K_a, "pressed")
- def use_wall(self):
- if time.time() - self.last_use >= 10: # 最多10秒用一次屏障
- self.visible = True
- yield 3000 # 屏障显示3秒
- self.visible = False
- self.last_use = time.time()
-
- class Gameover(Sprite):
- def __init__(self):
- super().__init__()
- self.add_costume("costume1",
- pygame.image.load("gameover.png").convert_alpha())
- self.visible = False
- @handle_broadcast("gameover")
- def gameover(self):
- self.visible = True
- class Witch(Sprite):
- def __init__(self):
- super().__init__()
- self.name = "Witch"
- self.add_costume("costume1",
- pygame.image.load("witch.svg").convert_alpha())
- self.fireball = FireBall()
- self.set_size(0.7)
- @on_key(pygame.K_RIGHT, "held")
- def right_held(self):
- self.turn_right(2)
- @on_key(pygame.K_LEFT, "held")
- def left_held(self):
- self.turn_left(2)
- @on_key(pygame.K_SPACE, "pressed")
- def space_pressed(self):
- self.fireball.direction = self.direction
- self.clone(self.fireball)
- @handle_sprite_collision("Bat1")
- @handle_sprite_collision("Bat2")
- def die(self):
- self.broadcast("gameover")
- # 定义场景
- class MyScene(Scene):
- def __init__(self):
- super().__init__()
- bat1 = Bat1()
- self.add_sprite(bat1)
- bat2 = Bat2()
- self.add_sprite(bat2)
- witch = Witch()
- self.add_sprite(witch)
- wall = Wall()
- self.add_sprite(wall)
- gameover = Gameover()
- self.add_sprite(gameover)
- # 运行游戏
- game.set_scene(MyScene())
- game.run(fps=60)
复制代码
2. Scrawl版的Flappybird:
完整源码:github.com/streetartist/scrawl_demo_flappybird
视频:s3plus.meituan.net/opapisdk/op_ticket_885190757_1756014484001_qdqqd_5eh880.mp4
主程序:- import pygame
- from pygame import K_SPACE
- import random
- from scrawl import Game, Scene, PhysicsSprite, Sprite, on_key, as_main, on_mouse_event, handle_edge_collision, handle_sprite_collision, CloudVariablesClient
- import time
- # 游戏常量
- SCREEN_WIDTH = 288
- SCREEN_HEIGHT = 512
- GRAVITY = 0.5
- FLAP_FORCE = -7
- PIPE_SPEED = 2.5
- PIPE_GAP = 120
- PIPE_FREQUENCY = 1500 # 每1500ms生成一个新管道
- client = CloudVariablesClient(project_id="0a668321-a7f0-4663-8d68-27c515142875")
- class Bird(PhysicsSprite):
- def __init__(self):
- super().__init__()
- self.name = "小鸟"
- self.set_collision_type("mask")
-
- # 创建小鸟图像
- bird_width = 24
- bird_height = 17
- surface = pygame.Surface((bird_width, bird_height), pygame.SRCALPHA)
- pygame.draw.ellipse(surface, (255, 200, 0), (0, 0, bird_width, bird_height)) # 鸟身体
- pygame.draw.circle(surface, (255, 100, 0), (bird_width - 9, 6), 3) # 鸟头
- pygame.draw.polygon(surface, (255, 0, 0), [
- (bird_width - 6, 6),
- (bird_width + 4, 6),
- (bird_width - 1, 8)
- ]) # 鸟嘴
- pygame.draw.ellipse(surface, (0, 0, 0), (bird_width - 12, 4, 3, 3)) # 眼睛
-
- self.add_costume("default", surface)
- self.pos.x = SCREEN_WIDTH // 3 # 初始位置在屏幕1/3处
- self.pos.y = SCREEN_HEIGHT // 2
-
- # 设置物理属性
- self.set_gravity(0, GRAVITY)
- self.set_elasticity(0.2)
- self.set_friction(0.99)
-
- # 游戏状态
- self.game_over = False
- self.score = 0
-
- @on_key(K_SPACE, "pressed")
- @on_mouse_event(button=1, mode="pressed")
- def flap(self):
- """按下空格键或鼠标使小鸟上升"""
- if not self.game_over:
- self.velocity.y = FLAP_FORCE
- self.play_sound("flap") # 播放拍翅膀音效
-
- @handle_edge_collision("top")
- def hit_top(self):
- """碰到上边界"""
- if not self.game_over:
- self.velocity.y = 0
- self.pos.y = 10
-
- @handle_edge_collision("bottom")
- @handle_sprite_collision("管道")
- def hit_bottom(self):
- """碰到下边界或管道 - 游戏结束"""
- if not self.game_over:
- self.game.log_debug("!!!!!")
- self.game_over = True
- self.say("Ouch!", 3000)
- self.play_sound("hit") # 播放撞击音效
-
- # 从云变量中获取当前最高分,如果不存在则默认为0
- current_highest = client.get_variable("highest_score", 0)
-
- # 如果当前分数高于最高分,则更新云变量
- if self.score > current_highest:
- client.set_variable("highest_score", self.score)
-
- @as_main
- def bird_physics(self):
- """处理小鸟物理状态"""
- while True:
- # 旋转小鸟基于下落速度
- self.direction = max(-30, min(self.velocity.y * 2, 90))
- yield 0
- class Pipe(Sprite):
- def __init__(self, x, gap_y):
- super().__init__()
-
- self.name = "管道"
- self.set_collision_type("mask")
-
- # 随机管道高度
- top_height = gap_y - PIPE_GAP // 2
- bottom_height = SCREEN_HEIGHT - (gap_y + PIPE_GAP // 2)
-
- # 管道宽度
- pipe_width = 52 * (SCREEN_WIDTH / 400)
-
- # 直接在一个surface上绘制管道
- surface = pygame.Surface((pipe_width, SCREEN_HEIGHT), pygame.SRCALPHA)
-
- # 绘制上管道(从顶部向下绘制)
- pygame.draw.rect(surface, (0, 180, 0), (0, 0, pipe_width, top_height))
- # 上管道顶部装饰
- pygame.draw.rect(surface, (0, 140, 0), (0, top_height - 15, pipe_width, 15))
-
- # 绘制下管道(从间隙底部开始绘制)
- bottom_pipe_y = gap_y + PIPE_GAP // 2
- pygame.draw.rect(surface, (0, 180, 0), (0, bottom_pipe_y, pipe_width, bottom_height))
- # 下管道顶部装饰
- pygame.draw.rect(surface, (0, 140, 0), (0, bottom_pipe_y, pipe_width, 15))
-
- self.add_costume("default", surface)
- self.collision_mask = pygame.mask.from_surface(self.image)
- self.pos.x = x
- self.pos.y = SCREEN_HEIGHT // 2
-
- # 管道属性
- self.passed = False # 小鸟是否已通过该管道
-
- @as_main
- def move_pipe(self):
- """移动管道"""
- while True:
- if not self.scene.bird.game_over:
- self.pos.x -= PIPE_SPEED
-
- # 检测小鸟是否通过管道
- if not self.passed and self.pos.x < self.scene.bird.pos.x:
- self.passed = True
- self.scene.bird.score += 1
- self.play_sound("point") # 播放得分音效
-
- # 管道移出屏幕后删除
- if self.pos.x < -100:
- self.delete_self()
-
- yield 0
- class Ground(Sprite):
- def __init__(self):
- super().__init__()
- self.name = "地面"
-
- # 创建地面图像
- ground_height = 40
- surface = pygame.Surface((SCREEN_WIDTH, ground_height))
- surface.fill((222, 184, 135)) # 浅棕色地面
-
- # 添加草地纹理
- pygame.draw.rect(surface, (0, 180, 0), (0, 0, SCREEN_WIDTH, 8))
- for i in range(0, SCREEN_WIDTH, 15):
- pygame.draw.line(surface, (0, 140, 0), (i, 8), (i+8, 8), 2)
-
- self.add_costume("default", surface)
- self.pos.x = SCREEN_WIDTH // 2
- self.pos.y = SCREEN_HEIGHT - ground_height // 2
- class ScoreSprite(Sprite):
- def __init__(self, bird):
- super().__init__()
- self.name = "得分显示"
- self.bird = bird # 引用小鸟对象以获取分数
-
- self.font = pygame.font.SysFont(None, 28)
-
- # 初始分数显示
- self.update_score()
-
- # 设置精灵位置 (居中靠上)
- self.pos.x = SCREEN_WIDTH // 2
- self.pos.y = 40
-
- def update_score(self):
- """更新分数显示"""
- # 渲染分数文本
- score_text = f"Score: {self.bird.score}"
- text_surface = self.font.render(score_text, True, (255, 255, 255))
-
- # 添加阴影效果
- shadow_surface = self.font.render(score_text, True, (0, 0, 0))
-
- # 创建最终表面(稍大一点以容纳阴影)
- self.surface = pygame.Surface((text_surface.get_width() + 4, text_surface.get_height() + 4), pygame.SRCALPHA)
-
- # 绘制阴影(偏移2像素)
- self.surface.blit(shadow_surface, (2, 2))
- # 绘制主文本
- self.surface.blit(text_surface, (0, 0))
-
- # 设置精灵造型
- self.add_costume("default", self.surface)
-
- @as_main
- def update_loop(self):
- """持续更新分数显示"""
- last_score = -1 # 初始值确保第一次会更新
- while True:
- # 当分数变化时更新显示
- if self.bird.score != last_score:
- self.update_score()
- last_score = self.bird.score
- yield 0
-
- # 最高分显示精灵
- class HighestScoreDisplay(Sprite):
- def __init__(self):
- super().__init__()
- self.name = "最高分显示"
-
- self.font = pygame.font.SysFont(None, 24) # 字体稍小
-
- self.highest_score = 0
- self.update_display()
-
- # 设置位置,在当前分数下方
- self.pos.x = SCREEN_WIDTH // 2
- self.pos.y = 70
-
- def update_display(self):
- """更新最高分显示"""
- self.highest_score = client.get_variable("highest_score", 0) # 从云端获取,默认为0
-
- score_text = f"Highest: {self.highest_score}"
- text_surface = self.font.render(score_text, True, (255, 255, 255))
- shadow_surface = self.font.render(score_text, True, (0, 0, 0))
-
- self.surface = pygame.Surface((text_surface.get_width() + 4, text_surface.get_height() + 4), pygame.SRCALPHA)
- self.surface.blit(shadow_surface, (2, 2))
- self.surface.blit(text_surface, (0, 0))
-
- self.add_costume("default", self.surface)
-
- @as_main
- def update_loop(self):
- """持续更新最高分显示"""
- last_highest_score = -1
- while True:
- current_highest = client.get_variable("highest_score", 0)
- if current_highest != last_highest_score:
- self.update_display()
- last_highest_score = current_highest
- # 每0.1秒更新一次最高分
- yield 100
- class FlappyScene(Scene):
- def __init__(self):
- super().__init__()
- self.name = "Flappy Bird"
- self.background_color = (135, 206, 235) # 天蓝色背景
-
- # 添加云朵装饰
- for _ in range(4):
- cloud = Sprite()
- cloud_size = random.randint(20, 50)
- surface = pygame.Surface((cloud_size*2, cloud_size), pygame.SRCALPHA)
- pygame.draw.ellipse(surface, (255, 255, 255), (0, 0, cloud_size*2, cloud_size))
- cloud.add_costume("default", surface)
- cloud.pos.x = random.randint(0, SCREEN_WIDTH)
- cloud.pos.y = random.randint(40, 150) # 降低云朵高度
- self.add_sprite(cloud)
-
- # 添加游戏元素
- self.bird = Bird()
- self.add_sprite(self.bird)
-
- self.ground = Ground()
- self.add_sprite(self.ground)
-
- # 添加得分显示精灵
- self.score_display = ScoreSprite(self.bird)
- self.add_sprite(self.score_display)
- # 添加最高分显示精灵
- self.highest_score_display = HighestScoreDisplay()
- self.add_sprite(self.highest_score_display)
-
- @as_main
- def generate_pipes(self):
- # 加载音效
- self.game.load_sound("flap", "sounds/flap.ogg")
- self.game.load_sound("hit", "sounds/hit.ogg")
- self.game.load_sound("point", "sounds/point.ogg")
-
- """定时生成新管道"""
- while True:
- if not self.bird.game_over:
- # 随机管道位置 (确保间隙在屏幕内)
- gap_y = random.randint(120, SCREEN_HEIGHT - 120)
- new_pipe = Pipe(SCREEN_WIDTH + 30, gap_y)
- self.add_sprite(new_pipe)
-
- yield PIPE_FREQUENCY # 等待一段时间后生成新管道
-
- time.sleep(2)
- # 创建并运行游戏
- game = Game(width=SCREEN_WIDTH, height=SCREEN_HEIGHT, title="Flappy Bird - Scrawl 复刻版", fullscreen=True)
- game.set_scene(FlappyScene())
- game.run(fps=60)
复制代码
|
|