One of the useful ideas I picked up from reading a book on how to make games with PhaserJS, was to take a look at the various frameworks and modules that had been built already for Monkey-X.
I have been slowing myself down by trying to handle some of the specifics of the game directly instead of building on the work of others.
The thing I am looking for most is a physics frameworks. Nothing says edge case like handling collisions of various shapes.
Some GUI help would be nice too.
The Frameworks
I did some searching and there are probably others out there, but these were the easiest to find frameworks I came across and some of my notes on them.
Creator wrote a book on how to make games with Monkey-X and Fantom Engine
Fairly well documented with examples
Recently updated
Has Box2D physics implementation
Has GUI helpers
Conclusion
Based on this research the choice is between Diddy, Pyro and Fantom X. I will be testing out the Fantom X engine for now and seeing how useful it can be, and depending on how that goes I will probably use it in the following parts of my making your first game with Monkey-X series.
There are 2 main reasons for this. First I will be using it for examples and for people just getting started into making games who may not want to invest too heavily financially to begin with or even don’t have the money for something like Pyro.
Second, for being a free framework Fantom X is well documented and the maintainer seems to keep it up to date.
Looking forward to evolving my game making skills.
This post is part of a series on beginning game creation with Monkey-X. You can find the previous article in the series here.
——————-
For your first game with Monkey-X (maybe your first game ever) we are going to create a simple side scrolling shooter.
Whenever I am getting ready to make a new game, I usually start with a simple 10 point check list called 10 things every game needs. I borrowed it from a fellow name Mark Rosewater who does some game design on a game called Magic: The Gathering for Wizards of the Coast, and we are going to use it here.
This is not the only way to design games nor is it super thorough, but I have found it useful to go through this exercise when I am getting ready to make a new game.
1: A Game should have a Goal
Your game should have pretty much 1 major goal. This is often the way the player wins the game. Like Checkmate in Chess, or getting the enemy flag to your base in some Capture The Flag.
For our side scroller, the goal will be to beat the Big Bad Boss.
Simple goal, to the point.
2: A Game should have some Rules
Whether it is a classic board game like Chess or an FPS like CS:GO, games need rules so the player knows what they can and cannot do to achieve the goal.
In Chess the rules layout which pieces can go where. In computer games, the rules are usually built into the code of the game and manage things like keeping track of the score and keeping players from doing things they should not be able to.
For our game we will have simple rules.
The player can move up and down, back and forth but cannot leave the screen.
When the player or an enemy gets hit by a bullet, they lose armor.
When the player or an enemy’s armor gets to 0 (or below), they are destroyed.
If the player destroys the Big Bad Boss, they win the game.
This is a simplified set of rules, but you get the idea.
3: Players should be able to Interact
If you do not have a way to interact with your opponent, you might as well be playing by yourself.
This is usually more relevant in a multiplayer game, but we will pretend the computer is a player in this game.
Our interaction mechanic is shooting and being able to destroy enemies.
4: Players who are behind should be able to Catch Up
There are few things worse than playing a game, falling behind and never having a chance to get back in it. Might as well give up at that point.
A lot of sports games have this built in. When a team scores in Football or Basketball, the other team gets the ball and a chance to score.
In video games this is often accomplished with powerups or extra lives that you can get.
We will build in both powerups and extra life mechanics into our game.
5: A game needs some Inertia to make sure it ends
Games that never end make players get tired of them very quickly. The ideal length is just a little less than what the player wants to play.
Regardless, you need something that pushes your game to end. Lots of games do this with a time limit. When the time runs out, the game is over.
We will sort of do this, but manage it by having our player move forward in the side scroller at a constant speed, and when they reach the boss, they have will beat him or lose.
6: Games need some element of Surprise
If you always know what is going to happen next in a game, it quickly becomes boring. Adding a little unpredictability or hidden information can make a game more fun.
For our side scroller, we will randomize the enemies and some of their movements to keep the player guessing.
7: Players need to be able to use Strategy to be able to get better at the game
If the game is completely random and no player skill is involved, players will see it as unfair and that there really is no point to play.
You need to have some element that the players can control and master to help them win.
For our side scroller it will be using their ability to dodge enemies and get kills. Maybe not the highest form of strategy, but not left up to chance.
8: A game should be Fun
You will notice that this is not number 1 in the list. Often the fun will come out of the other elements that make up your game and fun is hard to predict.
Often you find a fun mechanic and build your game around that. Sometimes you borrow from ideas you already know are fun like side scrolling shooters.
If you want to learn more on the subject of fun, I would suggest reading things like 8kindsoffun.com and theoryoffun.com.
9: Games should have some Flavor
Flavor here is meant to mean the setting or world, often referred to as the theme, that the game takes place in. Is your flavor a galaxy far, far away or maybe in the Caribbean on a pirate ship in the 1700’s?
For our shooter, the player will be captain of a submarine fighting through undersea machines and beasts.
10: A game should have a Hook
Last but not least, people need a reason to play your game. We call this a Hook.
You may have a great game, but if you can’t convince people to play it then it won’t matter.
Our hook will simply be, “Save the planet from the alien invasion”
Design Done
Going through some sort of process like this every time you make a game will be really helpful.
You don’t have to go through these 10 steps and even if you do, your game does not need all 10. I have simply found this a helpful exercise for designing games and would recommend it to you as well.
This post is part of a series on beginning game creation with Monkey-X. You can find the previous article in the series here.
A Basic Game Loop
Most games follow a similar pattern of running through the game logic, drawing updated information to the display, then repeating until the game is over.
The special functions that we discussed in the last section handle this loop in Monkey-X, OnUpdate and OnRender. You can control how often they are called by setting the SetUpdateRate. Using SetUpdateRate(30) would update and draw your game 30 times per second. And SetUpdateRate(60) would update and draw your game 60 times per second or be 60 fps (frames per second).
** Note: the SetUpdateRate function does not guarantee that it will be able to run that fast. It all depends on the machine the game is running on, how complicated your game logic is, and how many things your are trying to draw to the screen each time.
Game States
For most games, what is run during the update section of the loop and what is displayed is controlled by state.
When the game is in the MENU state, we draw the menu and watch for player input for which state to move to next. When the game is in the PLAY state, we draw the player’s character and the map, and whatever else and listen for input to move the player around, check for collisions or perform other game actions.
This is what we will use to manage what goes on each time Monkey-X calls OnUpdate and OnRender.
One of the most memorable statements from Clean Code was the author describing some of his own example code as a “festering pile.” And he doesn’t just use the term once, he uses it multiple times.
The most important statement however comes after he goes through how he cleans up his code and he is going over key points at the end of the chapter.
It is not enough for code to work. Code that works is often badly broken. Programmers who satisfy themselves with merely working code are behaving unprofessionally.
– Uncle Bob in Clean Code
Get It Working, Then Clean It Up
Don’t get me wrong, you should get your code working. If it does not work, it is completely useless.
But that is not where you stop.
When you stop here, it becomes difficult to add features to your program and makes it more likely that you will introduce errors later.
As a professional programmer and creator, cleaning up your code after you get it working is a must.
What’s That Smell?
As I am working on my current game, I started noticing that some of my own code was becoming a bit of its own smelly, festering pile.
Classes were getting a little clunky, functions where getting a little too long, and it was taking longer to make small changes.
Since I am trying to write the same logic in 2 different languages (Monkey-X for the client and Ruby for the server), having clean code was even more important to be able to replicate the logic in a second language.
And with the code getting a little cluttered, there started being some hard to figure out bugs where the client would show one thing, but the server would do another.
I finally got the code behaving the same and it took longer than it should have.
Programming Febreeze
Now it is time to get rid of some of these smells. The way to do that in programming is refactoring.
This is basically what Clean Code was all about. Changing your code to be more clear and designing the classes and methods to be easier to change (think DRY and SRP) have your code smelling like fresh linen.
That is what I am working on now with Drone Tournament. My original goal this year was to finish a game every 3 months, but I don’t want a half baked game. So I am taking the time to refactor the code and clean it up because I am a professional.
*Disclaimer: This tutorial is assuming that you have some basic knowledge of computers, programming and programming lingo. If you do, but something is still unclear, shoot me an email and I will be happy to help you out.
If you followed the instructions in the previous part of the tutorial you should have Monkey-X installed and be able to open up the default IDE that comes with it, TED. Which should look something like this.
Today, we are going to go through a few handy things to know about using TED and some of the basic structure of a Monkey-X game and the files associated with it.
Create a File
First thing we need to do is create a new file so we can start writing some code. We are going to create a file called main.monkey because it is going to hold our Main function. You can do create this file however you want, and to do it in TED you simply go to the “File” option on the menu and click “New”. You will want to put this file in a new folder by itself. We will use this as our project folder, I have called my folder “game_one”, and will use it in the following examples.
Open Your Folder As a Project
One of the handy things that TED allows you to do is open a Project view of the folder. This allows you to easily navigate and open files and folders as your game gets larger. You can do this by selecting the “Build” option from the main menu bar and clicking “Open Project”, then select the folder that you put your main.monkey file in.
When your games get more complex and you add multiple files, this view becomes very handy.
Mojo
The first thing we are going to do is add Mojo to the game. Mojo is an application framework provided with Monkey-X that gives you several useful tools for making games. It is specifically targeted at 2D games. Go ahead and add this to the top of your main.monkey file.
Import mojo
This will import all of Mojo’s components (graphic, audio, and app).
The Game
Now that we have mojo, we can create a class that is a Mojo app and will be what the rest of our game is built on. Lets do that now.
Import mojo
Class Game Extends App
End
Using Extends allows us to say that our Game is an App and it now has access to a lot of special functions and methods that Mojo gives us.
Quick note about the Monkey-X language. The End keyword is used to close Classes, Functions, Loops, Conditionals, and other code blocks. No need for ‘{ }’ everywhere in your code. Also lines do not use anything on the end to say they are done, so no ‘;’ are needed on the end of lines. There are a few other quirks about the language that it gets from Basic (from which it was derived) that we will discuss later.
Special Mojo Methods
The first special method that we will need to override that Mojo gives us is OnCreate. This method is called one time whenever you create a new instance of the Game class in your code. This is where you want to do things like load images and audio as well as set certain game values like screen width and height.
Import mojo
Class Game Extends App
Method OnCreate()
End
End
After OnCreate we have OnUpdate. The OnUpdate method is called a certain number of times per second (which you set in your OnCreate method). This is where your main game loop goes. This is where you will check for input and handle movement and changing game values. OnUpdate will only be called if you have set the update rate with a special method called SetUpdateRate. We call this in OnCreate and give it a number to tell it how many times per second to update. We will use 60 for now.
Import mojo
Class Game Extends App
Method OnCreate()
SetUpdateRate(60)
End
Method OnUpdate()
End
End
Next is OnRender. This is where all of your drawing code will go. If you want to display something to the player, it should happen in this method. Mojo tries to call it immediately after every OnUpdate so the update rate that you set also applies to how many times you draw to the screen.
Import mojo
Class Game Extends App
Method OnCreate()
SetUpdateRate(60)
End
Method OnUpdate()
End
Method OnRender()
End
End
Other Special Methods
There are several other special methods in Mojo (OnLoading, OnSuspend, OnResume, OnClose, OnBack) that we will discuss in a later step of the tutorial. For now it is just good to now about them. If you are curious about them now, you can go read about what they do in the Monkey-X Documentation.
Final Step
In order to have something that actually builds and runs (even though it only displays a blank screen) you will need to add a Main function creates a new instance of your Game class.
Import mojo
Class Game Extends App
Method OnCreate()
SetUpdateRate(60)
End
Method OnUpdate()
End
Method OnRender()
End
End
Function Main()
New Game()
End
Now if you build and run by hitting the little rocket ship with flames coming out the back (or by hitting the F5 key) TED should open your default web browser with an instance of the game running. Right now since we are not drawing anything in our OnRender function, it is just a blank screen. But it will soon get more interesting.
If you have been reading any of the previous posts in this blog, you know that I have been creating games over the last couple years using a programming language called Monkey-X. It is a nice straightforward language based on Basic. Today we will go through some of the strengths Monkey-X offers and how to get Monkey-X setup on your computer so you can start creating games with it too.
Why Use Monkey-X
One of the main reasons I use Monkey-X is that I have found that for some reason I am a little extra productive in it. I don’t know if it is the simple language syntax or the ease of creating a build of the game and testing it for fast feedback. It’s probably a combination of the two and a few other things.
Additionally there is a great little community built around what is referred to as the Blitz Basic family of programming languages (the language Monkey-X came from). Guides, how to’s, a forum, and at least 1 Youtube playlist to help you get started and help you when you get stuck.
This community has also developed plugins and libraries to help you take care of some of the more routine functions and tasks that you will be performing as you create games. We will discuss these later on.
One of the more powerful reasons I even looked into using Monkey-X at all is that it builds to tons of platforms. All the major desktops (Windows, OSX, Linux), HTML5 for web, mobile (Android and iOS), even XNA so it can run on an XBox, as well as PSM for the Playstation Vita.
The final reason it is good to get started making games with Monkey-X is the cost. It is completely free to download and make games for the HTML5 target which is perfect for a beginner. And when you are ready to upgrade to build to more platforms, the Pro version is fairly inexpensive (~$40 USD at the time of this writing). If you want a little more, there is a fancier IDE and some other addons you can purchase as well.
Installing Monkey-X
Monkey-X runs on pretty much any desktop OS. Whether you are using Windows, OSX, or Linux you should be able to install Monkey-X and start making games.
Installing Monkey-X On Windows
Visit the Monkey-X website and create a free account. You will need this to get the download.
Sign in and visit the Monkey-X download page and download the free version of Monkey-X (or the pro version if you have purchased it). Hint: the free download is at the bottom of the page.
Unzip the downloaded folder and put the files wherever you want.
(Optional) Right-click the Monkey-X executable file and create a shortcut to your desktop to make accessing it easier
Installing Monkey-X On OSX/Mac
Visit the Monkey-X website and create a free account. You will need this to get the download.
Sign in and visit the Monkey-X download page and download the free version of Monkey-X (or the pro version if you have purchased it). Hint: the free download is at the bottom of the page.
When the file has finished downloading, open in Finder and drag it to the Application folder. NOTE: You may need to change your systems security settings to allow apps from anywhere to be installed in order for it to work.
Installing Monkey-X On Ubuntu
For now I am just going to leave a link to this post someone in the Monkey-X community created for installing on Linux. If I get enough requests, I will do a walkthrough myself on setting up Monkey-X on a fresh Ubuntu install. If you would like that, shoot me an email.
Where To Look If You Get Stuck
If you have any trouble a few good places to look for help are the Monkey-X forums and sites like Stack Overflow. Also, you can email me and I will try to help you get up and running with Monkey-X.
I am currently working away at Drone Tournament, Game #2 for 2016, and started implementing combat into the game. In order to make combat happen, each little drone unit in the game will be able to fire their weapon every so often, and if they hit an opponent it loses armor and can be destroyed.
The trick is how to figure out if we get a hit.
Previous Collision Detection
At the beginning of 2015 I made my first game Prism Ship with Monkey-X and it implements a little ship that shoots blocks. The collision detection there is not pretty but fairly simple because everything is kept square and straight.
Projectiles go straight up and the things they hit are coming straight down so no real fancy math is needed. I simply checked each of the corners of the projectile to see if they were inside the squares you are trying to hit.
A New Challenge
In Drone Tournament however, things can turn when they shoot which means that bullets go off at weird angles and their potential targets are not always moving directly towards them. Additionally I did not make the projectiles in this game as large as in Prism Ship. They are basically line segments.
It Has Been Solved
This problem is common enough that it has been solved before, and in a most elegant and simple manner. Here is some Monkey-X code that I derived from an implementation of the solution in Python. I will explain what is going on below. I even borrowed a picture that shows what is going on really well.
Function LinesIntersect:Bool(pointA:Vec2D, pointB:Vec2D, pointC:Vec2D, pointD:Vec2D)
Local abc:Bool = CounterClockwise(pointA, pointB, pointC)
Local abd:Bool = CounterClockwise(pointA, pointB, pointD)
Local cda:Bool = CounterClockwise(pointC, pointD, pointA)
Local cdb:Bool = CounterClockwise(pointC, pointD, pointB)
Return(( abc <> abd) And (cda <> cdb))
End
Function CounterClockwise:Bool(pointOne:Vec2D, pointTwo:Vec2D, pointThree:Vec2D)
Return ((pointThree.y - pointOne.y) * (pointTwo.x - pointOne.x) >
(pointTwo.y - pointOne.y) * (pointThree.x - pointOne.x))
End
For those of you not familiar with Visual Basic, "<>" is its way of writing "!=" (Not Equal)
Explanation
If you remember from your geometry class back in high school, line segments have a slope which just measures the change from the beginning point to the end. If you have three points A, B, and C, and the slope of the line from A to B is larger than the slope from A to C (meaning it changes more) then the points are in Clockwise (CW) order. If the slope from A to B is less than that of A to C then they are considered Counter Clockwise (CCW).
Image borrowed from here (article 1 in reference below).
So we test the two points of our particle to see whether they are CW or CCW to each edge of the Drone hit box. If 1 point is CW and the other is CCW to an edge, then we know that the lines intersect because you have a point on either side of the edge of the hit box.
Special Case I am Ignoring
There is a special case where the 2 lines lay across one another called Collinear. I am ignoring this special case because for the purposes of the game it would not really be a solid hit and is not that important. If you would like to know how to handle it you can read more about this solution at the following articles.
For game #2 of 2016, I wanted to use a movement system similar to one that can be found in Steam Birds, Critical Mass, and some other games. I am most of the way there and would like to talk about some of the different approaches and barriers I have run into so far.
Hermite Interpolation
Gonna start with the difficult sounding silly math that is not really what I needed and also not as complicated as it sounds. Especially since the code for it is out there and you can almost copy pasta it into whatever language you are working in.
In Monkey-X it looks something like this:
Function CubicHermite:FloatDeque (start_point:Float, end_point:Float, start_velocity:Float, end_velocity:Float)
Local pathPoints:FloatDeque = New FloatDeque
Local division:Float = 30.0
For Local i:Int = 0 Until division
Local t:Float = (Float(i)/division)
Local t_square:Float = t * t
Local t_cube:Float = t_square * t
Local a:Float = 2*t_cube - 3*t_square + 1
Local b:Float = -2*t_cube + 3*t_square
Local c:Float = t_cube - 2*t_square + t
Local d:Float = t_cube - t_square
Local point:Float = a * start_point + b * end_point + c * start_velocity + d * end_velocity
pathPoints.PushLast(point)
End
Return pathPoints
End
Basically you use this method to find some number of points along the curvy path that you are trying to create. You need to know what direction and speed you are going to begin, the direction and speed you will be going when you are done, and what point in time you want to get on the line. Essentially if you wanted to move along the line at 30 frames per second you will need 30 points to render whatever you are moving at. You would loop through the function doing your interpolation 30 times where the point in time is 1/30 to 30/30. You push all of these points into some sort of ordered data structure, in my case a Deque.
Note: This particular implementation only finds 1 part of the coordinate. If you are in 2D you need to run it once for X and once for Y, and if you are in 3D you need to run it one more time for the Z coordinate.
The formula then creates a nice curvy path to go from the first point to the next but ended up being not exactly what I was going for.
Simple Steps Approach
The problem I was having getting my head around with the interpolation approach was how to properly put limits on the movement. Distance was not a problem, but I only want the units to be able to turn so far.
The solution I ended up going with was to give each unit a rotational angle limit. Then I went through the 30 step loop again and on each step I calculated the angle from where I was to where I was headed. If it was greater than my current heading, we add the rotation limit to our current heading. If less than, we subtract the rotation limit from our current heading. Then we move 1/30th of our speed in the new direction and repeat.
Method SetControl(click_x:Float, click_y:Float)
Local goal_angle = ATan2((click_y - position.y), (click_x - position.x))
Local start_angle = heading
Local control_pos:Vec2D = New Vec2D(position.x, position.y)
Points = New Deque
For Local i:Int = 0 Until 30
control_pos = NewPoint(control_pos, start_angle, goal_angle, maxRotation, maxVelocity/30.0)
If (start_angle > goal_angle)
start_angle = start_angle - maxRotation
Else If (start_angle < goal_angle)
start_angle = start_angle + maxRotation
End
goal_angle = ATan2((click_y - control_pos.y), (click_x - control_pos.x))
Points.PushLast(control_pos)
End
control.position.Set(control_pos.x, control_pos.y)
End
Function NewPoint:Vec2D (start_point:Vec2D, start_angle:Float, goal_angle:Float, max_angle_change:Float, distance:Float)
Local new_angle:Float
If (start_angle > goal_angle)
new_angle = start_angle - max_angle_change
Else If (start_angle < goal_angle)
new_angle = start_angle + max_angle_change
End
Return New Vec2D(start_point.x + distance * Cosr(new_angle * (PI/180)), start_point.y + distance * Sinr(new_angle * (PI/180)))
End
Doing this lets me intuitively limit the turning radius and maximum velocity of the units. It might not be the fancy way to do it or the most efficient. But it works. And that is all we are going for.
Note about Monkey-X Documentation for ATan2(x, y): the documentation reads that it gives the Arc Tangent of x / y in degrees. Traditionally these variable names are switched. If you are thinking in typical Cartesian coordinates you will want to pass you Y value in as the first parameter and X as the second. This caused me a bit of confusion.
p.s. This code was not cleaned up for this post and a lot of it was written between 10 P.M. and 1 A.M. The purpose was not to show of how code should look but rather sample implementations of useful functions.
Creating a 2D camera in a video game is a little trickier than I thought. I started out doing it wrong (didn’t look up enough info), and wasted a lot of time trying to figure out what was going on. Hopefully, I can remove some of that trickiness for you and give you a nice simple example of using a 2D camera in a game.
I looked up stuff in the Monkey X forums and came across this article, but it wasn’t plain enough. So this is my attempt at creating the article I wish existed for Monkey X users and 2D following cameras.
This is all code taken from game #3 in my one game a month challenge. Feel free to use it as needed.
The Theory Behind the Code
I started out by watching the tutorial video for camera movement in the Creating a Simple Game Tutorial. The problem was that it just moves the camera down and is not connected to player movement at all.
I wanted to follow my player around in all 4 directions to increase the play area beyond the screen size. This is where my problems began.
Most of the drawing stuff like scaling and rotating rely on matrix math. Moving the “camera” is done by translating the drawing matrix. This is a fancy way of shifting it one way or another.
So What Does Translating Have to Do With the Camera?
The trick is, when we use a camera in 2D, we aren’t actually moving the camera to follow the player. Instead, we treat the drawing space like a little window into the game, and move the game world into the view.
Think of it like a tablet or smartphone, if you want to see whats to the left you swipe to the right. If you want to see whats down below, you swipe up. Its the same with the camera, if you want to follow your player to the right, you move the world and player left and into your view.
Its that simple. Reading this discussion is what made it click for me.
The Trick
You need to determine how far to move you camera. I ended up using the velocity that the player is moving. However, you have to subtract the velocity from the camera’s “position” to get it to behave right.
This is one of the things I messed up before I figured out that left is right and up is down.
Without further ado, here is a simplified version of…
The Code
So before we begin with the camera we need a few things.
1. A character to follow around
2. A way to move the character
3. Something that lets us know when the character is moving
A character to follow and way to move him
Lets create a simple player with a couple of “vectors” that represent his position and movement.
Class Vec2D
' Vec2D class shamelessly borrowed from Jim's Small Time Outlaws
' Youtube channel on creating basic games with Monkey X
Field x:Float
Field y:Float
Method New(x:Float, y:Float)
Set(x, y)
End
Method Set(x:Float, y:Float)
Self.x = x
Self.y = y
End
' My own personal touch to Jim's code
' Calculates Euclidean Distance
Method Distance(point:Vec2D)
Local xdelta:Float = point.x - Self.x
Local ydelta:Float = point.y - Self.y
Return Sqrt(xdelta * xdelta + ydelta * ydelta)
End
End
Class Player
Field position:Vec2D
Field old_position:Vec2D
Field velocity:Vec2D
Field target:Vec2D
Field distance:Float
Field speed:Float
Method New(name:String, x:Float, y:Float, speed:Float)
Self.name = name
Self.position = New Vec2D(x, y)
Self.old_position = New Vec2D(x, y)
Self.speed = speed
Self.velocity = New Vec2D(0, 0)
Self.distance = 0
End
Method Draw()
SetColor(0, 255, 0)
DrawRect(position.x, position.y, 10, 10)
SetColor(255, 255, 255)
End
Method Update()
' update position
Self.old_position.Set(position.x, position.y)
Self.position.Set(position.x + velocity.x, position.y + velocity.y)
' update velocity
If (target <> Null)
If (position.Distance(target)) < distance
distance = position.Distance(target)
Else
' we are not getting closer to the target anymore so stop moving
velocity.Set(0, 0)
End
End
' update size
If exp >= 10
size += 1
exp -= 10
End
' update box
Self.box.Set(position.x, position.y, 10, 10)
End
Method SetTarget(x:Float, y:Float)
Self.target = New Vec2D(x - 5, y - 5)
distance = position.Distance(target)
Local deltax:Float = Abs(target.x - position.x)
Local deltay:Float = Abs(target.y - position.y)
Local sum_delta:Float = deltax + deltay
If (target.x > position.x)
velocity.x = speed * (deltax / sum_delta)
Else If (target.x < position.x)
velocity.x = -speed * (deltax / sum_delta)
End
If (target.y > position.y)
velocity.y = speed * (deltay / sum_delta)
Else If (target.y < position.y)
velocity.y = -speed * (deltay / sum_delta)
End
End
End
This is a slightly complicated player class that moves the player toward some target position that the user touched on the screen. You can read more about the Vec2D Distance method and the math behind the player SetTarget method in my previous article.
Something that lets us know our character is moving correctly
This particular game is about eating things (think Pacman crossed with snake and my own particular twist). So we are going to put some little dots on the screen for our player to eat. I called them plants (the code that lets him eat them is removed from this example, you can find the full game code on github).
Class PlantLife
Field position:Vec2D
Method New(x:Float, y:Float)
Self.position = New Vec2D(x, y)
End
Method Draw()
SetColor(0, 100, 255)
DrawRect(position.x, position.y, 5, 5)
SetColor(255, 255, 255)
End
End
We will be drawing a few of these to the screen as points of reference to see how we are moving.
The Camera
Surprisingly, the Camera class is really simple.
Class Camera
' Camera class alos shamelessly borrowed from Jim's Small Time Outlaws
' Youtube channel on creating basic games with Monkey X
' Great stuff you should seriously check it out
Field original_pos:Vec2D
Field position:Vec2D
Method New(x:Float=0, y:Float=0)
Self.position = New Vec2D(x, y)
Self.original_pos = New Vec2D(x, y)
End
Method Reset()
Self.position.Set(original_pos.x, original_pos.y)
End
' My own take on the update method though
' This is what we use to follow the player around
Method Update(velocity:Vec2D)
Self.position.x -= velocity.x
Self.position.y -= velocity.y
End
End
Now Putting It All Together
The actual game app is where it all comes together.
Import classfile
Class AppName Extends App
Field player:Player
Field cam:Camera
Field plants:List
Field max_plants:Int
Field map_width:Float
Field map_height:Float
Method OnCreate()
SetUpdateRate(60)
map_width = 1000
map_height = 1000
max_plants = 100
plants = New List()
player = New Player("Me", 320, 240, 4.0)
cam = New Camera( )
GeneratePlants()
' Set the random seed for this instance of the game
Seed = Millisecs()
End
Method OnUpdate()
‘ Get our target position to move the player
If TouchDown(0)
player.SetTarget(TouchX(0) - cam.position.x, TouchY(0) - cam.position.y)
End
‘ Update the camera before we update the player
‘ Because we don’t want to move the camera before the player starts moving
cam.Update(player.velocity)
player.Update()
‘ Refill our eaten plants
GeneratePlants()
End
Method OnRender()
Cls(255, 255, 255)
‘ Push the matrix so we can draw a HUD later
PushMatrix()
‘ Move our world to the camera position
Translate(cam.position.x, cam.position.y)
‘ Draw in our player and our plant life
player.Draw()
For Local plant:PlantLife = Eachin plants
plant.Draw()
End
‘ Pop the matrix back out
PopMatrix()
End
Method GeneratePlants()
‘ draw some plants on the screen so we can see when we move
Local plant_count:Int = plants.Count()
If plant_count < max_plants
For Local i:Int = plant_count Until max_plants
Local xpos:Float = Rnd(25.0, map_width - 20)
Local ypos:Float = Rnd(25.0, map_height - 20)
plants.AddLast(New PlantLife(xpos, ypos))
End
End
End
End
Function Main()
New AppName()
End
And that should give you a simple skeleton game with a player that moves around to wherever you touch on the screen and is always in the middle of the camera.
Bonus post coming Wednesday this week on Game Design podcasts.
When I went to college to study computer programming, it was not with the intent of making video games. The school I attended required that you have a minor, and since computer programming was all I wanted to do at the time, I chose the one that required the least classes, Math.
Fast forward a couple years and I am sitting at my computer trying to think of how to handle getting an object to move to where it gets clicked. Little did I know how useful that math minor would be in tackling the problem.
Euclidean Distance
The solution I have currently implemented relies on 2 things, the Euclidean Distance and the vector needed to move in the right direction.
Euclidean distance is a fancy way of saying distance between 2 things on a flat surface. The world is round so it is usually only accurate over small distances, but for a 2D video game, the world is completely flat so it works perfectly.
Don’t let this statement scare you, but it is basic trigonometry. As long as you know the X and Y coordinates of your 2 points, it is surprisingly simple to calculate.
You need the hypotenuse of a right triangle where the difference between the X coordinates of the 2 points is 1 side, and the difference between the Y coordinates of the 2 points is the other side.
The distance is then the square root of these sides squared and added together. You might know this formula by its more common name, the Pythagorean Theorem.
The Monkey code to calculate the distance between 2 points looks something like this:
Function Distance(point1:Point, point2:Point)
Local xdelta:Float = point1.x - point2.x
Local ydelta:Float = point1.y - point2.y
Return Sqrt(xdelta * xdelta + ydelta * ydelta)
End
So now that we know how far away our object is from its target point, how do we move it there?
Calculating Velocity
This is actually a little bit simpler.
So lets assume our object has some speed that we want it to move at, for example I will use 4 pixels per update. So now when we touch or click on the screen, we want the object to start moving at 4 pixels per update towards the point we touched.
Determining what part of the speed is in the X direction and what part is in the Y direction is done by finding what part X and Y have in the distance.
Now we don’t want our Euclidean distance from before. Now we just want the X and Y distances. So our X portion of our speed would be:
speed * X-distance / (X-distance + Y-distance)
and our Y portion of our speed would be:
speed * Y-distance / (X-distance + Y-distance)
I couldn’t find a nice place that spells this out without being too formal so my simplified version will have to do.
The Monkey code for this would look something like this:
Local deltax:Float = Abs(target.x - position.x)
Local deltay:Float = Abs(target.y - position.y)
Local sum_delta:Float = deltax + deltay
If (target.x > position.x)
velocity.x = speed * (deltax / sum_delta)
Else If (target.x < position.x)
velocity.x = -speed * (deltax / sum_delta)
End
If (target.y > position.y)
velocity.y = speed * (deltay / sum_delta)
Else If (target.y < position.y)
velocity.y = -speed * (deltay / sum_delta)
End
If the X difference was 12 and the Y difference was 23 then the resulting velocity in the X direction would be:
Giving us a nice straight line towards our target point.
Simple 2D Movement
Combining the velocity equation with the distance lets us move our object toward the target, checking the distance as we go.
As long as the distance is decreasing, we are heading in the right direction. But when the distance is 0 or when it starts to increase, it is time to stop because we have arrived or passed our target.
I hope this helps jumpstart some simple 2D movement for you. I will release a full working example in game #3 (coming soon) in my 11 games in 11 months series. Until then, keep making awesome things.