101. Snake. WinForms, GDI+

Published February 10, 2019
Advertisement

Step-by-step instruction of Snake 2D using C#, WinForms, GDI+

Let's make a very simple classic snake game. For example, if we have a snake head with 10x10 pixels then we will move our snake by a step with 10 pixels using timer.

This is the result of our work:

SnakeWinForms_EatingFood.gif.ee1d250975b4dae1a622899081a181f0.gif

Note. I take ideas from this tutorial: Python Snake Game

We can set a game field size like this:

// Set a game field size ClientSize = new Size(200, 200);

Let's create a method for drawing of a rectangle:

private void DrawRect(int x, int y, Color color, int size = 10) { Graphics g = CreateGraphics(); SolidBrush brush = new SolidBrush(color); g.FillRectangle(brush, new Rectangle(x, y, size, size)); brush.Dispose(); g.Dispose(); }

Each game must have a game loop that will be called by timer. I created the GameLoop method that just prints "Hello, World!" to the debug console every 500 ms:

public Form1() { InitializeComponent(); // Set a game field size ClientSize = new Size(200, 200); // Create a timer for the GameLoop method var timer = new Timer(); timer.Tick += GameLoop; timer.Interval = 500; timer.Start(); } private void GameLoop(object sender, System.EventArgs e) { System.Console.WriteLine("Hello, World!"); }

For a while our GameLoop will have only two called methods Update() and Draw(). The Update() method will have updates for snake cell coordinates and etc. The Draw() method will have only draw methods for game entities.

For example:

private void GameLoop(object sender, System.EventArgs e) { Update(); Draw(); } private void Update() { Console.WriteLine("Game entities coords was updated"); } private void Draw() { DrawFood(); DrawSnake(); } private void DrawSnake() { Console.WriteLine("Snake was drawn"); } private void DrawFood() { Console.WriteLine("Food was drawn"); }

List data structure is ideal for keeping snake cells coordinates:

// Snake list of (x, y) positions private List<Point> _snake = new List<Point>() { new Point(10, 10) };

Point (10, 10) - it is position of the head.

It is better to move a general Draw() method call to Form1_Paint() and add Invalidate() call to GameLoop:

private void Form1_Paint(object sender, PaintEventArgs e) { Draw(); } private void GameLoop(object sender, System.EventArgs e) { Update(); Invalidate(); }

Method for drawing the snake:

private void DrawSnake() { foreach (var cell in _snake) { DrawRect(cell.X, cell.Y, Color.Green); } }

For moving the snake we need to create the "snakeDir" variable:

/ Snake movement direction private Point _snakeDir = new Point(10, 0);

The snake moving is very simple: you need to add two vectors: the snake head vector position and the snake direction. You add a new vector positions intead of head and remove last element. Please, read comments:

private void Update() { // Calc a new position of the head Point newHeadPosition = new Point( _snake[0].X + _snakeDir.X, _snake[0].Y + _snakeDir.Y ); // Insert new position in the beginning of the snake list _snake.Insert(0, newHeadPosition); // Remove the last element _snake.RemoveAt(_snake.Count - 1); }

SnakeWinforms_HeadOnly.gif.0feeb955090cfbf3376dd2ccbc59572a.gif

I will explain eating food later. But you can read comments in the code.

This is a result code:

using System; using System.Collections.Generic; using System.Drawing; using System.Windows.Forms; namespace Snake { public partial class Form1 : Form { // Snake list of (x, y) positions private List<Point> _snake = new List<Point>() { new Point(10, 10) }; // Snake movement direction private Point _snakeDir = new Point(10, 0); // Food private Point _food = new Point(); // Random generator private Random _rnd = new Random(); // Game field size private int _fieldWidth = 200; private int _fieldHeight = 200; // Snake step private int _snakeStep = 10; public Form1() { InitializeComponent(); // Centers the form on the current screen CenterToScreen(); // Set a game field size ClientSize = new Size(_fieldWidth, _fieldHeight); // Generate an initial random position for the food GenerateFood(); // Create a timer for the GameLoop method var timer = new Timer(); timer.Tick += GameLoop; timer.Interval = 200; timer.Start(); } private void Form1_Paint(object sender, PaintEventArgs e) { Draw(); } private void GameLoop(object sender, System.EventArgs e) { Update(); Invalidate(); } private void Update() { // Calc a new position of the head Point newHeadPosition = new Point( _snake[0].X + _snakeDir.X, _snake[0].Y + _snakeDir.Y ); // Insert new position in the beginning of the snake list _snake.Insert(0, newHeadPosition); // Remove the last element _snake.RemoveAt(_snake.Count - 1); // Check collision with the food if (_snake[0].X == _food.X && _snake[0].Y == _food.Y) { // Add new element in the snake _snake.Add(new Point(_food.X, _food.Y)); // Generate a new food position GenerateFood(); } } private void Draw() { DrawFood(); DrawSnake(); } private void DrawSnake() { foreach (var cell in _snake) { DrawRect(cell.X, cell.Y, Color.Green); } } private void DrawFood() { DrawRect(_food.X, _food.Y, Color.OrangeRed); } private void DrawRect(int x, int y, Color color, int size = 10) { Graphics g = CreateGraphics(); SolidBrush brush = new SolidBrush(color); g.FillRectangle(brush, new Rectangle(x, y, size, size)); brush.Dispose(); g.Dispose(); } private void GenerateFood() { _food.X = 10 * _rnd.Next(0, _fieldHeight / 10 - 1); _food.Y = 10 * _rnd.Next(0, _fieldHeight / 10 - 1); } private void Form1_KeyPress(object sender, KeyPressEventArgs e) { switch (e.KeyChar) { case 'w': _snakeDir.X = 0; _snakeDir.Y = -_snakeStep; break; case 'a': _snakeDir.X = -_snakeStep; _snakeDir.Y = 0; break; case 's': _snakeDir.X = 0; _snakeDir.Y = _snakeStep; break; case 'd': _snakeDir.X = _snakeStep; _snakeDir.Y = 0; break; } } } }

2 likes 2 comments

Comments

Awoken

This is a fantastic post.  Well done, great 'how to' for many beginners.   Where's phil? :P 

February 09, 2019 08:54 PM
phil67rpg

well done, I am working on a  console based snake game. 

February 09, 2019 11:13 PM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement