How to Program a Primitive Twin-Stick Shooter in Monogame 3.4

Similar documents
Collision Detection Concept

A Summoner's Tale MonoGame Tutorial Series. Chapter 13. Leveling Up

IWKS 3400 Lab 3 1 JK Bennett

XNA 4.0 RPG Tutorials. Part 2. More Core Game Components

A Summoner's Tale MonoGame Tutorial Series. Chapter 9. Conversations Continued

Unit 5 Test Review Name: Hour: Date: 1) Describe two ways we have used paint to help us as we studied images in monogame.

Slides adapted from 4week course at Cornell by Tom Roeder

[ the academy_of_code] Senior Beginners

XNA 4.0 RPG Tutorials. Part 3. Even More Core Game Components

XNA (2D) Tutorial. Pong IAT410

GAME:IT Advanced. C# XNA Bouncing Ball First Game Part 1

Start Visual Studio, create a new project called Helicopter Game and press OK

XNA Game Studio 4.0.

Compile and run the code. You should see an empty window, cleared to a dark purple color.

XNA 4.0 RPG Tutorials. Part 5. The Tile Engine - Part 2

Creating a Role Playing Game with XNA Game Studio 3.0 Part 4 Adding the Action Screen and Tile Engine

A Summoner's Tale MonoGame Tutorial Series. Chapter 15. Saving Game State

Eyes of the Dragon - XNA Part 33 Non-Player Character Conversations

XNA Workshop at CS&IT Symposium 7/11/11

A Summoner's Tale MonoGame Tutorial Series. Chapter 12. Battling Avatars Continued

Visual C# 2010 Express

Start Visual Studio, start a new Windows Form project under the C# language, name the project BalloonPop MooICT and click OK.

Form Properties Window

Click on the empty form and apply the following options to the properties Windows.

A Summoner's Tale MonoGame Tutorial Series. Chapter 11. Battling Avatars

Slides built from Carter Chapter 10

This is the empty form we will be working with in this game. Look under the properties window and find the following and change them.

CSci 1113, Fall 2018 Lab Exercise 11 (Week 13): Graphics. Warm-up

CS12020 for CGVG. Practical 2. Jim Finnis

Danmaku Mono Documentation

Creating a Role Playing Game with XNA Game Studio 3.0 Part 7 Adding Sprites

Please go to the Riemer s 2D XNA Tutorial for C# by clicking on You are allowed to progress ahead of me by

XNA 4.0 RPG Tutorials. Part 22. Reading Data

Tutorial 2: Particles convected with the flow along a curved pipe.

CS 2110 Fall Instructions. 1 Installing the code. Homework 4 Paint Program. 0.1 Grading, Partners, Academic Integrity, Help

CS 134 Programming Exercise 9:

2D Graphics in XNA Game Studio Express (Modeling a Class in UML)

Programming Exercise

Introduction to Game Programming

Chapter 6 Reacting to Player Input

+ Inheritance. Sometimes we need to create new more specialized types that are similar to types we have already created.

1 Getting started with Processing

CS1950U Setup Spring 2018

Programming Project 1

3.Constructors and Destructors. Develop cpp program to implement constructor and destructor.

This Tutorial is for Word 2007 but 2003 instructions are included in [brackets] after of each step.

Creating Breakout - Part 2

The Mathcad Workspace 7

SEAMScript Language Reference Manual. Sean Inouye (si2281) Edmund Qiu (ejq2106) Akira Baruah (akb2158) Maclyn Brandwein (mgb2163)

Introduction. Create a New Project. Create the Main Form. Assignment 1 Lights Out! in C# GUI Programming 10 points

How to...create a Video VBOX Gauge in Inkscape. So you want to create your own gauge? How about a transparent background for those text elements?

Chapter 2 Surfer Tutorial

DOING MORE WITH WORD: MICROSOFT OFFICE 2010

Demo Scene Quick Start

CREATING AN AD HOC QUERY

Explain the significance of using a computer programming language. Describe the basic template of the Monogame framework.

Tutorials. Lesson 3 Work with Text

Practical 2: Ray Tracing

Autodesk Fusion 360: Model. Overview. Modeling techniques in Fusion 360

CS 201 Advanced Object-Oriented Programming Lab 4 - Asteroids, Part 2 Due: February 24/25, 11:30 PM

CISC 1600, Lab 2.3: Processing animation, objects, and arrays

Pong in Unity a basic Intro

Feature-based CAM software for mills, multi-tasking lathes and wire EDM. Getting Started

Session 5.1. Writing Text

