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

الصفحات

كيفية برمجة لعبة هروب من الغرفة ثلاثية الأبعاد على اندرويد ستوديو Java

تطوير ألعاب ثلاثية الأبعاد Android Java، برمجة لعبة هروب من الغرفة Android، Rajawali Engine Android، ألعاب ألغاز ثلاثية الأبعاد Android، بداية تطوير ألعاب Android ثلاثية الأبعاد، أساسيات برمجة ألعاب Android Java، تهيئة مشروع لعبة ثلاثية الأبعاد Android، إضافة مكتبات Android، Rajawali Engine dependency، Gradle Android، نظام حفظ التقدم في لعبة الهروب من الغرفة ثلاثية الأبعاد باستخدام SharedPreferences في Android Java، صوت في ألعاب Android, موسيقى خلفية ألعاب Android, MediaPlayer Android, SoundPool Android، واجهة مستخدم ألعاب ثلاثية الأبعاد Android, عرض المخزون في الألعاب، برمجة ألغاز ثلاثية الأبعاد Android, منطق جمع العناصر في الألعاب، تفاعل اللمس ثلاثي الأبعاد Android، Rajawali Ray Casting، تحديد الكائنات ثلاثية الأبعاد، نماذج ثلاثية الأبعاد Android، Rajawali Cube، Rajawali LoaderOBJ، استيراد نماذج ثلاثية الأبعاد Android، RajawaliSurfaceView، ربط العارض بالواجهة، عرض ثلاثي الأبعاد Android، لعبة هروب من الغرفة ثلاثية الأبعاد، تطوير ألعاب Android ثلاثية الأبعاد، Rajawali Engine، Android Studio، إعداد مشروع Android، إضافة مكتبات Android، Rajawali dependency، تطوير ألعاب ثلاثية الأبعاد Android Java، برمجة لعبة هروب من الغرفة Android، لعبة ألغاز ثلاثية الأبعاد Android، Rajawali Engine Android، تفاعل ثلاثي الأبعاد Android، واجهة مستخدم لعبة ثلاثية الأبعاد Android، جمع العناصر في لعبة ثلاثية الأبعاد Android، حل الألغاز في لعبة ثلاثية الأبعاد Android، نشر لعبة ثلاثية الأبعاد Android، Android Studio، Java، رسومات ثلاثية الأبعاد، تفاعل اللمس، إطلاق أشعة، نماذج ثلاثية الأبعاد، مواد، كاميرا، إضاءة، تطوير ألعاب ثلاثية الأبعاد Android Java، برمجة لعبة هروب من الغرفة Android، لعبة ألغاز ثلاثية الأبعاد Android، Rajawali Engine Android، تفاعل ثلاثي الأبعاد Android، واجهة مستخدم لعبة ثلاثية الأبعاد Android، جمع العناصر في لعبة ثلاثية الأبعاد Android، حل الألغاز في لعبة ثلاثية الأبعاد Android، نشر لعبة ثلاثية الأبعاد Android، لعبة هروب من الغرفة ثلاثية الأبعاد بسيطة: مع تفاعل محدود مع البيئة لجمع العناصر وحل الألغاز للهروب، برمجة لعبة هروب من الغرفة ثلاثية الأبعاد، حل الألغاز للهروب باستخدام اندرويد ستوديو جافا، برمجة لعبة هروب من الغرفة ثلاثية الأبعاد بسيطة باستخدام Android Studio و Java، Rajawali Engine أو jMonkeyEngine، Rajawali Engine، How to code a 3D room escape game on Android Studio and Java، كيفية برمجة لعبة هروب من الغرفة ثلاثية الأبعاد على اندرويد ستوديو Java، تطوير ألعاب ثلاثية الأبعاد Android Java، Rajawali Engine، jMonkeyEngine،





كيفية برمجة لعبة هروب من الغرفة ثلاثية الأبعاد على اندرويد ستوديو Java



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


