Jump to content
  • Advertisement
  • entries
    22
  • comments
    12
  • views
    1926

About this blog

I want to explain my progress of game development in the form of step-by-step instructions for beginners.

Entries in this blog

 

BabylonJS. Usage TS in Browser

If you what to use a few TypeScript files the Browser you need to bundle them in on file. What is simple and cheap way to make it? Use Browserify + UglifyJS tools. BabylonJS is a game engine for creating 3D browser games. BJS was written in TypeScript. I this instruction I will show you how to create an example with a few TypeScript files. I do not like use: Webpack, Gulp, Grunt and so on at the beginning for simple examples. Because they require a lot of disk space on my laptop for my little examples. I create a lot of examples when I study something. For example, Gulp requires 80 MB. If I have 100 examples in TS then I will lost 800 MB. I use Browserify for creating a bundle that I can use in the browser. And I use UglifyJS to minify this bundle. You can install them only once using this commands: npm install browserify -g npm install uglify-js -g Generate the “package.json” file using this command: npm init -y For example, you have two files with names “Program.ts” and “Game.ts” in the "src" folder. Open the “package.json” file and add these lines in the “scripts” section: "scripts": { "browserify": "browserify dist/Program.js -o dist/bundle.js", "uglifyjs": "uglifyjs dist/bundle.js -o dist/bundle.min.js", "build": "tsc && npm run browserify && npm run uglifyjs" }, You can generate the “bundle.min.js” in the "dist" folder later using this command: npm run build You can read here in the documentation: https://doc.babylonjs.com/ this information: I want to keep all “.d.ts” files in the separate folder with the name “typings”. Create a "tsconfig.json" file in your root folder and add this line of code to the “tsconfig.json” folder: "types": [ "./typings/babylon" ] You need to add two “script” tags to the “index.html”: <script src="https://preview.babylonjs.com/babylon.js"></script> <script src="dist/bundle.min.js"></script> I will show the “Program.ts” file. Another part of the example you will find here: https://doc.babylonjs.com/ Scroll down and click on the "TypeScript" button. Source code: official-example-bjs-ts.zip Program.ts import { Game } from "./Game"; class Program { public static Main() { // Create the game using the 'renderCanvas'. let game = new Game('renderCanvas'); // Create the scene. game.createScene(); // Start render loop. game.doRender(); } } window.onload = () => { Program.Main(); }  

8Observer8

8Observer8

 

I study these books for GameDev

Updated 3/4/2019 I study how to write my own game engines using modern/shader OpenGL + C# and WebGL + JS5 (and TypeScript). I advice you this book: C# Game Programming: For Serious Game Creation. This book shows how to write your own game engine with maintainable code using TDD. This is a great book. It is not for GameDev only. It shows how to develop big projects in general. I know that you like to write games using Game Engines like Unity. By this book you will know basics of Linear Algebra, Shader Math, Game Physics and so on. Shader Math is important for Unity too because you need to write shaders for Unity. HLSL and GLSL are very similar. It is a great book really. Behaviour-Driven Development: 2014 - 10 - BDD in Action: Behavior-driven development for the whole software lifecycle - John Ferguson Smart. Source Code: https://www.manning.com/books/bdd-in-action Test-Driven Development: 2013 - 12 - The Art of Unit Testing: with examples in C# - 2nd Edition - Roy Osherove. Source Code: https://github.com/royosherove/aout2 Writing Games: 2010 - 06 - C# Game Programming: For Serious Game Creation. Source Code: 9781435455566.zip (121 MB) 2015 - 09 - Build your own 2D Game Engine and Create Great Web Games Using HTML5, JavaScript, and WebGL by Kelvin Sung, Jebediah Pavleas, Fernando Arnez, and Jason Pace. Source Code: https://github.com/apress/build-your-own-2d-game-engine 2017 - 10 - Pro HTML5 Games - 2nd Edition - A.R. Shankar. Source Code: https://github.com/apress/pro-html5-games-17 2018 - 04 - Unity in Action - 2nd Edition - J. Hocking. Source Code: https://www.manning.com/books/unity-in-action-second-edition Computer graphics: 2013 - 07 - WebGL Programming Guide - K. Matsuda, R. Lea. Source Code: https://sites.google.com/site/webglbook/ 2013 - 06 - Computer Graphics Principles and Practice - 3rd Edition - John F. Hughes, Andries van Dam, Morgan McGuire, David F. Sklar, James D. Foley, Steven K. Feiner, Kurt Akeley. Source Code: http://cgpp.net/about.xml Multiplayer: 2015 - 05 - Multiplayer Game Development with HTML5 - Rodrigo Silveira. Source Code: https://www.packtpub.com/code_download/21527 2015 - 10 - Multiplayer Game Programming - &nbsp;Josh Glazer, Sanjay Madhav. Source Code: https://github.com/MultiplayerBook/MultiplayerBook

8Observer8

8Observer8

C# Update ProjMatrix. OpenGL 3.1, C#

This example show how to update projection matrix: Blackjack_WinFormsOpenGL31CSharp_UpdateProjMatrix.zip private void glControl_Resize(object sender, EventArgs e) { UpdateProjMatrix(); } private void UpdateProjMatrix() { float aspect = (float)glControl.Width / glControl.Height; float worldWidth = aspect * _worldHeight; // Define the project matrix Matrix4 projMatrix = Matrix4.CreateOrthographic(worldWidth, _worldHeight, 0.1f, 1000f); GL.UniformMatrix4(_uProjMatrixLoc, false, ref projMatrix); }

8Observer8

8Observer8

C# Textures. OpenTK, WinForms, C#

My example shows a simple way to draw a few textures using OpenGL 3.1 without writing you own engine like in previous instruction: BYO2DGE. 5.1 TextureShaders. C# Project for Visual Studio: Blackjack_WinFormsOpenGL31CSharp.zip You can use this example to start making card games.

8Observer8

8Observer8

C# BYO2DGE. 5.1 TextureShaders. C#

I study this book: Build your own 2D Game Engine. I rewrite the 2D game engine from the book from JavaScript to C#. I rewrote this example from the official book repository: 5.1.TextureShaders to C#: ColoredAndTexturedObjects_OpenTKOpenGL31CSharp.zip This example draw texture and color objects at the same time. You can move a tank by arrow keys and by WASD keys.

8Observer8

8Observer8

Javascript Specs for Node.js Calculator. Jasmine JS5

Let's write an executable documentation for server side calculator. Specifications: "Add(a, b)" method must to sum positive numbers. Specification name: Add_SumPositiveNumbers_ReturnsSum "Sub(a, b)" method must to subtract positive numbers. Specification name: Sub_SubtractPositiveNumbers_ReturnsSub Instruction: Create the "calculator-nodejs-jasmine-es5" folder Run these commands to install Jasmine locally: npm init -y npm install --save-dev jasmine Create the "jasmine.json" file in the "calculator-nodejs-jasmine-es5" folder Copy the content for the "jasmine.json" file from the link: https://jasmine.github.io/setup/nodejs.html and make some changes, see the content below: jasmine.json {     "spec_dir": "src_specs",     "spec_files": [         "**/*_tests.js",         "!**/*nospec.js"     ],     "random": false } Open the "package.json" file and add the command to run tests:     "scripts": {         "test": "node node_modules/jasmine/bin/jasmine.js  --config=jasmine.json"     }, Try to run tests. Enter the command:
  npm run test You will see a message that "No specs found" Create the "src_shared" folder. Create the "calculator.js" file the the "src_shared" folder Create the "src_specs" folder. Create the "calculator_tests.js" file the the "src_specs" folder Add our specs described above to the "calculator_tests.js" file: calculator_tests.js var Calculator = require("../src_shared/calculator"); describe("Calculator", function() {     it("Add_SumPositiveNumbers_ReturnsSum", function()     {         // Arrange         var calculator = new Calculator();         var a = 5;         var b = 2;         var expectedSum = 7;           // Act         var actualSum = calculator.Add(a, b);           // Assert         expect(actualSum).toEqual(expectedSum);     });       it("Sub_SubtractPositiveNumbers_ReturnsSub", function()     {         // Arrange         var calculator = new Calculator();         var a = 5;         var b = 2;         var expectedSub = 3;           // Act         var actualSub = calculator.Sub(a, b);           // Assert         expect(actualSub).toEqual(expectedSub);     }); }); If you will run tests using the command "npm run test" then you will see the message: Let's implement these methods and run tests using the command "npm run test". You will see that the tests are passedcalculator.js var Calculator = function() {   };   Calculator.prototype.Add = function(a, b) {     return a + b; };   Calculator.prototype.Sub = function(a, b) {     return a - b; }; module.exports = Calculator;  

8Observer8

8Observer8

Javascript Specs for Calculator. Jasmine, JS5

Specifications: "Add(a, b)" method must to sum positive numbers. Specification name: Add_SumPositiveNumbers_ReturnsSum "Sub(a, b)" method must to subtract positive numbers. Specification name: Sub_SubtractPositiveNumbers_ReturnsSub Source Code: https://github.com/8Observer8/calculator-jasmine-es5 Instruction: Create the "calculator-jasmine-es5" folder Search in the Internet: cdn jasmine Create the "SpecRunner.html file in the "calculator-jasmine-es5" folder and add links on files: <!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <meta name="viewport" content="width=device-width, initial-scale=1.0">     <meta http-equiv="X-UA-Compatible" content="ie=edge">     <title>Jasmine Spec Runner v3.3.0</title>     <link rel="shortcut icon" type="image/png"         href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.0.0/jasmine_favicon.png">     <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/3.3.0/jasmine.min.css">     <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/3.3.0/jasmine.min.js"></script>     <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/3.3.0/jasmine-html.min.js"></script>     <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/3.3.0/boot.min.js"></script>     <!-- include source files here... -->     <script src="src_client/calculator.js"></script>     <!-- include spec files here... -->     <script src="src_specs/calculator_tests.js"></script> </head> </html> Note. Files "calculator_tests.js" was not created yet but we added links on them above. Create the "src_client" folder. Create the "calculator.js" file the the "src_client" folder Create the "src_specs" folder. Create the "calculator_tests.js" file the the "src_specs" folder Add our specs described above to the "calculator_tests.js" file: calculator_tests.js describe("Calculator", function() {     it("Add_SumPositiveNumbers_ReturnsSum", function()     {         // Arrange         var calculator = new Calculator();         var a = 5;         var b = 2;         var expectedSum = 7;         // Act         var actualSum = calculator.Add(a, b);         // Assert         expect(actualSum).toEqual(expectedSum);     });     it("Sub_SubtractPositiveNumbers_ReturnsSub", function()     {         // Arrange         var calculator = new Calculator();         var a = 5;         var b = 2;         var expectedSub = 3;         // Act         var actualSub = calculator.Sub(a, b);         // Assert         expect(actualSub).toEqual(expectedSub);     }); }); Open the "SpecRunner.html" in a browser to run tests. You will see errors in the browser because we did not implement the methods: Add(a, b) and Sub(a, b) Let's implement these methods and open the "SpecRunner.html" in the browser again to run tests. You will see that the tests are passed. calculator.js var Calculator = function() { }; Calculator.prototype.Add = function(a, b) {     return a + b; }; Calculator.prototype.Sub = function(a, b) {     return a - b; };  

8Observer8

8Observer8

Specs for Calculator. Jasmine, TS

In this instruction we will study how to write executable specifications for a very simple Calculator project using Jasmine testing framework. Source code on GitHub: https://github.com/8Observer8/calculator-browserify-ts If you do not have "browserify" then install it globally using this command: npm install browserify -g Note. You can read about why we need "browserify" in this project in my instruction here: Browserify TypeScript Create the folder "calculator-browserify-ts" and install the necessary package: npm init -y npm i -D @types/jasmine Search in the Internet: jasmine cdn. Create the "SpecRunner.html" file and copy founded CDN links to it: SpecRunner.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Jasmine Spec Runner v3.3.0</title> <link rel="shortcut icon" type="image/png" href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.0.0/jasmine_favicon.png"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/3.3.0/jasmine.min.css"> <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/3.3.0/jasmine.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/3.3.0/jasmine-html.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/3.3.0/boot.min.js"></script> <!-- include source files here... --> <script src="dist_client/bundle.js"></script> <!-- include spec files here... --> <script src="dist_specs/bundle.js"></script> </head> </html> Let's describe specifications for our calculator. The calculator will have four operations: Add(a, b) Sub(a, b) Mul(a, b) Div(a, b) If "b == 0" then method "Div" will throw an exception with the text: "Divided by zero". Create the "src_specs" folder. Create the "calculator_tests.ts" in the "src_specs" folder Create the "src_client" folder. Create the "calculator.ts" in the "src_client" folder Let's create the first specification in the "calculator_tests.ts": import { Calculator } from "../src_client/calculator"; describe("Calculator", () => { it("Add_PositiveNumbers_ReturnsSum", () => { // Arrange let a = 9; let b = 1; let expectedSum = 10; // Act let actualSum = Calculator.Add(a, b); // Assert expect(actualSum).toBe(expectedSum); }); }); We could try to compile it but we cannot because we do not have the "Calculator" class. Let's add it in the "src_client" folder: calculator.ts export class Calculator { public static Add(a: number, b: number): number { return a + b; } } Add the "tsconfig.specs.json" file in the root of your projects: "tsconfig.specs.json" { "compilerOptions": { "sourceMap": true, "outDir": "dist_specs" }, "include": [ "src_specs/calculator_tests.ts", "src_client/calculator.ts" ], "exclude": [ "node_modules", "src_client" ] } Now we can compile and browserify the specification. Enter two commands: tsc -p tsconfig.specs.json browserify dist_specs/src_client/calculator.js dist_specs/src_specs/calculator_tests.js -o dist_specs/bundle.js You can add these commands to the "package.json" file in the "scripts" section: "scripts": { "build_specs": "tsc -p tsconfig.specs.json", "bundle_specs": "browserify dist_specs/src_client/calculator.js dist_specs/src_specs/calculator_tests.js -o dist_specs/bundle.js", "test": "echo \"Error: no test specified\" && exit 1" And you can run these short commands: npm run build_specs npm run bundle_specs Open the "SpecRunner.html" in the browser and you will see that the specification will run. Try to add another specifications for: Sub, Mul, and Div methods. Later I will add specification for "Divide by zero" exception.

8Observer8

8Observer8

 

Browserify TypeScript

It is the most common problem for anyone who starts to study TS. They cannot include a few ".js" files after compilation to <script> tags in "index.html". It is very simple in JS. You have two files in JS and you can include them in "index.html": index.html <html> <head>     <script src="js/sayHello.js"></script>     <script src="js/main.js"></script> </head> </html> sayHello.js function sayHello(name) {     console.log("Hello, " + name); } main.js function main() {     sayHello("Ivan"); } window.onload = main; Output:
  But if you rewrite these files in TypeScript: sayHello.ts export function sayHello(name: string): void {     console.log("Hello, " + name); } main.ts import { sayHello } from './sayHello'; function main(): void {     sayHello("Ivan"); } window.onload = main; And if you compile them to JavaScript: tsc ts/main.ts ts/sayHello.ts --outDir "dist" You cannot just include this files in "index.html": <html> <head>     <script src="dist/sayHello.js"></script>     <script src="dist/main.js"></script> </head> </html> You will see this errors in the browser debug console: There are a few ways to solve this problem: You can concatenate all generated ".js" files in one bundle.js file using: Webpack, Gulp, Grund and so on. For example, see this official instruction: Gulp - TypeScript You can compile to AMD modules and use RequreJS to load them. For example, see my instruction: A few TypeScript files on Sandbox You can use Browserify to concatenate all generated ".js" files in one bundle.js file I will show you how to use Browserify. Install Browserify using this command: npm install browserify -g You can create bundle.js using this command: browserify dist/main.js dist/sayHello.js -o dist/bundle.js You will see "bundle.js" in the "dist" folder. Now you can include "bundle.js" in "index.html" using <script> tag: <html> <head>     <script src="dist/bundle.js"></script> </head> </html> Open "index.html" file in a browser and you will see "Hello, Ivan" in the browser debug console. Bonus. UglifyJS You can install uglifyjs: npm install uglify-js -g And compress your "bundle.js" to "bundle.min.js": uglifyjs dist/bundle.js -o dist/bundle.min.js Do not forget to change a script name from "bundle.js" to "bundle.min.js" in "index.html": index.html <html> <head>     <script src="dist/bundle.min.js"></script> </head> </html>  

8Observer8

8Observer8

 

A few TypeScript files on Sandbox

There is a few TypeScript files in our example. We want to place these scripts on Sandbox. This is the result on the Sandbox: click. Open the debug console in your browser to see the result: "Ctrl+Shift+J" in Chrome. You will see in the debug console this messages: Create these files on Sandbox: https://plnkr.co/edit/ Program.ts import { Rectangle } from "./Rectangle"; export class Program { public static Main(): void { // Create a rectangle let rectangle = new Rectangle(); // Draw the rectangle rectangle.Draw(); } } Program.Main(); Rectangle.ts export class Rectangle {     public x: number;     public y: number;     public constructor(x: number = 0, y: number = 0)     {         /* ... */         console.log("Rectangle was created");     }     public Draw(): void     {         /* ... */         console.log("Rectangle was drawn");     } } We need to compile these files to AMD. For this, create the "tsconfig.json" file on the Sandbox: tsconfig.json { "compilerOptions": { "module": "amd", "outDir": ".", "sourceMap": true }, "include": [ "*.ts" ], "exclude": [ "" ] } Create the RequereConfig.ts file on the Sandbox: RequireConfig.ts requirejs.config({ baseUrl: "." }); requirejs(["Program"], (Program) => { }); Add "require.min.js" in the "index.html" file: index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>A few TypeScript files on sandbox</title> <script data-main="RequireConfig" src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js"></script> </head> <body> <h3>See the console output. Press "Ctrl+Shiprt+J" in the Chrome browser.</h3> </body> </html> This is the result on the Sandbox: click. Open the debug console in your browser to see the result: "Ctrl+Shift+J" in Chrome. You will see in the debug console this messages:   P.S. If you need to work locally you need to run these commands: npm init -y npm i -D @types/requirejs P.S.S. Read this book to learn more about AMD and RequireJS: Mastering TypeScript - 2nd Edition - Nathan Rozentals

8Observer8

8Observer8

C# GUI WPF + OpenGL 3.1

We will see how to place OpenTK.GLControl on WPF window to make GUI application with 2D/3D graphics using modern OpenGL 3. This is the result VS project: EditedTriangle_WPFOpenGL31CSharp.zip How to create the project from scratch Note 1: RMB - Right Mouse Button click
Note 2: Good Color calculator for normalized values: http://doc.instantreality.org/tools/color_calculator/ Create WPF application, with the name "EditedTriangle". See the screenshot: Download OpenTK.GLControl.zip and OpenTK.zip Create the empty "Libs" folder in the solution folder (where the ".sln" is placed) Unzip "OpenTK" and "OpenTK.GLControl" folder in the "Libs" folder Add references to "OpenTK.dll" and "OpenTK.GLControl.dll". For this: RMB on "References" -> select "Add Reference..." -> select "Browse" -> click the "Browse..." button -> select DLL's. See the screenshot with the result: Add "Assemblies". For this: select "Assemblies" -> "Framework" -> check: System.Drawing System.Windows.Forms WindowsFormsIntegration , see the screenshot with the result: Click "OK" button Open the file "MainWindow.xaml" in VS. Add this line as an attribute of the "Window" element: xmlns:opentk="clr-namespace:OpenTK;assembly=OpenTK.GLControl" Place this code inside of <Window></Window> element: <Grid> <DockPanel LastChildFill="True"> <StackPanel DockPanel.Dock="Right"> <Button x:Name="buttonSetBGColor" Content="Set BG Color" Margin="5" Click="buttonSetBGColor_Click"></Button> <Button x:Name="buttonSetTRColor" Content="Set TR Color" Margin="5" Click="buttonSetTRColor_Click"></Button> </StackPanel> <WindowsFormsHost Margin="5" Initialized="WindowsFormsHost_Initialized"> <opentk:GLControl x:Name="glControl" Load="glControl_Load" Paint="glControl_Paint" /> </WindowsFormsHost> </DockPanel> </Grid> Copy the code below to "MainWindow.xaml.cs" and run the project MainWindow.xaml.cs using System; using System.Windows; using OpenTK.Graphics.OpenGL; using OpenTK.Graphics; namespace EditedTriangle { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { private int _numOfVertices = 0; private int _uColorLocation; public MainWindow() { InitializeComponent(); } private void glControl_Load(object sender, EventArgs e) { glControl.MakeCurrent(); // Get a shader program ID int shaderProgram = InitShadersAndGetProgram(); _numOfVertices = 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.945f, 0.745f, 0.356f); // Set a color for clearing the glCotrol GL.ClearColor(new Color4(0.286f, 0.576f, 0.243f, 1f)); } private void glControl_Paint(object sender, System.Windows.Forms.PaintEventArgs e) { GL.Viewport(0, 0, glControl.Width, glControl.Height); // Clear the glControl with set color GL.Clear(ClearBufferMask.ColorBufferBit); if (_numOfVertices != 0) { GL.DrawArrays(PrimitiveType.Triangles, 0, _numOfVertices); } // Swap the front and back buffers glControl.SwapBuffers(); } private int InitVertexBuffers() { float[] vertices = new float[] { 0.0f, 0.5f, -0.5f, -0.5f, 0.5f, -0.5f }; int n = 3; int vbo; GL.GenBuffers(1, out vbo); // Get an array size in bytes GL.BindBuffer(BufferTarget.ArrayBuffer, vbo); int sizeInBytes = vertices.Length * sizeof(float); // Send the vertex array to a video card memory GL.BufferData(BufferTarget.ArrayBuffer, sizeInBytes, vertices, BufferUsageHint.StaticDraw); // Config the aPosition variable GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, 0, 0); GL.EnableVertexAttribArray(0); return n; } 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 buttonSetBGColor_Click(object sender, RoutedEventArgs e) { var dialog = new System.Windows.Forms.ColorDialog(); if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) { GL.ClearColor(dialog.Color); glControl.Invalidate(); } } private void buttonSetTRColor_Click(object sender, RoutedEventArgs e) { var dialog = new System.Windows.Forms.ColorDialog(); if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) { float r = dialog.Color.R / 255f; float g = dialog.Color.G / 255f; float b = dialog.Color.B / 255f; GL.Uniform3(_uColorLocation, r, g, b); glControl.Invalidate(); } } } } MainWindow.xaml <Window x:Class="EditedTriangle.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:EditedTriangle" xmlns:opentk="clr-namespace:OpenTK;assembly=OpenTK.GLControl" mc:Ignorable="d" Title="Triangle" Height="256" Width="290"> <Grid> <DockPanel LastChildFill="True"> <StackPanel DockPanel.Dock="Right"> <Button x:Name="buttonSetBGColor" Content="Set BG Color" Margin="5" Click="buttonSetBGColor_Click"></Button> <Button x:Name="buttonSetTRColor" Content="Set TR Color" Margin="5" Click="buttonSetTRColor_Click"></Button> </StackPanel> <WindowsFormsHost Margin="5"> <opentk:GLControl x:Name="glControl" Load="glControl_Load" Paint="glControl_Paint" /> </WindowsFormsHost> </DockPanel> </Grid> </Window>  

8Observer8

8Observer8

 

C++ Set Up GMock for TDD. C++, VS

If you need the example how to set up GTest without GMock you can see this example: Set Up GTest for TDD. C++, VS In this example: PersonService_GMockCpp.zip  we will see how to use Google Mock for creating mock objects and how to write a unit test for testing exceptions. I translated this example from the TypeScript tutorial: Using Jasmine Spies to Create Mocks and Simplify the Scope of Your Tests You can download and run the example. Google Test library is included in the project as source folder and it is placed in "Libs" folder. Note. If you have another version of VS then before you will run unit tests you need to select VS 2017, like in this screenshot: You need to: open the solution. The solution is file with name: "PersonService.sln" select your version of VS, for example VS 2017 instead of VS 2015 as in the screenshot above make the "PersonService.UnitTests" project as "StartUp Project". For this: make right mouse button click on the "PersonService.UnitTests" project -> select "Set as StartUp Project" press Ctrl+F5 to run unit tests How to set up GTest and GMock from scratch I use version 1.8.1 of GTest and GMock. You can download these libraries here: gtest-1.8.1.zip gmock-1.8.1.zip You need to create a new solution. Write some name for you solution and for your project, for example: PersonService (for the solution and for the project). Pay attention, you need to check "Create directory for solution" when you create a new solution and project. Note. RMB - Right Mouse Button click. You can set up the project from scratch like this: Create a new solution with the name "PersonService". Check "Create directory for solution". Write name "PersonService" for project. Set a new project as: empty console project, without the "precompiled headers". The "PersonService" project will be a project under test Copy and add these files to the "PersonService": IDataContext.h #pragma once #include "Person.h" class IDataContext { public: virtual ~IDataContext() {}; virtual void SavePerson(const Person &person) = 0; }; IPersonValidator.h #pragma once #include "Person.h" class IPersonValidator { public: virtual ~IPersonValidator() {}; virtual bool IsValid(const Person &person) = 0; }; Person.h #pragma once #include <string> class Person { public: int number; std::string name; }; PersonService.h #pragma once #include "Person.h" #include "IPersonValidator.h" #include "IDataContext.h" class PersonService { public: PersonService(IPersonValidator *validator, IDataContext *dataContext); void Save(const Person &person); private: IPersonValidator *_validator; IDataContext *_dataContext; }; PersonService.cpp #include "PersonService.h" PersonService::PersonService(IPersonValidator *validator, IDataContext *dataContext) { _validator = validator; _dataContext = dataContext; } void PersonService::Save(const Person &person) { if (_validator->IsValid(person)) { _dataContext->SavePerson(person); } else { throw std::runtime_error("Person is not valid"); } } Add a new project in your solution. For this: RMB on the solution name -> "Add" -> "New Project..." -> Set a name: "PersonService.UnitTests". You project must be: console, empty and without the "precompiled headers". The "PersonService.UnitTests" project will have unit tests for the "PersonService" project Create the "Libs" folder in your solution folder (where your ".sln" file is placed) Copy the "gtest-1.8.1" and the "gmock-1.8.1" folders to the "Libs" folder from these archives: gtest-1.8.1.zip, gmock-1.8.1.zip Open the project properties of the project "PersonService.UnitTests" (RMB on the project name and select "Properties") and add these lines to "C/C++" -> "General" -> "Additional Include Directories": $(SolutionDir)Libs\gtest-1.8.1\include $(SolutionDir)Libs\gtest-1.8.1 $(SolutionDir)Libs\gmock-1.8.1\include $(SolutionDir)Libs\gmock-1.8.1 $(SolutionDir)PersonService Click "Apply" and "OK" buttons Make "PersonService.UnitTests" as "StartUp Project". For this: RMB on the "PersonService.UnitTests" project -> select "Set as StartUp Project" Add as "Existing Item" this file: "Libs\gtest-1.8.1\src\gtest-all.cc" and "Libs\gmock-1.8.1\src\gmock-all.cc". For this: RMB on the "PersonService.UnitTests" project -> select "Add" -> "Existing Item..." -> choose these files: "Libs\gtest-1.8.1\src\gtest-all.cc" and "Libs\gmock-1.8.1\src\gmock-all.cc" Add as "Existing Item" the files that you will test. For example, in this case: "PersonService.cpp" from the "PersonService" project Copy and add these files to the "PersonService.UnitTests": main.cpp #include <gmock/gmock.h> #include <gtest/gtest.h> int main(int argc, char **argv) { testing::InitGoogleMock(&argc, argv); return RUN_ALL_TESTS(); } PersonServiceTests.cpp #include <gmock/gmock.h> #include <gtest/gtest.h> using ::testing::_; using ::testing::Return; #include "IDataContext.h" #include "IPersonValidator.h" #include "Person.h" #include "PersonService.h" class MockDataContext : public IDataContext { public: MOCK_METHOD1(SavePerson, void(const Person &person)); }; class MockPersonValidator : public IPersonValidator { public: MOCK_METHOD1(IsValid, bool(const Person &person)); }; TEST(PersonService, IsValid_ValidPerson_CallSavePerson) { MockDataContext dataContext; MockPersonValidator validator; PersonService service = PersonService(&validator, &dataContext); Person validPerson; EXPECT_CALL(validator, IsValid(_)) .WillOnce(Return(true)); EXPECT_CALL(dataContext, SavePerson(_)).Times(1); service.Save(validPerson); } TEST(PersonService, IsValid_NotValidPerson_ThrowException) { MockDataContext dataContext; MockPersonValidator validator; PersonService service = PersonService(&validator, &dataContext); Person validPerson; EXPECT_CALL(validator, IsValid(_)) .WillOnce(Return(false)); EXPECT_CALL(dataContext, SavePerson(_)).Times(0); //EXPECT_THROW({ // service.Save(validPerson); //}, std::runtime_error); try { service.Save(validPerson); FAIL() << "Exptected std::runtime_error"; } catch (std::runtime_error const & err) { EXPECT_EQ(err.what(), std::string("Person is not valid")); } catch (...) { FAIL() << "Exptected std::runtime_error"; } } Run the "PersonService.UnitTests" project by pressing on "Ctrl+F5" buttons You will see that you tests passed:

8Observer8

8Observer8

 

C++ Set Up GTest for TDD. C++, VS

I made an example of project in VS 2015: SortFunctions.zip  This project will show you how set up Google Test in Visual Studio and run simple unit tests. Note. If you have another version of VS then before you will run unit tests you need to select VS 2017, like in this screenshot: Google Test library is included in the project as source folder and it is placed in "Libs" folder. You need to: open the solution. The solution is file with name: "SortFunctions.sln" select your version of VS, for example VS 2017 instead of VS 2015 as in screenshot above make the "SortFunction_UnitTests" project as "StartUp Project". For this: make right mouse button click on the "SortFunction_UnitTests" project -> select "Set as StartUp Project" press Ctrl+F5 to run unit tests You will see this settings in the "SortFunction_UnitTests" project properties: $(SolutionDir)Libs\gtest-1.8.1\include $(SolutionDir)Libs\gtest-1.8.1 $(SolutionDir)SortFunction This solution include two projects: SortFunctions - this project contains modules that we want to test. For example, bubbleSort() method SortFunctions_UnitTests - this project contains unit tests The "SortFunctions" project has two files: SortFunctions.h #pragma once extern void bubbleSort(int *array, unsigned int amount); extern void countingSort(int *array, unsigned int amount); SortFunctions.cpp #include "SortFunctions.h" void bubbleSort(int *array, unsigned int amount) { } void countingSort(int *array, unsigned int amount) { } The "SortFunctions_UnitTests" project has tests. For example, this is the "bubbleSortTests.cpp" with two tests. The first test is for positive numbers and the second test is for negative numbers: bubbleSortTests.cpp #include <gtest/gtest.h> #include "SortFunctions.h" TEST(bubbleSortTest, AllPositiveElements) { // Arrange const unsigned int amount = 5; int actualArray[amount] = { 5, 3, 10, 2, 7 }; int expectedArray[amount] = { 2, 3, 5, 7, 10 }; // Act bubbleSort(actualArray, amount); // Assert for (size_t i = 0; i < amount; i++) { ASSERT_EQ(expectedArray[i], actualArray[i]); } } TEST(bubbleSortTest, AllNegativeElements) { // Arrange const unsigned int amount = 5; int actualArray[amount] = { -5, -3, -10, -2, -7 }; int expectedArray[amount] = { -10, -7, -5, -3, -2 }; // Act bubbleSort(actualArray, amount); // Assert for (size_t i = 0; i < amount; i++) { ASSERT_EQ(expectedArray[i], actualArray[i]); } }    

8Observer8

8Observer8

 

Python Set up OpenGL 3 for Python

You need to type commands in the console terminal (you need to run the console terminal as administrator): pip install GLFW pip install PyOpenGL pip install Pyrr pip install NumPy Pyrr - for trigonometry and linear algebra NumPy - for special arrays for OpenGL functions You will see how to use these libraries in video tutorial below. Now you are ready to create simple games in Python and OpenGL. See this video tutorial about basics of OpenGL. You can start from the second lesson: Modern OpenGL programming in Python - part 02 - creating a GLFW window After studying of this tutorial you can rewrite a prototype of the Snake 2D Tutorial: Python Snake Game from deprecated/legacy OpenGL 1 to modern OpenGL 3 and make it 3D with textures and lighting. And you will be able to load 3D models from Blender because the video tutorial above covers loading 3D models from Blender. How to create an empty window using GLFW: Copy the file below ("main.py") Place glfw3.dll (glfw3.dll.zip) with "main.py" Run the application. For this run the command in the console terminal: python main.py main.py import glfw def main(): # Initialize glfw if not glfw.init(): return window = glfw.create_window(800, 600, "My OpenGL Window", None, None) if not window: glfw.terminate() return glfw.make_context_current(window) while not glfw.window_should_close(window): glfw.poll_events() glfw.swap_buffers(window) glfw.terminate() if __name__ == "__main__": main()  

8Observer8

8Observer8

 

Javascript Deploying Game Server on Heroku

Heroku is a free hosting. Let's deploy the application from this instruction: Emit and Broadcast JSON You need to register on https://heroku.com/  and complete this official instruction: Getting Started on Heroku with Node.js Go here: https://dashboard.heroku.com/apps  and create a new application. For this you need to press "New" button in right top corner. Select "create new app". I created a new application with the name: red-game. In this case, my application will be available from this domain: https://red-game.herokuapp.com/  When you create your application at first time you will see this web page: Go to your work folder using the console terminal. You need to have the file "app.js" and "client" folder in your work folder. Indeside "client" folder you need  to have "index.html" from the instruction above. But we will change a few lines in "app.js". We will change a port number. Replace this line of code: var port = 8080; On this line: var port = process.env.PORT || 3000; Note. If you do not want to read the previous instruction you can just copy-past this files and install packages using this commands: app.js var express = require("express"); var app = express(); var server = require("http").Server(app); app.get("/", function(req, res) { res.sendFile(__dirname + "/client/index.html"); }); app.use("/client", express.static(__dirname + "/client")); var port = process.env.PORT || 3000; server.listen(port); console.log("Server started. Port = " + port); var io = require("socket.io")(server, {}); var shortid = require('shortid'); io.sockets.on("connection", function(socket) { var clientName = shortid.generate(); console.log("client was connected, name = " + clientName); socket.on("getMyName", function() { socket.emit("onGetMyName", { name: clientName }); }); socket.on("sendMyNameToAllClients", function() { socket.broadcast.emit("onSendMyNameToAllClients", { name: clientName }); console.log(clientName); }); }); index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js"></script> <title>Multiplayer Snake</title> </head> <body> <button onclick="getMyName();">Get My Name</button> <button onclick="sendMyNameToAllClients();">Send my Name to all Clients</button> <script> var socket = io(); socket.emit("hello", { message: "hello from client!" }); socket.on("onGetMyName", function(data) { console.log("My Name: " + data.name); }); socket.on("onSendMyNameToAllClients", function(data) { console.log("Name of another client: " + data.name); }); function getMyName() { socket.emit("getMyName"); } function sendMyNameToAllClients() { socket.emit("sendMyNameToAllClients"); } </script> </body> </html> Add a new file: .gitignore (pay attention, "." is a part of the name) The file ".gitignore" should have this content: Add the start script command in package.json here: "scripts": { "start": "app.js", "test": "echo \"Error: no test specified\" && exit 1" }, Enter these commands in the command terminal. Pay attention: you need to write your application name instead of "red-game": Now we can to add new files, commit them and push/deploy our application on Heroku. Enter these commands: Go to your application in the browser. For example, in my case: https://red-game.herokuapp.com/ Open the browser console, for example in Chrome: "Ctrl+Shift+J". Click on buttons and you will see messages in the browser console. Open two tabs and press "Send my Name to all Clients" button. You will see a messages in the console in the second client tab. P.S. You need to empty cache when you change your client side scripts of files: RMB on "Reload" button and select "Empty Cache and Hard Reload"

8Observer8

8Observer8

C# GUI WinForms + OpenGL 3.1

This my example how to use GUI WinForms and OpenTK.GLControl Source for Visual Studio (including DLLs): EditedTriangle_WinFormsOpenGL31CSharp.zip EXE: EditedTrianlge_WinFormsOpenGL31CSharp_EXE.zip Separated DLL's for references: OpenTK.zip, OpenTK.GLControl.zip Standard WinForms Color Dialog:          

8Observer8

8Observer8

 

Javascript Socket.io Connection, JS/ES5

I will try to explain a process in step-by-step how to prepare server but it will be good if you will watch this video tutorial in parallel: Setup & Sending Files. Node.js Tutorial Guide At first test let's create a very simple server script locally that will write in the console terminal "client connected" when client will be connected to the server. You need to download and install Node.js: https://nodejs.org/en/download/ Create a folder for your project and go to the folder using CMD. Write this command in the console terminale to create package.json: npm init -y Install "socket.io" and "express" to a local "node_modules" folder. Enter the command for this: npm install --save socket.io express Now we are ready to write a simple server script and a client script. I use VSCode editor: https://code.visualstudio.com/ Open your prefered code editor. If you use VSCode you can run in the console terminal this command from your project folder: code . (you need note that it is "code" word and dot ".") Create two folders in your project: "server" and "client". Create the "server.js" file in the "server" folder. Create the "client.js" file in the "client" folder. Create the "index.html" file in the "client" folder. index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Multiplayer Snake</title> </head> <body> <h1>Hello, World</h1> </body> </html> Create the file: "app.js" in the root of your project: app.js var express = require("express"); var app = express(); var server = require("http").Server(app); app.get("/", function(req, res) { res.sendFile(__dirname + "/client/index.html"); }); app.use("/client", express.static(__dirname + "/client")); var port = 8080; server.listen(port); console.log("Server started. Port = " + port); You can run this script from the console terminal by the command: node app.js You will see a message: "Server started. Port = 8080" Write the address in the browser: localhost:8080 You will see "Hello, World" on the web page. Let's find "cnd socket.io" url in the Internet. Add this code to the index.html: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js"></script> <title>Multiplayer Snake</title> </head> <body> <h1>Hello, World</h1> <script> var socket = io(); </script> </body> </html> Create a socket callback in app.js: app.js var express = require("express"); var app = express(); var server = require("http").Server(app); app.get("/", function(req, res) { res.sendFile(__dirname + "/client/index.html"); }); app.use("/client", express.static(__dirname + "/client")); var port = 8080; server.listen(port); console.log("Server started. Port = " + port); var io = require("socket.io")(server, {}); io.sockets.on("connection", function(socket) { console.log("socket connection"); }); Run the server: node app.js Write the address in the browser: localhost:8080 You will see the message "socket connection" in the console.

8Observer8

8Observer8

 

Javascript Emit and Broadcast JSON

I will show differences between "emit" and "broadcast" on server side. In short: "emit" sends JSON data to a connected client "broadcast" sends JSON data to every client except the connected client At first time create a connection between the server and the client using this instruction: 101. Socket.io Connection, JS/ES5 We have this callback function in "app.js": io.sockets.on("connection", function(socket) { console.log("client was connected"); }); We will generate a name for a connected client on the server side. Client will get his own name from the server and will show it on the screen in a browser. We will use "shortid" module for generation names for clients. Install "shortid" package. Type this command in the console from your project folder: npm install --save shortid We will use "shortid" module to generate a random unique name. Let's show generated name: app.js var shortid = require('shortid'); io.sockets.on("connection", function(socket) {    var clientName = shortid.generate();     console.log("client was connected, name = " + clientName); ); You will see a message in the console, like this: Let's create two buttons: "Get My Name" and "Send my Name to all Clients". Copy this code to server and to client sides. Do not forget to run the server by command: node app.js After this you need to open two clients in different browser tabs: http://localhost:8080/. Open the browser console, for example in Chrome: "Ctrl+Shift+J" and clear "cache" by RMB on "Reload" button and select "Empty Cache and Hard Reload". And do not forget reload the server when you made changes on the server side code. app.js var express = require("express"); var app = express(); var server = require("http").Server(app); app.get("/", function(req, res) { res.sendFile(__dirname + "/client/index.html"); }); app.use("/client", express.static(__dirname + "/client")); var port = 8080; server.listen(port); console.log("Server started. Port = " + port); var io = require("socket.io")(server, {}); var shortid = require('shortid'); io.sockets.on("connection", function(socket) { var clientName = shortid.generate(); console.log("client was connected, name = " + clientName); socket.on("getMyName", function() { socket.emit("onGetMyName", { name: clientName }); }); socket.on("sendMyNameToAllClients", function() { socket.broadcast.emit("onSendMyNameToAllClients", { name: clientName }); console.log(clientName); }); }); index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js"></script> <title>Multiplayer Snake</title> </head> <body> <button onclick="getMyName();">Get My Name</button> <button onclick="sendMyNameToAllClients();">Send my Name to all Clients</button> <script> var socket = io(); socket.emit("hello", { message: "hello from client!" }); socket.on("onGetMyName", function(data) { console.log("My Name: " + data.name); }); socket.on("onSendMyNameToAllClients", function(data) { console.log("Name of another client: " + data.name); }); function getMyName() { socket.emit("getMyName"); } function sendMyNameToAllClients() { socket.emit("sendMyNameToAllClients"); } </script> </body> </html>  

8Observer8

8Observer8

Algorithm Use UML Instead Of Flowcharts

Updated: 2/15/2019
- added "if...else" Double Selection Statement Visual Studio Enterprise 2015 has build in tool for creating UML Activity Diagrams. We can use this tool for creating flowcharts for describing algorithms. Creating UML Activity Diagram: Select: "File" -> "New" -> "Project..." -> "Modeling Projects" Write a name, for example: UseUMLInsteadOfFlowcharts_ModelingProject Press "OK" button Right Click on a name of the project Select: "Add" -> "New Item..." -> "UML Activity Diagram" Write a name, for example: UseUMLInsteadOfFlowcharts.activitydiagram Press "Add" button To add new items, drag them from the "Toolbox" I get these examples from the book: 2016 - 08 - C# 6 for Programmers - 6th Edition - Paul Deitel, Harvey Deitel. Code Examples: link 1. "if" Single-Selection Statement int studentGrade = 70; if (studentGrade >= 60) { Console.WriteLine("Passed"); } 2. "if...else" Double-Selection Statement int studentGrade = 70; if (studentGrade >= 60) { Console.WriteLine("Passed"); } else { Console.WriteLine("Failed"); } 3. "while" Iteration Statement int product = 3; while (product <= 100) { product = 3 * product; } 4. "for" Iteration Statement for (int counter = 1; counter <= 10; ++counter) { Console.Write($"{counter} "); }

8Observer8

8Observer8

Javascript 101. Snake. JS/ES5, Canvas API

Step-by-step instruction of Snake 2D using JavaScript/ES5, Canvas API 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: Sandbox Note. I take ideas from this tutorial: Python Snake Game We need to get a canvas context from the canvas element. The canvas context is an object that have methods for working with graphics. Lets' create a script element inside of "index.html" file, get the canvas element, get the canvas context and set a clear color to black: Sandbox !DOCTYPE html> <head> <title>Snake</title> </head> <body> <canvas width="200" height="200" id="gameCanvas"> Your browser does not support HTML5 Canvas. Please shift to a newer browser. </canvas> <script> var canvas, ctx; canvas = document.getElementById("gameCanvas"); ctx = canvas.getContext("2d"); // Clear the canvas element ctx.clearRect(0, 0, canvas.width, canvas.height); // Set a clear color ctx.fillStyle = "black"; // Fill the canvas element with the clear color ctx.beginPath(); ctx.rect(0, 0, canvas.width, canvas.height); ctx.fill(); </script> </body> </html> Write a function for drawing a rectangle and call this function for test: Sandbox <!DOCTYPE html> <head> <title>Snake</title> </head> <body> <canvas width="200" height="200" id="gameCanvas"> Your browser does not support HTML5 Canvas. Please shift to a newer browser. </canvas> <script> var canvas, ctx; canvas = document.getElementById("gameCanvas"); ctx = canvas.getContext("2d"); // Clear the canvas element ctx.clearRect(0, 0, canvas.width, canvas.height); // Set a clear color ctx.fillStyle = "black"; // Fill the canvas element with the clear color ctx.beginPath(); ctx.rect(0, 0, canvas.width, canvas.height); ctx.fill(); // Call a drawRectangle() function drawRectangle(0, 20, "green", 20); function drawRectangle(x, y, color, size) { ctx.beginPath(); ctx.rect(x, y, size, size); ctx.fillStyle = color; ctx.fill(); } </script> </body> </html> 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: setInterval(gameLoop, 500) function gameLoop() { console.log("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. function gameLoop() { update(); draw(); } function update() { console.log("update"); } function draw() { drawSnake(); drawFood(); } function drawSnake() { console.log("draw snake"); } function drawFood() { console.log("draw food"); } List data structure is ideal for keeping snake cells coordinates: // Snake list of (x, y) positions var snake = [{ x: 10, y: 10 }]; Create a function for clearing the canvas: function draw() { clearCanvas("black") drawSnake(); drawFood(); } function clearCanvas(color) { // Clear the canvas element ctx.clearRect(0, 0, canvas.width, canvas.height); // Set a clear color ctx.fillStyle = color; // Fill the canvas element with the clear color ctx.beginPath(); ctx.rect(0, 0, canvas.width, canvas.height); ctx.fill(); } Draw the snake: function drawSnake() { snake.forEach(cell => { drawRectangle(cell.x, cell.y, "green"); }); } Sandbox For moving the snake we need to create the "snakeDir" variable: // Snake movement direction var snakeDir = { x: 10, y: 0 }; The snake moving is very simple. Please, read comments: function update() { // Calc a new position of the head var newHeadPosition = { x: snake[0].x + snakeDir.x, y: snake[0].y + snakeDir.y } // Insert new position in the beginning of the snake list snake.unshift(newHeadPosition); // Remove the last element snake.pop(); } Sandbox I will explain eating food later. But you can read comments in the code. The result code: Sandbox <!DOCTYPE html> <head> <title>Snake</title> </head> <body> <canvas width="200" height="200" id="gameCanvas"> Your browser does not support HTML5 Canvas. Please shift to a newer browser. </canvas> <p>Click on this window to activate keyboard handlers to control the snake by WASD and arrow keys.</p> <script> // Canvas element and context var canvas, ctx; // Snake list of (x, y) positions var snake = [{ x: 10, y: 10 }]; // Snake movement direction var snakeDir = { x: 10, y: 0 }; // Snake step var cellSize = 10; // Food var food = { x: 0, y: 0 }; canvas = document.getElementById("gameCanvas"); ctx = canvas.getContext("2d"); // Field size var fieldWidth = canvas.width; var fieldHeight = canvas.height; // Generate an initial random position for the food generateFood(); // Set a key handler document.onkeydown = (event) => { switch (event.keyCode) { case 38: // Up case 87: // W snakeDir.x = 0; snakeDir.y = -cellSize; break; case 37: // Left case 65: // A snakeDir.x = -cellSize; snakeDir.y = 0; break; case 39: // Right case 68: // D snakeDir.x = cellSize; snakeDir.y = 0; break; case 40: // Down case 83: // S snakeDir.x = 0; snakeDir.y = cellSize; break; } }; // Run Game Loop setInterval(gameLoop, 200) function gameLoop() { update(); draw(); } function update() { // Calc a new position of the head var newHeadPosition = { x: snake[0].x + snakeDir.x, y: snake[0].y + snakeDir.y } // Insert new position in the beginning of the snake list snake.unshift(newHeadPosition); // Remove the last element snake.pop(); // Check a collision with the snake and the food if (snake[0].x === food.x && snake[0].y === food.y) { snake.push({ x: food.x, y: food.y }); // Generate a new food position generateFood(); } } function draw() { clearCanvas("black") drawSnake(); drawFood(); } function clearCanvas(color) { // Clear the canvas element ctx.clearRect(0, 0, canvas.width, canvas.height); // Set a clear color ctx.fillStyle = color; // Fill the canvas element with the clear color ctx.beginPath(); ctx.rect(0, 0, canvas.width, canvas.height); ctx.fill(); } function drawSnake() { snake.forEach(cell => { drawRectangle(cell.x, cell.y, "green", cellSize); }); } function drawFood() { drawRectangle(food.x, food.y, "orange", cellSize); } function drawRectangle(x, y, color, size) { ctx.beginPath(); ctx.rect(x, y, size, size); ctx.fillStyle = color; ctx.fill(); } function generateFood() { food.x = 10 * getRandomInt(0, fieldWidth / 10 - 1); food.y = 10 * getRandomInt(0, fieldHeight / 10 - 1); } // Returns a random integer between min (inclusive) and max (inclusive) function getRandomInt(min, max) { min = Math.ceil(min); max = Math.floor(max); return Math.floor(Math.random() * (max - min + 1)) + min; } </script> </body> </html>  

8Observer8

8Observer8

C# 101. Snake. WinForms, OpenGL 3.1

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(); } } }  

8Observer8

8Observer8

C# 101. Snake. WinForms, GDI+

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: 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); } 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; } } } }  

8Observer8

8Observer8

  • Advertisement
×

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!