
كيفية برمجة لعبة هروب من الغرفة ثلاثية الأبعاد على اندرويد ستوديو 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:@Overrideprotected void onPause() { super.onPause(); renderer.onPause(); renderer.releaseSoundPool();}
@Overrideprotected 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"; // الحالة الابتدائية للعبة
@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
saveManager = new GameSaveManager(this);
// ... تهيئة RajawaliSurfaceView والـ GameRenderer
loadSavedGame(); // استعادة التقدم المحفوظ عند بدء التشغيل}
@Overrideprotected 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;
@Overrideprotected 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.
أتمنى أن يكون هذا الهيكل التفصيلي بمثابة خريطة طريق لإنشاء لعبتك!
تذكر أن التنفيذ الفعلي سيتطلب الكثير من العمل والتعلم المستمر. بالتوفيق!