I have created my own solution for zooming in or out on a specific SKNode without having the zoom the entire scene, and it seems to work mostly how I would expect it to work, with 2 notable exceptions which I am hoping to get input on here. First the code (this control statement is within the touchesMoved method):
if (touches.count == 2) {
// this means there are two fingers on the screen
NSArray *fingers = [touches allObjects];
CGPoint fingOneCurr = [fingers[0] locationInNode:self];
CGPoint fingOnePrev = [fingers[0] previousLocationInNode:self];
CGPoint fingTwoCurr = [fingers[1] locationInNode:self];
CGPoint fingTwoPrev = [fingers[1] previousLocationInNode:self];
BOOL yPinch = fingOneCurr.y > fingOnePrev.y && fingTwoCurr.y < fingTwoPrev.y;
BOOL yUnpinch = fingOneCurr.y < fingOnePrev.y && fingTwoCurr.y > fingTwoPrev.y;
BOOL xPinch = fingOneCurr.x > fingOnePrev.x && fingTwoCurr.x < fingTwoPrev.x;
BOOL xUnpinch = fingOneCurr.x < fingOnePrev.x && fingTwoCurr.x > fingTwoPrev.x;
if (xUnpinch | yUnpinch) {
if (YES) NSLog(#"This means an unpinch is happening");
mapScale = mapScale +.02;
[map setScale:mapScale];
}
if (xPinch | yPinch) {
if (YES) NSLog(#"This means a pinch is happening");
mapScale = mapScale - .02;
[map setScale:mapScale];
}
}
Now the problems:
The pinch and unpinch are not always right sometimes, and I cannot quite put my finger on when this is happening, the pinch will behave as an unpinch and vis a versa.
When the pinching and unpinching is scaling the SKNode correctly, it is rarely as smooth as I would like. There is a bit of jerkiness to it which I find annoying.
Can anyone suggest improvements to this method? Thanks!
This will solve your problem, thanks to Steffen for the hints.
- (void)didMoveToView:(SKView *)view
{
UIPinchGestureRecognizer *precog = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(handlePinch:)];
[self.scene.view addGestureRecognizer:precog];
}
- (void)handlePinch:(UIPinchGestureRecognizer *) recognizer
{
//NSLog(#"Pinch %f", recognizer.scale);
//[_bg setScale:recognizer.scale];
[_bg runAction:[SKAction scaleBy:recognizer.scale duration:0]];
recognizer.scale = 1;
}
Related
I want to keep my sprite just moving in the range of the screen. So I create a edge loop by bodyWithEdgeLoopFromRect, also the collision bit mask has been set to make them collide with each other.
static const uint32_t kRocketCategory = 0x1 << 0;
static const uint32_t kEdgeCategory = 0x1 << 6;
I use the pan recognizer to move the sprite and here is the current code that sets the properties of all the things.
- (void)didMoveToView:(SKView *)view
{
// Pan gesture
self.panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanFrom:)];
[self.view addGestureRecognizer:self.panRecognizer];
// Edge
self.backgroundColor = [SKColor blackColor];
self.physicsWorld.gravity = CGVectorMake(0.0f, 0.0f);
self.physicsWorld.contactDelegate = self;
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
self.physicsBody.categoryBitMask = kEdgeCategory;
self.physicsBody.contactTestBitMask = kRocketCategory;
self.physicsBody.collisionBitMask = kRocketCategory;
self.physicsBody.usesPreciseCollisionDetection = YES;
self.physicsBody.restitution = 0;
// Rocket
SKTexture *texture = [SKTexture textureWithImageNamed:#"Rocketship-v2-1"];
texture.filteringMode = SKTextureFilteringNearest;
self.rocketSprite = [SKSpriteNode spriteNodeWithTexture:texture];
self.rocketSprite.position = CGPointMake(CGRectGetMidX(self.scene.frame), CGRectGetMaxY(self.scene.frame)*0.3); // 30% Y-axis
self.rocketSprite.name = #"rocketNode";
self.rocketSprite.xScale = 2;
self.rocketSprite.yScale = 2;
self.rocketSprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.rocketSprite.size];
self.rocketSprite.physicsBody.categoryBitMask = kRocketCategory;
self.rocketSprite.physicsBody.contactTestBitMask = kEdgeCategory;
self.rocketSprite.physicsBody.collisionBitMask = kEdgeCategory;
self.rocketSprite.physicsBody.usesPreciseCollisionDetection = YES;
self.rocketSprite.physicsBody.allowsRotation = NO;
self.rocketSprite.physicsBody.restitution = 0;
[self addChild:self.rocketSprite];
}
Pan recognizer code:
- (void)handlePanFrom:(UIPanGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateBegan) {
self.selectedRocket = self.rocketSprite;
} else if (recognizer.state == UIGestureRecognizerStateChanged) {
CGPoint translation = [recognizer translationInView:recognizer.view];
translation = CGPointMake(translation.x, -translation.y);
[self panForTranslation:translation];
[recognizer setTranslation:CGPointZero inView:recognizer.view];
} else if (recognizer.state == UIGestureRecognizerStateEnded) {
}
}
- (void)panForTranslation:(CGPoint)translation
{
CGPoint position = self.selectedRocket.position;
if ([self.selectedRocket.name isEqualToString:#"rocketNode"]) {
self.selectedRocket.position = CGPointMake(position.x + translation.x, position.y);
}
}
Now the problem is when I move the sprite (rocketship) slowly, the edge will stop the sprite going out of the screen. However, when I move the sprite very quickly, the sprite will rush out the range. See the animation below. I have read some solutions to the similar problem but I still don't know what's wrong with my code. Do the edge loop and collision bit mask not enough for the situation here?
Your attempt is similar to what I've found when searching online, and having read what you've tried it looks like you've pretty much covered most of the more common problems (Not using SKAction, for instance).
I don't currently use a contact Bitmask to handle collisions on the side of my scene, but I do use a loop that actively checks positions.
Here's the method I use to reposition objects: (Repositions everything, you can specify it to move only the rocket.
-(void)repositionObjects
{
for (CXSpriteNode *i in self.sceneObjects) // CXSpriteNode is a certain subclass
{
CGPoint position = i.position;
if (position.x > self.background.size.width || position.x < 0)
{
CGPoint newPosition;
if (position.x > self.background.size.width)
{
newPosition = CGPointMake(self.background.size.width-1, position.y);
} else {
newPosition = CGPointMake(1, position.y);
}
[i runAction:[SKAction moveTo:newPosition duration:0.0f]];
}
if (position.y > self.background.size.height || position.y < 0)
{
CGPoint newPosition;
if (position.y > self.background.size.height)
{
newPosition = CGPointMake(self.background.size.height-1, 1);
} else {
newPosition = CGPointMake(position.x, 1);
}
[i runAction:[SKAction moveTo:newPosition duration:0.0f]];
}
}
}
This gets called from the SKScene loop as such:
-(void)update:(NSTimeInterval)currentTime
{
[self repositionObjects];
}
Now, it's evidently not as elegant as your desired outcome, and I have a feeling it might still induce flickering, but I'd give it a try anyways.
Also, it might be worth trying to disable/cancel the gesture once it goes out of range momentarily to stop repeated swipes that may cause flickering.
Hope this helps.
Use SpriteKit actions instead of directly modifying the position of the sprite.
What is happening is that the recognizer isn't sending updates fast enough. The sprite's bounds never intersect with the edge in any one frame.
In one update, the recognizer says that the touch is within the bounds of the edge loop. But in the next update, the touch has moved way beyond the edge loop. This leaves no frame in which the sprite's bounds intersect the edge, so no collision gets detected.
I do not use sprite kit in daily life, but you could for example solve it the hard way by adding something like this in your sprite's update method
(Pseudo code)
CGFloat x = clamp(self.position.x, minXValue, maxXValue);
CGFloat y = clamp(self.position.y, minYValue, maxYValue);
self.position = CGPointMake(x, y);
Last days, I experimented some time with spriteKit and (amongst other things) tried to solve the problem to "throw" a sprite by touching it and dragging.
The same question is on Stackexchange, but they told me to first remove the bug and then let the code be reviewed.
I have tackled the major hurdles, and the code is working fine, but there consist one little problem.
(Additionally, I'd be interested if somebody has a more polished or better working solution for this. I'd also love to hear suggestions about how to perfect the feeling of realism in this interaction.)
Sometimes, the ball just gets stuck.
If you want to reproduce that, just swipe the ball really fast and short. I suspect the gestureRecognizer to make "touchesMoved" and "touchesEnded" callback asynchronous and through that some impossible state occurs in the physics simulation.
Can anybody provide a more reliable way to reproduce the issue, and what could be the solution for that?
The project is called ballThrow and BT is the class prefix.
#import "BTMyScene.h"
#import "BTBall.h"
#interface BTMyScene()
#property (strong, nonatomic) NSMutableArray *balls;
#property (nonatomic) CGFloat yPosition;
#property (nonatomic) CGFloat xCenter;
#property (nonatomic) BOOL updated;
#end
#implementation BTMyScene
const CGFloat BALLDISTANCE = 80;
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
_balls = [NSMutableArray arrayWithCapacity:5];
//define the region where the balls will spawn
_yPosition = size.height/2.0;
_xCenter = size.width/2.0;
/* Setup your scene here */
self.backgroundColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];
}
return self;
}
-(void)didMoveToView:(SKView *)view {
//Make an invisible border
//this seems to be offset... Why the heck is this?
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:view.frame];
[self createBalls:2];
//move balls with pan gesture
//could be improved by combining with touchesBegan for first locating the touch
[self.view addGestureRecognizer:[[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(moveBall:)]];
}
-(void)moveBall:(UIPanGestureRecognizer *)pgr {
//depending on the touch phase do different things to the ball
if (pgr.state == UIGestureRecognizerStateBegan) {
[self attachBallToTouch:pgr];
}
else if (pgr.state == UIGestureRecognizerStateChanged) {
[self moveBallToTouch:pgr];
}
else if (pgr.state == UIGestureRecognizerStateEnded) {
[self stopMovingTouch:pgr];
}
else if (pgr.state == UIGestureRecognizerStateCancelled) {
[self stopMovingTouch:pgr];
}
}
-(void)attachBallToTouch:(UIPanGestureRecognizer *)touch {
//determine the ball to move
for (BTBall *ball in self.balls) {
if ([self isMovingBall:ball forGestureRecognizer:touch])
{
//stop ball movement
[ball.physicsBody setAffectedByGravity:NO];
[ball.physicsBody setVelocity:CGVectorMake(0, 0)];
//the ball might not be touched right in its center, so save the relative location
ball.touchLocation = [self convertPoint:[self convertPointFromView:[touch locationInView:self.view]] toNode:ball];
//update location once, just in case...
[self setBallPosition:ball toTouch:touch];
if (_updated) {
_updated = NO;
[touch setTranslation:CGPointZero inView:self.view];
}
}
}
}
-(void)moveBallToTouch:(UIPanGestureRecognizer *)touch {
for (BTBall *ball in self.balls) {
if ([self isMovingBall:ball forGestureRecognizer:touch])
{
//update the position of the ball and reset translation
[self setBallPosition:ball toTouch:touch];
if (_updated) {
_updated = NO;
[touch setTranslation:CGPointZero inView:self.view];
}
break;
}
}
}
-(void)setBallPosition:(BTBall *)ball toTouch:(UIPanGestureRecognizer *)touch {
//gesture recognizers only deliver locations in views, thus convert to node
CGPoint touchPosition = [self convertPointFromView:[touch locationInView:self.view]];
//update the location to the toucheĀ“s location, offset by touch position in ball
[ball setNewPosition:CGPointApplyAffineTransform(touchPosition,
CGAffineTransformMakeTranslation(-ball.touchLocation.x,
-ball.touchLocation.y))];
//save the velocity between the last two touch records for later release
CGPoint velocity = [touch velocityInView:self.view];
//why the hell is the y coordinate inverted??
[ball setLastVelocity:CGVectorMake(velocity.x, -velocity.y)];
}
-(void)stopMovingTouch:(UIPanGestureRecognizer *)touch {
for (BTBall *ball in self.balls) {
if ([self isMovingBall:ball forGestureRecognizer:touch]) {
//release the ball: enable gravity impact and make it move
[ball.physicsBody setAffectedByGravity:YES];
[ball.physicsBody setVelocity:CGVectorMake(ball.lastVelocity.dx, ball.lastVelocity.dy)];
break;
}
}
}
-(BOOL)isMovingBall:(BTBall *)ball forGestureRecognizer:(UIPanGestureRecognizer *)touch {
//latest location of touch
CGPoint touchPosition = [touch locationInView:self.view];
//distance covered since the last call
CGPoint touchTranslation = [touch translationInView:self.view];
//position, where the ball must be, if it is the one
CGPoint translatedPosition = CGPointApplyAffineTransform(touchPosition,
CGAffineTransformMakeTranslation(-touchTranslation.x,
-touchTranslation.y));
CGPoint inScene = [self convertPointFromView:translatedPosition];
//determine weather the last touch location was on the ball
//if last touch location was on the ball, return true
return [[self nodesAtPoint:inScene] containsObject:ball];
}
-(void)update:(CFTimeInterval)currentTime {
//updating the ball position here improved performance dramatically
for (BTBall *ball in self.balls) {
//balls that move are not gravity affected
//easiest way to determine movement
if ([ball.physicsBody affectedByGravity] == NO) {
[ball setPosition:ball.newPosition];
}
}
//ball positions are refreshed
_updated = YES;
}
-(void)createBalls:(int)numberOfBalls {
for (int i = 0; i<numberOfBalls; i++) {
BTBall *ball;
//reuse balls (not necessary yet, but imagine balls spawning)
if(i<[self.balls count]) {
ball = self.balls[i];
}
else {
ball = [BTBall newBall];
}
[ball.physicsBody setAffectedByGravity:NO];
//calculate ballposition
CGPoint ballPosition = CGPointMake(self.xCenter-BALLSIZE/2+(i-(numberOfBalls-1)/2.0)*BALLDISTANCE, self.yPosition);
[ball setNewPosition:ballPosition];
[self.balls addObject:ball];
[self addChild:ball];
}
}
#end
The BTBall (subclass of SKShapeNode, because of the custom properties needed)
#import <SpriteKit/SpriteKit.h>
#interface BTBall : SKShapeNode
const extern CGFloat BALLSIZE;
//some properties for the throw animation
#property (nonatomic) CGPoint touchLocation;
#property (nonatomic) CGPoint newPosition;
#property (nonatomic) CGVector lastVelocity;
//create a standard ball
+(BTBall *)newBall;
#end
The BTBall.m with a class method to create new balls
#import "BTBall.h"
#implementation BTBall
const CGFloat BALLSIZE = 80;
+(BTBall *)newBall {
BTBall *ball = [BTBall node];
//look
[ball setPath:CGPathCreateWithEllipseInRect(CGRectMake(-BALLSIZE/2,-BALLSIZE/2,BALLSIZE,BALLSIZE), nil)];
[ball setFillColor:[UIColor redColor]];
[ball setStrokeColor:[UIColor clearColor]];
//physics
SKPhysicsBody *ballBody = [SKPhysicsBody bodyWithCircleOfRadius:BALLSIZE/2.0];
[ball setPhysicsBody:ballBody];
[ball.physicsBody setAllowsRotation:NO];
//ball is not moving at the beginning
ball.lastVelocity = CGVectorMake(0, 0);
return ball;
}
#end
1. A couple of problems (see comments in code) are related to the spriteKit coordinate system. I just do not get the border of the scene align with its actual frame, though I make it with the exact same code that Apple gives us in the programming guide. I have moved it from initWithSize to didMoveToView due to a suggestion here on Stackoverflow, but that did not help. It is possible to manually offset the border with hardcoded values, but that does not satisfy me.
2. Does anybody know a debugging tool, which colors the physics body of a sprite, in order to see its size and whether it is at the same position as the sprite?
Update: Problems above solved by using YMC Physics Debugger:
This lines of code are correct:
[ball setPath:CGPathCreateWithEllipseInRect(CGRectMake(-BALLSIZE/2,-BALLSIZE/2,BALLSIZE,BALLSIZE), nil)];
SKPhysicsBody *ballBody = [SKPhysicsBody bodyWithCircleOfRadius:BALLSIZE/2.0];
Because 0,0 is the center of the physics body, the origin of the path must be translated.
I've been working on this code for quite a while now but it just feels like one step forward and two steps back. I'm hoping someone can help me.
I'm working with Sprite Kit so I have a Scene file that manages the rendering, UI and touch controls. I have an SKNode thats functioning as the camera like so:
_world = [[SKNode alloc] init];
[_world setName:#"world"];
[self addChild:_world];
I am using UIGestureRecognizer, so I add the ones I need like so:
_panRecognizer = [[UIPanGestureRecognizer alloc]initWithTarget:self action:#selector(handlePanFrom:)];
[[self view] addGestureRecognizer:_panRecognizer];
_pinchRecognizer = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:#selector(handlePinch:)];
[[self view] addGestureRecognizer:_pinchRecognizer];
The panning is working okay, but not great. The pinching is the real problem. The idea for the pinching is to grab a point at the center of the screen, convert that point to the world node, and then move to it while zooming in. Here is the method for pinching:
-(void) handlePinch:(UIPinchGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateBegan) {
_tempScale = [sender scale];
}
if (sender.state == UIGestureRecognizerStateChanged) {
if([sender scale] > _tempScale) {
if (_world.xScale < 6) {
//_world.xScale += 0.05;
//_world.yScale += 0.05;
//[_world setScale:[sender scale]];
[_world setScale:_world.xScale += 0.05];
CGPoint screenCenter = CGPointMake(_initialScreenSize.width/2, _initialScreenSize.height/2);
CGPoint newWorldPoint = [self convertTouchPointToWorld:screenCenter];
//crazy method why does this work
CGPoint alteredWorldCenter = CGPointMake(((newWorldPoint.x*_world.xScale)*-1), (newWorldPoint.y*_world.yScale)*-1);
//why does the duration have to be exactly 0.3 to work
SKAction *moveToCenter = [SKAction moveTo:alteredWorldCenter duration:0.3];
[_world runAction:moveToCenter];
}
} else if ([sender scale] < _tempScale) {
if (_world.xScale > 0.5 && _world.xScale > 0.3){
//_world.xScale -= 0.05;
//_world.yScale -= 0.05;
//[_world setScale:[sender scale]];
[_world setScale:_world.xScale -= 0.05];
CGPoint screenCenter = CGPointMake(_initialScreenSize.width/2, _initialScreenSize.height/2);
CGPoint newWorldPoint = [self convertTouchPointToWorld:screenCenter];
//crazy method why does this work
CGPoint alteredWorldCenter = CGPointMake(((newWorldPoint.x*_world.xScale - _initialScreenSize.width)*-1), (newWorldPoint.y*_world.yScale - _initialScreenSize.height)*-1);
SKAction *moveToCenter = [SKAction moveTo:alteredWorldCenter duration:0.3];
[_world runAction:moveToCenter];
}
}
}
if (sender.state == UIGestureRecognizerStateEnded) {
[_world removeAllActions];
}
}
I've tried many iterations of this, but this exact code is what is getting me the closest to pinching on a point in the world. There are some problems though. As you get further out from the center, it doesn't work as well, as it pretty much still tries to zoom in on the very center of the world. After converting the center point to the world node, I still need to manipulate it again to get it centered properly (the formula I describe as crazy). And it has to be different for zooming in and zooming out to work. The duration of the move action has to be set to 0.3 or it pretty much won't work at all. Higher or lower and it doesn't zoom in on the center point. If I try to increment the zoom by more than a small amount, it moves crazy fast. If I don't end the actions when the pinch ends, the screen jerks. I don't understand why this works at all (it smoothly zooms in to the center point before the delay ends and the screen jerks) and I'm not sure what I'm doing wrong. Any help is much appreciated!
Take a look at my answer to a very similar question.
https://stackoverflow.com/a/21947549/3148272
The code I posted "anchors" the zoom at the location of the pinch gesture instead of the center of the screen, but that is easy to change as I tried it both ways.
As requested in the comments below, I am also adding my panning code to this answer.
Panning Code...
// instance variables of MyScene.
SKNode *_mySkNode;
UIPanGestureRecognizer *_panGestureRecognizer;
- (void)didMoveToView:(SKView *)view
{
_panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanFrom:)];
[[self view] addGestureRecognizer:_panGestureRecognizer];
}
- (void)handlePanFrom:(UIPanGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateBegan) {
[recognizer setTranslation:CGPointZero inView:recognizer.view];
} else if (recognizer.state == UIGestureRecognizerStateChanged) {
CGPoint translation = [recognizer translationInView:recognizer.view];
translation = CGPointMake(-translation.x, translation.y);
_mySkNode.position = CGPointSubtract(_mySkNode.position, translation);
[recognizer setTranslation:CGPointZero inView:recognizer.view];
} else if (recognizer.state == UIGestureRecognizerStateEnded) {
// No code needed for panning.
}
}
The following are the two helper functions that were used above. They are from the Ray Wenderlich book on Sprite Kit.
SKT_INLINE CGPoint CGPointAdd(CGPoint point1, CGPoint point2) {
return CGPointMake(point1.x + point2.x, point1.y + point2.y);
}
SKT_INLINE CGPoint CGPointSubtract(CGPoint point1, CGPoint point2) {
return CGPointMake(point1.x - point2.x, point1.y - point2.y);
}
Forgive me I am kinda new at this.
I am trying to detect a touch like the MoveMe example -- only I have an array of UIViews (studentCell) put in to a NSMutableArray called studentCellArray.
[self.studentCellArray addObject:self.studentCell];
When I have one touch I want to make the program smart enough to know if it has touched any of the UIViews in the array and if it has then to do something.
Here is the code in touchesBegan: method.
//prep for tap
int ct = [[touches anyObject] tapCount];
NSLog(#"touchesBegan for ClassRoomViewController tap[%i]", ct);
if (ct == 1) {
CGPoint point = [touch locationInView:[touch view]];
for (UIView *studentCard in self.studentCellArray) {
//if I Touch a Card then I grow card...
}
NSLog(#"I am here[%#]", NSStringFromCGPoint(point));
}
I don't know how t access the views and touch them.
I "solved" this issue by assigning a UIPanGestureRecognizer to each UIView in the array.
This propbably isn't the best way to do it but I can now move them on screen.
Here is the code:
for (int x = 0; x < [keys count]; x++) {
UIPanGestureRecognizer *pGr = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(dragging:)];
UIView *sca = [self.studentCellArray objectAtIndex:x];
[sca addGestureRecognizer:pGr];
[pGr release];
}
Here is the "dragging" method I used. I have divided the screen into thirds and there is an animation to snap the UIViews to a point if it happens to cross a threshold. I hope this gives someone some good ideas. Please help if you can see any better way to do this.
- (void) dragging:(UIPanGestureRecognizer *)p{
UIView *v = p.view;
if (p.state == UIGestureRecognizerStateBegan || p.state == UIGestureRecognizerStateChanged) {
CGPoint delta = [p translationInView:studentListScrollView];
CGPoint c = v.center;
c.x += delta.x;
//c.y += delta.x;
v.center = c;
[p setTranslation:CGPointZero inView:studentListScrollView];
}
if (p.state == UIGestureRecognizerStateEnded) {
CGPoint pcenter = v.center;
//CGRect frame = v.frame;
CGRect scrollFrame = studentListScrollView.frame;
CGFloat third = scrollFrame.size.width/3.0;
if (pcenter.x < third) {
pcenter = CGPointMake(third/2.0, pcenter.y);
//pop the view
[self showModalDialog:YES perfMode:YES andControlTag:[studentCellArray indexOfObjectIdenticalTo:p.view]];
}
else if (pcenter.x >= third && pcenter.x < 2.0*third) {
pcenter = CGPointMake(3.0*third/2.0, pcenter.y);
}
else
{
pcenter = CGPointMake(5.0 * third/2.0, pcenter.y);
//pop the view
[self showModalDialog:YES perfMode:YES andControlTag:[studentCellArray indexOfObjectIdenticalTo:p.view]];
}
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.2];
v.center = pcenter;
[UIView commitAnimations];
}
}
EDIT: Adding [studentCellArray indexOfObjectIdenticalTo:p.view] to the andControlTag gives me the position in the array of the view touched so i can pass that on the my modal dialog to present the appropriate information.
in a 3D game, after optimization i have hit 60fps on an iPad2, the main runloop is in a secondary thread,
with the interface builder built UI on the main thread, (this question is about the UI of the main thread)
i have two controllers, a throttle up, and a stick for roll in the UI that uses touch interaction, you can see the stick in this image below.
it has a rotation movement of 180 degrees as you can imagine from this image
if i run the game, and then scrub back and forth with my thumb on this stick, (rolling wildly from 0 degrees to -180 degrees) the fps drops to 33fps instead of 60fps..
simply commenting out this line below from the code listing below, brings the fps back to 60fps, (i scrub with my thumb and the game does it's rolling, but the image of this stick of course does not move) so it is possible to play the game at full fps without updating the image of this stick, (showing that it is this routine alone) this updating and transforming of the image with this method is greatly effecting my fps. see this code below, do you think there is a way around this performance hit? (otherwise i'm going to have to have some generic round area with just a circle representing the touch area to give an idea of roll that you see in some games like i think infinity blade.. but i would much rather have this interactive image showing the true purpose. do you see anything that i am missing? need to change? would be better?
joypadCap.transform = CGAffineTransformMakeRotation(touchAngle); //rotation in radians
here is the relevant code from the viewController that is set up, the "joypad" is the image you see above, the throttle is a different image and it is effecting the fps but not to the extent of this single call above because the throttle is just moving the image to the correct place, and not using this CGAffineTransformMakeRotation. but that operation does drop the fps by about 8fps.
#interface stickController : UIViewController
{
IBOutlet UIImageView *throttleStickCap;
IBOutlet UIImageView *joypadCap;
etc......
#implementation stickController
#synthesize frontViewButton, backViewButton, leftViewButton, rightViewButton, upViewButton, gunSight;
//------------------------------------------------------------------------------------
- (void)viewDidLoad
{
NSLog(#"viewDidLoad");
[super viewDidLoad];
throttleStick = throttleStickCap.frame;
throttleStickCenterx = throttleStickCap.center.x;
throttleStickCentery = throttleStickCap.center.y;
throttleStickMax = 72.0f;
throttleStickMin = -7.0f;
joypad = joypadCap.frame;
joypadCenterx = joypadCap.center.x;
joypadCentery = joypadCap.center.y;
joypadMax = 50.0f;
joypadMin = -50.0f;
theStickController = self;
}
//------------------------------------------------------------------------------------
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
NSSet *allTouches = [event allTouches];
for (UITouch *touch in allTouches)
{
CGPoint touchLocation = [touch locationInView:self.view];
if (CGRectContainsPoint(throttleStick, touchLocation))
{
throttleStickTouchHash = [touch hash];
}
if (CGRectContainsPoint(joypad, touchLocation))
{
joypadTouchHash = [touch hash];
CGPoint touchLocation = [touch locationInView:self.view];
float dx = touchLocation.x - (float)joypadCenterx;
float dy = touchLocation.y - (float)joypadCentery;
distance = sqrtf(powf(dx, 2.0f) + powf(dy, 2.0f));
if (distance > joypadMax)
{
enoughDistance = shootDistance;
createBulletSet();
}
}
}
}
//------------------------------------------------------------------------------------
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
NSSet *allTouches = [event allTouches];
for (UITouch *touch in allTouches)
{
if ([touch hash] == throttleStickTouchHash)
{
CGPoint touchLocation = [touch locationInView:self.view];
distance = throttleStickCentery - touchLocation.y;
if (distance > throttleStickMax)
{
throttleStickCap.center = CGPointMake(throttleStickCenterx, throttleStickCentery - throttleStickMax);
throttle = throttleStickMax;
}
else if (distance < throttleStickMin)
{
throttleStickCap.center = CGPointMake(throttleStickCenterx, throttleStickCentery - throttleStickMin);
throttle = throttleStickMin;
}
else
{
throttleStickCap.center = CGPointMake(throttleStickCap.center.x, touchLocation.y);
throttle = distance;
}
throttle *= .10;
throttleStick = throttleStickCap.frame;
}
if ([touch hash] == joypadTouchHash)
{
CGPoint touchLocation = [touch locationInView:self.view];
float dx = touchLocation.x - (float)joypadCenterx;
float dy = touchLocation.y - (float)joypadCentery;
distance = sqrtf(powf(dx, 2.0f) + powf(dy, 2.0f));
if (distance > joypadMax) createBulletSet();
else enoughDistance = shootDistance;
if (dx > joypadMax) roll = joypadMax;
else if (dx < joypadMin) roll = joypadMin;
else roll = dx;
joypad = joypadCap.frame;
touchAngle = atan2(dy, dx) + 1.570796;
if ((dx < 0.0f) && (dy > 0.0f)) touchAngle = -1.570796;
else if ((dx > 0.0f) && (dy > 0.0f)) touchAngle = 1.570796;
// joypadCap.transform = CGAffineTransformMakeRotation(touchAngle); //rotation in radians
}
}
}
//------------------------------------------------------------------------------------
- (void)forceTouchesEnd
{
throttleStickMoving = NO;
throttleStickTouchHash = 0;
throttle = 0.0f;
throttleStickCap.center = CGPointMake(throttleStickCenterx, throttleStickCentery);
throttleStick = throttleStickCap.frame;
joypadMoving = NO;
joypadTouchHash = 0;
roll = 0.0f;
joypadCap.transform = CGAffineTransformMakeRotation(0.0f); //rotation in radians
joypad = joypadCap.frame;
}
//------------------------------------------------------------------------------------
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
{
for (UITouch *touch in touches)
{
if ([touch hash] == throttleStickTouchHash)
{
throttleStickTouchHash = 0;
}
if ([touch hash] == joypadTouchHash)
{
joypadTouchHash = 0;
roll = 0.0f;
joypad = joypadCap.frame;
return;
}
}
}