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

الصفحات

كيفية برمجة لعبة Endless Runner على Android Studio Java

How to code Endless Runner in Android Studio Java، How to code، Endless Runner in Android Studio، Java، كيفية برمجة لعبة Endless Runner على Android Studio Java، لعبة جري لا نهاية لها، تطوير ألعاب أندرويد، برمجة لعبة، تطوير ألعاب أندرويد جافا، SurfaceView، برمجة لعبة أركيد Arcade على اندرويد ستوديو، برمجة لعبة Endless Runner، Android Studio، Java ، لعبة جري لا نهاية لها أندرويد، تطوير ألعاب أندرويد جافا، Android Studio لعبة، Canvas أندرويد لعبة، SurfaceView أندرويد لعبة، الرسومات ثنائية الأبعاد أندرويد، حركة الشخصيات أندرويد، تجنب العقبات أندرويد، نظام النقاط أندرويد، حلقة اللعبة أندرويد،



كيفية برمجة لعبة Endless Runner على Android Studio Java


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


خطوات برمجة لعبة Endless Runner 



* الخطوات التفصيلية مع أجزاء من الأكواد:
* الخطوة 1: إنشاء مشروع Android جديد
- ابدأ مشروع Android جديدًا في Android Studio ، اختر "Empty Activity" ،
 قم بتسمية مشروعك واسم الحزمة وما إلى ذلك.

* الخطوة 2: إنشاء فئة GameView
أنشئ فئة Java جديدة باسم GameView تقوم بتوسيع (extends)
 SurfaceView وتنفيذ (implements) SurfaceHolder.Callback:
Java




import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class GameView extends SurfaceView implements SurfaceHolder.Callback {
    private MainThread thread;
    private Player player;
    private Paint scorePaint;
    private int score = 0;
    private List<Obstacle> obstacles = new ArrayList<>();
    private long lastObstacleTime = 0;
    private int obstacleSpawnInterval = 1500; // بالمللي ثانية

    public GameView(Context context) {
        super(context);
        getHolder().addCallback(this);
        thread = new MainThread(getHolder(), this);
        player = new Player(getContext());
        scorePaint = new Paint();
        scorePaint.setColor(Color.BLACK);
        scorePaint.setTextSize(50);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        thread.setRunning(true);
        thread.start();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        // لا يلزم فعل شيء هنا في هذه اللعبة البسيطة
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        boolean retry = true;
        while (retry) {
            try {
                thread.setRunning(false);
                thread.join();
            } catch (InterruptedException e) {
                // حاول مرة أخرى
            }
            retry = false;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            player.jump();
            return true;
        }
        return super.onTouchEvent(event);
    }

    public void update() {
        player.update();
        spawnObstacles();
        updateObstacles();
        checkCollision();
    }

    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);
        canvas.drawColor(Color.WHITE);
        player.draw(canvas);
        for (Obstacle obstacle : obstacles) {
            obstacle.draw(canvas);
        }
        canvas.drawText("Score: " + score, 50, 50, scorePaint);
    }

    private void spawnObstacles() {
        long currentTime = System.currentTimeMillis();
        if (currentTime - lastObstacleTime > obstacleSpawnInterval) {
            Obstacle obstacle = new Obstacle(getContext(), getWidth(), getHeight());
            obstacles.add(obstacle);
            lastObstacleTime = currentTime;
            obstacleSpawnInterval -= 10; // زيادة الصعوبة تدريجياً
            if (obstacleSpawnInterval < 500) {
                obstacleSpawnInterval = 500;
            }
        }
    }

    private void updateObstacles() {
        for (int i = 0; i < obstacles.size(); i++) {
            Obstacle obstacle = obstacles.get(i);
            obstacle.update();
            if (obstacle.getX() + obstacle.getWidth() < 0) {
                obstacles.remove(i);
                score++;
                i--;
            }
        }
    }

    private void checkCollision() {
        for (Obstacle obstacle : obstacles) {
            if (player.isColliding(obstacle)) {
                thread.setRunning(false);
                // يمكنك هنا عرض شاشة نهاية اللعبة
            }
        }
    }
}


--

الخطوة 3: إنشاء فئة MainThread (حلقة اللعبة)

أنشئ فئة Java جديدة باسم MainThread تقوم بتوسيع (extends) Thread. 
هذه الفئة ستدير حلقة اللعبة (التحديث والرسم) :
Java




import android.graphics.Canvas;
import android.view.SurfaceHolder;

public class MainThread extends Thread {
    private SurfaceHolder surfaceHolder;
    private GameView gameView;
    private boolean running;
    private static final int FPS = 30;

    public MainThread(SurfaceHolder holder, GameView gameView) {
        super();
        this.surfaceHolder = holder;
        this.gameView = gameView;
    }

    public void setRunning(boolean running) {
        this.running = running;
    }

    @Override
    public void run() {
        long startTime;
        long timeMillis;
        long waitTime;
        long targetTime = 1000 / FPS;

        while (running) {
            startTime = System.nanoTime();
            Canvas canvas = null;
            try {
                canvas = this.surfaceHolder.lockCanvas();
                synchronized (surfaceHolder) {
                    this.gameView.update();
                    this.gameView.draw(canvas);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (canvas != null) {
                    try {
                        surfaceHolder.unlockCanvasAndPost(canvas);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }

            timeMillis = (System.nanoTime() - startTime) / 1000000;
            waitTime = targetTime - timeMillis;

            try {
                if (waitTime > 0)
                    this.sleep(waitTime);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}



--

الخطوة 4: إنشاء فئة Player

أنشئ فئة Java جديدة باسم Player لتمثيل شخصية اللاعب :




Java




import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Rect;

public class Player {
    private Bitmap image;
    private int x;
    private int y;
    private int velocityY = 0;
    private final int gravity = 5;
    private final int jumpStrength = -40;
    private Rect collisionRect;

    public Player(Context context) {
        image = BitmapFactory.decodeResource(context.getResources(), R.drawable.player); // استبدل بمسار صورة اللاعب
        x = 100;
        y = 300; // قيمة ابتدائية
        collisionRect = new Rect(x, y, x + image.getWidth(), y + image.getHeight());
    }

    public void update() {
        velocityY += gravity;
        y += velocityY;
        collisionRect.top = y;
        collisionRect.bottom = y + image.getHeight();
    }

    public void jump() {
        velocityY = jumpStrength;
    }

    public void draw(Canvas canvas) {
        canvas.drawBitmap(image, x, y, null);
    }

    public Rect getCollisionRect() {
        return collisionRect;
    }

    public boolean isColliding(Obstacle obstacle) {
        return Rect.intersects(collisionRect, obstacle.getCollisionRect());
    }
}



--

الخطوة 5: إنشاء فئة Obstacle

أنشئ فئة Java جديدة باسم MovingObstacle لتمثيل العقبات 
  (عقبة متحركة عموديًا):
Java




import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Rect;

import java.util.Random;

public class MovingObstacle extends Obstacle {
    private int initialY;
    private int amplitude;
    private double frequency;
    private double time = 0;

    public MovingObstacle(Context context, int screenWidth, int screenHeight) {
        super(context, screenWidth, screenHeight);
        image = BitmapFactory.decodeResource(context.getResources(), R.drawable.moving_obstacle); // صورة عقبة متحركة
        width = image.getWidth();
        height = image.getHeight();
        x = screenWidth;
        Random random = new Random();
        initialY = screenHeight - height - random.nextInt(200);
        y = initialY;
        amplitude = 50 + random.nextInt(100);
        frequency = 0.02 + random.nextDouble() * 0.05;
        collisionRect = new Rect(x, y, x + width, y + height);
    }

    @Override
    public void update(int currentSpeed) {
        super.update(currentSpeed); // تحديث الحركة الأفقية
        time += frequency;
        y = initialY + (int) (amplitude * Math.sin(time));
        collisionRect.top = y;
        collisionRect.bottom = y + height;
    }

    @Override
    public void draw(Canvas canvas) {
        canvas.drawBitmap(image, x, y, null);
    }
}



--

* إنشاء فئة SmallFastObstacle (عقبة صغيرة وسريعة):
Java




import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Rect;
import java.util.Random;

public class SmallFastObstacle extends Obstacle {
    public SmallFastObstacle(Context context, int screenWidth, int screenHeight) {
        super(context, screenWidth, screenHeight);
        image = BitmapFactory.decodeResource(context.getResources(), R.drawable.small_fast_obstacle); // صورة عقبة صغيرة وسريعة
        width = image.getWidth() / 2; // أصغر
        height = image.getHeight() / 2;
        x = screenWidth;
        Random random = new Random();
        y = screenHeight - height - random.nextInt(300);
        speed = -15; // أسرع
        collisionRect = new Rect(x, y, x + width, y + height);
    }

    @Override
    public void update(int currentSpeed) {
        x += speed; // استخدام السرعة الداخلية
        collisionRect.left = x;
        collisionRect.right = x + width;
    }

    @Override
    public void draw(Canvas canvas) {
        canvas.drawBitmap(image, x, y, null);
    }
}



--

* تعديل spawnObstacles() في GameView لإنشاء أنواع 
مختلفة من العقبات بشكل عشوائي:
Java

private void spawnObstacles() {
    long currentTime = System.currentTimeMillis();
    if (currentTime - lastObstacleTime > obstacleSpawnInterval) {
        Random random = new Random();
        int obstacleType = random.nextInt(3); // 0: Obstacle, 1: MovingObstacle, 2: SmallFastObstacle
        Obstacle obstacle;
        switch (obstacleType) {
            case 0:
                obstacle = new Obstacle(getContext(), getWidth(), getHeight());
                break;
            case 1:
                obstacle = new MovingObstacle(getContext(), getWidth(), getHeight());
                break;
            case 2:
                obstacle = new SmallFastObstacle(getContext(), getWidth(), getHeight());
                break;
            default:
                obstacle = new Obstacle(getContext(), getWidth(), getHeight());
                break;
        }
        obstacles.add(obstacle);
        lastObstacleTime = currentTime;
        obstacleSpawnInterval -= 10;
        if (obstacleSpawnInterval < 500) {
            obstacleSpawnInterval = 500;
        }
    }
}
--

تأكد من إضافة الصور moving_obstacle.png و
 small_fast_obstacle.png إلى مجلد res/drawable.

--

الخطوة 6: إضافة الصور (Drawables)

أضف صورًا لشخصية اللاعب والعقبات إلى مجلد res/drawable. 
قم بتسميتها player.png و obstacle.png (أو أي أسماء أخرى واستبدلها في الكود).

الخطوة 7: تعديل MainActivity

اجعل MainActivity تقوم بتعيين GameView كمحتوى للعرض :
Java

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(new GameView(this));
    }
}
--

الخطوة 8: تشغيل التطبيق

قم بتشغيل التطبيق على جهاز Android حقيقي أو محاكي.
لتشغيل التطبيق الذي قمت ببرمجته على Android Studio، اتبع الخطوات التالية:
1* توصيل جهاز Android حقيقي (مفضل):
- تأكد من تفعيل وضع المطورين (Developer Options) على جهاز Android. 
يمكنك تفعيله بالذهاب إلى Settings -> About phone والنقر على Build number سبع مرات.
- انتقل إلى Settings -> Developer Options وقم بتفعيل USB debugging.
- قم بتوصيل جهاز Android بجهاز الكمبيوتر الخاص بك باستخدام كابل USB.
- قد يظهر لك مطالبة على هاتفك للسماح بتصحيح أخطاء USB لجهاز الكمبيوتر الخاص بك. 
قم بالسماح بذلك.

2* استخدام محاكي Android (Emulator):
- إذا لم يكن لديك جهاز Android حقيقي، يمكنك استخدام 
محاكي Android المدمج في Android Studio.
- افتح AVD Manager (Android Virtual Device Manager) من
 شريط الأدوات في Android Studio (Tools -> AVD Manager).
- إذا لم يكن لديك جهاز افتراضي، قم بإنشاء جهاز جديد (Create Virtual Device). 
اختر فئة الجهاز (مثل Phone)، واختر نظام صورة (System Image) 
(اختر إصدار Android 7.0 Nougat أو أعلى). اتبع التعليمات لإنشاء الجهاز الافتراضي.
- بمجرد إنشاء جهاز افتراضي، يمكنك تشغيله بالنقر على زر التشغيل الأخضر
 بجوار اسم الجهاز في AVD Manager.

* تشغيل التطبيق من Android Studio:
- بمجرد توصيل جهازك أو تشغيل المحاكي، سترى اسم الجهاز يظهر
 في شريط الأدوات العلوي بجوار زر التشغيل الأخضر (Run 'app').
- انقر على زر التشغيل الأخضر. سيقوم Android Studio ببناء 
تطبيقك وتثبيته وتشغيله على الجهاز أو المحاكي المحدد.

ميزات اضافية لبرمجة لعبة Endless Runner


اليك أمثلة لكيفية إضافة نظام تسجيل النقاط وزيادة صعوبة اللعبة تدريجياً. 
يمكنك تطبيق نفس المبادئ لإضافة المزيد من الميزات :

1. نظام تسجيل النقاط :
- في فئة GameView:
- تم بالفعل تهيئة متغير score ورسمه في طريقة draw().
- يتم زيادة score في طريقة updateObstacles() عندما يتم تجاوز عقبة.




* إضافة عرض للنقاط في شاشة نهاية اللعبة (إذا قمت بإنشائها):
عندما يحدث التصادم في checkCollision()‎ وتتوقف اللعبة، 
يمكنك الانتقال إلى نشاط جديد يعرض النتيجة النهائية. 
ستحتاج إلى تمرير قيمة score إلى هذا النشاط باستخدام Intent :
Java

    private void checkCollision() {
        for (Obstacle obstacle : obstacles) {
            if (player.isColliding(obstacle)) {
                thread.setRunning(false);
                // مثال للانتقال إلى شاشة نهاية اللعبة
                Intent gameOverIntent = new Intent(getContext(), GameOverActivity.class);
                gameOverIntent.putExtra("finalScore", score);
                getContext().startActivity(gameOverIntent);
                ((Activity)getContext()).finish(); // إنهاء نشاط اللعبة الحالي
            }
        }
    }
--

* إنشاء GameOverActivity (تخطيط activity_game_over.xml
 وفئة GameOverActivity.java):
Java




// GameOverActivity.java
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.widget.TextView;
import android.widget.Button;

public class GameOverActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_game_over);

        TextView finalScoreTextView = findViewById(R.id.finalScoreTextView);
        Button restartButton = findViewById(R.id.restartButton);

        int finalScore = getIntent().getIntExtra("finalScore", 0);
        finalScoreTextView.setText("النتيجة النهائية: " + finalScore);

        restartButton.setOnClickListener(v -> {
            Intent restartIntent = new Intent(this, MainActivity.class);
            startActivity(restartIntent);
            finish();
        });
    }
}



--

XML


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout ...>
    <TextView
        android:id="@+id/gameOverTextView"
        android:text="انتهت اللعبة!" ... />
    <TextView
        android:id="@+id/finalScoreTextView"
        android:text="النتيجة النهائية: 0" ... />
    <Button
        android:id="@+id/restartButton"
        android:text="إعادة المحاولة" ... />
</LinearLayout>
--

2. زيادة صعوبة اللعبة تدريجياً:
* في فئة GameView:
تم بالفعل تنفيذ جزء من زيادة الصعوبة في طريقة spawnObstacles() عن
 طريق تقليل obstacleSpawnInterval بمرور الوقت.
يمكنك أيضًا زيادة سرعة العقبات تدريجياً. أضف متغيرًا لسرعة العقبات وزده بمرور الوقت :
Java




public class GameView extends SurfaceView implements SurfaceHolder.Callback {
    // ... متغيرات أخرى
    private int obstacleSpawnInterval = 1500;
    private long lastDifficultyIncreaseTime = 0;
    private int difficultyIncreaseInterval = 5000; // كل 5 ثواني
    private int obstacleSpeed = -10;

