সেরা রোগলাইকগুলো আপনাকে শুধু মেরে ফেলে না। তারা আপনাকে মরতে আগ্রহী করে তোলে।

শুনতে অদ্ভুত মনে হতে পারে, কিন্তু যারা মাঝরাতে একটি রান (run) হেরে গিয়ে ১২:০৩ মিনিটে আরেকটি শুরু করেছেন, তারা এই অনুভূতিটি বোঝেন। মৃত্যু যন্ত্রণাদায়ক। আপনার স্বাস্থ্য শূন্যে নেমে আসে। স্ক্রিন ব্যর্থতায় ভরে ওঠে। তবুও আপনার আঙুল ইতিমধ্যেই প্লে বাটনের ওপর স্থির হয়ে আছে। গত বিশ মিনিটের কিছু একটা এতটাই গুরুত্বপূর্ণ ছিল যে আপনি গেমটি যেখানে শেষ হয়েছে সেখানেই ছেড়ে যেতে পারছেন না।

এটি হলো "one more run" লুপ, এবং এটি কোনো আকস্মিক ঘটনা নয়। এটি পরিকল্পিত। আপনি যদি একটি রোগলাইক বা পারমডেথ (permadeath) যুক্ত কোনো গেম তৈরি করেন, তবে আপনার পুরো কাজ হলো দুটি বিপরীতমুখী শক্তির মধ্যে ভারসাম্য বজায় রাখা। খেলোয়াড়কে যথেষ্ট পরিমাণে হারতে হবে যাতে উত্তেজনা অনুভূত হয়। আবার তাদের যথেষ্ট কিছু অর্জন করতে হবে যাতে আশা বজায় থাকে।

ব্যর্থতার মুদ্রা

আমার সাম্প্রতিক প্রজেক্ট, Neon Survivor-এ, আমি ঠিক সেই উত্তেজনাটিই চেয়েছিলাম। যখন খেলোয়াড় মারা যায়, তখন রান চলাকালীন সংগৃহীত সবকিছু হারিয়ে যায় কেবল একটি জিনিস ছাড়া: গোল্ড (gold)। সেই গোল্ডটি স্বয়ংক্রিয়ভাবে জমা হয়ে যায়। মেনুতে ফিরে এসে, তারা এটি স্থায়ী আপগ্রেডের (permanent upgrades) জন্য খরচ করে। তারপর তারা আবার ঝাঁপিয়ে পড়ে, আগের চেয়ে কিছুটা শক্তিশালী হয়ে।

এই সাধারণ লুপটি পুরো গেমটিকে চালিত করে। এটি ছাড়া মৃত্যু মানেই একটি পূর্ণচ্ছেদ। খেলোয়াড় খেলা ছেড়ে চলে যায় কারণ শেষ রান থেকে সে কিছুই পায়নি। এটি থাকলে মৃত্যু হলো একটি কমা। রানটি একটি ফার্মিং ট্রিপে পরিণত হয়। হারটা কষ্টের, কিন্তু এটি আগামীকালের জন্য পথ তৈরি করে দেয়।

কৌশলটি সহজ। আপনি যখন সবকিছু হারিয়ে ফেলেন, তখনও কিছু একটা আপনার কাছে থেকে যায়। চ্যালেঞ্জ নষ্ট না করে সেই জিনিসটিকে গুরুত্বপূর্ণ রাখাটাই হলো আসল কাজ। যদি আপগ্রেডগুলো খুব দুর্বল হয়, তবে খেলোয়াড় আর গুরুত্ব দেবে না। আর যদি সেগুলো খুব শক্তিশালী হয়, তবে গেমটি নিজেই নিজে চলতে থাকবে। উভয় ক্ষেত্রেই লুপটি ভেঙে পড়বে।

দুটি ঘড়ি, কোনো বিভ্রান্তি নেই

এটি সঠিকভাবে তৈরি করতে আমাকে দুটি আলাদা টাইমলাইন পরিচালনা করতে হয়েছে।

