حسابات اللاعبين
امنح لاعبي لعبتك حسابًا يحتفظون به عبر كل لعبة Aukimi، بحفظ يتبعهم من جهاز إلى آخر، على خدمة Aukimi أو على خادم خاص بك.
باختصار#
يسجِّل اللاعب دخوله مرة واحدة على play.aukimi.com ويجد ذلك الحساب في كل لعبة صُنعت بـAukimi، والحفظ (saves) مشمول. تطلب لعبتك الحساب؛ ولا تتعامل مع كلمة المرور أبدًا.
لماذا لا ترى لعبتك كلمة المرور أبدًا#
يمكن لأي شخص نشر لعبة. لعبة تطلب كلمة مرور Aukimi ستكون صفحة تصيّد (phishing) مقنعة تمامًا، ولن تغيِّر أي كمية من النوايا الحسنة من جانبك ما يمكن لمطوِّر آخر أن يشحنه.
لذا تُكتَب كلمة المرور على play.aukimi.com ولا مكان آخر. تستدعي لعبتك
PlayerLoginAsync()، وتفتح نافذة متصفح بألوان Aukimi الخاصة، يوافق اللاعب،
وتسلِّم النافذة لعبتك رمزًا (token).
يُصدَر ذلك الرمز للعبتك فقط. رمز للعبة A يُقدَّم للعبة B يُرفَض، لا يُتجاهَل، بل يُرفَض. هذه هي الفكرة: الحساب مشترك بين الألعاب، الوصول ليس كذلك. بدون هذا، ستقرأ لعبة خبيثة واحدة حفظ لاعب في كل الألعاب الأخرى.
ما تحصل عليه عن لاعب#
شيئان، لا أكثر:
| ما تحصل عليه | ما لا تحصل عليه أبدًا |
|---|---|
اسم عرض (PlayerName()) | عنوان البريد الإلكتروني |
معرِّف مبهم (opaque id)، مختلف في كل لعبة (PlayerID()) | أي شيء يعرِّفهم في مكان آخر |
يتغيّر المعرِّف من لعبة إلى أخرى عن قصد. لا يستطيع مطوِّران يقارنان قائمتي لاعبيهما معرفة أنهما ينظران إلى الشخص نفسه. كما يبقيك ذلك مُعالِجًا (processor) لا مُتحكِّمًا (controller) في تلك البيانات، وهذا تمييز قانوني لا تجميلي.
لا شيء يعطِّل حلقة اللعبة#
كل أمر يتحدث إلى الشبكة يُعيد رقم مهمة (job number)، لا نتيجة أبدًا. تستمر حلقتك في العمل بستين إطارًا في الثانية بينما الطلب في الطريق، وهذا هو السبب الكامل وراء عدم إمكانية أن تنتظر لعبة ببساطة «انتظار الخادم».
job = PlayerLoginAsync()
ثم، مرة كل إطار:
etat$ = PlatformJobState(job)
if etat$ = "done"
resultat$ = PlatformJobResult(job)
PlatformJobRelease(job)
endif
if etat$ = "error"
Print(PlatformJobError(job))
PlatformJobRelease(job)
endif
أطلِق المهمة (release) بمجرد أن تقرأها. مهمة لا تُطلقها أبدًا تُبقي نتيجتها في الذاكرة لبقية الجلسة. لا شيء ينكسر، ولا شيء يحذِّرك، ولا يظهر التسرب إلا في جلسة لعب طويلة.
اللعب بلا حساب#
لا يريد الجميع التسجيل قبل تجربة لعبة. ينشئ PlayerGuestAsync(name$) حسابًا
بلا بريد إلكتروني، محفوظًا في هذا المتصفح.
إنه حساب حقيقي: يحفظ، ويحمِّل، ويمكن المطالبة به (claimed) لاحقًا: يضيف اللاعب بريدًا إلكترونيًا وكلمة مرور، وتتبعه الحفظات الموجودة دون أن تنتقل.
المأخذ الصادق: مسح بيانات موقع المتصفح يفقد ذلك الحساب، ولا توجد طريقة
لاستعادته. هذا هو ثمن «لا تسجيل»، ويجب أن تقول لعبتك ذلك بدلًا من ترك اللاعب
يكتشفه. PlayerIsGuest() موجود بالضبط لتلك الجملة.
الحفظ#
job = PlayerSaveAsync(donnees$)
job = PlayerLoadAsync()
الحفظ هو JSON بالشكل الذي تختاره أنت، مخزَّن مقابل الزوج (هذا اللاعب، هذه اللعبة). يتبعهم إلى متصفح آخر، وجهاز آخر، ومنصة أخرى.
لحفنة من القيم، SetCloudDataVariable() وCloudDataVariable() أبسط: بلا
مهمة، وبلا JSON. تحقق من CloudDataAllowed() أولًا: يعيد 0 عندما لا يكون أي
لاعب مسجَّلًا للدخول، والكتابة رغم ذلك لن تذهب إلى أي مكان بصمت.
رسم الشاشة#
لا يرسم Aukimi أي نموذج تسجيل دخول. نموذج يُركِّبه المحرك سيبدو دائمًا كصفحة ويب أُسقطت على لعبة فن بكسل، وسيكون الجزء الوحيد من لعبتك الذي لا يمكنك إعادة تصميمه.
أنت ترسمه. القطعة الوحيدة التي يجب أن تأتي من المتصفح هي إدخال النص، لذا
يمنحك CreateEditBox() حقل إدخال حقيقيًا تضع موضعه بنفسك:
CreateEditBox(1)
EditBoxPosition(1, 20, 42)
EditBoxSize(1, 60, 8)
nom$ = EditBoxText(1)
عرض Player Account التوضيحي في قائمة عيّنات Engine شاشة كاملة تعمل في
نحو مئة سطر. افتحه، اقرأه، ثم استبدل كل Print() بعملك الفني الخاص.
قبل أن يعمل أي من هذا#
يجب إعلان اللعبة من حساب Aukimi الخاص بك، تحت My games
(aukimi.com/app/games): عنوان، والرابط الذي يمكن اللعب عنده، والروابط
المسموح للعبتك بالعودة إليها. حتى ذلك الحين يعيد PlayerAvailable() 0،
وتقول لعبة جيدة «الحسابات غير متاحة» بدلًا من أن تبدو معلَّقة.
تلك الشاشة نفسها هي حيث ترى من يلعب. اقرأ الأرقام على حقيقتها: تعدّ اللاعبين الذين سجَّلوا الدخول والجلسات التي بدأت، لا الألعاب التي لُعبت أبدًا. لعبة لا تطلب من أحد التسجيل أبدًا لا تُظهر شيئًا هناك، وتقول الشاشة ذلك بدلًا من إظهار صفر صامت.
استخدام خادم حسابات خاص بك#
كل ما سبق يتحدث إلى خدمة حسابات Aukimi. إن استضفت لعبتك على خادم خاص بك، يمكن للأوامر الاثني عشر نفسها التحدث إلى خدمتك أنت بدلًا من ذلك، ولا يتغيّر سكريبتك بسطر واحد.
في Engine: لوحة Multiplayer ← قسم Player accounts ← اختر My own server ← املأ حقلًا واحدًا، عنوان خادمك. ذلك العنوان إعداد للمشهد، لذا يمكن للعبتين استخدام خدمتين مختلفتين.
طريقتان لتسجيل دخول لاعب#
يُتَّخذ الاختيار في سكريبتك، لا في إعداد. يوجد كلا الشكلين أيًّا كان الخادم الذي تشير إليه.
النافذة (ما تعرفه بالفعل)#
job = PlayerLoginAsync()
تفتح نافذة، ويسجِّل اللاعب دخوله هناك، وتُعيد للعبتك الحق في التصرف نيابة
عنه. مع خدمة Aukimi تلك النافذة هي play.aukimi.com. مع خادمك الخاص، تلك
الصفحة من صنعك أنت.
مباشرة، باسم مستخدم وكلمة مرور#
job = PlayerLoginAsync(username$, password$)
بلا نافذة. تقرأ لعبتك ما كتبه اللاعب على شاشتك الخاصة وترسله إلى خادمك. أبسط في الكتابة، ويتيح لك الحفاظ على مظهر لعبتك من البداية إلى النهاية.
تحذير: في هذا الشكل، تسافر كلمة المرور من لعبتك إلى خادمك كما كُتبت. نتيجتان، كلتاهما حازمة: يجب أن يبدأ عنوان خادمك بـ
https://(الاختبار على جهازك الخاص وحده مُعفى)، وهذا الشكل مخصص فقط لخادم تملكه أنت.يرفضه
play.aukimi.comعن قصد. لا يريد Aukimi أبدًا أن يكون في موضع يرى فيه كلمة مرور لاعب، ولا ينبغي أن تراها لعبة لم تكتبها أنت.
الخياران مستقلان عن الإعداد: يمكن لخادم مصنوع منزليًا أن يقدِّم النافذة فقط تمامًا، ولا يقبل كلمة مرور مباشرة أبدًا.
مثال ملموس#
لعبة بشاشة «تسجيل دخول» خاصة بها: حقلا نص ترسمهما اللعبة، وزر واحد، وهذا خلف الزر.
job = PlayerLoginAsync(typedName$, typedPassword$)
DO
etat$ = PlatformJobState(job)
if etat$ = "done"
PlatformJobRelease(job)
Print("Welcome " + PlayerName())
endif
if etat$ = "error"
PlatformJobRelease(job)
Print(PlayerLastError())
endif
Sync()
LOOP
كل شيء بعد تسجيل الدخول مطابق لبقية هذه الصفحة: لا تعرف PlayerSaveAsync
وPlayerLoadAsync وPlayerName وغيرها، ولا يهمها، أي خادم أجاب.
ما يجب أن يجيبه خادمك#
أربعة عناوين مطلوبة، أيًّا كان تسجيل الدخول الذي تقدِّمه:
| الغرض منه | ما تتوقعه اللعبة |
|---|---|
| اللعب كضيف | إعادة حساب بلا بريد إلكتروني |
| من هذا اللاعب | اسم العرض، والمعرِّف، وهل هو ضيف |
| قراءة الحفظ | البيانات المحفوظة |
| كتابة الحفظ | تخزين ما ترسله اللعبة |
ثم، بحسب ما تختار تقديمه:
- شكل النافذة يحتاج صفحة تسجيل دخول على خادمك، تخبر اللعبة بمن سجَّل الدخول بمجرد الانتهاء.
- الشكل المباشر يحتاج عنوانًا واحدًا إضافيًا، يأخذ اسم مستخدم وكلمة مرور ويجيب بنعم أو لا.
العناوين الدقيقة، وشكل كل إجابة، وأكواد الأخطاء موجودة في المستودع، في
docs/engine-player-accounts.md. ذلك الملف هو العقد؛ وهذه الصفحة هي الخريطة.
ملاحظة: لا شيء من هذا يعمل في تصدير أصلي (native). تحتاج أوامر
Player*إلى خادم تتحدث إليه ومتصفح تتحدث عبره، ولا يملك البناء الأصلي أيًّا منهما.
الأوامر#
| الأمر | ما الذي يفعله |
|---|---|
PlayerAvailable() | هل خدمة الحسابات مهيَّأة لهذه اللعبة |
PlayerLoginAsync() | يفتح نافذة تسجيل الدخول. يعيد مهمة (job) |
PlayerLoginAsync(user, pass) | يسجِّل الدخول مباشرة، على خادمك الخاص فقط. يعيد مهمة |
PlayerGuestAsync() | ينشئ حسابًا بلا بريد إلكتروني. يعيد مهمة |
PlayerLoggedIn() | هل هناك من سجَّل دخوله الآن |
PlayerName() | اسم عرضه |
PlayerID() | معرِّفه المبهم، الخاص بهذه اللعبة |
PlayerIsGuest() | هل هذا حساب بلا بريد إلكتروني |
PlayerSaveAsync() | يكتب الحفظ. يعيد مهمة |
PlayerLoadAsync() | يقرأه مرة أخرى. يعيد مهمة |
PlayerRefreshAsync() | يجدِّد الرمز (token) قبل انتهاء صلاحيته |
PlayerLogout() | يسجِّل الخروج على هذا الجهاز |
PlayerLastError() | لماذا فشل آخر استدعاء |