ألعاب ثلاثية الأبعاد على Instagram Javascript ، أو مسار رحلة النقانق



بعد المقالة الأولى حول ألعاب البرمجة في الأقنعة على Instagram ، طلب مني أحد العملاء إنشاء لعبة على Instagram من أجل مطعم البيتزا الخاص به. تم التخطيط لاستخدام هذه اللعبة لأغراض ترويج الأعمال. بالطبع ، أفهم أنه ، من خلال عدد المشاهدات للمقالة الأولى ، فإن موضوع Instagram ليس مثيرًا للاهتمام بشكل خاص لمجتمع Habr. على ما يبدو ، لا تزال تعتبر هذه الخدمة نوعًا من الترفيه التافه للشقراوات ، ومقال عنها مناسب فقط كقراءة يوم الجمعة لتناول كوكتيل مسائي. ومع ذلك ، اليوم ، الجمعة فقط. احصل على كوكتيل. ولا تنس دعوة الشقراوات. ومع ذلك ، فمن المحتمل أن تصل التكنولوجيا في المستقبل إلى مثل هذه المرتفعات التي سنبدأ في اللعب على Instagram في GTA-5. أو 10.

ستناقش هذه المقالة مباراتين في وقت واحد. لقد صنعت واحدة للطلب ، ثم الثانية لنفسي. مع كل لعبة جديدة ، واجهتني مهام جديدة ، والبحث عن حلول جعلني أتعرف على الفروق الدقيقة الجديدة في التطوير. ربما تكون هذه القصة مفيدة للمطورين الآخرين لعناصر الواقع المعزز. الألعاب مكتوبة بلغة Javascript فقط دون استخدام أي مكتبات إضافية. لعرض الرسومات ثلاثية الأبعاد ، يتم استخدام أدوات Instagram المدمجة.

لعبة 1. بيتزا


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

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

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

تم تقديم الجدول الزمني للعبة من قبل العميل. كانت هناك بعض الصعوبات في النماذج ثلاثية الأبعاد. على وجه الخصوص ، عند استخدام محرك الرسوم المتحركة المدمج Spark AR Studio ، اتضح أنه إذا تم تحجيم الكائن المتحرك ، فسيتم تحديد إحداثياته ​​بشكل غير صحيح في اللعبة. لكن هذا التأثير المزعج لا يلاحظ إذا لم تقم بقياس الكائن بالكامل تمامًا ، ولكن كل شبكة من شبكته بشكل منفصل. كان علي أن أترك مقياس البيتزا 1: 1 ، وأن أصف معاملًا معينًا لكل عنصر يصنعه. كانت هناك أيضًا مشكلات في تنسيق FBX حيث تحتاج إلى تصدير النماذج للعبة Instagram. أرسل مصمم الجرافيك نماذج ذات مواد مدمجة ، علاوة على ذلك ، تم وضعها على طول مسار نسبي. شاهدهم المحرر ثلاثي الأبعاد ، لكن Spark AR لم يرهم. كان علي إعادة حزم النماذج بحيث تكمن ملفات النسيج بشكل منفصل عن النماذج وعلى طول مسار واحد معهم.

وهناك مشكلة صغيرة أخرى واجهتها هي أن كائن api Saprk AR المسؤول عن عرض النص على الشاشة رفض قبول القيمة كقيمة - نتيجة لعبة ، على سبيل المثال. لفترة طويلة لم أتمكن من فهم سبب عدم عرض أي شيء. ما الخطأ الذي افعله؟ اتضح أنك تحتاج أولاً إلى تحويل الأرقام إلى سلاسل (.toString ()). هذا غني عن القول بلغات أخرى ، ولكنه غير نمطي بالنسبة لجافا سكريبت ، التي فعلت ذلك دائمًا.

الرسوم المتحركة البسيطة


