গেমের আর্কিটেকচার / ডিজাইনের প্রশ্ন - বৈশ্বিক দৃষ্টান্তগুলি এড়িয়ে গিয়ে একটি দক্ষ ইঞ্জিন তৈরি করা (সি ++ খেলা)


28

গেম আর্কিটেকচার সম্পর্কে আমার একটি প্রশ্ন ছিল: বিভিন্ন উপাদান একে অপরের সাথে যোগাযোগ করার সর্বোত্তম উপায় কী?

যদি এই প্রশ্নটি ইতিমধ্যে এক মিলিয়নবার জিজ্ঞাসা করা হয়েছে তবে আমি সত্যিই ক্ষমা চাইছি, তবে আমি ঠিক ঠিক কী ধরণের তথ্য খুঁজছি তার সাথে কিছুই পাই না।

আমি স্ক্র্যাচ থেকে গেমটি তৈরির চেষ্টা করছি (সি ++ যদি এটি গুরুত্বপূর্ণ হয়) এবং অনুপ্রেরণার জন্য কিছু মুক্ত উত্স গেম সফটওয়্যার পর্যবেক্ষণ করেছি (সুপার মেরিও ক্রনিকলস, ওপেনটিটিডি এবং অন্যান্য)। আমি লক্ষ্য করেছি যে এই গেম ডিজাইনের অনেকগুলি জায়গা জুড়ে বিশ্বব্যাপী দৃষ্টান্ত এবং / অথবা সিলেটলেট ব্যবহার করে (সারি সরবরাহকারী সারি, সত্তা পরিচালক, ভিডিও পরিচালক এবং আরও কিছু হিসাবে)। আমি বিশ্বব্যাপী দৃষ্টান্তগুলি এবং এককজনকে এড়াতে চেষ্টা করছি এবং এমন একটি ইঞ্জিন তৈরি করতে চাই যা যতটা সম্ভব স্বচ্ছলভাবে মিলিত হয় তবে কার্যকর ডিজাইনে আমার অনভিজ্ঞতার জন্য owণী এমন কিছু বাধা আমি আঘাত করছি। (এই প্রকল্পের প্রেরণার অংশ হ'ল এটি সম্বোধন করা :))

আমি এমন একটি নকশা তৈরি করেছি যেখানে আমার একটি প্রধান GameCoreঅবজেক্ট রয়েছে যার অন্যান্য সদস্য রয়েছে যা আমি অন্যান্য প্রকল্পগুলিতে দেখতে পাই বিশ্বব্যাপী দৃষ্টান্তের সাথে সাদৃশ্যপূর্ণ (যেমন, এটিতে একটি ইনপুট ম্যানেজার, একটি ভিডিও পরিচালক, একটি GameStageউপাদান যা সমস্ত সত্ত্বা এবং গেম প্লে নিয়ন্ত্রণ করে বর্তমানে যে কোনও পর্যায়ে লোড রয়েছে ইত্যাদি)। সমস্যাটি হ'ল যেহেতু সবকিছুতে GameCoreবস্তুটি কেন্দ্রীভূত , তাই বিভিন্ন উপাদানগুলির একে অপরের সাথে যোগাযোগ করার আমার কাছে সহজ উপায় নেই।

সুপার মেরিও ক্রনিকলস, উদাহরণস্বরূপ, যখনই গেমের কোনও উপাদানকে অন্য উপাদানগুলির সাথে যোগাযোগের প্রয়োজন হয় (অর্থাত, শত্রু অবজেক্ট রেন্ডার কাতারে রেন্ডার পর্যায়ে আঁকতে নিজেকে যুক্ত করতে চায়), এটি কেবল সাথে কথা বলে বিশ্বব্যাপী উদাহরণ।

আমার জন্য, আমার গেমের অবজেক্টগুলি প্রাসঙ্গিক তথ্যটি GameCoreবস্তুর কাছে ফেরত দিতে হবে , যাতে GameCoreঅবজেক্টটি সেই তথ্যটি সিস্টেমের অন্যান্য উপাদান (গুলি) এর কাছে প্রেরণ করতে পারে যার প্রয়োজন হয় (যেমন: উপরের পরিস্থিতির জন্য, প্রতিটি শত্রু অবজেক্ট) তাদের রেন্ডার তথ্যগুলি সেই GameStageবস্তুটিতে ফিরিয়ে দেয় , যা এগুলি সমস্ত সংগ্রহ করে তা আবার জমা করে দেবে GameCore, যার ফলে এটি ভিডিও পরিচালকের কাছে রেন্ডারিংয়ের জন্য প্রেরণ করবে)। এটি দেখতে যেমন সত্যই এক ভয়ঙ্কর নকশার মতো মনে হয় এবং আমি এটির একটি রেজোলিউশন ভাবার চেষ্টা করছিলাম। সম্ভাব্য নকশাগুলির বিষয়ে আমার মতামত:

  1. গ্লোবাল দৃষ্টান্ত (সুপার মেরিও ক্রনিকলসের নকশা, ওপেনটিটিডি, ইত্যাদি)
  2. রয়ে GameCoreএকটি ফড়িয়া, যার মাধ্যমে সমস্ত বস্তু যোগাযোগ যেমন বস্তুর অ্যাক্ট (বর্তমান নকশা উপরে বর্ণিত)
  3. অন্যান্য যে সমস্ত উপাদানগুলির সাথে তাদের কথা বলার প্রয়োজন হবে তাদের উপাদানগুলি পয়েন্টার দিন (উদাহরণস্বরূপ, উপরের মেরিও উদাহরণে শত্রু শ্রেণীর সাথে যে ভিডিওর সাথে কথা বলতে হবে তার পয়েন্টার থাকবে)
  4. গেমটি সাবসিস্টেমগুলিতে ভাঙ্গুন - উদাহরণস্বরূপ, অবজেক্টে ম্যানেজার অবজেক্ট রয়েছে যা GameCoreতাদের সাবসিস্টেমের মধ্যে অবজেক্টের মধ্যে যোগাযোগ পরিচালনা করে
  5. (অন্যান্য অপশন? ....)

আমি সর্বোত্তম সমাধান হিসাবে উপরের 4 বিকল্পটি কল্পনা করেছি, তবে এটির ডিজাইন করতে আমার কিছুটা সমস্যা হচ্ছে ... সম্ভবত যে কারণে আমি যে ডিজাইনগুলি দেখেছি সেগুলি বিবেচনা করে বিশ্বব্যাপী ব্যবহার করে। মনে হচ্ছে আমি আমার বর্তমান ডিজাইনে বিদ্যমান একই সমস্যাটি নিচ্ছি এবং প্রতিটি উপ-সিস্টেমে এটির তুলনা করছি, কেবলমাত্র একটি ছোট স্কেলে। উদাহরণস্বরূপ, GameStageউপরে বর্ণিত অবজেক্টটি এটির কিছুটা চেষ্টা হলেও GameCoreঅবজেক্টটি এখনও প্রক্রিয়াতে জড়িত।

এখানে কেউ ডিজাইনের পরামর্শ দিতে পারেন?

ধন্যবাদ!


1
আমি আপনার প্রবৃত্তিটি বুঝতে পারি যে সিলেটলেটগুলি দুর্দান্ত নকশা নয়। আমার অভিজ্ঞতা হিসাবে, তারা সিস্টেমে যোগাযোগ পরিচালনা করার সবচেয়ে সহজ উপায় ছিল
এমমেট বাটলার

4
একটি মন্তব্য হিসাবে যুক্ত করা যেহেতু আমি জানি না এটি একটি সেরা অনুশীলন কিনা। আমার একটি কেন্দ্রীয় গেম ম্যানেজার রয়েছে যা ইনপুটসিস্টেম, গ্রাফিক্স সিস্টেম ইত্যাদির মতো সাবসিস্টিমে গঠিত Each সেই সময়ে, আমি গেমম্যানেজার রেফারেন্সের মাধ্যমে অন্য যে কোনও সিস্টেমে এটি অ্যাক্সেস করে উল্লেখ করতে পারি।
ইনিশির

আমি ট্যাগগুলি পরিবর্তন করেছি কারণ এই প্রশ্নটি গেমের নকশা নয়, কোড সম্পর্কিত।
ক্লাইম

এই থ্রেডটি খানিকটা পুরানো তবে আমার ঠিক একই সমস্যা রয়েছে। আমি ওজিআরই ব্যবহার করি এবং আমি সর্বোত্তম উপায়ে ব্যবহার করার চেষ্টা করি, আমার মতে অপশনটি # 4 হ'ল সর্বোত্তম পন্থা। আমি অ্যাডভান্সড ওগ্রে ফ্রেমওয়ার্কের মতো কিছু তৈরি করেছি, তবে এটি খুব মডুলার নয়। আমি মনে করি আমার একটি সাবসিস্টেম ইনপুট হ্যান্ডলিং দরকার যা কেবল কীবোর্ড হিট এবং মাউস চালনা পায় get আমি যা বুঝতে পারি না তা হ'ল আমি কীভাবে সাবসিস্টেমগুলির মধ্যে এমন একটি "যোগাযোগ" পরিচালক তৈরি করতে পারি?
ডোমিনিক 2000

1
হাই @ ডোমিনিক 2000, এটি কোনও প্রশ্নোত্তর সাইট, কোনও ফোরাম নয়। আপনার যদি প্রশ্ন থাকে তবে আপনার একটি আসল প্রশ্ন পোস্ট করা উচিত, এবং কোনও বিদ্যমান প্রশ্নের উত্তর নয়। আরও তথ্যের জন্য FAQ দেখুন ।
জোশ

উত্তর:


19

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

