Here are a few things to make your game that much better looking:
Update your icon. The "coconut" icon looks nice, but you may decide you want something more specific to your program. You can do this easily by updating the "Icon.png" file (and it's relatives) to something else. You need to at least update the Icon.png file - make sure that its replacement is still 57x57 pixels - the iphone will automatically round off the corners and apply a gloss look. There are a few other variants - Icon-Small@2x.png, etc. You can update these too - just make sure the replacement graphics are the same size as the original.
You might also want to update your splash screen. This is the screen that shows up while your program is loading. This is the "Default.png" file in your Resources folder. Again, the easiest way to do this is just to replace Default.png with another copy (the same size) that has your preferred graphics.
Give your game a cool name. There are lots of settings based on this, but you can change all of them at once in the "Project" menu --> Rename... option. It should be pretty straightforward.
Oh, and don't forget sound to go with your graphics!
Thursday, January 20, 2011
Gesticulate this!
"What did you do in school today, Johnny?"
"Oh, Mom! Mr. Howe taught us how to make some gestures!"
(Before I forget, here is the example project for this.)
In the iOs world, a gesture is a motion that can be recognized by the iPhone, iPad, or iPod - such as a tap, a two finger pinch, a two finger swipe (aka a pan), or a two finger rotate. It turns out that there is a built-in way of handling this!
In this post, I'm going to show you how to use a PanGesture - this is when you drag two fingers across the screen together, and the object on the screen moves around with your fingers. In this case, we'll move an entire layer around, one that has a TMXMap in it.
The basic idea is to create an instance of UIPanGestureRecognizer and link it to a function you will write that handles the motion of the object. The second part is getting information about what the pan gesture involved - how far to move the layer, and doing so.
Part 1 is pretty straightforward. I started with a layer class that has another layer, called "gameLayer" in it, and I added a TMXMap to it. (You could add just about anything, but this made for a good example.) I then added a new function, which I called from the "init:" function:
-(void) setupPanGestureRecognition
{
UIPanGestureRecognizer* panGesture = [[UIPanGestureRecognizer alloc] initWithTarget: self action:@selector:(handlePanGesture:)[;
[[[CCDirector sharedDirector] openGLView] addGestureRecognizer: panGesture];
[panGesture release];
}
This creates a new PanGestureRecognizer, which is going to tell the target (self) to execute the function (handlePanGesture:) if it happens to notice a pan gesture. We then add it to the main view of the program - the director's openGLView. (And we release it, because the initWithTarget: method had retained it, but our class doesn't need to do so - the openGL view has it now.)
Then we have to make the actual handlePanGesture: function. It gets information about how much the pan gesture has moved from the beginning of the gesture. Before we can write this function, pop over to the header (.h) file and create a new CGPoint variable, panSoFar. Also, write the header for the function in the (.h) file:
-(IBAction)handlePanGesture:(UIPanGestureRecognizer*)sender;
Then we can write the function in the main (.m) file:
-(IBAction)handlePanGesture:(UIPanGestureRecognizer*)sender
{
if (sender.state == UIGestureRecognizerStateBegan)
panSoFar = ccp(0,0); // reset to a new pan...
CGPoint panFromSender = [sender translationInView:[[CCDirector sharedDirector] openGLView]];
// how far does the system say the image should pan... from the start of the motion
CGPoint panChange=ccpSub(panFromSender, panSoFar); // how far did the pan change since the last time we updated the pan?
panChange.y = panChange.y*-1; // flip the y-axis
gameLayer.position = ccpAdd(gameLayer.position, panChange); // move the gameLayer to match the pan.
panSoFar = panFromSender; //update the "panSoFar" so that next time it will just be a small increment to the pan.
}
Most of this calculation happens because despite the fact that you are moving the gameLayer a little bit every time this function is called, the information from the gesture recognizer is actually giving you how far the gameLayer needs to be moved from when the pan started. So we have to subtract how far we already moved it to find out how much more we need to move the layer.
Give it a try!
"Oh, Mom! Mr. Howe taught us how to make some gestures!"
(Before I forget, here is the example project for this.)
In the iOs world, a gesture is a motion that can be recognized by the iPhone, iPad, or iPod - such as a tap, a two finger pinch, a two finger swipe (aka a pan), or a two finger rotate. It turns out that there is a built-in way of handling this!
In this post, I'm going to show you how to use a PanGesture - this is when you drag two fingers across the screen together, and the object on the screen moves around with your fingers. In this case, we'll move an entire layer around, one that has a TMXMap in it.
The basic idea is to create an instance of UIPanGestureRecognizer and link it to a function you will write that handles the motion of the object. The second part is getting information about what the pan gesture involved - how far to move the layer, and doing so.
Part 1 is pretty straightforward. I started with a layer class that has another layer, called "gameLayer" in it, and I added a TMXMap to it. (You could add just about anything, but this made for a good example.) I then added a new function, which I called from the "init:" function:
-(void) setupPanGestureRecognition
{
UIPanGestureRecognizer* panGesture = [[UIPanGestureRecognizer alloc] initWithTarget: self action:@selector:(handlePanGesture:)[;
[[[CCDirector sharedDirector] openGLView] addGestureRecognizer: panGesture];
[panGesture release];
}
This creates a new PanGestureRecognizer, which is going to tell the target (self) to execute the function (handlePanGesture:) if it happens to notice a pan gesture. We then add it to the main view of the program - the director's openGLView. (And we release it, because the initWithTarget: method had retained it, but our class doesn't need to do so - the openGL view has it now.)
Then we have to make the actual handlePanGesture: function. It gets information about how much the pan gesture has moved from the beginning of the gesture. Before we can write this function, pop over to the header (.h) file and create a new CGPoint variable, panSoFar. Also, write the header for the function in the (.h) file:
-(IBAction)handlePanGesture:(UIPanGestureRecognizer*)sender;
Then we can write the function in the main (.m) file:
-(IBAction)handlePanGesture:(UIPanGestureRecognizer*)sender
{
if (sender.state == UIGestureRecognizerStateBegan)
panSoFar = ccp(0,0); // reset to a new pan...
CGPoint panFromSender = [sender translationInView:[[CCDirector sharedDirector] openGLView]];
// how far does the system say the image should pan... from the start of the motion
CGPoint panChange=ccpSub(panFromSender, panSoFar); // how far did the pan change since the last time we updated the pan?
panChange.y = panChange.y*-1; // flip the y-axis
gameLayer.position = ccpAdd(gameLayer.position, panChange); // move the gameLayer to match the pan.
panSoFar = panFromSender; //update the "panSoFar" so that next time it will just be a small increment to the pan.
}
Most of this calculation happens because despite the fact that you are moving the gameLayer a little bit every time this function is called, the information from the gesture recognizer is actually giving you how far the gameLayer needs to be moved from when the pan started. So we have to subtract how far we already moved it to find out how much more we need to move the layer.
Give it a try!
Tuesday, January 18, 2011
Multi-touch, part 2
So in the intervening time since I had to break from the first part of my multitouch post, I've done some more reading and had a revelation:
In your code, to handle touch events, you have to implement several different methods:
touchBegan, touchMoved, touchEnded, and touchCanceled (in case the phone rings - instead of the user picking up their fingers.) And in each case, you are sent a "UITouch" variable and a "UIEvent" variable.
And it's the same UITouch variable each time, from when the touch begins to when it ends. Oh, sure, there are things about it that change - the location of the touch for instance, or the status (begin/move/stationary/end/cancel) of the touch. But the memory location of the touch will be the same when you receive it as a touchBegan as it is when you receive it as a touchEnded!
Why does this matter? Well, frankly it doesn't, if you are using a single-touch model. But it is vital if you go to multi-touch. Because when the user touches the screen with two fingers, you get a touch object for the index finger and a touch object for the thumb. And those two touch objects will remain locked to those fingers until the user lets go. So you can track what each finger is doing.
For example:
One subtle difference you may notice is that instead of touchBegan (and its ilk), which is singular, this table includes touchesBegan (et al) - the plural. This is another difference with multitouch.
Let's step back a sec. In order to receive multitouch information, you first have to tell the view that is receiving the touches that it should receive multitouch info. As I mentioned in an earlier post, in Cocos2d-iOs, this can be done in the AppDelegate's applicationDidFinishLaunching: method, by adding the following:
[glView setMultipleTouchEnabled:YES];
right after the glView variable is initialized.
Then, in your Layer class, you still need to have the registerTouchDispatcher function:
-(void)registerTouchDispatcher
{
[[CCTouchDispatcher sharedTouchDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES];
}
Then you still need to write your responsive functions for when touch events arrive, just the plural version. So instead of the single-touch "began" responder:
-(BOOL)ccTouchBegan:(UITouch*)touch withEvent:(UIEvent*)event
you will write the multi-touch, plural version:
-(BOOL)ccTouchesBegan:(NSSet*)touches withEvent(UIEvent*)event
Note that in this case, you get an NSSet of touches, rather than a single UITouch. A set is a collection of several things in no particular order, so in this case, you'd get a set of one or more touch objects.
In their book, iPhone Programming: the Big Nerd Ranch Guide, Joe Conway and Aaron Hillegass have an elegant demonstration of how to use these sets of touch objects to maintain an NSDictionary of touches - and draw several lines at once. I won't recreate the whole thing here, but I will give an overview of what they did:
In your code, to handle touch events, you have to implement several different methods:
touchBegan, touchMoved, touchEnded, and touchCanceled (in case the phone rings - instead of the user picking up their fingers.) And in each case, you are sent a "UITouch" variable and a "UIEvent" variable.
And it's the same UITouch variable each time, from when the touch begins to when it ends. Oh, sure, there are things about it that change - the location of the touch for instance, or the status (begin/move/stationary/end/cancel) of the touch. But the memory location of the touch will be the same when you receive it as a touchBegan as it is when you receive it as a touchEnded!
Why does this matter? Well, frankly it doesn't, if you are using a single-touch model. But it is vital if you go to multi-touch. Because when the user touches the screen with two fingers, you get a touch object for the index finger and a touch object for the thumb. And those two touch objects will remain locked to those fingers until the user lets go. So you can track what each finger is doing.
For example:
User touches screen with index and thumb. | touchesBegan --> touch1 and touch2 |
User moves index finger | touchesMoved --> touch1 |
User touches screen with pinkie | touchesBegan --> touch3 |
User swipes all three fingers | touchesMoved --> touch1, touch2, and touch3 |
User lifts thumb | touchesEnded --> touch2 |
User lifts index and pinkie | touchesEnded --> touch1 and touch 3 |
One subtle difference you may notice is that instead of touchBegan (and its ilk), which is singular, this table includes touchesBegan (et al) - the plural. This is another difference with multitouch.
Let's step back a sec. In order to receive multitouch information, you first have to tell the view that is receiving the touches that it should receive multitouch info. As I mentioned in an earlier post, in Cocos2d-iOs, this can be done in the AppDelegate's applicationDidFinishLaunching: method, by adding the following:
[glView setMultipleTouchEnabled:YES];
right after the glView variable is initialized.
Then, in your Layer class, you still need to have the registerTouchDispatcher function:
-(void)registerTouchDispatcher
{
[[CCTouchDispatcher sharedTouchDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES];
}
Then you still need to write your responsive functions for when touch events arrive, just the plural version. So instead of the single-touch "began" responder:
-(BOOL)ccTouchBegan:(UITouch*)touch withEvent:(UIEvent*)event
you will write the multi-touch, plural version:
-(BOOL)ccTouchesBegan:(NSSet*)touches withEvent(UIEvent*)event
Note that in this case, you get an NSSet of touches, rather than a single UITouch. A set is a collection of several things in no particular order, so in this case, you'd get a set of one or more touch objects.
In their book, iPhone Programming: the Big Nerd Ranch Guide, Joe Conway and Aaron Hillegass have an elegant demonstration of how to use these sets of touch objects to maintain an NSDictionary of touches - and draw several lines at once. I won't recreate the whole thing here, but I will give an overview of what they did:
- They created a Line class that kept track of a starting point and an ending point.
- They had a member variable for the class that was an NSMutableDictionary.
- When the calls came into ccTouchesBegan, they encapsulated the UITouch pointers in NSValues so they could be used as keys in the dictionary and set new Line objects as the matching values, with both the start and end set to the touches' locations.
- Then when ccTouchesMoved came in, those touch objects were re-encapsulated in NSValues and used to look up the lines in the dictionary, and the end points of the lines adjusted to the new locations.
- Finally, when the ccTouchesEnded or ccTouchesCanceled came in, those touch objects were used again to look up the Lines in the dictionary. These entries in the dictionary were removed, and the Lines were transferred to more permanent storage in the program.
For the details, you should probably buy their book! It's definitely a good one for general iPhone programming.
Multitouch
Whew! I've been working on providing a good guide to making multitouch fun & easy for your games. I'd hoped to have this finished over the weekend, but I had a few personal setbacks. So let's see what I can do.
First of all, let's start with an overview.
When the user touches the screen with a finger, the iphone keeps track of that finger for as long as you are still touching it, until you let go. It fires off messages when you first touch it, when you move your finger, and when you release. We've seen this in action with our single touch functions - ccTouchBegan, ccTouchMoved, and ccTouchEnded. For single-touch handling, that's about all there is.
... except there's this little bit in ccTouchBegan, where we have to return YES. What's with that, anyway? Well, the truth is that there are many different things going on in the iphone that could be in charge of listening to that finger, your HelloWorld layer, sprites, other background layers, etc. And when the user touches the screen, the iphone is going to check with lots of them. But that isn't really efficient, once you start moving the finger around or releasing it. It is faster and better if just one part of your program starts dealing with the touch event.
This is why we say that your layer "swallows touches." When your touchBegan method returns a YES, you are really saying, "YES, I'll take responsibility for this touch event - nobody else needs to worry about it. Let me know about any further events associated with this finger." Then all the touchMoves and touchEnded events for this finger only will go to your layer - the rest won't be bothered with it. (Of course, the next time the user touches the screen, the process will start all over.)
So this is what is going on when you say:
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate: self
priority:0
swallowsTouches: YES];
you're telling the touch dispatcher that the layer should be informed of any touches - the #0 priority lets you get first stab at claiming the touches, and the YES for "swallows touches" means that you may very well claim a touch for your own.
So what does this mean for multitouch? The iphone is actually very clever - moreso than I gave it credit for when I first started programming it. See, the issue is that if you have two fingers on the screen, there are two touch events, and you might grab them both. But what happens if the two fingers were originally at (10,10) and (150,150) and later they are at (150,10) and (10,150)? How could you possibly know which finger went where?
Well, as it turns out, the iphone does - it tracks the touches for you.
Whoa! The bell just rang, so I'll have to finish this later. Next time we'll see how to activate the mutlitouch feature, and how to handle many different touches at once. It is similar to what you've seen so far, so don't worry!
...to be continued
First of all, let's start with an overview.
When the user touches the screen with a finger, the iphone keeps track of that finger for as long as you are still touching it, until you let go. It fires off messages when you first touch it, when you move your finger, and when you release. We've seen this in action with our single touch functions - ccTouchBegan, ccTouchMoved, and ccTouchEnded. For single-touch handling, that's about all there is.
... except there's this little bit in ccTouchBegan, where we have to return YES. What's with that, anyway? Well, the truth is that there are many different things going on in the iphone that could be in charge of listening to that finger, your HelloWorld layer, sprites, other background layers, etc. And when the user touches the screen, the iphone is going to check with lots of them. But that isn't really efficient, once you start moving the finger around or releasing it. It is faster and better if just one part of your program starts dealing with the touch event.
This is why we say that your layer "swallows touches." When your touchBegan method returns a YES, you are really saying, "YES, I'll take responsibility for this touch event - nobody else needs to worry about it. Let me know about any further events associated with this finger." Then all the touchMoves and touchEnded events for this finger only will go to your layer - the rest won't be bothered with it. (Of course, the next time the user touches the screen, the process will start all over.)
So this is what is going on when you say:
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate: self
priority:0
swallowsTouches: YES];
you're telling the touch dispatcher that the layer should be informed of any touches - the #0 priority lets you get first stab at claiming the touches, and the YES for "swallows touches" means that you may very well claim a touch for your own.
So what does this mean for multitouch? The iphone is actually very clever - moreso than I gave it credit for when I first started programming it. See, the issue is that if you have two fingers on the screen, there are two touch events, and you might grab them both. But what happens if the two fingers were originally at (10,10) and (150,150) and later they are at (150,10) and (10,150)? How could you possibly know which finger went where?
Well, as it turns out, the iphone does - it tracks the touches for you.
Whoa! The bell just rang, so I'll have to finish this later. Next time we'll see how to activate the mutlitouch feature, and how to handle many different touches at once. It is similar to what you've seen so far, so don't worry!
...to be continued
Monday, January 17, 2011
SneakyInput - addendum
I've promised a few of you instructions on how to handle multiple touches, but before I do, here is an answer to a more specific question: how do I get a SneakyJoystick and a SneakyButton to work at the same time? In other words, can I move my ship with my left thumb while I fire the guns with my right?
The version of the ShooterGame we wrote together would not do this. It turns out that it is a one-line change to fix this!
You have to go into the file that is called "yourProgramAppDelegate.m" (of course, it has your program name there, not "yourProgram"....) Partway through the "applicationDidFinishLaunching:" function, there is a line where the variable "glView" is created:
EAGLView *glView = [EAGLView viewWithFrame:...... and so forth, for about 4 lines.
Sometime after those lines, but still in the applicationDidFinishLaunching: function, add the following line:
[glView setMultipleTouchEnabled:YES];
... and that should fix it!
(For the curious, this post is where I found this solution.)
(Thanks to Patrick, who found a typo in the one line that you actually need to type in! It's fixed now.)
The version of the ShooterGame we wrote together would not do this. It turns out that it is a one-line change to fix this!
You have to go into the file that is called "yourProgramAppDelegate.m" (of course, it has your program name there, not "yourProgram"....) Partway through the "applicationDidFinishLaunching:" function, there is a line where the variable "glView" is created:
EAGLView *glView = [EAGLView viewWithFrame:...... and so forth, for about 4 lines.
Sometime after those lines, but still in the applicationDidFinishLaunching: function, add the following line:
[glView setMultipleTouchEnabled:YES];
... and that should fix it!
(For the curious, this post is where I found this solution.)
(Thanks to Patrick, who found a typo in the one line that you actually need to type in! It's fixed now.)
Thursday, January 13, 2011
Touchy, Touchy!
Here are notes from Matt on how to detect touches on the screen.
- In HelloWorldScene.h type this in the section where your functions are placed:
- -(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event;
- -(void) registerWithTouchDispatcher;
- -(void) ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event;
- In HelloWorldScene.m type this in the “init” function
- self.isTouchEnabled = YES;
- Next go to some place in your HelloWorldScene.m file where there is free space between the init and dealloc functions and create the three functions listed above
- We are going to define them one by one:
- For the ccTouchBegan, use this code:
- return YES;
- For the registerWithTouchDispatcher, use this code:
- [[CCTouchDispatcher sharedDispatcher] addTargetedDelegate: self
- priority: 0
- swallowsTouches:YES];
- For the ccTouchEnded, it is going to be up to you what you put in there. However, what is supposed to be placed into this function is defining where you want to move your player or whatever you want your touch to do. If you want to move your player to where you touched you will want to compare (using the ccpSub on the location of your touch and the location of your player relative to the screen). Then use this new CGPoint you generated to make your player to move to this new location. If you want to remove something on your touch, you will want to go through a list of enemies you want to remove and then compare each one to a certain distance from your touch location to this temporary enemy. (this code can be found in the tribble project’s “HelloWorldScene.m” in it’s ccTouchEnded function).
- Note: one thing you will want to do is use the line:
CGPoint touchLocation = [self convertTouchToNodeSpace: touch];
to get the location of where you touched on the screen.
This is all on single-touch events. Of course the iPhone is famous for multitouch sensitivity, and you might wish to play around with that. If so, great! I'll be posting on multitouch sometime this weekend, but first, you'll want to master the single touch material. - HH
A Sneaky post
Here are Matt's notes on using SneakyInput to make joysticks and buttons in your cocos2d-iphone game.
- In the “HelloWorldScene.h”:
- Import for buttons and JoyStick:
- #import "SneakyJoystick.h"
- #import "SneakyJoystickSkinnedBase.h"
- #import "SneakyButton.h"
- #import "SneakyButtonSkinnedBase.h"
- Create a SneakyJoystick and SneakyButton (in interface):
- SneakyJoystick* theJoystick;
- SneakyButton* theButton;
- After the interface, create a function that will make the buttons
- -(void) makeControls;
- In the “HelloWorldScene.m”
- Define the function you created in the ‘.h’ file (makeControls)
- You can copy/paste the makeControls function from your ShooterGame’s “HelloWorldScene.m” file
- At this point, you will either need to reuse the images for the button/joystick or you can create your own graphics.
- If you want to reuse the graphics, then you are going to have to track those down in the folder called “Shooter Graphics” or get them out of your spriteSheet you made for the Shooter Game project
- If you want to make your own graphics, remember that you are going to have to make 2 graphics for the joystick (one that is the background image and the other is the joystick look) and then 3 graphics for your button (one for unpressed button, one for the pressed button, and one for the recently released button)
- Put these graphics into a spritesheet and then import the ‘.plist’ file and the ‘.png’ image to your projects resources folder. Note! Don’t forget to check the box in the top left hand corner of the screen that pops up.
- Once you have either reused or made new graphics, change the names in the code you copy/pasted to match the names of the graphics you have just imported
- The very last thing you need to do is add to the init function of your “HelloWorldScene.m” file:
- [self makeControls];
- Example for making Joystick responding to movement
- In the class you want to move with the controller (i.e. your character), first create a function called something like “moveBy” and define it:
- -(void)moveBy:(CGPoint) amount;
- CGSize size = [[CCDirector sharedDirector] winSize];
- CGSize shipSize = playerShipSprite.contentSize;
- CGPoint loc = playerShipSprite.position;
- loc = ccpAdd(loc, amount);
- if (loc.x < 0 + shipSize.width/4) {
- loc.x = 0 + shipSize.width/4;}
- if (loc.x > size.width - shipSize.width/4){
- loc.x = size.width - shipSize.width/4;}
- if (loc.y < 0 + shipSize.height/6){
- loc.y = 0 + shipSize.height/6;}
- if (loc.y > size.height - shipSize.height/6){
- loc.y = size.height - shipSize.height/6;}
- playerShipSprite.position = loc;
- Then call this function in the Update of your HelloWorldScene.m file and determine what you want the “amount” part of the function to be
- CGPoint change = ccpMult(theJoystick.stickPosition, delta*6);
- [playerShip moveBy:change];
- This “amount” is going to be how much the person will move each update call, the lower the number the more smooth, but slower he will move, so test it out to try and balance speed with smoothness
- To get the button to do something when you push it do the following:
- if (theButton.active) {
- //put stuff in here to make the button do stuff}
Making a Scene
Here's something we didn't discuss in class.... the CCScene class.
We've said before that CCLayers are like sheets of transparency, which might be made, in turn, of subsheets of transparency. Perhaps the idea behind a CCScene is a box that holds a bunch of transparencies. And although you can see lots of CCLayers at once, you can only see one CCScene at a time.
So changing a scene then, means dramatically changing the look and feel of the game. A classic example of this would be to have one scene for a menu, another scene for your game, itself, and perhaps a third scene for a pause screen.
In fact, you have been making scenes all along! Your program doesn't really start with HelloWorldScene - it starts with the AppDelegate.m class which runs a method called applicationDidFinishLaunching: after all the behind-the-scenes stuff is set up. The very last thing it does is to tell the Director of your game to start running a scene from your HelloWorldScene class.
The next surprise is that your HelloWorldScene file isn't really a Scene - it's a Layer! Don't believe me? Check out the line in HelloWorldScene.h that says: @interface HelloWorld : CCLayer. However there is a function in HelloWorld that is called "+(id)scene:." If you look at the code for this, you can see that its job is to create a new scene, put a single HelloWorld layer into it and return that scene to whomever called the function.
So, if you wanted to create another scene (such as a GameMenuScene), you'd want to copy the default "HelloWorldScene" .h and .m files and modify them to make a GameMenu class. Then you'd change the last line of the applicationDidFinishLaunching in your program's MyGameTitleAppDelegate.m file so that it read:
[[CCDirector sharedDirector] runWithScene: [GameMenu scene]];
Supposing you had two classes, GameMenu and MainGame, both CCLayers with +(id)scene methods. Then once you had the GameMenu running, if the player pressed a button, the GameMenu class could detect it and change the scene to start the game:
[[CCDirector sharedDirector] replaceScene: [MainGame scene]];
(Note: you should only use "runWithScene" at the start of the program. After that, use "replaceScene.")
Then, in the MainGame class, if you win the game (or lose) you can replace the scene again with the GameMenu.
If you are using several scenes, memory considerations will come more to the fore, so we may need to take some specific steps to avoid running out of memory. But we'll cross that bridge when we come to it.
One thing that I can think of that may prove interesting is a way to transfer information (e.g., a score) between scenes. I have some ideas, so by the time you get to this, let's see whether we can work it out.
We've said before that CCLayers are like sheets of transparency, which might be made, in turn, of subsheets of transparency. Perhaps the idea behind a CCScene is a box that holds a bunch of transparencies. And although you can see lots of CCLayers at once, you can only see one CCScene at a time.
So changing a scene then, means dramatically changing the look and feel of the game. A classic example of this would be to have one scene for a menu, another scene for your game, itself, and perhaps a third scene for a pause screen.
In fact, you have been making scenes all along! Your program doesn't really start with HelloWorldScene - it starts with the AppDelegate.m class which runs a method called applicationDidFinishLaunching: after all the behind-the-scenes stuff is set up. The very last thing it does is to tell the Director of your game to start running a scene from your HelloWorldScene class.
The next surprise is that your HelloWorldScene file isn't really a Scene - it's a Layer! Don't believe me? Check out the line in HelloWorldScene.h that says: @interface HelloWorld : CCLayer. However there is a function in HelloWorld that is called "+(id)scene:." If you look at the code for this, you can see that its job is to create a new scene, put a single HelloWorld layer into it and return that scene to whomever called the function.
So, if you wanted to create another scene (such as a GameMenuScene), you'd want to copy the default "HelloWorldScene" .h and .m files and modify them to make a GameMenu class. Then you'd change the last line of the applicationDidFinishLaunching in your program's MyGameTitleAppDelegate.m file so that it read:
[[CCDirector sharedDirector] runWithScene: [GameMenu scene]];
Supposing you had two classes, GameMenu and MainGame, both CCLayers with +(id)scene methods. Then once you had the GameMenu running, if the player pressed a button, the GameMenu class could detect it and change the scene to start the game:
[[CCDirector sharedDirector] replaceScene: [MainGame scene]];
(Note: you should only use "runWithScene" at the start of the program. After that, use "replaceScene.")
Then, in the MainGame class, if you win the game (or lose) you can replace the scene again with the GameMenu.
If you are using several scenes, memory considerations will come more to the fore, so we may need to take some specific steps to avoid running out of memory. But we'll cross that bridge when we come to it.
One thing that I can think of that may prove interesting is a way to transfer information (e.g., a score) between scenes. I have some ideas, so by the time you get to this, let's see whether we can work it out.
Accelerate this!
Getting the accelerometer to work on your iOs device is actually not very hard - you just need to activate it in your program, and implement one function. To activate it, you go to the init function of your main scene and say:
self.isAccelerometerEnabled = YES;
This will cause the built-in accelerometer in the iPhone, iPod or iPad call the Accelerometer: didAccelerate: function over and over. You have to make sure you write this function! Here's an example that updates a CCLabelTTF object called "status" with the x,y, and z accelerations:
-(void)accelerometer: (UIAccelerometer*) accelerometer
didAccelerate:(UIAcceleration*)acceleration
{
[status setString: [NSString stringWithFormat:@"X:%3.2f, Y:%3.2f, Z:%3.2f",
acceleration.x, acceleration.y, acceleration.z]];
}
(The "%3.2f"s in the string will get replaced by the numbers that follow the string, formatted as numbers that go to the 1/100ths place.)
If you try running this in the simulator, you will get nothing happening - there is no accelerometer. However, if you run this on an actual device, you will see three numbers, each of one will mostly vary from -1 to +1. If you hold the device so that the square menu button is at the bottom, then "y" represents the up/down direction, "x" represents left/right and "z" represents towards/away from you.
A piece of advice: don't make the accelerometer code take very long - keep it short. You might use it to change your player's velocity, or maybe to change it's position, but don't go searching through the list of all your enemies to detect collisions - that's what update: is for. You want the response from the accelerometer to be fairly crisp.
Here is an example project, which includes the ability to change how often the accelerometer is "triggered" - there is, of course, an upper limit on how often it can fire, but some applications may only need to check on the orientation of the device every few seconds...
self.isAccelerometerEnabled = YES;
This will cause the built-in accelerometer in the iPhone, iPod or iPad call the Accelerometer: didAccelerate: function over and over. You have to make sure you write this function! Here's an example that updates a CCLabelTTF object called "status" with the x,y, and z accelerations:
-(void)accelerometer: (UIAccelerometer*) accelerometer
didAccelerate:(UIAcceleration*)acceleration
{
[status setString: [NSString stringWithFormat:@"X:%3.2f, Y:%3.2f, Z:%3.2f",
acceleration.x, acceleration.y, acceleration.z]];
}
(The "%3.2f"s in the string will get replaced by the numbers that follow the string, formatted as numbers that go to the 1/100ths place.)
If you try running this in the simulator, you will get nothing happening - there is no accelerometer. However, if you run this on an actual device, you will see three numbers, each of one will mostly vary from -1 to +1. If you hold the device so that the square menu button is at the bottom, then "y" represents the up/down direction, "x" represents left/right and "z" represents towards/away from you.
A piece of advice: don't make the accelerometer code take very long - keep it short. You might use it to change your player's velocity, or maybe to change it's position, but don't go searching through the list of all your enemies to detect collisions - that's what update: is for. You want the response from the accelerometer to be fairly crisp.
Here is an example project, which includes the ability to change how often the accelerometer is "triggered" - there is, of course, an upper limit on how often it can fire, but some applications may only need to check on the orientation of the device every few seconds...
Sprite Sheets and Zwoptex
Here are some notes from Matt on using Zwoptex and making sprite sheets in your program:
Sprite Sheets
- Doing “Tribble” way by adding in the picture with a bit of code and having the class handle it is fine, but if you add lots of instances of that class the iPod will start to run slow
- Sprite sheets make this less of a problem
- Sprite sheets
- A larger image that holds many smaller images in a power-of-2 dimension.
- A secondary file is then used to define the dimensions of each of the smaller sprites on the sheet
- This helps because then all the sprite images are loaded at once and it is easy to call for a specific picture in the whole sheet.
- Using Zwoptex
- Program to make the process of making spritesheets a lot easier by automatically put it in a power-of-two dimensional page
- Fairly straight forward once you have tweaked settings
- Import the pictures
- They will be stuck together
- Tweak settings to what we want then click “Apply” to make Zwoptex make the spritesheet
- Before clicking “Apply” make the spritesheet large enough for all the images to fit on the screen, but make it the smallest possible for the best results
- Once you have added all the pictures Zwoptex will crop down the images to be the smallest possible so they might not line up which is okay because cocos2d will compensate with the offset to realign the images
- Padding
- It is basically talking about how many pixels separate each picture
- Recommended to just use the default 2 pixel padding
- Once done with these steps click File => Save which saves it in a ZSS file. Then click the “Publish” button which will create a .png and .plist file for the sprite sheet
- .png is the actual image that holds all the spritesheets
- .plist holds all the information about which image is which
- talk about dimensional stuff if necessary
Wednesday, January 12, 2011
Making cocos2d TileMaps, part 3
Continued from part 2
Still with substantial attribution to Ray Wenderlich, Steffen Itterheim and the cocos2d-iphone tutorial!
So in parts 1 & 2, we created a tileset and made it appear on the screen and move so that the penguin stays centered in the screen and still moves through the world. The next thing is to interact with the things in that tiled world. In part 1, we created a tiled layer on top of the primary layer that represented things that were solid- things that we couldn't move a penguin through. Now to make it act that way!
In this program, the solidity of these objects only matters when the penguin is trying to move - if it is trying to move into a solid spot, we need to prevent that. This requires us to find out which tile the penguin is on (or moving into) and whether the "obstacle" tile there is a solid one.
Here's the math: Deciding on which tile the penguin is in is a matter of converting a set of pixel coordinates that might go as high as, say 2400 x 2400 into a row and column of tiles that are 24 x 24 pixels each (or another size of your choosing). So we might find the penguin at, say (1234, 567) and find that it refers to pixel coordinates (51, 77). This can be done by dividing the coordinates by the tile size and rounding down. (The coordinate on the y-axis gets subtracted from the max coordinate - the tiles get counted down from the top, but the pixels get counted from the bottom of the screen.) Try it!
So here's the code to make the conversion:
CGPoint penguinGrid = ccp((int)([thePenguin getPosition].x/theMap.tileSize.width),
(int)(theMap.mapSize.height - ([thePenguin getPosition].y/theMap.tileSize.height)));
So the next step, once we know which tile location we are on, is to find out what is there. In our example, I wrote a short function that asks the map for the information on the "obstacles" layer, gets the "global identifier" (GID) of the tile at that location, and looks up to see whether it is a tile that has a "solid" property set to "True."
// determines whether the tile at the given tile coordinates in the obstacles layer
// has a property of "solid" set to "True." If there is no tile there, it doesn't
// have any properties, it doesn't have a "solid" property, or the property isn't
// "True", then the answer is "NO."
-(BOOL)tileIsAnObstacle:(CGPoint)loc
{
CCTMXLayer* obstaclesLayer = [theMap layerNamed:@"obstacles"];
NSAssert(nil!=obstaclesLayer, @"Can't find a tile in the obstacles layer - no such layer found.");
int theGID = [obstaclesLayer tileGIDAt:loc];
if (theGID!=0) {
NSDictionary* properties = [theMap propertiesForGID:theGID];
if(nil!=properties)
{
NSString* solidStatus = [properties valueForKey:@"solid"];
if (nil!=solidStatus && [solidStatus compare:@"True"] == NSOrderedSame)
return YES;
}
}
return NO;
}
So what we can do is to check - if we want to move one cell to the right, is that square currently occupied by a "solid" tile? We need to consider which cell we are trying to move into:
CGPoint targetCell = cpp(penguinGrid.x+1,penguinGrid.y);
and then we can check whether that square is occupied:
if ([self tileIsAnObstacle: targetCell])
return; // cancel the move!
else
[thePenguin moveRight];
This should show the basic pieces that go into reading what is in the tile Map. But wait! There's more!
What if you want to change which tile is at a given location? It takes two steps: ask the map for the layer you want to change:
CCTMXLayer* backgroundLayer = [theMap layerNamed:@"background"];
and then we tell that layer to change the GID of a given tile:
[backgroundLayer setTileGID: newGID at: thePoint];
(where newGID is the new value and thePoint is the location in the grid)
How do you figure out what GID to put? This may take some experimentation - I suspect that you can just count across the row of tiles in your tilesheet, like a book, but I haven't tried it. (Can anyone confirm this?)
Still with substantial attribution to Ray Wenderlich, Steffen Itterheim and the cocos2d-iphone tutorial!
So in parts 1 & 2, we created a tileset and made it appear on the screen and move so that the penguin stays centered in the screen and still moves through the world. The next thing is to interact with the things in that tiled world. In part 1, we created a tiled layer on top of the primary layer that represented things that were solid- things that we couldn't move a penguin through. Now to make it act that way!
In this program, the solidity of these objects only matters when the penguin is trying to move - if it is trying to move into a solid spot, we need to prevent that. This requires us to find out which tile the penguin is on (or moving into) and whether the "obstacle" tile there is a solid one.
Here's the math: Deciding on which tile the penguin is in is a matter of converting a set of pixel coordinates that might go as high as, say 2400 x 2400 into a row and column of tiles that are 24 x 24 pixels each (or another size of your choosing). So we might find the penguin at, say (1234, 567) and find that it refers to pixel coordinates (51, 77). This can be done by dividing the coordinates by the tile size and rounding down. (The coordinate on the y-axis gets subtracted from the max coordinate - the tiles get counted down from the top, but the pixels get counted from the bottom of the screen.) Try it!
So here's the code to make the conversion:
CGPoint penguinGrid = ccp((int)([thePenguin getPosition].x/theMap.tileSize.width),
(int)(theMap.mapSize.height - ([thePenguin getPosition].y/theMap.tileSize.height)));
So the next step, once we know which tile location we are on, is to find out what is there. In our example, I wrote a short function that asks the map for the information on the "obstacles" layer, gets the "global identifier" (GID) of the tile at that location, and looks up to see whether it is a tile that has a "solid" property set to "True."
// determines whether the tile at the given tile coordinates in the obstacles layer
// has a property of "solid" set to "True." If there is no tile there, it doesn't
// have any properties, it doesn't have a "solid" property, or the property isn't
// "True", then the answer is "NO."
-(BOOL)tileIsAnObstacle:(CGPoint)loc
{
CCTMXLayer* obstaclesLayer = [theMap layerNamed:@"obstacles"];
NSAssert(nil!=obstaclesLayer, @"Can't find a tile in the obstacles layer - no such layer found.");
int theGID = [obstaclesLayer tileGIDAt:loc];
if (theGID!=0) {
NSDictionary* properties = [theMap propertiesForGID:theGID];
if(nil!=properties)
{
NSString* solidStatus = [properties valueForKey:@"solid"];
if (nil!=solidStatus && [solidStatus compare:@"True"] == NSOrderedSame)
return YES;
}
}
return NO;
}
So what we can do is to check - if we want to move one cell to the right, is that square currently occupied by a "solid" tile? We need to consider which cell we are trying to move into:
CGPoint targetCell = cpp(penguinGrid.x+1,penguinGrid.y);
and then we can check whether that square is occupied:
if ([self tileIsAnObstacle: targetCell])
return; // cancel the move!
else
[thePenguin moveRight];
This should show the basic pieces that go into reading what is in the tile Map. But wait! There's more!
What if you want to change which tile is at a given location? It takes two steps: ask the map for the layer you want to change:
CCTMXLayer* backgroundLayer = [theMap layerNamed:@"background"];
and then we tell that layer to change the GID of a given tile:
[backgroundLayer setTileGID: newGID at: thePoint];
(where newGID is the new value and thePoint is the location in the grid)
How do you figure out what GID to put? This may take some experimentation - I suspect that you can just count across the row of tiles in your tilesheet, like a book, but I haven't tried it. (Can anyone confirm this?)
Labels quicksheet
A few thoughts on making labels in your game from Matt...
- In HelloWorldScene.h create a new layer for score:
- CCLayer* scoreLayer;
- In HelloWorldScene.h import “CCLabelTTF.h” and add:
- int numScore
- CCLabelTTF* scoreLabel;
- In HelloWorldScene.m
- Initialize scoreLayer and add it to the scene
- scoreLayer = [[CCLayer alloc] init];
- [self addChild:scoreLayer z:2];
- Initialize the amount of numScore (numScore = 0;)
- Initialize what scoreLabel is going to hold
- scoreLabel = [CCLabelTTF labelWithString:[NSString stringWithFormat:@"Score: %d", numScore] fontName:@"Arial" fontSize:28]; (note: available fonts - http://daringfireball.net/misc/2007/07/iphone-osx-fonts)
- scoreLabel.position = ccp(size.width - 100,size.height - 11);
- scoreLabel.color = ccc3(255, 255, 255);
- [scoreLayer addChild:scoreLabel];
- In the update function:
- When we check to see if a bullet has hit an enemy, in the part of code that says “YES HIT IT!” increase the score of numScore by however much (default of 100?)
- Then update the scoreLabel to the new number
- [scoreLabel setString:[NSString stringWithFormat:@"Score: %d",numScore]];
- release the label in dealloc.
Making a cocos2d tilemap, part 2
Continued from part 1
Continuing with a "cheat sheet" on how to add Tiles to your cocos2d project...
Again, this is largely from the work of the cocos2d official tutorial, Steffen Itterheim and Ray Wenderlich.
Continuing with a "cheat sheet" on how to add Tiles to your cocos2d project...
Again, this is largely from the work of the cocos2d official tutorial, Steffen Itterheim and Ray Wenderlich.
- In XCode, add your .png tileset and your .tmx file (from Tiled) to the resources section of your project.
- In the header file for your primary scene class, (e.g., HelloWorldScene.h) create a variable to hold your map:
CCTMXTiledMap* theMap; - Then switch over to your .m file and add a line to the init function to load and initialize the tilemap:
theMap = [CCTMXTiledMap tiledMapWithTMXFile:@"yourfile.tmx"]; - You can make the tilemap appear onscreen by adding it as a child to another layer:
[self addChild: theMap]; - If you wish to make the background move around and keep another object stationary one easy way is to move the scene itself one way every time you move the object in which it resides the other. (For example, if the objects' x-position gets bigger by 5 pixels, then move the scene to the left by 5 pixels to compensate.) Alternately, you can just tell the scene to recenter on the screen during the update: cycle - we wrote a nice function called "centerScreenOnPenguin," which you could modify. (Here's a shorter version of the one we wrote together.)
(void)centerWindowOnPenguin
{
CGSize screenSize = [[CCDirector sharedDirector] winSize];
gameLayer.position = ccp(screenSize.width/2-[thePenguin getPosition].x,
screenSize.height/2-[thePenguin getPosition].y);
} - To give the penguin a starting location, we get a point that is based on the information in the "objects" layer. In the HelloWorld init function, we added in some code after we loaded the map but before we created the penguin. (Again, here is a more compact version.)
NSMutableDictionary* spawnPointRecord = [[theMap objectGroupNamed:@"objects"] objectNamed:@"playerStart"];
CGPoint startPos = ccp([[spawnPointRecord valueForKey:@"x"] intValue],
[[spawnPointRecord valueForKey:@"y"] intValue]);
Note: the "objects" and "playerStart" need to match what you picked when you set up your Tiled file.
Coming up - handling collisions with walls in your tiled world.
Tuesday, January 11, 2011
Making a TileMap in cocos2d
Here's how to get an orthoganal tilemap in your iphone game. This is largely based on work by Ray Wenderlich and Steffen Itterheim.
Step 1: Create some tiles
Step 2: Design the tile map
Step 1: Create some tiles
- Launch SternTiles, make a new file and pick a tile size for your project.
- Create some tiles! You can always come back and edit them or add more.
- Save your file and add (copy) it to your project folder.
Step 2: Design the tile map
- Open up Tiled and create a new document. Pick how many tiles should show up in your map (getting over 100 x 100 would be pushing it...) and the size of the tiles you chose in SternTiles.
- Go to the Map menu and select "New Tileset..." Click on the "Browse" button and navigate to the copy of the tilesheet file you made in SternTiles that lives in your project's "Resources" folder.
- Start drawing away!
- To create a starting point for your character:
- Go to the Layer menu and choose "New Object Layer." Name this layer "Objects," or something like that - don't forget what it is.
- Draw a small box on the screen in this layer - the lower left corner will be the starting location for your player.
- Right-click (control-click) on the box and select "object properties" for the name, type in "playerStart" or something like that.
- (Note: you can also use this to place other objects in the world, such as keys, coins, monster spawn points, etc. Just make sure that each of these objects has a unique name.)
- To manage collisions, you can create another layer for obstacles.
- Go to the Layer menu and create a new Tile Layer.
- Use another tile (like a solid red tile) to draw on the obstacle layer over all the objects that are "solid" on the background.
- In the tileset, right-click (control-click) on the tile you used and select "Tile Properties." Add a new key/value pair that says "solid"|"True."
- (Ideas: You can also repeat this process if you want to have areas that slow the player down or have other features (like traps!) associated with certain squares on the grid)
- Save your file - you can always come back and make changes!
Next up.... Getting your Tiles into your XCode program!
Cocos2d Animation quicksheet
Note: this is largely based on Steffen Itterheim's excellent code to make a helper file for the CCAnimation class. You can read more about it in his book.
(I've added two helper methods to his CCAnimationHelper class. For those of you in my class, I've included this updated version in the class handouts.)
//******************************
/**
returns an action that will cause the animation to play for a sprite one time.
*/
-(CCAnimate*) singleAnimateAction:(CCAnimation*) animationFrames
{
return [CCAnimate actionWithAnimation:animationFrames];
}
//******************************
/**
returns an Action that will cause the animation to play for a sprite over and over.
*/
-(CCRepeatForever*) loopedAnimateAction:(CCAnimation*) animationFrames
{
return [CCRepeatForever actionWithAction:[self singleAnimateAction:animationFrames]];
}
So here is the basic process:
Create a sequence of graphics files. Make all of them the same size, and name them with the same base name, with numbers (starting at zero) at the end. They all have to be .png files. For example: penguinFrame0.png, penguinFrame1.png, penguinFrame2.png.
If you are planning to use the frames as individual pictures....
(I've added two helper methods to his CCAnimationHelper class. For those of you in my class, I've included this updated version in the class handouts.)
//******************************
/**
returns an action that will cause the animation to play for a sprite one time.
*/
-(CCAnimate*) singleAnimateAction:(CCAnimation*) animationFrames
{
return [CCAnimate actionWithAnimation:animationFrames];
}
//******************************
/**
returns an Action that will cause the animation to play for a sprite over and over.
*/
-(CCRepeatForever*) loopedAnimateAction:(CCAnimation*) animationFrames
{
return [CCRepeatForever actionWithAction:[self singleAnimateAction:animationFrames]];
}
So here is the basic process:
Create a sequence of graphics files. Make all of them the same size, and name them with the same base name, with numbers (starting at zero) at the end. They all have to be .png files. For example: penguinFrame0.png, penguinFrame1.png, penguinFrame2.png.
If you are planning to use the frames as individual pictures....
- Drag the CCAnimationHelper files into your project's source folder.
- In your class's header file, #import "CCAnimationHelper.h." (Which header file? Whichever one will be using the rest of the code....)
- Import all of the pictures into the Resources section of your project.
- When you are ready to start animating, type in
CCAnimation* theAnimation = [CCAnimation animationWithFile:@"penguinFrame" frameCount:10 delay: 0.15];
(this assumes that you have 10 frames with a 0.15 second delay between each frame.) - Then either say
[mySprite runAction: [theAnimation singleAnimateAction]];
or
[mySprite runAction: [theAnimation loopedAnimateAction]];
If you are planning to use the frames with a SpriteBatchNode (much better and faster), you can put the frames from several different animations into one spritesheet.... It's basically the same process, except for step 4 is slightly different.
- Drag the CCAnimationHelper files into your project's source folder.
- In your class's header file, #import "CCAnimationHelper.h." (Which header file? Whichever one will be using the rest of the code....)
- Import all of the pictures into the Resources section of your project.
- When you are ready to start animating, type in
CCAnimation* theAnimation = [CCAnimation animationWithFrame:@"penguinFrame" frameCount:10 delay: 0.15];
(this assumes that you have 10 frames with a 0.15 second delay between each frame.) - Then either say
[mySprite runAction: [theAnimation singleAnimateAction]];
or
[mySprite runAction: [theAnimation loopedAnimateAction]];
That should do it! If you think you will use that animation frequently, you may want to store it in a variable, rather than reloading it each time...
Sunday, January 9, 2011
Vocab....
I don't know about any of y'all, but as I've been working through this material getting ready to teach it, I've been finding quite a bit of vocabulary that can be a bit confusing.... Here is my take on some of it:
- Node - a generic name for anything that appears on the screen in cocos2d - that might include sprites, layers, scenes, spritebatchnodes or more.
- Sprite - an individual graphic object, which can be moved, faded, stretched or rotated. Each sprite is based on a graphics file.
- Layer - like a sheet of transparency, capable of holding Sprites or more Layers.
- Scene - a collection of one or more Layers. Typically, scenes can be switched in and out, and are representative of a different behavior. For example, your game might have one scene for the game menu and another for the game itself.
- SpriteBatchNode - similar to a layer, this can hold many different sprites, but the difference is that they all must use the same graphics file as each other. The advantage is that it can then draw them all extremely quickly.
- SpriteSheet - a graphics file that holds many smaller pictures, in conjunction with a second (plist) file that describes how the smaller images can be extracted from the larger one. Often used in conjunction with a SpriteBatchNode to make many different-looking Sprites with the advantage of the SpriteBatchNode's speed.
- Texture Atlas - the same thing as a SpriteSheet.
- TileMap - associated with a TileSet, a tilemap is a description of a large grid, each square of which has a tile associated with it. It allows you to build worlds much larger than the screen.
- TileSet - Similar to a SpriteSheet, this is also a graphic with many smaller graphics in it. However, this is used to make up the repeating tiles that appear in a TileMap. Each of the smaller tile graphics is assigned a number (GID) so that it can be looked up easily.
- CGPoint - a data structure that keeps track of a set of (x, y) coordinates - but might also represent a vector, such as the (x, y) components of a velocity.
- CGSize - a data structure that keeps track of a set of (width, height) sizes.
- CGRect - a data structure that keeps track of enough information to describe a rectangle - the location of the rectangle and how large it is.
- Anchor Point - where on a sprite or layer is the "relevant" point that describes where it is. This is a CGPoint, one where both x and y are between 0 and 1. For x, 0 represents the left edge, and 1 represents the right edge. Similarly, for y, 0 is the bottom and 1 is the top. By default, objects start with an anchor point of (0.5, 0.5) - the object's center.
- int - a integer number
- float - a "floating point number" - can have a fractional value and/or an exponent.
- BOOL - short for "boolean" - a value that can only be "YES" or "NO."
- NSString - a sequence of letters or other characters. In this programming language, strings must be surrounded by quotes and preceded by "at" signs. (@"Hello, World!")
- NSMutableArray - a list of objects, which can be modified.
- .h files - header files. Describes what variables a class object will keep track of and what functions it will have. Basically a list of the nouns and verbs associated with this class, as well as any file that need to be included for it to work.
- .m files - main files. Describes the details of how the functions listed in the .h file will work, rather than just listing them. May include some extra functions expected by the program, like dealloc and update.
Have I missed anything? Any followup questions?
Friday, January 7, 2011
agenda for Friday... and beyond!
Agenda for the next few days:
Short term:
- A couple of brief modifications to make things easier later...
- import CCSpriteHelper
- make a permanent variable for the SpriteBatchNode in HelloWorldScene.h
- make getSprite and getPosition functions in the PlayerShip, PlayerBullet, and EnemyShip classes
- Make lots of bullets when the player presses the button.
- Remove the “exampleBullet” variable from HelloWorld.
- Add an NSMutableArray variable to HelloWorldScene.h (since we’ll want to access this list in several situations).
- Initialize the list of bullets in HelloWorld’s “init” function.
- In HelloWorld’s update: function, check whether the button is pressed
- if so, create a new bullet and add it to the list.
- correct timing issues... you’ll see what I mean!
- Make the bullets delete when they go off the screen.
- In HelloWorld’s update: function, go through the list of bullets and remove any bullets that have gone off the screen from the list and from the screen.
- Make a bunch of enemies and get them moving.
- Add some code in EnemyShip’s update: function to make it move erratically.
- Remove the “exampleEnemy” variable from HelloWorld.
- Add an NSMutableArray variable to HelloWorldScene.h.
- Initialize the list of enemies in HelloWorld’s “init” function and put some enemies in it.
- Make the enemies disappear if a bullet hits them.
- Create a new function in HelloWorld called checkForBulletEnemyCollisions.... at the end of HW’s update: function, we’ll say [self checkForBulletEnemyCollisions];
- In this new function go through the list of bullets, one by one. For each bullet, go through the list of enemies one by one, and see whether their sprites overlap. If so remove ‘em!
Coming up, in no particular order:
- Animating the sprites - give the monsters googly eyes!
- Putting words on the screen - e.g., the score
- new project: Designing larger worlds with TileMaps
- Particle Effects - very cool!
- Discussing how to plan to write a game...
Thursday, January 6, 2011
Completion and Frustration: Day 3 of iPhone Game Programming
We spent yesterday working on two things - adding and removing objects to and from the screen "on the fly" (instead of just when the game starts up) and then beginning a project by creating classes.
The first part of this took a bit longer than I had planned for - I meant it to take about 30 minutes, and it took 60 minutes. I'm glad we spent the time on it, though, for two reasons - one, it is an important skill to have, and I wanted everybody to feel like they had a handle on it, and two, because the second part was frustrating for a lot of people, and I'm glad it didn't drag on more than it did.
The second part of the class involved making a new project, importing graphics, creating classes to represent objects on the screen, and instantiating those objects. Basically, it was the next step in the learning process - instead of just copying what I was doing on screen, the students had to remember how to do stuff, and integrate what they had learned with a bit more independence.
It's hard to do - I am always tempted to reach in and "fix" everything for them, and sometimes I do. I think it was the right thing to do, but I don't want it to sap our momentum. Unless it needs to. I've worried (based on last year) that the class could become a grueling march of concepts without much chance for the students to innovate or practice on their own. So a pause in the "instructional" part of the class seemed like a good idea.
So what's coming up? Today, I plan on doing:
I think we'll be using this project for a couple of days. I can see us working on collision detection, text fields and possibly particle effects!
Stuff I want to emphasize today - I hope I remember at the start of class:
The first part of this took a bit longer than I had planned for - I meant it to take about 30 minutes, and it took 60 minutes. I'm glad we spent the time on it, though, for two reasons - one, it is an important skill to have, and I wanted everybody to feel like they had a handle on it, and two, because the second part was frustrating for a lot of people, and I'm glad it didn't drag on more than it did.
The second part of the class involved making a new project, importing graphics, creating classes to represent objects on the screen, and instantiating those objects. Basically, it was the next step in the learning process - instead of just copying what I was doing on screen, the students had to remember how to do stuff, and integrate what they had learned with a bit more independence.
It's hard to do - I am always tempted to reach in and "fix" everything for them, and sometimes I do. I think it was the right thing to do, but I don't want it to sap our momentum. Unless it needs to. I've worried (based on last year) that the class could become a grueling march of concepts without much chance for the students to innovate or practice on their own. So a pause in the "instructional" part of the class seemed like a good idea.
So what's coming up? Today, I plan on doing:
- Layers
- "SneakyInputs" - joysticks and buttons
- a lesson on Spritesheets (a.k.a. Texture Atlases) by Matt
- animation of sprites
- and perhaps, in the case of the bullets, keeping a stash of bullets that are hidden when not in use.
I think we'll be using this project for a couple of days. I can see us working on collision detection, text fields and possibly particle effects!
Stuff I want to emphasize today - I hope I remember at the start of class:
- Reading and commenting on each other's blogs
- One class per type of object
- The difference between sprites and things that hold sprites
Monday, January 3, 2011
First day...
I think we got off to a good start on the first day. We had a bunch of new material, but some time to play, as well. Let's see, what did we do....
- Went over the class requirements, including the blogging requirement
- Got everybody going in XCode with the default "Hello World" app
- Added a couple of graphics to the scene
- Introduced Actions and played around with them.
- Started listening for touches
Whew! That's quite a lot, and I think the kids handled it well. We have a few folks that are a bit frustrated with semicolons, typos and matching brackets, but that seems par for the course - I think we all have been frustrated by that in most languages.
Tomorrow, I'm looking forward to creating a new class, moving around with the update: function, and some simple loops. If there's time, perhaps we can make a simple game of it....
Sunday, January 2, 2011
Last minute prep for cocos2d class
I'm sitting down with a chunk of time and few distractions to actually try the examples that I am planning to teach tomorrow in my cocos2d class - the first day. As always, it is interesting to note the difference between what I know I can do and the practice of actually doing it!
Right now, I am playing around with actions, updates and touches, working through the "Your first game" tutorials in both cocos2d's official tutorial and Steffen Itterheim's book. There are things I like about both of them, and things I'd prefer to avoid, at least at first, of course. (E.g., accelerometers - many of our students will be rocking the simulator, so that's going to fall under "advanced" for this course.) I think I'll try to find a middle ground.
One tricky thing I need to do is to find a way to do an exercise for the kids so they can extend what they are doing, not just repeat what we did in class. I don't think I did a very good job of that last year, so we just did a lockstep "type in the following! Look what it did!" kind of class. For some students, that's enough to get them thinking and exploring, but I can't assume that of everybody, of course.
So those are my challenges as I head into the evening.
- Posted using BlogPress from my iPad
Right now, I am playing around with actions, updates and touches, working through the "Your first game" tutorials in both cocos2d's official tutorial and Steffen Itterheim's book. There are things I like about both of them, and things I'd prefer to avoid, at least at first, of course. (E.g., accelerometers - many of our students will be rocking the simulator, so that's going to fall under "advanced" for this course.) I think I'll try to find a middle ground.
One tricky thing I need to do is to find a way to do an exercise for the kids so they can extend what they are doing, not just repeat what we did in class. I don't think I did a very good job of that last year, so we just did a lockstep "type in the following! Look what it did!" kind of class. For some students, that's enough to get them thinking and exploring, but I can't assume that of everybody, of course.
So those are my challenges as I head into the evening.
- Posted using BlogPress from my iPad
Subscribe to:
Posts (Atom)