كان أحد الأشياء الجديدة بالنسبة لي في هذه اللعبة هو برمجة الرسوم المتحركة للكائنات ثلاثية الأبعاد. (في لعبة Instagram السابقة "Tic-Tac-Toe" ، لم يكن هناك رسوم متحركة على الإطلاق.) محرك الرسوم المتحركة في Spark AR Studio محدد للغاية. يستغرق بعض معلمات الإدخال ، ثم يربط المتغير بشكل تفاعلي مع الكائن الذي تريد تغيير شيء فيه. على سبيل المثال ، سيؤدي ذلك إلى تغيير إحداثيات y (startValue ، endValue - قيمها الأولية والنهائية) لبعض الكائنات ثلاثية الأبعاد بمرور الوقت t:

var driverParameters = {
    durationMilliseconds: t,
    loopCount: Infinity,
    mirror: false
};
var driver = Animation.timeDriver(driverParameters);
var sampler = Animation.samplers.linear(startValue, endValue);
sceneObject.transform.y = Animation.animate(driver, sampler);
driver.start();

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

FaceTracking.face(0).mouth.openness.monitor().subscribe(function(event) {
    if (event.newValue > 0.2) {
        ...
    };
});

FaceTracking.face(1).mouth.openness.monitor().subscribe(function(event) {
    if (event.newValue > 0.2) {
        ...
    };
});

النقطة الرئيسية في هذه اللعبة هي التقاط المكونات الطائرة بفمك ، بعبارة أخرى ، العثور على جسم طائر في منطقة صغيرة معينة حول مركز فم الشخص. في البداية ، لم يكن هذا الحساب يريد أن يتم بشكل صحيح بالنسبة لي ، ولكن تم العثور على حل للمشكلة بعد إدخال كائن مخفي مرتبط بإحداثيات الفم ، وعملت. لسبب ما ، لم ترجع الإحداثيات المباشرة للفم. هنا cameraTransform.applyTo هو تقليل إحداثيات نقاط الوجه إلى الإحداثيات في العالم ثلاثي الأبعاد (الطريقة مأخوذة من التوثيق).

move: function() {
    //   (   )

    //    
    var object = Scene.root.find('pizzafly');
    var olast = {
        x: object.transform.x.pinLastValue(),
        y: object.transform.y.pinLastValue(),
        z: object.transform.z.pinLastValue()
    };

    //   ,       
    var objectHidden = Scene.root.find('nullObject');
    objectHidden.transform.x = FaceTracking.face(face).cameraTransform.applyTo(FaceTracking.face(face).mouth.center).x;
    objectHidden.transform.y = FaceTracking.face(face).cameraTransform.applyTo(FaceTracking.face(face).mouth.center).y;
    objectHidden.transform.z = FaceTracking.face(face).cameraTransform.applyTo(FaceTracking.face(face).mouth.center).z;

    //   
    var mouth = {
        x: objectHidden.transform.x.pinLastValue(),
        y: objectHidden.transform.y.pinLastValue(),
        z: objectHidden.transform.z.pinLastValue()
    };

    //    
    var d = {
        x: Math.abs(olast.x - mouth.x),
        y: Math.abs(olast.y - mouth.y),
        z: Math.abs(olast.z - mouth.z)
    };

    //  
    if ((d.x > 0.03) || (d.y > 0.03) || (d.z > 0.03)) {
        // 
        ...
    } else {
        // 
        ...
    };

},

بعد ذلك ، أدركت أنه يجب عليك إزالة فحص العمق (إحداثيات z) ، لأنه في هذه اللعبة بالذات من الصعب بصريًا تقدير العمق. أي أنه أصبح من الممكن الآن الإمساك بالعنصر عن طريق فتح فمك في أي وقت أثناء الرحلة. الشيء الرئيسي هو مزيج من x و y.


أخيرًا ، كانت آخر مشكلة واجهتها أثناء كتابة هذه اللعبة هي تحديد حجم البنية النهائية بـ 4 ميغابايت. علاوة على ذلك ، وفقًا لتوصية Facebook ، من أجل عرض اللعبة على أكبر عدد ممكن من الأجهزة المحمولة ، من المستحسن احتواء حتى 2 ميغابايت. ومصممو النماذج ثلاثية الأبعاد هم أشخاص مبدعون ويريدون صنع نماذج ثقيلة بشبكة كثيفة وقوام ضخم ، لا يهتمون بنا تمامًا ، أو المبرمجين ، أو بالأحرى ، حول الأداء النهائي في الألعاب. لقد قمت أيضًا بطريقة ما بتقليل الحجم في الحجم وضغطه بتنسيق jpg (بدلاً من png) ، ولكن كان يجب إرسال نموذج البيتزا نفسه للمراجعة (retopology). ونتيجة لذلك ، مع ذلك ، كان من الممكن ملاءمته مع حجم 2 ميجا بايت مع جميع النماذج والقوام والنص. واستمرت اللعبة باعتدال.