خطوات كيفية برمجة لعبة هروب من الغرفة ثلاثية الأبعاد على Android Studio



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

الجزء الأول: المفاهيم الأساسية والإعداد

في هذا الجزء، سنقوم بتجهيز الأدوات الأساسية ووضع الأساس لمشروع 
لعبة الهروب من الغرفة ثلاثية الأبعاد الخاص بك.

إعداد بيئة التطوير:

- تثبيت Android Studio وتكوينه، إنشاء مشروع Android جديد،
- إضافة مكتبة Rajawali Engine: يتضمن ذلك إضافة الاعتمادية (dependency)
 في ملف build.gradle (app).
Gradle

dependencies {
    // ... other dependencies
    implementation 'org.rajawali3d:rajawali:1.1.1375' // أو أحدث إصدار
}
-- 

مزامنة المشروع مع ملفات Gradle.

إنشاء العارض ثلاثي الأبعاد (Renderer):

إنشاء كلاس جديد يمتد من RajawaliRenderer. هذا الكلاس مسؤول عن تهيئة 
المشهد ثلاثي الأبعاد، تحميل النماذج، وتحديث وعرض الإطارات.
تهيئة المشهد، الكاميرا، والإضاءة الأساسية.
إنشاء كلاس العارض (GameRenderer.java):
Java





import org.rajawali3d.renderer.RajawaliRenderer;
import org.rajawali3d.cameras.PerspectiveCamera;
import org.rajawali3d.lights.DirectionalLight;
import org.rajawali3d.math.vector.Vector3;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class GameRenderer extends RajawaliRenderer {
    private PerspectiveCamera camera;
    private DirectionalLight directionalLight;

    public GameRenderer(MainActivity activity) {
        super(activity);
        setFrameRate(60);
    }

    @Override
    public void initScene() {
        camera = new PerspectiveCamera();
        camera.setPosition(0, 1, 5);
        camera.lookAt(0, 0, 0);
        getCurrentScene().addCamera(camera);
        setCamera(camera);

        directionalLight = new DirectionalLight(1, 1, 1);
        directionalLight.setLookAt(0, -1, 0);
        directionalLight.enableShadows(false);
        getCurrentScene().addLight(directionalLight);

        getCurrentScene().setBackgroundColor(0.2f, 0.2f, 0.2f, 1.0f);
    }

    @Override
    public void onDrawFrame(GL10 glUnused) {
        super.onDrawFrame(glUnused);
        // تحديثات المشهد هنا
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        super.onSurfaceChanged(gl, width, height);
        camera.setAspectRatio((double) width / height);
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        super.onSurfaceCreated(gl, config);
    }
}


--


* الحصول على مرجع لـ `RajawaliSurfaceView` في `MainActivity`
 وإنشاء مثيل لـ `GameRenderer` وربطه به.
java




import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import org.rajawali3d.view.RajawaliSurfaceView;

public class MainActivity extends AppCompatActivity {

    private GameRenderer renderer;

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

        final RajawaliSurfaceView surfaceView = findViewById(R.id.rajawali_surface_view);
        surfaceView.setFrameRate(60);
        surfaceView.setRenderMode(RajawaliSurfaceView.RENDERMODE_WHEN_DIRTY);

        renderer = new GameRenderer(this);
        surfaceView.setRenderer(renderer);
    }
}


--

الجزء الثاني: إنشاء المشهد ثلاثي الأبعاد والتفاعل

* إنشاء نماذج ثلاثية الأبعاد بسيطة:

يمكنك إنشاء نماذج بسيطة مباشرة في الكود باستخدام كلاسات
 Rajawali مثل Cube, Sphere, Plane.
بدلاً من ذلك، يمكنك استيراد نماذج ثلاثية الأبعاد بتنسيقات شائعة (مثل OBJ، FBX)
 باستخدام Loader كلاسات Rajawali. ستحتاج إلى وضع ملفات النماذج في مجلد assets الخاص بمشروعك.
Java