    // ...

    public void update() {
        player.update();
        increaseDifficulty();
        spawnObstacles();
        updateObstacles();
        checkCollision();
    }

    private void increaseDifficulty() {
        long currentTime = System.currentTimeMillis();
        if (currentTime - lastDifficultyIncreaseTime > difficultyIncreaseInterval) {
            obstacleSpeed -= 1; // زيادة سرعة العقبات
            lastDifficultyIncreaseTime = currentTime;
            if (obstacleSpeed < -20) {
                obstacleSpeed = -20; // حد أقصى للسرعة
            }
        }
    }

    private void updateObstacles() {
        for (int i = 0; i < obstacles.size(); i++) {
            Obstacle obstacle = obstacles.get(i);
            obstacle.update(obstacleSpeed); // تمرير السرعة المتغيرة
            if (obstacle.getX() + obstacle.getWidth() < 0) {
                obstacles.remove(i);
                score++;
                i--;
            }
        }
    }
}


--

* تعديل فئة Obstacle لتلقي السرعة كمعامل:
Java

public class Obstacle {
    // ... متغيرات أخرى
    private int speed;

    public Obstacle(Context context, int screenWidth, int screenHeight) {
        // ... تهيئة الصورة والموضع
        speed = -10; // قيمة ابتدائية
        // ... تهيئة مستطيل التصادم
    }

