
دليل LibGDX الشامل : Animation، الموسيقى، (UI)، Box2D
يُعد التحريك (Animation) عنصرًا أساسيًا لإضفاء الحيوية والتفاعل
على ألعابك في LibGDX. سواء كنت تقوم بتحريك شخصية لاعب،
مؤثرات انفجار، أو أي عنصر مرئي آخر، فإن LibGDX توفر
أدوات قوية للتعامل مع التحريك خطوة بخطوة.
مفهوم التحريك في LibGDX
يعتمد التحريك في LibGDX بشكل أساسي على عرض سلسلة من الصور
(إطارات) متتالية بسرعة، لخلق وهم الحركة. هذه الإطارات تُعرف غالبًا باسم
"sprites" أو "regions" ضمن TextureAtlas.
خطوات إنشاء التحريك :
1- إعداد الأصول (Asset Preparation):
- قم بإنشاء أو جمع جميع إطارات التحريك كصور منفصلة
(مثل player_walk_01.png, player_walk_02.png, إلخ).
- الأفضل والأكثر كفاءة : استخدم أداة مثل TexturePacker (موصى به بشدة)
لتجميع كل هذه الإطارات في ملف صورة واحد كبير (texture atlas) وملف وصف
(عادةً .atlas أو .pack). هذا يُقلل من عدد مرات تبديل النسيج
(texture switching) على وحدة معالجة الرسوميات (GPU) ويُحسن الأداء.
2- تحميل TextureAtlas :
استخدم AssetManager أو التحميل المباشر لتحميل TextureAtlas
الذي يحتوي على إطارات التحريك الخاصة بك :
Java
TextureAtlas atlas;
// في create()
atlas = new TextureAtlas(Gdx.files.internal("sprites/player_atlas.atlas"));
3- إنشاء Animation Object :
Animation في LibGDX هو كائن يدير مجموعة من TextureRegions
(إطارات التحريك) ويحسب أي إطار يجب عرضه بناءً على الوقت المنقضي.
ستحتاج إلى استخراج الإطارات من TextureAtlas أولاً :
Java
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.utils.Array; // مهم لـ Animation constructor
// ... داخل الفئة ...
Animation<TextureRegion> playerWalkAnimation;
float stateTime; // لتتبع الوقت المنقضي
// في create() بعد تحميل الأطلس
Array<TextureAtlas.AtlasRegion> walkFrames = atlas.findRegions("player_walk"); // "player_walk" هو اسم السلسلة في الأطلس
playerWalkAnimation = new Animation<TextureRegion>(0.1f, walkFrames, Animation.PlayMode.LOOP); // 0.1f = مدة الإطار
stateTime = 0f;
--
- شرح معامل Animation :
- frameDuration (0.1f في المثال) : المدة التي يستغرقها كل إطار قبل الانتقال إلى التالي.
- keyFrames (walkFrames) : مجموعة من TextureRegions التي تشكل التحريك.
- playMode (Animation.PlayMode.LOOP) :
يحدد كيفية تشغيل التحريك (مثل LOOP, NORMAL, REVERSED, PINGPONG).
- تحديث وعرض التحريك في ()render :
في طريقة render()، ستحتاج إلى تحديث stateTime ثم
استخدامها للحصول على الإطار الحالي ورسمه :
Java
// في render()
stateTime += Gdx.graphics.getDeltaTime(); // إضافة الوقت المنقضي منذ آخر إطار
TextureRegion currentFrame = playerWalkAnimation.getKeyFrame(stateTime, true); // true لضمان التكرار
// رسم الإطار الحالي باستخدام SpriteBatch
batch.begin();
batch.draw(currentFrame, x, y);
batch.end();
--
- التفريغ (Dispose) :
تذكر دائمًا تفريغ TextureAtlas عندما لا تعد بحاجة إليه، خاصة عند إغلاق التطبيق :
Java
// في dispose()
atlas.dispose();
--
إضافة المؤثرات الصوتية والموسيقى في LibGDX
تُضيف المؤثرات الصوتية والموسيقى طبقة حيوية لأي لعبة، حيث تعزز الانغماس
وتُقدم تغذية راجعة للاعب. تُقدم LibGDX واجهة بسيطة وفعالة للتعامل مع الصوت.
أنواع الصوت في LibGDX :
* Sound :
- للمؤثرات الصوتية القصيرة والاندفاعية (مثل إطلاق النار، جمع قطعة نقدية، قفزة، انفجار).
- يتم تحميلها بالكامل في الذاكرة لسرعة الوصول والتشغيل المتكرر دون تأخير.
- مثالي للمؤثرات التي قد تحتاج إلى تشغيل متزامن عدة مرات.
* Music:
- للموسيقى الخلفية الطويلة، المقاطع الصوتية، أو الحوار.
- لا تُحمّل بالكامل في الذاكرة بل تُشغل تدريجيًا (streaming) من القرص، مما يوفر الذاكرة.
- مثالي للمقاطع الصوتية التي تُشغل مرة واحدة أو تُكرر في الخلفية.
- خطوات إضافة الصوت :
- وضع ملفات الصوت :
ضع ملفات الصوت (مثل .mp3, .ogg, .wav) في مجلد assets الخاص
بمشروعك (عادةً android/assets). يُفضل استخدام .ogg لأنه
مدعوم على نطم أساسية أكثر ويوفر ضغطًا جيدًا.
- تحميل وتشغيل Sound :
Java
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.audio.Sound;
Sound shootSound;
// في create()
shootSound = Gdx.audio.newSound(Gdx.files.internal("audio/shoot.ogg"));
// عند حدوث الحدث (مثلاً عند إطلاق النار)
shootSound.play(); // تشغيل الصوت مرة واحدة
// shootSound.play(volume); // مع تحديد مستوى الصوت (0.0 - 1.0)
// shootSound.play(volume, pitch, pan); // تحكم كامل (درجة الصوت، اتجاه الصوت)
--
- تحميل وتشغيل Music :
Java
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.audio.Music;
Music backgroundMusic;
// في create()
backgroundMusic = Gdx.audio.newMusic(Gdx.files.internal("audio/bensound-funkyelement.mp3"));
// تشغيل الموسيقى
backgroundMusic.play();
backgroundMusic.setLooping(true); // لجعل الموسيقى تتكرر
backgroundMusic.setVolume(0.5f); // ضبط مستوى الصوت (0.0 - 1.0)
// في أي نقطة لاحقة
// backgroundMusic.pause(); // لإيقاف مؤقت
// backgroundMusic.stop(); // للإيقاف الكامل
--
- التفريغ (Dispose) :
من الأهمية بمكان تحرير موارد الصوت عندما لا تكون هناك حاجة إليها لمنع تسرب الذاكرة :
Java
// في dispose()
shootSound.dispose();
backgroundMusic.dispose();
--
كيفية إنشاء واجهة مستخدم (UI) Scene2D في LibGDX
تُعد Scene2D جزءًا قويًا من LibGDX يُمكّنك من إنشاء واجهة مستخدم
(UI) تفاعلية ومنظمة لألعابك. توفر Scene2D نموذجًا قائمًا على الممثل
(Actor-based model) يسهل التعامل مع عناصر واجهة
المستخدم مثل الأزرار، مربعات النصوص، والقوائم.
- المفاهيم الأساسية لـ Scene2D :
- Actor : الوحدة الأساسية في Scene2D. أي شيء يُمكنه أن يُرسم،
يتفاعل مع الإدخال، أو يتغير بمرور الوقت يُعد Actor.
الأزرار والنصوص والصور كلها يمكن أن تكون Actors.
- Stage : هي حاوية منطقية تستضيف Actors وتُعالج أحداث الإدخال الخاصة بهم
(اللمس، النقر، سحب وإفلات). عادةً ما يكون لديك Stage واحد لكل شاشة في لعبتك.
- Table : أداة قوية لتخطيط وتنظيم Actors داخل واجهة المستخدم.
تُسهل وضع الأزرار والنصوص في صفوف وأعمدة.
- Skin : يحدد مظهر عناصر واجهة المستخدم (الخلفيات، ألوان الخطوط،
أحجام الأزرار، إلخ). يمكن تعريف الـ Skin باستخدام ملفات JSON.
خطوات إنشاء واجهة مستخدم بسيطة :
- إعداد Stage :
يجب أن يكون لديك Stage واحد على الأقل. عادةً ما يتم إعداده في
طريقة create() أو عند بدء شاشة UI :
Java
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.utils.viewport.ScreenViewport; // أو أي Viewport آخر
Stage stage;
// في create()
stage = new Stage(new ScreenViewport()); // Viewport لإدارة أحجام الشاشة
Gdx.input.setInputProcessor(stage); // هام: لجعل Stage يتعامل مع المدخلات
- تحميل Skin (اختياري ولكنه موصى به) :
لجعل واجهتك تبدو احترافية، قم بتحميل Skin (يمكنك استخدام
uiskin.json الافتراضي في LibGDX للمبتدئين) :
Java
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
Skin skin;
// في create()
skin = new Skin(Gdx.files.internal("ui/uiskin.json")); // تأكد من وجود الملفات
--
- إنشاء عناصر واجهة المستخدم (Actors) :
سنقوم بإنشاء زر بسيط ومربع نص :
Java
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
TextButton playButton;
Label titleLabel;
// بعد تحميل الـ skin
titleLabel = new Label("My Awesome Game", skin);
playButton = new TextButton("Play Game", skin);
// إضافة مستمع للأحداث للزر
playButton.addListener(new ClickListener() {
@Override
public void clicked(InputEvent event, float x, float y) {
Gdx.app.log("UI", "Play Button Clicked!");
// هنا يمكنك الانتقال إلى شاشة اللعبة
}
});
--
- تنظيم العناصر باستخدام Table :
Table هو العنصر الأكثر مرونة لتخطيط واجهة المستخدم :
Java
import com.badlogic.gdx.scenes.scene2d.ui.Table;
Table table;
// في create()
table = new Table();
table.setFillParent(true); // يجعل الجدول يملأ الشاشة بالكامل
// إضافة العناصر إلى الجدول وتحديد تخطيطها
table.add(titleLabel).padBottom(20).row(); // إضافة تسمية، مسافة سفلية، ثم صف جديد
table.add(playButton).width(200).height(50).row(); // إضافة زر، تحديد أبعاده، ثم صف جديد
// إضافة الجدول إلى الـ Stage
stage.addActor(table);
--
- تحديث ورسم واجهة المستخدم في ()render :
يجب عليك تحديث ورسم الـ Stage في كل إطار :
Java
// في render()
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); // مسح الشاشة
stage.act(Gdx.graphics.getDeltaTime()); // تحديث منطق Stage (التحريك، التفاعلات)
stage.draw(); // رسم الـ Stage وعناصره
--
- التعامل مع تغيير الحجم (resize) والتفريغ (dispose) :
عندما يتغير حجم الشاشة، يجب تحديث الـ Stage ليتكيف مع الأبعاد الجديدة.
تفريغ الـ Stage والـ Skin عند الانتهاء :
Java
// في resize(int width, int height)
stage.getViewport().update(width, height, true); // true لمركزة الكاميرا
// في dispose()
stage.dispose();
skin.dispose();
--
التعامل مع فيزياء 2D باستخدام Box2D في LibGDX
لإضافة حركة وسلوك واقعي قائم على الفيزياء إلى ألعابك ثنائية الأبعاد،
تُعد مكتبة Box2D هي الحل الأمثل في LibGDX. تُوفر Box2D محاكاة فيزيائية
قوية للكائنات الصلبة (rigid bodies)، التعامل مع التصادمات، وقوى مثل الجاذبية.
- المفاهيم الأساسية لـ Box2D :
World: تمثل عالم الفيزياء الذي توجد فيه جميع الأجسام. تُعرف الجاذبية والإعدادات العالمية الأخرى هنا.
Body : يمثل كائنًا في عالم الفيزياء. لكل Body خصائص فيزيائية مثل الكتلة، السرعة، الدوران، والاحتكاك.
BodyDef : يُعرف خصائص Body عند إنشائه (الموقع، النوع، إلخ).
- أنواع Body :
- StaticBody : جسم ثابت لا يتأثر بالقوى (مثل الأرض، الجدران).
- KinematicBody : جسم يتحرك بواسطة سرعة محددة ولكن لا يتأثر بالقوى (مثل منصات متحركة).
- DynamicBody : جسم يتحرك ويتأثر بالقوى (مثل اللاعب، الأعداء، الصناديق).
- Fixture: تُحدد الشكل الهندسي لجسم ما، كثافته، احتكاكه، ومرونته (restitution).
يمكن أن يحتوي Body واحد على عدة Fixtures.
- Shape : يحدد الشكل الهندسي لـ Fixture (مثل CircleShape, PolygonShape, EdgeShape).
FixtureDef: يُعرف خصائص Fixture عند إنشائه.
- ContactListener : واجهة تمكنك من الكشف عن التصادمات بين الأجسام والرد عليها.
- Box2DDebugRenderer : أداة تصحيح أخطاء مفيدة ترسم أشكال Box2D في
عالم اللعبة، مما يساعد على تصور حدود التصادمات.
* خطوات استخدام Box2D :
- إعداد عالم Box2D (World) :
يتم إنشاء World في create() ويُمرر له متجه الجاذبية :
Java
import com.badlogic.gdx.physics.box2d.World;
import com.badlogic.gdx.math.Vector2; // لمتجه الجاذبية
World world;
Box2DDebugRenderer debugRenderer; // لتصحيح الأخطاء
// في create()
world = new World(new Vector2(0, -9.8f), true); // (x, y) الجاذبية، true لعدم السماح بالأجسام النائمة
debugRenderer = new Box2DDebugRenderer(); // اختياري: لرؤية أشكال الفيزياء
--
- إنشاء الأجسام (Bodys) :
لكل كائن تريد أن يتفاعل مع الفيزياء، ستقوم بإنشاء Body :
Java
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.BodyDef;
import com.badlogic.gdx.physics.box2d.FixtureDef;
import com.badlogic.gdx.physics.box2d.PolygonShape; // مثال على شكل
Body playerBody;
Body groundBody;
// إنشاء تعريفات الجسم
BodyDef bodyDef = new BodyDef();
FixtureDef fixtureDef = new FixtureDef();
PolygonShape shape = new PolygonShape(); // شكل مربع للاعب
// جسم اللاعب (ديناميكي)
bodyDef.type = BodyDef.BodyType.DynamicBody;
bodyDef.position.set(10, 10); // تعيين الموضع بالوحدات الفيزيائية
playerBody = world.createBody(bodyDef);
shape.setAsBox(0.5f, 0.5f); // حجم اللاعب (نصف عرض، نصف ارتفاع)
fixtureDef.shape = shape;
fixtureDef.density = 1.0f; // الكثافة
fixtureDef.friction = 0.5f; // الاحتكاك
playerBody.createFixture(fixtureDef);
// جسم الأرضية (ثابت)
bodyDef.type = BodyDef.BodyType.StaticBody;
bodyDef.position.set(0, 0);
groundBody = world.createBody(bodyDef);
shape.setAsBox(50, 1); // أرضية كبيرة
groundBody.createFixture(fixtureDef);
shape.dispose(); // تذكر تفريغ الأشكال بعد استخدامها
--
* ملاحظة هامة : Box2D يعمل بشكل أفضل مع وحدات صغيرة (مثل الأمتار).
قد تحتاج إلى تحويل مقياس (scaling) بين وحدات العالم الفيزيائي (الأمتار)
ووحدات الرسم على الشاشة (البكسل). عادةً ما يُستخدم ثابت مثل PIXELS_PER_METER.
- تحديث عالم الفيزياء (render()) :
يجب عليك تحديث عالم الفيزياء في كل إطار :
Java
// في render()
float timeStep = 1 / 60f; // ثابت زمني للخطوة الفيزيائية (60 هرتز)
int velocityIterations = 6; // عدد تكرارات السرعة
int positionIterations = 2; // عدد تكرارات الموضع
world.step(timeStep, velocityIterations, positionIterations);
// (اختياري) رسم أشكال تصحيح الأخطاء
debugRenderer.render(world, camera.combined); // camera.combined هي مصفوفة تحويل الكاميرا
--
- التعامل مع التصادمات (ContactListener) :
للكشف عن متى تتصادم الأجسام، ستقوم بتعيين ContactListener للعالم :
Java
import com.badlogic.gdx.physics.box2d.ContactListener;
import com.badlogic.gdx.physics.box2d.Contact;
import com.badlogic.gdx.physics.box2d.Manifold;
import com.badlogic.gdx.physics.box2d.ContactImpulse;
// في create()
world.setContactListener(new ContactListener() {
@Override
public void beginContact(Contact contact) {
// يتم استدعاؤها عندما يبدأ جسمان في التصادم
Gdx.app.log("Box2D", "Collision started!");
// يمكنك الوصول إلى الأجسام المتصادمة عبر contact.getFixtureA().getBody() و contact.getFixtureB().getBody()
}
@Override
public void endContact(Contact contact) {
// يتم استدعاؤها عندما يتوقف جسمان عن التصادم
}
@Override
public void preSolve(Contact contact, Manifold oldManifold) {
// يتم استدعاؤها قبل حل التصادم، يمكنك تعديل سلوك التصادم هنا
}
@Override
public void postSolve(Contact contact, ContactImpulse impulse) {
// يتم استدعاؤها بعد حل التصادم، يمكنك الحصول على معلومات حول قوة التصادم
}
});
--
- التفريغ (Dispose) :
من الضروري تفريغ عالم Box2D و debugRenderer عند إغلاق التطبيق :
Java
// في dispose()
world.dispose();
debugRenderer.dispose();
--
الخلاصة :
- يُعد Scene2D في LibGDX إطار عمل قويًا لإنشاء واجهة مستخدم (UI) تفاعلية
باستخدام مفهوم الـ Actor والـ Stage.
- استخدم Table في Scene2D لتنظيم عناصر واجهة المستخدم بمرونة، مما يسهل
تخطيط الأزرار والنصوص.
- لا تنسَ تعيين Gdx.input.setInputProcessor(stage) لجعل الـ Stage قادرًا على
معالجة مدخلات المستخدم مثل النقر واللمس.
- الـ Skin يُحدد مظهر جميع عناصر واجهة المستخدم في Scene2D، مما يتيح تخصيصًا
سهلاً للخطوط والألوان والخلفيات.
- Sound في LibGDX مخصصة للمؤثرات الصوتية القصيرة التي تُحمل بالكامل في الذاكرة،
بينما Music تُستخدم للموسيقى الخلفية الطويلة وتُشغل تدريجيًا لتوفير الذاكرة.
- تأكد دائمًا من تفريغ موارد الصوت والموسيقى باستخدام .dispose() لمنع تسرب الذاكرة في تطبيق LibGDX .
- يمكنك التحكم في مستوى الصوت، درجة الصوت (pitch)، واتجاه الصوت (pan)
للمؤثرات الصوتية والموسيقى في LibGDX.
- يُعد التحريك في LibGDX عرضًا سريعًا لسلسلة من الصور (إطارات) لخلق وهم الحركة،
مع استخدام TextureAtlas لتحسين الأداء.
- تتبع الوقت المنقضي باستخدام Gdx.graphics.getDeltaTime() ضروري
لتشغيل التحريك بسلاسة في LibGDX.
- استخدم Animation.PlayMode للتحكم في كيفية تشغيل تحريكك، مثل التكرار المستمر
(LOOP) أو التشغيل لمرة واحدة (NORMAL).
- يُحسن TexturePacker أداء التحريك في LibGDX بدمج إطارات متعددة في
صورة واحدة كبيرة، مما يقلل من تبديل النسيج.
- تُقدم Box2D في LibGDX محاكاة فيزيائية قوية للأجسام الصلبة ثنائية الأبعاد،
بما في ذلك الجاذبية والتصادمات.
- يجب عليك تحديث عالم Box2D باستمرار في حلقة اللعبة (render())
لدفع المحاكاة الفيزيائية إلى الأمام.
- استخدم ContactListener للكشف عن التصادمات بين الأجسام الفيزيائية والتفاعل معها في Box2D.
- تذكر دائمًا تفريغ موارد World وBox2DDebugRenderer
لمنع تسرب الذاكرة في تطبيق LibGDX.
- من الضروري فهم أنواع الأجسام (Static, Kinematic, Dynamic) في
Box2D لاختيار السلوك الفيزيائي الصحيح لكائنات لعبتك.