Run Clock প্রতিবার প্লে করার সময় রিসেট হয়। এটি স্বাস্থ্য, বর্তমান স্কোর, এনিমি ওয়েভ কাউন্ট এবং সেশনের সময় নেওয়া যেকোনো অস্থায়ী পাওয়ার-আপ ট্র্যাক করে। চরিত্রটি মারা গেলে, এই ঘড়িটি শূন্যে ফিরে আসে।

Meta Clock কখনোই রিসেট হয় না। এটি প্রতিটি প্রচেষ্টায় অর্জিত মোট গোল্ড, সর্বোচ্চ কততম ওয়েভে পৌঁছানো গেছে এবং কেনা প্রতিটি স্থায়ী আপগ্রেড ধরে রাখে। ব্রাউজার যতবারই রিফ্রেশ করা হোক না কেন, এই ঘড়িটি চলতে থাকে।

এই দুটিকে একসাথে মিশিয়ে ফেললে এমন বাগ (bug) তৈরি হয় যা ট্র্যাক করা কঠিন এবং ঠিক করা যন্ত্রণাদায়ক। আমি দেখেছি ডেভেলপাররা রুটিন সিন রিসেটের (scene reset) সময় ভুলবশত খেলোয়াড়ের প্রগ্রেস মুছে ফেলেন, কারণ একটি ক্লিনআপ ফাংশন ভুল ডেটা স্টোরে স্পর্শ করেছিল। Meta Clock-এর ডেটা বাষ্পীভূত হয়ে যায়। খেলোয়াড় শূন্য গোল্ড এবং শূন্য আপগ্রেড নিয়ে ফিরে আসে। সেই মুহূর্তে, আপনার এবং আপনার খেলোয়াড়ের মধ্যকার সম্পর্ক ভেঙে যায়। তারা নতুন কোনো রান শুরু করছে না; তারা নতুন কোনো ক্ষোভ শুরু করছে।

ঘড়িগুলোকে আলাদা রাখা কেবল একটি শৈলীগত পছন্দ নয়। এটি একটি টিকে থাকার কৌশল।

Phaser v4 যেভাবে এই বিভাজনটি পরিচালনা করে

আমি Neon Survivor তৈরি করেছি Phaser v4-এ, যা এই সমস্যার জন্য দুটি নির্দিষ্ট টুল প্রদান করে।

Registry বর্তমান সেশনের জন্য মেমরিতে লাইভ ডেটা ধরে রাখে। এটি দ্রুত। এটি সহজ। খেলোয়াড় পেজ রিফ্রেশ করার সাথে সাথেই এটি মুছে যায়।

LocalStorage ব্রাউজারের মধ্যেই ডেটা সংরক্ষণ করে। এটি ট্যাব বন্ধ করা, ব্রাউজার রিস্টার্ট এবং বিদ্যুৎ বিভ্রাটের পরেও টিকে থাকে। এটি কিছুটা ধীরগতির এবং কম নির্ভরযোগ্য। স্টোরেজ কোটা পূর্ণ হয়ে গেলে ব্রাউজার এটি ব্লক করতে পারে, ধীর করে দিতে পারে বা মুছে ফেলতে পারে।

আমার ডিজাইনের সিদ্ধান্ত ছিল কঠোর। গেমপ্লে চলাকালীন Registry হলো একমাত্র সত্যের উৎস (source of truth)। গেমটি এখান থেকে ডেটা পড়ে, এখানে লেখে এবং এটিকে সম্পূর্ণ বিশ্বাস করে। LocalStorage কোনো সহ-লেখক (co-author) হিসেবে কাজ করে না। এটি একটি আয়নার মতো কাজ করে।

কাজের প্রক্রিয়াটি এরকম: গেমটি একটি আপগ্রেড কেনা সংক্রান্ত তথ্য Registry-তে লেখে। একটি একক ম্যানেজার ক্লাস Registry-কে পর্যবেক্ষণ করে। উপযুক্ত সময়ে, সেই ম্যানেজার Registry-এর ডেটাকে LocalStorage-এ মিরর (mirror) করে। যদি ব্রাউজার রাইট (write) অপারেশন ব্লক করে, তবে গেমটি আটকে যায় না। যদি স্টোরেজ ব্যর্থ হয়, বর্তমান সেশনটি তবুও নিখুঁতভাবে চলে। খেলোয়াড় কেবল তখনই প্রগ্রেস হারাতে পারে যদি তারা ঠিক সেই সেকেন্ডেই ট্যাবটি বন্ধ করে দেয়, কিন্তু সেশনটি কখনোই ক্র্যাশ করে না।