وبعد مرور بعض الوقت عادت بصياغة أن "التأثير يحتوي على نص ثابت للغاية". اضطررت إلى إزالة أرقام العد التنازلي وبدلاً من ذلك ، قمت بتعيين الرسوم المتحركة إلى السهم على ساعة الإيقاف ، والتي بدأت الآن في حساب الوقت بدلاً منها. بعد ذلك ، تمت الموافقة على اللعبة.

لعبة 2. عن الفراشة (ButterFlap)



الآن سأتحدث عن إنشاء لعبة ثانية. لا يمكنني المساعدة في صنع الألعاب ، هذه هوايتي. قبل أيام قليلة من 8 مارس ، قررت إنشاء لعبة Instagram لهذه العطلة. شيء عن الزهور والفراشات والحلويات. لكني لم أستطع التفكير في جوهر اللعبة. تدور الفكرة في رأسي. ربما ستجمع الفراشة الحلوى وتضعها في سلة؟ أو ربما تلتقط الفراشات الحلوى في الهواء وترميها ، وسيحتاج اللاعب إلى الإمساك بها بفمه؟ بشكل عام ، وقعت في فوضى لبضعة أيام ، وبعد أن لم أجد حلاً ، أدركت أنه بحلول 8 مارس ، لن يكون لدى لعبتي الوقت الكافي للاعتدال ، حيث تستغرق الأخيرة 3-4 أيام. لقد أردت بالفعل مغادرة هذا المشروع ، عندما جاءت الفكرة من تلقاء نفسها فجأة ، فجأة. لم أعد أربط اللعبة بيوم المرأة ، والآن كانت مجرد لعبة.

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

نعم ، كل هذا يبدو جيدًا. أردت بشكل مباشر أن ألعب بنفسي. لكنني تذكرت أنني لست فنانًا على الإطلاق. ومع ذلك ، أنا قادر على عمل نماذج ثلاثية الأبعاد ، وهذا أسهل من الرسم. لذلك ، تقرر أن يكون هناك سكرولر ثلاثي الأبعاد.

الفنون التصويرية


لقد عثرت على نماذج خالية من حقوق الملكية عبر الإنترنت اضطررت إلى تحسينها. قام بسحب القوام إلى تلك التي كانت بدون مواد ، بعد أن وضع هذا الأخير في أطلس نسيج: القطع الأثرية في شكل "سلالم" مرئية على نماذج غير محكم ، ويمكن تعيين التنعيم للقوام في اللعبة ، مما يجعل مظهر الأشياء أكثر عرضًا وليس مسطحًا. تلك النماذج التي تحتوي على مواد لها أيضًا عيوبها التي يجب إصلاحها. أولاً ، لم يكن حجم القوام مضاعفًا لاثنين في السلطة. أي ، على سبيل المثال ، يمكن أن تكون مساوية لـ 1200x1200. اضطررت للضغط على 1024x1024. يمكن لمعالجات الفيديو إما قياس أو ملء الزخارف غير المناسبة بمساحة فارغة ، أي تلك التي ليست 1024x1024 ، 512x512 ، 256x256 ، إلخ. على أي حال ، مثل هذه الإجراءات هي حمل إضافي أثناء عمل اللعبة واستهلاك الذاكرة بلا معنى ،لذلك ، من الأفضل تحضير الصور الصحيحة أولاً يدويًا. تم حفظ الموقف أيضًا من خلال حقيقة أنني وزعت جميع القوام بواسطة أطالس الملمس ، لذلك ، على سبيل المثال ، إذا كان حجم الملمس الأصلي 400 × 200 بكسل ، فيمكنني فقط وضعه في الأطلس 1024x1024 ، كما هو ، بجانب المماثلات الأخرى المماثلة. بعد ذلك ، من الضروري بطبيعة الحال توسيع نطاق الأشعة فوق البنفسجية أيضًا ، ولكن يتم ذلك في بضع ثوانٍ. لا يزال يأتي عبر أشكال مختلفة من النماذج حيث لسبب ما تم ربط المواد على طول مسار مطلق مثل "C: \ Work \ Vasya \ Map.jpg". حسنًا ، لا يوجد مثل هذا المجلد على جهاز الكمبيوتر الخاص بي! كان علي تحديد المسارات للقوام يدويًا ... ولكن لماذا توصلت إلى فكرة أنه ينبغي علي الاحتفاظ بجميع مشاريعي على محرك الأقراص "C:"!؟ أوه ، هؤلاء المصممين ، الفنانين الأحرار ... بالمناسبة ، بهذه الطريقة ، بأسماء المجلدات في المسارات اليسرى بشكل عشوائي ، يمكنك معرفة المزيد عن هوية المصمم عن غير قصدعلى سبيل المثال ، اسمه. تشرفت بمقابلتك!

