Magya - Hello World Tutorial

Welcome to the exciting world of game development!


In this tutorial, we will guide you through creating a simple modified version of the Pong game using Magya.

Prequisites

Before we begin, make sure you have an IDE situable for C# programming.

(Get started with RiderVS Code or Visual Studio)


Also, install the Magya NuGet package in your project.

(Here's how)

Script

In this tutorial, we are gonna make a game where the user needs to bounce a ball in a paddle without letting it fall.


At the end, you will be able to customize the finished game and even transform it into a whole new one.


Now let's get started with the first part of this tutorial.

Using the enigne

Creating a window

After installing the NuGet package in your project, create a .cs file (you can name it whatever you want).


Now put this using statement on the beginning of the file:


using static Magya;


Initialize the engine:


Init();


And create a window:


Window win = new(

    "Window",

    Window.POS_CENTERED,

    Window.POS_CENTERED,

    800, 600

);


With only this code, the window not stay open until you close it, but it will just close immediately as the program will end.


To avoid this, put a main loop after the creation of the window.


In Magya, the main loop looks like this:


while (KeepOpen) {

    Update();

}


Note: The engine keeps track of the window and updates the `KeepOpen` variable when the window should be closed through the `Update()` function.

After the main loop, the window should be closed and the engine should shut down automatically, but it's still good practice calling the functions Quit() and Window.Destroy() at the end of the program.


win.Destroy();

Quit();


Now, the code should look like this:


using static Magya;


internal class Program {


    static void Main(string[] args) {


        Window win = new(

            "Window",

            Window.POS_CENTERED,

            Window.POS_CENTERED,

            800, 600

        );


        while (KeepOpen) {

            Update();

        }


        win.Destroy();

        Quit();


    }

}


Now, run the code and a window should appear.


You now have created your first Magya game!


Except that this is not a game. It's just a blank window.


We need to show something on the window.

So let's move to the second part of this tutorial.

Drawing shapes


First, just after the creation of the window, create a camera:


// Arguments are respectively the X and Y positions and the distance of the camera.

Camera cam = new(0, 0, 20);


Create a scene, passing an empty list as argument: (we will change this later)


Scene scene = new([]);


In the main loop, put the functions Window.BeginDrawing() and EndDrawing():


while (KeepOpen) {

    Update();

    // Argument is the background RGB color (dark gray).

    win.BeginDrawing(new(0x0d, 0x0d, 0x0d));

    win.EndDrawing();

}


Update the scene after the Update() function:


scene.Update();


And draw the scene between the functions Window.BeginDrawing() and EndDrawing():


win.DrawScene(scene, cam);


Now the program will draw and update everything in the scene.

But it still won't draw nothing cause there's nothing on the scene.


Scenes are really just containers for sprites.


Sprites can be represented as "objects" in the game, it can be a player, an item, a button or anything else.

Sprites can also represent multiple objects of the game at once, so they can serve as a parallax background or particles without having to actually create a lot of sprites.


Let's now create some sprites.


First, outside the Main() function, define two classes called Ball and Paddle which inherits the Sprite class:


internal class Ball : Sprite {


}


internal class Paddle : Sprite {


}


This is how to "define" a sprite type.

Inside the Ball class, override the Draw() method and call the Window.DrawCircle() function:


public override void Draw(Window win, Camera cam) {

    // Arguments are respectively a circle, a RGB color (light red) and a camera.


    win.DrawCircle(new(0, 0, 1), new(0xff, 0x80, 0x80), cam);

}


The Sprite.Draw() method is called on Window.DrawScene().

And inside the Paddle class, do the same thing but call the Window.DrawRect() function instead:


public override void Draw(Window win, Camera cam) {

    // Arguments are respectively a rectangle, a RGB color (white) and a camera.


    win.DrawRect(new(0, -8, 5, 1), new(0xff, 0xff, 0xff), cam);

}


Back to the Main() function, create a Ball and Paddle sprites before creating the scene:


Ball ball = new();

Paddle paddle = new();


Change the line that creates a scene to include the sprites in the list:


// Scene scene = new([]); -> Old

Scene scene = new([ paddle, ball ]); // New


The sprites are updated in the order of the list.

Now, the code should look like this:


using static Magya;


internal class Ball : Sprite {


    public override void Draw(Window win, Camera cam) {

        win.DrawCircle(new(0, 0, 1), new(0xff, 0x80, 0x80), cam);

    }

}


internal class Paddle : Sprite {


    public override void Draw(Window win, Camera cam) {

        win.DrawRect(new(0, -8, 5, 1), new(0xff, 0xff, 0xff), cam);

    }

}


internal class Program {


    static void Main(string[] args) {


        Window win = new(

            "Window",

            Window.POS_CENTERED,

            Window.POS_CENTERED,

            800, 600

        );

        Camera cam = new(0, 0, 20);

        Ball ball = new();

        Paddle paddle = new();

        Scene scene = new([ paddle, ball ]);


        while (KeepOpen) {

            Update();

            scene.Update();


            win.BeginDrawing(new(0x0d, 0x0d, 0x0d));

            win.DrawScene(scene, cam);

            win.EndDrawing();

        }


        win.Destroy();

        Quit();


    }

}


Now run the code and you should se a ball and a paddle in a dark gray background.


It looks like the ball is frozen in time, which is not what we want.


Now let's make the sprites move and make this an actual, playable game.


You can change the values freely, like the RGB colors, the camera and the rectangle positions to adapt the visual to your liking.

Moving the sprites


Inside the Ball class, override the Update() method:


public override void Update() {


}


The `Sprite.Update()` method is called on Scene.Update().

Here we will update the speed and position of the ball.


To do that, create four double variables outside the Update() method to store the x and y positions and velocities:


internal class Ball : Sprite {

    private double x = 0, y = 0, vx = 0, vy = 0;


    /* other stuff */

}


We are going to store the velocities in variables cause we don't want the ball to be always moving at a constant velocity.


Why? Cause with a non-constant Y velocity, we can make the ball to appear that is being pushed down by a force (the gravity) and accelerating more and more until it hits the paddle.


Now, go to the `Paddle` class and create one public double variable to store the X position of the paddle (the Ball class will access this later):


internal class Paddle : Sprite {

    public double x = 0;


    /* other stuff */

}


In this case, creating a variable for the y position is optional, as it is constant.

Create a constructor that assigns a string to the variable Tag:


public Paddle() {

    // The Tag variable is already declared in the Sprite class.


    Tag = "paddle";

}


The tag serves to sprites be able to recognize another sprite to access data from it.

Go to the Main() function and change the line that creates the scene to pass a tag as a parameter:


// Scene scene = new([paddle, ball]); -> Old

Scene scene = new([paddle, ball], "scene"); // New


Now, back to the Update() method in the Ball class, access the scene and the paddle:


Scene scene = GetScene("scene");

Paddle paddle = (Paddle)scene.GetSprite("paddle");


Note that we have just assigned the tags to the scene and the paddle.

After getting the paddle, create an instance of the class Random:


Random rnd = new();


Check if the ball is at the right X and Y position to hit the paddle, if it is, then change the ball's Y velocity to a random positive number (bounce), if not, increase the ball's Y velocity to go down (gravity):


// Remember: The Paddle.Draw() method draws a 5x1 rectangle at a Y position of -8,

//           and the Ball.Draw() method draws a circle of radius 1.

if (x >= paddle.x - 5d / 2 - 1d / 2 &&

    x <= paddle.x + 5d / 2 + 1d / 2 &&

    y <= -8 + 1d / 2 + 1 &&

    y >= -8 - 1d / 2) vy = 20 + rnd.NextDouble() * 10;

else vy -= 50 * DeltaTime;


The variable `DeltaTime` of the engine is mesuared in seconds and, in this case, the ball will get a Y velocity of 5 units per second in 1 second.
It's ok if you don't understand the math stuff, you can just copy the code.

Then, update the position of the ball based on it's velocity:


x += vx * DeltaTime;

y += vy * DeltaTime;

Go to the Draw() method and change the line that draws a circle to draw it on the X and Y positions stored in the variables x and y:


// win.DrawCircle(new(0, 0, 1), new(0xff, 0x80, 0x80), cam); -> Old

win.DrawCircle(new(x, y, 1), new(0xff, 0x80, 0x80), cam); // New

Now we will make the ball go sideways.

Modify the if statement so that when the ball hits the paddle, it goes in a random direction at a random speed:


if (x >= paddle.x - 5d / 2 - 1d / 2 &&

    x <= paddle.x + 5d / 2 + 1d / 2 &&

    y <= -8 + 1d / 2 + 1 &&

    y >= -8 - 1d / 2) { /* vy = 20 + rnd.NextDouble() * 10; -> Old */

        // New

        vy = 20 + rnd.NextDouble() * 10;

        vx = (rnd.NextDouble() - 0.5) * 2 * 30;

} else vy -= 50 * DeltaTime;

After the `if` statement and before the position update of the ball, check if the collding with the wall, if it is, then change the direction of the X velocity of the ball:


// Remember: The Ball.Draw() method draws a circle with a radius of 1.

if (x <= -10 + 1) vx = Math.Abs(vx);

if (x >= 10 - 1) vx = -Math.Abs(vx);


The minimum area that the camera can see is a square with the camera's distance number of width and height. In this tutorial, the camera's distance was set to 20, and the X positions of the walls is set to -10 and 10, this means that the ball will always be visible if between the two walls.

Now we're done with the ball physics, let's now focus on the paddle.

The paddle should move when the W, D or arrow keys are pressed.

Override the Update() method in the Paddle class:


public override void Update() {


}

Inside the `Update()` method, check if the A or left arrow keys are being pressed, if one them are, check if the paddle is colliding with the wall, if not, then move the paddle to the left:


if ((KeysDown[(int)Key.A] || KeysDown[(int)Key.LEFT]) && x >= -10)

    x -= 30 * DeltaTime;

Do the same for the D and right arrow keys:


if ((KeysDown[(int)Key.D] || KeysDown[(int)Key.RIGHT]) && x <= 10)

    x += 30 * DeltaTime;

Go to the Draw() method and change the line that draws a rectangle to draw it on the X position stored on the variable x:


// win.DrawRect(new(0, -8, 5, 1), new(0xff, 0xff, 0xff), cam); -> Old

win.DrawRect(new(x, -8, 5, 1), new(0xff, 0xff, 0xff), cam); // New

After doing all that stuff, your code should look like this:


using static Magya;


internal class Ball : Sprite {


    private double x = 0, y = 0, vx = 0, vy = 0;


    public override void Update() {

        Scene scene = GetScene("scene");

        Paddle paddle = (Paddle)scene.GetSprite("paddle");

        Random rnd = new();


        if (x >= paddle.x - 5d / 2 - 1d / 2 &&

            x <= paddle.x + 5d / 2 + 1d / 2 &&

            y <= -8 + 1d / 2 + 1 &&

            y >= -8 - 1d / 2) {

            vy = 20 + rnd.NextDouble() * 10;

            vx = (rnd.NextDouble() - 0.5) * 2 * 10;

        } else vy -= 50 * DeltaTime;


        if (x <= -10 + 1) vx = Math.Abs(vx);

        if (x >= 10 - 1) vx = -Math.Abs(vx);


        x += vx * DeltaTime;

        y += vy * DeltaTime;

    }


    public override void Draw(Window win, Camera cam) {

        win.DrawCircle(new(x, y, 1), new(0xff, 0x80, 0x80), cam);

    }

}


internal class Paddle : Sprite {

    public double x = 0;


    public Paddle() {

        Tag = "paddle";

    }


    public override void Update() {

        if ((KeysDown[(int)Key.A] || KeysDown[(int)Key.LEFT]) && x >= -10)

            x -= 30 * DeltaTime;

        if ((KeysDown[(int)Key.D] || KeysDown[(int)Key.RIGHT]) && x <= 10)

            x += 30 * DeltaTime;

    }


    public override void Draw(Window win, Camera cam) {

        win.DrawRect(new(x, -8, 5, 1), new(0xff, 0xff, 0xff), cam);

    }

}


internal class Program {


    static void Main(string[] args) {


        Window win = new(

            "Window",

            Window.POS_CENTERED,

            Window.POS_CENTERED,

            800, 600

        );

        Camera cam = new(0, 0, 20);

        Ball ball = new();

        Paddle paddle = new();

        Scene scene = new([paddle, ball], "scene");


        while (KeepOpen) {

            Update();

            scene.Update();


            win.BeginDrawing(new(0x0d, 0x0d, 0x0d));

            win.DrawScene(scene, cam);

            win.EndDrawing();

        }


        win.Destroy();

        Quit();

    }

}


Now run this code, and there you have it!

A game where the user needs to bounce a ball in a paddle without letting it fall, just as said on the start of this tutorial.

Now that you have made this game, you can customize it freely to your liking, or even create an entire new game based on this code.


So, have a good programming journey and...

Happy coding!