import org.rajawali3d.loader.LoaderOBJ;
import org.rajawali3d.loader.ParsingException;
import org.rajawali3d.materials.Material;
import org.rajawali3d.materials.textures.ATexture;
import org.rajawali3d.materials.textures.Texture;
import org.rajawali3d.primitives.Cube;

public class GameRenderer extends RajawaliRenderer {
    // ...

    private Cube room;
    private LoaderOBJ loader;

    @Override
    public void initScene() {
        super.initScene();

        // إنشاء غرفة بسيطة (مجرد مثال)
        room = new Cube(10);
        Material roomMaterial = new Material();
        roomMaterial.setColor(0.5f, 0.5f, 0.5f, 1.0f);
        room.setMaterial(roomMaterial);
        getCurrentScene().addChild(room);

        // تحميل نموذج ثلاثي الأبعاد (إذا كان لديك ملف obj في assets)
        loader = new LoaderOBJ(mContext.getResources(), mTextureManager, R.raw.table_obj); // R.raw.table_obj يشير إلى ملف table.obj في مجلد res/raw
        try {
            loader.parse();
            getCurrentScene().addChild(loader.getParsedObject());
        } catch (ParsingException e) {
            e.printStackTrace();
        }

       public class GameRenderer extends RajawaliRenderer {
    // ... (تهيئة الكاميرا والإضاءة)

    private Object3D roomObject;
    private Object3D tableObject;

    @Override
    public void initScene() {
        super.initScene();

        // ... (تهيئة الكاميرا والإضاءة)

        LoaderOBJ roomLoader = new LoaderOBJ(mContext.getResources(), mTextureManager, R.raw.room_obj);
        try {
            roomLoader.parse();
            roomObject = roomLoader.getParsedObject();
            getCurrentScene().addChild(roomObject);
        } catch (ParsingException e) {
            e.printStackTrace();
        }

        LoaderOBJ tableLoader = new LoaderOBJ(mContext.getResources(), mTextureManager, R.raw.table_obj);
        try {
            tableLoader.parse();
            tableObject = tableLoader.getParsedObject();
            tableObject.setPosition(0, 0, -2);
            getCurrentScene().addChild(tableObject);
        } catch (ParsingException e) {
            e.printStackTrace();
        }
    }

    // ...
}


--

* إضافة تفاعل محدود مع البيئة:

- التعرف على اللمس: استخدام OnTouchListener على 
RajawaliSurfaceView لاكتشاف لمسات المستخدم.
- إطلاق أشعة (Ray Casting): عند لمس الشاشة، يمكنك إطلاق شعاع من 
الكاميرا إلى داخل المشهد ثلاثي الأبعاد لمعرفة ما إذا اصطدم بأي كائن.
- تحديد العناصر القابلة للتفاعل: وضع علامات أو خصائص على 
الكائنات ثلاثية الأبعاد التي يمكن للاعب التفاعل معها.




- تنفيذ إجراءات التفاعل: بناءً على العنصر الذي تم لمسه، يمكنك تنفيذ إجراءات مثل 
تحريك الكاميرا حول المشهد (للتفتيش)، أو إظهار معلومات عن العنصر، أو تفعيله لحدث ما (مثل فتح درج).
Java




import android.view.MotionEvent;
import android.view.View;
import org.rajawali3d.math.Ray;
import org.rajawali3d.math.vector.Vector3;
import org.rajawali3d.Object3D;
import org.rajawali3d.renderer.RajawaliRenderer;

public class GameRenderer extends RajawaliRenderer implements View.OnTouchListener {
    // ... (تهيئة المشهد والنماذج)

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            Ray ray = getCurrentCamera().getRay(event.getX(), event.getY(), v.getWidth(), v.getHeight());
            Object3D hitObject = getCurrentScene().getFirstObjectUnderRay(ray);
            if (hitObject != null) {
                handleObjectTouch(hitObject);
            }
        }
        return true;
    }