Unit 21 - Creating a Navigation Bar in Macromedia Fireworks

Introduction to Unreal Engine Blueprints for Beginners. By Chaven R Yenketswamy

PowerPoint Basics (Office 2000 PC Version)

Visual C# Program: Simple Game 3

Add the backgrounds. Add the font.

3D Starfields for Unity

Next, we re going to specify some extra stuff related to our window such as its size and title. Add this code to the Initialize method:

Advanced Computer Programming

Adobe Flash CS3 Reference Flash CS3 Application Window

Google Earth Tutorial 1: The Basics of Map-making in Google Earth 6.2

Tower Drawing. Learning how to combine shapes and lines

Application of Skills: Microsoft PowerPoint 2013 Tutorial

LECTURE 4. Announcements

A Summoner's Tale MonoGame Tutorial Series. Chapter 3. Tile Engine and Game Play State

Topic 7: Algebraic Data Types

Lesson 1: Creating T- Spline Forms. In Samples section of your Data Panel, browse to: Fusion 101 Training > 03 Sculpt > 03_Sculpting_Introduction.

Google SketchUp/Unity Tutorial Basics

This is a set of tiles I made for a mobile game (a long time ago). You can download it here:

SolidWorks 2½D Parts

CLASSES AND OBJECTS. Fundamentals of Computer Science I

SolidWorks Intro Part 1b

Vectorworks Essential Tutorial Manual by Jonathan Pickup. Sample

Eyes of the Dragon - XNA Part 37 Map Editor Revisited

In this lecture we will briefly examine a few new controls, introduce the concept of scope, random numbers, and drawing simple graphics.

Osmond Tutorial. First Page / J C Chavez / / Osmond Tutorial

Assignment 3: Inheritance

Some (semi-)advanced tips for LibreOffice

OneNote 2016 Tutorial

We will start our journey into Processing with creating static images using commands available in Processing:

Burning Laser. In this tutorial we are going to use particle flow to create a laser beam that shoots off sparks and leaves a burn mark on a surface!

POWERPOINT 2003 OVERVIEW DISCLAIMER:

CS206: Evolutionary Robotics

SFU CMPT 361 Computer Graphics Fall 2017 Assignment 2. Assignment due Thursday, October 19, 11:59pm

JASCO CANVAS PROGRAM OPERATION MANUAL

disclabel help: what is disclabel?

Graphics: Legacy Library

Transcription:

How to Program a Primitive Twin-Stick Shooter in Monogame 3.4 This is a tutorial for making a basic twin-stick style shooter in C# using Monogame 3.4 and Microsoft Visual Studio. This guide will demonstrate a rudimentary system that can handle moving a player character, aiming and shooting projectiles, and rudimentary enemy behavior. It is assumed that the reader has moderate experience with both C# and some version of Microsoft Visual Studio, preferably MSVS2015. Materials: 1. A Windows PC, preferably Windows 7 or later 2. Physical mouse and keyboard compatible with PC 3. A stable internet connection 0. Setting Up the Project 1. Download and install the free version of Microsoft Visual Studio 2015 by clicking the Download Community Free button on this link. 2. Download and install Monogame 3.4 from this link by clicking Monogame 3.4 for Visual Studio. 3. Download the C3.XNA.Primitives2D zip file by following this link. Once it is downloaded, unzip the directory into your work space. 4. Open Visual Studio 2015. Open a new Monogame project by clicking File->New->Project under the navigation bar at the top. Once the new project pop-up appears, navigate to Templates->Visual C#->Monogame->Monogame Windows Project. 5. Name the project as you desire and select OK. Note: From now on, whenever you see [YOUR_NAME], replace it with whatever you named your project in Step 5. 6. Right click on the newly created solution in the Solution Explorer. Select Add->Existing Item. In the file selection pop-up that appears, navigate to your work space and select Primitives2D.cs from the folder we unzipped in Step 3. 7. Open the file [YOUR_NAME].cs by double clicking on it in the solution explorer. 8. At the top of the file, add the code using C3.XNA; using System; using System.Collections.Generic; to the existing include statements.

1. Initializing the Window Caution: Compile code regularly to ensure that everything is working properly. 1. Declare the following class-scope constants: const int screenwidth = 1280; const int screenheight = 768; const bool fullscreen = false; 2. Find the [YOUR_NAME]() constructor. Underneath the line graphics = new GraphicsDeviceManager(this);, insert the following lines of code: graphics.isfullscreen = fullscreen; graphics.preferredbackbufferwidth = screenwidth; graphics.preferredbackbufferheight = screenheight; 3. Find the Draw(GameTime gametime) method. Change the method call GraphicsDevice.Clear(Color.Black); Note: Every frame will begin with the window set to a solid rectangle of this color. You can choose a different color if you wish. At this time, if you compile and run the program you should see a window with a completely black background (or whichever color you chose in Step 3.)

