Path of the Monkey is a beginnerís guide to mastering the art of crossplattform development using the new Monkey Language. Follow it and one day you may learn to Master the Path of the Monkey, but for now be happy that you are a novice and that you have many exciting challenges and tests in front of you ;)
First download and extract monkey. I assume you have done that already so letís start.
In this guide we are going to create a very simple app that CAN run in html5, flash, on iPhone/iPad, Android and in XNA. However, the guide will only show you step by step how to do run in html5. When you have mastered the basics of monkey, youíll be ready to try out the other platforms, so letís go!
Iím going to assume you installed monkey to C:\Monkey. If you have it installed elsewhere thatís just fine, Iíll call this folder the monkey folder in the examples, so just replace that with whatever path you have it installed at.
Since monkey is more Ďobject orientedí that itís ancestors. This means that you might not only want to learn monkey, but also try to master object orientation.
If you are a seasoned veteran there is a quick index overview at the end of this article you might want to do a quick review of.
Monkey works like this: you start by writing your game or program in monkey code (more on coding later), then you select what platform you want to translate to. Valid targets are: Html5, Flash, XNA, Android, iPhone, C++.
Importantly, monkey does not compile games directly to each target platform. Instead it translates monkey code into code for each platform, which you then compile and run using that platformís tools. The advantage of this approach is that you could add platform specific code to further to take advantage of that platformís strengths (eg, for a iPhone game you would edit the Xcode to include Appleís iAds or Game Center functionality). It also avoids any legal issues, since you are indeed using Appleís own tools when you build on iPhone for instance, even though itís just a F5 click for you.
Before you can build to a certain platform you need to have all requirements for that target platform installed (except Html5 which you probably have already if you have an updated browser). How to install platform tools will be something youíll have to look out for in other guide; however the information on how to do that is out there on goggle J
Once you have compiled your monkey game to one of these platforms then a folder will be created called /nameOfYourMainFile.build/. How exactly this works will be explained step by step in the next chapter!
In this build folder you will find one folder for each target platform you have built to. In these platform folders you will find the platform-specific files that you can open. If you compiled to html5 then that folder will contain an html document that you can view and open with a web browser, the XNA folder a XNA project that you can open in Visual Studio, and so on.
What monkey does is that it takes the code you write and translate it to this other language. After that, monkey uses each targetís external tools (that you have to install yourself) to execute the translated code. In html5 this is very simple since the program can be directly run in any new browser. However for other platforms you need to install and setup stuff (such as the Android SDK, or Adobeís Flex SDK for Flash) but then you can just click N play on your Andriod phone.
We are going to start by compiling a short application in html5; the only thing we are going to do is to color the background blue and write a simple text message.
1. Create a file called blueScreen.monkey. Open the Monk Editor if you havenít already. (note the filename must start with a lowercase letter!)
2. Put this file in a folder of your choice. This will be your game folder.
3. Open the file, paste this code into the file:
Class BlueScreen Extends App
DrawText "You got a bluescreen",200,200
4. Click F5. Choose Target html5. You should now have a bluescreen in your web browser. Not very useful, but itís something to build upon J
5. Open your game folder, and find the newly created folder called /blueScreen.build/. This is your build folder. In this folder, you should find a target folder called html5.
6. In the html5 folder you will find a data folder called /data/, ajaxserver.php and main.html. Opening main.html is the same as ďrunningĒ your latest application version!
7. If you have any errors, make sure your browser is html5 compatible.
8. You could copy the contents of your html5 folder to a website and make it playable online (although you may want to edit main.html to add some extras and modifications)
9. Still have questions about the code? Fear not, all will be revealed in the next chapter!
To use monkey for anything useful you need to also use the mojo framework. Mojo allows you to use a set of 2D graphics commands that work on all supported platforms.
Monkey uses a very handy module system. Modules contain sets of commands that extend the monkey language and allow it to do more things. The core monkey language itself only contains the core language and logic. To do things like 2D graphics or get input from a keyboard or touchscreen youíll need to use the right module.
A Module is a .monkey file. You canít import files, but you can import modules. (Donít get confused, read on).
If you want to use a module you must import it. In C:\Monkey\modules\ you will find the modules that you have installed, if you are starting out you probably only see three folders: mojo, monkey and trans. These folders each contain several of modules that you can use. The monkey module contains lists, random, and such things and are included by default.
To begin with we want to use mojo.app and mojo.graphics.
In the /modules/mojo/ folder you will find a file called app.monkey and another called graphics.monkey. To use these you write:
The import will first look in your game folder, since you have no folder there called mojo, the import will continue to look in the modules folder, where in this case these files will be found, and when it is found it is imported. Once successfully imported you can use all functions, global, classes and methods in that Module.
Note that the module and folder names are using small letters. monkey is a CaseSenSitive language and it is important that you, by language definition, use a naming convention where the first letter of your files = modules and folders are lower case. Like: map.monkey or myTribe.monkey or spaceShipAssaultCannon.monkey. Any classes you put in these should start with an UpperCase letter, like Map, MyTribe or SpaceShipAssaultCannon.
When you build you must select a .monkey file that contains a Main Function. The Main Function is where the code will start. Since mojo is a framework you need to use inheritance to use it. Inheritance will be explained later in more detail. To get a game running you must write your own Class that Extends the base App Class., You can see how that looks like in the blueScreen,monkey example above. App is a base class that every mojo program must extend.
To activate the App you need to create an instance of the Class you wrote that Extends App.
This created a new instance of the class BlueScreen :
This will initialize mojoís graphics engine and once that is done the OnCreate Method (if we have one) will be called, followed by OnUpdate, followed by OnRender.It is recommended that you separate your logic from your graphics code, and to do that you can use the OnUpdate Method. However for the OnUpdate method to be called you must first in the OnCreate method specify SetUpdateRate 60, which will call OnUpdate 60 times every second.
The blueScreen.monkey example was simplistic to say the least. Let us add to it.
The fun stuff starts when we can draw images and primitive shapes. Before you can draw an image you need to load it. You can only load stuff after OnCreate has been called, which is why it is common for simple demos like this one to put all ďloadingĒ into the OnCreate method, however you can load stuff later on too if you prefer. To the left is the car.png image I used.
Note: The size of images you use should be a factor of 2, to avoid weird graphics problems on some phones. For this example create a file called racing.monkey and another called car.monkey. You also need an image called car.png, Iím using the one to the top-right. Itís 64x64 pixels, and you are free to use it too of course J racing.monkey Ė This file contains the main method. See further explanation of the code below.
Class Racing Extends App
Field FPS:Int = 100
Field Delta:Float = 0.01
CarImage = LoadImage("car.png")
PowerupImage = LoadImage("powerup.png",64,64,2)
'EngineSound = LoadSound("engine.wav")
Car = new Car( DeviceWidth()*0.5, DeviceHeight()*0.5 )
Car.Render( CarImage )
Strict forces you to declare return types on Methods and Fields. This could be quite important, especially if you deal with inheritance, since the return type determines if the inheritance works! Like OnCreate, if you where to change :Int to something else, OnCreate would not work as you would expect. Anyhow since so many painful errors can creep into your code if you do not use strict, Iím going to use it here, and I advise you to unless you have a lot of programming experience from before.
This example is divided into two files. The other file is called car.monkey, and it is in the same directory as the file that holds the Main Function, which is this file, racing.monkey. This means that car is imported by writing import.car. If we put car into a directory, letís say /vehicles/, then we would import it like this: import vehicles.car
Every game you make must have a Main Function, it tells monkey that all imports will be measured from this location and that the code will start to run here. The function must be called Main and return void, however you can put whatever you want into the function.
The App Class is a bit special since every game you make will need to extend it (assuming you use mojo, and at the moment there are no alternatives). When my class called Racing extends App it becomes an App itself. And since Racing now is an App it also has methods that we can use.
A Class is a blueprint that can be used to create objects of that type; of that Class. Classes and objects are the key to Object Oriented Programming (OOP). SO in its infinite simplicity, how does OOP work? Programming is all about taking something real, like the concept of a spaceship, and re-creating it in the computer. When we do this we simplify a lot.
In OOP you create your game by using Objects that you create and populate with data. For example if we have a SpaceShip Class like this:
This little Class explains how we model a SpaceShip. This SpaceShip (think itís a destroyer) has three Weapons, Weapon is in turn also a Class that defines how a Weapon works. See below:
Ammo -= 1
Print "FIRE CANNON! Remaining Ammo " +Ammo
However Classes are just blueprints, and a blueprint does little good if no one is using it. Using our Class blueprints it is just a matter of creating an Object of that Class. So if we want a Weapon, we go: New Weapon. Seen that before? Like in the example above, Classes can contain other classes, but what this really means is that an object created from a Class can contain an Object created of another Class.
Letís create a SpaceShip. To be able to use the spaceship we put it into a variable. The following code example creates a simple empty instance of the ShipGame Object, which is called MonkeyShip.
Class ShipGame Extends App
Field MonkeyShip:SpaceShip 'This holds the Ship for later access
' Add code here...
To create a new SpaceShip you use the New keyword. Just type New SpaceShip. To save it in the Field called MonkeyShip we would simply go: MonkeyShip = New SpaceShip.
Now that we have created an Object we can use it and access everything it contains. We want our ship to have Weapons, but right now we have a SpaceShip with no fuel and no weapons. Why? Because it was empty when we created it. Either we can make it that all new SpaceShips have fully equipped weapons and are fully tanked, or we can do it manually when we create each SpaceShip.
This is simple stuff so Iíll be quick, add this code to the example above where it says ďadd code hereÖĒ. Check back on how the blueprint (the Class) of our SpaceShip looked like to see the names of the Fields we access.
MonkeyShip.Fuel = 3500
MonkeyShip.LeftCannon = new Weapon
MonkeyShip.LeftCannon.Ammo = 500
MonkeyShip.RightCannon = new Weapon
MonkeyShip.RightCannon.Ammo = 500
I will leave the Main Cannon for you. Now that we have a couple of cannons we can fire them. That would look like this:
Now if you put the Example above in the same file as the SpaceShip Class and the Weapon Class it should work. But it is still boring since there is no real purpose to this firing and no cool effects, but the logic is the same.
LoadImage is a bit tricky since we now need to create a data folder for our images and sounds. This is important! The folderís name must be the same as the file where your main function is, but end with .data. So this means you should create a folder called /racing.data/ in your game folder. Then you put your images in this folder ŗ Like this: C:\Monkey\MyRacingGame\racing.data\car.png
Your racing.data folder should contain car.png (64x64 in size) and powerup.png (64x128 in size, found to the right here ŗ )
As an option try to find a .wav file with an engine sound, and play a sound when we you run over a power-up.
PowerupImage = LoadImage("powerup.png",64,64,2)
This line above means we load the power-up image, we also tell monkey that the width and height of the image is 64x64, and that it contains two images. monkey will now automatically load both these images under the powerup.png identifier. This is often used for animated images, since it makes things simpler by loading all frames at once. You can likewise keep all your images in one large image, if you want to, but I would not recommend it, as it easily gets messy.
Car = new Car( DeviceWidth()*0.5, DeviceHeight()*0.5 )
Here I create an Object Classified as Car, when I do this I also take the time to set some data in the Car Object on the same line. If you are to follow OOP principles then an Object should be self sufficient, and once created it should be fully functional. You donít need to be strict about it, but if you are, you need to send any data that the Object needs along when you create it. In this example I made the Car Class so that once it is created one must specify the carís position on the screen. DeviceWidth()*0.5, DeviceHeight()*0.5 is the middle of the screen or device .
Try changing stuff, or keep reading.
The SetUpdateRate FPS line forces the game to update using this Framerate variable; FPS (which stands for Frames per Second). If you forget to call this, OnUpdate will never get called and OnRender will only ever be called once. This can be very confusing - you have been warned.
In OnUpdate and OnRender I call two Methods that are found in the Car Class, Car.Update() and Car.Render(). You might also have noticed that I send some parameters there, the first one called Delta is the time in seconds between OnUpdate is called, I use this to get accurate timing, more on that later too!
Now here comes the Car class. Save it as car.monkey next to racing.monkey in your game folder .
Field Power:Float,MaxPower:Float = 100
Method New( startX:Float, startY:Float )
' Start in the middle of the screen
X = startX
Y = startY
Method Update:Void( delta:Float )
If KeyDown( KEY_UP ) or KeyDown( KEY_W )
Power += 50 * delta
If Power > MaxPower then Power = MaxPower
If KeyDown( KEY_DOWN ) or KeyDown( KEY_S )
if Power > 0
Power -= 70 * delta
Power -= 30 * delta
If KeyDown( KEY_LEFT ) or KeyDown( KEY_A )
Direction += 90 * delta
If KeyDown( KEY_RIGHT ) or KeyDown( KEY_D )
Direction -= 90 * delta
X += Power*Sin(Direction)*delta
Y += Power*Cos(Direction)*delta
Local Thickness:Int = 32
If X > DeviceWidth()-Thickness then X = DeviceWidth()-Thickness
if X < Thickness then X = Thickness
If Y > DeviceHeight()-Thickness then Y=DeviceHeight()-Thickness
if Y < Thickness then Y = Thickness
Method Render:Void( image:Image )
DrawImage image, 0,0
In the Car Class I make use of keyboard input and maths, so I imported mojo.input and std.math.
Take a good look at the KeyDown command. You can send in many different constants, like KEY_SPACE or KEY_LMB. You can also check for KeyHit, the difference with KeyHit is that it required that you release the key until it activates again. KeyDown will return true as long as the key is held down.
The Power field represents how fast the engine is turning, or in other words it represents your speed.
X += Power*Sin(Direction)*delta
Y += Power*Cos(Direction)*delta
This could look confusing, but what it does is that we create an arrow (also called Vector) that points in the angle of our Carís Direction, and the length of the arrow is equal to the power of the carís engine. The result is that our position, denoted by X,Y, will change when we press Up, Down and Turn Left and Right.
Monkey has some useful but simple features to manage rotating, scaling and translating (moving) images.
Method Render:Void( image:Image )
DrawImage image, 0,0
PushMatrix means the same as ďSave the current render stateĒ, which includes where we draw (called translation), the angle towards we look (called rotation), and how big or small we are compared to the original image ( called scale).
Now since this is the only thing drawn in the entire game the call I make to PushMatrix() here will save the ďdefault stateĒ which is Translation at 0,0 Rotation in angle 0, and Scale 1,1.
After that we are free to change the rendering settings, because we know that we can set them back to default when we are done using PopMatrix
So I move the rendering to X,Y which is the location of the Car on the screen. I then rotate the Car with the angle determined by Direction, which is changed when you hold KEY_LEFT or KEY_RIGHT. After doing the rotation I move the rendering 32 pixels up and to the left, this is because our image is 64 pixels wide and high, and I want to rotate it from the imageís center Ė remember that the default is to rotate it from its top-left corner!
Now I draw the image at location 0,0, which in reality is Car.X, Car.Y and with Car.Direction rotation, and then with an offset of -32,-32. If you change this offset then you can rotate the car at the front, try -32,0 instead for example.
Whatever target you compile to you will need to modify that targetís settings or code. In html5 thatís as easy as opening the MonkeyGame.html file in your .build folder. If you open this file you will find CODE tags. Anything outside these tags can be modified WITHOUT being overridden during the next compile. So for instance you can now change the size of the html5 render canvas.
Monkey do have commands for Touch Input, called Touch so check that out in the Docs if you want to learn more about that.
You should be able to find the completed sample code in the samples shipped with monkey. Feel free to use the source and images in any way you want, and if you want to contribute to this or future guides please post feedback and suggestions on the forum!
Need more help? If you feel there still are questions to be answered, confusion to resolve then I sincerely suggest that you post in the forums, the community is very warm and friendly and it is likely your question will be answered in a very short time, it can save you a lot of time in comparison to figuring it out yourself J
Here is a quick rundown on the most important parts and commands of monkey. Itís more of a quick index.
Print "Print this"
Class SimpleApp extends App
OnCreate is called once when the App has been
initialized, you cannot access any graphics commands before this.
OnRender is called automatically each frame; you can put draw commands here.
SetUpdateRate framesPerSecond is a command that allows you to add an Update loop separate from the graphics.
OnLoading is similar to OnRender, you can use
graphics commands here but images may not have been loaded, however drawing a
not-loaded image here will not cause any error, allowing you to get your app
Update is called as often as specified in SetUpdateRate, you cannot put draw commands in here.
Files are imported relative to two points, the modules folder and the folder in which the file that contains you main-method resides in.
This would import a file called ship.monkey (Case Sensitive mind you!) in the modules folder, or if no such file is found there, it would try to find that file in the same folder containing your main-file.
This would import a file called meteor.monkey found in the folder meteor in your modules folder, if no such file or folder exists in your module folder it will check for that folder and file in the folder containing your main-file.
Any content, such as images, sounds, txt documents must be placed in a folder with the same name as your main-file with .data added to the end, this folder must also reside in the same folder as your main-file. If you main file is fishgame.monkey the content folder should be named /fishgame.data/
These requires import mojo.graphics and must be called in or after OnCreate(), not before.
Loadimage can only load content directly from the data-folder. You canít specify a path, only specify the filename, remember it is caseSensitive. (import mojo.graphics). You can load .jpg and .png
LoadString loads a text-file found in your data-folder into a string, must be .txt.
LoadSound, load a .wav from your data-folder.
DrawText Ė There is only bitmap fonts, so using drawtext means you are drawing a bunch of images. There is a lib already that you can use for beautiful fonts if you need, check the forums. If you just use it for debugging the included one works great, at first DrawText the default will be loaded if you donít specify one yourself. Youíll find the bitmap font file in the .data folder. For actual font usage use the angelfont module supplied with monkey.
DrawImage Ė Specify a position, or use the Matrix functions and draw it at 0,0.
DrawLine, DrawRect, DrawOval Ė Handy for debugging, affected by SetColor and like all graphics, by matrix functions.
When DrawImage, or any other render command is used the position, rotation and scale will be determined both by the values entered into DrawImage, but also based on the current global offset. The only way to rotate a rect drawn using DrawRect is to use the Global matrix commands for instance.
Translation x,y - Move the global draw offset this much in x and y direction. If you call this using 10,10 two times, you will move the offset 20,20.
Rotation angle Ė The angle to rotate around the current global offset position. This also affects translations, meaning that if you rotate 180, and move 10,10, you end up at -10,-10 assuming no other matrix commands are used.
Scale width,height Ė Use to stretch the offset in width and height. This directly affects the offset, meaning that if you scale 2,2 and translate 10,10, you end up at 20,20.
PushMatrix Ė Since affecting the global state can mess with other parts of your game the handy PushMatrix command allows you to save the previous state, pushing it on top of the save-pile. This means that you can Push the current state, use any matrix command to change the global offset, then draw, and then use PopMatrix to restor it back.
PopMatrix Ė Pop or restore the last pushed or saved Matrix state.
GetMatrix Ė You can use this to get the current Matrix as an array to save current state or do conversion math.
These are all included by default in all projects. But donít worry, unless you use them they wonít be translated to your output.
Random(min,max) Ė There is no int random, so always expect this to return a float.
List Ė List is a linked list. You use it by specifying what it should carry, like this: List<Car> this is a list that can only contain cars. Complete definition would look like this, Local list:= new List<Car>
Map Ė Map is used to link one object to another. Like this, Local map:= new Map<Car,Ship>. Car is the Key and Ship is the Value. You canít use int,float,string with Map but there are special Maps for those cases, like this, Local map:= new StringMap<Car> this will create a Map with string as the Key, and Car as the Value.
Math Ė Remember these? Sqrt, Floor, Ceil, Pow, Abs, Sin, Cos, Tan, ASin, ACos, ATan and ATan2. They are at direct access.