    private void handleObjectTouch(Object3D object) {
        String name = object.getName();
        if (name.equals("key_obj")) {
            // منطق جمع المفتاح
            object.setVisible(false);
            // ... إضافة المفتاح إلى المخزون
        } else if (name.equals("door_obj")) {
            // منطق التفاعل مع الباب
            // ... التحقق من وجود المفتاح في المخزون
        }
    }

    // ...
}

// في MainActivity:
surfaceView.setOnTouchListener(renderer);


--

*جمع العناصر:
عند تفاعل اللاعب مع عنصر قابل للجمع، قم بإخفائه من المشهد ثلاثي الأبعاد وإضافته
 إلى قائمة "المخزون" الخاصة باللاعب (يمكن تمثيلها بواجهة مستخدم
 ثنائية الأبعاد منفصلة فوق المشهد ثلاثي الأبعاد).
*حل الألغاز:
تصميم ألغاز بسيطة تتطلب من اللاعب استخدام العناصر التي جمعها أو التفاعل مع 
البيئة بترتيب معين لفتح شيء ما أو الكشف عن شيء جديد. يمكن تمثيل الألغاز بتغيير
 حالة الكائنات ثلاثية الأبعاد بناءً على تفاعلات اللاعب.

*مثال على لغز بسيط: يجب على اللاعب لمس ثلاثة كتب بترتيب معين لفتح درج.
Java




public class GameRenderer extends RajawaliRenderer implements View.OnTouchListener {
    // ... (المشهد والنماذج والتفاعل باللمس)

    private int booksTouchedCount = 0;

    private void handleObjectTouch(Object3D object) {
        String name = object.getName();
        if (name.equals("book_1")) {
            if (booksTouchedCount == 0) {
                booksTouchedCount++;
                object.setVisible(false);
            }
        } else if (name.equals("book_2")) {
            if (booksTouchedCount == 1) {
                booksTouchedCount++;
                object.setVisible(false);
            }
        } else if (name.equals("book_3")) {
            if (booksTouchedCount == 2) {
                // فتح الدرج
                Object3D drawer = getCurrentScene().getObjectByName("drawer_obj");
                if (drawer != null) {
                    drawer.setZ(drawer.getZ() - 0.5f); // تحريك الدرج للخارج
                }
            } else {
                booksTouchedCount = 0; // إعادة تعيين اللغز إذا تم لمس الكتب بترتيب خاطئ
            }
            object.setVisible(false);
        }
        // ... منطق جمع العناصر الأخرى
    }

    // ...



}
--

الجزء الثالث: واجهة المستخدم (UI) الإضافية

- عرض المخزون: إنشاء تخطيط XML لعرض قائمة العناصر التي جمعها اللاعب
 (باستخدام RecyclerView أو LinearLayout). يمكن عرض هذا التخطيط فوق
 RajawaliSurfaceView باستخدام FrameLayout.
- عناصر تحكم إضافية: إضافة أزرار أو عناصر تفاعلية أخرى 
(مثل زر "استخدام عنصر" لسحب عنصر من المخزون والتفاعل به مع البيئة).
- إنشاء تخطيط XML لعرض المخزون (inventory_layout.xml):
استخدام ImageView لعرض صور العناصر التي تم جمعها.
- إضافة FrameLayout في activity_main.xml:
 لوضع RajawaliSurfaceView وتخطيط المخزون فوقه.
ربط واجهة المستخدم بالعارض 
(MainActivity.java و GameRenderer.java):
- الحصول على مرجع لـ ImageView في MainActivity.
في GameRenderer، عند جمع عنصر، قم بتحديث ImageView لعرض صورة العنصر 
(ستحتاج إلى تحميل الصور كـ Bitmaps).
Java




// في MainActivity:
ImageView inventorySlot1 = findViewById(R.id.inventory_slot_1);

// في GameRenderer:
private MainActivity mActivity;

public GameRenderer(MainActivity activity) {
    super(activity);
    mActivity = activity;
}