এই প্যাটার্নটি একটি সূক্ষ্ম বিপর্যয় রোধ করে। আপনি যদি প্রতিটি সিস্টেমকে সরাসরি LocalStorage-এ লেখার অনুমতি দেন, তবে আপনি একটি ভঙ্গুর API-এর ওপর নির্ভরশীলতা তৈরি করবেন। উচ্চ গোপনীয়তা সেটিংস বা কম স্টোরেজ সম্পন্ন ডিভাইসের কোনো খেলোয়াড় হয়তো যুদ্ধের সময় গেমটি ধীর হওয়া বা ফ্রিজ হওয়া দেখতে পারেন, কারণ কোনো ব্যাকগ্রাউন্ড ফাংশন স্ট্যাটস সেভ করার চেষ্টা করছিল। Registry-কে একমাত্র সত্যের উৎস হিসেবে ব্যবহার করে আপনি অ্যাকশন দ্রুত রাখেন এবং ঝুঁকি নিয়ন্ত্রণে রাখেন।

কোডকে স্বাচ্ছন্দ্যে চলতে দিন

আমি সিস্টেমগুলোকে ডিসকাপল (decouple) করার জন্য ইভেন্টও ব্যবহার করেছি। যখন একটি রান শেষ হয়, GameScene তার নিজের শেষকৃত্য নিজে পালন করে না। এটি কোনো সেভ ফাংশন কল করে না। এটি কোনো স্টোরেজ ইউটিলিটি ইম্পোর্ট করে না। এটি কেবল প্রাসঙ্গিক ডেটা সহ একটি "run-ended" ইভেন্ট নির্গত (emit) করে।

একটি আলাদা লিসেনার হিসাবরক্ষণের কাজ সামলায়। এটি ইভেন্ট গ্রহণ করে, Meta Clock আপডেট করে এবং ম্যানেজারকে নতুন টোটালগুলো LocalStorage-এ মিরর করার নির্দেশ দেয়।

এই পৃথকীকরণটি তাৎক্ষণিকভাবে ফলপ্রসূ হয়। আমি সেভ সিস্টেম স্পর্শ না করেই পুরো GameScene নতুন করে লিখতে পারি, প্লেয়ার ক্যারেক্টার পরিবর্তন করতে পারি, ক্যামেরা অ্যাঙ্গেল বদলাতে পারি, এমনকি জনরা সারভাইভাল থেকে বুলেট হেল-এও পরিবর্তন করতে পারি। সিস্টেমগুলো একে অপরের থেকে স্বাধীন। তারা সরাসরি ফাংশন কলের মাধ্যমে নয়, বরং ইভেন্টের মাধ্যমে যোগাযোগ করে। এর মানে হলো কম মার্জ কনফ্লিক্ট, কম বাগ এবং এমন একটি কোডবেস যা ছয় মাস পর স্প্যাগেটির মতো জট পাকিয়ে যাবে না।

গেম বদলে দেওয়ার মতো আপগ্রেডসমূহ

একটি শক্তিশালী প্রযুক্তিগত ভিত্তি থাকা সত্ত্বেও যদি পুরস্কারগুলো কেবল একটি স্প্রেডশিটের মতো মনে হয়, তবে তা নিরর্থক। আপগ্রেডগুলো খেলার সময় আসলে কেমন অনুভূতি দেয়, সে বিষয়ে আমি অনেক সময় ব্যয় করেছি।

কিছু আপগ্রেড নিরাপদ। অতিরিক্ত মুভমেন্ট স্পিড। বোনাস হেলথ। দ্রুত রিলোড। এগুলো প্লেয়ারকে ভুল করার জন্য বাড়তি সুযোগ দেয়। এগুলো স্বস্তিদায়ক। এগুলো গেমের নিয়ম পরিবর্তন না করেই গেমটিকে সহজ করে দেয়।