2. Player and Cursor Movement 2.1 Movement Trails This section will allow us to display faded circles behind the player, bullets, and enemies as they move to add visual flair. 1. Declare a void method UpdateMovementTrail(ref Vector2[] trail, int trail_count). 2. Implement the above method. It should contain a for loop that iterates backwards from the index trail_count and set the i th element equal to the i-1 th element. Declare the class-scope constant const double MOVEMENT_UPDATE = 0.025;. 3. Declare a void method DrawMovementTrail(SpriteBatch spritebatch, Vector2[] trail, int trail_count, float radius, float sides, Color color). Note: The arguments are used as follows: 1. spritebatch is a native Monogame entity used for drawing. 2. trail is the list of positions at which we will draw a circle. 3. trail_count is the number of circles we will draw 4. radius is the radius in pixels of each circle 5. sides is the number of sides we will use to approximate a circle. 6. color is the color that we will make each circle, ignoring the alpha channel. 4. Implement a for loop in the drawing method that iterates backwards from trail_count-1 to 0. In each step it should check if both the X and Y coordinate of the point is greater than 0, as our convention is that empty positions in the array start out at (-1,-1). Inside the if statement, place the following method call: Primitives2D.DrawArc( spritebatch, trail[i], radius, sides, 0, MathHelper.TwoPi, new Color( (float)color.r / 255, (float)color.g / 255, (float)color.b / 255, (1 - ((float)i / trail_count)) / (float)(i+1)

), radius); Note: This method calls a function defined in the Primitives2D file we included earlier in Section 0. As of version 3.4, Monogame does not support native primitve rendering in 2D, so we are utilizing 3 rd party code. Our code does not include a method for filling circles, so we get around this by drawing a circular arc with the thickness set to be the thickness of our radius. At each position in the movement trail, we draw a circle with slightly more opacity. This is what the fourth computation in the color constructor represents. 2.2 Player Movement 1. Declare the following class-scope constants: Color PLAYER_COLOR = new Color(40, 255, 27); Color CURSOR_COLOR = new Color(40, 128, 255); const int CURSOR_SIDES = 100; const int CURSOR_RADIUS = 10; const int MOVE_SPEED = 500; const float SHOOT_DELAY = 0.25f; const int PLAYER_RADIUS = 25; const int PLAYER_SIDES = 100; const int PLAYER_MOVEMENT_TRAIL = 25; Note: The Monogame Color datatype cannot be declared as constant. We use captialization conventions to communicate that this value should not be changed. 2. Declare the following class-scope variables: Vector2[] pos = new Vector2[PLAYER_MOVEMENT_TRAIL];

double lasttime = 0.0f; double lastshot = 0.0f; Vector2 cursorpos; 3. In the Initialize() method, set the first element of the pos array with the constructor call new Vector2(screenWidth / 2, screenheight / 2). Set the rest of the elements of the array using the constructor call new Vector2(-1, -1). 4. Declare the void method UpdatePlayer(GameTime gametime). 5. In the Update(GameTime gametime) method, add a call to the newly created UpdatePlayer method with gametime as the argument. 6. Place the following code in the UpdatePlayer(GameTime gametime) method: float deltatime = (float)gametime.elapsedgametime.totalseconds; KeyboardState keystate = Keyboard.GetState(); cursorpos = new Vector2(Mouse.GetState().Position.X, Mouse.GetState().Position.Y); if(gametime.totalgametime.totalseconds - lasttime > MOVEMENT_UPDATE) lasttime = gametime.totalgametime.totalseconds; UpdateMovementTrail(ref pos, PLAYER_MOVEMENT_TRAIL); Here we update the movement trail after a set interval. if(keystate.iskeydown(keys.a)) pos[0].x -= MOVE_SPEED * deltatime; if (keystate.iskeydown(keys.d)) These conditionals check to see if the movement key is down. If so, update the player's position accordingly. pos[0].x += MOVE_SPEED * deltatime; if (keystate.iskeydown(keys.s)) pos[0].y += MOVE_SPEED * deltatime;