private void handleObjectTouch(Object3D object) {
    // ...
    if (name.equals("key_obj")) {
        object.setVisible(false);
        mActivity.runOnUiThread(() -> {
            mActivity.inventorySlot1.setImageResource(R.drawable.key_icon); // R.drawable.key_icon هو صورة المفتاح
        });
    }
    // ...
}


--

الجزء الرابع: إدارة حالة اللعبة والهروب

- تتبع حالة اللعبة: الاحتفاظ بمتغيرات لتتبع العناصر التي تم جمعها، الألغاز
 التي تم حلها، وما إذا كان اللاعب قد هرب.
- شرط الهروب: تحديد الشرط الذي يعتبر اللاعب قد هرب بنجاح 
(مثل العثور على مفتاح وفتح باب معين).
- شاشة النهاية: عرض شاشة تهنئة عند الهروب أو شاشة "Game Over" إذا فشل اللاعب 
(على الرغم من أن هذا المثال يركز على الهروب الناجح).


الجزء الخامس: الصوت والموسيقى (اختياري)

إضافة مؤثرات صوتية عند التفاعل مع العناصر أو حل الألغاز.
إضافة موسيقى خلفية بسيطة.
إضافة ملفات الصوت إلى مجلد res/raw (مثل ملفات WAV أو MP3).
استخدام كلاس MediaPlayer أو SoundPool لتشغيل الصوت:

Java




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

public class GameRenderer extends RajawaliRenderer implements View.OnTouchListener {
    // ...

    private MediaPlayer backgroundMusic;
    private SoundPool soundPool;
    private int itemPickupSound;

    @Override
    public void initScene() {
        super.initScene();
        // ...

        backgroundMusic = MediaPlayer.create(mActivity, R.raw.background_music);
        backgroundMusic.setLooping(true);
        backgroundMusic.start();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            soundPool = new SoundPool.Builder()
                    .setMaxStreams(5)
                    .build();
        } else {
            soundPool = new SoundPool(5, android.media.AudioManager.STREAM_GAME, 0);
        }
        itemPickupSound = soundPool.load(mActivity, R.raw.pickup_sound, 1);
    }

    private void handleObjectTouch(Object3D object) {
        // ...
        if (name.equals("key_obj")) {
            // ...
            soundPool.play(itemPickupSound, 1, 1, 0, 0, 1);
        }
        // ...
    }

    // ...

    public void releaseSoundPool() {
        if (soundPool != null) {
            soundPool.release();
            soundPool = null;
        }
        if (backgroundMusic != null) {
            backgroundMusic.release();
            backgroundMusic = null;
        }
    }
}

// في MainActivity:
@Override
protected void onPause() {
    super.onPause();
    renderer.onPause();
    renderer.releaseSoundPool();
}

@Override
protected void onResume() {
    super.onResume();
    renderer.onResume();
    // إعادة تشغيل الموسيقى إذا لزم الأمر
}


--

الجزء السادس برمجة قائمة الاعدادات :

سأوضح لك كيفية إضافة قائمة إعدادات بسيطة إلى اللعبة.

1. نظام حفظ التقدم باستخدام SharedPreferences:

SharedPreferences هي طريقة بسيطة لتخزين بيانات key-value صغيرة 
الحجم بشكل دائم في تخزين خاص بالتطبيق. سنستخدمها لتخزين حالة اللعبة الأساسية مثل
 العناصر التي تم جمعها والألغاز التي تم حلها.

أ) كلاس لإدارة حفظ التقدم (GameSaveManager.java):

Java