অন্যান্য আপগ্রেডগুলো নিয়মগুলো পুরোপুরি বদলে দেয়। Neon Survivor-এ আমি "Piercing Rounds" যোগ করেছি। এই আপগ্রেডের আগে, একটি বুলেট প্রথম শত্রুকে আঘাত করার সাথে সাথেই থেমে যেত। আপগ্রেড করার পর, এটি শত্রুদের ভেদ করে চলে যায়, যা একটি মাত্র শটে পুরো লাইন পরিষ্কার করে দিতে পারে।

এই পার্থক্যটি বিশাল। স্পিড এবং হেলথ হয়তো আপনাকে দীর্ঘক্ষণ টিকে থাকতে সাহায্য করতে পারে, কিন্তু Piercing Rounds আপনার অবস্থান নেওয়ার কৌশল বদলে দেয়। আপনি শত্রুদের সারিবদ্ধভাবে সাজাতে শুরু করেন। আপনি প্রান্ত দিয়ে ঘুরপাক খাওয়া (kiting) বন্ধ করে কেন্দ্রের মধ্য দিয়ে সরাসরি আঘাত করতে শুরু করেন। গেমের সিদ্ধান্ত নেওয়ার পরিধি বিস্তৃত হয়।

ভালো প্রগ্রেসন কেবল সংখ্যা বৃদ্ধি করবে না, বরং প্লেয়ারের সিদ্ধান্ত পরিবর্তন করবে। যদি প্রতিটি আপগ্রেড কেবল একটি শতাংশের বৃদ্ধি হয়, তবে প্লেয়ার বর্ণনা পড়া বন্ধ করে দেয়। তারা ক্লিক করে, আপগ্রেড করে এবং ভুলে যায়। যদি একটি আপগ্রেড তাদের কৌশল নিয়ে পুনরায় ভাবতে বাধ্য করে, তবে তারা সেটি মনে রাখে। তারা এটি নিয়ে আলোচনা করে। গেমটিকে ওলটপালট করে দিতে পারে এমন আর কী কী আছে তা দেখতে তারা আবার ফিরে আসে।

আসল প্রাপ্তি

"আরও একটি রান" (one more run) লুপটি কোনো একক সিস্টেম নয়। এটি ক্ষতি এবং লাভের মধ্যে একটি সম্পর্ক, যা ক্লিন আর্কিটেকচার এবং অর্থবহ পুরস্কারের ওপর ভিত্তি করে তৈরি।

দুটি আলাদা টাইমলাইন তৈরি করুন এবং Meta Clock-কে এমনভাবে রক্ষা করুন যেন এটি আপনার প্লেয়ারদের বিশ্বাস ধরে রাখে, কারণ এটি সত্যিই তাই। লাইভ ডেটাকে দ্রুত এবং পারসিস্টেন্ট ডেটাকে নিরাপদ রাখতে আপনার ইঞ্জিনের টুলস ব্যবহার করুন। আপনার সিনগুলোকে (scenes) স্টোরেজ থেকে আলাদা বা ডিকাপল করুন যাতে আপনি নির্ভয়ে ইটারেট করতে পারেন। আর যখন আপনি আপগ্রেড ডিজাইন করবেন, তখন নিজেকে প্রশ্ন করুন যে সেগুলো প্লেয়ারকে কি কেবল বেশি সময় দিচ্ছে, নাকি আরও আকর্ষণীয় পছন্দ করার সুযোগ দিচ্ছে।

এটি সঠিকভাবে করতে পারলে, আপনার প্লেয়াররা কেবল মৃত্যুকে সহ্যই করবে না, বরং তারা এর ওপর নির্ভর করবে। প্রতিটি রান পরবর্তী রানের জন্য একটি অগ্রিম বিনিয়োগ হয়ে দাঁড়াবে। গেমটি আর কেবল বারবার রিস্টার্ট করার একটি ধারা থাকবে না, বরং একটি একক, অবিচ্ছিন্ন উর্ধ্বগতিতে পরিণত হবে।