كان أصعب شيء هو العثور على نموذج فراشة مناسب مع الرسوم المتحركة. يستخدم Spark AR الرسوم المتحركة التي يمكن تصديرها من أي محرر ثلاثي الأبعاد إلى تنسيق FBX. كانت أجنحة زوج من نماذج الفراشات التي قمت بتنزيلها معكوسة بشكل غريب نوعًا ما - تم تصميم جناح واحد ، وتم عكس الجناح الثاني بطريقة لم أفهمها ، وبالتالي ، ظهر اثنان منها. مع هذا النهج في النمذجة ، في النهاية ، في اللعبة ، لم يرغب أحد الأجنحة (المنسوخة) في تلقي الضوء من المصدر وبقي دائمًا خافتًا. لم أجازف بتغيير النموذج بشكل جذري ، لأن الرسوم المتحركة كانت ستطير. وفي الرسوم المتحركة ، أنا أكبر مستجد من النمذجة ثلاثية الأبعاد. ربما كانت المشكلة شيئًا آخر: حاولت ، على سبيل المثال ، توسيع المعايير الطبيعية ، لكن ذلك لم يساعد. باختصار ، النماذج ثلاثية الأبعاد المجانية هي ألم. ونتيجة لذلك ، بعد غسل كل مساء ،عن طريق التجربة والخطأ ، وجدت نموذجًا مناسبًا ، والذي بدأ بعد بعض الضبط الدقيق للملف يبدو مرضيًا. شيء لم يعجبني في ذلك ، لكنه كان أشياء صغيرة: لقد أعدت تصميم البنية ، وغيرت الهندسة في بعض الأماكن ، وعدلت معلمات المواد. وأخيرا ، أقلعت الفراشة. مرحى. ولكن الآن أنا منهكة. لذلك قررت الذهاب للنوم والاستمرار في اليوم التالي. نعم ، قضيت 7-8 أيام على إنشاء اللعبة. لكنه كان عملًا مترفًا في المساء ، وقراءة الوثائق والمقالات والعثور على إجابات للأسئلة.لذلك قررت الذهاب للنوم والاستمرار في اليوم التالي. نعم ، قضيت 7-8 أيام على إنشاء اللعبة. لكنه كان عملًا مترفًا في المساء ، وقراءة الوثائق والمقالات والعثور على إجابات للأسئلة.لذلك قررت الذهاب للنوم والاستمرار في اليوم التالي. نعم ، قضيت 7-8 أيام على إنشاء اللعبة. لكنه كان عملًا مترفًا في المساء ، وقراءة الوثائق والمقالات والعثور على إجابات للأسئلة.


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

بشكل عام ، من المستحسن تقليل عدد الأنسجة ، بالإضافة إلى حجمها بالبايت. إنها القوام الذي يخلق الجزء الأكبر من اللعبة. لقد اعتدت على الأنسجة التي تكون فيها الشفافية مطلوبة ، ويتم وضعها في أطلس نسيج واحد - بتنسيق png 32 بت ، والأنسجة التي لا تستخدم قناة ألفا ، وتعبئتها في أطلس آخر ، وحفظها بتنسيق jpg. يمكن تقليص Jpg أقوى من png. ذهبت عناصر العشب وواجهة المستخدم التي تحتاج إلى شفافية إلى أطلس png ، وكل شيء آخر إلى jpg.

