繁体   English   中英

基于文本的游戏中的Python计数器

[英]Python Counter in Text-Based Game

我正在使用对象制作基于文本的Farmville克隆,但我需要能够控制增长率。 我需要某种计数器,该计数器将在程序的后台运行并确定农作物的生长方式。 例如:

class Grow(object):
    def growth(self, crop):
        self.grown = 0
        while self.grown < 5:
            <every x number of seconds add one to self.grown>

我需要类似time.sleep()的东西,但是并不能阻止程序运行。 谢谢= D

如果您只需要知道自上次检查以来作物的生长量,则可以将其内置到Crop对象中:

from datetime import datetime

class Crop:

    RATE = 1 # rate of growth, units per second

    def __init__(self, ..., grown=0): # allow starting growth to be set
        ...
        self.last_update = datetime.now()
        self.grown = grown

    def grow(self):
        """Set current growth based on time since last update."""
        now = datetime.now()
        self.grown += Crop.RATE * (now - self.last_update).seconds
        self.last_update = now

另外,您可以在单独的Growable类中定义此功能, Growable所有增长的对象(例如CropAnimal )从该超类继承grow方法。

class Growable:

    def __init__(self, grown=0):
        self.last_update = datetime.now()
        self.grown = grown

    def grow(self, rate):
        """Set current growth based on time since last update and rate."""
        now = datetime.now()
        self.grown += rate * (now - self.last_update).seconds
        self.last_update = now

class Crop(Growable):

    RATE = 1

    def __init__(self, ..., grown=0):
        super().__init__(grown)
        ...

    def grow(self):
        super().grow(Crop.RATE)

有多种方法可以执行此操作,这取决于您要如何构建应用程序。 每个游戏基本上都在运行某种循环。 问题是您使用的是哪种循环。


对于简单的“控制台模式”游戏,该循环只是围绕input()的循环。当您在等待用户键入其输入时,其他任何事情都不会发生。 这就是您要解决的问题。


解决这个问题的一种方法是伪造它。 在等待用户输入时,您可能无法运行任何代码,但是您可以弄清您要运行的所有代码,并可以完成相同的操作。 如果该作物每1.0秒增长5倍,并且自种植以来已经是3.7秒,那么现在已经增长了3倍。 jonrsharpe的答案显示了一种很好的结构方式。


同样的想法也适用于由帧率循环驱动的图形游戏,就像传统的街机游戏一样,但更为简单。 每帧,您都要检查输入,更新所有对象,执行任何输出,然后休眠直到下一个帧。 由于帧以固定速率发送,因此您可以执行以下操作:

def grow(self, rate):
    self.grown += rate / FRAMES_PER_SECOND

另一种解决方案是使用后台线程。 尽管您的线程在等待用户输入时无法运行任何代码,但其他线程仍在运行。 因此,您可以为作物裁剪背景线程 您可以使用原始的growth方法以及self.growth(crop) time.sleep(1.0)和其他所有方法,但是可以调用threading.Thread(target=self.growth, args=[crop]).start()而不是调用self.growth(crop) threading.Thread(target=self.growth, args=[crop]).start() 那差不多就变得简单了,但是这种简单性是有代价的。 如果每个80x25 = 2000块土地都有一个线程,则将在调度程序中使用所有CPU时间,并在线程堆栈中使用所有内存。 因此,仅当您只有几十个独立活动的对象时,此选项才有效。 线程的另一个问题是,您必须同步在多个线程上使用的任何对象,否则最终会出现争用条件,并且可能很难解决。


解决“线程过多”问题(而不是同步问题)的方法是使用Timer 内置在stdlib中的那个并不是真正可用的(因为它为每个计时器创建了一个线程),但是您可以找到像timer2这样的第三方实现。 因此,与其先休眠一秒钟然后再处理其余的代码,不如将其余的代码移到一个函数中,并创建一个Timer,在一秒钟后调用该函数:

def growth(self, crop):
    self.grown = 0
    def grow_callback():
        with self.lock:
            if self.grown < 5:
                self.grown += 1
                Timer(1.0, grow_callback).start()
    Timer(1.0, grow_callback).start()

现在您可以正常调用self.growth(crop) 但是请注意,必须将睡眠后的所有内容(在循环的中间)移动到一个单独的函数中,从而使控制流由内而外翻转。


最后,您可以使用完整的事件循环,而不是围绕输入的循环或进入下一帧之前的睡眠,而要使用完整的事件循环:等到发生某种事情,其中​​“某些事情”可以是用户输入,计时器到期或其他任何事情。 这就是大多数GUI应用程序和网络服务器的工作方式,并且在许多游戏中也使用了它。 在事件循环程序中计划计时器事件就像在安排线程计时器一样,但是没有锁。 例如,使用Tkinter,它看起来像这样:

def growth(self, crop):
    self.grown = 0
    def grow_callback():
        if self.grown < 5:
            self.grown += 1
            self.after(1000, function=grow_callback)
    self.after(1000, function=grow_callback)

最后一种选择是将程序分为两部分:引擎和界面。 将它们放在通过队列 (或管道或套接字)进行通信的两个单独的线程(或子进程,甚至是完全独立的程序)中,然后可以用最自然的方式编写每个线程。 这也意味着您可以在不重写引擎中任何逻辑的情况下,用Tkinter GUI,pygame全屏图形界面甚至Web应用程序替换该界面。

特别是,您可以将接口编写为围绕input的循环,该接口仅检查输入队列中等待时发生的任何更改,然后将所有命令发布到引擎的输出队列中。 然后,将引擎编写为事件循环,将输入队列中的新命令视为事件,或者将帧速率循环(每帧检查队列)或其他最有意义的帧编写为引擎。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM