草庐IT

【编程教室】PONG - 100行代码写一个弹球游戏

Crossin的编程教室 2023-03-28 原文

大家好,欢迎来到 Crossin的编程教室 !

今天跟大家讲一讲:如何做游戏

游戏的主题是弹球游戏《PONG》,它是史上第一款街机游戏。因此选它作为我这个游戏开发系列的第一期主题。

游戏引擎用的是 Python 的一个游戏库:pgzero。它是对 pygame 的一个封装,让你不需要写多余的套路代码,只要配置游戏的内容逻辑即可。

我们这个游戏用它来写,一共只需要100行代码。

首先需要安装 python 环境。这一步没搞定的同学,可以参考我们 python 入门教程:http://python666.cn ,上面有详细图文介绍。

然后需要安装 pgzero 库,可以命令行下通过 pip 命令安装:

pip install pgzero

安装完,运行一句

pgzrun.go()

我们的游戏世界之门就已经打开了。

现在上面还是混沌初开,一片漆黑。

设定一个矩形的左上角坐标和长宽,在游戏的绘制函数 draw 中用指定颜色填充,我们就得到了一个矩形。

pad_1 = Rect((20, 20), (10, 100))

def draw():
    screen.clear()
    screen.draw.filled_rect(pad_1, 'white')

适当调整一下,就得到了一块游戏中用来挡球的板。

在游戏的更新函数中增加判断,当键盘上的“上”、“下”按键被按下时,修改挡板的y坐标,就可以在游戏中控制挡板的移动了。

PAD_SPEED = 10

def update(dt):
    if keyboard.up:
        pad_1.y -= PAD_SPEED
    elif keyboard.down:
        pad_1.y += PAD_SPEED

这样就已经完成 PONG 游戏中的玩家操控角色:一块可上下移动的挡板。而现在我们用到的代码仅仅10行。

有的小伙伴可能注意到了,这里有两个函数,一个叫 draw,它是负责游戏中的画面绘制,另一个叫 update,它负责游戏中的逻辑更新。

我们经常听到说游戏运行时速度是每秒30帧、60帧之类,或者叫做 FPS(Frames Per Second)。draw 和 update 就是在游戏的“一帧”画面中所要做的事情。你的计算机或者游戏主机的性能越高,每一帧所花费的计算时间就越少,游戏帧数就可以更高,游戏体验也就更流畅。

创建一个叫做 Ball 的类型,属性值包括位置和速度。然后,在绘图函数中以小球的位置为圆心画一个圆,在更新函数中按照匀速直线运动位移公式,也就是 位移=速度x时间,计算出小球下一帧的位置。如此就实现了一个会运动的小球。

class Ball():
    def __init__(self):
        self.pos = [300, 200]
        self.speed = [1, 1]        
    def update(self, dt):
        for i in range(2):
            self.pos[i] += self.speed[i] * dt

ball = Ball()

def draw():
    screen.clear()
    screen.draw.filled_rect(pad_1, 'white')
    screen.draw.filled_circle(ball.pos, BALL_RADIUS, 'white')

再设置一下边界条件,让小球到达屏幕边缘时可以改变对应的速度方向,碰到上下边缘就将y速度分量乘以-1,超出左右边缘则位置重新设置回屏幕中心。

class Ball():
    ...      

    def update(self, dt):
        for i in range(2):
            self.pos[i] += self.speed[i]

        if self.pos[1] < 0 or self.pos[1] > HEIGHT:
            self.speed[1] *= -1
        if self.pos[0] < 0 or self.pos[0] > WIDTH:
            self.reset()

有了板,有了球,接下来就是让他们之间产生关联。

在更新函数中做一个碰撞检测:如果板子的矩形与球的圆心产生了交集,就让球反弹回去。

def update(dt):
    ...
    
    ball.update(dt)

    if pad_1.collidepoint(ball.pos) and ball.speed[0] < 0:
        ball.speed[0] *= -1

