اذهب إلى المحتوى

محاكاة أثر الجاذبية في لعبة بايثون


رشا سعد

عالمنا الحقيقي مليء بالحركة، والفيزياء هي ما يحكم ويفسر كافة ظواهره المتعلقة بحركة المادة، أما عالم ألعاب الفيديو فهو خالٍ من المادة ما يعني أنه منطقيًا خالٍ من الفيزياء أيضًا، لذا ينبغي على مطوري الألعاب محاكاة الفيزياء في ألعابهم لمنحها لمسة واقعية، وأكثر المبادئ الفيزيائية أهمية في عالم الألعاب هي الجاذبية والتصادم.

تعلمنا قليلًا عن التصادم عندما أضفنا شخصية العدو إلى اللعبة، لكننا سنتناوله بمزيدٍ من التعمق في هذا المقال والذي يليه فالجاذبية تفرض علينا ذلك، فما هي العلاقة بين الجاذبية والتصادم؟ ستجيبك عن ذلك تعليماتنا البرمجية اللاحقة.

الجاذبية في العالم الحقيقي كما نعرفها جميعًا من دروس الفيزياء هي ميل الأجسام ذات الكتلة إلى التجاذب المتبادل فيما بينها، وكلما زادت كتلة الجسم زاد تأثير الجاذبية التي يمارسها على غيره من الأجسام، فكيف سينعكس ذلك في عالم الألعاب؟

في الواقع الأثر الأوضح للجاذبية الذي ينبغي علينا محاكاته في الألعاب هو ميل الأجسام للسقوط تجاه الكائن الذي يملك افتراضيًا أكبر كتلة في عالم اللعبة، وهو عالم اللعبة نفسه.

قبل أن نبدأ نذكرك بمقالات السلسلة:

  1. بناء لعبة نرد بسيطة بلغة بايثون.
  2. بناء لعبة رسومية باستخدام بايثون ووحدة الألعاب PyGame.
  3. إضافة لاعب إلى اللعبة المطورة باستخدام بايثون و Pygame.
  4. تحريك شخصية اللعبة باستخدام PyGame.
  5. إضافة شخصية العدو للعبة.
  6. إضافة المنصات إلى لعبة بايثون باستخدام الوحدة Pygame.
  7. محاكاة أثر الجاذبية في لعبة بايثون.
  8. إضافة خاصية القفز والركض إلى لعبة بايثون.
  9. إضافة الجوائز إلى اللعبة المطورة بلغة بايثون.
  10. تسجيل نتائج اللعبة المطورة بلغة بايثون وعرضها على الشاشة.
  11. إضافة آليات القذف إلى اللعبة المطورة بلغة بايثون.

إضافة دالة الجاذبية

سنستفيد من خاصية الحركة التي يملكها كائن اللاعب حتى نسحبه أو نحركه باتجاه أسفل شاشة العرض، بما يحاكي أثر الجاذبية الأرضية، وتذكر أن صفر المحور العمودي في Pygame يقع في الأعلى ما يعني أن الإحداثيات الأكبر هي أكثر قربًا لأسفل الشاشة.

الجاذبية انتقائية في ألعاب المنصات فالمبرمج يختار أثرها ولن تؤثر على كل شيء كما في العالم الحقيقي، فلو فعلت ذلك لرأيت منصات اللعبة بأكملها تسقط إلى الأرضية، لذا سنطبق أثر الجاذبية على شخصيتي اللاعب والعدو فقط.

أضف أولاً دالة الجاذبية في صنف اللاعب:

    def gravity(self):
        self.movey += 3.2 # معدل سرعة سقوط اللاعب

الدالة بسيطة فقد ضبطنا حركة اللاعب العمودية لتكون باتجاه أرضية اللعبة دائمًا بغض النظر إن كان أساسًا بحالة حركة أو سكون، أي أنه سيسقط دائمًا وهذا بالفعل معنى الجاذبية الأرضية، واستخدمنا لذلك خاصية الحركة العمودية التي يمتلكها اللاعب.

اكتب دالة الجاذبية في الحلقة الرئيسية ليصبح أثرها دائمًا كما في السطر الأول من التعليمات أدناه وبذلك سيُطبقها بايثون على اللاعب مع كل نبضة ساعة.

    player.gravity() # تحقق من الجاذبية
    player.update()

شغل لعبتك الآن وشاهد النتائج، سيسقط اللاعب سقوطًا حادًا وسريعًا من سماء عالم اللعبة إلى أسفل الشاشة، ربما ترغب بتغيير مقدار سرعة السقوط لتحظى بنتيجة أفضل، لكن أثر الجاذبية يعمل وهذا يكفي في هذه المرحلة.

