[아이폰 앱 개발] OpenGL ES for iPhone : Part 3 with Accelerometer control

OpenGL ES for iPhone : Part 3 with Accelerometer control

In this part 3, we will add the accelerometer control to move the position of ellipse object that we have created in part 2 of the Tutorial.



1) UIAccelerometerDelegate
We need to add the UIAccelerometerDelegate protocol to the EAGLView and implement the accelerometer: didAccelerate: method as below


@interface EAGLView : UIView <UIAccelerometerDelegate>

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


We need to configure and start the accelerometer in the setupView method

[[UIAccelerometer sharedAccelerometer] setUpdateInterval:(1.0 / kAccelerometerFrequency)];
[[UIAccelerometer sharedAccelerometer] setDelegate:self];


2) Accelerometer values
Inside the accelerometer: didAccelerate: method, we add a low-pass filter in the accelerometer values. This low-pass filter codes are sourced from the GLGravity Sample Code from Apple.

//Use a basic low-pass filter in the accelerometer values
accel[0] = acceleration.x * kFilteringFactor + accel[0] * (1.0 - kFilteringFactor);
accel[1] = acceleration.y * kFilteringFactor + accel[1] * (1.0 - kFilteringFactor);
accel[2] = acceleration.z * kFilteringFactor + accel[2] * (1.0 - kFilteringFactor);


The meaning of accelerometer values:

acceleration.x = Roll. It corresponds to roll, or rotation around the axis that runs from your home button to your earpiece. Values vary from 1.0 (rolled all the way to the right) to -1.0 (rolled all the way to the left).

acceleration.y = Pitch. Place your iPhone on the table and mentally draw a horizontal line about half-way down the screen. That's the axis around which the Y value rotates. Values go from 1.0 (the headphone jack straight down) to -1.0 (the headphone jack straight up).

acceleration.z = Face up/face down. It refers to whether your iPhone is face up (-1.0) or face down (1.0). When placed on it side, either the side with the volume controls and ringer switch, or the side directly opposite, the Z value equates to 0.0.

3) Control on movement of the ellipse is using the variables moveX and moveY and the ellipse position will be changed according to acceleration.x (that is accel[0]) and acceleration.y (that is accel[1]) values that passed from the Accelerometer control after the low-pass filter. The larger the absolute value of acceleration.x/acceleration.y, the greater for the magnitude for the value of moveX/moveY and thus the faster the ellipse will change its position to that direction. As the object should not move beyond the screen view, the ellipseData.pos.x and ellipseData.pos.y values will be governed by the boundaries of the screen.

 ellipseData.pos.x += moveX;
 if (accel[0] > -0.1 & accel[0] < 0.1 ) {
   moveX = 0.0f;
 }
 else {
  moveX = 10.0f * accel[0];
 }

 ellipseData.pos.y += moveY;
 if (accel[1] > -0.1 & accel[1] < 0.1 ) {
   moveY = 0.0f;
 }
 else {
   moveY = -10.0f * accel[1];
 }


4) Conditional compilation code for the iPhone Simulator and on-screen debug info
As iPhone Simulator does not have Accelerometer control, we have added the code that will change the ellipse position inside this compiler directive, so that the ellipse will keep moving on the iPhone Simulator.
  #if TARGET_IPHONE_SIMULATOR 

Moroever, we have added a UILabel to the code so that we can read the Accelerometer values while we debug the program on actual device. This UILabel can be disabled using this define directive.
  #undef DEBUGSCREEN

5) The source codes are here, you just need to create a new project from OpenGL ES Application template of XCode and copy the source codes of EAGLView.h and EAGLView.m from below and paste them for Build & Go in XCode. The accelerometer control can only be tested on actual device.



EAGLView.h Select all

// EAGLView.h
// OpenGL ES Tutorial - Part 3 by javacom


// To enable Debug NSLog, add GCC_PREPROCESSOR_DEFINITIONS DEBUGON in Project Settings for Debug Build Only and replace NSLog() with DEBUGLOG()
#ifdef DEBUGON
#define DEBUGLOG if (DEBUGON) NSLog
#else
#define DEBUGLOG
#endif

#define DEBUGSCREEN

#import <UIKit/UIKit.h>
#import <OpenGLES/EAGL.h>
#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>

typedef struct
{
BOOL rotstop; // stop self rotation
BOOL touchInside; // finger tap inside of the object ?
BOOL scalestart; // start to scale the obejct ?
CGPoint pos; // position of the object on the screen
CGPoint startTouchPosition; // Start Touch Position
CGPoint currentTouchPosition; // Current Touch Position
GLfloat pinchDistance; // distance between two fingers pinch
GLfloat pinchDistanceShown; // distance that have shown on screen
GLfloat scale; // OpenGL scale factor of the object
GLfloat rotation; // OpenGL rotation factor of the object
GLfloat rotspeed; // control rotation speed of the object
} ObjectData;

/*
This class wraps the CAEAGLLayer from CoreAnimation into a convenient UIView subclass.
The view content is basically an EAGL surface you render your OpenGL scene into.
Note that setting the view non-opaque will only work if the EAGL surface has an alpha channel.
*/
@interface EAGLView : UIView {

@private
/* The pixel dimensions of the backbuffer */
GLint backingWidth;
GLint backingHeight;

EAGLContext *context;

/* OpenGL names for the renderbuffer and framebuffers used to render to this view */
GLuint viewRenderbuffer, viewFramebuffer;

/* OpenGL name for the depth buffer that is attached to viewFramebuffer, if it exists (0 if it does not exist) */
GLuint depthRenderbuffer;

NSTimer *animationTimer;
NSTimeInterval animationInterval;

@public
ObjectData squareData;
ObjectData ellipseData;
GLfloat ellipseVertices[720];
CGFloat initialDistance;
UIAccelerationValue accel[3];
GLfloat moveX, moveY;
#ifdef DEBUGSCREEN
UILabel *textView;
#endif
}

@property NSTimeInterval animationInterval;

@property (nonatomic) ObjectData squareData;
@property (nonatomic) ObjectData ellipseData;
@property CGFloat initialDistance;
#ifdef DEBUGSCREEN
@property (nonatomic, assign) UILabel *textView;
#endif

- (void)startAnimation;
- (void)stopAnimation;
- (void)drawView;
- (void)setupView;

@end


EAGLView.m Select all

// EAGLView.m
// OpenGL ES Tutorial - Part 3 by javacom
//
#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/EAGLDrawable.h>

#import "EAGLView.h"

#include <math.h>

// Macros
#define degreesToRadians(__ANGLE__) (M_PI * (__ANGLE__) / 180.0)
#define radiansToDegrees(__ANGLE__) (180.0 * (__ANGLE__) / M_PI)

CGFloat distanceBetweenPoints (CGPoint first, CGPoint second) {
CGFloat deltaX = second.x - first.x;
CGFloat deltaY = second.y - first.y;
return sqrt(deltaX*deltaX + deltaY*deltaY );
};

CGFloat angleBetweenPoints(CGPoint first, CGPoint second) {
// atan((top - bottom)/(right - left))
CGFloat rads = atan((second.y - first.y) / (first.x - second.x));
return radiansToDegrees(rads);
}

CGFloat angleBetweenLines(CGPoint line1Start, CGPoint line1End, CGPoint line2Start, CGPoint line2End) {

CGFloat a = line1End.x - line1Start.x;
CGFloat b = line1End.y - line1Start.y;
CGFloat c = line2End.x - line2Start.x;
CGFloat d = line2End.y - line2Start.y;

CGFloat rads = acos(((a*c) + (b*d)) / ((sqrt(a*a + b*b)) * (sqrt(c*c + d*d))));

return radiansToDegrees(rads);
}

#define USE_DEPTH_BUFFER 0

