به راه انداختن ناوگان

اکنون کاری می‌کنیم که ناوگان به سمت راست صفحه حرکت کند تا به لبه‌ی آن برسد، سپس آن را به اندازه‌ی مشخّص‌شده‌ای پایین می‌بریم و دوباره در جهت مخالف حرکت می‌دهیم. این را تا وقوع یکی از موارد زیر ادامه می‌دهیم: تمام موجودات بیگانه هدف گلوله قرار بگیرند، یکی از آن‌ها به سفینه بخورد یا به پایین صفحه برسد. کار را با حرکت دادن ناوگان به سمت راست شروع می‌کنیم.

به راست راندن ناوگان

برای حرکت دادن موجودات بیگانه از یک متد update() در alien.py استفاده می‌کنیم که آن را برای هر کدام از موجودات داخل گروه فرامی‌خوانیم. ابتدا تنظیمات مربوط به کنترل سرعت هر موجود بیگانه را اضافه می‌کنیم:

1
2
3
4
def __init__(self):
    --snip--
    # Alien settings
    self.alien_speed = 1.0

سپس از این تنظیمات برای پیاده‌سازی update() بهره می‌گیریم:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def __init__(self, ai_game):
    """Initialize the alien and set its starting position."""
    super().__init__()
    self.screen = ai_game.screen
    self.settings = ai_game.settings
    --snip--

def update(self):
    """Move the alien to the right."""
    self.x += self.settings.alien_speed
    self.rect.x = self.x

در __init__() پارامتری برای تنظیمات ایجاد می‌کنیم تا در update() به سرعت موجود بیگانه دسترسی داشته باشیم. هر بار که موقعیّت موجود بیگانه را به‌روز می‌کنیم آن را به مقداری که در alien_speed ذخیره شده به سمت راست می‌بریم. موقعیّت دقیق هر موجود را از طریق مشخّصه‌ی self.x دنبال می‌کنیم که می‌تواند اعداد اعشاری را هم ذخیره کند (۱). سپس از مقدار self.x برای به‌روزرسانی موقعیّت rect هر موجود استفاده می‌کنیم (۲).

پیش از این در حلقه‌ی while اصلی دو فراخوانی برای به‌روزرسانی موقعیّت سفینه و گلوله داشتیم. اکنون به‌روزرسانی موقعیّت هر موجود بیگانه را هم اضافه می‌کنیم:

1
2
3
4
5
6
while True:
    self._check_events()
    self.ship.update()
    self._update_bullets()
    self._update_aliens()
    self._update_screen()

ما قصد داریم کدی برای مدیریّت حرکت ناوگان بنویسیم بنابراین متد جدیدی به نام _update_aliens() می‌سازیم. ما موقعیّت موجود بیگانه را پس از به‌روزرسانی گلوله‌ها به‌روز می‌کنیم زیرا به زودی برخورد گلوله به هر یک از موجودات بیگانه را هم بررسی خواهیم کرد.

محل قرارگیری این متد در ماژول خیلی مهم نیست. ولی من برای حفظ ساختار آن را درست بعد از _update_bullets قرار می‌دهم تا به ترتیب فراخوانی متدها در حلقه‌ی while بخورد. این اوّلین نسخه‌ی _update_aliens() است:

1
2
3
def _update_aliens(self):
    """Update the positions of all aliens in the fleet."""
    self.aliens.update()

متد update() را بر روی گروه aliens اعمال می‌کنیم که متد update() هر موجود بیگانه را فراخوانی می‌کند. اگر اکنون هجوم بیگانگان را اجرا کنید باید ناوگان را در حالت حرکت به سمت راست ببینید تا جایی که از صفحه محو شوند.

ایجاد تنظیمات برای جهت حرکت ناوگان

حال تنظیماتی را ایجاد می‌کنیم که هنگام برخورد ناوگان به ضلع سمت راست صفحه، موجب حرکت آن به سمت پایین و سپس چپ صفحه می‌شود. اینجا چگونگی پیاده‌سازی این رفتار را می‌بینیم:

1
2
3
4
5
# Alien settings
self.alien_speed = 1.0
self.fleet_drop_speed = 10
# fleet_direction of 1 represents right; -1 represents left.
self.fleet_direction = 1

متغیّر fleet_drop_speed سرعت پایین افتادن هر موجود را پس از رسیدن به هر دو سمت صفحه را کنترل می‌کند. جدا کردن این سرعت از سرعت افقی موجودات بیگانه کمک می‌کند که بتوانیم این دو را جداگانه تنظیم کنیم.

برای پیاده‌سازی fleet_direction می‌توانیم از مقدار متنی مانند 'left' یا 'right' استفاده کنیم امّا در نهایت باید از عبارت‌های if-elif برای آزمودن جهت حرکت ناوگان استفاده کنیم. پس به جای این و چون تنها دو جهت برای حرکت داریم، از مقدارهای 1 و -1 برای تعیین جهت استفاده می‌کنیم و هر بار خواستیم جهت ناوگان را تغییر دهیم یکی از این دو عدد را انتخاب می‌کنیم. (به‌کارگیری اعداد در اینجا معنادار هم هست زیرا حرکت به سمت راست متناظر با افزوده شدن به مقدار مختصه‌ی x هر موجود و حرکت به چپ متناظر با کم کردن این مقدار است.)

بررسی برخورد یک موجود بیگانه با کناره‌های صفحه