    public void update(int currentSpeed) {
        x += currentSpeed;
        collisionRect.left = x;
        collisionRect.right = x + width;
    }

    // ...
}
--

3. إضافة عناصر قابلة للجمع (Collectibles):
- إنشاء فئة Collectible:
Java




import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Rect;

public class Collectible {
    private Bitmap image;
    private int x;
    private int y;
    private int width;
    private int height;
    private final int speed = -10;
    private Rect collisionRect;
    private boolean collected = false;

    public Collectible(Context context, int screenWidth, int screenHeight) {
        image = BitmapFactory.decodeResource(context.getResources(), R.drawable.coin); // صورة العنصر القابل للجمع
        width = image.getWidth();
        height = image.getHeight();
        x = screenWidth;
        Random random = new Random();
        y = 100 + random.nextInt(screenHeight - 200 - height); // وضع عشوائي
        collisionRect = new Rect(x, y, x + width, y + height);
    }

    public void update(int currentSpeed) {
        x += speed;
        collisionRect.left = x;
        collisionRect.right = x + width;
    }

    public void draw(Canvas canvas) {
        if (!collected) {
            canvas.drawBitmap(image, x, y, null);
        }
    }

    public Rect getCollisionRect() {
        return collisionRect;
    }

    public boolean isCollected() {
        return collected;
    }