// CONSTANTS
#define kMinimumTouchLength 30
#define kMaximumScale 7.0f
#define kMinimumPinchDelta 15
#define kAccelerometerFrequency 100.0 // Hz
#define kFilteringFactor 0.1


// A class extension to declare private methods
@interface EAGLView ()

@property (nonatomic, retain) EAGLContext *context;
@property (nonatomic, assign) NSTimer *animationTimer;

- (BOOL) createFramebuffer;
- (void) destroyFramebuffer;

@end


@implementation EAGLView

@synthesize context;
@synthesize animationTimer;
@synthesize animationInterval;
@synthesize squareData;
@synthesize ellipseData;
@synthesize initialDistance;
#ifdef DEBUGSCREEN
@synthesize textView;
#endif

// You must implement this method
+ (Class)layerClass {
return [CAEAGLLayer class];
}


//The GL view is stored in the nib file. When it's unarchived it's sent -initWithCoder:
- (id)initWithCoder:(NSCoder*)coder {

if ((self = [super initWithCoder:coder])) {

// Get the layer
CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;

eaglLayer.opaque = YES;
eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];

context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];

if (!context || ![EAGLContext setCurrentContext:context]) {
[self release];
return nil;
}

animationInterval = 1.0 / 60.0;
[self setupView];
}
return self;
}

// These are four methods touchesBegan, touchesMoved, touchesEnded, touchesCancelled and use to notify about touches and gestures

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/*
NSUInteger numTaps = [[touches anyObject] tapCount]; // number of taps
NSUInteger numTouches = [touches count]; // number of touches
*/
UITouch *touch = [[touches allObjects] objectAtIndex:0];

DEBUGLOG(@"TouchBegan event counts = %d ",[[event touchesForView:self] count]);
DEBUGLOG(@"TouchBegan tounches counts = %d ",[touches count]);
if ([touches count]== 2) {
NSArray *twoTouches = [touches allObjects];
UITouch *first = [twoTouches objectAtIndex:0];
UITouch *second = [twoTouches objectAtIndex:1];
initialDistance = distanceBetweenPoints([first locationInView:self], [second locationInView:self]);
squareData.rotstop = YES;
squareData.touchInside = NO;
}
else if ([touches count]==[[event touchesForView:self] count] & [[event touchesForView:self] count] == 1) {
squareData.startTouchPosition = [touch locationInView:self];
if (distanceBetweenPoints([touch locationInView:self], squareData.pos) <= kMinimumTouchLength * squareData.scale) {
DEBUGLOG(@"Square Touch at %.2f, %.2f ",squareData.pos.x,squareData.pos.y);
squareData.rotstop = YES;
squareData.touchInside = YES;
}
}

}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[touches allObjects] objectAtIndex:0];
squareData.currentTouchPosition = [touch locationInView:self];
if ([touches count]== 2) {
NSArray *twoTouches = [touches allObjects];
UITouch *first = [twoTouches objectAtIndex:0];
UITouch *second = [twoTouches objectAtIndex:1];

// Calculate the distance bewtween the two fingers(touches) to determine the pinch distance
CGFloat currentDistance = distanceBetweenPoints([first locationInView:self], [second locationInView:self]);

squareData.rotstop = YES;
squareData.touchInside = NO;

if (initialDistance == 0.0f)
initialDistance = currentDistance;
if (currentDistance - initialDistance > kMinimumPinchDelta) {
squareData.pinchDistance = currentDistance - initialDistance;
squareData.scalestart = YES;
DEBUGLOG(@"Outward Pinch %.2f", squareData.pinchDistance);
}
else if (initialDistance - currentDistance > kMinimumPinchDelta) {
squareData.pinchDistance = currentDistance - initialDistance;
squareData.scalestart = YES;
DEBUGLOG(@"Inward Pinch %.2f", squareData.pinchDistance);
}
}
else if ([touches count]==[[event touchesForView:self] count] & [[event touchesForView:self] count] == 1) {
if (squareData.touchInside) {
// Only move the square to new position when touchBegan is inside the square
squareData.pos.x = [touch locationInView:self].x;
squareData.pos.y = [touch locationInView:self].y;
DEBUGLOG(@"Square Move to %.2f, %.2f ",squareData.pos.x,squareData.pos.y);
squareData.rotstop = YES;
}
}
}


- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if ([touches count] == [[event touchesForView:self] count]) {
initialDistance = squareData.pinchDistanceShown = squareData.pinchDistance = 0.0f;
squareData.rotstop = squareData.touchInside = squareData.scalestart = NO;
DEBUGLOG(@"touchesEnded, all fingers up");
}
else {
initialDistance = squareData.pinchDistanceShown = squareData.pinchDistance = 0.0f;
squareData.scalestart = NO;
DEBUGLOG(@"touchesEnded");
}
}


- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
initialDistance = squareData.pinchDistanceShown = squareData.pinchDistance = 0.0f;
squareData.rotstop = squareData.touchInside = squareData.scalestart = NO;
DEBUGLOG(@"touchesCancelled");
}

- (void)setupView { // new method for intialisation of variables and states

// Enable Multi Touch of the view
self.multipleTouchEnabled = YES;

//Configure and start accelerometer
[[UIAccelerometer sharedAccelerometer] setUpdateInterval:(1.0 / kAccelerometerFrequency)];
[[UIAccelerometer sharedAccelerometer] setDelegate:self];
#if TARGET_IPHONE_SIMULATOR
moveX = 2.0f;
moveY = 3.0f;
#else
moveX = 0.0f;
moveY = 0.0f;
#endif

#ifdef DEBUGSCREEN
UIColor *bgColor = [[UIColor alloc] initWithWhite:1.0f alpha:0.0f];
textView = [[UILabel alloc] initWithFrame:CGRectMake(10.0f, 350.0f, 300.0f, 96.0f)];
textView.text = [NSString stringWithFormat:@"-Accelerometer Data-"];
textView.textAlignment = UITextAlignmentLeft;
[textView setNumberOfLines:4];
textView.backgroundColor = bgColor;
textView.font = [UIFont fontWithName:@"Arial" size:18];
[self addSubview:textView];
[self bringSubviewToFront:textView];
#endif


// Initialise square data
squareData.rotation = squareData.pinchDistance = squareData.pinchDistanceShown = 0.0f;
ellipseData.rotation = 0.0f;
squareData.scale = 1.0f;
squareData.rotstop = squareData.touchInside = squareData.scalestart = NO;
squareData.pos.x = 160.0f;
squareData.pos.y = 240.0f;
squareData.pinchDistance = 0.0f;
squareData.rotspeed = 1.0f;

// Initialise ellipse data
ellipseData.rotation = 0.0f;
ellipseData.rotstop = ellipseData.touchInside = ellipseData.scalestart = NO;
ellipseData.pos.x = 160.0f;
ellipseData.pos.y = 100.0f;
ellipseData.rotspeed = -4.0f;

// calculate the vertices of ellipse
const GLfloat xradius = 35.0f;
const GLfloat yradius = 25.0f;
for (int i = 0; i < 720; i+=2) {
ellipseVertices[i] = (cos(degreesToRadians(i)) * xradius) + 0.0f;
ellipseVertices[i+1] = (sin(degreesToRadians(i)) * yradius) + 0.0f;
// DEBUGLOG(@"ellipseVertices[v%d] %.1f, %.1f",i, ellipseVertices[i], ellipseVertices[i+1]);
}

// setup the projection matrix
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

// Setup Orthographic Projection for the 320 x 480 of the iPhone screen
glOrthof(0.0f, 320.0f, 480.0f, 0.0f, -1.0f, 1.0f);
glMatrixMode(GL_MODELVIEW);

}