تعيين أرضية الجاذبية ليقف عندها اللاعب بعد السقوط

دائمًا ما يلي السقوط إجراءٌ معين في ألعاب الفيديو، قد يكون مسح اللاعب عن شاشة اللعبة وإعادة إنتاجه في مكانٍ مختلف، أو خصم بعض النقاط من رصيده أو ما شابه، بالنتيجة أيًا كان الإجراء الذي تريده فينبغي أولًا إيجاد طريقة تجعل البرنامج يكتشف حادثة السقوط ويقرر إن سقط اللاعب واختفى عن الشاشة، أم أن ذلك لم يحدث، ويُتخذ بعدها الإجراء المطلوب، أما أنسب تعليمة لهذا النوع من الكشف فهي تعليمة if الشرطية.

ستتحقق إذًا فيما إذا سقط لاعبك أم لا، وماهو مدى سقوطه، فلو سقط بعيدًا جدًا متجاوزًا الأرضية إلى أخفض نقطة في الشاشة فالوضع غير مقبول ويحتاج لبعض التعديلات فمنطيقًا كان عليه الاستقرار على أرضية اللعبة.

لنبسط الموضوع اضبط موقع اللاعب عند 20 بكسل، وعدّل دالة الجاذبية لتصبح:

    def gravity(self):
        self.movey += 3.2 # معدل سرعة السقوط

        if self.rect.y > worldy and self.movey >= 0:
            self.movey = 0
            self.rect.y = worldy-ty

ومن ثم شغل اللعبة ولاحظ أن اللاعب يسقط من الأعلى ويتوقف عند أسفل الشاشة، حتى أنك قد لا تتمكن من رؤيته خلف كائن الأرضية، سنعالج الموضوع بإضافة ty- ثانية إلى موقعه الجديد تجعله يرتد أعلى بعد اصطدامه بأسفل الشاشة فيظهر على مسافة معينة ضمن الشاشة، وذلك وفق الأوامر التالية:

    def gravity(self):
        self.movey += 3.2 # how fast player falls

        if self.rect.y > worldy and self.movey >= 0:
            self.movey = 0
            self.rect.y = worldy-ty-ty

يحتاج بطل اللعبة إلى طريقة أو قوة دفع تساعده على مقاومة أثر الجاذبية ليجتاز المستويات ويتابع اللعب هذه القوة هي القفز سيناقشها المقال التالي مع توسع إضافي في تصادمه مع الأرضية والمنصات المختلفة.

حاول بنفسك تطبيق أثر الجاذبية على كائن شخصية العدو، وانظر الشكل النهائي لكود اللعبة متضمنًا كل فعلناه في السلسلة حتى الآن:

#!/usr/bin/env python3
# by Seth Kenlon

# GPLv3
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import pygame
import sys
import os

'''
Variables
'''

worldx = 960
worldy = 720
fps = 40
ani = 4
world = pygame.display.set_mode([worldx, worldy])

BLUE = (25, 25, 200)
BLACK = (23, 23, 23)
WHITE = (254, 254, 254)
ALPHA = (0, 255, 0)

'''
Objects
'''

# x location, y location, img width, img height, img file
class Platform(pygame.sprite.Sprite):
    def __init__(self, xloc, yloc, imgw, imgh, img):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load(os.path.join('images', img)).convert()
        self.image.convert_alpha()
        self.image.set_colorkey(ALPHA)
        self.rect = self.image.get_rect()
        self.rect.y = yloc
        self.rect.x = xloc

class Player(pygame.sprite.Sprite):
    """
    Spawn a player
    """

    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.movex = 0
        self.movey = 0
        self.frame = 0
        self.health = 10
        self.images = []
        for i in range(1, 5):
            img = pygame.image.load(os.path.join('images', 'hero' + str(i) + '.png')).convert()
            img.convert_alpha()
            img.set_colorkey(ALPHA)
            self.images.append(img)
            self.image = self.images[0]
            self.rect = self.image.get_rect()

    def gravity(self):
        self.movey += 3.2
        if self.rect.y > worldy and self.movey >= 0:
            self.movey = 0
            self.rect.y = worldy-ty-ty

    def control(self, x, y):
        """
        control player movement
        """
        self.movex += x
        self.movey += y

    def update(self):
        """
        Update sprite position
        """

        self.rect.x = self.rect.x + self.movex
        self.rect.y = self.rect.y + self.movey

        # moving left
        if self.movex < 0:
            self.frame += 1
            if self.frame > 3 * ani:
                self.frame = 0
            self.image = pygame.transform.flip(self.images[self.frame // ani], True, False)

        # moving right
        if self.movex > 0:
            self.frame += 1
            if self.frame > 3 * ani:
                self.frame = 0
            self.image = self.images[self.frame // ani]

        hit_list = pygame.sprite.spritecollide(self, enemy_list, False)
        for enemy in hit_list:
            self.health -= 1
            print(self.health)


class Enemy(pygame.sprite.Sprite):
    """
    Spawn an enemy
    """

    def __init__(self, x, y, img):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load(os.path.join('images', img))
        self.image.convert_alpha()
        self.image.set_colorkey(ALPHA)
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y
        self.counter = 0

    def move(self):
        """
        enemy movement
        """
        distance = 80
        speed = 8

        if self.counter >= 0 and self.counter <= distance:
            self.rect.x += speed
        elif self.counter >= distance and self.counter <= distance * 2:
            self.rect.x -= speed
        else:
            self.counter = 0

        self.counter += 1


class Level:
    def ground(lvl, gloc, tx, ty):
        ground_list = pygame.sprite.Group()
        i = 0
        if lvl == 1:
            while i < len(gloc):
                ground = Platform(gloc[i], worldy - ty, tx, ty, 'tile-ground.png')
                ground_list.add(ground)
                i = i + 1

        if lvl == 2:
            print("Level " + str(lvl))

        return ground_list

    def bad(lvl, eloc):
        if lvl == 1:
            enemy = Enemy(eloc[0], eloc[1], 'enemy.png')
            enemy_list = pygame.sprite.Group()
            enemy_list.add(enemy)
        if lvl == 2:
            print("Level " + str(lvl))

        return enemy_list

    # x location, y location, img width, img height, img file
    def platform(lvl, tx, ty):
        plat_list = pygame.sprite.Group()
        ploc = []
        i = 0
        if lvl == 1:
            ploc.append((200, worldy - ty - 128, 3))
            ploc.append((300, worldy - ty - 256, 3))
            ploc.append((500, worldy - ty - 128, 4))
            while i < len(ploc):
                j = 0
                while j <= ploc[i][2]:
                    plat = Platform((ploc[i][0] + (j * tx)), ploc[i][1], tx, ty, 'tile.png')
                    plat_list.add(plat)
                    j = j + 1
                print('run' + str(i) + str(ploc[i]))
                i = i + 1

        if lvl == 2:
            print("Level " + str(lvl))

        return plat_list


'''
Setup
'''

backdrop = pygame.image.load(os.path.join('images', 'stage.png'))
clock = pygame.time.Clock()
pygame.init()
backdropbox = world.get_rect()
main = True

player = Player()  # spawn player
player.rect.x = 0  # go to x
player.rect.y = 30  # go to y
player_list = pygame.sprite.Group()
player_list.add(player)
steps = 10

eloc = []
eloc = [300, 0]
enemy_list = Level.bad(1, eloc)

gloc = []
tx = 64
ty = 64

i = 0
while i <= (worldx / tx) + tx:
    gloc.append(i * tx)
    i = i + 1

ground_list = Level.ground(1, gloc, tx, ty)
plat_list = Level.platform(1, tx, ty)

'''
Main Loop
'''

while main:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            try:
                sys.exit()
            finally:
                main = False

        if event.type == pygame.KEYDOWN:
            if event.key == ord('q'):
                pygame.quit()
                try:
                    sys.exit()
                finally:
                    main = False
            if event.key == pygame.K_LEFT or event.key == ord('a'):
                player.control(-steps, 0)
            if event.key == pygame.K_RIGHT or event.key == ord('d'):
                player.control(steps, 0)
            if event.key == pygame.K_UP or event.key == ord('w'):
                print('jump')

        if event.type == pygame.KEYUP:
            if event.key == pygame.K_LEFT or event.key == ord('a'):
                player.control(steps, 0)
            if event.key == pygame.K_RIGHT or event.key == ord('d'):
                player.control(-steps, 0)

    world.blit(backdrop, backdropbox)
    player.gravity()
    player.update()
    player_list.draw(world)
    enemy_list.draw(world)
    ground_list.draw(world)
    plat_list.draw(world)
    for e in enemy_list:
        e.move()
    pygame.display.flip()
    clock.tick(fps)

كان هذا المقال السابع من سلسلة بناء لعبة من الصفر باستخدام بايثون 3 و Pygame.

ترجمة -وبتصرف- للمقال Simulate gravity in your Python game لصاحبيه Seth Kenlon و Jess Weichler.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...