import android.content.Context;
import android.content.SharedPreferences;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class GameSaveManager {

    private static final String PREF_NAME = "game_progress";
    private static final String KEY_ITEMS = "collected_items";
    private static final String KEY_PUZZLES = "solved_puzzles";
    private static final String KEY_CURRENT_ROOM = "current_room";
    // يمكنك إضافة المزيد من المفاتيح لحفظ حالة الألغاز الفردية

    private final SharedPreferences sharedPreferences;
    private final Gson gson;

    public GameSaveManager(Context context) {
        sharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
        gson = new Gson();
    }

    public void saveGame(Set<String> collectedItems, Set<String> solvedPuzzles, String currentRoom) {
        SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.putStringSet(KEY_ITEMS, collectedItems);
        editor.putStringSet(KEY_PUZZLES, solvedPuzzles);
        editor.putString(KEY_CURRENT_ROOM, currentRoom);
        editor.apply();
    }

    public GameState loadGame() {
        Set<String> collectedItems = sharedPreferences.getStringSet(KEY_ITEMS, new HashSet<>());
        Set<String> solvedPuzzles = sharedPreferences.getStringSet(KEY_PUZZLES, new HashSet<>());
        String currentRoom = sharedPreferences.getString(KEY_CURRENT_ROOM, "initial_room"); // قيمة افتراضية
        return new GameState(collectedItems, solvedPuzzles, currentRoom);
    }

    public void clearSave() {
        SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.clear();
        editor.apply();
    }

    public static class GameState {
        public Set<String> collectedItems;
        public Set<String> solvedPuzzles;
        public String currentRoom;

        public GameState(Set<String> collectedItems, Set<String> solvedPuzzles, String currentRoom) {
            this.collectedItems = collectedItems;
            this.solvedPuzzles = solvedPuzzles;
            this.currentRoom = currentRoom;
        }
    }
}


--

ب) استخدام GameSaveManager في GameRenderer أو MainActivity:


Java




// في MainActivity:
private GameSaveManager saveManager;
private Set<String> collectedItems = new HashSet<>();
private Set<String> solvedPuzzles = new HashSet<>();
private String currentRoom = "room_1"; // الحالة الابتدائية للعبة

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

    saveManager = new GameSaveManager(this);

    // ... تهيئة RajawaliSurfaceView والـ GameRenderer

    loadSavedGame(); // استعادة التقدم المحفوظ عند بدء التشغيل
}

@Override
protected void onPause() {
    super.onPause();
    renderer.onPause();
    renderer.releaseSoundPool();
    saveGameProgress(); // حفظ التقدم عند إيقاف اللعبة مؤقتًا
}

private void saveGameProgress() {
    saveManager.saveGame(collectedItems, solvedPuzzles, currentRoom);
}

private void loadSavedGame() {
    GameSaveManager.GameState gameState = saveManager.loadGame();
    collectedItems = gameState.collectedItems;
    solvedPuzzles = gameState.solvedPuzzles;
    currentRoom = gameState.currentRoom;
    // قم بتحديث حالة اللعبة (إخفاء العناصر التي تم جمعها بالفعل، فتح الأبواب التي تم فتحها، إلخ.) بناءً على هذه البيانات
    renderer.updateGameState(collectedItems, solvedPuzzles, currentRoom); // تحتاج إلى إنشاء هذه الدالة في GameRenderer
}

// دالة لمسح الحفظ (يمكن ربطها بزر في قائمة الإعدادات)
private void clearSavedProgress() {
    saveManager.clearSave();
    // أعد تهيئة حالة اللعبة إلى الوضع الابتدائي
    collectedItems.clear();
    solvedPuzzles.clear();
    currentRoom = "room_1";
    renderer.resetGame(); // تحتاج إلى إنشاء هذه الدالة في GameRenderer
}

// في GameRenderer:
private Set<String> savedCollectedItems;
private Set<String> savedSolvedPuzzles;
private String savedCurrentRoom;

public void updateGameState(Set<String> collectedItems, Set<String> solvedPuzzles, String currentRoom) {
    this.savedCollectedItems = collectedItems;
    this.savedSolvedPuzzles = solvedPuzzles;
    this.savedCurrentRoom = currentRoom;

    // بناءً على هذه البيانات، قم بتحديث حالة المشهد ثلاثي الأبعاد
    // على سبيل المثال، إخفاء العناصر التي تم جمعها:
    for (String itemName : savedCollectedItems) {
        Object3D item = getCurrentScene().getObjectByName(itemName);
        if (item != null) {
            item.setVisible(false);
        }
    }
    // وبالمثل، قم بتحديث حالة الألغاز والأبواب بناءً على savedSolvedPuzzles و savedCurrentRoom
}

