Jump to content
  • Advertisement

C# 101. Snake. WinForms, GDI+

8Observer8

1403 views

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 Comments


Recommended Comments

Awoken

Posted (edited)

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

Edited by Awoken

Share this comment


Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Advertisement
  • Advertisement
  • What is your GameDev Story?

    In 2019 we are celebrating 20 years of GameDev.net! Share your GameDev Story with us.

    (You must login to your GameDev.net account.)

  • Blog Entries

  • Similar Content

    • By TexasJack
      Firstly, a disclaimer: my query covers both graphics AND bits of artificial intelligence, if it's in the wrong forum - shift it by all means.

      I am a graphic designer and I have a real layman's interest in the recent developments in AI and how it is being used to enhance images. It sounds sad, but as a sort of hobby, I constantly muse over different methods of calculating how to upscale images based on samples of other pixels etc... I have heaps of jottings/notes of formulas that I have devised that I would love to put into practise by writing little experimental programs to implement them.

      By extension, I am also fascinated by deep learning and neural networks and how they can be trained to develop image enhancement algorithms.

      Here's the problem.

      I have no idea about programming/coding (but I am trying to learn). I've been struggling along with C# as part of also learning to make games in Unity - but I'm barely at the 'Hello World' stage and would have no idea how to write a program that would handle images (for example, how do C# programs break down and handle the individual pixels in a bitmap - and how are these individual pixels edited etc...).

      I would really appreciate pointers on what the best way to start learning something like this in my spare time would be?

      Sorry if this is really vague, I can clarify anything if need be.
    • By Turbo14
      I'm having some problems with path-tracing code. Not sure if it's from the random number generator or what. but my projected rays don't "stick" to surfaces.

      Here's what I'm doing:

      what I want is the hit points to appear to "stick" to surfaces but instead they're flying all around. The only time they stick is when I strafe. No idea what I'm doing wrong.

      void Update () {
      m_Clear_Buff.CopyTo(m_Color_Buff, 0);

      m_main_Cam.enabled = false;
      m_Trace_Cam.enabled = true;
      System.Random r = new System.Random(98);

      for (int i = 0; i < 300; i++)
      {
      Vector3 dir = new Vector3((float)r.Next(360) /360f-.5f, (float)r.Next(360) / 360f - .5f, (float)r.Next(360) / 360f - .5f);

      Ray ray = new Ray(new Vector3(0,0,0), dir);
      RaycastHit rch;
      if (Physics.Raycast(ray, out rch, 50f))
      {

      Vector3 sp = m_Trace_Cam.WorldToScreenPoint(rch.point);

      if (sp.x >=0 && sp.x < m_Trace_Cam.pixelRect.width-1 && sp.y >=0 && sp.y < m_Trace_Cam.pixelRect.height-1)
      {
      float lv = Mathf.Max(Vector3.Dot(rch.point.normalized, new Vector3(0, 0, 0)), 0);
      lv *= 10;

      Material m = rch.collider.gameObject.GetComponent().material;
      m_Color_Buff[(int)(sp.x + sp.y * m_Trace_Cam.pixelRect.width)] = new Color(m.color.r * lv, m.color.g * lv, m.color.b * lv,1);
      }
      }
      }
      m_Trace_Cam.enabled = false;
      m_main_Cam.enabled = true;
      m_Trace_Tex.SetPixels(m_Color_Buff);
      m_Trace_Tex.Apply();
      }

    • By Okkama Games
      Hi to all!

      I've created minimalistic puzzle game for Android, and need some feedback about gameplay!
      It's simple 2D game with unique game mechanic (I did not see exactly the same mechanic in other games, if you know similar games, please tell me)

      Also, i need feedback about levels difficulty. (Some levels may seem impassable at first glance)
      Download and strain your brains!
      https://play.google.com/store/apps/details?id=org.godotengine.projectionfree
      Some screenshots:
       



    • By sidbhati32
      https://www.youtube.com/watch?v=xuKz-komT5I&t=12s     hey     How to achieve the horizontal swipe somewhat close to the video?      
    • By sidbhati32
      What I have done is I have made empty gameobjects in every platform and set their position to where the next platform is supposed to be generated. I am keeping a starting platform always on the scene mode and the next platform that gets generated just after the starting platform works fine, after that it behaves differently       I have shown the position of the objects   I have made an empty object called "Next" and set its transform to where I want the next platform to be generated.   ``` using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlatformGeneration : MonoBehaviour { public List<GameObject> PlatformList; public List<GameObject> RenderedPlat; public GameObject currentPlat; public GameObject nextAttach; // Use this for initialization void Start () { for(int i = 0; i < PlatformList.Capacity; i++) { nextAttach = currentPlat.transform.Find("Next").gameObject; Instantiate(PlatformList, nextAttach.transform.position, Quaternion.identity); Debug.Log(nextAttach.transform.p); currentPlat = PlatformList; } } // Update is called once per frame void Update () { } } ```

×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!