উদাহরণস্বরূপ (সি # কোড যা সহজেই সি ++ বা জাভাতে অনুবাদ করা যায়)

বলুন যে আপনার কাছে একটি রেন্ডারিং ব্যাকএন্ড ইন্টারফেস রয়েছে যা রেন্ডারিং স্টাফের জন্য কিছু সাধারণ ক্রিয়াকলাপ রয়েছে has

public interface IRenderBackend
{
    void Draw();
}

এবং এটি আপনার ডিফল্ট রেন্ডার ব্যাকএন্ড বাস্তবায়ন আছে

public class DefaultRenderBackend : IRenderBackend
{
    public void Draw()
    {
        //do default rendering stuff.
    }
}

কিছু ডিজাইনে এটি বৈশ্বিকভাবে রেন্ডার ব্যাকএন্ড অ্যাক্সেস করতে সক্ষম বলে মনে হয়। ইন একক প্যাটার্ন মানে প্রতিটি IRenderBackend বাস্তবায়ন অনন্য বিশ্বব্যাপী উদাহরণস্বরূপ হিসাবে প্রয়োগ করা হবে। তবে সার্ভিস লোকেটর প্যাটার্ন ব্যবহার করার জন্য এটির প্রয়োজন হয় না।

এখানে কীভাবে:

public class ServiceLocator<T>
{
    private static T currGlobalInstance;

    public static T Service
    {
        get { return currGlobalInstance; }
        set { currGlobalInstance = value; }
    }
}

আপনার গ্লোবাল অবজেক্ট অ্যাক্সেস করতে সক্ষম হওয়ার জন্য আপনাকে প্রথমে এটি অন্তর্নিহিত করা দরকার।

//somewhere during program initialization
ServiceLocator<IRenderBackend>.Service = new DefaultRenderBackend();

//somewhere else in the code
IRenderBackend currentRenderBackend = ServiceLocator<IRenderBackend>.Service;

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

public class IsometricRenderBackend : IRenderBackend
{
    void draw()
    {
        //do rendering using an isometric view
    }
}

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

ServiceLocator<IRenderBackend>.Service = new IsometricRenderBackend();

আরেকটি সুবিধা হ'ল আপনি নাল পরিষেবাও ব্যবহার করতে পারেন। উদাহরণস্বরূপ, যদি আমরা একটি ছিল ISoundManager সেবা এবং ব্যবহারকারী শব্দ বন্ধ করতে তাই সেটিং দ্বারা, আমরা শুধু একটি NullSoundManager যে যখন তার পদ্ধতি বলা হয় কিছুই না বাস্তবায়ন হতে পারে চেয়েছিলেন ServiceLocator একটি থেকে এর সেবা বস্তুর NullSoundManager বস্তুর আমরা অর্জন করতে পারে খুব কম কাজের সাথে এই ফলাফল।

সংক্ষিপ্তসার হিসাবে, কখনও কখনও বৈশ্বিক ডেটা মুছে ফেলা অসম্ভব হতে পারে তবে এর অর্থ এই নয় যে আপনি সেগুলি সঠিকভাবে এবং কোনও অবজেক্ট ওরিয়েন্টেড পদ্ধতিতে সংগঠিত করতে পারবেন না।


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

3
@ এরিভিস সুতরাং, মূলত, আপনি পলিমারফিক অবজেক্টের জন্য বিশ্বব্যাপী রেফারেন্স বর্ণনা করছেন। পরিবর্তে, এটি কেবল ডাবল ইন্ডিরেশন (পয়েন্টার -> ইন্টারফেস -> বাস্তবায়ন)। সি ++ এ এটি হিসাবে সহজেই প্রয়োগ করা যেতে পারে std::unique_ptr<ISomeService>
বৃষ্টির ছায়াগুলি

1
আপনি "প্রথম অ্যাক্সেসের সূচনাতে" আরম্ভের কৌশলটি পরিবর্তন করতে পারেন এবং কিছু বাহ্যিক কোড সিক্যুয়েন্স বরাদ্দ করতে এবং লোকেটারগুলিতে পরিষেবাগুলি পুশ করার প্রয়োজন এড়াতে পারেন। আপনি পরিষেবাগুলিতে একটি "নির্ভর করে" তালিকা যুক্ত করতে পারেন যাতে এটি যখন শুরু করা হয় তখন এটি স্বয়ংক্রিয়ভাবে প্রয়োজনীয় অন্যান্য পরিষেবাগুলি সেটআপ করে দেয় এবং প্রার্থনা করে না যে কেউ মেইন সিপিপি-তে এটি করার জন্য মনে রাখে। ভবিষ্যতে টুইট করার জন্য নমনীয়তার সাথে একটি ভাল উত্তর।
প্যাট্রিক হিউজেস

4

গেম ইঞ্জিনটি ডিজাইনের অনেকগুলি উপায় রয়েছে এবং এটি সমস্ত পছন্দকে ছাড়িয়ে যায়।

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

আমি বিশ্বাস করি আপনি বিকল্প # 4 এর চিন্তাভাবনা নিয়ে আপনি সঠিক পথে আছেন।

যখন নিজেই যোগাযোগের বিষয়টি আসে তখন মনে রাখবেন, এটি সর্বদা সরাসরি ফাংশন কলকে বোঝায় না। যোগাযোগ অনেকগুলি পরোক্ষ উপায় হতে পারে, এটি ব্যবহার Signal and Slotsবা ব্যবহারের মাধ্যমে কিছু পরোক্ষ পদ্ধতির মাধ্যমেই হোক Messages

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

কেবলমাত্র জেনে রাখুন যে আপনার দুটি সিঙ্ক্রোনাস এবং অ্যাসিক্রোনাস যোগাযোগের মিশ্রণ পাবেন। উপযুক্ত কি বাছাই করুন তবে জেনে রাখুন আপনার সাবসিস্টেমগুলির মধ্যে আপনাকে উভয় শৈলীর সমর্থন করতে হবে। উভয়ের জন্য সমর্থন নকশা ভবিষ্যতে আপনাকে ভাল পরিবেশন করবে।


1

আপনাকে কেবল এটি নিশ্চিত করতে হবে যে কোনও বিপরীত বা চক্রীয় নির্ভরতা নেই। উদাহরণস্বরূপ, যদি আপনার একটি বর্গ থাকে Coreএবং Coreএর একটি থাকে Levelএবং Levelএর একটি তালিকা থাকে Entityতবে নির্ভরতা গাছের মতো দেখতে পাওয়া উচিত:

Core --> Level --> Entity

সুতরাং, এই প্রাথমিক নির্ভরতা গাছকে দেওয়া, আপনার কখনই Entityনির্ভর Levelবা হওয়া উচিত নয় Coreএবং Levelকখনও নির্ভর করতে হবে না Core। যদি পারেন LevelবাEntity নির্ভরতা গাছের উচ্চতর ডেটাতে অ্যাক্সেসের প্রয়োজন তা রেফারেন্সের মাধ্যমে প্যারামিটার হিসাবে পাস করা উচিত।

নিম্নলিখিত কোড বিবেচনা করুন (সি ++):

class Core;
class Entity;
class Level;

class Level
{
    public:
        Level(Core& coreIn) : core(coreIn) {}

        Core& core;
}

class Entity
{
    public:
        Entity(Level& levelIn) : level(levelIn) {}

        Level& level;
}

এই কৌশলটি ব্যবহার করে, আপনি দেখতে পাচ্ছেন যে প্রতিটিটিরই Entityঅ্যাক্সেস রয়েছে Levelএবং Levelএতে প্রবেশাধিকার রয়েছে Core। লক্ষ্য করুন যে প্রতিটি মেমরি নষ্ট Entityকরে একই রেফারেন্স সঞ্চয় করে Level। এটি লক্ষ্য করার পরে, আপনার প্রশ্ন করা উচিত যে প্রত্যেককেই Entityসত্যিই এর অ্যাক্সেসের প্রয়োজন কিনা Level

আমার অভিজ্ঞতায়, হয় এ) বিপরীত নির্ভরতা এড়ানোর সত্যিই সুস্পষ্ট সমাধান, বা খ) বৈশ্বিক দৃষ্টান্ত এবং সিঙ্গেলন এড়ানোর কোনও সম্ভাব্য উপায় নেই।


