[英]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
所有增长的对象(例如Crop
, Animal
)从该超类继承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.