Build a 2D Portal Puzzle Game With Unity: More Mechanics and New Levels – Active Premium
It’s time for another fantastic Active Premium tutorial, available exclusively to Premium members. At the end of the third part of this series, we had made the portals teleport the character. In this part, we’ll finish up the game by adding new mechanics and other miscellaneous stuff. We’ll also create a few example levels that make use of our mechanics.
Preview
Let’s take a look at the final result we will be working towards, across the whole of this multi-part tutorial:
Please view the full post to see the Unity content.
Hit the number keys 1-8 to try different levels. The aim is to get the little yellow Roly character to the designated end point, using portal mechanics. The demo shows off a few of the different mechanics we’ll introduce.
Warning: this tutorial is huge! It’s just as long as the third part.
Active Premium Membership
We run a Premium membership system which periodically gives members access to extra tutorials, like this one! You’ll also get access to Psd Premium, Vector Premium, Audio Premium, Net Premium, Ae Premium, Cg Premium and Photo Premium too. If you’re a Premium member, you can log in and download the tutorial. If you’re not a member, you can of course join today!
Also, don’t forget to follow @envatoactive on twitter and grab the Activetuts+ RSS Feed to stay up to date with the latest tutorials and articles.
View full post on Activetuts+


It’s time for another fantastic Active Premium tutorial, available exclusively to Premium members. At the end of the third part of this series, we had made the portals teleport the character. In this part, we’ll finish up the game by adding new mechanics and other miscellaneous stuff. We’ll also create a few example levels that make use of our mechanics.
Preview
Let’s take a look at the final result we will be working towards, across the whole of this multi-part tutorial:
Please view the full post to see the Unity content.
Hit the number keys 1-8 to try different levels. The aim is to get the little yellow Roly character to the designated end point, using portal mechanics. The demo shows off a few of the different mechanics we’ll introduce.
Warning: this tutorial is huge! It’s just as long as the third part.
Active Premium Membership
We run a Premium membership system which periodically gives members access to extra tutorials, like this one! You’ll also get access to Psd Premium, Vector Premium, Audio Premium, Net Premium, Ae Premium, Cg Premium and Photo Premium too. If you’re a Premium member, you can log in and download the tutorial. If you’re not a member, you can of course join today!
Also, don’t forget to follow @envatoactive on twitter and grab the Activetuts+ RSS Feed to stay up to date with the latest tutorials and articles.
Twice a month, we revisit some of our best posts from throughout the history of Activetuts+. This week’s retro-Active post is an interview with one of my favorite Flash and Unity game developers. Does Sissy’s Magical Ponycorn Adventure ring a bell?
Ryan Henson Creighton has eleven years’ experience in the gaming industry, and founded the Toronto-based game studio Untold Entertainment in 2007. He runs one of my favorite blogs on the whole Internet, covering Flash tutorials, Unity tutorials, game design, conference summaries, and rants. Ryan’s very active on Twitter, and has written an excellent guide to game development with Unity.
He also has all the coins.
(Photo of Ryan credit Brendan Lynch.)
QWhen you first got into Unity, you said this was partly because you didn’t want to put all your eggs in one basket, and partly because of a few questionable decisions Adobe made with Flash. That was eighteen months ago. For a games developer curious about Unity today, what would you say are the main advantages?
i’m striving to realize that “build once, deploy anywhere” dream that Adobe hinted at but couldn’t deliver on. Unity doesn’t live up to that promise either, but both platforms get us closer to a place where we developers don’t have to learn a dozen competing technologies just to see our work on different platforms.
Me? Oh – i’m just doing a little game development.
The advantage of Unity over Flash is that Unity 3D is a game engine. Flash is not, plain and simple – it’s a content creation tool. In fact, a few months ago, Adobe announced that they were putting together their first on-staff focus group for game development. Their first! This comes after about fifteen years of developers strangling Macromedia/Adobe’s product into something that can play games. This all leaves a sour taste in my mouth. Flash should have had proper collision detection, pathfinding, particle systems, physics, and gamepad control about eight years ago.
“Marty – MARTY! Flash is in trouble!”
Unity, conversely (as with Scratch, GameMaker, Blender, UDK, and many others) is a proper game engine – you know, for making games. The trade-off with something like Unity is the tool’s reach. Adobe rightly touts its Flash player’s ubiquity … I think 104% of computers are running it, including computers from the future and fictional computers from your favourite sci-fi novels (HAL from 2001 enjoys a good game of Bloons, i hear.) Unity can boast maybe a 5% installation base, if you squint and hold the chart at an angle. It’s to the point where it doesn’t yet make economic sense to develop a web game with Unity, which is why i haven’t. This situation should improve dramatically, as Unity announced they’re working on an “Export to swf” capability, facilitated by Molehill (the agonizingly past-due Flash 3D functionality).
QIt’s only recently that Adobe have embraced Flash game development, while Unity have been all about that from launch. Does the attitude of the companies have a clear difference on the tools themselves?
i think it took Adobe far too long to realize the value of their army of two million developers, and they’re scrambling to do right by that group as companies like Unity Technologies threaten to eat their lunch. After the incredible let-down of Adobe’s half-assed lip-service to 3D in Flash (no built-in depth management? Seriously?), the push for Molehill in the wake of Unity’s popularity is not coincidental. Unity is in a very strong position, because they can look at what Macromedia/Adobe have done and sort of patch holes in history. Building the Unity Asset Store, and offering the Union game brokering service, for example, were very smart moves; MM/Adobe left a lot of money on the table by letting sites like ActiveDen sell Flash widgets.
“Pssst … wanna buy 8.fla?”
As a developer, with so many platforms and languages and development tools out there, I want to ally with a company that has my back – a company that cares that i’m using their tool, responds to the needs of the community, and respects the fact that i could flit like a butterfly to the next nectar-filled development platform, but i don’t. Can Adobe pull it out of the fire with their game squad? Maybe. i’ve spent 11 years working with Flash, and inertia makes it very tough for me to dislodge myself. But i feel there’s bad blood with Adobe, and that their sudden interest in developers feels cynical. When i spoke to an Adobe employee earlier this year about it, i said “A focus on game developers? Finally! What took you so long?” He merely shrugged.
Game developers are gold to these companies. They deserve more than a shrug.
QYou’ve had a lot of experience helping people learn to develop games; besides your book and your blog, you’ve worked for the Board of Education, and as a college instructor. Is Unity easy to learn?
i don’t know if this is a worldwide phenomenon, but one of the pitfalls the colleges around Toronto succumb to is that they hear Unity and Flash are sooo easy to develop for. Console developers like to say, somewhat disparagingly, “we use Flash for rapid prototyping.” So i hear this repeated in colleges: “We’ll teach our students to learn Flash, but just for – you know – rapid prototyping”, as if all the “cool” and “real” programming are only happening with C-based languages, and Flash is only appropriate for toddlers or Special Olympians. Same deal with Unity 3D – the deans and program co-ordinators hear it’s “super-easy” to develop games with the tool, so they figure they can just run a 4-month course inside a Game Development program and that’ll be sufficient.
We merely make it look easy.
If course, if you’ve actually bothered to make a game in either program, you’ll know that both require real-live Object-Oriented Programming chops. Sure, they do a lot of heavy-lifting for you – you don’t have to write your own physics engine in Unity, and you don’t have to worry about memory management or garbage collection in Flash … but both Unity and Flash require real, actual programming skills. Shocker! You need to know at least the very basics: operators, conditionals, functions, arguments, return values, variables, loops, and arrays. This is the very stuff I’m covering in my Understanding Programming tutorials on the Untold Entertainment blog. Once you’ve got that stuff under your belt, you can check out something like the Understanding Classes series, which is written for everyday folks like you and me (not CompSci eggheads).
So these colleges, who attract mostly video game players instead of video game creators, lead kids with “art skills” down the garden path, thinking that since these tools are “so easy”, a teacher should just be able to plow through 4 months with the students and they’ll come out the other end knowing how to build games with Flash or Unity. Then these kids put “i know teh Flash and teh Unity and sum UDK hyre me hor hor hor” on their resumes, and the market is flooded with grads who think they know how to do things. These grads – in the very worst cases – are hired by people who believe them. And then clients come to more experienced developers like Untold Entertainment asking me to fix what these kids broke. It’s a grim state of affairs.
“Let’s get my nephew to do it! He learned Flash at a community college!”
A much healthier approach is if you’ve got a student intake full of “artists” and you’re running a Flash course, teach them asset prep: how to build a puppet for animation, how to create both tweened and straight-ahead animations, how to name things, how to manage layers and libraries, etc. Then the graduate writes “i know teh asset prep in Flash hyre me hor hor hor” on his resume, and he’s actually correct, and so everyone is happy. If you’re running a Game Development Program and you want people to believe you and not get a shitty reputation like many community colleges are getting, you absolutely need to bite the bullet and properly teach your students how to program. Don’t run a Flash course or a Unity course. Run a programming course, which uses Flash or Unity to help students see results more quickly and easily (note to colleges: “more quickly and easily” is a comparative statement. It is NOT the same as “quickly and easily”!)
So, “is Unity easy to learn”? Yes and no. It’s certainly the easiest time you’ll ever have trying to build a 3D game, but that’s all relative. Designing in 3 dimensions is just straight-up complicated, which is why in my Unity 3D Beginners Guide, I stuck mostly to quasi-3D, and 2D games using 3D models. You don’t have to chug along very far before having to worry about trig and differential calculus, depending on your game design. Thankfully, there are a vast number of simpler game designs that you can absolutely pull off as a beginner, which is what my book aims to show you.
QFor a new Unity developer, what are three big mistakes that are easy to make but really important not to?
1. Do NOT pay a hooker with a cheque.
i can come up with others, but if you break that rule, you’re basically done for.
As pertains to game development, the biggest mistake that new and experienced developers alike make is dreaming too big. It’s very easy to play a video game and to be blind to all the effort that went into developing it – to forget the fact that you’re one person and they have a team of dozens … that they’ve got a budget and work 9-5 for years and you’re building your game for “free” in your off-hours and weekends. As i say in the book, many developers think they realize this, and they start making concessions: “Well, i wanna build something like World of Warcraft except better, because i feel the developers really messed up certain aspects of that game. But i realize it’s probably out of scope for me to build WoW, even if I get Johnny to do the art for it next week while he’s off school with an ingrown toenail, so i’ll tone it down a bit: i’ll only have twelve races and fourteen classes and thirty-seven weapons and blah blah blah …”
“Delusion” is not just a river in Africa.
This is the wrong approach. You won’t finish, and you’ll become bitter and jaded and you’ll move out of game development to pursue something easier, like hauling lumber in a logging camp.
The right approach is to think of the simplest game you can imagine, like motherfuck*ng dress-up, and then SCALE IT DOWN. Instead of a dress-up game with twenty outfits, reduce it to FIVE. i’m absolutely serious about this. Think of something almost insultingly simple, and then simplify it. At the end of the day, if you have one finished project, you are a fundamentally better human being than someone with twenty-thousand great ideas. He who executes, wins.
i think this is one area where those of us who grew up in the 80’s arcade era have a distinct advantage. We know what a small, manageable and playable game looks like. We know that you don’t need Gears of War to have fun – you just need Mr. Do or Bentley Bear running around some maze collecting golden whatevers. Today’s new crop of game developers have only monstrous multi-million dollar console titles as their models. The solution is for gamers to play more casual and classic games, and to really explore games that create fun with very few bells and whistles.
Of course, even veteran devs aren’t immune to this problem of over-scoping things. It’s probably my Achilles heel, and the reason why i don’t finish more of my own games. Here’s an anecdote from the trenches: i’m working on a platform game for a client right now, and it’s going to have a Level Select screen. i asked how many levels he wanted me to build, and he said “Oh, i dunno … 25?” and i said “Sure”, because 25 levels seems like a decent number of levels for a casual platform game. You see how that works? i wasn’t thinking about how long it was going to take me or how much money I was being paid – i was measuring my expectations against other games i’ve played, notwithstanding the staff, budget, or development time those games enjoyed.
It wasn’t until i sat down and pieced it together that i realized my mistake. If each level took one hour to build, it would take me 25 hours – over half a work week – to build all those levels. If levels took two hours to build, I’d be spending well over a week on them. To my horror, i realized it took upwards of FOUR HOURS to compose even a simple level. That worked out to over two straight 40-hour work weeks of building levels, which wasn’t sustainable for the project’s timeline and budget. Thankfully, my client is totally cool and agreed to reduce the workload to a more reasonable 12 levels. With a less understanding client, i could have lost a huge chunk of cash on the project. And I need that cash, you see, because apparently you can’t pay a hooker with a cheque.
Asking a prostitute if you can run your credit card through her slot is usually misconstrued as innuendo.
QDuring the past year or two we’ve seen a huge changes in the “smaller” games industry: Facebook games are everywhere and mobile games are hot. Over the next 3-5 years, and ignoring the obvious trends, where do you think the industry will go next?
Technically, Facebook games aren’t everywhere. They’re in one place: Facebook. NEWS of Facebook games is everywhere. The press really goes nuts whenever anyone makes boatloads of money on something many consider frivolous or fun. Their coverage conveniently ignores the scads of people who aren’t making money, so we get a real blockbuster mentality. Cityville is HUGE! Great. What about all the other developers who are making games and earning nothing? Iron Man is HUGE!! Super. What about all the people making indie films about people and their feelings?
To predict trends in the game industry, you really only have to look to film. People – especially old people – are going to try to shape and mold the games industry into something with a studio system, where five or six very large entities control all the developers, and in order to get your work seen anywhere, you’re going to have to go through gatekeepers. They say “content is king”, but it’s a ruse: content is peasant. Content is only king if you’re an aggregator of content; without the content, you’re not in play. But the sneaky thing about saying “content is king” is that it implies the people who make the content have some sort of power. They don’t. The people who squeeze the content out of the creators and use their dominance to wrestle rights and control out of the creators’ hands have all the power.
So many people are trying to get into game development now that developers themselves have become a commodity. Companies now “have” developers, and they wield that power like a stick. That’s why Adobe is finally organizing a game team – they want to try to control the community of Flash game developers as “their people”, just as Apple has its throng of iOS devs that it touts. Whenever a new device comes out, the developer incentives are getting sweeter – just take a look at RIM’s free Blackberry Playbook give-away, the Google Nexus One and Xoom give-aways at GDC. All these companies will increasingly dance and shimmy and wiggle their jiggly bits in order to attract developers to their platforms.
Hey, big boy … our platform is well-developed.
Once they have developers in their snares, though, the treatment gets a little rough. Check out the stink Amazon caused with its app store. After tanking the casual downloadable market in its price war with Big Fish Games (dragging the average price of a game from $20 down to $6.99), they developed absolutely vicious Terms of Service for their new app store that turns developers’ hard-fought work into collateral in another high-level price war. (Thankfully, the IGDA and others grew wise and are warning developers away from Amazon’s predatory practices.) Apple famously drove the price of mobile games down to 99 cents, which makes sense perhaps for songs, but is absolutely deplorable when it comes to something like a game.
For the future, I see a continued cat-and-mouse game of gatekeepers vs. developers, where gatekeepers and conglomerates try to snatch up developers in their net, and hurl those wads at each other in a fight for dominance, while clever developers slip through the holes in the net and find new places to have their work seen, heard, and purchased. They almost had us with the console publishing model, and then digital distribution came about. Now, publishers are trying to lock up that market with publishers, but some of us will escape. And escape we must.
I’m lifting a lot of what Dan Cook from Lost Garden has to say on this topic. Go check out his blog, and his excellent GDC’11 presentation on platform power.
Another prediction: at the end of the next five years, I will be surprised to find myself paying for any tool that enables me to develop games.
QYou’ve talked in the past about how TV guys just don’t get game developers — they’re very naive, and just want a number for their budget — is the situation improving?
Not. At. All.
The situation in Canada is a little different than elsewhere. We have a number of funds that teevee production companies can tap into. In fact, one of the largest funds for teevee actually requires the production company to create an “interactive digital media” component, which can include games. (Often, the teevee prodcos produce “webisodes” to take the easy way out on this.) Since teevee people really don’t understand games or how to profit from them, they see the IDM arm as a necessary evil – a marketing expense. They often try to spend as little money as possible on the IDM component of a show. They can sell the show to broadcasters, but they don’t know how to sell the IDM aspect, and many broadcasters expect them to throw in the mini-site, games, webisodes, etc. for free with the show license. And in the case of preschool and kids’ entertainment, which is where Untold Entertainment specializes, it’s even more difficult, because you essentially have to rule out the advertising model. No parent wants their four-year-old kid playing a game with a big-ass McDonald’s logo slapped on top of it.
Um …
Mobile is budging that a tiny bit. Many of the kids’ teevee companies here in Canada are diverting large portions of their tiny IDM budgets to mobile, because they’re just tickled that they can charge money for those products. Even if it is only 99 cents, that’s 99 cents instead of the ZERO DOLLARS they’d normally have earned. i’m not convinced these investments will necessarily pay out for teevee companies, though. Teevee people love to talk about the “pass-back” phenomenon: the classic example is in the car, and the kids are screaming in the back seat, so mom quickly queues up some kids’ app on her iPhone and passes it back to the little brats to shut them up for a while so she doesn’t send the SUV screaming off a cliff.
Teevee prodcos have a distinct advantage in selling their apps because they have a teevee show, which has the potential to drive a lot of awareness for their apps. But they face the same problems as any other mobile developer: namely, marketing and discovery, and since games aren’t their bag (but, insultingly, they think they understand the industry just fine), the teevee companies risk throwing all their money into ventures that are often more expensive than developing Flash web games, where we know kids are spending time. Teevee is gambling on mobile, where we think kids might be spending time.
QWhat are you working on at the moment?
Untold Entertainment is a funny company, and it must be frustrating to watch us crawl along in relative obscurity. We earn money by completing fee-for-service projects (mostly Flash web games for Canadian kids’ teevee shows and broadcasters). Once the overhead is paid – office rent, phone and Internet, and my obscene cocaine habit – we may or may not have a little money left-over, depending on whether or not we agreed to develop 25 G-d levels for that bloody game. This mark-up is often eaten up by me writing vapid blog posts that i string together with stolen LOLCat pictures and dick jokes. After that, the remaining money goes towards sending me to international conferences like GDC and Casual Connect, where i don’t have any actual products to market because i was so busy writing those blog posts. And then – THEN, if i have any money left at all, it goes towards developing an original Untold Entertainment Inc. game.
(obligatory Untold Entertainment ganked LOLCat picture)
We have three big projects in development, which have been in development for a very long time. Two of them are Interrupting Cow Trivia, an online multiplayer Flash game; and Spellirium, a post-apocalyptic puzzle adventure game. Each of these projects, i’m convinced, has been cursed. Production difficulties, staff rotation, lack of cash-flow and trademark issues have held them all back. But i have established a do-or-die mandate to finish them all this year, which has nothing to do with my stalwart perseverance, and everything to do with a government-imposed deadline on each project. The Canadian government is my project manager. God save the Queen.
i continue to struggle in vain trying to drive traffic to my game portals, including ZombieGameWorld.com. i detail my futile efforts in a funny series called Pimp My Portal. In the coming months, i will unleash Formerly Earl Peterson on the world. He is a puppet, and puppets are awesome. Formerly Earl is an articulate zombie who remembers what it is to be human. He acts as a liaison between the living and the undead, and is an avid zombie rights activist. He’s going to star in some games (like the Are You a Zombie? quiz), and will perform video reviews of zombie games. He’ll also answer your burning questions about zombies, including “What do brains taste like?” and “How do zombies do it?”
We’re still steadily improving UGAGS, the Untold Graphic Adventure Game System. We built and initially deployed UGAGS on a client project called Jinx 3, and then used for the quickie weekend jam game Heads (which is available for the Blackberry Playbook), and then again for Summer in Smallywood, another client project. With Smallywood, UGAGS now supports proper branching dialogue, along with nav meshes, which give the player more freedom to move around the game screen. The next two games we’re building with UGAGS are Panda, which is about a welfare-dependent panda bear trying to raise abortion money for his artificially-inseminated ex-girlfriend, and Excuse Me For Living (But the Graveyard’s Full), which is a graphic adventure game involving zombies.
Finally, there are a few smaller projects we’re developing, including the notorious Bear in a Sombrero. This year at TOJam (the Toronto indie game jam), i’ll be working with my five-year-old daughter Cassandra on a platformer called Sissy’s Magical Ponycorn Adventure. She’s doing the pictures and the voices.
Cassie FTFW!
(Editor’s note: in case you missed it, Sissy’s Magical Ponycorn Adventure was made and released in May, and has been very succesful!)
QWhat will people learn from your book?
You will learn the very best way to part with $30-50 dollars.
If you’re new to coding, you’ll learn a lot. i tried to make the book non-coder friendly. That doesn’t mean there’s no code in it – that means that the book gently explains each line, trying not to leave anyone in the dust.
If you come from a Flash background, you’ll get a real quick crash course in Unity to whet your appetite. You’ll learn a lot of stuff that you could eventually hunt down online, but the nice thing about a book is that it lays out a comprehensive course of study for you, and saves you from doing a lot of running around.
The book teaches about starting small, about separating skin from mechanic, and about the difference between features and content (which is where we get into trouble over-scoping our projects). By the time you finish the book, you’ll have built four small games in Unity 3D, and should be well equipped to pick a new, small game idea and run with it.
And with any luck – and I’m not making this claim officially or with any air of scientific or medical authority – but I’m pretty sure that reading Unity 3D Game Development by Example will cure cancer.
Ready for another quiz? This month’s ties in with Dru Kepple’s ongoing series about Fixing Bugs in AS3. It’s an important skill to have – let’s see how you score!
Let’s Get Quizzy
Help!
Feeling stuck? Didn’t score so well? Check out our posts on debugging:
Just So You Know…
This quiz was built with the jQuizzy Quiz Engine by Siddharth, ace reviewer for Envato. jQuizzy is available for purchase over on Codecanyon
Thanks also to Orman Clark and MediaLoot for their graphical contributions to the Activetuts+ Coffee Break Quizzes.
What Would You Like To Be Tested On?
If you’ve got an idea for an Activetuts+-related quiz subject, let us know in the comments!
In this Quick Tip screencast, I’ll show you how to embed your Flash SWFs in an HTML webpage using SWFObject.
Where to Get SWFObject
The latest version of SWFObject is available on its Google Code page. Grab whichever file is marked as “Featured” on this page (at time of writing, that’s version 2.2).
Watch the Screencast
Don’t like ads? Download the screencast, or subscribe to Activetuts+ screencasts via iTunes!
The Starting HTML
For a beginner’s guide to HTML, see this tutorial.
The Final HTML
<!DOCTYPE html> <html> <head> <title>Our HTML Page</title> <script type="text/javascript" src="swfobject.js"></script> <script type="text/javascript">swfobject.embedSWF('animation.swf', 'flash', '550', '400', '9.0.0');</script> </head> <body> <div id="flash"> <p>At this moment you do not support Flash Player 9. We're sorry.</p> </div> </body> </html>Thank You
Thank you for watching, if you have any questions, feel free to comment.
In this tutorial I will introduce a class by Senocular.com that allows easy movement of game characters with minimal code.
Final Result Preview
In the SWF you’ll see a spaceship; use your Left, Right, Up, and Down arrow keys to move it.
Step 1: Explanation of KeyObject.as
When ActionScript 3.0 came out we lost the functionality of AS2′s Key.isDown() method. Senocular has coded a great little class that will let us emulate this functionality within actionscript 3 and that is what we will look at in the tutorial.
Step 2: Setting Up the Project
Go to File > New and create a new Actionscript 3.0 document, with the following properties:
Save this file as "KeyObject.fla"
Step 3: Downloading KeyObject.as
Before we can code our application we need to get the "KeyObject.as" file, so head over to Senocular.com. Under the Flash Menu, click on Actionscript. Once there you will want to drill down to "KeyObject.as" and download it. Get there by going to Actionscript 3.0 > com > senocular > utils.
You can right-click on the download link and save it as "KeyObject.as".
Once you have done this you need to remove com.senocular.utils right after the package declaration in the file, since we are not using the com.senocular class path.
Change this:
package com.senocular.utils { import flash.display.Stage; import flash.events.KeyboardEvent; //Rest of ClassTo this:
package { import flash.display.Stage; import flash.events.KeyboardEvent; //Rest of ClassStep 4: Importing the Player Graphic
In the download files there is a spaceship image called player.png. In Flash, import this to the stage, by going to File > Import > Import To Stage. Right-click on it and choose "Convert To Symbol", give it the symbol name "player", and make sure the registration point is set to the top left. Now give it the instance name of "player" as well.
Step 5: Setting Up the Main Class
Go to File > New and choose ActionScript File.
Save this as Main.as and set it as your Document Class within "KeyObject.fla".
Next add the following code to "Main.as":
package { import flash.display.Sprite import flash.events.Event; import KeyObject; public class Main extends Sprite{ private var key:KeyObject; public function Main() { addEventListener(Event.ADDED_TO_STAGE,setupKeyObject); } function setupKeyObject(e:Event){ key = new KeyObject(stage); stage.addEventListener(Event.ENTER_FRAME,movePlayer); } function movePlayer(e:Event){ if(key.isDown(key.LEFT)){ player.x -= 5; } if(key.isDown(key.RIGHT)){ player.x +=5; } if(key.isDown(key.DOWN)){ player.y +=5; } if(key.isDown(key.UP)){ player.y -=5; } if(player.y<0){ player.y =0; } if(player .y > (stage.stageHeight - player.height)){ player.y = stage.stageHeight - player.height; } if(player.x<0){ player.x = 0; } if(player.x > (stage.stageWidth - player.width)){ player.x = stage.stageWidth - player.width; } } } }Here we set up our package and import the classes we will be using. Next we set up the
keyvariable as typeKeyObject, and within ourMainconstructor we add anADDED_TO_STAGEEvent Listener. This gets called when the movie is fully loaded and the stage is ready.Inside the
setupKeyObjectfunction, we set thekeyvariable to be a new instance of theKeyObjectclass and add anENTER_FRAMEEvent Listener to the stage.Within the
movePlayerfunction we check which key is being pressed by usingkey.isDown()and move our player accordingly.Finally, we check to see whether the object has moved outside the bounds of the stage, and if it has we put it back just inside the stage.
Conclusion
Using Senocular's KeyObject class makes it dead simple to move your game characters! I hope this tutorial has helped; thanks for reading.
Picturing animation in terms of vectors is intuitive, but understanding vector mathematics is a pain. In this tutorial, I hope to ease that pain and provide a solution to animation problems using a custom written Vector2D class. We will look at some fundamental concepts of linear kinematics in the Eulerian approach: displacement, velocity and acceleration. Then, we’ll build a simple application with it.
Final Result Preview
Let's take a look at the final result we will be working towards. Click on the Flash panel below and control the arrowhead by pressing the four directional keys.
Step 1: Vector Quantity
All vector quantities have two components: magnitude and direction.
Step 2: Change in Vector Quantity
A change in vector quantities refers to one of these cases:
Step 3: Displacement as a Vector Quantity
Displacement, velocity and acceleration are vector quantities. Their definitions are as follows:
The animation below shows displacement as we are going to implement in Flash later.
Step 4: Velocity as a Vector Quantity
Velocity is illustrated by the animation below. Note velocity is constant, which means acceleration is absent in this scenario. If velocity is zero, displacement will remain constant throughout.
Step 5: Acceleration as a Vector Quantity
Acceleration is illustrated by the animation below. Note: kinematics implies constant acceleration. If acceleration changes over time, it falls under the topic of dynamics. Dynamics is the study of forces that cause of acceleration to vary over time. One such force is gravity, and I’ve written a post on animating that.
Step 6: Start Building a Projectile
Now that you have gotten a brief understanding of linear kinematics quantities and able to related them to vectors, we can start building our Projectile class. We would like the projectile be able to capture all these quantities: displacement, velocity and acceleration – so that it can be manipulated on each frame.
Below is the data we shall record in our Projectile class:
Step 7: Initialize Projectile
Upon initiation of this Projectile class, we shall initialise the mentioned variables and draw its graphical representation.
public function Projectile() { //draw graphics this.draw(); //init all vector quantities displace = new Vector2D(this.x, this.y); velo = new Vector2D(0, 0); acc = new Vector2D(0, 0); } protected function draw():void { //drawing the arrowhead var height:Number = 30; var width:Number = 60; graphics.beginFill(0x0000FF); graphics.moveTo(0, 0); graphics.lineTo(width / -3, height / -2); graphics.lineTo(width / 2, 0); graphics.lineTo(width / -3, height / 2); graphics.lineTo(0, 0); graphics.endFill(); }Step 8: Accessors of Vector Quantities
The following are accessors of our private variables –
displace,velo,acc– in the Projectile class.public function setDisp(mag:Number, angle:Number):void { displace.redefine(mag, angle); } public function getDisp():Vector2D { return displace; } public function setVelo(mag:Number, angle:Number):void { velo.redefine(mag, angle); } public function getVelo():Vector2D { return velo; } public function setAcc(mag:Number, angle:Number):void { acc.redefine(mag, angle); } public function getAcc():Vector2D { return acc }Step 9: Updaters of Vector Quantities
Upon refreshing every frame, we need to update velocity (using acceleration) and update displacement (using the said velocity). This can be achieved using the following functions. For a thorough explanation on Vector addition, do visit this great post from Daniel Sidhon.
public function applyVelo():void { this.displace = this.displace.add(velo); } public function applyAcc():void { this.velo = this.velo.add(acc); } //update sprite's position by displacement. public function animate():void { this.x = this.displace.x; this.y = this.displace.y; }Step 10: Updater for Sprite'S Orientation
We will also need to update the orientation of the Sprite. This can be achieved through the
rotationproperty of Sprite.public function orient():void { this.rotation = Math2.degreeOf(velo.getAngle()); }I have also implemented a
Math2static class, in which I’ve written a function to easily convert back and forth from the angle’s units of degrees and radians.public static function radianOf (deg:Number):Number { return deg/180*Math.PI; } public static function degreeOf (rad:Number):Number { return rad/Math.PI*180; }Step 11: The Main Class
Now that we have established our Projectile and Math2 class, we can start to code our Main class. We will need a Vector2D class as well although thorough explanation is not included due to the aforementioned article on Vectors by Daniel Sidhon. I assume readers understand the Vector2D class after reading it. However, if clarifications are needed, do prompt me with your queries.
First of all, we need to know private variables of this class.
Step 12: Initializing Main
Upon initialization of Main, function
initwill be launched. This function will create a new Projectile and set its initial velocity. Then, listeners to events will be assigned.private function init(e:Event = null):void { removeEventListener(Event.ADDED_TO_STAGE, init); // entry point b1 = new Projectile(); stage.addChild(b1); //setting initial velocity b1.setVelo(5, Math2.radianOf(30)); //setting event listeners b1.addEventListener(Event.ENTER_FRAME, proj_enterFrame); stage.addEventListener(KeyboardEvent.KEY_DOWN, handle_keyDown); stage.addEventListener(KeyboardEvent.KEY_UP, handle_keyUp); }Step 13: Keyboard Event Listeners
I have defined user control as keypresses of Up, Left, Down and Left arrow keys. Upon pressing and releasing those keys, flag variables of Main (Step 11) will be turned true and false. Based on these flags, the Vector quantities will be manipulated on every frame. Note as well I have divided controls into horizontal and vertical axis manipulators.
private function handle_keyDown(e:KeyboardEvent):void { if (e.keyCode == Keyboard.UP) UP = true; else if (e.keyCode == Keyboard.DOWN) DOWN = true; if (e.keyCode == Keyboard.LEFT) LEFT = true; else if (e.keyCode == Keyboard.RIGHT) RIGHT = true; } private function handle_keyUp(e:KeyboardEvent):void { if (e.keyCode == Keyboard.UP) UP = false; else if (e.keyCode == Keyboard.DOWN) DOWN = false; if (e.keyCode == Keyboard.LEFT) LEFT = false; else if (e.keyCode == Keyboard.RIGHT) RIGHT = false; }Step 14: EnterFrame Event Listeners
Upon refresh of each frame the following code will be executed. It is long, but don't worry; just read on.
private function proj_enterFrame(e:Event):void { //define acceleration var accMag:Number = 0.1; if (UP) { b1.setAcc(accMag, Math2.radianOf(-90)); b1.applyAcc(); } else if (DOWN) { b1.setAcc(accMag, Math2.radianOf(90)); b1.applyAcc(); } if (LEFT) { b1.setAcc(accMag, Math2.radianOf(180)); b1.applyAcc(); } else if (RIGHT) { b1.setAcc(accMag, Math2.radianOf(0)); b1.applyAcc(); } //decelerate as nothng is pressed to simulate friction. if (UP + DOWN + LEFT + RIGHT == 0) { var currentVeloMag:Number = b1.getVelo().getMagnitude(); var currentVeloAng:Number = b1.getVelo().getAngle(); if(currentVeloMag > 1){ b1.setAcc(accMag * -1, currentVeloAng); b1.applyAcc(); } } b1.applyVelo(); //restricting sprite to borders of the stage b1.getDisp().x = Math2.implementBound(0, stage.stageWidth, b1.getDisp().x); b1.getDisp().y = Math2.implementBound(0, stage.stageHeight, b1.getDisp().y); b1.animate(); b1.orient(); }Step 15: Update Motion
Updating the motion should be done in the following order:
I’ve highlighted the codes for easy identification of these steps.
private function proj_enterFrame(e:Event):void { //define acceleration var accMag:Number = 0.1; if (UP) { b1.setAcc(accMag, Math2.radianOf(-90)); b1.applyAcc(); } else if (DOWN) { b1.setAcc(accMag, Math2.radianOf(90)); b1.applyAcc(); } if (LEFT) { b1.setAcc(accMag, Math2.radianOf(180)); b1.applyAcc(); } else if (RIGHT) { b1.setAcc(accMag, Math2.radianOf(0)); b1.applyAcc(); } //decelerate as nothing is pressed to simulate friction. if (UP + DOWN + LEFT + RIGHT == 0) { var currentVeloMag:Number = b1.getVelo().getMagnitude(); var currentVeloAng:Number = b1.getVelo().getAngle(); if(currentVeloMag > 1){ b1.setAcc(accMag * -1, currentVeloAng); b1.applyAcc(); } } b1.applyVelo(); //restricting sprite to borders of the stage b1.getDisp().x = Math2.implementBound(0, stage.stageWidth, b1.getDisp().x); b1.getDisp().y = Math2.implementBound(0, stage.stageHeight, b1.getDisp().y); b1.animate(); b1.orient(); }Step 16: Slowing Down Motion
You may find that there are other functions slotted in between these highlighted codes. What are they? One is to apply another vector to slow down our projectile as the user does not press on any keys. This is applied before we add velocity to our displacement.
//decelerate as nothng is pressed to simulate friction. if (UP + DOWN + LEFT + RIGHT == 0) { var currentVeloMag:Number = b1.getVelo().getMagnitude(); var currentVeloAng:Number = b1.getVelo().getAngle(); if(currentVeloMag > 1){ b1.setAcc(accMag * -1, currentVeloAng); b1.applyAcc(); } }Step 17: Stay Inside
The next one is to restrict our projectile to always stay on the stage, otherwise it will fly out of the screen. Again,
implementBoundis a function I’ve included in the Math2 static class. Given an upper bound, lower bound and a random value,implementBoundwill return a value that is within the boundaries.After applying this constraints onto our displacement (and only after that), we update the Sprite's position with this displacment value.
Step 18: Orient Sprite
Before we leave this sprite as it is, we should orient it so that it always points in the position it's heading using function
orient.Step 19: Get Set and Go!
Now everything is set to go. As you launch this piece by pressing on Ctrl + Enter, you will see an arrow that gradually slows down as it heads diagonally down the screen. Press on the four directional keys to move the arrow about. Don't worry about losing your arrow; it'll stay inside your view.
Conclusion
This article should get you familiar with using vectors to animate motion. Once you have understood kinematics, do proceed to read up on my post on dynamics. Let me know how it goes. Terima Kasih.
Fans of the Activetuts+ Facebook page can access a new bonus tutorial, this month covering Flash’s underrated Graphic symbol!
Introduction
I first began using Flash since version MX. And through all the enhancements and added features in every release, one thing that has remained constant is the graphic symbol. But what has also remained constant, surprisingly enough, is how many Flash users don’t know what the graphic symbol actually does. Somewhere along the line, this symbol has received a bad rap as being totally useless.
I can’t tell you how many articles and tutorials I have come across on how to use symbols in Flash that immediately dismiss the graphic symbol as having no practical purpose, relegating it as just a step above grouping items. This article will attempt to dispel this myth by showing that the graphic symbol actually has some pretty cool and convenient features and knowing how and when to utilize them is a nice tool to have when you’re creating animations in Flash.
So if you ever wondered what exactly the purpose of the graphic symbol is and why the heck Adobe continues to keep it in Flash, this article is for you.
Download This Fan Bonus Now!
All you have to do is Like us…
Not On Facebook?
Don’t worry, the tutorial will be posted on Activetuts+ in a month’s time!
In this tutorial I will walk you through some of the uses of the Twitter REST API, and how you can use it to create a game that not only displays real-time Tweets, but uses them to generate objects the player can interact with.
Before you read this tutorial, I recommend that you read the AS3 101 sessions by Dru Kepple, Understanding JSON, Understanding the Game Loop – Basix, Greensock Tweening Platform sessions and maybe some design and animation tutorials.
Final Result Preview
Let’s take a look at the final result we will be working towards:
Step 1: Let’s Get the Assets Ready
Before we get started we will need to set up some assets; we are building a game, so you might want to make it look pretty
. We will need buttons for New Game, Instructions, Menu and Tweet (for the player’s score); we will also need a background, some birds, the golden egg and the cracked egg. Besides all that we will need some misc assets such as the loading message, the Tweet message, a “follow me” button, the instructions window and the bar where you will display your Tweets.
Step 2: Let’s Create Some Classes
For this tutorial we will be working with some classes, if you are not familiar with classes you should take a look at the How to Use a Document Class in Flash tutorial.
We will need a
Mainclass which will be our main brain, that’s the one you will link to your FLA file. Besides that one, we will need aGameclass which will handle the game logic of the game and do most of the work. Since games have events that you need to listen to we will create a class namedGameEventwhich we will use to communicate with our main class. We will also need anEggclass which will hold the data of the Tweet, the kind of egg that it will be, and some other things that we will need later on. And we will need aTweetHolderclass which we will link to a movie clip on the library so we can autosize and add some properties to the Tweet that we will show. All of those classes will go to ourcomfolder which needs to be right next to our FLA file.Main.as
package com { import flash.display.*; // Extends MovieClip cause this will be linked our FLA file public class Main extends MovieClip { public function Main() { // Here we will add our code
}
}
}
Game.as
package com { import flash.display.*; public class Game extends Sprite { public function Game() { // Here we will add the logics for our game } } }GameEvent.as
package com { import flash.events.*; // This will be our custom event so it will extend Event public class GameEvent extends Event { // Here we will store the parameters that we will receive, so that we can use them later on, that's why it's public public var parameters:String; // Here we add the params since we will use it later on to share information between classes public function GameEvent(type:String = "Default", params:String=null, bubbles:Boolean = false, cancelable:Boolean = false) { // We need to initialize our super so we get all its properties super(type, bubbles, cancelable); } } }Egg.as
package com { import flash.display.*; // This will be our custom event so it will extend Event public class Egg extends Event { public function Egg() { // Here we will add the logics for our eggs } } }TweetHolder.as
package com { import flash.display.*; // This will be our custom event so it will extend Event public class TweetHolder extends Event { public function TweetHolder() { // Here we will add the logics for our TweetHolder } } }Step 3: Arrange the Menu
It’s time to start the fun! Place all our menu assets, place your new game button on stage, create the title for your game, add the instructions as well as the instructions button. Once you have all that on stage assign an instance name to each of them since we will need to refer to them in our Main class.
Step 4: Prepare TweenLite
For this tutorial we will be using TweenLite from GreenSock which you can download HERE (click AS3, download the ZIP file containing it, then unzip it and copy the file named greensock.swc to the folder where you have your FLA file).
When you have the SWC file right next to your FLA we will need to link it to our FLA so click Edit on the Properties panel of your file:
Then click Settings right next to ActionScript 3.0, then the +, and enter “./greensock.swc”
After that we are ready to start working in our Main class.
Step 5: Showing the Instructions
To show the instructions of the game we will need to have already created a movie clip which will contain the instructions with the instance name of
instructions_contentand the button that will summon it with the instance name ofinstructions_btn. Besides that we will take the time to make everything act as a button.We will use just one function to make the instructions show and hide so we will need a variable that will tell us whether the instructions are already being displayed; we will call that variable
showingInstructions.package com { // import what we need to animate the objects import com.greensock.TweenLite; import com.greensock.easing.*; import flash.display.*; import flash.events.*; public class Main extends MovieClip { // variable that we will use to know if the instructions are being displayed // set to false since the first position of the instructions will be out of frame private var showingInstructions:Boolean = false; public function Main() { this.addEventListener(Event.ADDED, init); } private function init(e:Event):void { this.removeEventListener(Event.ADDED, init); // set objects to act like button and get the pointer hand instructions_btn.buttonMode = true; instructions_content.close_btn.buttonMode = true; // we will call the function instructionsHandler when the instructions_btn.addEventListener(MouseEvent.CLICK, instructionsHandler); instructions_content.close_btn.addEventListener(MouseEvent.CLICK, instructionsHandler); } private function instructionsHandler(e:MouseEvent):void { // we ask if the instructions are being showed if(showingInstructions) { // if they are we will want to send them out of frame and tell the game that is not being showed TweenLite.to(instructions_content, .4,{y:600, ease:Cubic.easeIn}); showingInstructions = false; } else { // if it's not then we will animate it into stage and tell that it's being showed TweenLite.to(instructions_content, .4,{y:364, ease:Cubic.easeOut}); showingInstructions = true; } } } }Step 6: Follow Me Button
Since you are a developer and you might want users to get in touch with you a “follow me” button sounds like a good idea – so let’s make your button send the users to your twitter profile; we will do this by like so:
private function init(e:Event):void { this.removeEventListener(Event.ADDED, init); // set objects to act like button and get the pointer hand instructions_btn.buttonMode = true; followMe_btn.buttonMode = true; instructions_content.close_btn.buttonMode = true; // we will call the function instructionsHandler when the instructions_btn.addEventListener(MouseEvent.CLICK, instructionsHandler); instructions_content.close_btn.addEventListener(MouseEvent.CLICK, instructionsHandler); followMe_btn.addEventListener(MouseEvent.CLICK, followMeHandler); }The we will need to create our handler which will be called
followMeHandleras we wrote when we declared the listener:// this function will receive a MouseEvent object which we will call "e" private function followMeHandler(e:MouseEvent):void { // You will need to change jscamposcr for your username, twitter will take care of the rest navigateToURL(new URLRequest("http://www.twitter.com/jscamposcr"
, "_blank");
}
Step 7: Prepare the Stage for a New Game
When you have your menu, the instructions and the “follow me” button working it’s time to get your hands dirty and start the hard work – but first we will need to prepare the area with the right objects to start the game. This we will do it by moving out all the objects of the menu, including the instructions.
First let’s add a click listener to the
New Gamebutton that we have in our stage, and then let’s create a function that will clean up the stage.private function init(e:Event):void { this.removeEventListener(Event.ADDED, init); // set up listeners, positions, visibility, etc newGame_btn.buttonMode = true; instructions_btn.buttonMode = true; followMe_btn.buttonMode = true; menu_btn.buttonMode = true; instructions_content.close_btn.buttonMode = true; newGame_btn.addEventListener(MouseEvent.CLICK, startGame); instructions_btn.addEventListener(MouseEvent.CLICK, instructionsHandler); instructions_content.close_btn.addEventListener(MouseEvent.CLICK, instructionsHandler); followMe_btn.addEventListener(MouseEvent.CLICK, followMeHandler); }Next we will take all our objects out of the stage in the function
startGameprivate function startGame(e:* = null):void { TweenLite.to(newGame_btn, .3, {x:-200, ease:Quad.easeIn}); TweenLite.to(instructions_btn, .3, {x:500, ease:Quad.easeIn}); TweenLite.to(followMe_btn, .3, {y:650, ease:Quad.easeIn}); // we will need to hide the instructions if they are being desplayed if(showingInstructions) { TweenLite.to(instructions_content, .3,{y:600, ease:Quad.easeIn}); showingInstructions = false; } }Now we need to add a menu button and a score text field, we will call the menu button
menu_btnand the score will be calledscore_textand it will be a dynamic text field. Don’t forget to embed the font; for this I will embed Verdana Bold by clicking on Embed and adding the characters we will need.Then add both objects at the top of the stage but out of it since we wont need them in our first frame, then we will add two lines that will animate both of them on our
startGamefunction:private function startGame(e:* = null):void { TweenLite.to(newGame_btn, .3, {x:-200, ease:Quad.easeIn}); TweenLite.to(instructions_btn, .3, {x:500, ease:Quad.easeIn}); TweenLite.to(followMe_btn, .3, {y:650, ease:Quad.easeIn}); TweenLite.to(menu_btn, .3,{y:0, delay:.2, ease:Quad.easeOut}); TweenLite.to(score_text, .3,{y:10, delay:.2, ease:Quad.easeOut}); if(showingInstructions) { TweenLite.to(instructions_content, .3,{y:600, ease:Quad.easeIn}); showingInstructions = false; } }Now let’s give the user the chance to go back to the menu by adding a function to the menu button that we just added; we will call this one
goToMenuand we will call it when the user clicks on themenu_btn. For that, we will add an event listener in our start game function:private function startGame(e:* = null):void { TweenLite.to(newGame_btn, .3, {x:-200, ease:Quad.easeIn}); TweenLite.to(instructions_btn, .3, {x:500, ease:Quad.easeIn}); TweenLite.to(followMe_btn, .3, {y:650, ease:Quad.easeIn}); menu_btn.buttonMode = true; menu_btn.addEventListener(MouseEvent.CLICK, goToMenu); TweenLite.to(menu_btn, .3,{y:0, delay:.2, ease:Quad.easeOut}); TweenLite.to(score_text, .3,{y:10, delay:.2, ease:Quad.easeOut}); if(showingInstructions) { TweenLite.to(instructions_content, .3,{y:600, ease:Quad.easeIn}); showingInstructions = false; } }Now the function that will make things come back:
private function goToMenu(e:MouseEvent):void { // we disable the event listener menu_btn.removeEventListener(MouseEvent.CLICK, goToMenu); // we move back all our assets TweenLite.to(title, .3, {y:91, delay:.2, ease:Quad.easeOut}); TweenLite.to(newGame_btn, .3, {x:162, delay:.2, ease:Quad.easeOut}); TweenLite.to(instructions_btn, .3, {x:162, delay:.2, ease:Quad.easeOut}); TweenLite.to(followMe_btn, .3, {y:500, delay:.2, ease:Quad.easeOut}); TweenLite.to(menu_btn, .3,{y:-35, ease:Quad.easeIn}); TweenLite.to(score_text, .3,{y:-30, ease:Quad.easeIn}); }So far the project should look like this:
We didn’t actually remove the title; we will leave it there while we show a message that says that the Tweets are being loaded.
Step 8: Create the Needed Events
We are almost ready to start our game engine, but as part of it we need to be able to communicate with our
Mainclass. We will do this with events, so let’s take ourGameEventand add the type of events that we want to listen to:GAME_ENDED.SCORE_CHANGE.TWEETS_READY.TWEETS_ERROR.So with all that sorted now we need to edit our GameEvent class
package com { import flash.events.*; public class GameEvent extends Event { public static const GAME_ENDED:String = "Game Ended"; public static const SCORE_CHANGE:String = "Score Change"; public static const TWEETS_READY:String = "Tweets Ready"; public static const TWEETS_ERROR:String = "Tweets Error"; public var parameters:String; public function GameEvent(type:String = "Default", params:String=null, bubbles:Boolean = false, cancelable:Boolean = false) { super(type, bubbles, cancelable); parameters = params; } } }With our menu and events created we are finally ready to start our game engine
Step 9: Setting Up a Game Instance
To get our game working properly we need to instantiate it, add the event listeners, and destroy them when no longer needed; for that we will add some code to the
startGamefunction, thegoToMenufunction, and a new function we will created calledendGame(this one will kill our game instance and remove the event listeners).First let’s edit our “start game” function; we will add the next lines to the end of the end of the
startGamefunction.Since we are using a var called game we will need to declare it at the begining of our class as a private var:
In our goToMenu function we will add a call to our endGame function which we will create and add the following code:
private function endGame(e:GameEvent = null):void { // This will reset the score in case the user starts a new game score_text.text = "Score: 0"; // This will kill our game with the function killGame that we will create inside our game class game.killGame(); // We will remove all our listeners from our game instance game.removeEventListener(GameEvent.GAME_ENDED, endGame); game.removeEventListener(GameEvent.SCORE_CHANGE, changeScore); game.removeEventListener(GameEvent.TWEETS_READY, addGame); game.removeEventListener(GameEvent.TWEETS_ERROR, errorHandler); // If the game has been added then we will fade it out and then we will remove it from stage and memory if (game.addedToStage) { TweenLite.to(game, .4, {alpha:0, onComplete:(function (){game.parent.removeChild(game); game = null})}); } // If not then we will just kill it else { game = null; } }With this we will be good to go and work on our Game class
Step 10: Loading Tweets
First we will need to declare some variables that we will use later on:
Then we need to tell the user what is going on so we will add a movie clip to the stage and name it
loadingTweets; this says that the Tweets are being loaded. We will fade it in within ourstartGamefrom our Main classprivate function startGame(e:MouseEvent):void { loadingTweets.x = 130; loadingTweets.y = 230; loadingTweets.alpha = 0; TweenLite.to(newGame_btn, .3, {x:-200, ease:Quad.easeIn}); TweenLite.to(instructions_btn, .3, {x:500, ease:Quad.easeIn}); TweenLite.to(followMe_btn, .3, {y:650, ease:Quad.easeIn}); TweenLite.to(loadingTweets, .3, {alpha:1, delay: .2}); menu_btn.buttonMode = true; menu_btn.addEventListener(MouseEvent.CLICK, goToMenu); TweenLite.to(menu_btn, .3,{y:0, delay:.2, ease:Quad.easeOut}); TweenLite.to(score_text, .3,{y:10, delay:.2, ease:Quad.easeOut}); if (showingInstructions) { TweenLite.to(instructions_content, .3,{y:600, ease:Quad.easeIn}); showingInstructions = false; } game = new Game(); game.addEventListener(GameEvent.GAME_ENDED, endGame); game.addEventListener(GameEvent.SCORE_CHANGE, changeScore); game.addEventListener(GameEvent.TWEETS_READY, addGame); game.addEventListener(GameEvent.TWEETS_ERROR, errorHandler); }And we will fade it out in our
endGamefunction:private function endGame(e:GameEvent = null):void { score_text.text = "Score: 0"; game.killGame(); game.removeEventListener(GameEvent.GAME_ENDED, endGame); game.removeEventListener(GameEvent.SCORE_CHANGE, changeScore); game.removeEventListener(GameEvent.TWEETS_READY, addGame); game.removeEventListener(GameEvent.TWEETS_ERROR, errorHandler); if (game.addedToStage) { TweenLite.to(game, .4, {alpha:0, onComplete:(function (){game.parent.removeChild(game); game = null})}); } else { game = null; loadingTweets.alpha = 0; loadingTweets.x = 800; loadingTweets.y = 2300; } }It’s now time to start the fun and create the actual game! For that we will need Tweets. The main idea of this tutorial is to load Tweets, and we will use the search API from Twitter to do this.
I recommend you to go and check it out, but for this game we will just use a few of the possible options; we will make two requests and then add the game to the stage, that’s why we didnt add it when it was created, we will first load the Tweets for the golden eggs in our
Gamefunction and we will add an event listener when it’s added, where we start the game:public function Game() { // tweets loader var loader:URLLoader = new URLLoader(); loader.addEventListener(Event.COMPLETE, loadCrackedEggs); loader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler); // construct the URL q = what we are looking for rpp = the amount of tweets that we want // If you are going to use complex string in your URL make sure to use the function encodeURI("Your String") and then add it to the URL var url:String = "http://search.twitter.com/search.json?lang=en&q=golden%20eggs&rpp=50"; loader.load(new URLRequest(url)); this.addEventListener(Event.ADDED, init); }For this request we used just
langwhich is the language for the Tweets that we want to receive given in a ISO 639-1 code;qwhich is the main word (can be a phrase as well – just make sure to encode the URL) we are looking for; andrppwhich is the number of Tweets that we want to receive. If you want to search for more than one thing you can useOR, it still works just fine but isn’t in the new documentation page so I can’t tell if or they are going to stop supporting it (the same forNOTwhich becomes handy when you are getting too many spam Tweets).For even more information about this go to the “using search” page from Twitter.
When our Tweets for golden eggs are loaded we will load the Tweets for “cracked eggs” in the function
loadCrackedEggsand then we will dispatch an event saying that everything is ready for the game to start:private function loadCrackedEggs(e:Event):void { // We will declare this variable as a private variable so we can store the data here goldenEggsData = JSON.decode(e.currentTarget.data); // tweets loader var loader:URLLoader = new URLLoader(); loader.addEventListener(Event.COMPLETE, startGame); loader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler); // construct the URL ors = what we are looking for rpp = the amount of tweets that we want var url:String = "http://search.twitter.com/search.json?q=cracked%20eggs&lang=en&rpp=50"; loader.load(new URLRequest(url)); }Once we load the JSON we will convert it into a object variable that we will declare at the begining of our code – to understand a JSON I recommend you read Understanding JSON – to convert it to an Object we will use the as3corelib from Mike Chambers and its function
JSON.decode, which will return an object with all the contents of the JSON file.Step 11: Starting Our Game
To start we will create two arrays which will contain the eggs information and for that we will need our class Egg which we created in Step 2; in that class we will store some data and add the graphics for our eggs. To add the graphics we will need to export the graphics for the eggs from the library to use them in our code like this:
Then we will need to work in our class:
package com { import flash.display.*; import flash.events.*; public class Egg extends Sprite { // Here we will store what type of egg this one is so the right graphic is used public var type:String; // Stores the string text public var tweet:String; // Stores the username of the autor of the tweet public var user:String; // Stores the URL of the avatar of the user public var userImg:String; public function Egg() { this.addEventListener(Event.ADDED, init); } private function init(e:Event):void { this.removeEventListener(Event.ADDED, init); // Here we add the graphic corresponding the the type of egg // We use the MovieClips that we exported in our library if(type == "Golden Egg") { var goldenEgg:GoldenEgg = new GoldenEgg(); addChild(goldenEgg); } else { var crackedEgg:CrackedEgg = new CrackedEgg(); addChild(crackedEgg); } } } }This is all that we will do to our Egg class.
Now we will work in our
startGamefunction. Here we will go through the object that contains the information of our Tweets, then for each Tweet that we loaded we will create an egg, give it the required information, and then add it to an array so we can use it later on.private function startGame(e:Event):void { crackedEggsData = JSON.decode(e.currentTarget.data); for (var i:int = 0; i < goldenEggsData.results.length; i++) { var goldenEgg:Egg = new Egg(); goldenEgg.tweet = goldenEggsData.results[i].text; goldenEgg.user = goldenEggsData.results[i].from_user; goldenEgg.userImg = goldenEggsData.results[i].profile_image_url; goldenEgg.type = "Golden Egg"; goldenEggsArray.push(goldenEgg); } for (var x:int = 0; x < crackedEggsData.results.length; x++) { var crackedEgg:Egg = new Egg(); crackedEgg.tweet = crackedEggsData.results[x].text; crackedEgg.user = crackedEggsData.results[x].from_user; crackedEgg.userImg = crackedEggsData.results[x].profile_image_url; crackedEgg.type = "Cracked Egg"; crackedEggsArray.push(crackedEgg); } dispatchEvent(new GameEvent(GameEvent.TWEETS_READY)); }Step 12: Error Handling
It can happen that the Tweets can’t be loaded so it’s a good practice to do something about and not just let it happen, that’s why every time we loaded Tweets we added an IOErrorEvent listener, set to call a function named
errorHandler. This function will just dispatch an error event so our Main class can handle it:private function errorHandler(e:IOErrorEvent):void { dispatchEvent(new GameEvent(GameEvent.TWEETS_ERROR)); }Then in our FLA we will add a new frame to our loadingTweets movie clip, there you can add your message for the user to know that something went wrong and move to a different frame so that it doesn’t show what it isn’t supposed to. We will display that in the
errorHandlerclass that we set up as listener for TWEET_ERRORs.private function errorHandler(e:GameEvent):void { loadingTweets.gotoAndStop(2); }Step 13: Initializing Our Game
Once the Tweets are ready we can initialize it; for this we will create our
addGamefunction in our Main class, and in this function we will add our game to the stage so theinitfunction on our Game class gets triggered, we will animate out our title, and we will move away the animation we had for the user to know that the Tweets were being loaded.private function addGame(e:Event):void { addChild(game); TweenLite.to(title, .3, {y:-200, ease:Quad.easeIn}); loadingTweets.alpha = 0; loadingTweets.x = 800; loadingTweets.y = 2300; }Then in our
initfunction for the Game class we will create our first bird, and add a nest (which the player will use to catch the eggs) and a black bar (which will hold the Tweets that the user has catched) – so let’s create those movie clips.Our nest needs to have graphics and a invisible movie clip that we will use to test collisions with the eggs; the black bar needs just a basic title. We will need to export both of them for AS use.
private function init(e:Event):void { this.removeEventListener(Event.ADDED, init); // We set our varible to true so our Main class knows if it has been added or not addedToStage = true; // Set up the position, alpha and animation for our nest object nest.x = 10; nest.y = 497; nest.alpha = 0; TweenLite.to(nest, .5,{alpha:1}); addChild(nest); // Add, position and anomation for the Tweets bar tweetsBar.x = 350; tweetsBar.alpha = 0; TweenLite.to(tweetsBar, .6,{alpha:1}); addChild(tweetsBar); // This is our main loop which will take care of our game and tell whats going on stage.addEventListener(Event.ENTER_FRAME, updateGame); // We will move our nest via keys so we will need to listen to keyboards events stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler); stage.addEventListener(KeyboardEvent.KEY_UP, keyUpHandler); // We will create our first bird which is the begining of the actual game createBird(); // To make this game a bit interesting we will change the dificulty of our game as the user plays it TweenLite.delayedCall(10, changeDifficulty); }With this ready we will now need to work on our birds, our eggs, our Tweets and the main loop for this game.
Step 14: Creating Our Birds
Our birds will drop our eggs so they are kind of important! So let’s create them and animate them. In our
createBirdfunction we will create a bird, animate it, and decide when the next bird will be created; besides that we will set up the speed of the bird (to give some variety in the difficulty level they will move faster as the game goes on).private function createBird():void { // Create a new Bird instance from out library var bird:Bird = new Bird(); // This is the time that will pass before the egg is released var time:Number = (Math.random()*1.3)+.5; // We will position out bird out of stage and in random Y position so they look a bit more real bird.x = -100; bird.y = (Math.random()*60) + 50; // Animates the bird and destroys it after it's done animating TweenLite.to(bird, 5, {x: 600, onComplete:killObject, onCompleteParams:[bird]}); addChild(bird); if (! ended) { // We need to tell our bird to release our eggs after a certian time which will be random TweenLite.delayedCall(time, releaseEgg, [bird]); // If the game has no ended we need to create a new bird and depending on the difficulty it will come faster TweenLite.delayedCall(Math.random()*difficulty, createBird); } else { // Since the game has ended we wont need any updates stage.removeEventListener(Event.ENTER_FRAME, updateGame); // If the game has ended we won't want any eggs to be released so we call any calls to that function TweenLite.killTweensOf(releaseEgg); // Besides not letting any eggs come we will delete all the eggs if (eggs.length > 0) { for (var i:int = 0; i < eggs.length; i) { killObject(eggs[i]); eggs.shift(); } } // We will animate out our nest since no more eggs will be catched TweenLite.to(nest, .6,{y:700}); // We will show the score and give the user the option to Tweet it showTweetScore(); } }Step 15: Releasing Eggs
Now that we have birds flying around we need them to drop eggs, so in our
releaseEggfunction we will do that:private function releaseEgg(b:Bird):void { var r:Number = Math.random(); var egg:Egg; // Here we choose if our egg will be a golden eggs or a cracked egg, giving a bit more chance to get a golden egg if (r > 0.45) { // If the user gets a golden egg then we need to assign one egg form our array of golden eggs and refresh the count egg = goldenEggsArray[goldenEggsCount]; goldenEggsCount++; } else { // If the user gets a cracked egg then we need to assign one egg form our array of cracked eggs and refresh the count egg = crackedEggsArray[crackedEggsCount]; crackedEggsCount++; } // Then we will assign the position of the bird that is going to release it egg.x = b.x; egg.y = b.y; // Then we add it to stage and push it to our eggs array so it gets updated addChild(egg); eggs.push(egg); }Step 16: Change Difficulty
Over time the difficulty will change; we already made a call for that to happen in our
initfunction, so now we will make that happen:private function changeDifficulty():void { // The first difficulty is set to 3 so if this function is called we will change it so more birds come together and speed up the eggs if (difficulty == 3) { difficulty = 2; speed = 5; // We call this function again so the difficulty chages again TweenLite.delayedCall(10, changeDifficulty); return; } if (difficulty == 2) { difficulty = 1.5; speed = 7; TweenLite.delayedCall(5, changeDifficulty); return; } if (difficulty == 1.5) { difficulty = 1; speed = 8; TweenLite.delayedCall(5, changeDifficulty); return; } // If the difficulty is set to 1 it means that this function has been called some times and that the 30 seconds of this game are gone so it's time to end this game if (difficulty == 1) { ended = true; return; } }Step 17: Getting Things Ready to Move
To move our nest we will use our keyboard and our update loop, so we need to tell our game which key is being pressed, for that we already created a couple of variables but now we will add the functionality that will make our nest move. We already set up a couple of event listeners – one for when a key is down and another for when it is released – in our
initfunction, so now let’s handle those calls. We will need to use keycodes for this, for more information about those you can visit the Quick Tip about the usage of keycodesprivate function keyDownHandler(e:KeyboardEvent):void { // When the left or right key is holded we need to set it's value to true // 37 = left // 39 = right if (e.keyCode == 37) { leftKey = true; } if (e.keyCode == 39) { rightKey = true; } } private function keyUpHandler(e:KeyboardEvent):void { // When the left or right key is released we need to set it's value to false // 37 = left // 39 = right if (e.keyCode == 37) { leftKey = false; } if (e.keyCode == 39) { rightKey = false; } }Step 18: Showing Score
When the time is gone we will give our user the option to see and share their score, for that we will create a movie clip that states the score and gives the option to share it:
Dont forget to assign an instance name to your dynamic text field and to embed the font that you want to use. When your graphic is ready we are good to go and code it; for this we will create a function named
showTweetScorein which we will create a new instance of that movie clip, position it, add a message with the score, and give the option to Tweet the score.private function showTweetScore():void { var tweetMessage:TweetMessage = new TweetMessage(); tweetMessage.x = 10; tweetMessage.y = 270; tweetMessage.message_text.text = "You just scored "+score.toString()+" point on Golden Eggs!"; tweetMessage.embedFonts = true; addChild(tweetMessage); TweenLite.from(tweetMessage, .6, {alpha:0}); tweetMessage.tweet_btn.buttonMode = true; tweetMessage.tweet_btn.addEventListener(MouseEvent.CLICK, shareScore); }Step 19: Share Score
Once the user clicks on the
tweet_btnof our message he or shee will be redirected to a page on Twitter with a prewritten message so they can share their score. To do this we will use another call form the Twitter API – the Tweet button API – for more information about it please visit Twitter’s page.For this tutorial we will use just three variables: the text; the URL that we want to share; and “via”, which is kind of the bridge that we are using, in this case Activetuts+.
The URL that we need to send our user to is
https://twitter.com/share, there the user will be able to log in and Tweet about our game. To that URL we must append variables like this:private function shareScore(e:MouseEvent):void { // We need to encode the string var url:String = encodeURI("https://twitter.com/share?text=I just scored "+score.toString()+" points in Golden Eggs!, try to beat me!&url=http://www.jsCampos.com/GoldenEggs&via=envatoactive"); // We use _blank so the user can come back and keep playing the game without having to load again the game navigateToURL(new URLRequest(url), "_blank"); }There are some other variables that you can add to your URL, such as recommended accounts and language, but we won’t use those because those are for the Tweet button so are useless for this tutorial. But I recommend you to go check them out, as they might come in handy some day.
Step 20: Destroy Objects
Some times when you create too many variables and objects your application might become slow, so we make use of the garbage collector and tell it what to pick up and what to leave for us to use. Once the eggs get out of stage it means that we don’t need them any more (the same is true for our birds) so it’s a good practice to get rid of them. We will do this with a function called
killObjectwhich we have called already a few times and we will call later on. This function will clear that object and get rid of it; it will take a sprite since we are just going to kill display objects.private function killObject(ob:Sprite):void { // We will remove them from stage and then set them to null so the garbage collector takes care of it ob.parent.removeChild(ob); ob = null; }Besides those objects we might need to get rid of our game instance; in those cases we will kill all the listeners so we dont waste memory on anything.
public function killGame():void { if(addedToStage) { stage.removeEventListener(Event.ENTER_FRAME, updateGame); stage.removeEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler); stage.removeEventListener(KeyboardEvent.KEY_UP, keyUpHandler); } }Step 21: Our Game Cycle
Our cycle will be one of the most important functions; this will take care of everything that is on stage and keep track of everything. The first thing this function will do is move our nest when the user is pressing the right or left key – keeping in mind that there should be limits for it, so the nest doesn’t go out of stage.
private function updateGame(e:Event):void { if (nest.x < 250 && rightKey) { nest.x += 5; } if (nest.x > 9 && leftKey) { nest.x -= 5; } }Then we need to take care of our eggs, check whether any have been caught or have moved out of the stage. For that we will need to add the following code to our function:
// First we need to check if there are any eggs on stage if (eggs.length > 0) { for (var i:int = 0; i < eggs.length; i++) { // if there are we need to move them so they fall eggs[i].y += speed; // Then we need to check if any of those has been catched by using hitTestObject with the invisible object inside our nest if (eggs[i].hitTestObject(nest.hit)) { // If one egg has been catched then we need to add or substract points if (eggs[i].type == "Golden Egg") { score += 100; } else { score -= 80; } // Then we need to let our Main class know so the score text is changed // We send the score as a parameter of the GameEvent var gameEvent:GameEvent = new GameEvent(GameEvent.SCORE_CHANGE,score.toString()); dispatchEvent(gameEvent); // Then we will show out Tweet in out bar using a class that we will create // We will assign the values to this Tweet from out egg, that's why that class has those properties var tweet:TweetHolder = new TweetHolder(); tweet.user_text.text = eggs[i].user; tweet.tweet_text.text = eggs[i].tweet; tweet.userImg = eggs[i].userImg; tweet.x = 5; tweetsBar.addChild(tweet); // If there are too many Tweets being showed we will need to hide some if (tweetsOnScreen.length > 0) { // first we need to make room for out new tweet for (var a:int = 0; a < tweetsOnScreen.length; a++) { tweetsOnScreen[a].y = tweetsOnScreen[a].y - tweet.height; } // if our first tweet is going to high then we need to fade it out if (tweetsOnScreen[0].y < 100) { // We fade it and them we remove it from stage and from our array of Tweets TweenLite.to(tweetsOnScreen[0], .5,{alpha:0, y:"-100", onComplete:tweetsOnScreen[0].parent.removeChild, onCompleteParams:[tweetsOnScreen[0]]}); tweetsOnScreen.shift(); } // If the new tweet is too long the next one might be too high as well so we need to check and apply the same process if (tweetsOnScreen[0].y < 100) { tweetsOnScreen[0].parent.removeChild(tweetsOnScreen[0]); tweetsOnScreen.shift(); } } // After everything is ready we need to add our new Tweet to our array of tweets tweetsOnScreen.push(tweet); tweet.alpha = 1; tweet.y = 600 - tweet.height - 10; // after we are done it's time to kill that egg killObject(eggs[i]); eggs.splice(i, 1); } // If the Tweet wasn't catched and is out of frame then we should destroy it as well else if (eggs[i].y > 600) { killObject(eggs[i]); eggs.splice(i, 1); } } }Step 22: Creating Tweet Objects
For our Tweet objects we will first need an object from our library, which we will export, and to which we will assign a new class to handle the data that is pushed. This movie clip will have a backup image in case the user image can’t be loaded, a space for the name of the user who Tweeted it, and a space for the Tweet content.
When you create your graphics don’t forget to embed the font that you are using.
The class that we are going to create will fill the data, resize the text fields, load the publisher’s avatar and give the user the posibility to click on the tweet and go to the publisher’s profile:
package com { import flash.display.*; import flash.events.*; import flash.net.*; public class TweetHolder extends MovieClip { // This will hold the URL of the image so it can be loaded public var userImg:String; public function TweetHolder() { this.addEventListener(Event.ADDED, init); } private function init(e:Event):void { // As we did in our Game class we first added the data and then added this to the stage this.removeEventListener(Event.ADDED, init); // When it's added it resizes the text fields so no space is wasted tweet_text.autoSize = "left"; hit.height = tweet_text.height + tweet_text.y; // Then we load the image and call a function if the image is loaded or is we get an error var loader:Loader = new Loader(); loader.contentLoaderInfo.addEventListener(Event.COMPLETE, addImage); loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onError); loader.load(new URLRequest(userImg)); // We finally make this Tweet clickable and add a function to handle the clicks this.buttonMode = true; this.addEventListener(MouseEvent.CLICK, clickHandler); } private function clickHandler(e:MouseEvent):void { // To go to the user's profile we just need to add the username to Twitter's URL navigateToURL(new URLRequest("http://www.twitter.com/"+user_text.text), "_blank"); } private function addImage(e:Event):void { // When the image is added we need to make it look smooth Bitmap(e.currentTarget.content).smoothing = true; // Resize it e.currentTarget.content.height = 40; e.currentTarget.content.width = 40; // And add it to our Tweet holder addChild(e.currentTarget.content); } private function onError(e:Event):void { // If there is an error we just need to catch it // Since we already have a backup image we don't need to do anything if the image is not loaded trace("Image couldnt be loaded"); } } }When we are done with this class everything should be ready to go.
Conclusion
I hope you guys find this tutorial helpful and find great ways to apply what has been taught! For another example of the usage of this you can visit my site home page at JsCampos.com, or check out some other nice games that use Twitter, such as Tweet Land and Tweet Hunt.
Thanks for your time and I hope you liked it, any feedback is well received.
In this Quick Tip, we’ll take on run-time Error 2044, the un-handled IO error. It’s actually very simple, but it plagues even experienced developers, so we’ll go in-depth and turn everyone here into IO error ninjas.
Step 1: Setting up the Problem
Let’s start by setting up some code in a Flash file that produces error 2044. Create a new AS3 Flash file, and enter this code into the Script panel:
var loader:Loader = new Loader(); loader.load(new URLRequest("some-non-existant.url"));Go ahead and run the SWF, and you should see this error:
You will see the same error, with a slight variation if we just change
LoadertoURLLoader, as in below:var loader:URLLoader = new URLLoader(); loader.load(new URLRequest("some-non-existant.url"));You should see something like this, only with the file path reflecting your environment:
Error #2044: Unhandled ioError:. text=Error #2032: Stream Error. URL: file:////Volumes/Mac%20Pro/Users/dru/Library/Caches/TemporaryItems/non-existant.url at Untitled_fla::MainTimeline/frame1()Step 2: The Accused
As you might be able to surmise from the fact that Error 2044 crops up with
LoaderandURLLoaderin use, this error has something to do with the loading of external files.In fact, the error has something to do with the failure to load an external file. As the fake URL in my code snippets would suggest, the file we are trying to load is having a problem of some sort. Most likely it’s a case of the file being unreachable; this might simply be a mis-spelled URL, or a URL being created dynamically resulting in a bad location, or because the server or network is down at the moment.
However, Error 2044 is not accusing you loading a bad file. That’s going to happen. We can’t control the network, so a load failure is bound to happen at some point. Error 2044 is accusing you of not being prepared for when that happens.
Step 3: Good Boy Scouts
Both
LoaderandURLLoaderare event dispatchers, as you should know from working with them. You need to utilize theEvent.COMPLETEevent in order to know when a load is ready for you to work with it. If you’re reading this, though, you might not realize that these loading classes also dispatch other events, notably theIOErrorEvent.IO_ERRORevent.When a
LoaderorURLLoaderencounters a failure, such as described in the previous step, it will dispatch anIOErrorEvent.IO_ERRORevent. This is a specialized event for cases such as this. It carries atextproperty that describes the nature of the error, as seen in the errors we created in the first step; both code snippets produced Error 2044, but the text of each was different (even though it was semantically the same).Unlike most events, though, when
IOErrorEvents are dispatched, the dispatcher checks for the existence of at least one event listener. If it doesn’t find any, it throws the un-handled IO error.So the solution is simple: simply add a listener for the
IOErrorEvent.IO_ERRORevent to your loader(s). Even if the listener doesn’t do anything, it will at least suppress the Error 2044, by virtue of merely existing.var loader:URLLoader = new URLLoader; loader.load(new URLRequest("some-non-existant.url")); loader.addEventListener(IOErrorEvent.IO_ERROR, onError); function onError(e:IOErrorEvent):void { // Do nothing }Remember that you add events to the
contentLoaderInfoproperty ofLoaderobjects, not to theLoaderdirectly:var loader:Loader = new Loader(); loader.load(new URLRequest("some-non-existant.url")); loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onError); function onError(e:IOErrorEvent):void { // Do nothing }However, you should be better prepared than this, like a boy scout; you would ideally determine what an appropriate action to take would be, then start that action in the event handler function. You might decide to load an “image not found” image or a default XML file instead. You might present an alert to the user notifying them that a required resource could not be loaded, and should try again later. Perhaps you also disable parts of your SWF because the required data couldn’t be loaded. You might even fire off a message to a server-side log with details, so that you can look into the situation.
That Is All
As I mentioned, this one is pretty easy, really. It’s just a matter of getting in the habit of adding that event handler in the first place, so that you never see Error 2044 again. It won’t prevent resource loading from failing, but it can let you degrade gracefully and recover from the failure as best as you are able.
Thanks for reading. I’ll see you again shortly in another Debug Quick Tip.
In this tutorial (the first of a series), you’ll learn the basics of HTML5 game development with JavaScript and the
<canvas>element. You don’t need to have any programming experience, or even any HTML experience (apart from this one article). Let’s get started!Introduction
It would be difficult to have missed the “HTML5 vs Flash” articles that have sprung up all over the web, particularly since Steve Jobs’s Thoughts on Flash last year, and Microsoft’s announcement this week that Windows 8′s web browser won’t support Flash on tablets by default. I’m not going to get into that debate here; whatever your opinion, there’s no harm in learning HTML5. Even if you know Flash, it doesn’t hurt to have another tool in your kit.
This tutorial does not require you know know Flash, or to have had any experience of programming before. In fact, everything that you need to know before you get started is explained in this single article: Get Up to Speed With HTML. That’s it! If you can follow that, you can follow this.
I’m basing this series on my Flash tutorial series, which in turn was based on an even older Flash tutorial by a guy named FrozenHaddock (to whom I am very grateful for letting me use his ideas). This isn’t a direct port of either tutorial, however; I’ll be completely rewriting the code and the explanations to suit HTML5.
A couple of notes:
In this first part of the tutorial, we’ll just be setting everything up and putting in some very basic game mechanics. Future parts will add multiple spawning enemies, high scores, menu screens, multiple levels, and all that stuff.
Enough talk – let’s get started!
Setting Up
First thing to do is create a .html file. You can use a basic text editor for this, or spend a few hundred dollars on software specifically designed for HTML development; personally, I’d stick with free software for now. Here are three recommendations: TextEdit (for Mac), Notepad++ (for Windows), and Sublime Text 2 (for Windows, OS X, and Linux). Take your pick.
Create a new file, and enter the following:
If you don’t understand what any of that does, read my basic guide to HTML.
Create a new folder on your hard drive called AvoiderGame, and save this HTML file inside it as game.html. If you load it right now, it’ll just show a blank white page (as you know), so put a paragraph of text in there just to make sure everything’s okay. I’ll add a link to this tutorial, but you could enter anything you like – your name and website, perhaps?
JavaScript
Okay, now, you will not be surprised to hear that we will soon be writing some JavaScript – remember, JavaScript lets web pages do things, and that’s exactly what we need for making games. We’ll put all our JavaScript in an external file, in order to keep things tidy, and put this file in a separate folder, to keep things tidier still.
So, create a new folder, called js inside your AvoiderGame folder. Then, create a new, empty text file, and save it as main.js inside this new AvoiderGame/js/ folder.
Alter your HTML to refer to this JS file:
Note that I haven’t written
src="http://active.tutsplus.com/...whatever.../js/main.js", orsrc="C:\AvoiderGame\js\main.js"; this way, we’re telling the HTML page, “look for a js folder in the same directory as you, and then use the main.js file that’s inside it.” It’s called a relative path.If you want to test that this is working, put
alert("Working!");in your JS file, then load the HTML page. If you get a dialog box, everything’s okay.CSS
While we’re at it, let’s link an external CSS file as well; we can use it to make the text look nicer, and we might need to use CSS in the game later.
Create a new folder inside AvoiderGame called css, and then create a new, empty text file called style.css inside that. Modify your HTML like so:
I’m going to modify my CSS file to match some styles we often use on demo pages here at Activetuts+; feel free to copy it, come up with your own, or leave yours blank:
body { background: #ffffff; text-align: center; padding: 20px; color: #575757; font: 14px/21px Arial,Helvetica,sans-serif; } a { color: #B93F14; } a:hover { text-decoration: none; } ol { width: 600px; text-align: left; margin: 15px auto }This tutorial isn’t about CSS, so if you don’t understand that, don’t worry about it. (If you’re curious, you can look up the meaning of those CSS attributes on W3Schools.com.)
Okay, that’s the dull setup out of the way. You can see how the page looks by clicking here, and you can download the entire source so far in a ZIP file here. Let’s create our avatar!
Get Your Head in the Game
We need an image that will represent our player’s character in this game. Use whatever you like – a photograph of your face, your Twitter avatar, a picture you’ve drawn – but make sure it has a transparent background, that it’s roughly circular, and that it’s about 30x30px.
The original tutorial on which this one is based used a skull. I’m not sure why, but I suspect it was an attempt to subvert games’ typical anti-skeleton stance; after all, under our skin, doesn’t every one of us have a skull?
I’m not one to break with tradition, so I’ll use a skull here too. You can download mine by right-clicking it, if you don’t want to make your own:
And before you ask: yes, I am available for commission.
Whatever you choose, save it as avatar.png inside a new folder, called img inside AvoiderGame. Your folder structure now looks like this:
So how do we get this into our game? If you’ve been paying attention, you’ll probably suggest this:
And, it’s true, that would put the avatar in our page! But it’s not what we’re going to use.
Canvas
An
imgelement shows a single image, loaded from a PNG or JPG (or whatever) file. Thecanvastag, new to HTML5, can generate an image dynamically, made up of other, smaller images, text, primitive shapes, and so much more, if you desire. Its contents can be modified at any point, so you can give the illusion of motion – or of interaction, if you make the contents change according to what the user does.We create a canvas in the same way that we create any other HTML element:
…though, if you look at this, you won’t be able to see anything there. It’s invisible, so the only effect it has is to move the text down a little.
With CSS, we can give it an outline so that we can tell it apart from the background. Add this to your CSS:
canvas { border: 1px solid black; }Check it out. It’s kinda small, though; let’s make it 400px by 300px (old-school TV dimensions):
That’s better. Now, I said we could add images to the canvas dynamically, so let’s do that next.
Functions
Remember in the HTML guide I showed you how to make things happen when you clicked HTML elements? Here’s a quick recap:
<!DOCTYPE html> <html> <head> <title>HTML5 Avoider Game</title> <script src="js/main.js"></script> <link rel="stylesheet" href="css/style.css" /> </head> <body> <canvas onclick="alert('Clicked the canvas');" width="400" height="300"></canvas> <p>From <a href="http://active.tutsplus.com/tutorials/html5/learn-html5-with-this-simple-avoider-game-tutorial" rel="external">Learn HTML5 With This Simple Avoider Game Tutorial</a>.</p> </body> </html>If you click the canvas, you’ll get a dialog box message. This is because
alert()is a JavaScript function: it’s a shortcut to a few lines of code. We can write our own functions in our JS file. Open main.js and enter the following:function alertSeveralTimes() { alert("Hello!"); alert("Look, we can run several messages in a row."); alert("Annoyed yet?"); }(Delete the original
alert("Working!");if you haven’t already.)Do you see how this works? We have created a new
functioncalledalertSeveralTimes(), whose contents are inside the curly braces ({and}). When we tell the browser to run thisalertSeveralTimes()function, it will run each of the alerts in turn, one after the other.Let’s try it out:
Try it! We’ve effectively bundled up several
alert()functions into one bigger function calledalertSeveralTimes(), and told it to run whenever the canvas is clicked.You might be wondering why the
alert("Working!")ran as soon as we opened the page, but thealertSeveralTimes()didn’t, even though they were both in the same place (at the top of main.js). It’s because of that magic keywordfunction; when the browser sees this, it doesn’t think, “aha, this is some code I must run immediately!”, it thinks, “aha, this is some code I must bundle up into a new function, which I can run later!”Anyway. Now let’s make our function do something to the canvas. Making it load an image is a little tricky, so we’ll start with something a bit simpler: changing its size.
Modifying the Canvas
One of the most amazing features of JavaScript is its ability to change the HTML of the page. Check this out; modify your JS file so that it contains this:
function changeCanvasSize() { gameCanvas.width = 600; gameCanvas.height = 800; }You can probably guess what this is doing: it takes the canvas element, and modifies its
widthandheightattributes (we don’t need to use quotes around numbers in JavaScript, unlike in HTML attributes).Except… how does it know that
gameCanvasrefers to the canvas that we have in our page?Well, it doesn’t… yet. We have to make it realise that.
First, we have to give the canvas element an
id(short for “identification”) attribute; this is just a name that we use so that we can refer to it in JavaScript later:While we’re at it, let’s make the
onclickattribute point to our newchangeCanvasSize()function:This still isn’t quite enough. We have to let the JavaScript know that it’s dealing with an element from the HTML page (or ‘HTML document’, as it’s more correctly known):
function changeCanvasSize() { document.getElementById("gameCanvas").width = 600; document.getElementById("gameCanvas").height = 800; }Now, I know, this doesn’t seem entirely logical. Why is
gameCanvassuddenly in quotes? Why do we usedocument.getElementById("gameCanvas")instead of just, say,getDocumentElement("gameCanvas"), or evendocument.gameCanvas? I promise, this will all become clear during the tutorial series, but for now, just go with it, please.Test out your new code. The canvas resizes itself as soon as you click on it. Awesome!
Now, I should warn you: programmers are lazy. We hate writing the same code over and over again, and if there’s any way we can reduce the typing required, we’ll take it. So, let me introduce you to a nice shorthand way of referring to the canvas:
function changeCanvasSize() { var gameCanvas = document.getElementById("gameCanvas"); gameCanvas.width = 600; gameCanvas.height = 800; }See how that works? Just as the
functionkeyword says, “hey, wrap all this code up under the namechangeCanvasSize(), please”, thevarkeyword says, “hey, use the wordgameCanvasto refer to the HTML element with an ID of “gameCanvas”, please”. Then (in lines 3 and 4, above), we can use this new shorthandgameCanvasin please of the longerdocument.getElementById("gameCanvas")– because they refer to the same thing.That’s important: we haven’t created a new canvas; we’ve just made
gameCanvasrefer to the existing canvas element.However, it is possible to use
varto create something new…Click to Skull
Like I said, we’re moving towards adding an image to the (currently empty) canvas. But before we can do that, we have to load the image. And before we can do that, we have to have something to load the image into.
Modify your JS like so:
function changeCanvasSize() { var gameCanvas = document.getElementById("gameCanvas"); var avatarImage = new Image(); gameCanvas.width = 600; gameCanvas.height = 800; }(I’ve added a blank line to clearly separate the
varstatements from the rest.)Here, we’re using the
varkeyword again – but this time, it says something subtly different: “hey, create a newImageobject and use the wordavatarImageto refer to it from now on, please.” TheImageobject is basically like aimgelement; the crucial difference here is, it’s not in the HTML. We’ve created this brand new element, but it’s nowhere in the HTML; it’s just floating around in the JavaScript aether. I find that a bit weird.Just like an
imgelement in the page, thisImageis pretty much useless without setting itssrc, so do that next:function changeCanvasSize() { var gameCanvas = document.getElementById("gameCanvas"); var avatarImage = new Image(); gameCanvas.width = 600; gameCanvas.height = 800; avatarImage.src = "img/avatar.png"; }(Once again I’m using a blank line to keep bits of code that do different things separated from each other (like paragraphs in text), and once again I’m using a relative path to refer to a file’s location.)
So this is now loading the image, but you’ll have to take my word for it at the moment, since it’s still out there in the aether where we can’t see it. However, we can check its other attributes:
function changeCanvasSize() { var gameCanvas = document.getElementById("gameCanvas"); var avatarImage = new Image(); gameCanvas.width = 600; gameCanvas.height = 800; avatarImage.src = "img/avatar.png"; alert(avatarImage.width); }We’re telling it to show us a dialog box containing the value of the
widthattribute of our image. Check it on your code and see what you get; I get 29, which is exactly right.With just one more line of code, we can draw the avatar on the canvas:
function changeCanvasSize() { var gameCanvas = document.getElementById("gameCanvas"); var avatarImage = new Image(); gameCanvas.width = 600; gameCanvas.height = 800; avatarImage.src = "img/avatar.png"; gameCanvas.getContext("2d").drawImage(avatarImage, 0, 0); }Let’s break this down:
gameCanvas.getContext("2d"): We don’t actually draw directly on the canvas, we draw onto what’s called a “drawing context”; this lets us determine whether we want to draw in 2D or 3D. Okay, there’s no 3D context at the moment, but this is letting us plan for the future.drawImage(): Pretty straightforward. This is a function that lets us draw an image onto the context of a canvas.avatarImage: This is the image object we’ve got floating around in the aether, remember?0, 0: These are the coordinates at which we want to draw the image. In school, you’re taught that (0, 0) is the bottom-left of the page; on a computer, it’s the top-left (the x-axis points to the right, and the y-axis points downwards).Take a look. It works! (If it doesn’t, remember that you should be viewing this in Chrome; I don’t guarantee that this will work in any other browser.)
Multiple Skulls
The
drawImage()function works like a potato stamp:Photo by jimmiehomeschoolmom
It just takes the contents of the image object and clones them onto the canvas; of course, we’re dealing with pixels, not paint, but you get the idea.
This means we can add multiple skulls to the canvas, like so:
function changeCanvasSize() { var gameCanvas = document.getElementById("gameCanvas"); var avatarImage = new Image(); gameCanvas.width = 600; gameCanvas.height = 800; avatarImage.src = "img/avatar.png"; gameCanvas.getContext("2d").drawImage(avatarImage, 0, 0); gameCanvas.getContext("2d").drawImage(avatarImage, 100, 50); gameCanvas.getContext("2d").drawImage(avatarImage, 200, 130); gameCanvas.getContext("2d").drawImage(avatarImage, 300, 270); }Check it out, skull party. We can also make the skull appear in a random place each time:
function changeCanvasSize() { var gameCanvas = document.getElementById("gameCanvas"); var avatarImage = new Image(); gameCanvas.width = 600; gameCanvas.height = 800; avatarImage.src = "img/avatar.png"; gameCanvas.getContext("2d").drawImage(avatarImage, Math.random() * 100, Math.random() * 100); }Math.random()give you a random number between 0 and 1, soMath.random() * 100gives you a random number between 0 and 100; this means that the coordinates of the new skull are anywhere between (0, 0) and (100, 100). Take a look!But hold on – why is there only one skull at a time now? Is it something to do with it being a new function? Does the canvas get cleared every time you run a function?
No. The canvas is cleared every time you modify its height or width, even if you don’t change either. So, if we change our JS like so:
function changeCanvasSize() { var gameCanvas = document.getElementById("gameCanvas"); var avatarImage = new Image(); avatarImage.src = "img/avatar.png"; gameCanvas.getContext("2d").drawImage(avatarImage, Math.random() * 100, Math.random() * 100); }…then we can keep adding new skulls.
In fact, let’s change the name of the function to
drawAvatar(), and tidy things up a bit:function drawAvatar() { var gameCanvas = document.getElementById("gameCanvas"); var avatarImage = new Image(); avatarImage.src = "img/avatar.png"; gameCanvas.getContext("2d").drawImage(avatarImage, Math.random() * 100, Math.random() * 100); }Don’t forget to change the
onclickattribute ofcanvasin your HTML:Okay, now let’s get that avatar moving.
Adding Interactivity
I want to make the avatar follow the mouse. We can use the same principle that animators do: if we keep erasing the contents of the canvas, and then re-drawing the avatar at a different position, the avatar will appear to move. So all we have to do is keep redrawing the avatar at the mouse’s coordinates, and we’re set!
How do we do that, though?
A Grand Event
Judging by what we’ve done so far, you might guess that we’d add a
onmousemoveevent attribute to the canvas (which would be triggered every time the user moved their mouse), then make it rundrawAvatar(), but specifically at the mouse’s current coordinates. This is inspired, but unfortunately doesn’t really work, simply because it doesn’t offer an easy way to obtain the mouse’s coordinates.However, it is very close to what we want to do! Take a look at this:
function drawAvatar() { var gameCanvas = document.getElementById("gameCanvas"); var avatarImage = new Image(); avatarImage.src = "img/avatar.png"; gameCanvas.getContext("2d").drawImage(avatarImage, Math.random() * 100, Math.random() * 100); gameCanvas.addEventListener("mousemove", redrawAvatar); }This does roughly the same thing as the above suggestion; the
redrawAvatar()function (which we haven’t written yet) will be called whenever the mouse moves over the canvas. But there’s one big difference.Notice how we write
redrawAvatar, rather thanredrawAvatar()in the code above, whereas in our HTML page, we putdrawAvatar()– with “parentheses” (round brackets) – in theonclickevent attribute of our canvas. The full reason for that is a little complicated to go into now (though you’ll understand by the end of the series), but it has one very important upshot: it allows us to obtain the mouse’s coordinates.When the mouse moves, the browser creates a new object – just like when we created a new Image in our JavaScript earlier. This object has certain attributes that have something to do with the thing that triggered its creation; in this case, because the mouse moved, it contains the coordinates of the mouse. Brilliant!
So how do we access it? Well, this new object (which is called a
MouseEvent, for reasons that you might be able to guess) gets passed to theredrawAvatar()function. Er, but we haven’t written that yet, so let’s do that now. Add this code to your JS file:function drawAvatar() { var gameCanvas = document.getElementById("gameCanvas"); var avatarImage = new Image(); avatarImage.src = "img/avatar.png"; gameCanvas.getContext("2d").drawImage(avatarImage, Math.random() * 100, Math.random() * 100); gameCanvas.addEventListener("mousemove", redrawAvatar); } function redrawAvatar(mouseEvent) { }Aha – this time, the way we define the function is a little different: we’ve added the word
mouseEventin-between those parentheses. This is because we are expecting the browser to pass aMouseEventobject to our new function, just like when we passed the coordinates to thedrawImage()function.Since we’ve given it a name, we can access the attributes of this new object:
function drawAvatar() { var gameCanvas = document.getElementById("gameCanvas"); var avatarImage = new Image(); avatarImage.src = "img/avatar.png"; gameCanvas.getContext("2d").drawImage(avatarImage, Math.random() * 100, Math.random() * 100); gameCanvas.addEventListener("mousemove", redrawAvatar); } function redrawAvatar(mouseEvent) { alert(mouseEvent.x); }Test this out; you’ll have to click the canvas before anything will happen, because it’s inside that function that we make the browser start paying attention to mouse movements.
You’ll notice that the dialog box only appears when the mouse moves over the canvas element. You might also notice something odd about the number: it’s too big! I’m getting numbers of over 900, even though the width of the canvas is only 400.
This is because
mouseEvent.xgives the mouse’s x-position from the edge of the page, rather than the edge of the canvas. We can usemouseEvent.offsetXto get the mouse’s x-position from the edge of the canvas:function drawAvatar() { var gameCanvas = document.getElementById("gameCanvas"); var avatarImage = new Image(); avatarImage.src = "img/avatar.png"; gameCanvas.getContext("2d").drawImage(avatarImage, Math.random() * 100, Math.random() * 100); gameCanvas.addEventListener("mousemove", redrawAvatar); } function redrawAvatar(mouseEvent) { alert(mouseEvent.offsetX); }Much better!
So, to recap:
addEventListener()makes the browser listen for certain events – such as the movement of the mouse – and then run a function when this event is “heard”. The browser creates a new object (like aMouseEvent), and passes it to that function.It’s a little hard to wrap your head around, but don’t worry; we’ll be using it a lot, so you’ll get the hang of it!
Move Your Head
We’ve nearly got movement. In fact, I recommend you have a go at making the avatar follow the mouse on your own before reading further. You’ll probably come very close!
There’s one big thing that’ll trip you up, though: the word
var– which, you’ll remember, you can use to set a shorthand – only “lasts” within the function in which it was defined.This means that if you try to do, say:
function drawAvatar() { var gameCanvas = document.getElementById("gameCanvas"); var avatarImage = new Image(); avatarImage.src = "img/avatar.png"; gameCanvas.getContext("2d").drawImage(avatarImage, Math.random() * 100, Math.random() * 100); gameCanvas.addEventListener("mousemove", redrawAvatar); } function redrawAvatar(mouseEvent) { gameCanvas.width = 400; alert(mouseEvent.offsetX); }…it won’t work, because
gameCanvasmeans nothing outside ofdrawAvatar()!So, if you didn’t get it the first time, have another go.
My code is here if you want to check yours:
function drawAvatar() { var gameCanvas = document.getElementById("gameCanvas"); var avatarImage = new Image(); avatarImage.src = "img/avatar.png"; gameCanvas.getContext("2d").drawImage(avatarImage, Math.random() * 100, Math.random() * 100); gameCanvas.addEventListener("mousemove", redrawAvatar); } function redrawAvatar(mouseEvent) { var gameCanvas = document.getElementById("gameCanvas"); var avatarImage = new Image(); avatarImage.src = "img/avatar.png"; gameCanvas.getContext("2d").drawImage(avatarImage, mouseEvent.offsetX, mouseEvent.offsetY); }Tada! Oh, wait, dang, I forgot to erase the canvas by changing its width or height. Cool effect, but let’s try that again:
function drawAvatar() { var gameCanvas = document.getElementById("gameCanvas"); var avatarImage = new Image(); avatarImage.src = "img/avatar.png"; gameCanvas.getContext("2d").drawImage(avatarImage, Math.random() * 100, Math.random() * 100); gameCanvas.addEventListener("mousemove", redrawAvatar); } function redrawAvatar(mouseEvent) { var gameCanvas = document.getElementById("gameCanvas"); var avatarImage = new Image(); avatarImage.src = "img/avatar.png"; gameCanvas.width = 400; gameCanvas.getContext("2d").drawImage(avatarImage, mouseEvent.offsetX, mouseEvent.offsetY); }Try it out now. Success!
Wait, What Was That For?
When you take another look at your code in a few days’ time, you’re likely to have forgotten what a lot of it is for. In particular, I suspect you’ll forget why you need to resize the canvas.
Fortunately, there’s a way to remind yourself: comments.
Look at this:
function drawAvatar() { var gameCanvas = document.getElementById("gameCanvas"); var avatarImage = new Image(); avatarImage.src = "img/avatar.png"; gameCanvas.getContext("2d").drawImage(avatarImage, Math.random() * 100, Math.random() * 100); gameCanvas.addEventListener("mousemove", redrawAvatar); } function redrawAvatar(mouseEvent) { var gameCanvas = document.getElementById("gameCanvas"); var avatarImage = new Image(); avatarImage.src = "img/avatar.png"; gameCanvas.width = 400; //this erases the contents of the canvas gameCanvas.getContext("2d").drawImage(avatarImage, mouseEvent.offsetX, mouseEvent.offsetY); }The browser ignores everything on the line after a
//. This means you can write whatever you like there, and it won’t be run as code. It’s called an inline comment and is very important. To help get yourself into the habit of commenting your code, go through it now and write comments after each line that you think you might have trouble understanding after a few days’ break.Commenting may seem like a waste of time. I think a lot of new programmers assume that when they first start out; it only takes one bad experience with trying to update your own, uncommented code (or worse: someone else’s!) to convince you of its worth, though
Hide the Mouse
At the moment, you’ve got a big stupid mouse cursor hovering over your avatar:
We can fix this with a little CSS. Modify your stylesheet like so:
canvas { border: 1px solid black; cursor: none; }In most browsers, this will make your cursor disappear when it’s on top of the canvas… but not in Chrome.
Chrome doesn’t support
cursor: none;, but it does allow you to replace the cursor with a PNG file of your choice. So, you can create a PNG that’s completely transparent, put it in your img folder, and use that, like so:canvas { border: 1px solid black; cursor: url("../img/transparentcursor.png"), none; }(I’ve had to put ../ at the start of the URL because, in CSS, relative paths are relative to the folder of the CSS file, not the HTML file, and “..” says, “the folder above this one”. Also, I’ve put
, noneafter this, because it means that if any browsers don’t support using PNGs for cursors, they’ll use thenoneattribute instead. Can you see why I wanted to avoid focusing on cross-browser compatibility?)Unfortunately, this doesn’t work either, because if you use a completely transparent PNG, Chrome just shows a solid black rectangle instead. Thanks, Chrome.
So, instead, I’ve made a 1x1px PNG that’s almost transparent (the single pixel is white, with an opacity of 1%). You can download it here. Copy it to your img folder, then modify your CSS stylesheet:
canvas { border: 1px solid black; cursor: url("../img/almosttransparent.png"), none; }Test it out. It does work, after all that effort.
Make an Enemy
We’ve accomplished a lot so far, but our avoider game still doesn’t have anything to avoid! The last thing we’ll do, in this part of the series, is create an enemy.
We need an image to represent this. Draw whatever you like, but make sure that it’s roughly circular, and about 30x30px. I’m going to take my cue from FrozenHaddock again. He picked a smiley face for his game’s enemy; I’m not sure why, but I suspect it was a comment on the over-pervasiveness of smileys in modern conversation; a yearning for the days of emotions over emoticons, where poets would pour their hearts into a single sentence of text, rather than simply typing semicolon close-parenthesis ell oh ell. Or maybe it’s just because smiley faces are easier to draw. Regardless, here’s mine:
Call yours enemy.png and put it in the img directory.
You can probably figure out how to draw this enemy (unmoving) on the canvas – if so, give it a shot! Once again, I recommend you do this before reading on.
Here’s my solution:
function drawAvatar() { var gameCanvas = document.getElementById("gameCanvas"); var avatarImage = new Image(); avatarImage.src = "img/avatar.png"; gameCanvas.getContext("2d").drawImage(avatarImage, Math.random() * 100, Math.random() * 100); gameCanvas.addEventListener("mousemove", redrawAvatar); } function redrawAvatar(mouseEvent) { var gameCanvas = document.getElementById("gameCanvas"); var avatarImage = new Image(); var enemyImage = new Image(); avatarImage.src = "img/avatar.png"; enemyImage.src = "img/enemy.png"; gameCanvas.width = 400; //this erases the contents of the canvas gameCanvas.getContext("2d").drawImage(avatarImage, mouseEvent.offsetX, mouseEvent.offsetY); gameCanvas.getContext("2d").drawImage(enemyImage, 250, 150); }Your method might be different to mine – that’s okay, as long as it works! But for consistency, please copy my method into your code.
Note that I keep the
varstatements at the top of their respective functions. This isn’t strictly necessary, but it keeps things tidy, so I recommend it.Besides that, nothing here should surprise you. There’s no particular significance to the coordinates (250, 150) that I’ve chosen.
Try it out!
Putting the “Avoid” in “Avoider Game”
We’re not going to worry about making the enemy move in this part of the tutorial; that topic deserves more space than I can afford it here. But we will check for a collision between the avatar and the enemy!
First, we’ll do something simpler: we’ll consider a certain area of the screen “off-limits” and pop up a dialog box if the avatar moves into that area.
Modify your
redrawAvatar()function like so:function redrawAvatar(mouseEvent) { var gameCanvas = document.getElementById("gameCanvas"); var avatarImage = new Image(); var enemyImage = new Image(); avatarImage.src = "img/avatar.png"; enemyImage.src = "img/enemy.png"; gameCanvas.width = 400; //this erases the contents of the canvas gameCanvas.getContext("2d").drawImage(avatarImage, mouseEvent.offsetX, mouseEvent.offsetY); gameCanvas.getContext("2d").drawImage(enemyImage, 250, 150); if (mouseEvent.offsetX < 100) { alert("Too far left!"); } }Try it out. If your avatar goes too far to the left side of the screen, the dialog box appears.
Let’s take a closer look at the code:
if (condition) { outcome; }The
ifstatement is a way of checking whether something has happened. It’s made up of two parts: a condition, inside the parentheses, and a result, inside the curly braces. If the condition is true, then the outcome is called.In our case, the condition is
mouseEvent.offsetX < 100. The<symbol means "less than", and remember thatmouseEvent.offsetXis the cursor's horizontal distance from the left edge of the canvas. So, this is checking whether the cursor is within 100 pixels of the left edge of the canvas. If it is, then......the outcome is run. And in our case, the outcome is
alert("Too far left!");, the dialog box function we've used a number of times.Make sense? Okay, good, because I'm going to make it more complicated:
function redrawAvatar(mouseEvent) { var gameCanvas = document.getElementById("gameCanvas"); var avatarImage = new Image(); var enemyImage = new Image(); avatarImage.src = "img/avatar.png"; enemyImage.src = "img/enemy.png"; gameCanvas.width = 400; //this erases the contents of the canvas gameCanvas.getContext("2d").drawImage(avatarImage, mouseEvent.offsetX, mouseEvent.offsetY); gameCanvas.getContext("2d").drawImage(enemyImage, 250, 150); if (mouseEvent.offsetX < 100 || mouseEvent.offsetY < 100) { alert("Too far left, or too far up!"); } }In this code, we've introduced a new operator:
||.||means (and is pronounced) "or". Theifstatement therefore reads:"If the mouse is within 100 pixels of the left edge of the canvas, OR the mouse is within 100 pixels of the top edge of the canvas, show the dialog box."
Try this out, and you'll see that you can't get anywhere near the top or the left of the canvas.
How about this:
function redrawAvatar(mouseEvent) { var gameCanvas = document.getElementById("gameCanvas"); var avatarImage = new Image(); var enemyImage = new Image(); avatarImage.src = "img/avatar.png"; enemyImage.src = "img/enemy.png"; gameCanvas.width = 400; //this erases the contents of the canvas gameCanvas.getContext("2d").drawImage(avatarImage, mouseEvent.offsetX, mouseEvent.offsetY); gameCanvas.getContext("2d").drawImage(enemyImage, 250, 150); if (mouseEvent.offsetX > 150 && mouseEvent.offsetX < 250) { alert("Stay out of the middle!"); } }The
&&operator means (and is pronounced) "and", and>means "greater than". So, ourifstatement now reads:"If the mouse is more than 150 pixels away from the left edge of the canvas AND the mouse is less than 250 pixels away from the left edge of the canvas, show the dialog box."
Test it out, and you'll see that we effectively have an invisible "stripe" down the middle of the canvas where we can't put our mouse.
We're not restricted to just using two clauses at once; check this out:
function redrawAvatar(mouseEvent) { var gameCanvas = document.getElementById("gameCanvas"); var avatarImage = new Image(); var enemyImage = new Image(); avatarImage.src = "img/avatar.png"; enemyImage.src = "img/enemy.png"; gameCanvas.width = 400; //this erases the contents of the canvas gameCanvas.getContext("2d").drawImage(avatarImage, mouseEvent.offsetX, mouseEvent.offsetY); gameCanvas.getContext("2d").drawImage(enemyImage, 250, 150); if (mouseEvent.offsetX > 150 && mouseEvent.offsetX < 250 && mouseEvent.offsetY > 100 && mouseEvent.offsetY < 200) { alert("Stay out of the center!"); } }We've now effectively drawn a 100x100px box, in the centre of the canvas, where we can't put our mouse.
Can you see where we're going with this?
Rather than a 100x100px box in the middle of the canvas, we should use a 30x30px box positioned where our enemy is.
This is the last thing we're going to do in this part of the tutorial, so once again I recommend you have a go yourself. It's pretty fiddly - you'll probably want to get some paper out to draw the avatar and the enemy and label some coordinates - but you can do it if you use what you've learned so far.
My solution is below.
function redrawAvatar(mouseEvent) { var gameCanvas = document.getElementById("gameCanvas"); var avatarImage = new Image(); var enemyImage = new Image(); avatarImage.src = "img/avatar.png"; enemyImage.src = "img/enemy.png"; gameCanvas.width = 400; //this erases the contents of the canvas gameCanvas.getContext("2d").drawImage(avatarImage, mouseEvent.offsetX, mouseEvent.offsetY); gameCanvas.getContext("2d").drawImage(enemyImage, 250, 150); //my avatar is 30px wide and the enemy is at x=250, so I have to check whether mouseEvent.offsetX is within 30px //either side of x=250 (i.e., from 220 to 280) //similarly, since my avatar is 33px tall, I have to check whether mouseEvent.offsetX is within 33px ABOVE y=150 //but since enemy is only 30px tall, I also check whether mouseEvent.offsetX is within 30px BELOW y=150 //therefore, I check from (117 to 180) if (mouseEvent.offsetX > 220 && mouseEvent.offsetX < 280 && mouseEvent.offsetY > 117 && mouseEvent.offsetY < 180) { alert("You hit the enemy!"); } }Try it out here!
Next Time
That's it for the first part of the tutorial. In the next part, we'll get that enemy moving, and we'll add more of them, so that this can start to be a real game!
In the meantime, why not experiment with what you've learned? You could try adding multiple (unmoving) enemies yourself, perhaps with different graphics. Or you could add several avatars on screen at once, all following the mouse in different ways. What happens if you use something like
mouseEvent.offsetX + 100or300 - mouseEvent.offsetYin your call togameCanvas.getContext("2d").drawImage()?I hope you've enjoyed this so far. If anything's not clear, please ask about it in the comments
(One other quick note: at the moment, your code should work just fine on your own computer, but won't work if uploaded to the web. My demos work online, because I did a sneaky trick. Don't worry, I'll explain how to solve this in a future part of the tutorial.)