- (void)drawView {

// Define the square vertices
const GLfloat squareVertices[] = {
-20.0f, -20.0f,
20.0f, -20.0f,
-20.0f, 20.0f,
20.0f, 20.0f,
};

// Define the colors of the square vertices
const GLubyte squareColors[] = {
255, 255, 0, 255,
0, 255, 255, 255,
0, 0, 0, 0,
255, 0, 255, 255,
};


// Define the colors of the ellipse vertices
const GLubyte ellipseColors[] = {
233, 85, 85, 255,
233, 85, 85, 255,
233, 85, 85, 255,
233, 85, 85, 255,
233, 85, 85, 255,
};


[EAGLContext setCurrentContext:context];
glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
glViewport(0, 0, backingWidth, backingHeight);

// Clear background color
glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);

// draw the square
glLoadIdentity();
glTranslatef(squareData.pos.x, squareData.pos.y, 0.0f);
glRotatef(squareData.rotation, 0.0f, 0.0f, 1.0f);
glScalef(squareData.scale, squareData.scale, 1.0f);
glVertexPointer(2, GL_FLOAT, 0, squareVertices);
glColorPointer(4, GL_UNSIGNED_BYTE, 0, squareColors);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

// draw the ellipse
glLoadIdentity();
glTranslatef(ellipseData.pos.x, ellipseData.pos.y, 0.0f);
glRotatef(ellipseData.rotation, 0.0f, 0.0f, 1.0f);
glVertexPointer(2, GL_FLOAT, 0, ellipseVertices);
glColorPointer(4, GL_UNSIGNED_BYTE, 0, ellipseColors);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glDrawArrays(GL_TRIANGLE_FAN, 0, 360); // the ellipse has 360 vertices

// control the square rotation
if (!squareData.rotstop) {
squareData.rotation += squareData.rotspeed;
if(squareData.rotation > 360.0f)
squareData.rotation -= 360.0f;
else if(squareData.rotation < -360.0f)
squareData.rotation += 360.0f;
}

// control the ellipse rotation
if (!ellipseData.rotstop) {
ellipseData.rotation += ellipseData.rotspeed;
if(ellipseData.rotation > 360.0f)
ellipseData.rotation -= 360.0f;
else if(ellipseData.rotation < -360.0f)
ellipseData.rotation += 360.0f;
}

// control the square scaling
if (squareData.scalestart && squareData.scale <= kMaximumScale) {
GLfloat pinchDelta = squareData.pinchDistance - squareData.pinchDistanceShown;
if (squareData.pinchDistance != 0.0f) {
squareData.scale += pinchDelta/30;
squareData.pinchDistanceShown = squareData.pinchDistance;
if (squareData.scale >= kMaximumScale) {
squareData.scale = kMaximumScale;
squareData.pinchDistanceShown = squareData.pinchDistance = 0.0f;
squareData.scalestart = NO;
} else if (squareData.scale <= 1.0f) {
squareData.scale = 1.0f;
squareData.pinchDistanceShown = squareData.pinchDistance = 0.0f;
squareData.scalestart = NO;
}
DEBUGLOG(@"scale is %.2f",squareData.scale);
}
}

// control the ellipse movement
#if TARGET_IPHONE_SIMULATOR
ellipseData.pos.x += moveX;
if (ellipseData.pos.x >= 290.f) {
moveX = -2.0f;
}
else if (ellipseData.pos.x <= 30.f) {
moveX = 2.0f;
}

ellipseData.pos.y += moveY;
if (ellipseData.pos.y >= 450.f) {
moveY = -1.5f;
}
else if (ellipseData.pos.y <= 55.f) {
moveY = 3.5f;
}
#else
ellipseData.pos.x += moveX;
if (accel[0] > -0.1 & accel[0] < 0.1 ) {
moveX = 0.0f;
}
else {
moveX = 10.0f * accel[0];
}

ellipseData.pos.y += moveY;
if (accel[1] > -0.1 & accel[1] < 0.1 ) {
moveY = 0.0f;
}
else {
moveY = -10.0f * accel[1];
}
#endif
if (ellipseData.pos.x >= 290.f) {
ellipseData.pos.x = 290.0f;
}
else if (ellipseData.pos.x <= 30.f) {
ellipseData.pos.x = 30.0f;
}
if (ellipseData.pos.y >= 450.f) {
ellipseData.pos.y = 450.0f;
}
else if (ellipseData.pos.y <= 55.f) {
ellipseData.pos.y = 55.0f;
}


glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER_OES];
}

- (void)accelerometer:(UIAccelerometer*)accelerometer didAccelerate:(UIAcceleration*)acceleration
{
/*
The meaning of acceleration values for firmware 2.x
acceleration.x = Roll. It corresponds to roll, or rotation around the axis that runs from your home button to your earpiece.
Values vary from 1.0 (rolled all the way to the right) to -1.0 (rolled all the way to the left).

acceleration.y = Pitch. Place your iPhone on the table and mentally draw a horizontal line about half-way down the screen.
That's the axis around which the Y value rotates.
Values go from 1.0 (the headphone jack straight down) to -1.0 (the headphone jack straight up).

acceleration.z = Face up/face down.
It refers to whether your iPhone is face up (-1.0) or face down (1.0).
When placed on it side, either the side with the volume controls and ringer switch, or the side directly opposite
, the Z value equates to 0.0.
*/

//Use a basic low-pass filter in the accelerometer values
accel[0] = acceleration.x * kFilteringFactor + accel[0] * (1.0 - kFilteringFactor);
accel[1] = acceleration.y * kFilteringFactor + accel[1] * (1.0 - kFilteringFactor);
accel[2] = acceleration.z * kFilteringFactor + accel[2] * (1.0 - kFilteringFactor);

#ifdef DEBUGSCREEN
textView.text = [NSString stringWithFormat:
@"X (roll, %4.1f%%): %f\nY (pitch %4.1f%%): %f\nZ (%4.1f%%) : %f",
100.0 - (accel[0] + 1.0) * 50.0, accel[0],
100.0 - (accel[1] + 1.0) * 50.0, accel[1],
100.0 - (accel[2] + 1.0) * 50.0, accel[2]
];
#endif
}

- (void)layoutSubviews {
[EAGLContext setCurrentContext:context];
[self destroyFramebuffer];
[self createFramebuffer];
[self drawView];
}


- (BOOL)createFramebuffer {

glGenFramebuffersOES(1, &viewFramebuffer);
glGenRenderbuffersOES(1, &viewRenderbuffer);

glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
[context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(CAEAGLLayer*)self.layer];
glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);

glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);

if (USE_DEPTH_BUFFER) {
glGenRenderbuffersOES(1, &depthRenderbuffer);
glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
}

if(glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
DEBUGLOG(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
return NO;
}

return YES;
}


- (void)destroyFramebuffer {

glDeleteFramebuffersOES(1, &viewFramebuffer);
viewFramebuffer = 0;
glDeleteRenderbuffersOES(1, &viewRenderbuffer);
viewRenderbuffer = 0;

if(depthRenderbuffer) {
glDeleteRenderbuffersOES(1, &depthRenderbuffer);
depthRenderbuffer = 0;
}
}


- (void)startAnimation {
self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES];
}


- (void)stopAnimation {
self.animationTimer = nil;
}


- (void)setAnimationTimer:(NSTimer *)newTimer {
[animationTimer invalidate];
animationTimer = newTimer;
}


- (void)setAnimationInterval:(NSTimeInterval)interval {

animationInterval = interval;
if (animationTimer) {
[self stopAnimation];
[self startAnimation];
}
}


- (void)dealloc {

[self stopAnimation];

if ([EAGLContext currentContext] == context) {
[EAGLContext setCurrentContext:nil];
}

[context release];
[super dealloc];
}

@end

.http://iphonesdkdev.blogspot.com/2009/04/opengl-es-for-iphone-part-3-with.html
.
Posted by 오늘마감
How to override UIWebView links request action with your own custom method

How to override UIWebView links request action with your own custom method

Ivan Kalaica

