Step-by-step instruction of Snake 2D using C#, WinForms, GDI+
We will place OpenTK.GLControl on the Form to draw graphics with modern shader OpenGL 3.1.
This is a gif animation of the final result of our work:
Note. I take ideas from this tutorial: Python Snake Game
Please, download this empty project: Snake_WinFormsOpenGL31CSharp.zip. It includes OpenTK.dll and OpenTK.GLControl.dll
Or if you know how to add libraries from References and how to add Control to Toolbox you can download these two DLL's: OpenTK.zip and OpenTK.GLControl.zip You can search in the Internet how to do it.
Current Form1.css file:
using System;
using System.Windows.Forms;
using OpenTK.Graphics.OpenGL;
using OpenTK.Graphics;
namespace Snake
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
// Centers the form on the current screen
CenterToScreen();
}
private void glControl_Load(object sender, EventArgs e)
{
glControl.MakeCurrent();
// Set a color for clear the glControl
GL.ClearColor(Color4.Black);
}
private void glControl_Paint(object sender, PaintEventArgs e)
{
GL.Clear(ClearBufferMask.ColorBufferBit);
Draw();
glControl.SwapBuffers();
}
private void Draw()
{
// Draw game entities here
}
}
}
These commands just clear a render canvas (glControl) with a black color. RGB values (0f, 0f, 0f) mean the black color. If you write (1f, 0f, 0f) - it will be a red color or if you write (1f, 1f, 1f) - it will be a white color. You can choose your own value of normalized color using this color calculator.
I set "FormBorderStyle" to "FixedDialog". You cannot change size of the window with the mouse. And I set "MaximizeBox" to "False". You cannot maximize the window by click on "Maximize" button on the window.
Let's draw a square. We need to write a pair of shaders: a vertex shader and a fragment shader. This pair will be associated with a shader program object that we will create too. The pair of shaders and the shader program will placed in a video card memory. We will create an array of vertices of our square and vertex buffer object (VBO) on the video card memory. We will move the array of coordinates of vertices to VBO. The vertex shader will be executed for every vertex when we will call the function: drawArrays(). The vertex shader just set coordinates for vertices. The fragment shader will be executed for every pixel of the square and set a color for every pixel.
This book is one of the best for start: WebGL Programming Guide
using System;
using System.Windows.Forms;
using OpenTK.Graphics.OpenGL;
using OpenTK.Graphics;
namespace Snake
{
public partial class Form1 : Form
{
private int _shaderProgram;
private int _uColorLocation;
public Form1()
{
InitializeComponent();
// Centers the form on the current screen
CenterToScreen();
}
private void glControl_Load(object sender, EventArgs e)
{
glControl.MakeCurrent();
// Set a color for clear the glControl
GL.ClearColor(Color4.Black);
// Initialize shaders and get a shader program
_shaderProgram = InitShadersAndGetProgram();
if (_shaderProgram < 0) return;
// Initialize vertex buffers
InitVertexBuffers();
_uColorLocation = GL.GetUniformLocation(_shaderProgram, "uColor");
if (_uColorLocation < 0)
{
MessageBox.Show("Failed to get uColorLocation variable");
return;
}
// Set a triangle color
GL.Uniform3(_uColorLocation, 0.1f, 0.8f, 0.3f);
}
private void glControl_Paint(object sender, PaintEventArgs e)
{
GL.Clear(ClearBufferMask.ColorBufferBit);
Draw();
glControl.SwapBuffers();
}
private void Draw()
{
// Draw game entities here
DrawSquare(0, 0, Color4.LightCoral, 10);
}
private void DrawSquare(int x, int y, Color4 color, int size)
{
// Set color to fragment shader
GL.Uniform3(_uColorLocation, color.R, color.G, color.B);
// Draw Rectangle
GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4);
}
private int InitShadersAndGetProgram()
{
string vertexShaderSource =
"#version 140\n" +
"in vec2 aPosition;" +
"void main()" +
"{" +
" gl_Position = vec4(aPosition, 1.0, 1.0);" +
"}";
string fragmentShaderSource =
"#version 140\n" +
"out vec4 fragColor;" +
"uniform vec3 uColor;" +
"void main()" +
"{" +
" fragColor = vec4(uColor, 1.0);" +
"}";
// Vertex Shader
int vShader = GL.CreateShader(ShaderType.VertexShader);
GL.ShaderSource(vShader, vertexShaderSource);
GL.CompileShader(vShader);
// Check compilation
string vShaderInfo = GL.GetShaderInfoLog(vShader);
if (!vShaderInfo.StartsWith("No errors"))
{
MessageBox.Show(vShaderInfo);
return -1;
}
// Fragment Shader
int fShader = GL.CreateShader(ShaderType.FragmentShader);
GL.ShaderSource(fShader, fragmentShaderSource);
GL.CompileShader(fShader);
string fShaderInfo = GL.GetShaderInfoLog(fShader);
if (!fShaderInfo.StartsWith("No errors"))
{
MessageBox.Show(fShaderInfo);
return -1;
}
int program = GL.CreateProgram();
GL.AttachShader(program, vShader);
GL.AttachShader(program, fShader);
GL.LinkProgram(program);
GL.UseProgram(program);
return program;
}
private void InitVertexBuffers()
{
float[] vertices = new float[]
{
-0.5f, -0.5f,
0.5f, -0.5f,
-0.5f, 0.5f,
0.5f, 0.5f
};
int vbo;
GL.GenBuffers(1, out vbo);
GL.BindBuffer(BufferTarget.ArrayBuffer, vbo);
// Get an array size in bytes
int sizeInBytes = vertices.Length * sizeof(float);
// Send the vertex array to a video card memory
GL.BufferData(BufferTarget.ArrayBuffer, sizeInBytes, vertices, BufferUsageHint.StaticDraw);
// Config
GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, 0, 0);
GL.EnableVertexAttribArray(0);
}
}
}
We need to set a coordinate system: [0, 20]. I want to have (0, 0) in top left corner. Y axis will have a direction from top to bottom. And I add ability to set a size and a position of square:
using System;
using System.Windows.Forms;
using OpenTK;
using OpenTK.Graphics.OpenGL;
using OpenTK.Graphics;
namespace Snake
{
public partial class Form1 : Form
{
private int _shaderProgram;
private int _uColorLocation;
private int _gameFieldWidth = 20;
private int _gameFieldHeight = 20;
private int _uScaleMatrixLocation;
private int _uTranslateMatrixLocation;
private Matrix4 _scaleMatrix;
private Matrix4 _translateMatrix;
public Form1()
{
InitializeComponent();
// Centers the form on the current screen
CenterToScreen();
}
private void glControl_Load(object sender, EventArgs e)
{
glControl.MakeCurrent();
// Set a color for clear the glControl
GL.ClearColor(Color4.Black);
// Initialize shaders and get a shader program
_shaderProgram = InitShadersAndGetProgram();
if (_shaderProgram < 0) return;
// Initialize vertex buffers
InitVertexBuffers();
_uColorLocation = GL.GetUniformLocation(_shaderProgram, "uColor");
if (_uColorLocation < 0)
{
MessageBox.Show("Failed to get uColorLocation variable");
return;
}
// Set a coordinate cell
int uProjMatrixLocation = GL.GetUniformLocation(_shaderProgram, "uProjMatrix");
if (uProjMatrixLocation < 0)
{
MessageBox.Show("Failed to get a location uProjMatrix variable");
return;
}
Matrix4 projMatrix = Matrix4.CreateOrthographicOffCenter(0f, _gameFieldWidth, _gameFieldHeight, 0f, -100f, 100f);
GL.UniformMatrix4(uProjMatrixLocation, false, ref projMatrix);
// Get uScaleMatrix Location
_uScaleMatrixLocation = GL.GetUniformLocation(_shaderProgram, "uScaleMatrix");
if (_uScaleMatrixLocation < 0)
{
MessageBox.Show("Failed to get a location uScaleMatrix variable");
return;
}
_scaleMatrix = new Matrix4();
// Get uTranslateMatrix Location
_uTranslateMatrixLocation = GL.GetUniformLocation(_shaderProgram, "uTranslateMatrix");
if (_uTranslateMatrixLocation < 0)
{
MessageBox.Show("Failed to get a location uTranslateMatrix variable");
return;
}
_translateMatrix = new Matrix4();
GL.Viewport(0, 0, glControl.Width, glControl.Height);
}
private void glControl_Paint(object sender, PaintEventArgs e)
{
GL.Clear(ClearBufferMask.ColorBufferBit);
Draw();
glControl.SwapBuffers();
}
private void Draw()
{
// Draw game entities here
DrawSquare(1, 1, Color4.LightCoral, 1);
}
private void DrawSquare(int x, int y, Color4 color, int size = 1)
{
// Set color to fragment shader
GL.Uniform3(_uColorLocation, color.R, color.G, color.B);
// Set a size of the square
_scaleMatrix = Matrix4.CreateScale(size);
GL.UniformMatrix4(_uScaleMatrixLocation, false, ref _scaleMatrix);
// Set a position of the square
_translateMatrix = Matrix4.CreateTranslation(new Vector3(x, y, 1f));
GL.UniformMatrix4(_uTranslateMatrixLocation, false, ref _translateMatrix);
// Draw Rectangle
GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4);
}
private int InitShadersAndGetProgram()
{
string vertexShaderSource =
"#version 140\n" +
"in vec2 aPosition;" +
"uniform mat4 uProjMatrix;" +
"uniform mat4 uScaleMatrix;" +
"uniform mat4 uTranslateMatrix;" +
"void main()" +
"{" +
" gl_Position = uProjMatrix * uTranslateMatrix * uScaleMatrix * vec4(aPosition, 1.0, 1.0);" +
"}";
string fragmentShaderSource =
"#version 140\n" +
"out vec4 fragColor;" +
"uniform vec3 uColor;" +
"void main()" +
"{" +
" fragColor = vec4(uColor, 1.0);" +
"}";
// Vertex Shader
int vShader = GL.CreateShader(ShaderType.VertexShader);
GL.ShaderSource(vShader, vertexShaderSource);
GL.CompileShader(vShader);
// Check compilation
string vShaderInfo = GL.GetShaderInfoLog(vShader);
if (!vShaderInfo.StartsWith("No errors"))
{
MessageBox.Show(vShaderInfo);
return -1;
}
// Fragment Shader
int fShader = GL.CreateShader(ShaderType.FragmentShader);
GL.ShaderSource(fShader, fragmentShaderSource);
GL.CompileShader(fShader);
string fShaderInfo = GL.GetShaderInfoLog(fShader);
if (!fShaderInfo.StartsWith("No errors"))
{
MessageBox.Show(fShaderInfo);
return -1;
}
int program = GL.CreateProgram();
GL.AttachShader(program, vShader);
GL.AttachShader(program, fShader);
GL.LinkProgram(program);
GL.UseProgram(program);
return program;
}
private void InitVertexBuffers()
{
float[] vertices = new float[]
{
0f, 0f,
0f, 1f,
1f, 0f,
1f, 1f
};
int vbo;
GL.GenBuffers(1, out vbo);
GL.BindBuffer(BufferTarget.ArrayBuffer, vbo);
// Get an array size in bytes
int sizeInBytes = vertices.Length * sizeof(float);
// Send the vertex array to a video card memory
GL.BufferData(BufferTarget.ArrayBuffer, sizeInBytes, vertices, BufferUsageHint.StaticDraw);
// Config
GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, 0, 0);
GL.EnableVertexAttribArray(0);
}
}
}
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();
// Centers the form on the current screen
CenterToScreen();
// 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)
{
Console.WriteLine("Hello, World!");
}
Update() method will have updates for snake cell coordinates and etc. The Draw() method will have only draw methods for game entities. Method glControl.Invalidate() will provoke a call of Draw() method.
private void GameLoop(object sender, System.EventArgs e)
{
// Update coordinates of game entities
// or check collisions
Update();
// This method calls glControl_Paint
glControl.Invalidate();
}
private void Update()
{
Console.WriteLine("Game entities coords was updated");
}
private void glControl_Paint(object sender, PaintEventArgs e)
{
GL.Clear(ClearBufferMask.ColorBufferBit);
Draw();
glControl.SwapBuffers();
}
private void Draw()
{
DrawFood();
DrawSnake();
}
private void DrawSnake()
{
Console.WriteLine("Snake was drawn");
DrawSquare(2, 1, Color4.LightGreen);
}
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(1, 1)
};
Point(1, 1) - it is position of the head.
Method for drawing the snake:
private void DrawSnake()
{
foreach (var cell in _snake)
{
DrawSquare(cell.X, cell.Y, Color4.LightGreen);
}
}
For moving the snake we need to create the "snakeDir" variable:
// Snake movement direction
private Point _snakeDir = new Point(1, 0);
The snake moving is very simple. 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);
}
using System;
using System.Windows.Forms;
using OpenTK;
using OpenTK.Graphics.OpenGL;
using OpenTK.Graphics;
using System.Collections.Generic;
using System.Drawing;
namespace Snake
{
public partial class Form1 : Form
{
private int _shaderProgram;
private int _uColorLocation;
private int _gameFieldWidth = 20;
private int _gameFieldHeight = 20;
private int _uScaleMatrixLocation;
private int _uTranslateMatrixLocation;
private Matrix4 _scaleMatrix;
private Matrix4 _translateMatrix;
// Snake list of (x, y) positions
private List<Point> _snake = new List<Point>()
{
new Point(1, 1)
};
// Snake movement direction
private Point _snakeDir = new Point(1, 0);
public Form1()
{
InitializeComponent();
// Centers the form on the current screen
CenterToScreen();
// 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)
{
// Update coordinates of game entities
// or check collisions
Update();
// This method calls glControl_Paint
glControl.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);
}
private void glControl_Paint(object sender, PaintEventArgs e)
{
GL.Clear(ClearBufferMask.ColorBufferBit);
Draw();
glControl.SwapBuffers();
}
private void Draw()
{
DrawFood();
DrawSnake();
}
private void DrawSnake()
{
foreach (var cell in _snake)
{
DrawSquare(cell.X, cell.Y, Color4.LightGreen);
}
}
private void DrawFood()
{
Console.WriteLine("Food was drawn");
}
private void glControl_Load(object sender, EventArgs e)
{
glControl.MakeCurrent();
// Set a color for clear the glControl
GL.ClearColor(Color4.Black);
// Initialize shaders and get a shader program
_shaderProgram = InitShadersAndGetProgram();
if (_shaderProgram < 0) return;
// Initialize vertex buffers
InitVertexBuffers();
_uColorLocation = GL.GetUniformLocation(_shaderProgram, "uColor");
if (_uColorLocation < 0)
{
MessageBox.Show("Failed to get uColorLocation variable");
return;
}
// Set a coordinate cell
int uProjMatrixLocation = GL.GetUniformLocation(_shaderProgram, "uProjMatrix");
if (uProjMatrixLocation < 0)
{
MessageBox.Show("Failed to get a location uProjMatrix variable");
return;
}
Matrix4 projMatrix = Matrix4.CreateOrthographicOffCenter(0f, _gameFieldWidth, _gameFieldHeight, 0f, -100f, 100f);
GL.UniformMatrix4(uProjMatrixLocation, false, ref projMatrix);
// Get uScaleMatrix Location
_uScaleMatrixLocation = GL.GetUniformLocation(_shaderProgram, "uScaleMatrix");
if (_uScaleMatrixLocation < 0)
{
MessageBox.Show("Failed to get a location uScaleMatrix variable");
return;
}
_scaleMatrix = new Matrix4();
// Get uTranslateMatrix Location
_uTranslateMatrixLocation = GL.GetUniformLocation(_shaderProgram, "uTranslateMatrix");
if (_uTranslateMatrixLocation < 0)
{
MessageBox.Show("Failed to get a location uTranslateMatrix variable");
return;
}
_translateMatrix = new Matrix4();
GL.Viewport(0, 0, glControl.Width, glControl.Height);
}
private void DrawSquare(int x, int y, Color4 color, int size = 1)
{
// Set color to fragment shader
GL.Uniform3(_uColorLocation, color.R, color.G, color.B);
// Set a size of the square
_scaleMatrix = Matrix4.CreateScale(size);
GL.UniformMatrix4(_uScaleMatrixLocation, false, ref _scaleMatrix);
// Set a position of the square
_translateMatrix = Matrix4.CreateTranslation(new Vector3(x, y, 1f));
GL.UniformMatrix4(_uTranslateMatrixLocation, false, ref _translateMatrix);
// Draw Rectangle
GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4);
}
private int InitShadersAndGetProgram()
{
string vertexShaderSource =
"#version 140\n" +
"in vec2 aPosition;" +
"uniform mat4 uProjMatrix;" +
"uniform mat4 uScaleMatrix;" +
"uniform mat4 uTranslateMatrix;" +
"void main()" +
"{" +
" gl_Position = uProjMatrix * uTranslateMatrix * uScaleMatrix * vec4(aPosition, 1.0, 1.0);" +
"}";
string fragmentShaderSource =
"#version 140\n" +
"out vec4 fragColor;" +
"uniform vec3 uColor;" +
"void main()" +
"{" +
" fragColor = vec4(uColor, 1.0);" +
"}";
// Vertex Shader
int vShader = GL.CreateShader(ShaderType.VertexShader);
GL.ShaderSource(vShader, vertexShaderSource);
GL.CompileShader(vShader);
// Check compilation
string vShaderInfo = GL.GetShaderInfoLog(vShader);
if (!vShaderInfo.StartsWith("No errors"))
{
MessageBox.Show(vShaderInfo);
return -1;
}
// Fragment Shader
int fShader = GL.CreateShader(ShaderType.FragmentShader);
GL.ShaderSource(fShader, fragmentShaderSource);
GL.CompileShader(fShader);
string fShaderInfo = GL.GetShaderInfoLog(fShader);
if (!fShaderInfo.StartsWith("No errors"))
{
MessageBox.Show(fShaderInfo);
return -1;
}
int program = GL.CreateProgram();
GL.AttachShader(program, vShader);
GL.AttachShader(program, fShader);
GL.LinkProgram(program);
GL.UseProgram(program);
return program;
}
private void InitVertexBuffers()
{
float[] vertices = new float[]
{
0f, 0f,
0f, 1f,
1f, 0f,
1f, 1f
};
int vbo;
GL.GenBuffers(1, out vbo);
GL.BindBuffer(BufferTarget.ArrayBuffer, vbo);
// Get an array size in bytes
int sizeInBytes = vertices.Length * sizeof(float);
// Send the vertex array to a video card memory
GL.BufferData(BufferTarget.ArrayBuffer, sizeInBytes, vertices, BufferUsageHint.StaticDraw);
// Config
GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, 0, 0);
GL.EnableVertexAttribArray(0);
}
}
}
I will explain eating food later. But you can read comments in the code.
This is the result:
using System;
using System.Windows.Forms;
using OpenTK;
using OpenTK.Graphics.OpenGL;
using OpenTK.Graphics;
using System.Collections.Generic;
using System.Drawing;
namespace Snake
{
public partial class Form1 : Form
{
private int _shaderProgram;
private int _uColorLocation;
private int _gameFieldWidth = 20;
private int _gameFieldHeight = 20;
private int _uScaleMatrixLocation;
private int _uTranslateMatrixLocation;
private Matrix4 _scaleMatrix;
private Matrix4 _translateMatrix;
// Snake list of (x, y) positions
private List<Point> _snake = new List<Point>()
{
new Point(1, 1)
};
// Snake movement direction
private Point _snakeDir = new Point(1, 0);
// Food
private Point _food = new Point();
// Random generator
private Random _rnd = new Random();
public Form1()
{
InitializeComponent();
// Centers the form on the current screen
CenterToScreen();
// 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 GameLoop(object sender, System.EventArgs e)
{
// Update coordinates of game entities
// or check collisions
Update();
// This method calls glControl_Paint
glControl.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 glControl_Paint(object sender, PaintEventArgs e)
{
GL.Clear(ClearBufferMask.ColorBufferBit);
Draw();
glControl.SwapBuffers();
}
private void Draw()
{
DrawFood();
DrawSnake();
}
private void DrawSnake()
{
foreach (var cell in _snake)
{
DrawSquare(cell.X, cell.Y, Color4.LightGreen);
}
}
private void DrawFood()
{
DrawSquare(_food.X, _food.Y, Color.OrangeRed);
}
private void GenerateFood()
{
_food.X = _rnd.Next(0, _gameFieldWidth);
_food.Y = _rnd.Next(0, _gameFieldHeight);
}
private void glControl_Load(object sender, EventArgs e)
{
glControl.MakeCurrent();
// Set a color for clear the glControl
GL.ClearColor(Color4.Black);
// Initialize shaders and get a shader program
_shaderProgram = InitShadersAndGetProgram();
if (_shaderProgram < 0) return;
// Initialize vertex buffers
InitVertexBuffers();
_uColorLocation = GL.GetUniformLocation(_shaderProgram, "uColor");
if (_uColorLocation < 0)
{
MessageBox.Show("Failed to get uColorLocation variable");
return;
}
// Set a coordinate cell
int uProjMatrixLocation = GL.GetUniformLocation(_shaderProgram, "uProjMatrix");
if (uProjMatrixLocation < 0)
{
MessageBox.Show("Failed to get a location uProjMatrix variable");
return;
}
Matrix4 projMatrix = Matrix4.CreateOrthographicOffCenter(0f, _gameFieldWidth, _gameFieldHeight, 0f, -100f, 100f);
GL.UniformMatrix4(uProjMatrixLocation, false, ref projMatrix);
// Get uScaleMatrix Location
_uScaleMatrixLocation = GL.GetUniformLocation(_shaderProgram, "uScaleMatrix");
if (_uScaleMatrixLocation < 0)
{
MessageBox.Show("Failed to get a location uScaleMatrix variable");
return;
}
_scaleMatrix = new Matrix4();
// Get uTranslateMatrix Location
_uTranslateMatrixLocation = GL.GetUniformLocation(_shaderProgram, "uTranslateMatrix");
if (_uTranslateMatrixLocation < 0)
{
MessageBox.Show("Failed to get a location uTranslateMatrix variable");
return;
}
_translateMatrix = new Matrix4();
GL.Viewport(0, 0, glControl.Width, glControl.Height);
}
private void DrawSquare(int x, int y, Color4 color, int size = 1)
{
// Set color to fragment shader
GL.Uniform3(_uColorLocation, color.R, color.G, color.B);
// Set a size of the square
_scaleMatrix = Matrix4.CreateScale(size);
GL.UniformMatrix4(_uScaleMatrixLocation, false, ref _scaleMatrix);
// Set a position of the square
_translateMatrix = Matrix4.CreateTranslation(new Vector3(x, y, 1f));
GL.UniformMatrix4(_uTranslateMatrixLocation, false, ref _translateMatrix);
// Draw Rectangle
GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4);
}
private int InitShadersAndGetProgram()
{
string vertexShaderSource =
"#version 140\n" +
"in vec2 aPosition;" +
"uniform mat4 uProjMatrix;" +
"uniform mat4 uScaleMatrix;" +
"uniform mat4 uTranslateMatrix;" +
"void main()" +
"{" +
" gl_Position = uProjMatrix * uTranslateMatrix * uScaleMatrix * vec4(aPosition, 1.0, 1.0);" +
"}";
string fragmentShaderSource =
"#version 140\n" +
"out vec4 fragColor;" +
"uniform vec3 uColor;" +
"void main()" +
"{" +
" fragColor = vec4(uColor, 1.0);" +
"}";
// Vertex Shader
int vShader = GL.CreateShader(ShaderType.VertexShader);
GL.ShaderSource(vShader, vertexShaderSource);
GL.CompileShader(vShader);
// Check compilation
string vShaderInfo = GL.GetShaderInfoLog(vShader);
if (!vShaderInfo.StartsWith("No errors"))
{
MessageBox.Show(vShaderInfo);
return -1;
}
// Fragment Shader
int fShader = GL.CreateShader(ShaderType.FragmentShader);
GL.ShaderSource(fShader, fragmentShaderSource);
GL.CompileShader(fShader);
string fShaderInfo = GL.GetShaderInfoLog(fShader);
if (!fShaderInfo.StartsWith("No errors"))
{
MessageBox.Show(fShaderInfo);
return -1;
}
int program = GL.CreateProgram();
GL.AttachShader(program, vShader);
GL.AttachShader(program, fShader);
GL.LinkProgram(program);
GL.UseProgram(program);
return program;
}
private void InitVertexBuffers()
{
float[] vertices = new float[]
{
0f, 0f,
0f, 1f,
1f, 0f,
1f, 1f
};
int vbo;
GL.GenBuffers(1, out vbo);
GL.BindBuffer(BufferTarget.ArrayBuffer, vbo);
// Get an array size in bytes
int sizeInBytes = vertices.Length * sizeof(float);
// Send the vertex array to a video card memory
GL.BufferData(BufferTarget.ArrayBuffer, sizeInBytes, vertices, BufferUsageHint.StaticDraw);
// Config
GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, 0, 0);
GL.EnableVertexAttribArray(0);
}
private void glControl_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e)
{
switch (e.KeyChar)
{
case 'w':
_snakeDir.X = 0;
_snakeDir.Y = -1;
break;
case 'a':
_snakeDir.X = -1;
_snakeDir.Y = 0;
break;
case 's':
_snakeDir.X = 0;
_snakeDir.Y = 1;
break;
case 'd':
_snakeDir.X = 1;
_snakeDir.Y = 0;
break;
}
//glControl.Invalidate();
}
}
}