public void resetGame() {
    // أعد تهيئة جميع عناصر المشهد إلى حالتها الأصلية
    // أظهر العناصر المخفية، أغلق الأبواب المفتوحة، إلخ.
}

private void handleObjectTouch(Object3D object) {
    String name = object.getName();
    if (name.equals("key_obj") && !savedCollectedItems.contains("key_obj")) {
        object.setVisible(false);
        collectedItems.add("key_obj");
        // ... تشغيل صوت الالتقاط وتحديث واجهة المستخدم للمخزون
    } else if (name.equals("door_obj") && collectedItems.contains("key_obj") && !savedSolvedPuzzles.contains("door_unlocked")) {
        // فتح الباب
        // ... تحريك الباب أو جعله غير مرئي
        solvedPuzzles.add("door_unlocked");
    }
    // ... تعامل مع تفاعلات أخرى وقم بتحديث collectedItems و solvedPuzzles
}


--

2. قائمة الإعدادات البسيطة :

سنقوم بإنشاء نشاط (Activity) منفصل لقائمة الإعدادات.




أ) إنشاء تخطيط XML لقائمة الإعدادات (settings_activity.xml):

XML




<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="الإعدادات"
        android:textSize="24sp"
        android:textStyle="bold"
        android:layout_marginBottom="16dp" />

    <Button
        android:id="@+id/clear_progress_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="مسح التقدم المحفوظ" />

    </LinearLayout>


--

ب) إنشاء نشاط Java لقائمة الإعدادات (SettingsActivity.java):

Java




import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class SettingsActivity extends AppCompatActivity {

    private Button clearProgressButton;
    private GameSaveManager saveManager;

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

        saveManager = new GameSaveManager(this);
        clearProgressButton = findViewById(R.id.clear_progress_button);

        clearProgressButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // قم باستدعاء الدالة لمسح التقدم المحفوظ من MainActivity أو GameRenderer
                // الطريقة تعتمد على مكان وجود منطق إدارة حالة اللعبة
                if (getParent() instanceof MainActivity) {
                    ((MainActivity) getParent()).clearSavedProgress();
                } else if (getParent() instanceof GameRenderer) {
                    ((GameRenderer) getParent()).resetGame();
                    // ... تحتاج إلى طريقة لتحديث حالة الحفظ في MainActivity أيضًا
                } else {
                    // حل بديل إذا لم يكن الوصول المباشر ممكنًا (مثل استخدام Intent لإرسال رسالة)
                    // يمكنك استخدام واجهة (Interface) لإنشاء اتصال بين الأنشطة
                }
                finish(); // العودة إلى الشاشة الرئيسية بعد المسح
            }
        });

        // قم بإضافة منطق لعناصر التحكم الأخرى في الإعدادات هنا
    }
}


--

ج) إضافة زر لقائمة الإعدادات في التخطيط الرئيسي (activity_main.xml):

XML




<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <org.rajawali3d.view.RajawaliSurfaceView
        android:id="@+id/rajawali_surface_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <LinearLayout
        android:id="@+id/inventory_ui"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center"
        android:padding="8dp"
        android:background="#80000000"
        android:layout_gravity="bottom">

        <ImageView
            android:id="@+id/inventory_slot_1"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_margin="8dp"
            android:scaleType="fitCenter" />

        </LinearLayout>

    <Button
        android:id="@+id/settings_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="الإعدادات"
        android:layout_gravity="top|end"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp" />

</FrameLayout>


--

د) إضافة مستمع (Listener) لزر الإعدادات في MainActivity:

Java




// في MainActivity:
private Button settingsButton;

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

    // ... تهيئة saveManager والـ RecyclerView

    settingsButton = findViewById(R.id.settings_button);
    settingsButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            startActivity(new Intent(MainActivity.this, SettingsActivity.class));
        }
    });

    loadSavedGame();
}



--

* شرح الكود:

*GameSaveManager:
مسؤول عن حفظ واستعادة بيانات اللعبة باستخدام SharedPreferences.
يستخدم Gson لتحويل قوائم العناصر والألغاز إلى JSON strings لتخزينها واستعادتها.
saveGame(): يحفظ العناصر التي تم جمعها، والألغاز التي تم حلها، والغرفة الحالية.
loadGame(): يستعيد البيانات المحفوظة.
clearSave(): يمسح جميع بيانات الحفظ.
GameState: كلاس بسيط لتغليف بيانات حفظ اللعبة.

*التكامل في MainActivity:
يتم إنشاء مثيل لـ GameSaveManager.
saveGameProgress(): يتم استدعاؤها عند إيقاف النشاط مؤقتًا لحفظ التقدم.
loadSavedGame(): يتم استدعاؤها عند إنشاء النشاط لاستعادة التقدم المحفوظ.
clearSavedProgress(): دالة لمسح الحفظ (يجب ربطها بزر في قائمة الإعدادات).
زر الإعدادات يقوم بفتح SettingsActivity.

*التكامل في GameRenderer:
يتم استدعاء updateGameState() لتحديث حالة المشهد ثلاثي الأبعاد بناءً على البيانات المحفوظة.
يجب تعديل handleObjectTouch() لتحديث قوائم collectedItems و
 solvedPuzzles في MainActivity عند تفاعل اللاعب مع العناصر وحل الألغاز.
resetGame(): دالة لإعادة تهيئة المشهد إلى حالته الأولية.

* SettingsActivity:
نشاط بسيط يحتوي على زر لمسح التقدم المحفوظ.
عند النقر على زر المسح، يتم استدعاء دالة clearSavedProgress() في
 MainActivity (هناك حاجة إلى حل أنيق للوصول إلى دالة في النشاط الأصلي، مثل استخدام واجهة أو EventBus).

الجزء السابع : الاختبار والتصحيح

اختبار اللعبة على أجهزة Android فعلية.
استخدام أدوات التصحيح في Android Studio لتحديد الأخطاء وإصلاحها 
في كل من كود Java ومشهد Rajawali.

الجزء الثامن: نشر اللعبة على متجر Google Play

اتبع نفس خطوات نشر التطبيق الموضحة في المقال السابق (إنشاء ملف توقيع، 
تكوين build.gradle، إنشاء قائمة في Google Play Console، تحميل ملف APK/AAB).


ملاحظات هامة:
هذا مثال أساسي. قد تحتاج إلى حفظ المزيد من بيانات حالة اللعبة بناءً على 
تعقيد الألغاز والتفاعلات في لعبتك (مثل حالة الأبواب، مواقع العناصر التي تم تحريكها، إلخ.).
بالنسبة لقائمة الإعدادات، يمكنك إضافة المزيد من الخيارات مثل التحكم في 
مستوى الصوت، إعدادات الرسومات (إذا كانت لعبتك تدعمها)، إلخ.
يجب عليك التأكد من تحديث حالة اللعبة في GameRenderer بشكل صحيح عند تحميل التقدم المحفوظ.
بالنسبة للوصول إلى دالة clearSavedProgress() من SettingsActivity،
 يمكنك استخدام واجهة (Interface) يتم تنفيذها بواسطة MainActivity ويمرر 
مثيلها إلى SettingsActivity أو استخدام مكتبة مثل EventBus لتسهيل الاتصال بين الأنشطة.
آمل أن يكون هذا الكود نقطة انطلاق جيدة لتنفيذ نظام حفظ التقدم وقائمة إعدادات بسيطة في لعبتك! 
تذكر أن هذا يتطلب المزيد من التعديل والتكامل مع منطق اللعبة المحدد.

الخلاصة:

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


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