If you have a Web View in your view you can call a custom action when the user taps on a link in that UIWebView instance.

Here’s how…

Set the delegate of that UIWebView class instance on your instance of UIViewController class. Now just copy & paste the following code into your class.  This code implements the UIWebView instance and adds a call to the custom action (method).

- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType {
if(navigationType == UIWebViewNavigationTypeLinkClicked) {
if (overrideLinksSwitch.on == TRUE) {
[self myMethodAction];
[myWebView stopLoading];
return YES;
}
else {
<>return YES;
}
}
return YES;
}

You can download an example app here.

http://surgeworksmobile.com/iphone/how-to-override-uiwebview-links-request-action-with-your-own-custom-method
Posted by 오늘마감
alert with 2buttons

Alert View and Url

May 9, 2010 1:30 AM


- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {     [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"http://www.apple.com"]]; } -(void)awakeFromNib {         UIAlertView *baseAlert = [[UIAlertView alloc]                               initWithTitle:@"Url" message:@"Do you want to go to this website ?"                               delegate:self cancelButtonTitle:@"Yes"                               otherButtonTitles: @"No", nil];     [baseAlert self];     [baseAlert show];     [baseAlert release]; } 
Hello,
The Alert have Two buttons,
when the customer touch yes he must go to the website
but my problem is that know if he touch "no" he still go to the url
thanks for your help
In response to valicoldoklyne on May 10, 2010 3:41 PM

If your second button cancels the UIAlert View, the declaration of the Alert View needs to look like this.

UIAlertView *endAlert = [[UIAlertView alloc]
                                   initWithTitle:@"Game Over"
                                   message:@"This is the message"
                                   delegate:self
                                   cancelButtonTitle:@"Cancel"
                                   otherButtonTitles:@"Go to Apple's website", nil];

or if the second button performs a function,

UIAlertView *endAlert = [[UIAlertView alloc]
                                   initWithTitle:@"Game Over"
                                   message:@"This is the message"
                                   delegate:self
                                   cancelButtonTitle:nil
                                   otherButtonTitles:@"Restart", @"Go to Apple's website", nil];

and the void should be

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
    if (buttonIndex == 0) {

           //Restart Code


    }
    if (buttonIndex == 1) {

            //Go to Apple's website

   }
    
}

In response to Thomas Messner on May 10, 2010 4:08 PM