    public void setCollected(boolean collected) {
        this.collected = collected;
    }
}



--

* إضافة قائمة بالعناصر القابلة للجمع في GameView وإنشائها وتحديثها :
Java




private List<Collectible> collectibles = new ArrayList<>();
private long lastCollectibleTime = 0;
private int collectibleSpawnInterval = 3000;

// ... في طريقة GameView() قم بتهيئة القائمة

@Override
public void update() {
    player.update();
    increaseDifficulty();
    spawnObstacles();
    updateObstacles();
    spawnCollectibles();
    updateCollectibles();
    checkCollision();
    checkCollectibleCollision(); // طريقة جديدة للتحقق من جمع العناصر
}

private void spawnCollectibles() {
    long currentTime = System.currentTimeMillis();
    if (currentTime - lastCollectibleTime > collectibleSpawnInterval) {
        Collectible collectible = new Collectible(getContext(), getWidth(), getHeight());
        collectibles.add(collectible);
        lastCollectibleTime = currentTime;
    }
}

private void updateCollectibles() {
    for (int i = 0; i < collectibles.size(); i++) {
        Collectible collectible = collectibles.get(i);
        collectible.update(obstacleSpeed);
        if (collectible.getX() + collectible.getWidth() < 0) {
            collectibles.remove(i);
            i--;
        }
    }
}

private void checkCollectibleCollision() {
    for (Collectible collectible : collectibles) {
        if (!collectible.isCollected() && player.isColliding(collectible)) {
            collectible.setCollected(true);
            score += 10; // منح نقاط عند الجمع
            // تشغيل مؤثر صوتي للجمع (سيتم شرحه لاحقًا)
        }
    }
}

@Override
public void draw(Canvas canvas) {
    super.draw(canvas);
    // ... رسم اللاعب والعقبات
    for (Collectible collectible : collectibles) {
        collectible.draw(canvas);
    }
    canvas.drawText("Score: " + score, 50, 50, scorePaint);
}



--

 تأكد من إضافة الصورة coin.png إلى مجلد res/drawable.

4. موسيقى ومؤثرات صوتية :

- إضافة ملفات الصوت: ضع ملفات الموسيقى 
(مثل background_music.mp3) والمؤثرات الصوتية 
(مثل jump_sound.wav, collect_sound.wav, collision_sound.wav)
 في مجلد res/raw. إذا لم يكن موجودًا، فأنشئ مجلدًا باسم raw داخل مجلد res.

* تشغيل الموسيقى في الخلفية (GameView):
Java

import android.media.MediaPlayer;

public class GameView extends SurfaceView implements SurfaceHolder.Callback {
    private MediaPlayer backgroundMusicPlayer;

