pyehouse
6Nov/110

Facebook Page Tab and WordPress

Previously I discussed what I went through to use the generic SSL certificate that came with my ICDSoft hosting service with the Facebook application I set up for my Facebook Page tab. I mentioned at the end that I also had to do a little rejiggering to use a WordPress page for the Facebook app. It's actually pretty simple and I'm going to share that, too. You may want to review the setup steps for a Facebook app as a Facebook Page tab. Got it? Good, let's move on.

As you saw, when setting up a Facebook app for use as a Page tab, Facebook asks you to give them a secure (https) URL to point to, which they embed into an iframe on the page. The URL will be sent to the browser to be loaded by the client. Whatever is served up will be viewed in an iframe with a max width of 520 pixels. It is critical to understand that much. If you don't pay attention to the 520 pixel width limitation, your FB page will look rather ugly and likely have horizontal scrollbars and only partially revealed assets.

You'll want to make sure that your WordPress site will work when invoked from the https URL. I imagine this will typically be the case. I know for me, simply changing the URL to https was sufficient and I could easily traverse my WordPress driven site via SSL. If this isn't the case for you and you want to use WordPress to handle your Facebook Page tab content, you are going to have to find some means of getting your WordPress site to respond to https URLs.

Let's assume for a moment that you do manage to get SSL working for your WordPress site. Next you are going to need to create a page which you want to hit via your FB app. That much is easy, but there are two issues you will likely need to resolve. One is that your new FB app page is going to show up in any list of pages for your WP site which you have and the other is that your new FB WP page is not going to be styled optimally for inclusion as an FB app page.

Starting with the first point, why would it matter that your FB app page on your WP site is going to show up in the list of pages? Frankly, because of the second point, optimizing it for FB app usage. It's likely going to look quite different because of how it will be used, and so it would be good not to have it navigable under normal browsing situations.  To eliminate this, for starters if you are using the 'Pages' widget in your sidebar, go to your WP dashboard and open 'Appearance->Widgets' and click to expand the 'Pages' widget. By default, WP allows an 'Exclude' area which you can enter the page ID of each page, separated by commas, which you do not want to have show up in the 'Pages' widget. More tricky are themes which include the pages in the header or other areas. Unless the theme provides the option to not list certain pages, you will find yourself in a bit of pickle, perhaps having to go so far as to custom edit your header in order to not list them. Because that is going to depend on which theme you have installed, it's beyond the scope of this article to discuss code customization.

The second point, however, will involve some customization in the form of a custom page template. Here is what the WordPress Codex has to say on creating your own page template. It's a bit barebones, so let me help you out a bit. First, create a new file in your theme's folder called 'customfbpage.php'. The contents of the file should look like this:

<?php
/*
Template Name: Facebook Page
*/
?>

You can upload this via FTP or your website's control panel or whatever other means you have of pushing new files up to your template folder. Now, go to your WordPress console and go to the Appearance->Editor link:

On the right you will see a list of files which can be edited for your template. You can see in my snippet I have only a scant few because I'm using a child template. YMMV. Now you need to find the 'page.php' for your template. If you are using a child template, you will need to open the parent template's copy of page.php if you haven't already overridden it in your child. Otherwise click the child copy. Regardless, just copy all of the text within by highlighting all of it, right clicking, and choosing 'Copy'. Now go back to the customfbpage.php and edit that. Click below the comment block and paste the contents in by right clicking and choosing 'Paste'. I'm using the NotesIL theme as my parent theme, so this is what I see:


<?php
/*
Template Name: Facebook Page
*/
?>

<?php get_header(); ?>

	<div id="container">
		<div id="content">
			<?php while ( have_posts() ) : the_post(); ?>
			<div id="post-<?php the_ID(); ?>" class="<?php notesil_post_class(); ?>">
				<h2 class="entry-title"><?php the_title(); ?></h2>
				<div class="entry-content">
					<?php the_content(); ?>
					<?php wp_link_pages( 'before=<div class="page-link">' . __( 'Pages:', 'notesil' ) . '&after=</div>' ); ?>
					<?php edit_post_link( __( 'Edit', 'notesil' ), '<p class="edit-link">', '</p>' ); ?>
				</div>
			</div><!-- .post -->
			<?php comments_template(); ?>
			<?php endwhile; // end of the loop. ?>
		</div><!-- #content -->
		<?php get_sidebar(); ?>
	</div><!-- #container -->
<?php get_footer(); ?>

Click on 'Update File' to save your changes to customfbpage.php then click on 'Pages' over on the left to list all of the pages including your Facebook app page. Click the 'Quick Edit' option for the FB app page and make sure it looks similar to this:

There are several key points here. First, make sure you pay attention to the 'slug' field. If you told Facebook to look for your Facebook app content at https://www.joeswebsite.com/fbpage/, then your slug should likely be 'fbpage' because that is where WordPress will display that page's content. Next, make sure the 'Template' is set to the 'Facebook Page' template which you just saved. Of course, the name will be different if you opted for a different template name. Finally, you probably don't want to allow comments. Maybe you do. I didn't, so I have that turned off. Once you are happy with your settings, click 'Update'. Now go back to the 'Appearance->Editor' panel and click on customfbpage.php again. Time to get nitty-gritty!

First, I'll just say that for my case, using a NotesIL based theme, my choices were simple. I removed the lines in the template which contained the following:

  • the_title()
  • wp_link_pages()
  • edit_post_link()
  • comments_template()
  • get_sidebar()
  • get_footer()

The title line wasn't necessary because the embedded content was going to be embedded within a Facebook page, so what was the point in including space to say "By the way, this content is for a Facebook page"? The wp_link_pages() line would have shown links to other pages which I didn't want to do. The edit_post_link() line would, if I were logged in and an admin, have displayed an 'Edit' link allowing me to start editing the content. It wouldn't have been visible to other users, but I would have seen it and I didn't want to, so I removed it. I can still edit the page by going through the 'Pages' section in the dashboard. The comments_template() shows any attached comments, but I have comments disabled, so that got removed. get_sidebar() and get_footer() show the sidebar and footer respectively, both things I wanted gone to save space. What you have in your page.php is going to determine what you need to remove from your customfbpage.php file.

But that wasn't it. I still needed to do some CSS customization. I also edited the style.css for my child theme and added the following:

.page-template-customfbpage-php div#header h1 {
width:520px;
}
.page-template-customfbpage-php div#blog-description {
width:520px;
}
.page-template-customfbpage-php div#container {
width:520px;
margin:0; padding:0; border:0;
}
.page-template-customfbpage-php div#content {
width:520px;
margin:0; padding:0; border:0;
}

This sets pertinent content blocks to a 520 pixel width with no margins, padding or border, something we need in order to make our FB page look alright. Once I had this in place, I was able to load my Facebook page up and load the tab and my content looked right as rain.

Now, there are some other pitfalls. One example is a link I included in the content of the FB page to our contact page. This caused the contact page to load in the embedded iframe, with all of the default look and nasty horizontal scrolling. To fix this, I altered the link to have a target of _top, which caused the link to load into the page as a whole, not just the iframe. So keep in mind that what your content is loading into is an iframe and be cognizant of  all that entails.

I hope you find this helpful. Let me know in the comments below or drop me a line by email.

4Nov/112

Facebook Page Tab, SSL and Hosted Websites

Here's the setup. My company, PNG Support, has a Facebook page and we wanted to be able to set up custom content there rather than just rely on using the wall to post links back to the main site. If you are unfamiliar with how to do this, there is an excellent write up here on setting up an iFrame Application on your Facebook Page. While that page provides some nifty details, for the purposes of our discussion here I will point out that it involves the following:

  • Have a website hosted somewhere
  • Make sure it has SSL enabled
  • Tell Facebook the URL to create the Facebook application
  • Link the application to your Facebook Page

What happens is that when someone visits your Facebook Page, specifically the tab you created for the custom content, Facebook sees it is an app and embeds an iframe linked to your secure (https) URL. At that point, the behavior is entirely determined by the client's browser. And that is where things can fall down if you are using the default SSL certificate that likely came with your hosted website.

If you are unfamiliar with SSL or https or secure webpages, think of it as a means of determining trust. When you visit a secure webpage, an SSL certificate is given to your browser. Like an actual certificate, it states who you are (e.g. PNG Support or Lynn Pye or Barack Obama or...) but it also states what domain the certificate was issued for. In other words, it not only says who you are supposed to be, but where the content is supposed to be coming from. In addition, these certificates have an expiration date. So the browser checks to see if the certificate is expired or is coming from a website which it is not issued for and alerts you to this. What does that mean? Suppose I get a certificate issued for www.pyehouse.com but I install it on www.pngsupport.com. When you visit www.pngsupport.com, your browser can, and should, complain bitterly that the certificate is not valid because of the domain mismatch. In most cases you are given the option of continuing anyway.

This can also come into play on the bulk hosted services. These services have multiple servers with each server configured to host many websites, sometimes several hundred or more. If the hosting provider offers SSL as a default freebie, it means they have a certificate for their server to provide basic SSL encryption when someone uses the https handler for your website. You see, that's the other part to SSL. Once the certificate is accepted, the browser can have an encrypted conversation with the server, making it far more difficult for someone to snoop on what you are browsing for.

In the case of my company, we use ICDSoft for our hosting provider, whom I highly recommend. In our particular case, we happen to be hosted on server261. This means that if you visit https://www.pngsupport.com (note the https) you will get a certificate for server261.com, your browser will gripe, you will likely be given the option of proceeding anyway and, if you do, you will see the site as it normally looks. That's because the certificate is issued for server261.com but you visited pngsupport.com.

Now what does this have to do with Facebook Pages and custom iframe apps? Well, for security reasons Facebook requires you to use https links. Like I said, it embeds your link in an iframe on the page. So if the security doesn't match up, your audience is going to get some nasty security warning embedded in your Facebook page instead of the luscious layout you perfected last night. At this point, you'd be tempted to start investigating purchasing options for your SSL certificate. I know I did. And when I started looking at the price tag (over $100/yr for the cheaper options), I realized I didn't like that option. Then I had a thought. The certificate covers anything hosted on server261.com. Was there a way to use the server261.com domain to reach our content and therefore get a certificate match and have everything play nice.

As it turns out, there is, at least with ICDSoft. Keep in mind this may not always work if the server is not configured for this. But for ICDSoft, if you have, for example, the pngsupport.com domain hosted on their server261.com server, you can reach your content at http://pngsupport.server261.com. Even better, you can get an encrypted link with https://pngsupport.server261.com with zero complaints from your browser. So then I went into the Facebook application's settings and replaced www.pngsupport.com with pngsupport.server261.com and everything worked flawlessly. The certificate was matching, the content was coming up as desired and all was right with the world.

Of course, then I decided I wanted to link to a WordPress page instead of a hand written bit of PHP. More on that another time.

3Oct/111

The Best Programming Tool… Ever

The best programming tool I've ever used was a whiteboard with dry erase markers. When I worked for IBM, we had a team of, oh, about 10 or so developers working on an internal project with usage spanning the globe. We turned out tight code on a tight schedule and we did it with relatively few errors and hell if we didn't come in under budget, just for kicks. We had a really great team of developers but one thing which I think had a big impact was having a huge whiteboard in every. single. office.

We were, at the time, technically part of the services division, even though we had absolutely no outside contact with customers. We provided purely internal benefit through our software. In a nutshell, we wrote the ticketing and billing software for the maintenance and support services for IBM. When you called IBM for support on your AS/400, we were the ones responsible for tracking that little guy and making sure you were billed appropriately. This involved working with several systems and a home brewed database (it was some time before we completed our migration to DB/2) but it was well done. We didn't get many complaints.

I don't know if it was due to being part of the services division or if it was something IBM was doing as a whole (I suspect the latter) but at the time, we were undergoing a management shift as well as a process management shift. Our management shift was moving to matrix management where projects had business managers and individuals had HR managers so you were reporting to two entities and.. well.. the less said the better. The process management involved weekly meetings, lots of status updates and tons of charts. But in spite of all of this, the whiteboard kept us on time and under budget.

Each office held two developers (or architect or team lead, whatever). The wall as you walked in was taken up by a massive whiteboard with two or three dry erase markers plus an eraser and maybe some whiteboard cleaner. You had to go the breakroom and grab some paper towels if you wanted to actually use the stuff.

Anyway, we used the hell out of those things. You could walk into any office and immediately know what they were working on by checking out the edges of the board, where long term task lists were kept. But the big plus were the middle areas, where technical notes and diagrams were kept. We would do object designs and data flows and leave them up for a month or so until things were complete. Need a gigantic scratch pad to work out the logic for your module? No problem, just stand up and walk over to the whiteboard and write down the contents of your mind.

Perhaps the lone problem was lack of space. Make no mistake, these whiteboards were huge, wide enough to span two cubicles. Still, if the problem was sticky enough, you wanted, nay, needed more space. And of course you were sharing with your officemate, so you had to be mindful of what space they needed. As it happened, we had a few extra rooms where we could use whiteboards unfettered by occupancy restrictions. It was a wonderful time.

Since then, I have been to many locations and seen many team sharing arrangements. The supremacy of the whiteboard has typically received at least a nod in the form of a shared whiteboard in a common area but never was worshipped as it was when I was at IBM.

Things have probably changed since then. Times certainly have. There are whiteboard software applications that allow users to draw to a shared space on their computer, but it's not the same. Using a mouse to try to draw programming diagrams and notes is like trying to build a ship in a bottle using salad tongs. And if you have a Wacom tablet or similar device you're probably not the focus of this post. No, there is no substitute for the real thing. The whiteboard is the king of programmer tools. Long live the king!

30Sep/110

Bad News For MiniStumble (StumbleUpon Safari Extension)

As you may be aware, I've been working on a StumbleUpon Safari extension to mimic the functionality of the Firefox plugin. I've written about it here on my blog, created a page devoted to the MiniStumble project and also attempted to gather input from the StumbleUpon forums.

Yesterday, my StumbleUpon forum posts were removed, the only message in the otherwise automated email being "not legal". I then submitted a request for more information on the StumbleUpon feedback page. The following is the response I received:

Your request (#######) has been updated.
You can add a comment by replying to this email.

[Name Redacted], Sep-30 05:17 pm (PDT):
Hello Lynn,

Thanks for writing to us,

While we appreciate your enthusiasm for StumbleUpon, unfortunately, we do not allow third parties to create StumbleUpon extensions and we do not recommend you to invest resources on building such an extension from us. Our legal team may be contacting you with more details.

Thank you for your interest!

Best,

[Name Redacted]
StumbleUpon Support Team

Read our blog: http://www.stumbleupon.com/sublog/
Be our friend on Facebook: https://www.facebook.com/StumbleUpon
Follow us on Twitter: http://twitter.com/#!/StumbleUpon
Check out our YouTube videos: http://www.youtube.com/user/stumbleupon

Stumbleupon, Sep-29 03:44 pm (PDT):
I'm developing a Safari extension for StumbleUpon based upon the code in the Firefox plugin. This would provide similar functionality to the web toolbar but because it doesn't embed the stumbled content into an iframe it is resistant to frame breaking logic. Additionally it would be always on rather than having to use the toolbar. You know all the reasons it is better to use a plugin/extension in lieu of the web toolbar.

I had posted some links on the forums in response to people asking for just this. Today they were all removed and the only text I got in the email was "not legal".

I would like to know if the implication is that there is an actual legal issue or if that was intended to just mean "not allowed on our forums".

Furthermore I would like to know if there is any chance that I could receive assistance on this, or perhaps offer my own assistance to you in developing a proper, StumbleUpon officially sanctioned Safari extension.

There is definitely desire for a Safari extension to use in place of the web toolbar with the obvious benefits.

I would absolutely love to hear back from you to help clarify things for me.

Regards,

Lynn

As you can see, the claim is being made that not only do they disapprove but that what I am doing is illegal. I'm not sure what exactly is illegal about it. I have my own ideas on why they are as protective as they are about outside parties working on something like this but regardless it would appear that I'm kicking against the goads as it were. As this was intended to be a free offering for myself and other Safari users, to improve upon the web toolbar experience, I have no inclination to fight StumbleUpon on this, especially if they plan to bring in lawyers.

I'm going to update the project page and remove the link to the extension itself and take it down as well. Had I known it would meet with this resistance I wouldn't have gotten anyone's hopes up on bringing this forth. My hope is that they will take the hint that this is really something that people want and that some are willing to invest time and effort into doing if it isn't done for them, and therefore build one themselves with all the bells and whistles. Based on what I see in the Firefox plugin, I think it is absolutely a manageable goal to get a similar capability going on Safari.

26Sep/110

Private API’s (Should You Use Them?)

In working on the StumbleUpon Safari extension, I came across requests in the SU support forums about SU providing an API for developers to create SU toolbars of their own. Currently there is a Firefox plugin provided by StumbleUpon but that's it as far as actual extensions available for SU functionality. They created a toolbar that essentially puts the stumbles into an iframe but that's fragile and can be broken by sites that have code expressly to break out of iframes. And there are other limitations too, limitations that the Firefox plugin lacks. Limitations which could be overcome in other browsers if only the private API used in the Firefox plugin were made available to all. I've stared tackling the task of breaking code out of the Firefox plugin for use elsewhere but I'm still left wondering whether I should.

Ask the Right Question

Keep in mind, there are two questions to ask here. One is whether it's a good idea. The other is whether I have a right to do so. Reusing a private API has some pretty nasty downsides. Since it is private, the interface isn't likely to be documented. You're going to be spending time looking at message traffic, poring over all of the usage in their code to see what sorts of return values are checked. And if the developers of the private API get upset over your usage of their API they could change things such that you will be forced to extensively redevelop your version just to keep up. In cases where, as with the StumbleUpon extension, communication with a server is required, it gets worse because now you're dealing with their servers and interfaces there which they define. In my case, I'm actually rewriting their private API, essentially recreating code to reuse their protocol. While it's unlikely they would change the protocol extensively, since that could alienate Firefox users, it's not outside the realm of possibility.

So why do it? If you're considering using a private API, it's likely because that is the only way to do what you want to do. Left with another choice, most developers would typically prefer to use a well documented public API if one is available since the drawbacks are far fewer. There are cases, though, where there are both public and private APIs available and the public API provides less power than the private APIs. This seems to tend to happen when the APIs are provided by a single vendor who also provides applications which make use of the APIs. Their apps can make use of the private APIs, resulting in more features than any third party app can provide.

Really, it seems like this is just a judgement call. Are you crazy enough to want to ride this train? Do you really want to risk pissing off the developers who created the private API, such that you end up having to rewrite your app from scratch just because they got a bee in their bonnet about its use? Heck, that could happen even if they don't know your name. It might have been a planned revision. The fact is, you have no promises. But if you want to create your project badly enough, maybe you're just crazy enough.

A Question of Right or Wrong

The other question is of the rightness or wrongness of using the private APIs. Of course there may be legal ramifications but that's not what I'm talking about. Let me give you an example. With MiniStumble, I'm using the StumbleUpon API to retrieve stumble URLs from their servers. That makes use of their bandwidth, CPU and power. This may be miniscule, but it all adds up. Additionally, consider that they make money off of what they call paid discovery, where advertisers can pay to inject a URL into the stumble lineup for certain categories. What happens, then, if some rogue extension grabs URLs and never reports back that they were visited? SU would not be able to report the proper number of visitors sent to that site which begins to mess with their revenue stream. That is not being a good neighbor.

As it happens, the SU servers appear not to serve up additional stumbles until previous stumbles are reported as having been visited, so there is some sanity checking that goes on, and I'm aware enough of at least that part of what SU does to try to avoid being a prick about things anyway, but it could have been a problem. The Firefox plugin for SU is not the most complex software I've seen, but the code paths are many and it is easy to lose track of where different pieces of functionality will wind up taking you. In the process of trying to rewrite or just reuse a private API, you could be underdelivering to the original API writer's customers. You might be creating problems for the API writer's business. In short, maybe you shouldn't have been doing what you are doing in the first place.

"First do no harm." The physician's creed. This business about doing this in the right is, frankly, the part that gives me the most bother. I think I'm doing it responsibly and I think I'm doing it well enough, making sure folks understand that when they grab my extension, they aren't getting something officially endorsed by StumbleUpon and so on. And the code is there. Granted, just because I can doesn't imply that I should. But then just because I could screw up doesn't mean that I will either. I expect this is also something that has to be considered on a case by case basis too. Apple actively checks for private API usage for any apps entering the App Store. SU doesn't have that option, but I've also posted in their forums about this, so other than sending emails directly to their dev team, I'm not sure what else I could do. I suppose if they want me to stop, they can just send me an email. Until then, I think I'll try to make use of what they've provided, albeit inadvertently. But I'm going to try to place nice while doing it.

Filed under: development No Comments
23Sep/110

More MiniStumble Hacking Fun (We Have v2.0!)

Just a quickie here. We have a version 2.0 of MiniStumble, the StumbleUpon Safari extension bar! You can find more details on the MiniStumble page.

21Sep/110

Fun With MiniStumble – Hacking a StumbleUpon Safari Extension

Image of stick figure stumbling over rockYou may not be aware that I've created a teensy little StumbleUpon Safari extension. If not, go check it out. Apple supposedly has approved it for inclusion in their gallery, but I've checked and I don't yet see it. Regardless, as the page says it is a minimally functional StumbleUpon Safari extension, only providing a few page redirects and using the badge API to query for the view count and page ID from their database. That's all well and good but you can't use it vote on things, and it offers no protection from iframe breakers. What's a stumbler to do!?

Well, keeping in mind that this whole mess is SOLELY being developed because for whatever reason the powers that be at StumbleUpon.com have yet to develop their own Safari extension, I've been hacking around with their Firefox extension, reverse engineering it in order to do what needs doing in mine. It's been fun and I'm making some headway but I'm not exactly sure when the next release will be. I have login capability working, but I haven't yet tested doing anything useful once logged in.

One thing I'm a little concerned about is the fragility of what it is I'm doing. There is no official API for any of this. Assuming for a moment I actually manage to rip the guts out of the Firefox extension and make it work for Safari, there's no guarantee the interface won't change later on. They might alter the FF functionality such that it uses different calls. They might break some single interface that causes my whole little project to fall to pieces. Because there is no official StumbleUpon API, I have no guarantees that any of this will continue working. But, I'm enjoying doing this little bit of hackery and it seems like something useful.

I should mention too that the Firefox extension developers were thorough. I was thinking of something quick and dirty, but there are quite a few corner cases dealt with in the code and as I start bringing things over, I'm becoming torn as to whether to try to rewrite the few bits I want to pull in or try to port the entire back end over. The pain of missing those corner cases and coding for them myself or the pain of suffering through roadblock after roadblock of making Firefox extension-compatible Javascript work in a Safari extension environment. I'm leaning toward the former though the latter would, I think, be a more thorough implementation. I don't doubt that the checks in the FF extension are necessary (like making sure to check for rapid clicking on the Stumble icon and not causing multiple async stumble requests), I may start off with a more fragile implementation in some respects, just to get it out there, followed by some touch up afterward to clean things up. I'll be honest, I'm not doing this with my usual forethought, mostly because I think at some level I still see it as a "small" project.

Anyhow, I just figured I would mention what's going on with it. Bear with me and mind the mess.

6Sep/110

Set Up Constant Speed in box2d

This brief tutorial will show you how to set up dynamic objects with a constant speed in box2d. It assumes you have a basic understanding of how to set up a box2d project with cocos2d.

As you are no doubt aware, the box2d physics engine is a wonderful tool for creating a virtual world filled with objects that interact in a way analagous to the real world. In this world gravity, friction (including rotational friction), inertia and momentum are all simulated.

But what of the concept of cruise control? You know, you set a speed and then the target continues to try to match that speed. You might control the vector direction in some other method, but the speed is intended to remain constant. How do you do that? That's what we're going to take a look out now.

Conceptually, what we want to do is measure our current speed on each update cycle and then fire an impulse of the appropriate size and direction in order to nudge us up to (or down to) speed. What we specifically do not want to do is simply call SetLinearVelocity(). Why not, you may ask. The problem is that doing so essentially tells the box2d engine "Hey, ignore whatever you *think* should be happening to that body. Here's the actual velocity." Instead, what we want to do is tell the box2d engine, "See that body over there? I want you to add this new impulse to it and factor that in along with everything else." This lets the box2d engine take the entire model and any ongoing interactions into account rather than dropping everything and running with the new values.

Because I'm taking code out of my game Centripetal, I don't have a full project with a demo set up to show you what I'm talking about. But I will pull out the pertinent bits and provide some illumination on what I'm doing.

Before we get started, remember that box2d is a physics simulator only. It does not display graphics. b2Body objects do have a UserData attribute which is a void* and which can therefore store a pointer to, for example, a CCSprite. Likewise, you can also create a CCSprite subclass which has a b2Body* member and thus the two could refer to one another. I will leave the pointer management concerns to you based on your own implementation.

In my case, I have a CCNode subclass which has member pointers to both the b2World and CCSprite objects.

@interface BodyNode : CCNode
{
	...
	b2Body* body;
	CCSprite* sprite;
	...
}
@property (readonly, nonatomic) b2Body* body;
@property (readonly, nonatomic) CCSprite* sprite;
...
@end

There's more to it, but that's enough to get us going here. Now let's focus on our CruiseControl object. We're going to subclass BodyNode for this and add a little to it:

@interface CruiseControl : BodyNode
{
	...
	float speed;
	...
}
@property (nonatomic) float speed;
...
@end

We've got a BodyNode subclass to which we have added a speed member. Why speed? Why not a b2Vec? We don't want a steady direction, just a steady rate of movement. We want the box2d engine to bounce us around and change our direction but we want to know just how fast we should be moving and try to nudge ourselves just enough, but in the current direction, in order to achieve that. Let's so how we do it:

-(id) init
{
	if ((self = [super init]))
	{
		...
		[self scheduleUpdate];
	}
	return self;
}

Okay, the first thing you'll see is that, among other things in init, I'm scheduling an update callback. This doesn't have to happen in init, but it's a convenient place to do so. Note that this is a subclass of BodyNode which has a pointer to the CCSprite we will ultimately be moving around in cocos2d. The update will occur on our BodyNode subclass and not directly on the CCSprite we contain.

-(void) update:(ccTime)delta
{
	b2Vec2 curvel = body->GetLinearVelocity();
	if (curvel.Length() < self.speed || curvel.Length() > self.speed + 0.25f)
	{
		float curspeed = curvel.Normalize();
		float velChange = self.speed - curspeed;
		float impulse = body->GetMass() * velChange;
		curvel *= impulse;
		body->ApplyLinearImpulse(curvel, body->GetPosition());
	}
}

There may be more going on inside your update method (there is in mine in fact), but what you see here is the nuts and bolts of the cruise control concept. We first retrieve the current velocity which is a vector with scale equal to current speed. We check that speed against our desired speed. If it is too low or if it is too high, we want to apply an impulse.

Note that I have a bit of a fudge factor. You are dealing with floating point numbers and the usual lack of precision that entails. You can play with your fudge factor as you like. Maybe you're okay with being a little slower but no faster. Maybe you don't mind a little wiggle room in either direction. You can alter that to your heart's content.

So if we need to apply an impulse, we first normalize our current vector of movement. That gives us the direction with a factor of 1.0f which conveniently allows us to reuse the vector by multiplying it by our speed to get what we need. We calculate our speed by simply subtracing the current speed from our desired speed. Note that if we are moving too fast, this gives us a negative value. This is important in the next step as we multiply by the body's mass to get the needed scale to apply to the vector to give us our new direction. In the case of excessive speed, this becomes a negative value which reverses the vector for purposes of applying the impulse. Finally, we apply the impulse to our body at its location, allowing the physics engine to nudge us enough to get us back to the correct level of speed.

Naturally, you can play around with this as much as you like. You can alter the scheduled update to call whichever method you prefer. You can alter the frequency of the scheduled callback too. Or if you prefer, you could conceivably eliminate the update callback on your BodyNode subclass by using the box2d processing loop to watch for your CruiseControl object and perform your check at that time. Regardless, you now have a simple method of setting up cruise control for your box2d objects.

An additional note concerning gravity: When developing Centripetal, I set the simulator up with no gravity as I was simulating a top down view of a frictionless surface. I didn't need gravity. The problem you will face when adding gravity is that if the gravity is intense enough compared to your desired speed, even with constant impulses to push the object along it won't be enough to counteract the gravitational pull between steps. So your object might end up slinking around on the bottom of your simulation view rather than moving about freely. If the gravity is low enough relative to the desired speed, then the steady stream of impulses coming each step should be enough to let you fly.

2Sep/110

cocos2d: Using Tilt and Calibration/Bias

If you have created a tilt-based game using the iOS accelerometer, one of the complaints you might have heard was about having to hold the device flat, face up, in order to keep the tilt centered. You hear people explain they would like to be able to at least hold it at a slight angle and still have it be playable.

Intuitively it seems obvious that there should be a way to allow for this, and as it turns out there is. What you need to do is calibrate the device to account for the level of tilt the player is comfortable with.

First, if you have used the accelerometer (whether in cocos2D or elsewhere) you know that you provide a hook for the following method:

-(void) accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration;

Then, when the accelerometer sends you a message, you will receive a UIAcceleration object which has four properties (assuming the device is lying flat on its back):

x - acceleration along the x-axis or side to side
y - acceleration along the y-axis or top to bottom
z - acceleration along the z-axis or up and down
timestamp - a precise time of when the acceleration event occurred

The wording might suggest that the acceleration implies movement, but instead think of it as how much tug there is on an invisible rubber ball at the center of the phone, based on gravity as well as movement. In that way, as you tilt your phone, the x, y and z values will change depending on which direction the ball would try to roll.

The values are bound to -1 to 1. So if the phone is tilted so that it is sitting vertically, the y value would be -1, and x and z would be 0 or neutral. Now tilt it to the left a bit and x starts going negative, with y increasing as you aren't tilting it straight down anymore. Eventually, once it is entirely horizontal, x is -1 and now y and z are 0.

Well, we want to recalibrate the tilt. Imagine that you've tilted your phone down a little so that y is at -0.5. Suppose that's the position your player wants to play in so that in that tilt their little avatar stands stock still, center screen. What we want is for the tilting to be remapped. Tilting back up to sitting flat on the back should now provide something akin to a tilt value of .5 and achieving tilt values of negative values should require tilting further down than the new neutral position our player picked.

You probably see where we're going with this. We're going to let our player tilt their phone and then tell us when it's at the new neutral position. We're then going to record the current amount of tilt (i.e. acceleration) and from then on subtract that tilt from acceleration values when calculating new positions.

One more thing though; we need to provide a calibration screen for our player. But once we have recorded the bias, it seems a bit silly to have to duplicate a bunch of code in order to build a new scene to utilize the tilt. What I'm going to show you are two (well, technically three) classes which you can customize for your own use but which might prove useful in putting a calibration screen in your own tilt based game.

Disclaimer: One of the classes, AcceleratableLayer, is based upon the GameScene class in the DoodleDrop example created by Steffen Itterheim and published in his book "Learn iPhone and iPad Cocos2D Game Development". Just as he allowed his code to be built upon with no strings attached, so do I for the purposes of this tutorial.

To begin with, let's look at the interface declaration for AcceleratableLayer:

@interface AcceleratableLayer : CCLayer {
    float biasX;
    float biasY;
    float lastAccelX;
    float lastAccelY;
    CGPoint playerVelocity;
    BOOL adjustForBias;
    float pctSlow;
}
@property (nonatomic) float biasX;
@property (nonatomic) float biasY;
@property (nonatomic) BOOL adjustForBias;
@property (nonatomic) float pctSlow;
-(CGPoint) adjustPositionByVelocity:(CGPoint)oldpos;
-(CGRect) allowableMovementArea;
+(float) biasX;
+(float) biasY;
+(void) setBiasX:(float)x;
+(void) setBiasY:(float)y;
@end

The biasX and biasY properties are what you expect. We could also do a Z bias easily enough but for cocos2D, we're only doing the first two D's 😉 These properties can be used to retrieve or set the bias.

The adjustForBias property is used to determine whether we want to turn off our tilt adjustment without actually zeroing out our stored bias.

The adjustPositionByVelocity function tells us the new position based on a combination of the old position, the amount of tilt, the recorded speed and bias adjustments.

The allowableMovementArea function will be used to fence in our movement.

The static bias methods are used to actually store and retrieve the bias values into and out of the NSUserDefaults.

Now let's take a look at the AcceleratableLayer implementation:

#import "AcceleratableLayer.h"
// You can alter this to prevent someone from calibrating for too much tilt
#define MAX_ACCEL_BIAS (0.5f)
#pragma mark AcceleratableLayer
@implementation AcceleratableLayer
@synthesize biasX, biasY, adjustForBias;
static NSString* NSD_BIASX = @"biasX";
static NSString* NSD_BIASY = @"biasY";
+(float) biasX
{
    return [[NSUserDefaults standardUserDefaults] floatForKey:NSD_BIASX];
}
+(float) biasY
{
    return [[NSUserDefaults standardUserDefaults] floatForKey:NSD_BIASY];
}
+(void) setBiasX:(float)x
{
    [[NSUserDefaults standardUserDefaults] setFloat:x forKey:NSD_BIASX];
    [[NSUserDefaults standardUserDefaults] synchronize];
}
+(void) setBiasY:(float)y
{
    [[NSUserDefaults standardUserDefaults] setFloat:y forKey:NSD_BIASY];
    [[NSUserDefaults standardUserDefaults] synchronize];
}
-(id) init
{
    if ((self = [super init]))
    {
        biasX = [AcceleratableLayer biasX];
        biasY = [AcceleratableLayer biasY];
        self.adjustForBias = YES;
    }
    return self;
}
// We will require a subclass
-(CGRect) allowableMovementArea
{
    [NSException exceptionWithName:@"MethodNotOverridden" reason:@"Must override this method" userInfo:nil];
    return CGRectZero;
}
-(void) accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
    // used for calibration
    lastAccelX = acceleration.x;
    lastAccelY = acceleration.y;
    lastAccelX = fmaxf(fminf(lastAccelX,MAX_ACCEL_BIAS),-MAX_ACCEL_BIAS);
    lastAccelY = fmaxf(fminf(lastAccelY,MAX_ACCEL_BIAS),-MAX_ACCEL_BIAS);
	// These three values control how the player is moved. I call such values "design parameters" as they
	// need to be tweaked a lot and are critical for the game to "feel right".
	// Sometimes, like in the case with deceleration and sensitivity, such values can affect one another.
	// For example if you increase deceleration, the velocity will reach maxSpeed faster while the effect
	// of sensitivity is reduced.
	// this controls how quickly the velocity decelerates (lower = quicker to change direction)
	float deceleration = 0.4f;
	// this determines how sensitive the accelerometer reacts (higher = more sensitive)
	float sensitivity = 6.0f;
	// how fast the velocity can be at most
	float maxVelocity = 10.0f;
	// adjust velocity based on current accelerometer acceleration (adjusting for bias)
    if (adjustForBias)
    {
        playerVelocity.x = playerVelocity.x * deceleration + (acceleration.x-biasX) * sensitivity;
        playerVelocity.y = playerVelocity.y * deceleration + (acceleration.y-biasY) * sensitivity;
    }
    else
    {
        playerVelocity.x = playerVelocity.x * deceleration + acceleration.x * sensitivity;
        playerVelocity.y = playerVelocity.y * deceleration + acceleration.y * sensitivity;
    }
    // we must limit the maximum velocity of the player sprite, in both directions (positive & negative values)
    playerVelocity.x = fmaxf(fminf(playerVelocity.x,maxVelocity),-maxVelocity);
    playerVelocity.y = fmaxf(fminf(playerVelocity.y,maxVelocity),-maxVelocity);
}
-(CGPoint) adjustPositionByVelocity:(CGPoint)oldpos
{
    CGPoint pos = oldpos;
	pos.x += playerVelocity.x;
    pos.y += playerVelocity.y;
	// Alternatively you could re-write the above 3 lines as follows. I find the above more readable however.
	// player.position = CGPointMake(player.position.x + playerVelocity.x, player.position.y);
	// The seemingly obvious alternative won't work in Objective-C! It'll give you the following error.
	// ERROR: lvalue required as left operand of assignment
	// player.position.x += playerVelocity.x;
	// The Player should also be stopped from going outside the allowed area
    CGRect allowedRect = [self allowableMovementArea];
	// the left/right border check is performed against half the player image's size so that the sides of the actual
	// sprite are blocked from going outside the screen because the player sprite's position is at the center of the image
	if (pos.x < allowedRect.origin.x) 	{ 		pos.x = allowedRect.origin.x;          		// also set velocity to zero because the player is still accelerating towards the border 		playerVelocity.x = 0; 	} 	else if (pos.x > (allowedRect.origin.x + allowedRect.size.width))
	{
		pos.x = allowedRect.origin.x + allowedRect.size.width;
		// also set velocity to zero because the player is still accelerating towards the border
		playerVelocity.x = 0;
	}
    if (pos.y < allowedRect.origin.y)     {         pos.y = allowedRect.origin.y;                  playerVelocity.y = 0;     }     else if (pos.y > (allowedRect.origin.y + allowedRect.size.height))
    {
        pos.y = allowedRect.origin.y + allowedRect.size.height;
        playerVelocity.y = 0;
    }
    return pos;
}
@end

That's a bit of meat with those potatoes. Let's chop it up a bit.

First, I want to point out the MAX_ACCEL_BIAS macro which is set to 0.5f. What this does for us is, as the name implies, lock the bias to a max of 0.5f in either direction. Consider for a moment what would happen if your user tilts their phone almost vertically. The y value will be near -1. Now you calibrate. They can't tilt their phone any further down in order to move the avatar downward on the screen. Meanwhile, tilting forward will result in moving upward with rocket like speed. Even letting the user go this far results in a little lagginess going downward. It becomes a matter of taste as to how much bias you want to let them introduce.

Next we have the static methods to set and retrieve bias via the NSUserDefaults system. If you feel like using a different method of stashing your player's bias settings, feel free to replace them here. The usage is pretty straightforward.

Next we have the -(id)init method where aside from the usual, we are grabbing any stored bias settings and stashing them in our local members as well as presuming we are adjusting for bias.

The next method is -(CGRect)allowableMovementArea and you will immediately notice it does nothing of any use whatsoever. In fact, it throws an NSException right off the bat and returns a CGPointZero just for good measure. What are we doing here? Unlike other languages, Objective C doesn't have a way to ensure that a class cannot be directly instantiated. You have to simply agree to do so. Just to be a little forceful about it, if you do instantiate an AcceleratableLayer object, it's going to blow up on you mighty quickly. This means that in order to make use of AcceleratableLayer, you must subclass it and specifically override this method with valid functionality.

So what is this method supposed to do? It is supposed to return a CGRect that describes the boundary outside of which the controlled object is not allowed to travel. This allows the later code to know when to stop increasing velocity and altering movement because you have reached a border location. There are some assumptions built in here. We use a CGRect, so with this code as is, you can't lock the movable object into another shaped area like a triangle or circle. Additionally, the code that adjusts movement and velocity does not take into account the dimensions of the CCSprite rectangle which represents the object being moved, so your CGRect should represent the area which the center of the moved object is bound within. That is, your CCSprite will likely overlap the edge of the returned CGRect so make sure it is small enough that the sprite is not clipped in a way you don't wish it to be. Once again, it must be stressed that you must subclass AcceleratableLayer and override this method with your own code to return your own CGRect.

This brings us to the -(void)accelerometer:(UIAccelerometer*)accelerometer didAccelerate:(UIAcceleration*)acceleration method. This is mostly identical to the original code Steffen Itterheim provided with a few alterations to make adjustments for bias. Adding code to accomodate the z value is as easy as it would appear. Note the use of the playerVelocity CGPoint value. AcceleratableLayer will maintain a constant update of the new velocity based on accelerometer callbacks without needing any further prompting.

Finally we reach -(CGPoint)adjustPositionByVelocity:(CGPoint)oldpos. This method gets called by subclasses in order to retrieve an updated position based on a combination of the previous position and the playerVelocity value being tracked. In my case, I perform this in the update:(ccTime) method which I schedule, but of course you can update this however you wish.

Okay, so that provides some core functionality, but how do we use it to actually do calibration? Glad you asked! First off, we're going to introduce a new CCScene subclass called CalibrationScene. For those keeping score at home, this is the second class I'm going to mention to you. Ready for the interface declaration?

@interface CalibrationScene : CCScene
{
}
+(id) scene;
@end

Exciting stuff, eh? The +(id)scene method, as you expect, creates a new CalibrationScene to be pushed onto the CCDirector. That's it.

Okay, so let's take a look at the implementation file for this guy:

#import "CalibrationScene.h"
#import "AcceleratableLayer.h"
#pragma mark CalibrationLayer
@interface CalibrationLayer : AcceleratableLayer {
    CCSprite* testsubject;
    CGPoint cenpt;
}
@end
@implementation CalibrationLayer
- (id) init
{
    if ((self = [super init]))
    {
        // We need the accelerometer
        self.isAccelerometerEnabled = YES;
        // You can create whatever CCSprite you want
        // For the purposes of this demo, I'm creating a simple red square on the fly
        GLubyte pixels[900][4]; // 60x60 square
		int i;
		for(i = 0; i < 900; i++) {
			pixels[i][0] = 0xFF; /* Red channel */
			pixels[i][1] = 0x00; /* Blue channel */
			pixels[i][2] = 0x00; /* Green channel */
			pixels[i][3] = 0xFF; /* Alpha channel */
		}
		CCTexture2D* myTexture = [[CCTexture2D alloc] initWithData: (void*) pixels
                                          pixelFormat: kTexture2DPixelFormat_RGBA8888
                                           pixelsWide: 30
                                           pixelsHigh: 30
                                          contentSize: CGSizeMake(30,30)];
        testsubject = [CCSprite spriteWithTexture:myTexture];
        // Let's start our test subject out in the center of the screen
        CGSize winSize = [[CCDirector sharedDirector] winSize];
        cenpt = ccp(winSize.width*0.5f,winSize.height*0.5f);
        testsubject.position = cenpt;
        [self addChild:testsubject];
        // And add a simple menu so we know whether we are Done, want to Calibrate to the current tilt, or Zero things
        // back out to normal
        CCMenuItemLabel* closeMenu = [CCMenuItemLabel
                                      itemWithLabel:[CCLabelTTF labelWithString:@"Done" fontName:@"Helvetica" fontSize:12.0f]
                                      target:self
                                      selector:@selector(closeScene)];
        CCMenuItemLabel* calibrateMenu = [CCMenuItemLabel
                                          itemWithLabel:[CCLabelTTF labelWithString:@"Calibrate" fontName:@"Helvetica" fontSize:12.0f]
                                          target:self
                                          selector:@selector(calibrate)];
        CCMenuItemLabel* zeroMenu = [CCMenuItemLabel
                                          itemWithLabel:[CCLabelTTF labelWithString:@"Zero" fontName:@"Helvetica" fontSize:12.0f]
                                          target:self
                                          selector:@selector(zero)];
        CCMenu* menu = [CCMenu menuWithItems:closeMenu, calibrateMenu, zeroMenu, nil];
        [menu alignItemsHorizontallyWithPadding:50];
        menu.position = ccp(winSize.width*0.5f, 25 /* arbitrary */);
        [self addChild:menu];
        // And finally, schedule an update callback
        [self scheduleUpdate];
    }
    return self;
}
// Remember, ANY class inheriting from AcceleratableLayer is going to have to
// override this method, which defines the allowable portion of the screen that
// a target is allowed to move into
-(CGRect) allowableMovementArea
{
	CGSize screenSize = [[CCDirector sharedDirector] winSize];
	float imageWidthHalved = [testsubject contentSize].width * 0.5f;
	float leftBorderLimit = imageWidthHalved;
	float rightBorderLimit = screenSize.width - imageWidthHalved;
    float imageHeightHalved = [testsubject contentSize].height * 0.5f;
    float topBorderLimit = screenSize.height - imageHeightHalved;
    float bottomBorderLimit = imageHeightHalved;
    return CGRectMake(leftBorderLimit, bottomBorderLimit, rightBorderLimit-leftBorderLimit, topBorderLimit-bottomBorderLimit);
}
// We just use the AcceleratableLayer method -(void)adjustPositionByVelocity: to
// set our test subject's new position
-(void) update:(ccTime)delta
{
    testsubject.position = [self adjustPositionByVelocity:testsubject.position];
}
// Only really useful if we're not the only scene in the app, which normally we won't be.
-(void) closeScene
{
    CCLOG(@"close the calibration scene");
    // Uncomment the following line ONLY if this scene is not the only remaining scene
    //[[CCDirector sharedDirector] popScene];
}
// Calibration is fairly straightforward... we adjust the bias based on how much
// we are currently tilted. Here we are saving it to NSUserDefaults via the
// AcceleratableLayer methods. We also push the test subject back to the center
// of the screen for further refinement if needed
-(void) calibrate
{
    float x = lastAccelX;
    float y = lastAccelY;
    self.biasX = x;
    self.biasY = y;
    // reposition test item to center
    testsubject.position = cenpt;
    // now save the prefs, which also sets the bias in the inputlayer if it needs it
    [AcceleratableLayer setBiasX:x];
    [AcceleratableLayer setBiasY:y];
}
// Zeroing means forcing the bias back to zero.
-(void) zero
{
    float x = 0;
    float y = 0;
    self.biasX = x;
    self.biasY = y;
    // reposition test item to center
    testsubject.position = cenpt;
    // now save the prefs, which also sets the bias in the inputlayer if it needs it
    [AcceleratableLayer setBiasX:x];
    [AcceleratableLayer setBiasY:y];
}
@end
#pragma mark CalibrationScene
@implementation CalibrationScene
+(id) scene
{
    return [[[self alloc] init] autorelease];
}
- (id)init
{
    self = [super init];
    if (self) {
        // Initialization code here.
        [self addChild:[CalibrationLayer node]];
    }
    return self;
}
- (void)dealloc
{
    [super dealloc];
}
@end

Whoa! That's a lot more than what you would expect from that tiny little interface right? Well, that's because there's actually an extra class defined and implemented in there, CalibrationLayer. That would be the third class I mentioned.

But let's start by pointing out that right there at the bottom of the implementation is the full implementation of CalibrationScene. And all it does is create and add as a child one CalibrationLayer. So in reality, the meat here is all in CalibrationLayer. Let's dig in!

To start with, CalibrationLayer has two members, a CCSprite we lovingly call testsubject, and CGPoint called cenpt. testsubject is the sprite that we will move around via tilt. cenpt is just a stashed copy of the center of the screen. No surprises here.

Opening up -(id)init we start off by enabling the accelerometer. Note that we actually didn't do that in the AcceleratableLayer init method. We probably could but it's fine either way. Just remember to enable it somewhere.

The next bit of code may look odd and it is the result of my wanting to not have to include an image in this tutorial or the project which will be available for download. I'm going to create a 30x30 red square as a sprite. I'll repeat the relevant code below:

        // You can create whatever CCSprite you want
        // For the purposes of this demo, I'm creating a simple red square on the fly
        GLubyte pixels[900][4]; // 60x60 square
		int i;
		for(i = 0; i < 900; i++) {
			pixels[i][0] = 0xFF; /* Red channel */
			pixels[i][1] = 0x00; /* Blue channel */
			pixels[i][2] = 0x00; /* Green channel */
			pixels[i][3] = 0xFF; /* Alpha channel */
		}
		CCTexture2D* myTexture = [[CCTexture2D alloc] initWithData: (void*) pixels
                                          pixelFormat: kTexture2DPixelFormat_RGBA8888
                                           pixelsWide: 30
                                           pixelsHigh: 30
                                          contentSize: CGSizeMake(30,30)];
        testsubject = [CCSprite spriteWithTexture:myTexture];

This isn't really the most pertinent code for this tutorial but I did want to make mention of it. Essentially, we construct the bytes to represent a red 30x30 bitmap with alpha channel. Then we pass those bytes into a texture object. Finally we pass that texture object in to create a new sprite on the fly.

Moving on, we calculate the center point of the screen, stash the value and put the testsubject there by setting its position attribute. We also add the testsubject to the layer.

Next we set up a menu at the bottom of the screen to allow us to either say we are done calibrating, we want to accept the current calibration and store it, or we want to zero out the calibration and start over from scratch.

Finally we call scheduleUpdate to we are getting our update method called each frame.

Next up is the implementation of our -(CGRect)allowableMovementArea method. Remember how I said that subclassing was required as was overriding of this method? Well, here's a sample implementation meant to lock you to .. anywhere on the screen. But note how I'm taking into account the size of the tracked sprite and using that to help define the CGRect.

The update method is pretty straight forward. We set the testsubject.position attribute to the result of calling adjustPositionByVelocity with the original testsubject.position. This calculates the new position based on how we've been tilting the device up til now.

The closeScene method is not terribly interesting. It gets invoked if the Done menu item is tapped. For now it doesn't do anything because popping the only scene tends to provide for a dull experience. You could uncomment that line if it's been pushed on top of another scene though and you would get the expected result.

The calibrate method is run when the user taps the Calibrate menu item. It grabs the previous acceleration (i.e. tilt) for x and y and pushes those values into the local bias members, sends the testsubject back to the center of the screen for possible further calibration, and then sets the user defaults with the new bias as well.

The zero method is identical to the calibrate method, but gets called when the Zero menu item is tapped and is hardcoded to storing 0 for the bias values. This represents restoring the bias back to the neutral state.

And that is it. Drop in the interface and implementation files for these classes and you could have your very own calibration scene along with a subclassable layer class that will provide this acceleration logic for you. Additionally by doing it this way, you guarantee that any tweaks you make in AcceleratableLayer to adjust your acceleration and deceleration curves, max velocities, max biases and other items will automatically be applied to any layer which subclasses it, making it easy to perform these adjustments in one place.

Have fun programming!

Also, if you're interested, you can download the full project here: CalibrationDemo

31Aug/110

Centripetal: Free for a Day!

PNG is making Centripetal free for a day!