القائمة الرئيسية

الصفحات

كيفية برمجة لعبة Endless Runner باستخدام Arduino IDE

How to Code Endless Runner in Arduino IDE، Arduino IDE، Endless Runner، برمجة ألعاب، ألعاب مدمجة، متحكم دقيق، شاشة OLED، زر ضغط، إلكترونيات، الجاذبية، القفز، العقبات، اصطدام، نقاط، Arduino، SSD1306، GFX Library، Input Control، Game Physics، برمجة لعبة Endless Runner باستخدام Arduino IDE، تطوير الألعاب المصغرة على الأردوينو، بناء لعبة سباق بسيطة بالاردوينو، كيفية برمجة لعبة ركض لانهائي على الأردوينو، توصيل شاشة OLED مع Arduino لبرمجة الألعاب، توليد العقبات العشوائية في ألعاب الأردوينو، اكتشاف الاصطدام في الألعاب على المتحكمات الدقيقة، محاكاة الجاذبية والقفز في ألعاب الأردوينو، مشروع Arduino لتعلم برمجة الألعاب، برمجة الألعاب باستخدام شاشة عرض 128x64 OLED، الأردوينو للمبتدئين في تطوير الألعاب، تحكم اللاعب في لعبة Endless Runner باستخدام زر ضغط، تخزين أعلى نتيجة في لعبة الأردوينو باستخدام EEPROM، تحسين رسوميات الألعاب على شاشة OLED، مفاهيم برمجة الألعاب المدمجة للمبتدئين، arduino ide، Arduino IDE، Endless Runner، برمجة ألعاب، ألعاب مدمجة، تطوير الألعاب المصغرة، متحكم دقيق، شاشة OLED، مستشعر مدخلات، برمجة فيزيائية، رسوميات بسيطة، توليد عشوائي، إلكترونيات، كائن اللاعب، الجاذبية، القفز، سرعة عمودية، محاكاة فيزيائية بسيطة، حالة اللعبة، توليد العقبات، عشوائية، اكتشاف الاصطدام (Collision Detection)، مستطيلات متداخلة (AABB Collision)، رسوميات متقدمة، bitmaps، مؤثرات صوتية، صعوبة ديناميكية، شاشة بداية، EEPROM، مشروع Arduino، برمجة ألعاب مدمجة، تطوير الألعاب، تفاعل الأجهزة، مفاهيم برمجية، Arduino programming، إلكترونيات، متحكمات دقيقة، تعلم برمجة الألعاب، برمجة لعبة Endless Runner باستخدام arduino ide، برمجة لعبة Endless Runner باستخدام Arduino IDE، مشروع تعليمي، الإلكترونيات والبرمجة، كيفية برمجة لعبة Endless Runner باستخدام Arduino IDE، برمجة ألعاب، GFX Library، شاشة OLED مع Arduino لبرمجة الألعاب، EEPROM،
 


كيفية برمجة لعبة Endless Runner باستخدام Arduino IDE


في عالم تطوير الألعاب المدمجة (Embedded Game Development)،
 يقدم الأردوينو (Arduino) منصة مثالية للمبتدئين لاستكشاف برمجة 
الألعاب على مستوى الأجهزة المادية. إذا كنت تحلم بإنشاء لعبتك الخاصة، ولكنك تفضل 
البدء بمشروع عملي وملموس يتفاعل مع العالم الحقيقي، فإن برمجة لعبة
 Endless Runner (لعبة الركض اللانهائي) باستخدام Arduino IDE هي
 نقطة انطلاق ممتازة. ستتعلم في هذا المقال كيفية بناء لعبة بسيطة ومسببة للإدمان 
حيث يتحكم اللاعب بشخصية تتجنب العقبات المتولدة عشوائياً، كل ذلك باستخدام
 مكونات إلكترونية بسيطة وشاشة عرض صغيرة. سنغوص في مفاهيم مثل 
التحكم بالمدخلات (Input Control)، عرض الرسوميات (Graphics Display)،
 توليد العقبات العشوائية (Random Obstacle Generation)، والفيزياء
 الأساسية للعبة (Basic Game Physics)، مما يمنحك فهماً قوياً لأساسيات
 برمجة الألعاب المصغرة (Mini-Game Programming).

خطوات بناء لعبة Endless Runner على Arduino IDE


لإنشاء لعبة Endless Runner على منصة الأردوينو، سنقوم بتقسيم
 المشروع إلى خطوات منطقية، بدءاً من إعداد المكونات وصولاً إلى برمجة منطق اللعبة والتفاعل مع اللاعب.

* الأدوات والمكونات المطلوبة :

- لوحة أردوينو: Arduino Uno أو Nano (أو أي لوحة متوافقة).
- شاشة عرض OLED: شاشة 0.96 بوصة I2C OLED Display (128x64 بكسل).
- زر ضغط (Push Button): واحد للقفز/التفاعل.
- مقاومة (Resistor): 10 كيلو أوم (لزر الضغط).
- لوحة تجارب (Breadboard).
- أسلاك توصيل (Jumper Wires).
- كابل USB: لتوصيل الأردوينو بالكمبيوتر.

1. إعداد بيئة الأردوينو وتوصيل المكونات

قبل البدء في البرمجة، يجب عليك تثبيت Arduino IDE وتوصيل شاشة
 OLED وزر الضغط بلوحة الأردوينو بشكل صحيح.

1.1. تثبيت مكتبات Arduino IDE:

- افتح Arduino IDE وانتقل إلى 
Sketch > Include Library > Manage Libraries... وابحث
 عن المكتبات التالية وقم بتثبيتها:
- Adafruit GFX Library: مكتبة أساسية للرسوميات.
- Adafruit SSD1306: مكتبة خاصة للتحكم بشاشة OLED (SSD1306 Chip).

1.2. توصيل المكونات (Fritzing/Diagram):

* توصيل شاشة OLED (I2C):
- VCC: توصيل إلى 5V في الأردوينو.
- GND: توصيل إلى GND في الأردوينو.
- SDA: توصيل إلى A4 في الأردوينو (بيانات I2C).
- SCL: توصيل إلى A5 في الأردوينو (ساعة I2C).

* توصيل زر الضغط :
طرف واحد من الزر إلى Digital Pin 2 في الأردوينو.
الطرف الآخر من الزر إلى GND في الأردوينو عبر مقاومة 10 كيلو أوم (Pull-Down Resistor).
نفس الطرف المتصل بـ Digital Pin 2 يوصل أيضاً مباشرةً إلى 5V (أو VCC).

* مخطط التوصيل (نص وصفي) :
تخيل أن لوحة الأردوينو هي الأساس. قم بتوصيل أسلاك الطاقة 
(الأحمر للـ 5V، الأسود للـ GND) من الأردوينو إلى جانبي لوحة التجارب. 
الآن، قم بتوصيل دبابيس شاشة OLED: دبوس VCC إلى 5V، دبوس
 GND إلى GND، دبوس SDA إلى دبوس A4، ودبوس SCL إلى دبوس A5.
بالنسبة للزر، ضع الزر على لوحة التجارب. قم بتوصيل أحد طرفي الزر إلى
 5V (أو VCC) مباشرةً. الطرف المقابل لذلك الطرف على نفس جانب الزر
 (عبر الفجوة المركزية في لوحة التجارب) يوصل إلى دبوس 2 في الأردوينو،
 وإلى GND عبر مقاومة 10 كيلو أوم. هذا يضمن أن الدبوس 2 يقرأ "منخفض"
 عندما لا يتم الضغط على الزر و"مرتفع" عند الضغط عليه.




2. الكود الأساسي لشاشة OLED والمدخلات

سنبدأ بكود يتحقق من عمل الشاشة وزر الضغط.
مكان الكود: arduino_endless_runner/arduino_endless_runner.ino 
(الملف الرئيسي في مشروع الأردوينو)
++C


#include <Wire.h>          // مكتبة الاتصال I2C
#include <Adafruit_GFX.h>  // مكتبة الرسوميات الأساسية
#include <Adafruit_SSD1306.h> // مكتبة شاشة OLED

// تعريف أبعاد الشاشة
#define SCREEN_WIDTH 128    // عرض الشاشة بالبكسل
#define SCREEN_HEIGHT 64    // ارتفاع الشاشة بالبكسل

// تعريف عنوان شاشة OLED (عادة 0x3C أو 0x3D، تحقق من شاشتك)
#define OLED_RESET -1 // دبوس إعادة الضبط (عادة لا يستخدم مع I2C)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// تعريف دبوس زر الضغط
const int JUMP_BUTTON_PIN = 2;

void setup() {
  Serial.begin(9600); // بدء الاتصال التسلسلي للمراقبة (Debugging)

  // تهيئة شاشة OLED
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // عنوان I2C 0x3C
    Serial.println(F("SSD1306 allocation failed"));
    for (;;) ; // توقف إذا فشل التهيئة
  }

  display.display(); // مسح الشاشة
  delay(2000);       // انتظار ثانيتين
  display.clearDisplay(); // مسح الشاشة مرة أخرى

  // تهيئة دبوس زر الضغط كمدخل مع مقاومة سحب داخلية (لتبسيط التوصيل إذا لم تستخدم مقاومة خارجية)
  //pinMode(JUMP_BUTTON_PIN, INPUT_PULLUP); // إذا كان الزر موصولاً بـ GND ويقرأ LOW عند الضغط
  pinMode(JUMP_BUTTON_PIN, INPUT); // إذا كان الزر موصولاً بـ 5V ويقرأ HIGH عند الضغط (مع مقاومة خارجية)
}

void loop() {
  display.clearDisplay(); // امسح الشاشة في كل حلقة

  // قراءة حالة زر الضغط
  int buttonState = digitalRead(JUMP_BUTTON_PIN);

  display.setTextSize(1); // حجم النص
  display.setTextColor(SSD1306_WHITE); // لون النص أبيض
  display.setCursor(0, 0); // موضع بداية النص

  if (buttonState == HIGH) { // إذا تم الضغط على الزر (يعتمد على توصيلك)
    display.println("Button Pressed!");
  } else {
    display.println("Button Not Pressed.");
  }

  display.display(); // عرض ما كتبته على الشاشة
  delay(10); // تأخير قصير لتجنب الوميض السريع
}


--

3. منطق اللعبة: الشخصية، الجاذبية، والقفز

الآن سنبدأ في برمجة شخصية اللاعب (مربع بسيط) ومنطق حركته.
مكان الكود: ضمن نفس الملف arduino_endless_runner.ino،
 استبدل محتويات loop() وأضف المتغيرات الجديدة.
++C





// ... (الاستيرادات والتعريفات الموجودة في الجزء 2)

// ======= متغيرات اللعبة =======
// الشخصية (اللاعب)
int playerX = 20;            // موضع اللاعب الأفقي (ثابت تقريباً)
int playerY = SCREEN_HEIGHT - 10 - 1; // موضع اللاعب العمودي (أعلى الأرض بقليل)
int playerWidth = 10;        // عرض اللاعب
int playerHeight = 10;       // ارتفاع اللاعب

// الجاذبية والقفز
float verticalSpeed = 0;     // السرعة العمودية للاعب (لأسفل بفعل الجاذبية، للأعلى عند القفز)
const float GRAVITY = 0.5;   // قيمة الجاذبية
const float JUMP_FORCE = -8; // قوة القفز (قيمة سالبة للحركة للأعلى)
bool isJumping = false;      // لتتبع ما إذا كان اللاعب يقفز

// حالة اللعبة
enum GameState { RUNNING, GAMEOVER };
GameState currentGameState = RUNNING;

void setup() {
  // ... (setup() كما في الجزء 2)
}

void loop() {
  // إذا كانت اللعبة تعمل
  if (currentGameState == RUNNING) {
    display.clearDisplay(); // امسح الشاشة في كل حلقة

    // ======= تحديث الجاذبية والقفز =======
    verticalSpeed += GRAVITY;       // تطبيق الجاذبية
    playerY += verticalSpeed;       // تحديث موضع اللاعب العمودي

    // التأكد من أن اللاعب لا يخترق الأرضية
    if (playerY >= SCREEN_HEIGHT - playerHeight - 1) {
      playerY = SCREEN_HEIGHT - playerHeight - 1; // ثبت اللاعب على الأرض
      verticalSpeed = 0;                      // أوقف السرعة العمودية
      isJumping = false;                      // لم يعد يقفز
    }

    // التحكم بالقفز
    if (digitalRead(JUMP_BUTTON_PIN) == HIGH && !isJumping) {
      verticalSpeed = JUMP_FORCE; // تطبيق قوة القفز
      isJumping = true;           // اللاعب يقفز الآن
    }

    // ======= رسم اللاعب =======
    display.fillRect(playerX, playerY, playerWidth, playerHeight, SSD1306_WHITE); // رسم مربع اللاعب

    // ======= رسم الأرضية =======
    display.drawLine(0, SCREEN_HEIGHT - 1, SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1, SSD1306_WHITE);

    display.display(); // عرض ما كتبته على الشاشة
    delay(10);         // تأخير قصير للتحكم في سرعة اللعبة
  }
  // إذا كانت اللعبة قد انتهت (Game Over)
  else if (currentGameState == GAMEOVER) {
    display.clearDisplay();
    display.setTextSize(2);
    display.setTextColor(SSD1306_WHITE);
    display.setCursor(10, SCREEN_HEIGHT / 2 - 10);
    display.println("GAME OVER!");
    display.setTextSize(1);
    display.setCursor(0, SCREEN_HEIGHT / 2 + 10);
    display.println("Press button to restart");
    display.display();

    // انتظار الضغط على الزر لإعادة التشغيل
    if (digitalRead(JUMP_BUTTON_PIN) == HIGH) {
      // إعادة تهيئة اللعبة
      playerY = SCREEN_HEIGHT - 10 - 1;
      verticalSpeed = 0;
      isJumping = false;
      currentGameState = RUNNING;
      // يجب أيضاً إعادة تهيئة العقبات والنقاط هنا
    }
  }
}



--

4. توليد العقبات واكتشاف الاصطدام

سنضيف الآن عقبات تتحرك نحو اللاعب وتوليدها بشكل عشوائي، بالإضافة إلى منطق اكتشاف الاصطدام.
مكان الكود: ضمن نفس الملف arduino_endless_runner.ino.




 أضف متغيرات العقبات، كلاس العقبة (اختياري، أو مجرد متغيرات)، 
وقم بتعديل loop() ليتضمن منطق العقبات.
++C




// ... (الاستيرادات والتعريفات الموجودة)

// ======= متغيرات اللعبة (إضافة العقبات) =======
// ... (متغيرات اللاعب، الجاذبية، القفز كما في الجزء 3)

// العقبات
const int MAX_OBSTACLES = 3;  // الحد الأقصى لعدد العقبات التي تظهر على الشاشة
int obstacleX[MAX_OBSTACLES]; // موضع العقبة الأفقي
int obstacleWidth[MAX_OBSTACLES]; // عرض العقبة
int obstacleHeight[MAX_OBSTACLES]; // ارتفاع العقبة
bool obstacleActive[MAX_OBSTACLES]; // هل العقبة نشطة؟
int obstacleSpeed = 2;        // سرعة حركة العقبات

// متى يتم توليد عقبة جديدة
long lastObstacleTime = 0;
const long MIN_OBSTACLE_INTERVAL = 1500; // الحد الأدنى للوقت بين عقبتين (بالمللي ثانية)
const long MAX_OBSTACLE_INTERVAL = 3000; // الحد الأقصى للوقت بين عقبتين

// النقاط
long score = 0;
long highScore = 0; // لحفظ أعلى نتيجة

// ... (enum GameState كما في الجزء 3)

void setup() {
  // ... (setup() كما في الجزء 2)
  randomSeed(analogRead(0)); // تهيئة مولد الأرقام العشوائية (باستخدام مدخل A0 غير المتصل)
  resetGame(); // استدعاء دالة لإعادة تهيئة اللعبة (سنقوم بإنشائها)
}

// دالة لإعادة تهيئة اللعبة
void resetGame() {
  playerY = SCREEN_HEIGHT - 10 - 1;
  verticalSpeed = 0;
  isJumping = false;
  score = 0;
  currentGameState = RUNNING;

  // إعادة تهيئة العقبات
  for (int i = 0; i < MAX_OBSTACLES; i++) {
    obstacleActive[i] = false;
  }
  lastObstacleTime = millis(); // إعادة ضبط مؤقت العقبات
}


void loop() {
  if (currentGameState == RUNNING) {
    display.clearDisplay();

    // ======= تحديث الجاذبية والقفز (كما في الجزء 3) =======
    verticalSpeed += GRAVITY;
    playerY += verticalSpeed;
    if (playerY >= SCREEN_HEIGHT - playerHeight - 1) {
      playerY = SCREEN_HEIGHT - playerHeight - 1;
      verticalSpeed = 0;
      isJumping = false;
    }
    if (digitalRead(JUMP_BUTTON_PIN) == HIGH && !isJumping) {
      verticalSpeed = JUMP_FORCE;
      isJumping = true;
    }

    // ======= تحديث وتوليد العقبات =======
    for (int i = 0; i < MAX_OBSTACLES; i++) {
      if (obstacleActive[i]) {
        obstacleX[i] -= obstacleSpeed; // حرك العقبة لليسار

        // إذا خرجت العقبة من الشاشة، أوقف تفعيلها
        if (obstacleX[i] + obstacleWidth[i] < 0) {
          obstacleActive[i] = false;
          score++; // زيادة النقاط عند تجاوز العقبة
        } else {
          // رسم العقبة
          display.fillRect(obstacleX[i], SCREEN_HEIGHT - obstacleHeight[i] - 1,
                           obstacleWidth[i], obstacleHeight[i], SSD1306_WHITE);

          // ======= اكتشاف الاصطدام (AABB Collision) =======
          // إذا تداخل مستطيل اللاعب مع مستطيل العقبة
          if (playerX < obstacleX[i] + obstacleWidth[i] &&
              playerX + playerWidth > obstacleX[i] &&
              playerY < SCREEN_HEIGHT - obstacleHeight[i] &&
              playerY + playerHeight > SCREEN_HEIGHT - obstacleHeight[i] - 1) {
            currentGameState = GAMEOVER; // اللعبة انتهت
            if (score > highScore) {
              highScore = score; // تحديث أعلى نتيجة
            }
          }
        }
      }
    }

    // توليد عقبة جديدة إذا حان الوقت وهناك مكان شاغر
    long currentTime = millis();
    if (currentTime - lastObstacleTime > random(MIN_OBSTACLE_INTERVAL, MAX_OBSTACLE_INTERVAL)) {
      int nextObstacleIndex = -1;
      for (int i = 0; i < MAX_OBSTACLES; i++) {
        if (!obstacleActive[i]) {
          nextObstacleIndex = i;
          break;
        }
      }
      if (nextObstacleIndex != -1) {
        obstacleX[nextObstacleIndex] = SCREEN_WIDTH + random(20, 50); // تبدأ من خارج الشاشة
        obstacleWidth[nextObstacleIndex] = random(10, 25); // عرض عشوائي
        obstacleHeight[nextObstacleIndex] = random(10, 25); // ارتفاع عشوائي
        obstacleActive[nextObstacleIndex] = true;
        lastObstacleTime = currentTime;
      }
    }

    // ======= رسم اللاعب والأرضية (كما في الجزء 3) =======
    display.fillRect(playerX, playerY, playerWidth, playerHeight, SSD1306_WHITE);
    display.drawLine(0, SCREEN_HEIGHT - 1, SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1, SSD1306_WHITE);

    // ======= عرض النقاط =======
    display.setTextSize(1);
    display.setCursor(0, 0);
    display.print("Score: ");
    display.println(score);
    display.print("High: ");
    display.println(highScore);

    display.display();
    delay(10);
  }
  // إذا كانت اللعبة قد انتهت (Game Over)
  else if (currentGameState == GAMEOVER) {
    display.clearDisplay();
    display.setTextSize(2);
    display.setTextColor(SSD1306_WHITE);
    display.setCursor(10, SCREEN_HEIGHT / 2 - 20);
    display.println("GAME OVER!");
    display.setTextSize(1);
    display.setCursor(5, SCREEN_HEIGHT / 2 + 5);
    display.print("Score: ");
    display.println(score);
    display.setCursor(5, SCREEN_HEIGHT / 2 + 20);
    display.println("Press button to restart");
    display.display();

    if (digitalRead(JUMP_BUTTON_PIN) == HIGH) {
      resetGame(); // استدعاء دالة إعادة التهيئة
    }
  }
}



--

5. تحسينات إضافية (اختياري)

يمكنك تطوير اللعبة أكثر بإضافة هذه الميزات:
رسوميات متقدمة: استخدام صور (Bitmaps) بدلاً من المربعات للشخصية والعقبات.
 يمكن رسمها باستخدام drawBitmap() في مكتبة Adafruit GFX.
- صوتيات: إضافة مؤثرات صوتية بسيطة باستخدام buzzer أو 
مكبر صوت صغير (للقفز، الاصطدام، نهاية اللعبة).
- زيادة الصعوبة تدريجياً: زيادة obstacleSpeed أو تقليل
 MIN_OBSTACLE_INTERVAL مع مرور الوقت أو زيادة النقاط.

* شاشة بداية: إضافة شاشة ترحيب قبل بدء اللعبة.
حفظ أعلى نتيجة: استخدام الذاكرة الداخلية لـ الأردوينو (EEPROM) 
لحفظ highScore حتى بعد إيقاف تشغيل الجهاز.

الخاتمة: خطوتك الأولى في برمجة الألعاب المدمجة
لقد أكملت للتو مشروع لعبة Endless Runner بسيطة باستخدام Arduino IDE،
 مما يمثل خطوة أولى رائعة في عالم برمجة الألعاب المدمجة. 
من خلال هذا المشروع، تعلمت كيفية التفاعل مع الأجهزة المادية 
(Hardware Interaction) مثل شاشات OLED والأزرار، وتطبيق
 مفاهيم برمجية أساسية (Core Programming Concepts) مثل الجاذبية،
 القفز، توليد العشوائية (Random Generation)، واكتشاف الاصطدام 
(Collision Detection). هذه التجربة لا تعزز فقط مهاراتك في برمجة الأردوينو 
(Arduino Programming) والإلكترونيات (Electronics)، بل تفتح لك
 أيضاً الباب لاستكشاف مشاريع ألعاب أكثر تعقيداً على المتحكمات الدقيقة. 
تذكر أن الإبداع لا حدود له، وأن هذا المشروع هو مجرد نقطة انطلاق لرحلتك 
في تطوير الألعاب المادية (Physical Game Development).


جدول المحتويات