الحجم الكلي. في المجموع ، حصلت على 4 أنواع من النباتات ، صخرة ، فراشة ، والتي سيسيطر عليها اللاعب ، وسحابة. استغرق تركيب جميع هذه النماذج في شكل مضغوط 2 أطالس 1024x1024 jpg و png بحجم إجمالي 500 كيلوبايت. استغرق النماذج نفسها حوالي 200 كيلو بايت. بالإضافة إلى برنامج نصي. الأصوات - 31 كيلو بايت. الإجمالي: حوالي 1 ميجا بايت. باللون الأخضر ، يتم عرض حجم البناء فقط (وهذا ما يجب أن يتناسب مع 2 ميغابايت).


فجأة واجهت مشكلة غير متوقعة تمامًا. لقد حذفت العديد من النماذج غير المستخدمة ، ولكن ليس من خلال Spark AR Studio ، ولكن من نظام الملفات. بعد ذلك ، عند تجميع المبنى ، وجدت أنهم اختفوا من مشهد Spark AR Studio ، لكنهم دخلوا إلى التجميع. وإزالة المشروع من الموارد غير المستخدمة أمر مستحيل. لا تؤدي الروابط إليها من "ملخص الأصول" إلى أي مكان. يبدو أن هذا عيب في Spark AR Studio. كان علي إعادة إنشاء المشروع مرة أخرى ، وإضافة جميع الموارد اللازمة هناك منذ البداية.

مشهد


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

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

lv:[
    {n:'flower1', x:8.0, y:-6.5},
    {n:'cloud', x:10.0, y:0.1},
    {n:'rain', x:10.0, y:6.6},
    {n:'flower1', x:14, y:-2.5},
    {n:'diamond_red', x:20, y:2.0},
	...
],

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

colld:{
    'flower1': {dx:0, dy:5, w:2.3, h:1.4, col:true},
    'diamond_green': {dx:0, dy:0, w:1, h:1, col:false, v:1, destroy:true},
    'diamond_red':{dx:0, dy:0, w:1, h:1, col:false, v:-2},

    ...
},

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

مع الأخذ في الاعتبار مشكلة المقياس التي واجهتها عند تطوير اللعبة الأولى ، أعدت تعيين مقياس جميع النماذج في محرر 3D. وهكذا ، في نماذج Saprk AR يتم تحميلها على الفور بأحجام عادية. صحيح ، في النص على أي حال ، لا يمكن الاستغناء عن "الرقم السحري" ، المعامل العالمي ، الكود العالمي ، الذي يحتوي على جوهر الكون. عالم افتراضي بالطبع. وأنا على استعداد لكشف هذا الرقم لك. استخدمها أيها الناس! أنا لا أمانع! هذا الرقم هو 0.023423. باختصار ، على الرغم من إعادة تعيين جميع المقاييس ، تبين أن متر واحد في محرر 3D يساوي هذا الرقم في Spark AR Studio. على الأرجح ، أنا ، مع ذلك ، لا أفهم تمامًا جميع تعقيدات العمل مع الرسومات ثلاثية الأبعاد ، وبالتالي المعامل. يتم ضرب إحداثيات (لكن ليس حجم) جميع الكائنات التي تم تصديرها من المحرر بها ، تخمينها.أين ضبط حجم المشهد في Spark AR ، لم أجد.

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

الرسوم المتحركة المعقدة


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

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



تبدو وظيفة تحديث إحداثيات الكائنات كما يلي:

oCoordSet: function(v) {
    //  :    
    //   
    var camx = -ap.oWorld.transform.x.pinLastValue();
    //  
    var x1 = camx - ap.game.scro.w2;
    var x2 = camx + ap.game.scro.w2;
    //   
    for (var i = 0; i < ap.d.lv.length; i++) {
        //    
        if ((ap.d.lv[i].x >= x1) & (ap.d.lv[i].x <= x2)) {
            //   
            ap.d.lv[i].o = Scene.root.find(ap.d.lv[i].n);
            //  -  
            ap.d.lv[i].o.transform.y = ap.d.lv[i].y;
            if ((ap.d.lv[i].n == 'cloud') || (ap.d.lv[i].n == 'rain')) {
                //    ,
                //  
                //   2.3,
                //     
                ap.d.lv[i].o.transform.x = ap.d.lv[i].x - (x2 - ap.d.lv[i].x) * 2.3 + 0.2;
            } else {
                //    ,
                //      
                ap.d.lv[i].o.transform.x = ap.d.lv[i].x;
            };
        };
    };
    //        
    //     
    if (camx > ap.game.grassPosLast) {
        ap.game.grassPosLast += ap.game.grassd;
        ap.game.grassi = 1 - ap.game.grassi;
        ap[ap.game.grassNm[ap.game.grassi]].transform.x = ap.game.grassPosLast;
    };
},

الشخصية الرئيسية


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

FaceTracking.face(0).cameraTransform.rotationX.monitor().subscribe(function(event) {
    var v = event.newValue;
    //  
    var scrH2 = ap.game.scr.h2;
    //
    var p = -v * 0.5;
    if (p < -scrH2) {
        p = -scrH2;
    } else if (p > scrH2) {
        p = scrH2;
    };
    //  
    var d = 0.006;
    //  
    var cur = ap.oPers.transform.y.pinLastValue();
    if (p < cur) {
        cur -= d;
        if (cur < p) {cur = p;};
    } else {
        cur += d;
        if (cur > p) {cur = p;};
    };
    //    ()
    ap.oPointer1.transform.y = p;
    //   ,
    // ,    
    ap.game.pers.dy + = cur - ap.game.pers.y;
});

وبالمثل للأفقي. هناك فقط من الضروري الاشتراك في الحدث ليس من الميل ، ولكن من دوران الرأس (الدوران Y) ، وبدلاً من ارتفاع الشاشة ، فكر في عرضه.

المصادمات


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

نظرًا لأنني لا أعرف كيف تعمل تكرارات معالج الوجه في Spark AR - سواء تم استدعاؤها بتردد معين أو تم ضبطها على أقصى تردد ساعة ممكن ، وفي محرك الرسوم المتحركة الخاص بي يمكنني التحكم في هذه المعلمة ، قررت أنني سأحدد التصادمات في وظيفتي حركة العالم ، والتي يتم استدعاؤها بتردد يحدده لي (60 لقطة في الثانية). في أحداث معالجة الوجه ، سنقوم فقط "بتجميع" الحركة.

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

setPersPos: function(camx, camdx) {
    //   
    //       (camx,camdx)

    //     
    var persx = ap.game.pers.x;
    var persy = ap.game.pers.y;
    var dx = ap.game.pers.dx;
    var dy = ap.game.pers.dy;

    // ,     
    var col = ap.collisionDetect(
        {x: persx, y: persy, dx: dx, dy: dy},
        {x: camx, dx: camdx, y: 0}
    );

    if (col.f == true) {
        // 

        if (col.colx == true) {
            //   
            //    ,
            //   
            //(    ,    )
            ap.game.pers.x = col.x;
        } else {
            //    ,
            //   
            ap.game.pers.x = persx + dx;
        };

        if (col.coly == true) {
            // -  
            ap.game.pers.y = col.y;
        } else {
            ap.game.pers.y = persy + dy;
        };

    } else {
        //  ,   
        ap.game.pers.x = persx + dx;
        ap.game.pers.y = persy + dy;
    };

    // 
    ap.game.pers.dx = 0;
    ap.game.pers.dy = 0;

    //     
    ap.oPers.transform.x = ap.game.pers.x;
    ap.oPers.transform.y = ap.game.pers.y;
},

وظيفة الكشف عن التصادم نفسها:

collisionDetect: function(opers, ow) {
    // , opers -   , ow -  

    var res = {f: false, colx: false, coly: false, x: 0, y: 0};

    var ocoll, x, y, w, h, persx, persy, persx0, persy0, od, colx1, coly1, colx2, coly2;
    var collw = false, collh = false;

    //  
    //(  ,  "" )
    persx0 = opers.x + ow.x - ow.dx;
    persy0 = opers.y + ow.y;

    //       
    persx = persx0 + opers.dx;
    persy = persy0 + opers.dy;

    //  
    for (var i = 0; i < ap.d.lv.length; i++) {
        od = ap.d.lv[i]; //obj data

        //    (   ),
        //     
        //        
        if (typeof ap.d.colld[od.n] !== "undefined") {

            //       
            ocoll = ap.d.colld[od.n];
            colx1 = od.x + ocoll.x1;
            colx2 = od.x + ocoll.x2;
            coly1 = od.y + ocoll.y1;
            coly2 = od.y + ocoll.y2;

            if ((persx < colx1) || (persx > colx2) || (persy < coly1) || (persy > coly2)) {} else {
                //   
                res.f = true;

                //        ,
                //,    
                if ((persx0 < colx1) || (persx0 > colx2)) {
                    collw = true;
                };
                //        ,
                //,    
                if ((persy0 < coly1) || (persy0 > coly2)) {
                    collh = true;
                };

            };
        };

    };

    //   

    //  ,     ,
    //  
    if (collw == true) {
        res.colx = true;
        res.x = persx0 - ow.x;
    } else {
        res.x = opers.x;
    };

    // -  
    if (collh == true) {
        res.coly = true;
        res.y = persy0 + ow.y;
    } else {
        res.y = opers.y;
    };

    return res;
},

تمطر


لقد استخدمت محرك الجسيمات القياسي Spark AR Studio لتحريك المطر. لا يوجد شيء خاص هنا. نضيف كائن Emtter إلى المشهد ، ولا ننسى أن نطلبه Emitter -> Space -> Local (بدلاً من World). هذا للتأكد من أن القطرات لا تتأخر ديناميكيًا خلف السحب أثناء الحركة ، ولكنها دائمًا ما تسقط مباشرة. هذه الطريقة أكثر ملاءمة لتسهيل تحديد لحظة سقوط الفراشة تحت المطر - لا حاجة إلى تصحيح الارتفاع. بالنسبة للقطرات نفسها ، قمت بإعداد الملمس المناسب. حسنًا ، وبالطبع ، يتحرك جسم المطر مع جسم السحابة. ثم أضفت حالة إصابة السحابة إلى رمز معالجة التصادم. وإذا لم يكن هناك عائق فوق اللاعب في هذه اللحظة ، فإن الفراشة تسقط وتنتهي اللعبة.

الاعتدال


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

ملخص


لا يعني أن لعبتي كانت مثيرة بشكل خاص. ربما تفتقر إلى الديناميكيات والميكانيكا الإضافية. سوف أفرج عن تحديث. لكنني أدركت أن صنع الألعاب على Instagram أمر مثير للاهتمام. هذا نوع من التحدي. لقد استمتعت حقًا بعملية البرمجة وحل جميع أنواع المهام في أضيق الحدود من حيث الأدوات ومقدار الذاكرة المخصصة. أتذكر أن أحدهم قال إن 640 كيلوبايت كافية للجميع. حاول الآن احتواء لعبة ثلاثية الأبعاد بحجم 2 ميجابايت. ربما لن أجادل أنها كافية للجميع ... ولكن جربها!


في الختام ، أود أن أضع في قائمة واحدة جميع اللحظات غير الواضحة التي واجهتها. ربما يكون هذا مفيدًا لشخص ما باعتباره ورقة غش عند إنشاء ألعاب لـ Instagram.

  • مقياس. اضبط مقياس جميع النماذج ثلاثية الأبعاد في محرر 3D بحيث يصبح على الفور 1: 1 على المسرح في اللعبة.
  • . . , .
  • . FBX, . -. , .
  • . JPG , , , -, JPG, PNG.
  • . , , . Spark AR , . , , , 3D-.
  • 3D , : , . . .
  • , , . javascript .
  • في سياق Spark AR ، لا يوجد كائن نافذة وجميع أنواع الكائنات الخاصة بالمتصفح مثل webkitRequestAnimationFrame و mozRequestAnimationFrame وما إلى ذلك. هذا مصير عند البرمجة في الرسوم المتحركة جافا سكريبت.

All Articles