Project Overview
C# Space Shooter with BrainPad Pulse
Embark on an exciting journey into the Games domain with our C# Space Shooter, seamlessly integrated with the BrainPad Pulse microcomputer. This project showcases the compatibility between C# programming and BrainPad microcomputers, bringing the game development field’s timeless excitement with the electronic circuits and microcomputers.
How It Works
the spaceship will move on the X-axis, navigating randomly while taking on three aliens. Armed with one bullet at a time, you must shoot the aliens to prevent them from reaching your space. Be cautious; each alien breach costs you one of your three lives.
Hardware Requirements
The Enjoy a straightforward setup with minimal hardware requirements – all you need is a BrainPad Pulse and a standard USB cable for a quick and easy connection.
Software Requirements
Executing this project successfully requires installing the DUE Link library, from NuGet.org so go to Package Manager Console then using the command:
install-package GHIElectronics.DUELink
The provided library is implemented in C#, but the user can use any .NET system, such as Visual Basic, Visual Studio.
Code Overview
The first C# module named Sprite.cs within the SpriteMasterGame namespace. Let’s break down the key components of this class:
Properties:
- Name: A property representing the name of the sprite.
- X and Y: Properties representing the coordinates of the sprite on a 2D plane.
- Height and Width: Properties representing the height and width of the sprite.
- Image: An array of integers representing the image data of the sprite.
- Character: A property representing a character associated with the sprite.
Constructors:
- Sprite(): Default constructor with no parameters.
- Sprite(string name, int x, int y, int height, int width, int[] image): A constructor with parameters to initialize the Name, X, Y, Height, Width, and Image properties.
- Sprite(string name, int x, int y, int height, int width): Another constructor with parameters to initialize Name, X, Y, Height, and Width properties. However, it does not initialize the Image property.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SpriteMasterGame
{
internal class Sprite
{
public string Name { get; set; }
public int X { get; set; }
public int Y { get; set; }
public int Height { get; set; }
public int Width { get; set; }
public int[] Image { get; set; }
public char Character { get; set; }
public Sprite() { }
public Sprite(string name, int x, int y, int height, int width, int[] image)
{
name = Name;
x = X;
y = Y;
height = Height;
width = Width;
}
public Sprite(string name, int x, int y, int height, int width)
{
name = Name;
x = X;
y = Y;
height = Height;
width = Width;
}
}
}
The second module is SpriteMaster.cs within the SpriteMasterGame namespace
using GHIElectronics.DUELink This statement includes the necessary namespace for the DUELink library.
Fields:
- DUELinkController BP: An instance of the DUELinkController class, which is presumably used for controlling hardware components.
- SpriteMaster(DUELinkController bp): Constructor that takes a DUELinkController instance as a parameter and initializes the BP field with it.
Methods:
- DoGame(): The main method that runs the game loop.
- DrawSprite(Sprite sprite): A method for drawing a sprite on the display.
Static Fields:
- enemy, enemy2, enemy3: Static instances of the Sprite class representing enemy sprites.
- enemyAlive, enemyAlive2, enemyAlive3: Boolean flags indicating whether the corresponding enemy sprites are alive.
- bullet: Static instance of the Sprite class representing the player’s bullet.
- player: Static instance of the Sprite class representing the player.
- score: An integer representing the player’s score.
- white and black: Colors represented as unsigned integers.
- BulletIsOut: Boolean flag indicating whether the bullet is currently in motion.
- enemySpeed1, enemySpeed2, enemySpeed3: Integers representing the speed of the enemy sprites.
- lives: Integer representing the player’s remaining lives.
Game Logic:
- The DoGame() method implements the game logic within a while loop. Here’s a summary of the key elements:
Initialization:
- Initialization of various sprites, including player, enemies, bullets, and their corresponding attributes.
- Setting up initial positions, dimensions, and images for each sprite.
Game Loop:
- Continuously runs the game loop as long as the player has lives (lives > 0).
- Clears the display and draws the game elements, including the player’s lives and score.
- Processes the bullet movement, collision detection with enemies, and updates the score.
- Handles the movement and behavior of enemy sprites.
- Checks for collisions with the player and updates the player’s position.
- Updates the display and continues the loop.
using GHIElectronics.DUELink;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace SpriteMasterGame
{
public class SpriteMaster
{
DUELinkController BP;
public SpriteMaster(DUELinkController bp)
{
this.BP = bp;
}
public void DoGame()
{
int[] frame1 = new int[] { 1,0,0,1,1,1,1,1,0,
0,1,1,1,1,1,1,1,1,
1,0,0,0,0,1,0,0,0,
1,1,1,1,1,1,1,1,1,
0,0,1,0,0,0,0,0,1
};
int[] frame2 = new int[] { 1,0,0,1,1,1,1,1,0,
0,1,1,1,1,1,1,1,1,
1,0,1,0,0,1,1,0,0,
1,1,1,1,1,1,1,1,1,
0,0,1,0,0,0,0,0,1
};
int[] bulletImage = new int[] {1, 0,
0, 1,
1, 0,
0, 1
};
int[] playerImage = new int[] {0,0,0,1,1,1,1,0,0,0,0,
0,0,0,1,1,1,1,0,0,0,0,
1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1
};
bullet.X = 150;
bullet.Y = 150;
bullet.Width = 2;
bullet.Height = 4;
bullet.Image = bulletImage;
enemy.X = 10;
enemy.Y = 10;
enemy.Width = 9;
enemy.Height = 5;
enemy.Image = frame1;
enemy2.X = 20;
enemy2.Y = 10;
enemy2.Width = 9;
enemy2.Height = 5;
enemy2.Image = frame2;
enemy3.X = 30;
enemy3.Y = 10;
enemy3.Width = 9;
enemy3.Height = 5;
enemy3.Image = frame1;
player.X = 75;
player.Y = 60;
player.Width = 11;
player.Height = 4;
player.Image = playerImage;
var start = DateTime.Now.Ticks;
var end = DateTime.Now.Ticks;
while (lives > 0)
{
start = DateTime.Now.Ticks;
BP.Display.Clear(0);
BP.Display.DrawLine(white, 103, 0, 103, 64);
for (var i = 0; i < lives; i++)
{
BP.Display.DrawText("^", white, 110, i * 10);
}
BP.Display.DrawText(score.ToString(), white, 110, 55);
if (BulletIsOut)
{
bullet.Y -= 12;
if (bullet.Y < 0)
{
BulletIsOut = false;
}
else
{
DrawSprite(bullet);
}
if (bullet.X >= enemy.X - 3 && bullet.X <= enemy.X + enemy.Width + 3 &&
bullet.Y >= enemy.Y - 3 && bullet.Y <= enemy.Y + enemy.Height + 3)
{
score += 10;
enemyAlive = false;
}
if (bullet.X >= enemy2.X - 3 && bullet.X <= enemy2.X + enemy2.Width + 3 &&
bullet.Y >= enemy2.Y - 3 && bullet.Y <= enemy2.Y + enemy2.Height + 3)
{
score += 10;
enemyAlive2 = false;
}
if (bullet.X >= enemy3.X - 3 && bullet.X <= enemy3.X + enemy3.Width + 3 &&
bullet.Y >= enemy3.Y - 3 && bullet.Y <= enemy3.Y + enemy3.Height + 3)
{
score += 10;
enemyAlive3 = false;
}
if (enemyAlive == false)
{
enemyAlive = true;
enemy.X = 10;
enemy.Y = 10;
}
if (enemyAlive2 == false)
{
enemyAlive2 = true;
enemy2.X = 20;
enemy2.Y = 10;
}
if (enemyAlive3 == false)
{
enemyAlive3 = true;
enemy3.X = 30;
enemy3.Y = 10;
}
}
else
{
var buttonshotB = BP.Button.JustPressed('b');
if (buttonshotB)
{
BP.System.Beep('p', 80, 100);
bullet.Y = 64;
bullet.X = player.X + 5;
BulletIsOut = true;
}
}
if (enemyAlive) DrawSprite(enemy);
if (enemyAlive2) DrawSprite(enemy2);
if (enemyAlive3) DrawSprite(enemy3);
DrawSprite(player);
var random_rocket = new Random();
var rockerX = random_rocket.Next(0, 100);
if (rockerX < 50) player.X += 5;
if (rockerX > 50) player.X -= 5;
if (player.X < 0) player.X = 0;
if (player.X > 90) player.X = 90;
enemy.X += enemySpeed1;
enemy2.X += enemySpeed2;
enemy3.X += enemySpeed3;
if (enemy.X < 5 || enemy.X > 85)
{
enemySpeed1 *= -1;
enemy.Y += 5;
}
if (enemy2.X < 5 || enemy2.X > 85)
{
enemySpeed2 *= -1;
enemy2.Y += 5;
}
if (enemy3.X < 5 || enemy3.X > 85)
{
enemySpeed3 *= -1;
enemy3.Y += 5;
}
if (enemy.Y > 56)
{
lives--;
enemy.X = 10;
enemy.Y = 10;
}
if (enemy2.Y > 56)
{
lives--;
enemy2.X = 20;
enemy2.Y = 10;
}
if (enemy3.Y > 56)
{
lives--;
enemy3.X = 30;
enemy3.Y = 10;
}
BP.Display.Show();
}
}
void DrawSprite(Sprite sprite)
{
var index = 0;
while (index <= sprite.Image.Length - 1)
{
for (int y = 0; y < sprite.Height; y++)
{
for (int x = 0; x <= sprite.Width - 1; x++)
{
if (sprite.Image[index] == 1)
{
BP.Display.SetPixel(white, sprite.X + x, sprite.Y + row);
}
else
{
BP.Display.SetPixel(black, sprite.X + x, sprite.Y + row);
}
index++;
}
row++;
}
row = 0;
}
}
static int row = 0;
static Sprite enemy = new Sprite();
static Sprite enemy2 = new Sprite();
static Sprite enemy3 = new Sprite();
static bool enemyAlive = true;
static bool enemyAlive2 = true;
static bool enemyAlive3 = true;
static Sprite bullet = new Sprite();
static Sprite player = new Sprite();
static int score = 0;
static uint white = 1;
static uint black = 0;
static bool BulletIsOut = false;
static int enemySpeed1 = 5;
static int enemySpeed2 = 5;
static int enemySpeed3 = 5;
static int lives = 3;
}
}
The last module is the program.cs
Main Method:
- Main(string[] args): The entry point of the program.
- DUELinkController.GetConnectionPort(): Retrieves the connection port for the DUELink controller.
- new DUELinkController(availablePort): Initializes a new instance of the DUELinkController class with the obtained connection port.
- BP.Button.Enable(‘b’, true): Enables the ‘b’ button on the brainpad controller for use in the game.
Game Initialization:
- var spritemasterdisplay = new SpriteMaster(BP) Creates a new instance of the SpriteMaster class, passing the DUELinkController instance (BP) to it.
Game Execution:
- spritemasterdisplay.DoGame(): Calls the DoGame() method on the SpriteMaster instance to start the game loop.
- Thread.Sleep(-1) Pauses the program indefinitely, allowing the game loop to continue running until manually interrupted.
using GHIElectronics.DUELink;
namespace SpriteMasterGame
{
public class Program
{
static DUELinkController BP;
static void Main(string[] args)
{
var availablePort = DUELinkController.GetConnectionPort();
var BP = new DUELinkController(availablePort);
BP.Button.Enable('b', true);
var spritemasterdisplay = new SpriteMaster(BP);
spritemasterdisplay.DoGame();
Thread.Sleep(-1);
}
}
}
Customization:
- Enhance Game Ending:
After losing all three lives, let’s make something special happen in the game—like showing the final score and playing a sound. Check out the .NET DUE library here.
- Change Spaceship Movement:
Instead of moving randomly, let’s add buttons to control the spaceship. Connect them using alligator clips to the edge connector. this way, players can enjoy a more interactive gaming experience. See the BrainClip kit here for reference.
- Explore Different Programming Languages with BrainPad Pulse:
If C# and .NET aren’t your go-to choices, you can create the same game using alternative languages such as Python, JavaScript, Due, and more. Discover the various coding options available here.
Start the discussion at GHI Electronics forums