প্লেয়ার অ্যাকাউন্ট
আপনার গেমের খেলোয়াড়দের এমন একটি অ্যাকাউন্ট দিন যা তারা প্রতিটি Aukimi গেমে ধরে রাখে, এমন সেভ সহ যা এক ডিভাইস থেকে আরেকটিতে তাদের অনুসরণ করে, Aukimi-র নিজস্ব সার্ভিসে বা আপনার নিজের একটি সার্ভারে।
একনজরে#
একজন খেলোয়াড় একবার play.aukimi.com-এ সাইন ইন করে এবং Aukimi দিয়ে তৈরি প্রতিটি গেমে সেই অ্যাকাউন্ট খুঁজে পায়, সেভসহ। আপনার গেম অ্যাকাউন্টটা চায়; এটি কখনও পাসওয়ার্ড হ্যান্ডল করে না।
আপনার গেম পাসওয়ার্ড কখনও কেন দেখে না#
যে কেউ একটি গেম পাবলিশ করতে পারে। একটি Aukimi পাসওয়ার্ড চাওয়া একটি গেম হুবহু বিশ্বাসযোগ্য একটা phishing পাতা হয়ে যেত, আর আপনার দিক থেকে যত ভালো উদ্দেশ্যই থাকুক না কেন সেটা বদলাবে না যে একজন ভিন্ন ডেভেলপার কী শিপ করতে পারত।
তাই পাসওয়ার্ডটা play.aukimi.com-এ টাইপ করা হয়, আর কোথাও নয়। আপনার গেম PlayerLoginAsync() কল করে, Aukimi-র নিজস্ব রঙে একটি ব্রাউজার উইন্ডো খোলে, খেলোয়াড় অনুমোদন করে, আর উইন্ডোটা আপনার গেমকে একটি token তুলে দেয়।
সেই token শুধু আপনার গেমের জন্যই ইস্যু করা হয়। গেম A-র জন্য একটা token গেম B-তে দিলে সেটা প্রত্যাখ্যাত হয়, উপেক্ষা নয়, প্রত্যাখ্যান। এটাই মূল কথা: অ্যাকাউন্ট গেমগুলোর মধ্যে শেয়ার করা, access নয়। এটা না থাকলে, একটা দূষিত গেম বাকি সবগুলোতে একজন খেলোয়াড়ের সেভ পড়ে ফেলতে পারত।
একজন খেলোয়াড় সম্পর্কে আপনি যা পান#
দুটো জিনিস, এর বেশি নয়:
| আপনি যা পান | আপনি যা কখনও পান না |
|---|---|
একটি display name (PlayerName()) | ইমেইল ঠিকানা |
একটি অস্বচ্ছ id, প্রতিটি গেমে আলাদা (PlayerID()) | অন্য কোথাও তাদের চেনাতে পারে এমন কিছু |
id-টা একটা গেম থেকে আরেকটাতে ইচ্ছাকৃতভাবে বদলে যায়। দুইজন ডেভেলপার তাদের প্লেয়ার তালিকা তুলনা করলে বুঝতে পারবেন না যে তারা একই ব্যক্তিকে দেখছেন। এটা আপনাকে সেই ডেটার একজন controller না রেখে processor হিসেবেও রাখে, যা একটা আইনি পার্থক্য, সাজসজ্জার নয়।
গেম লুপকে কিছুই আটকায় না#
নেটওয়ার্কের সাথে কথা বলা প্রতিটি কমান্ড একটা 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
পড়ার পর job-টা release করুন। কখনও release না করা একটা job সেশনের বাকি সময় তার result মেমোরিতে ধরে রাখে। কিছুই ভাঙে না, কিছুই আপনাকে সতর্ক করে না, আর leak-টা শুধু একটা লম্বা play সেশনেই ধরা পড়ে।
অ্যাকাউন্ট ছাড়া খেলা#
একটা গেম চেষ্টা করার আগে সবাই সাইন আপ করতে চায় না। PlayerGuestAsync(name$) কোনো ইমেইল ছাড়াই একটা অ্যাকাউন্ট তৈরি করে, এই ব্রাউজারে রাখা।
এটা একটা আসল অ্যাকাউন্ট: এটা সেভ করে, লোড করে, আর পরে এটা claim করা যায়, খেলোয়াড় একটা ইমেইল ও পাসওয়ার্ড যোগ করে, আর বিদ্যমান সেভগুলো না সরেই অনুসরণ করে।
সৎ শর্তটা হলো: ব্রাউজারের সাইট ডেটা মুছে ফেললে সেই অ্যাকাউন্টটা হারিয়ে যায়, আর সেটা ফিরিয়ে আনার কোনো উপায় নেই। এটাই "সাইন-আপ নেই"-এর মূল্য, আর খেলোয়াড়কে নিজে খুঁজে পেতে না দিয়ে আপনার গেমেরই সেটা বলা উচিত। PlayerIsGuest() ঠিক সেই বাক্যটার জন্যই আছে।
সেভ করা#
job = PlayerSaveAsync(donnees$)
job = PlayerLoadAsync()
সেভটা আপনার নিজের গঠনের JSON, (এই খেলোয়াড়, এই গেম) জুটির বিপরীতে সংরক্ষিত। এটা তাদের অন্য একটা ব্রাউজারে, অন্য একটা মেশিনে, অন্য একটা প্ল্যাটফর্মে অনুসরণ করে।
কয়েকটা মানের জন্য, SetCloudDataVariable() এবং CloudDataVariable() সহজ, কোনো job নেই, কোনো JSON নেই। আগে CloudDataAllowed() চেক করুন: কোনো খেলোয়াড় সাইন ইন করা না থাকলে এটা 0 ফেরত দেয়, আর তা সত্ত্বেও লিখলে নীরবে কোথাও পৌঁছাবে না।
স্ক্রিন আঁকা#
Aukimi কোনো sign-in ফর্ম আঁকে না। ইঞ্জিন দিয়ে তৈরি একটা ফর্ম সবসময় একটা pixel-art গেমের উপর ফেলে দেওয়া একটা ওয়েব পাতার মতো দেখাত, আর এটা হতো আপনার গেমের একমাত্র অংশ যা আপনি নিজের মতো করে সাজাতে পারতেন না।
আপনিই এটা আঁকেন। ব্রাউজার থেকেই আসতে হবে এমন একমাত্র অংশ হলো টেক্সট এন্ট্রি, তাই CreateEditBox() আপনাকে একটা আসল ইনপুট ফিল্ড দেয় যা আপনি নিজে বসান:
CreateEditBox(1)
EditBoxPosition(1, 20, 42)
EditBoxSize(1, 60, 8)
nom$ = EditBoxText(1)
Engine-এর sample তালিকায় Player Account ডেমোটা প্রায় একশো লাইনে একটা সম্পূর্ণ কার্যকর স্ক্রিন। এটা খুলুন, পড়ুন, তারপর প্রতিটা Print() নিজের আর্টওয়ার্ক দিয়ে বদলে দিন।
এসবের কিছু কাজ করার আগে#
গেমটা অবশ্যই আপনার Aukimi অ্যাকাউন্ট থেকে My games (aukimi.com/app/games)-এর অধীনে ঘোষণা করতে হবে: একটা title, যে ঠিকানায় এটা খেলা যায়, এবং যে ঠিকানাগুলোতে আপনার গেম ফিরে যেতে পারে। ততক্ষণ PlayerAvailable() 0 ফেরত দেয়, আর একটা ভালো গেম আটকে আছে বলে মনে হওয়ার বদলে বলে "accounts are not available"।
সেই একই স্ক্রিনেই আপনি দেখেন কে খেলছে। সংখ্যাগুলো যা তা হিসেবেই পড়ুন: এটা গোনে যারা সাইন ইন করেছে আর শুরু হওয়া সেশন, কখনও খেলা গেম নয়। যে গেম কাউকে সাইন ইন করতে বলেই না সেখানে কিছুই দেখায় না, আর স্ক্রিনটা একটা নীরব শূন্য দেখানোর বদলে সেটাই বলে।
আপনার নিজের অ্যাকাউন্ট সার্ভার ব্যবহার করা#
উপরের সবকিছু Aukimi-র অ্যাকাউন্ট সার্ভিসের সাথে কথা বলে। আপনি নিজের সার্ভারে আপনার গেম হোস্ট করলে, একই বারোটা কমান্ড এর বদলে আপনার সার্ভিসের সাথে কথা বলতে পারে, আর আপনার স্ক্রিপ্ট এক লাইনও বদলায় না।
Engine-এ: প্যানেল Multiplayer → সেকশন Player accounts → My own server বেছে নিন → একটা ফিল্ড পূরণ করুন, আপনার সার্ভারের ঠিকানা। সেই ঠিকানাটা সিনের একটা সেটিং, তাই দুটো গেম দুটো ভিন্ন সার্ভিস ব্যবহার করতে পারে।
একজন খেলোয়াড়কে সাইন ইন করানোর দুটো উপায়#
সিদ্ধান্তটা আপনার স্ক্রিপ্টে নেওয়া হয়, কোনো সেটিংয়ে নয়। আপনি যেকোনো সার্ভারই লক্ষ্য করুন না কেন দুটো ফর্মই থাকে।
উইন্ডো (যা আপনি আগে থেকেই জানেন)#
job = PlayerLoginAsync()
একটা উইন্ডো খোলে, খেলোয়াড় সেখানে সাইন ইন করে, আর এটা আপনার গেমকে তাদের হয়ে কাজ করার অধিকার ফিরিয়ে দেয়। Aukimi-র সার্ভিসের সাথে সেই উইন্ডোটা play.aukimi.com। আপনার নিজের সার্ভারের সাথে, সেই পাতাটা আপনারই তৈরি করার।
সরাসরি, একটা username ও পাসওয়ার্ড দিয়ে#
job = PlayerLoginAsync(username$, password$)
কোনো উইন্ডো নেই। আপনার গেম খেলোয়াড় আপনার নিজের স্ক্রিনে যা টাইপ করেছে তা পড়ে এবং আপনার সার্ভারে পাঠায়। লেখা সহজ, আর এটা আপনাকে শুরু থেকে শেষ পর্যন্ত আপনার গেমের লুকটা ধরে রাখতে দেয়।
সতর্কতা: এই ফর্মে, পাসওয়ার্ড আপনার গেম থেকে আপনার সার্ভারে যেভাবে টাইপ করা হয়েছে সেভাবেই যায়। দুটো ফল, দুটোই কঠোর: আপনার সার্ভারের ঠিকানা অবশ্যই
https://দিয়ে শুরু হতে হবে (শুধু আপনার নিজের মেশিনে একটা টেস্ট এর ব্যতিক্রম), আর এই ফর্মটা শুধু আপনার নিজের মালিকানার একটা সার্ভারের জন্যই বানানো।
play.aukimi.comইচ্ছাকৃতভাবে এটা প্রত্যাখ্যান করে। Aukimi কখনও এমন অবস্থানে থাকতে চায় না যেখানে একজন খেলোয়াড়ের পাসওয়ার্ড দেখা যায়, আর আপনি যে গেম লেখেননি সেটাও তা চাওয়া উচিত না।
দুটো পছন্দ সেটিং থেকে স্বাধীন: একটা ঘরে-বানানো সার্ভার শুধু উইন্ডোই দিতে পারে, আর সরাসরি কোনো পাসওয়ার্ড কখনও গ্রহণ না করতেও পারে।
একটা বাস্তব উদাহরণ#
নিজস্ব একটা "Sign in" স্ক্রিন থাকা একটা গেম: গেমের আঁকা দুটো টেক্সট ফিল্ড, একটা বোতাম, আর বোতামের পেছনে এটা।
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 এবং বাকিরা জানে না, আর পরোয়াও করে না, কোন সার্ভার উত্তর দিয়েছে।
আপনার সার্ভারকে যা জবাব দিতে হবে#
আপনি যেই sign-in দিন না কেন, চারটা ঠিকানা লাগবে:
| কীসের জন্য | গেম যা আশা করে |
|---|---|
| Guest হিসেবে খেলা | কোনো ইমেইল ছাড়া একটা অ্যাকাউন্ট ফিরিয়ে দিন |
| এই খেলোয়াড় কে | display name, id, তারা guest কিনা |
| সেভ পড়া | সংরক্ষিত ডেটা |
| সেভ লেখা | গেম যা পাঠায় তা সংরক্ষণ করুন |
তারপর, আপনি কী দিতে চান তার উপর নির্ভর করে:
- উইন্ডো ফর্মের জন্য আপনার সার্ভারে একটা sign-in পাতা দরকার, যা শেষ হয়ে গেলে গেমকে বলে কে সাইন ইন করেছে।
- সরাসরি ফর্মের আরেকটা ঠিকানা দরকার, যা একটা username ও পাসওয়ার্ড নেয় এবং হ্যাঁ বা না জবাব দেয়।
সঠিক ঠিকানা, প্রতিটা উত্তরের গঠন এবং error কোডগুলো রিপোজিটরিতে আছে, docs/engine-player-accounts.md-এ। সেই ফাইলটা হলো contract; এই পাতাটা ম্যাপ।
নোট: এর কিছুই একটা নেটিভ এক্সপোর্টে কাজ করে না।
Player*কমান্ডগুলোর কথা বলার জন্য একটা সার্ভার এবং কথা বলার জন্য একটা ব্রাউজার দরকার, আর একটা নেটিভ build-এ কোনোটাই নেই।
কমান্ডগুলো#
| কমান্ড | এটা কী করে |
|---|---|
PlayerAvailable() | এই গেমের জন্য অ্যাকাউন্ট সার্ভিস কনফিগার করা আছে কিনা |
PlayerLoginAsync() | sign-in উইন্ডো খোলে। একটা job ফেরত দেয় |
PlayerLoginAsync(user, pass) | সরাসরি সাইন ইন করে, শুধু আপনার নিজের সার্ভারে। একটা job ফেরত দেয় |
PlayerGuestAsync() | কোনো ইমেইল ছাড়া একটা অ্যাকাউন্ট তৈরি করে। একটা job ফেরত দেয় |
PlayerLoggedIn() | এই মুহূর্তে কেউ সাইন ইন করা আছে কিনা |
PlayerName() | তাদের display name |
PlayerID() | তাদের অস্বচ্ছ id, এই গেমের জন্য নির্দিষ্ট |
PlayerIsGuest() | এটা কি ইমেইল ছাড়া একটা অ্যাকাউন্ট |
PlayerSaveAsync() | সেভ লেখে। একটা job ফেরত দেয় |
PlayerLoadAsync() | সেটা আবার পড়ে। একটা job ফেরত দেয় |
PlayerRefreshAsync() | মেয়াদ শেষ হওয়ার আগে token রিনিউ করে |
PlayerLogout() | এই ডিভাইসে সাইন আউট করে |
PlayerLastError() | শেষ কলটা কেন ব্যর্থ হয়েছিল |