    public GameView(Context context) {
        super(context);
        // ... تهيئة العناصر الأخرى
        backgroundMusicPlayer = MediaPlayer.create(context, R.raw.background_music);
        backgroundMusicPlayer.setLooping(true); // تكرار الموسيقى
        backgroundMusicPlayer.start();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // ... إيقاف مؤشر ترابط اللعبة
        if (backgroundMusicPlayer != null) {
            backgroundMusicPlayer.release();
            backgroundMusicPlayer = null;
        }
    }

    // ...
}
--

* تشغيل المؤثرات الصوتية (GameView):
Java




import android.media.SoundPool;
import android.os.Build;

public class GameView extends SurfaceView implements SurfaceHolder.Callback {
    private SoundPool soundPool;
    private int jumpSoundId;
    private int collectSoundId;
    private int collisionSoundId;

    public GameView(Context context) {
        super(context);
        // ... تهيئة العناصر الأخرى
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            soundPool = new SoundPool.Builder()
                    .setMaxStreams(10)
                    .build();
        } else {
            soundPool = new SoundPool(10, AudioManager.STREAM_MUSIC, 0);
        }

        jumpSoundId = soundPool.load(context, R.raw.jump_sound, 1);
        collectSoundId = soundPool.load(context, R.raw.collect_sound, 1);
        collisionSoundId = soundPool.load(context, R.raw.collision_sound, 1);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            player.jump();
            soundPool.play(jumpSoundId, 1, 1, 0, 0, 1); // تشغيل صوت القفز
            return true;
        }
        return super.onTouchEvent(event);
    }

    private void checkCollision() {
        for (Obstacle obstacle : obstacles) {
            if (player.isColliding(obstacle)) {
                thread.setRunning(false);
                soundPool.play(collisionSoundId, 1, 1, 0, 0, 1); // تشغيل صوت التصادم
                // ... الانتقال إلى شاشة نهاية اللعبة
            }
        }
    }

    private void checkCollectibleCollision() {
        for (Collectible collectible : collectibles) {
            if (!collectible.isCollected() && player.isColliding(collectible)) {
                collectible.setCollected(true);
                score += 10;
                soundPool.play(collectSoundId, 1, 1, 0, 0, 1); // تشغيل صوت الجمع
            }
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // ... إيقاف مؤشر ترابط اللعبة وإيقاف الموسيقى
        if (soundPool != null) {
            soundPool.release();
            soundPool = null;
        }
    }

    // ...
}




--

تذكر استبدال R.drawable.xxx و R.raw.xxx بأسماء
 الملفات الصحيحة التي أضفتها إلى مشروعك.

* ملاحظات هامة:
- الرسومات: هذا مثال بسيط يستخدم صور ثابتة. لرسومات أكثر تعقيدًا، يمكنك 
استخدام الرسوم المتحركة للإطارات (Frame Animation) أو الرسوم 
المتحركة القائمة على الرسوم البيانية (Sprite Sheets).
- التعامل مع الأبعاد: يجب أن تجعل أبعاد العناصر وسرعتها تعتمد على
 أبعاد الشاشة المختلفة لضمان عمل اللعبة بشكل جيد على أجهزة مختلفة. 
يمكنك الحصول على أبعاد الشاشة باستخدام getWidth() و getHeight() في GameView.
- إدارة الحالة: لتتبع حالة اللعبة (مثل النقاط، وحالة اللاعب)، يمكنك إنشاء فئة GameState.
- الكشف عن التصادم: Rect.intersects() هي طريقة بسيطة للكشف عن التصادم. 
للأشكال الأكثر تعقيدًا، قد تحتاج إلى طرق كشف تصادم أكثر تقدمًا.
- الأداء: للألعاب الأكثر تعقيدًا، قد تحتاج إلى تحسين أداء الرسم والتحديث لتجنب التأخير (Lag).
هذا المقال يوفر أساسًا لإنشاء لعبة Endless Runner بسيطة على Android باستخدام Java. 
يمكنك تطوير هذا الأساس بإضافة المزيد من الميزات والتحسينات لجعل لعبتك أكثر جاذبية. 
يمكنك الآن بناءً على هذا الأساس لتوسيع لعبتك بمزيد من الميزات والتحسينات، حظًا موفقًا في تطوير لعبتك.


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