if (keystate.iskeydown(keys.w)) pos[0].y -= MOVE_SPEED * deltatime; if(pos[0].x < PLAYER_RADIUS) pos[0].x = PLAYER_RADIUS; Monogame's coordinate system starts with (0,0) in the upper left corner. X increases to the right and Y towards the bottom. Thus if we want the player to move up, the Y coordinate needs to shrink. else if(pos[0].x > screenwidth - PLAYER_RADIUS) pos[0].x = screenwidth - PLAYER_RADIUS; if(pos[0].y < PLAYER_RADIUS) These conditionals make sure that the player cannot exit the screen. pos[0].y = PLAYER_RADIUS; else if(pos[0].y > screenheight - PLAYER_RADIUS) pos[0].y = screenheight - PLAYER_RADIUS; 7. Place the following code in the Draw(GameTime gametime) method after the call to the clear method: spritebatch.begin(spritesortmode.deferred, BlendState.Additive); Primitives2D.DrawArc(spriteBatch, cursorpos, CURSOR_RADIUS, CURSOR_SIDES, 0, MathHelper.TwoPi, CURSOR_COLOR); Primitives2D.DrawArc(spriteBatch, cursorpos, 0.5f * CURSOR_RADIUS, CURSOR_SIDES, 0, MathHelper.TwoPi, new Color(0.5f * (float)cursor_color.r / 255, 0.5f * (float)cursor_color.g / 255, 0.5f * (float)cursor_color.b / 255, CURSOR_COLOR.A)); DrawMovementTrail(spriteBatch, pos, PLAYER_MOVEMENT_TRAIL, PLAYER_RADIUS, PLAYER_SIDES, PLAYER_COLOR); All drawing calls in spritebatch.end(); Monogame need to be between calls to Begin and End

If you compile and run the program now, you should see a green circle and two blue circles on the screen. The blue circles should move with your mouse, and the green circle should move in response to the WASD keys. 3. Shooting 1. Declare the following class-scope constants: Color BULLET_COLOR = new Color(255, 40, 200); const float BULLET_SPEED = 1500; const int BULLET_MOVEMENT_TRAIL = 5; const int BULLET_RADIUS = 12; const int BULLET_SIDES = 20; 2. Declare a Bullet class. Give it the following fields and constructor: public Vector2[] positions = new Vector2[BULLET_MOVEMENT_TRAIL]; public Vector2 direction; public double lasttime = 0.0f; public bool remove = false; public Bullet(Vector2 pos, Vector2 dir) direction = dir; We will not draw elements of the movement trail outside of the bounds of the window, so setting the initial value outside ensures nothing is drawn until necessary. positions[0] = pos; for (int i = 1; i < BULLET_MOVEMENT_TRAIL; i++) positions[i] = new Vector2(-1, -1);

3. Declare the following class-scope variable: List<Bullet> bullets = new List<Bullet>(); Note: List<Type> is a C# template found in the System.Collections.Generic namespace. It behaves like a sophisticated linked list. 4. Declare the void method UpdateBullets(GameTime gametime). 5. Inside the new method, compute deltatime as done in the UpdatePlayer(GameTime gametime) method. 6. Create a foreach loop that iterates over the bullets list. Inside the for loop, use an if statement and the lasttime property of the Bullet class to update the object's movement trail as done in the UpdatePlayer(GameTime gametime) method. Add the following line of code outside the if statement but inside the foreach loop: bullet.positions[0] += BULLET_SPEED * deltatime * bullet.direction; This moves the bullet in the Stored direction at the constant Speed defined earlier. The deltatime Multiplier is used for frame rate Independent movement. 7. Add a call to the UpdateBullets(GameTime gametime) method beneath the call to UpdatePlayer(gameTime) in the Update(GameTime gametime) method. 8. Add the following code to the bottom of the UpdatePlayer(GameTime gametime) method: if (Mouse.GetState().LeftButton == ButtonState.Pressed) if (gametime.totalgametime.totalseconds - lastshot > SHOOT_DELAY) lastshot = gametime.totalgametime.totalseconds;

Vector2 dir = cursorpos - pos[0]; dir.normalize(); bullets.add(new Bullet(pos[0], dir)); 9. Finally, add the following lines of code to the Draw(GameTime gametime) beneath the code used to render the player and above the call to spritebatch.end(). foreach(bullet bullet in bullets) DrawMovementTrail(spriteBatch, bullet.positions, BULLET_MOVEMENT_TRAIL, BULLET_RADIUS, BULLET_SIDES, BULLET_COLOR); If you compile and run the program now, try shooting by clicking the left mouse button. A pink circle should move from the player (or green circle) and travel towards the cursor (blue circle.) 1. Declare the following class-scope constants: 4. Enemies and Collisions Color ENEMY_COLOR = new Color(128, 10, 0); const float ENEMY_SPEED = 250; const int ENEMY_MOVEMENT_TRAIL = 7; const int ENEMY_RADIUS = 20;

const int ENEMY_SIDES = 25; const double SPAWN_DELAY = 1.5; const double POINT_MULTIPLIER = 0.25; 2. Declare an Enemy class. It should be almost identical to the Bullet class, the only difference being that positions uses the ENEMY_MOVEMENT_TRAIL constant instead of BULLET_MOVEMENT_TRAIL and the direction field is computed instead of passed in in the constructor: direction = new Vector2(screenWidth / 2 - pos.x, screenheight / 2 - pos.y); direction.normalize(); 3. Create a list (using the above template) of Enemy objects. Declare a class-scope integer variable points, a class-scope double variable lastspawn and set both to 0. 4. Declare the float method PointMultiplier(), which should contain the single line return 1 + ((float)point_multiplier * points);. 5. Declare the void method Spawn() and implement it as follows: Random rand = new Random(); int border = (rand.next() % 4); float x = ENEMY_RADIUS; float y = ENEMY_RADIUS; Randomly pick a border to spawn along. if(border == 0 border == 2) if(border == 2) y = screenheight - ENEMY_RADIUS; Place the new enemy somewhere random along the length of the selected border. x = ENEMY_RADIUS + (rand.next() % (screenwidth - 2 * ENEMY_RADIUS)); else if(border == 3) x = screenwidth - ENEMY_RADIUS;

y = ENEMY_RADIUS + (rand.next() % (screenheight - 2 * ENEMY_RADIUS)); enemies.add(new Enemy(new Vector2(x, y))); 6. Declare the void method UpdateEnemies(GameTime gametime) and implement it as follows: float deltatime = (float)gametime.elapsedgametime.totalseconds; if(gametime.totalgametime.totalseconds - lastspawn > SPAWN_DELAY / PointMultiplier()) lastspawn = gametime.totalgametime.totalseconds; Spawn(); foreach(enemy enemy in enemies) The point multiplier is used to speed the game up as the player scores more points. if(gametime.totalgametime.totalseconds - enemy.lasttime > MOVEMENT_UPDATE) enemy.lasttime = gametime.totalgametime.totalseconds; UpdateMovementTrail(ref enemy.positions, ENEMY_MOVEMENT_TRAIL); enemy.positions[0] += PointMultiplier() * ENEMY_SPEED * deltatime * enemy.direction; 7. Add a call to the UpdateEnemies(GameTime gametime) method beneath the call to UpdateBullets(gameTime) in the Update(GameTime gametime) method. 8. Add the following code to the top of the Update(GameTime gametime) method: foreach (Enemy enemy in enemies) if ((enemy.positions[0] - pos[0]).lengthsquared() < (PLAYER_RADIUS * PLAYER_RADIUS) + (ENEMY_RADIUS * ENEMY_RADIUS) + 2 * PLAYER_RADIUS * ENEMY_RADIUS) Exit(); If the player intersects an enemy, it is game over. foreach(bullet bullet in bullets)

if ((enemy.positions[0] - bullet.positions[0]).lengthsquared() < (BULLET_RADIUS * BULLET_RADIUS) + (ENEMY_RADIUS * ENEMY_RADIUS) + 2 * BULLET_RADIUS * ENEMY_RADIUS) points += 1; bullet.remove = true; enemy.remove = true; for(int i = bullets.count - 1; i > -1; i--) if(bullets[i].remove) bullets.removeat(i); Remove all bullets and enemies that have collided. for(int i = enemies.count - 1; i > -1; i--) if(enemies[i].remove) enemies.removeat(i); 9. Add code to draw the enemies on-screen between the render code for the bullets and player. It should mirror the draw code for bullets using corresponding Enemy fields and constants. That's it! You should now compile and run, and you should see red circles coming from the outside edges of the screen, passing through the middle, and leaving the other side. Red circles that come into contact with the green one will end the game, and shooting them will make them spawn more often and move more quickly.

To recap, we installed and prepared a fresh Monogame project. We customized the window size and background color. We used the keyboard and mouse to manipulate on screen objects, making a player character that could move about the world. We added the ability to shoot while clicking the left mouse button. Finally, we added primitive enemies that fly from the edge of the screen towards the player. Think about extending this example. Try first changing the constants to make the game faster and slower paced. Think about making the enemies fly in a semi-random direction once they spawn instead of straight at the center. Can you think of a way to remove bullets and enemies that are offscreen but have not collided with anything?