আমি কিছু অনুপস্থিত করছি? আপনি উল্লেখ করেছেন যে 'আপনার সত্তা কখনই স্তরের উপর নির্ভরশীল না হওয়া উচিত' তবে তারপরে আপনি এটিকে 'সত্তা (স্তর ও স্তরীয়)' হিসাবে বর্ণনা করেন। আমি বুঝতে পারি যে নির্ভরতা রেফ দ্বারা পাস করা হয় তবে এটি এখনও নির্ভরতা।
অ্যাডাম নায়লার

@ অ্যাডামনায়েলর মূল কথাটি হ'ল কখনও কখনও আপনার সত্যিকারের বিপরীত নির্ভরতা দরকার হয় এবং আপনি রেফারেন্সগুলি পাস করে গ্লোবালগুলি এড়াতে পারেন। যদিও সাধারণভাবে, এই নির্ভরতা সম্পূর্ণরূপে এড়ানো ভাল, এবং এটি কীভাবে করা যায় তা সবসময় পরিষ্কার নয়।
আপেল

0

সুতরাং, মূলত, আপনি বৈশ্বিক পরিবর্তনীয় রাষ্ট্র এড়াতে চান ? আপনি এটিকে স্থানীয়, অপরিবর্তনীয়, বা কোনও রাষ্ট্রই তৈরি করতে পারবেন না। ল্যাটার সবচেয়ে দক্ষ এবং নমনীয়, ইমো। এটি আইপ্লেমেন্টেশন লুকানো হিসাবে পরিচিত।

class ISomeComponent // abstract base class
{
    //...
};

extern ISomeComponent & g_SomeComponent; // will be defined somewhere else;

0

প্রশ্নটি আসলে মনে হচ্ছে পারফরম্যান্সের ত্যাগ ছাড়াই কীভাবে সংযুক্তি হ্রাস করা যায় about সমস্ত বিশ্বব্যাপী অবজেক্ট (পরিষেবাদি) সাধারণত একটি প্রকারের প্রসঙ্গ তৈরি করে যা গেমের রান-টাইম চলাকালীন পরিবর্তনযোগ্য। এই অর্থে, পরিষেবা লোকেটার প্যাটার্নটি প্রসঙ্গের বিভিন্ন অংশকে অ্যাপ্লিকেশনের বিভিন্ন অংশে ছড়িয়ে দেয়, যা আপনি চান বা নাও করতে পারেন। আর একটি আসল বিশ্ব পদ্ধতির মাধ্যমে এই জাতীয় কাঠামো ঘোষণা করা হবে:

struct sEnvironment
{
    owning<iAudio*> m_Audio;
    owning<iRenderer*> m_Renderer;
    owning<iGameLevel*> m_GameLevel;
    ...
}

এবং এটি অ-মালিকানাধীন কাঁচা পয়েন্টার হিসাবে চারপাশে পাস করুন sEnvironment*। এখানে পয়েন্টারগুলি ইন্টারফেসগুলিতে ইঙ্গিত দেয় যাতে পরিষেবা লোকেটারের তুলনায় কাপলিং একইভাবে হ্রাস পায়। তবে, সমস্ত পরিষেবা এক জায়গায় (যা ভাল বা নাও হতে পারে)। এটি কেবল অন্য পদ্ধতির।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.