到这一步,游戏的核心物理规则就已经定义完毕。

按照同样的方法,在屏幕的右侧创建第二块板,通过另外的按键进行控制。然后,当小球超出左右边界时,分别给对面一方得分。

class Ball():
    ...      

    def dead(self, side):
        scores[side] += 1
        self.reset()

这样,一个最最简单的,双人版弹球游戏就完成了。

当然,如果你找不到另一个人陪你一起玩,也可以让自己的左手跟右手玩。

或者,给一侧板增加一点自动追踪的代码:让板的位置随着球的位置移动。这也算是一个游戏AI了。

def auto_move_pad(dt):
    if ball.pos[0] > WIDTH / 2 and ball.speed[0] > 0:
        if pad_2.y + pad_2.height * 0.25 > ball.pos[1]:
            pad_2.y -= PAD_SPEED * dt
            if pad_2.top < 0:
                pad_2.top = 0
        elif pad_2.y + pad_2.height * 0.75 < ball.pos[1]:
            pad_2.y += PAD_SPEED * dt
            if pad_2.bottom > HEIGHT:
                pad_2.bottom = HEIGHT

至此,一个具备完整核心玩法的弹球游戏 PONG 已经完成了。加上空格也不到100行代码。特别适合编程新手刚刚接触游戏开发的小伙伴进行练习。

不过,我还给游戏增加了一点点细节,感兴趣的小伙伴可点击视频进行观看。喜欢的话欢迎点赞和转发!

https://www.bilibili.com/video/BV1Pr4y1s7wk/

之后我还会来尝试更多的游戏类型,更多的玩法。争取完成最初立下的FLAG:实现100个游戏。如果你想看某类游戏或者某个游戏的实现,或者对某个实现细节有疑问,也可以留言中告诉我,我会优先考虑。

代码已经开源,可通过“Crossin的编程教室”获取

https://github.com/crossin/games100


获取更多教程和案例,

欢迎搜索及关注:Crossin的编程教室

每天5分钟,轻松学编程。

有关【编程教室】PONG - 100行代码写一个弹球游戏的更多相关文章

  1. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  2. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  3. ruby-on-rails - Rails 源代码 : initialize hash in a weird way? - 2

    在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has

  4. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  5. ruby-on-rails - 渲染另一个 Controller 的 View - 2

    我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>

  6. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

  7. ruby - 为什么 SecureRandom.uuid 创建一个唯一的字符串? - 2

    关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?

  8. ruby-on-rails - Rails - 从另一个模型中创建一个模型的实例 - 2

    我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案

  9. ruby-on-rails - 浏览 Ruby 源代码 - 2

    我的主要目标是能够完全理解我正在使用的库/gem。我尝试在Github上从头到尾阅读源代码,但这真的很难。我认为更有趣、更温和的踏脚石就是在使用时阅读每个库/gem方法的源代码。例如,我想知道RubyonRails中的redirect_to方法是如何工作的:如何查找redirect_to方法的源代码?我知道在pry中我可以执行类似show-methodmethod的操作,但我如何才能对Rails框架中的方法执行此操作?您对我如何更好地理解Gem及其API有什么建议吗?仅仅阅读源代码似乎真的很难,尤其是对于框架。谢谢! 最佳答案 Ru

  10. ruby - 模块嵌套代码风格偏好 - 2

    我的假设是moduleAmoduleBendend和moduleA::Bend是一样的。我能够从thisblog找到解决方案,thisSOthread和andthisSOthread.为什么以及什么时候应该更喜欢紧凑语法A::B而不是另一个,因为它显然有一个缺点?我有一种直觉,它可能与性能有关,因为在更多命名空间中查找常量需要更多计算。但是我无法通过对普通类进行基准测试来验证这一点。 最佳答案 这两种写作方法经常被混淆。首先要说的是,据我所知,没有可衡量的性能差异。(在下面的书面示例中不断查找)最明显的区别,可能也是最著名的,是你的

随机推荐