به متدی احتیاج داریم که مشخّص کند آیا موجود بیگانه‌ای به کناره‌های صفحه برخورد کرده است یا نه. همچنین باید update() را طوری تغییر دهیم که امکان حرکت در جهت درست را فراهم کند. این کد بخشی از کلاس Alien است:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def check_edges(self):
    """Return True if alien is at edge of screen."""
    screen_rect = self.screen.get_rect()
    if self.rect.right >= screen_rect.right or self.rect.left <= 0:
        return True

def update(self):
    """Move the alien right or left."""
    self.x += (self.settings.alien_speed *
                self.settings.fleet_direction)
    self.rect.x = self.x

برای بررسی این که هر کدام از موجودات بیگانه به سمت چپ یا راست صفحه رسیده‌اند یا نه می‌توانیم متد check_edges() را صدا کنیم. اگر مشخّصه‌ی right مربوط به rect بزرگتر یا مساوی مشخّصه‌ی right مربوط به rect صفحه باشد، موجود بیگانه در ضلع راست صفحه قرار دارد. اگر مقدار left کمتر یا مساوی صفر باشد موجود بیگانه در ضلع چپ صفحه قرار دارد (۱).

برای ممکن ساختن حرکت به چپ یا راست متد update() را تغییر می‌دهیم و سرعت موجود بیگانه را در مقدار fleet_direction ضرب می‌کنیم (۲). اگر fleet_direction برابر با 1 باشد مقدار alien_speed به مقدار فعلی موقعیّت اضافه می‌شود و موجود بیگانه به راست می‌رود؛ اگر fleet_direction برابر -1 باشد این مقدار از موقعّیت فعلی کسر خواهد شد که موجب حرکت به سمت چپ می‌شود.

پایین آوردن ناوگان و تغییر جهت آن

زمانی که یک موجود بیگانه به کناره‌ی صفحه می‌رسد کل ناوگان باید به سمت پایین بیاید و جهت حرکت خود را تغییر دهد. بنابراین باید چند خط کد به AlienInvasion اضافه کنیم زیرا در آن جا باید بررسی کنیم که آیا هیچ موجود بیگانه‌ای به ضلع چپ یا راست صفحه رسیده است یا نه. این کار را با نوشتن دو متد _check_fleet_edges() و _change_fleet_direction() و تغییر دادن _update_aliens() انجام می‌دهیم. من متدهای جدید را پس از _create_alien() قرار می‌دهم امّا دوباره تاکید می‌کنم که محل قرارگیری متدها در کلاس مهم نیست.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def _check_fleet_edges(self):
    """Respond appropriately if any aliens have reached an edge."""
    for alien in self.aliens.sprites():
        if alien.check_edges():
            self._change_fleet_direction()
            break

def _change_fleet_direction(self):
    """Drop the entire fleet and change the fleet's direction."""
    for alien in self.aliens.sprites():
        alien.rect.y += self.settings.fleet_drop_speed
    self.settings.fleet_direction *= -1

در متد _check_fleet_edges() در ناوگان حلقه می‌زنیم و check_edges() را برای هر موجود بیگانه صدا می‌کنیم (۱). اگر check_edges() درست (True) برگرداند می‌فهمیم که موجود بیگانه به کناره‌ی صفحه رسیده است و کل ناوگان باید تغییر جهت انجام دهد؛ بنابراین متد _change_fleet_direction() را فراخوانی کرده و حلقه را می‌شکنیم (۲). از _change_fleet_direction() بین تمام موجودات بیگانه حلقه می‌زنیم و هر کدام را به کمک تنظیمات fleet_drop_speed پایین می‌آوریم (۳)؛ سپس مقدار fleet_direction را با ضرب کردن مقدار آن در -1 تغییر می‌دهیم. خطّی که در آن جهت حرکت تغییر می‌کند جزء حلقه‌ی for نیست. ما می‌خواهیم موقعیّت تمامِ موجودات بیگانه را تغییر دهیم امّا جهت حرکت کل ناوگان را یک بار تغییر می‌دهیم.

این تغییراتی است که در _update_aliens() ایجاد می‌کنیم:

1
2
3
4
5
6
7
def _update_aliens(self):
    """
    Check if the fleet is at an edge,
      then update the positions of all aliens in the fleet.
    """
    self._check_fleet_edges()
    self.aliens.update()

تغییری که ایجاد کرده‌ایم فراخوانی _check_fleet_edges() پیش از به‌روزرسانی موقعیّت هر موجود بیگانه است.

اگر اکنون بازی را اجرا کنید ناوگان باید بین دو کناره‌ی صفحه حرکت کند و با رسیدن به هر طرف به سمت پایین بیاید. حال می‌توانیم شروع به منهدم کردن موجودات بیگانه کنیم و بررسی کنیم که آیا هیچ کدام به سفینه خورده‌ یا به پایین صفحه رسیده‌اند یا نه.

خودآزمایی

۱۳-۳ قطره‌های باران: تصویر یک قطره‌ی باران را پیدا کنید و شبکه‌ای از قطره‌های باران بسازید. کاری کنید که قطره‌ها به سمت پایین صفحه حرکت کنند تا جایی که ناپدید شوند.

۱۳-۴ باران مداوم: کد خود در تمرین ۱۳-۳ را جوری تغییر دهید که با ناپدید شدن هر ردیف قطره‌های باران در پایین صفحه، یک ردیف جدید در بالای آن اضافه شود و شروع به حرکت کند.