عندما بدأت البرمجة ، أعتقد ، مثل كثيرين ، أنني أرغب في إنشاء ألعاب. لكن أمامي كان هناك الكثير من القضايا المعمارية التي لم أكن أعرف كيفية حلها ، لم أسمع حتى عن التخزين المؤقت المزدوج ، وأردت الحصول على النتيجة في أسرع وقت ممكن. لذلك ، قررت مؤخرًا أن أكتب مشروعًا سيكون من الممكن فيه كتابة ألعاب بسيطة دون أي مشاكل. يمكن إنشاء ألعاب في هذا المشروع مثل GameBoy ، وهي: تتريس ، ثعبان ، إلخ. ولكن يمكنك أيضًا النقر فوقه باستخدام الماوس.
رابط للمشروع على جيثب .
في هذه المقالة أريد أن أقوم بإنشاء ثعبان.
أول شيء تحتاج أن تبدأ به هو إنشاء فئة لعبتك الخاصة والوراثة من فئة اللعبة الأساسية.
class Snake : Game
انها تنفذ بالفعل الملعب والأحداث التي تحدث عندما تنتقل اللعبة من حالة إلى أخرى. في الأساس ، كل ما نحتاج إلى القيام به هو إعلان التعامل مع الحدث.
public Snake() : base() { OnPreview += BasePreview; OnNewGame += Snake_OnNewGame; OnUpdateGame += Snake_OnUpdateGame; OnGameOver += DrawScore; }
بالنسبة إلى أحداث OnPreview و OnGameOver ، توجد بالفعل كعوب جاهزة في فئة اللعبة ، لا يمكنك تنفيذها. يبقى فقط لتهيئة لعبة جديدة ومعالجة أحداث التحديث.
private GameBlock head; private List<GameBlock> body; private GameBlock eat; private void Snake_OnNewGame() { head = new GameBlock() { X = 10, Y = 10, Vector = Vector.Up, Color = GameColor.Green }; body = new List<GameBlock>(); body.Add( head ); body.Add( new GameBlock() { X = 10, Y = 11, Vector = Vector.Up, Color = GameColor.Black } ); body.Add( new GameBlock() { X = 10, Y = 12, Vector = Vector.Up, Color = GameColor.Black } ); CreateEat(); DrawField(); }
يمكنك العمل معها مباشرةً لرسم حقل ، أو يمكنك استخدام فئة GameBlock الجاهزة ، وهي تنفذ أشياء مثل الموضع واتجاه الحركة والألوان.
في هذه الوظيفة ، أعلنا جسد الثعبان ، وصنعنا أول قطعة من الطعام ونعرض ما يحدث في هذا المجال.
private void CreateEat() { var emptyBlocks = new List<GameBlock>(); for( int i = 0; i < MainForm.FIELD_SIZE; i++ ) for( int j = 0; j < MainForm.FIELD_SIZE; j++ ) if( CheckEmptyBlock( i, j ) ) emptyBlocks.Add(new GameBlock() { X = i, Y = j, Color = GameColor.Red } ); if (emptyBlocks.Count > 0) eat = emptyBlocks[random.Next( emptyBlocks.Count )]; }
لإنشاء وجبة ، نحصل على قائمة بالكتل الفارغة وبمساعدة عشوائية (حدد بالفعل في اللعبة) حدد عشوائيًا. في حالة احتلال الثعبان الحقل بأكمله ، هناك فحص لحجم القائمة.
في الواقع ، وظيفة التحقق من الخلية الفارغة:
private bool CheckEmptyBlock(int x, int y) => !( x < 0 || y < 0 || x == MainForm.FIELD_SIZE || y == MainForm.FIELD_SIZE ) && !body.Exists( a => a.Equals( new GameBlock() { X = x, Y = y } ) );
عرض الحقل على النحو التالي:
private void DrawField() { Field.Clear( GameColor.White ); Field.DrawGameBlock( eat ); Field.DrawGameBlocks( body ); WriteScore(); }
نظرًا لأنه ليس من الصعب تخمينه ، يتم تنظيف الحقل باللون الأبيض ويتم عرض الطعام مع الثعبان. WriteScore هي ميزة قياسية أخرى لعرض النتيجة في شريط الحالة الخاص.
لذلك ، ننتقل إلى حدث تحديث اللعبة ، والذي يحدث بتردد 300 مللي ثانية.
private void Snake_OnUpdateGame( Controller controller ) { ControlMove( controller.GameKey ); if( CheckGameOver() ) GameOver(); else SnakeMove(); }
تحدث أربعة أشياء: تغيير اتجاه الحركة ، والتحقق من نهاية اللعبة ، واستدعاء حدث نهاية اللعبة وتحريك الثعبان في حالة كل شيء على ما يرام.
private void ControlMove( GameKey key ) { switch( key ) { case GameKey.Left: head.Vector = head.Vector == Vector.Right ? Vector.Right : Vector.Left; break; case GameKey.Right: head.Vector = head.Vector == Vector.Left ? Vector.Left : Vector.Right; break; case GameKey.Up: head.Vector = head.Vector == Vector.Down ? Vector.Down : Vector.Up; break; case GameKey.Down: head.Vector = head.Vector == Vector.Up ? Vector.Up : Vector.Down; break; default: break; } }
لتغيير اتجاه الحركة في الثعبان نحتاج إلى تغيير الموجه في رأسها. لذلك ، في السيطرة على الحركة هناك فحص لحالة انعكاس المتجه ، بحيث لا يبدأ الثعبان في الصعود على نفسه.
private bool CheckGameOver() { switch( head.Vector ) { case Vector.Up: return !CheckEmptyBlock( head.X, head.Y - 1 ); case Vector.Down: return !CheckEmptyBlock( head.X, head.Y + 1 ); case Vector.Left: return !CheckEmptyBlock( head.X - 1, head.Y ); case Vector.Right: return !CheckEmptyBlock( head.X + 1, head.Y ); default: throw new NotImplementedException(); } }
للتحقق من نهاية اللعبة ، يكفي التحقق مما إذا كانت الكتلة في الاتجاه مجانية أم لا. كما قد تخمن ، يتم تجاهل الطعام في الشيك.
يبقى تحليل وظيفة حركة الثعبان:
private void SnakeMove() { var temp = body.Last().Copy(); foreach( var block in body ) block.Move(); for( int i = body.Count - 1; i > 0; i-- ) body[i].Vector = body[i - 1].Vector; if( head.Equals( eat ) ) { score++; body.Add( temp ); CreateEat(); } DrawField(); }
يتم نسخ نهاية الذيل بحيث إذا تم الوصول إلى الطعام ، قم بإضافته كملحق ثعبان. كتل النقل ليست صعبة ، لأن هذه الوظيفة تم تنفيذها بالفعل في فئة الكتلة. ثم يتم توزيع المتجهات على حركة الثعبان والتحقق من تقاطعها مع الطعام. إذا تم العثور على طعام ، تزداد الفاتورة ، وينمو الثعبان ويتم إنشاء طعام جديد. لكي تظهر لعبتنا في قائمة الألعاب ، تحتاج إلى إضافتها إلى تهيئة النموذج:
List<Game> games = new List<Game>(); games.Add( new Snake() ); games.Add( new Tetris() ); games.Add( new Life() ); Application.Run( new MainForm( games ) );
هذا كل شيء. استغرق رمز اللعبة بأكمله 102 خطوط فقط. كما ترون من المثال ، تم بالفعل إضافة لعبة تتريس وحياة اللعبة إلى المشروع. أدناه يمكنك العثور على النتيجة.
قائمة اختيار اللعبة
عملية اللعبة
نهاية اللعبة