Just one suggestion, you might want to use cancelButtonIndex and strings, rather than those index constants:

    if (buttonIndex == alertView.cancelButtonIndex) {

        ......

    } else {

      NSString* l = [alertView buttonTitleAtIndex: buttonIndex];

      if ([l isEqualToString:@"xxxxx"]) {


etc.

This is more robust if you re-order the buttons, or if the indexes change (for example the actionSheet on the iPad doesn't have a cancel button).

Posted by 오늘마감
[cocos2D] 2D Scrolling Game with Cocos2D TileMap with Zoom!

A common question I see on the cocos2d forums is ‘when I want to make my game scroll, do I move the camera or the layer?’ or some variant of that. I also got some more detailed questions about how to make a functioning 2D scroller, so I’m going to described how I got it to work.

First of all, the answer to the above question is you move the layer. If you follow the examples in the cocos2d download, you have a “GameScene” and a “GameLayer”. Well, everything that needs to be moved when your game scrolls should be added as a child to that GameLayer. If you are using a TileMap as a background, this includes that tilemap. The only thing that you don’t add as a child to the GameLayer is stuff that does not move with the scrolling view, such as your HUDLayer that has text that shows your characters health. Other than that, your character, the background, other characters, should all be added to the GameLayer.

You have to remember which objects are absolute (attached to your GameScene or other layers) versus those that are relative (a child of your GameLayer) when you set up your touch handling code. For my HUD that has buttons you can press at any time, say to pause the game, you want to add the touch handling object to your GameScene or HUDLayer class, since it doesn’t move. But if you want to be able to touch objects that scroll along with your view in the game itself, your touch handling code needs to be in an object that is a child of your GameLayer.

This might be a little confusing, so let’s see some code:

gameLayer = [GameLayer node]; [gameScene addChild:gameLayer z:zOrder_GameLayer];   hudLayer = [HUDLayer node]; [gameScene addChild:hudLayer z:zOrder_HudLayer];   tileMap = [BGTileMap node]; [gameLayer addChild:tileMap z:-1];   [gameScene addChild:[PauseGameButton node] z:zOrder_GameButtons]; [gameLayer addChild:[FireGunAtTouchPoint node]];

The pause game button is always on your screen, while the point at which your character fires the gun depends on how how far your view has been scrolled (by moving GameLayer).

Let’s see some of the code that actually moves this game layer:

- (void)setViewpointCenter:(CGPoint)point { CGPoint centerPoint = ccp(240, 160); viewPoint = ccpSub(centerPoint, point);   // dont scroll so far so we see anywhere outside the visible map which would show up as black bars if(point.x &lt; centerPoint.x) viewPoint.x = 0; if(point.y &lt; centerPoint.y) viewPoint.y = 0;   // while zoomed out, don't adjust the viewpoint if(!isZoomedOut) gameLayer.position = viewPoint; }

When do you call that method? Well, it depends on what you want, but generally these scrolling games follow around the movement of your ‘main’ character, right? So whatever character the you want to follow, add this to override the standard CocosNode setPosition method so you update your viewpoint whenever the character moves

- (void)setPosition:(CGPoint)point { [[StandardGameController sharedSingleton] setViewpointCenter:point]; [super setPosition:point]; }

Note that the StandardGameController is a construct of mine that I use to separate the game logic out from the display code. It doesn’t matter exactly how you do it, you just need a way to have your main character object call back to something that contains a reference to GameLayer so it can adjust the position of your GameLayer.

Now remember, for your background to scroll properly, you need to add your background tileMap as a child of your GameLayer that is being moved around.

That being said, I found that an important method was missing from the cocos2d tilemap that I need to use in order to detect collisions based on the types of tiles encountered. I created a subclass of TMXTiledMap and added in these methods:

- (CGPoint)coordinatesAtPosition:(CGPoint)point { return ccp((int)(point.x / self.tileSize.width), (int)(self.mapSize.height - (point.y / self.tileSize.height))); }   - (unsigned int)getGIDAtPosition:(CGPoint)point { return [layer tileGIDAt:[self coordinatesAtPosition:point]]; }

That way it’s easy to figure out what tile any individual object is colliding with. For example, in my main character object I can have code that runs in step: function with this:

BGTileMap* tileMap = [StandardGameController sharedSingleton].tileMap; CGPoint coordinate = [tileMap coordinatesAtPosition:self.position]; BBLog(@"Right now on tile %d",[tileMap.layer tileGIDAt:coordinate]);

Now I know what type of tile I am overlapping, and I can respond to the environment accordingly.

This is really all the code that you need to make a scrolling game view…I think some people overthink it and try adjusting the position of every object individually with some offset, but it’s not necessary since your objects can use relative positions with their parent.

I have one last bit of code to add, and this is something kind of fun. It’s not complete, but at least it’s a start. What this allows you to do is ‘zoom out’ so you can see your entire map with ALL the objects shrunk down, and then zoom back in to your character. The only tricky part is when you zoom back in, you have to slowly adjust your viewpoint in steps so the zoom in action is centered on your character, instead of jumping at the end.

#define ZOOM_BACK_IN_INTERVALS 10 #define ZOOM_OUT_RATE 0.3 // TODO need to refine this so for each step it uses the new viewpoint - (void)setZoom:(BOOL)zoomedIn { BBLog(@"Zooming in %d", zoomedIn);   // this scales it out so the whole height of the tilemap is in the screen float zoomScaleFactor = 320 / (tileMap.mapSize.height * tileMap.tileSize.height); if(zoomedIn) { [gameLayer runAction:[ScaleTo actionWithDuration:ZOOM_OUT_RATE scale:1.0]]; [gameLayer runAction:[MoveTo actionWithDuration:ZOOM_OUT_RATE position:viewPoint]]; [self schedule:@selector(setZoomedBackIn:) interval:ZOOM_OUT_RATE]; // need this for the transistion } else { [gameLayer runAction:[ScaleTo actionWithDuration:ZOOM_OUT_RATE scale:zoomScaleFactor]]; [gameLayer runAction:[MoveTo actionWithDuration:ZOOM_OUT_RATE position:ccp(0,0)]]; isZoomedOut = YES; } }   // need this small correction at the end to account of the player is moving and the viewpoint has changed to avoid jitter #define ZOOM_OUT_CORRECTION_RATE 0.3 - (void)setZoomedBackIn:(ccTime)dt { [self unschedule:@selector(setZoomedBackIn:)]; [gameLayer runAction:[MoveTo actionWithDuration:ZOOM_OUT_CORRECTION_RATE position:viewPoint]]; [self schedule:@selector(setZoomedBackInFinished:) interval:ZOOM_OUT_CORRECTION_RATE]; // need this for the transistion }   - (void)setZoomedBackInFinished:(ccTime)dt { [self unschedule:@selector(setZoomedBackInFinished:)]; isZoomedOut = NO; }

Now you see why my viewPoint variable was a class member…you’ll need it for these methods to work.

The reason this code isn’t complete is when it starts zooming in, it creates the actions to zoom in on the current viewpoint, but if the player is moving by the time the zoom animation is done, that viewpoint is changed and needs to ’snap’ to the new viewpoint. That is the reason for the setZoomedBackIn: method, which really shouldn’t have to move the gameLayer anymore. However, I haven’t yet written the code to continuously create smaller move actions to take into account a moving viewpoint as the animation continues, but doing so shouldn’t be that hard. If you want to see that bit when I finish, post in the comments and I’ll add it in.


http://johnehartzog.com/2009/10/2d-scrolling-game-with-cocos2d-tilemap-with-zoom/

Posted by 오늘마감
[cocos2D] Easy To Create Buttons with Cocos2D

Those of you who use cocos2d a lot might understand why I created this class as some point. Hopefully it may save some of you those nasty 5 line blobs that you normally need to create a simple button. Usage is simple, just do:

[self addChild:[Button buttonWithText:@"back" atPosition:ccp(80, 50) target:self selector:@selector(back:)]]; [self addChild:[Button buttonWithImage:@"openFeint.png" atPosition:ccp(400, 50) target:self selector:@selector(openOpenFeint:)]];

You’ll need to create your own button.png and button_p.png (the second one is the image shown when you are touching the button). Also you’ll need to choose your own font. Here is the code…

// // Button.h // StickWars - Siege // // Created by EricH on 8/3/09. //   @interface Button : Menu { } + (id)buttonWithText:(NSString*)text atPosition:(CGPoint)position target:(id)target selector:(SEL)selector; + (id)buttonWithImage:(NSString*)file atPosition:(CGPoint)position target:(id)target selector:(SEL)selector; @end   @interface ButtonItem : MenuItem { Sprite *back; Sprite *backPressed; } + (id)buttonWithText:(NSString*)text target:(id)target selector:(SEL)selector; + (id)buttonWithImage:(NSString*)file target:(id)target selector:(SEL)selector; - (id)initWithText:(NSString*)text target:(id)target selector:(SEL)selector; - (id)initWithImage:(NSString*)file target:(id)target selector:(SEL)selector; @end
// // Button.m // StickWars - Siege // // Created by EricH on 8/3/09. //   #import "Button.h"     @implementation Button + (id)buttonWithText:(NSString*)text atPosition:(CGPoint)position target:(id)target selector:(SEL)selector { Menu *menu = [Menu menuWithItems:[ButtonItem buttonWithText:text target:target selector:selector], nil]; menu.position = position; return menu; }   + (id)buttonWithImage:(NSString*)file atPosition:(CGPoint)position target:(id)target selector:(SEL)selector { Menu *menu = [Menu menuWithItems:[ButtonItem buttonWithImage:file target:target selector:selector], nil]; menu.position = position; return menu; } @end   @implementation ButtonItem + (id)buttonWithText:(NSString*)text target:(id)target selector:(SEL)selector { return [[[self alloc] initWithText:text target:target selector:selector] autorelease]; }   + (id)buttonWithImage:(NSString*)file target:(id)target selector:(SEL)selector { return [[[self alloc] initWithImage:file target:target selector:selector] autorelease]; }   - (id)initWithText:(NSString*)text target:(id)target selector:(SEL)selector { if(self = [super initWithTarget:target selector:selector]) { back = [[Sprite spriteWithFile:@"button.png"] retain]; back.anchorPoint = ccp(0,0); backPressed = [[Sprite spriteWithFile:@"button_p.png"] retain]; backPressed.anchorPoint = ccp(0,0); [self addChild:back];   self.contentSize = back.contentSize;   Label* textLabel = [Label labelWithString:text fontName:@"take_out_the_garbage" fontSize:22]; textLabel.position = ccp(self.contentSize.width / 2, self.contentSize.height / 2); textLabel.anchorPoint = ccp(0.5, 0.3); [self addChild:textLabel z:1]; } return self; }   - (id)initWithImage:(NSString*)file target:(id)target selector:(SEL)selector { if(self = [super initWithTarget:target selector:selector]) {   back = [[Sprite spriteWithFile:@"button.png"] retain]; back.anchorPoint = ccp(0,0); backPressed = [[Sprite spriteWithFile:@"button_p.png"] retain]; backPressed.anchorPoint = ccp(0,0); [self addChild:back];   self.contentSize = back.contentSize;   Sprite* image = [Sprite spriteWithFile:file]; [self addChild:image z:1]; image.position = ccp(self.contentSize.width / 2, self.contentSize.height / 2); } return self; }   -(void) selected { [self removeChild:back cleanup:NO]; [self addChild:backPressed]; [super selected]; }   -(void) unselected { [self removeChild:backPressed cleanup:NO]; [self addChild:back]; [super unselected]; }   // this prevents double taps - (void)activate { [super activate]; [self setIsEnabled:NO]; [self schedule:@selector(resetButton:) interval:0.5]; }   - (void)resetButton:(ccTime)dt { [self unschedule:@selector(resetButton:)]; [self setIsEnabled:YES]; }   - (void)dealloc { [back release]; [backPressed release]; [super dealloc]; }   @end

A common question I see on the cocos2d forums is ‘when I want to make my game scroll, do I move the camera or the layer?’ or some variant of that. I also got some more detailed questions about how to make a functioning 2D scroller, so I’m going to described how I got it to work.

First of all, the answer to the above question is you move the layer. If you follow the examples in the cocos2d download, you have a “GameScene” and a “GameLayer”. Well, everything that needs to be moved when your game scrolls should be added as a child to that GameLayer. If you are using a TileMap as a background, this includes that tilemap. The only thing that you don’t add as a child to the GameLayer is stuff that does not move with the scrolling view, such as your HUDLayer that has text that shows your characters health. Other than that, your character, the background, other characters, should all be added to the GameLayer.

You have to remember which objects are absolute (attached to your GameScene or other layers) versus those that are relative (a child of your GameLayer) when you set up your touch handling code. For my HUD that has buttons you can press at any time, say to pause the game, you want to add the touch handling object to your GameScene or HUDLayer class, since it doesn’t move. But if you want to be able to touch objects that scroll along with your view in the game itself, your touch handling code needs to be in an object that is a child of your GameLayer.

This might be a little confusing, so let’s see some code:

gameLayer = [GameLayer node]; [gameScene addChild:gameLayer z:zOrder_GameLayer];   hudLayer = [HUDLayer node]; [gameScene addChild:hudLayer z:zOrder_HudLayer];   tileMap = [BGTileMap node]; [gameLayer addChild:tileMap z:-1];   [gameScene addChild:[PauseGameButton node] z:zOrder_GameButtons]; [gameLayer addChild:[FireGunAtTouchPoint node]];

The pause game button is always on your screen, while the point at which your character fires the gun depends on how how far your view has been scrolled (by moving GameLayer).

Let’s see some of the code that actually moves this game layer:

- (void)setViewpointCenter:(CGPoint)point { CGPoint centerPoint = ccp(240, 160); viewPoint = ccpSub(centerPoint, point);   // dont scroll so far so we see anywhere outside the visible map which would show up as black bars if(point.x &lt; centerPoint.x) viewPoint.x = 0; if(point.y &lt; centerPoint.y) viewPoint.y = 0;   // while zoomed out, don't adjust the viewpoint if(!isZoomedOut) gameLayer.position = viewPoint; }

When do you call that method? Well, it depends on what you want, but generally these scrolling games follow around the movement of your ‘main’ character, right? So whatever character the you want to follow, add this to override the standard CocosNode setPosition method so you update your viewpoint whenever the character moves

- (void)setPosition:(CGPoint)point { [[StandardGameController sharedSingleton] setViewpointCenter:point]; [super setPosition:point]; }

Note that the StandardGameController is a construct of mine that I use to separate the game logic out from the display code. It doesn’t matter exactly how you do it, you just need a way to have your main character object call back to something that contains a reference to GameLayer so it can adjust the position of your GameLayer.

Now remember, for your background to scroll properly, you need to add your background tileMap as a child of your GameLayer that is being moved around.

That being said, I found that an important method was missing from the cocos2d tilemap that I need to use in order to detect collisions based on the types of tiles encountered. I created a subclass of TMXTiledMap and added in these methods:

- (CGPoint)coordinatesAtPosition:(CGPoint)point { return ccp((int)(point.x / self.tileSize.width), (int)(self.mapSize.height - (point.y / self.tileSize.height))); }   - (unsigned int)getGIDAtPosition:(CGPoint)point { return [layer tileGIDAt:[self coordinatesAtPosition:point]]; }

That way it’s easy to figure out what tile any individual object is colliding with. For example, in my main character object I can have code that runs in step: function with this:

BGTileMap* tileMap = [StandardGameController sharedSingleton].tileMap; CGPoint coordinate = [tileMap coordinatesAtPosition:self.position]; BBLog(@"Right now on tile %d",[tileMap.layer tileGIDAt:coordinate]);

Now I know what type of tile I am overlapping, and I can respond to the environment accordingly.

This is really all the code that you need to make a scrolling game view…I think some people overthink it and try adjusting the position of every object individually with some offset, but it’s not necessary since your objects can use relative positions with their parent.

I have one last bit of code to add, and this is something kind of fun. It’s not complete, but at least it’s a start. What this allows you to do is ‘zoom out’ so you can see your entire map with ALL the objects shrunk down, and then zoom back in to your character. The only tricky part is when you zoom back in, you have to slowly adjust your viewpoint in steps so the zoom in action is centered on your character, instead of jumping at the end.

#define ZOOM_BACK_IN_INTERVALS 10 #define ZOOM_OUT_RATE 0.3 // TODO need to refine this so for each step it uses the new viewpoint - (void)setZoom:(BOOL)zoomedIn { BBLog(@"Zooming in %d", zoomedIn);   // this scales it out so the whole height of the tilemap is in the screen float zoomScaleFactor = 320 / (tileMap.mapSize.height * tileMap.tileSize.height); if(zoomedIn) { [gameLayer runAction:[ScaleTo actionWithDuration:ZOOM_OUT_RATE scale:1.0]]; [gameLayer runAction:[MoveTo actionWithDuration:ZOOM_OUT_RATE position:viewPoint]]; [self schedule:@selector(setZoomedBackIn:) interval:ZOOM_OUT_RATE]; // need this for the transistion } else { [gameLayer runAction:[ScaleTo actionWithDuration:ZOOM_OUT_RATE scale:zoomScaleFactor]]; [gameLayer runAction:[MoveTo actionWithDuration:ZOOM_OUT_RATE position:ccp(0,0)]]; isZoomedOut = YES; } }   // need this small correction at the end to account of the player is moving and the viewpoint has changed to avoid jitter #define ZOOM_OUT_CORRECTION_RATE 0.3 - (void)setZoomedBackIn:(ccTime)dt { [self unschedule:@selector(setZoomedBackIn:)]; [gameLayer runAction:[MoveTo actionWithDuration:ZOOM_OUT_CORRECTION_RATE position:viewPoint]]; [self schedule:@selector(setZoomedBackInFinished:) interval:ZOOM_OUT_CORRECTION_RATE]; // need this for the transistion }   - (void)setZoomedBackInFinished:(ccTime)dt { [self unschedule:@selector(setZoomedBackInFinished:)]; isZoomedOut = NO; }

Now you see why my viewPoint variable was a class member…you’ll need it for these methods to work.

The reason this code isn’t complete is when it starts zooming in, it creates the actions to zoom in on the current viewpoint, but if the player is moving by the time the zoom animation is done, that viewpoint is changed and needs to ’snap’ to the new viewpoint. That is the reason for the setZoomedBackIn: method, which really shouldn’t have to move the gameLayer anymore. However, I haven’t yet written the code to continuously create smaller move actions to take into account a moving viewpoint as the animation continues, but doing so shouldn’t be that hard. If you want to see that bit when I finish, post in the comments and I’ll add it in.

Posted by 오늘마감
ActionSheet with Picker

One more solution:

  • no toolbar but a segmented control (eyecandy)

UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:nil delegate:nil cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles:nil];


[actionSheet setActionSheetStyle:UIActionSheetStyleBlackTranslucent];

CGRect pickerFrame =CGRectMake(0,40,0,0);

UIPickerView*pickerView =[[UIPickerView alloc] initWithFrame:pickerFrame];
pickerView
.showsSelectionIndicator = YES;
pickerView
.dataSource =self;
pickerView
.delegate=self;

[actionSheet addSubview:pickerView];
[pickerView release];

UISegmentedControl*closeButton =[[UISegmentedControl alloc] initWithItems:[NSArray arrayWithObject:@"Close"]];
closeButton
.momentary = YES;
closeButton
.frame =CGRectMake(260,7.0f,50.0f,30.0f);
closeButton
.segmentedControlStyle =UISegmentedControlStyleBar;
closeButton
.tintColor =[UIColor blackColor];
[closeButton addTarget:self action:@selector(dismissActionSheet:) forControlEvents:UIControlEventValueChanged];
[actionSheet addSubview:closeButton];
[closeButton release];

[actionSheet showInView:[UIApplication mainWindow]];

[actionSheet setBounds:CGRectMake(0,0,320,485)];
Posted by 오늘마감
[cocos2D] Using Box2D Physics Engine with Cocos2D iPhone

Starting work on my new project, I’ve found Box2D to be a far superior physics engine than chipmunk. It is more mature, the API more flexible, and it seems to even perform faster. However, the cocos2d code for it was rather space, so here is a sort of helper file I had to create to make it work with my game.

Keep in mind this game is in progress and I’ve only been using this for a few days, so it may have issues that might pop up later on. It seems to be working very well for now though, with 40-50 objects on screen moving around with ~40 FPS.

Here is the header

// // Box2DEngine.h // // Created by EricH on 7/22/09. // Copyright 2009 __MyCompanyName__. All rights reserved. //   #import "Box2D.h"   // made this an extern constant to avoid obj c lookup overhead extern b2World *bb_world;   @interface Box2DEngine : CocosNode { }   + (Box2DEngine *)sharedSingleton; - (void)createWorld:(CGSize)size; - (void)deleteWorld; - (void)runSimulation; @end

The .mm file

// // Box2DEngine.mm // // Created by EricH on 7/22/09. // Copyright 2009 __MyCompanyName__. All rights reserved. //   #import "HookActor.h"     #import "Box2DEngine.h" #import "SuperBox2DActor.h"   b2World* bb_world;   const float32 timeStep = 1.0f / 60.0f; const int32 velocityIterations = 10; const int32 positionIterations = 10;   #define MAX_NUM_COLLISIONS 2048 // TODO make sure this buffer is the right size b2ContactResult contactResultCache[MAX_NUM_COLLISIONS]; int32 contactResultCount = 0;   class MyContactListener : public b2ContactListener { public: void Add(const b2ContactPoint* point) { }   void Persist(const b2ContactPoint* point) { }   void Remove(const b2ContactPoint* point) { }   void Result(const b2ContactResult* point) { // TODO we are making a deep copy of every contact point here // check the box2d contact masks to make sure we minimize unwanted contact results contactResultCache[contactResultCount++] = *point; } };   void handleCachedContactResults() { b2ContactResult contactResult; for(int i = 0; i < contactResultCount; i++) { #ifdef BBDEBUG if(contactResultCount >= MAX_NUM_COLLISIONS) { NSLog(@"RAN OUT OF BUFFER"); assert(NO); } #endif contactResult = contactResultCache[i]; SuperBox2DActor* actorOne = (SuperBox2DActor*)contactResult.shape1->GetBody()->GetUserData(); SuperBox2DActor* actorTwo = (SuperBox2DActor*)contactResult.shape2->GetBody()->GetUserData(); if(!actorOne.isDead && !actorTwo.isDead) { [actorOne collisionResultOne:&contactResult withActor:actorTwo]; [actorTwo collisionResultTwo:&contactResult withActor:actorOne]; } }   // clear the collision cache contactResultCount = 0; }   void removeDeadActors() { b2Body* node = bb_world->GetBodyList(); while (node) { b2Body* b = node; node = node->GetNext();   SuperBox2DActor* actor = (SuperBox2DActor*)b->GetUserData(); if (actor.isDead) {   // remove from physics engine bb_world->DestroyBody(b);   // remove from game engine (cocos2d) [[StandardGameController sharedSingleton] removeGameActor:actor]; } } }     @implementation Box2DEngine + (Box2DEngine*)sharedSingleton { static Box2DEngine* sharedSingleton; if (!sharedSingleton) sharedSingleton = [[Box2DEngine alloc] init];   return sharedSingleton; }   - (void)createWorld:(CGSize)size { b2AABB worldAABB; worldAABB.lowerBound.Set(0, 0); worldAABB.upperBound.Set(size.width * BOX2D_SCALE_FACTOR_INVERSE, size.height * BOX2D_SCALE_FACTOR_INVERSE); // TODO this shouldnt be inverse?   b2Vec2 gravity(0.0f, -30.0f); bool doSleep = true;   bb_world = new b2World(worldAABB, gravity, doSleep); bb_world->SetContactListener(new MyContactListener()); }   - (void)deleteWorld { [self unschedule:@selector(step:)]; delete bb_world; bb_world = NULL; }   - (void)runSimulation { [self schedule:@selector(step:)]; }   - (void)step:(ccTime)dt {   // step the world bb_world->Step(dt, velocityIterations, positionIterations); // TODO do i use timestep or dt here?   //BBLog(@"Num of contact results is %d",contactResultCount); // do stuff with collisions handleCachedContactResults();   // remove all actors that are marked as dead removeDeadActors();   // update cocosnode positions for (b2Body* b = bb_world->GetBodyList(); b; b = b->GetNext()) { if (b->GetUserData() != NULL) { SuperBox2DActor *actor = (SuperBox2DActor*)b->GetUserData(); b2Vec2 position = b->GetPosition(); actor.position = b2toCGPoint(position); actor.rotation = -1 * CC_RADIANS_TO_DEGREES(b->GetAngle()); //NSLog(@"obj %@ %4.2f %4.2f\n",actor, position.x, position.y); } }   }   @end

Update: This code is obsolete now. You can just do

Label *messageLabel = [Label labelWithString:message dimensions:CGSizeMake(380, 120) alignment:UITextAlignmentCenter fontName:@"your_custom_font" fontSize:26];

by using the new FontManager class. For example, run this once in your app delegate when your program first loads

[[FontManager sharedManager] loadFont:@"your_custom_font"];

It can take a long NSString and create multiple labels without breaking up a word. I’ll eventually use this for my help screen, replacing the current 6 different 480×320 png images that I load for each one  .

The code is simple, but hopefully it might save somebody the time it took me to write it. I had to look up some very basic elements of ObjC here, so if there is a much easier way to do this, please let me know but don’t make too much fun of me.

You can easily switch out the BitmapFontAtlas for just a normal Label and it would work just fine.

(void) setTipString:(NSString*)str {   NSInteger lineChars = 0; BOOL isSpace = NO; NSInteger index = 0; NSInteger numLines = 0;   NSMutableString *line = [NSMutableString stringWithCapacity:LINE_LENGTH];   while (index <= [str length]) { if(index == [str length]) { BitmapFontAtlas *tip = [[BitmapFontAtlas bitmapFontAtlasWithString:[NSString stringWithString:line] fntFile:@"text.fnt" alignment:UITextAlignmentLeft] retain]; [tip setPosition: cpv(30,210 - 20 * numLines)]; [self addChild:tip]; return; }     NSString *tmp = [str substringWithRange:NSMakeRange(index, 1)]; [line appendString:tmp];   if([tmp isEqual:@" "]) isSpace = YES; else isSpace = NO;   if(lineChars >= LINE_LENGTH && isSpace) { BitmapFontAtlas *tip = [[BitmapFontAtlas bitmapFontAtlasWithString:[NSString stringWithString:line] fntFile:@"text.fnt" alignment:UITextAlignmentLeft] retain]; [tip setPosition: cpv(30,210 - 20 * numLines)]; [self addChild:tip]; lineChars = -1; [line setString:@""]; numLines++; } lineChars++; index++; } }
Posted by 오늘마감
Actionsheet with uipicker

Yep ! I finally Find it.

implement following code on your button click event, to pop up action sheet as given in the image of question.


UIActionSheet*aac =[[UIActionSheet alloc] initWithTitle:@"How many?"
delegate:self
cancelButtonTitle
:nil
destructiveButtonTitle
:nil
otherButtonTitles
:nil];

UIDatePicker*theDatePicker =[[UIDatePicker alloc] initWithFrame:CGRectMake(0.0,44.0,0.0,0.0)];
if(IsDateSelected==YES)
{
theDatePicker
.datePickerMode =UIDatePickerModeDate;
theDatePicker
.maximumDate=[NSDate date];
}else{
theDatePicker
.datePickerMode =UIDatePickerModeTime;
}

self.dtpicker = theDatePicker;
[theDatePicker release];
[dtpicker addTarget:self action:@selector(dateChanged) forControlEvents:UIControlEventValueChanged];

pickerDateToolbar
=[[UIToolbar alloc] initWithFrame:CGRectMake(0,0,320,44)];
pickerDateToolbar
.barStyle =UIBarStyleBlackOpaque;
[pickerDateToolbar sizeToFit];

NSMutableArray*barItems =[[NSMutableArray alloc] init];

UIBarButtonItem*flexSpace =[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:self action:nil];
[barItems addObject:flexSpace];

UIBarButtonItem*doneBtn =[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(DatePickerDoneClick)];
[barItems addObject:doneBtn];

[pickerDateToolbar setItems:barItems animated:YES];

[aac addSubview:pickerDateToolbar];
[aac addSubview:dtpicker];
[aac showInView:self.view];
[aac setBounds:CGRectMake(0,0,320,464)];
Posted by 오늘마감
How to override UIWebView links request action with your own custom method

How to override UIWebView links request action with your own custom method

If you have a Web View in your view you can call a custom action when the user taps on a link in that UIWebView instance.

Here’s how…

Set the delegate of that UIWebView class instance on your instance of UIViewController class. Now just copy & paste the following code into your class.  This code implements the UIWebView instance and adds a call to the custom action (method).

- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType {
if(navigationType == UIWebViewNavigationTypeLinkClicked) {
if (overrideLinksSwitch.on == TRUE) {
[self myMethodAction];
[myWebView stopLoading];
return YES;
}
else {
<>return YES;
}
}
return YES;
}

You can download an example app here.

http://surgeworksmobile.com/iphone/how-to-override-uiwebview-links-request-action-with-your-own-custom-method
Posted by 오늘마감
2D Scrolling Game with Cocos2D TileMap with Zoom!

A common question I see on the cocos2d forums is ‘when I want to make my game scroll, do I move the camera or the layer?’ or some variant of that. I also got some more detailed questions about how to make a functioning 2D scroller, so I’m going to described how I got it to work.

First of all, the answer to the above question is you move the layer. If you follow the examples in the cocos2d download, you have a “GameScene” and a “GameLayer”. Well, everything that needs to be moved when your game scrolls should be added as a child to that GameLayer. If you are using a TileMap as a background, this includes that tilemap. The only thing that you don’t add as a child to the GameLayer is stuff that does not move with the scrolling view, such as your HUDLayer that has text that shows your characters health. Other than that, your character, the background, other characters, should all be added to the GameLayer.

You have to remember which objects are absolute (attached to your GameScene or other layers) versus those that are relative (a child of your GameLayer) when you set up your touch handling code. For my HUD that has buttons you can press at any time, say to pause the game, you want to add the touch handling object to your GameScene or HUDLayer class, since it doesn’t move. But if you want to be able to touch objects that scroll along with your view in the game itself, your touch handling code needs to be in an object that is a child of your GameLayer.

This might be a little confusing, so let’s see some code:

gameLayer = [GameLayer node]; [gameScene addChild:gameLayer z:zOrder_GameLayer];   hudLayer = [HUDLayer node]; [gameScene addChild:hudLayer z:zOrder_HudLayer];   tileMap = [BGTileMap node]; [gameLayer addChild:tileMap z:-1];   [gameScene addChild:[PauseGameButton node] z:zOrder_GameButtons]; [gameLayer addChild:[FireGunAtTouchPoint node]];

The pause game button is always on your screen, while the point at which your character fires the gun depends on how how far your view has been scrolled (by moving GameLayer).

Let’s see some of the code that actually moves this game layer:

- (void)setViewpointCenter:(CGPoint)point { CGPoint centerPoint = ccp(240, 160); viewPoint = ccpSub(centerPoint, point);   // dont scroll so far so we see anywhere outside the visible map which would show up as black bars if(point.x &lt; centerPoint.x) viewPoint.x = 0; if(point.y &lt; centerPoint.y) viewPoint.y = 0;   // while zoomed out, don't adjust the viewpoint if(!isZoomedOut) gameLayer.position = viewPoint; }

When do you call that method? Well, it depends on what you want, but generally these scrolling games follow around the movement of your ‘main’ character, right? So whatever character the you want to follow, add this to override the standard CocosNode setPosition method so you update your viewpoint whenever the character moves

- (void)setPosition:(CGPoint)point { [[StandardGameController sharedSingleton] setViewpointCenter:point]; [super setPosition:point]; }

Note that the StandardGameController is a construct of mine that I use to separate the game logic out from the display code. It doesn’t matter exactly how you do it, you just need a way to have your main character object call back to something that contains a reference to GameLayer so it can adjust the position of your GameLayer.

Now remember, for your background to scroll properly, you need to add your background tileMap as a child of your GameLayer that is being moved around.

That being said, I found that an important method was missing from the cocos2d tilemap that I need to use in order to detect collisions based on the types of tiles encountered. I created a subclass of TMXTiledMap and added in these methods:

- (CGPoint)coordinatesAtPosition:(CGPoint)point { return ccp((int)(point.x / self.tileSize.width), (int)(self.mapSize.height - (point.y / self.tileSize.height))); }   - (unsigned int)getGIDAtPosition:(CGPoint)point { return [layer tileGIDAt:[self coordinatesAtPosition:point]]; }

That way it’s easy to figure out what tile any individual object is colliding with. For example, in my main character object I can have code that runs in step: function with this:

BGTileMap* tileMap = [StandardGameController sharedSingleton].tileMap; CGPoint coordinate = [tileMap coordinatesAtPosition:self.position]; BBLog(@"Right now on tile %d",[tileMap.layer tileGIDAt:coordinate]);

Now I know what type of tile I am overlapping, and I can respond to the environment accordingly.

This is really all the code that you need to make a scrolling game view…I think some people overthink it and try adjusting the position of every object individually with some offset, but it’s not necessary since your objects can use relative positions with their parent.

I have one last bit of code to add, and this is something kind of fun. It’s not complete, but at least it’s a start. What this allows you to do is ‘zoom out’ so you can see your entire map with ALL the objects shrunk down, and then zoom back in to your character. The only tricky part is when you zoom back in, you have to slowly adjust your viewpoint in steps so the zoom in action is centered on your character, instead of jumping at the end.

#define ZOOM_BACK_IN_INTERVALS 10 #define ZOOM_OUT_RATE 0.3 // TODO need to refine this so for each step it uses the new viewpoint - (void)setZoom:(BOOL)zoomedIn { BBLog(@"Zooming in %d", zoomedIn);   // this scales it out so the whole height of the tilemap is in the screen float zoomScaleFactor = 320 / (tileMap.mapSize.height * tileMap.tileSize.height); if(zoomedIn) { [gameLayer runAction:[ScaleTo actionWithDuration:ZOOM_OUT_RATE scale:1.0]]; [gameLayer runAction:[MoveTo actionWithDuration:ZOOM_OUT_RATE position:viewPoint]]; [self schedule:@selector(setZoomedBackIn:) interval:ZOOM_OUT_RATE]; // need this for the transistion } else { [gameLayer runAction:[ScaleTo actionWithDuration:ZOOM_OUT_RATE scale:zoomScaleFactor]]; [gameLayer runAction:[MoveTo actionWithDuration:ZOOM_OUT_RATE position:ccp(0,0)]]; isZoomedOut = YES; } }   // need this small correction at the end to account of the player is moving and the viewpoint has changed to avoid jitter #define ZOOM_OUT_CORRECTION_RATE 0.3 - (void)setZoomedBackIn:(ccTime)dt { [self unschedule:@selector(setZoomedBackIn:)]; [gameLayer runAction:[MoveTo actionWithDuration:ZOOM_OUT_CORRECTION_RATE position:viewPoint]]; [self schedule:@selector(setZoomedBackInFinished:) interval:ZOOM_OUT_CORRECTION_RATE]; // need this for the transistion }   - (void)setZoomedBackInFinished:(ccTime)dt { [self unschedule:@selector(setZoomedBackInFinished:)]; isZoomedOut = NO; }

Now you see why my viewPoint variable was a class member…you’ll need it for these methods to work.

The reason this code isn’t complete is when it starts zooming in, it creates the actions to zoom in on the current viewpoint, but if the player is moving by the time the zoom animation is done, that viewpoint is changed and needs to ’snap’ to the new viewpoint. That is the reason for the setZoomedBackIn: method, which really shouldn’t have to move the gameLayer anymore. However, I haven’t yet written the code to continuously create smaller move actions to take into account a moving viewpoint as the animation continues, but doing so shouldn’t be that hard. If you want to see that bit when I finish, post in the comments and I’ll add it in.

http://johnehartzog.com/2009/10/2d-scrolling-game-with-cocos2d-tilemap-with-zoom/

Posted by 오늘마감