I got an UIImageView composed of two images with transparancy channel activated.
The view looks something like this:
image
I would like to be able to detect precisely the touches within the center circle and distinguish them from the ones in the outern circle.
I am thinking of a collision detection algorithm based the difference between two circles. First test in the outern layer to see if there is a collision at all and then in the inner layer. If in the inner layer then activate inner botton otherwise active outern button.
Any help or suggestion in this?
Shall I create a github repo so everyone could contribute in it?
Here something than can help you:
UIImageView *myImageView;
// In viewDidLoad, the place you are created your UIImageView place this:
myImageView.userInteractionEnabled = YES;
UITapGestureRecognizer *tapInView = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapInImageView:)];
[myImageView addGestureRecognizer:tapInView];
}
-(void)tapInImageView:(UITapGestureRecognizer *)tap
{
CGPoint tapPoint = [tap locationInView:tap.view];
CGPoint centerView = tap.view.center;
double distanceToCenter = sqrt((tapPoint.x - centerView.x)*(tapPoint.x - centerView.x) + (tapPoint.y - centerView.y)*(tapPoint.y - centerView.y) );
if (distanceToCenter < RADIUS) {
// It's in center
} else {
// Touch outside
}
I have created a SKSpriteNode for a camera with a physic body size of 0.0 , to avoid unwanted collisions and a world node:
-(void)createSceneContents {
SKNode *world = [SKNode node];
world.name = #"world";
self.anchorPoint = CGPointMake(0.1, 0);
SKSpriteNode *camera = [SKSpriteNode spriteNodeWithColor:[UIColor redColor] size:CGSizeMake(300, 300)];
camera.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(0, 0)];
camera.physicsBody.affectedByGravity = NO;
camera.physicsBody.usesPreciseCollisionDetection = NO;
camera.physicsBody.categoryBitMask = noColisions;
camera.alpha = 0.5;
camera.zPosition = 1;
camera.name = #"cam";
[self addChild:world];
[world addChild:camera];
I've tried a little tutorial to add a camera in a spriteKit platform game, but i can't even move the view, i don't know hoy to access to the property that move the view. Anybody knows what am i doing wrong?
Here's my code:
-(void)didSimulatePhysics
{
//I've tried with #"cam" and #"hero"
[self centerOnNode: [self childNodeWithName:#"world"]];
}
-(void)centerOnNode:(SKNode *) camera {
CGPoint cameraPositionInScene = [camera.scene convertPoint:camera.position fromNode:camera.parent];
[self.parent setPosition:CGPointMake(
camera.parent.position.x - cameraPositionInScene.x,
camera.parent.position.y - cameraPositionInScene.y
)];
}
In the example from Apple's Documentation, which you are following the camera node isn't an SKSprite, it's an SKNode. I think that will fix your problem.
To answer the question from the title, what you're essentially doing is attaching a world node to the scene. Inside this node, all the sprites are placed. As a child to the world node you add another node for the camera.
This gives you three distinct coordinate systems. Imagine, three sheets of paper, the bottom most one is your world, ie the layer with all the sprites. On top of that is a small piece of paper that represents the camera. Above all of this you have a transparent box that represents your viewing area.
The way it's set up it's impossible to move the top most transparent viewing layer. Instead, what you're doing is moving the point that's sits on top of the world layer and then sliding the world layer to that point.
Now imagine, in the paper scenario, this is a 2D scrolling world where you can only go left and right. Now take the camera point and put it all the way to the right most side of the viewing area. Now, take the world layer and drag it to the left until the camera is in the center of the non-moveable viewing area. That is more or less, what's happening.
In Apple's Adventure sample game they don't move the camera but the "World" SKNode which is the top one.
Excerpt from Apple docs on how they do it:
In Adventure all world-related nodes, including background tiles,
characters, and foliage, are children of a world node, which in turn
is a child of the scene. We change the position of this top-of-tree
world node within the scene to give the effect of moving a camera
across the level. By contrast, the nodes that make up the HUD are
children of a separate node that is a direct child of the scene rather
than of the world node, so that the elements in the HUD don’t move
when we “move the camera.”
Read about it more here
to add the previous answers , you should center on your camera , not the world..
so instead of
[self centerOnNode: [self childNodeWithName:#"world"]];
you should use
[self centerOnNode: [self childNodeWithName:#"cam"]];
and dont forget to change your camera to SKNode instead of SKSprite.
.. and for testing, add a moveTo action on your camera node , move it around back and forth to check if your camera centering works. I recommend putting the call in the touchesbegan
example (put this on your scene where your camera is) :
Put these before the #implementation
#interface yourClassNameHere() // edit this to your own class name
#property SKNode *theWorld;
#property SKNode *theCamera;
#property BOOL cameraRunning;
#end
As you see above, i put the nodes (world and camera) on property of this class, so i dont refer them with node name like you did on your post..
Put this on the Implementation section
// Process Camera centering
-(void) didSimulatePhysics {
[self centerOnNode:self.theCamera];
}
-(void) centerOnNode: (SKNode *) node {
CGPoint pos = [node.scene convertPoint:node.position fromNode:node.parent];
CGPoint p = node.parent.position;
node.parent.position = CGPointMake(p.x - pos.x, p.y-pos.y);
}
// .. Move the camera around when you touch , to see if it works..
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (!self.cameraRunning) {
self.cameraRunning = YES;
SKAction *moveUp = [SKAction moveByX:0 y:500 duration:3];
SKAction *moveDown = [SKAction moveByX:0 y:-500 duration:3];
SKAction *moveLeft = [SKAction moveByX:-500 y:0 duration:3];
SKAction *moveRight = [SKAction moveByX:500 y:0 duration:3];
SKAction *sequence = [SKAction sequence:#[moveUp, moveRight,moveDown,moveLeft]];
[self.theCamera runAction:sequence];
} else {
self.cameraRunning = NO;
[self.theCamera removeAllActions];
self.theCamera.position = CGPointZero;
}
}
regards
PS: do you want anchor point 0,0 or 1,1 ? check your anchor point setting there
If you want to move the view, just move the camera:
// Center the view at 100, 0
camera.position = CGPointMake(100, 0);
Here's a slightly longer example here on how to set up a 2D camera system in SpriteKit (in Swift, not ObjC, but easily translated).
I'm having a problem with side scrolling in Cocos2d. What the situation is, is that i have a sprite that contains multiple other sprites know as actions. The user can swipe back and forth horizontally to scroll through the multiple actions. Whats happening now is that it is very jerky and seems to lag and not a smooth scroll but just very choppy. Not sure what the problem is, I've tried to change the time of the animation but that doesn't seem to work.
- (void)translateInventoryForSwipe:(int)xTranslationValue {
NSArray* tempArray = [NSArray arrayWithArray:self.slotsCenterCoordinates];
[self.slotsCenterCoordinates removeAllObjects];
for (NSNumber* i in tempArray) {
NSNumber* newXCoordinate = [NSNumber numberWithInt:[i intValue] + xTranslationValue];
[self.slotsCenterCoordinates addObject:newXCoordinate];
}
[self updatePositionOfActionsInInventory];
}
this method takes in the delta x of the two touches from the parent view. (current touch minus previous touch) This sets the centre coord of all the actions in the scrolling view.
- (void)updatePositionOfActionsInInventory {
for (int inventoryCounter = 0; inventoryCounter < self.inventorySize; inventoryCounter++) {
FFAction* action = [self.actions objectAtIndex:inventoryCounter];
if (action != self.actionBeingDragged)
[self placeAction:action atIndex:inventoryCounter];
}
self.tempAction = nil;
}
- (void)placeAction:(FFAction*)action atIndex:(int)index {
const float yCenterCoordinate = self.boundingBox.size.height/2;
NSNumber* xCenterCoordinate = [self.slotsCenterCoordinates objectAtIndex:index];
CGPoint centerPointForActionAtIndex = ccp([xCenterCoordinate floatValue], yCenterCoordinate);
CCAction* updatePositionAction = [CCMoveTo actionWithDuration:0.03f position:centerPointForActionAtIndex];
if ([action.view numberOfRunningActions] == 0 || self.tempAction == action) {
[action.view runAction:updatePositionAction];
[action.view released];
}
}
this part is from the parent sprite that handles the touch:
CGPoint currentTouch = [self convertTouchToNodeSpace:touch];
CGPoint previousTouch = [touch previousLocationInView:[touch view]];
int translationPoint = currentTouch.x - previousTouch.x;
[self.inventory translateInventoryForSwipe:translationPoint withPoint:currentTouch];
this then sets the action coordinate mimicking a scrolling effect. I'm not sure where its causing the jerky motion but if anyone has any help on the situation it would be awesome!
Assuming all of the complexity in your code is not required, there are several aspects to consider here, I'll go through them one by one.
First, memory allocation is expensive and a lot of it is done in every call of translateInventoryForSwipe:. A whole new NSArray is created and the self.slotsCenterCoordinates is repopulated. Instead, you should iterate the action sprites and reposition them one by one.
This brings us to the second aspect, which is the use of CCAction to move the sprites. A new CCAction is created for every sprite, again causing delay because of the memory allocation. The CCAction is created, even if it would not be used. Also, the use of actions might be the main cause of the lag as a new action won't be accepted until the previous has finished. A better way to do this would be to directly reposition the sprites by delta instead of assigning actions for repositioning. The action is not required to get smooth movement as the frequency of calls to translateInventoryForSwipe: will be high.
You should also consider using float to send the delta value to the method instead of int. The touch coordinates are floats and especially on retina devices this matters as the distance of two pixels is 0.5f.
Based on these aspects, here is a template of what a fixed method could look like. This is not tested, so there may be errors. Also, I assumed that action.view is the actual sprite, as the actions are assigned there.
- (void)translateInventoryForSwipe:(float)xTranslationValue {
for (FFAction *action in self.actions) {
if (action == self.actionBeingDragged)
continue;
// Position the items manually
float xCoordinate = action.view.position.x + xTranslationValue;
float yCoordinate = self.boundingBox.size.height/2;
action.view.position = ccp(xCoordinate, yCoordinate);
}
}
Right now my UIPanGestureRecognizer recognizes every single pan, which is great and necessary, but as I'm using it as a sliding gesture to increase and decrease a variable's value, within the method I only want to act every so often. If I increment by even 1 every time it's detected the value goes up far too fast.
Is there a way to do something like, every 10 pixels of panning do this, or something similar?
You're looking for translationInView:, which tells you how far the pan has progressed and can be tested against your minimum distance. This solution doesn't cover the case where you go back and forth in one direction in an amount equal to the minimum distance, but if that's important for your scenario it's not too hard to add.
#define kMinimumPanDistance 100.0f
UIPanGestureRecognizer *recognizer;
CGPoint lastRecognizedInterval;
- (void)viewDidLoad {
[super viewDidLoad];
recognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(didRecognizePan:)];
[self.view addGestureRecognizer:recognizer];
}
- (void)didRecognizePan:(UIPanGestureRecognizer*)sender {
CGPoint thisInterval = [recognizer translationInView:self.view];
if (abs(lastRecognizedInterval.x - thisInterval.x) > kMinimumPanDistance ||
abs(lastRecognizedInterval.y - thisInterval.y) > kMinimumPanDistance) {
lastRecognizedInterval = thisInterval;
// you would add your method call here
}
}
I have a collection of six seat objects (UIViews with the alpha property set to 0) on my screen and I have player objects basically placed on top of them. The seats may or may not have a player on top of it. What I have right now is I've programmed the player's touchesMoved event so that when I drag a player on top of a seat object the seat's alpha property will go from 0 to 0.6. And then while still dragging the player, if I drag him off the seat the alpha property will go back to 0.
Instead, is there a built in UIView animation that could instead cause the alpha property to kind of fluctuate back and forth between .6 and .2? Kind of a throbbing effect? Would this require core animation or something more advanced?
I'm using the following in the Player's touchesMoved method to drag a player and to detect if it's above a seat:
UITouch *aTouch = [touches anyObject];
self.center = [aTouch locationInView:[self.superview]];
Seat *seat = [controller seatAtPoint:[aTouch locationInView:self.superview]];
if (seat) {
self.hoverSeat = seat;
seat.alpha = .6;
} else {
self.hoverSeat.alpha = 0;
}
The seatAtPoint method in my controller is as follows:
- (Seat *) seatAtPoint:(CGPoint)point {
NSMutableArray seats = [NSMutableArray arrayWithCapacity:6];
for (int i = 1; i <= 6; i++) {
Seat *aSeat = (Seat*)[self.view viewWithTag:i];
[seats addObject:aSeat];
}
for (Seat *seat in seats) {
if (CGRectContainsPoint([seat frame], point)) {
return seat;
}
}
return nil;
}
I use a hoverSeat ivar to hold the seat above which the player is hovering. And then if the seat returned is nil then it sets that seat's alpha to 0.
A bug I'm seeing with this code is if I move the player around the screen a little too quickly sometimes the alpha property won't go back to 0. Can anyone think of a more effective way to ensure that it goes back to 0?
Thank you for any suggestions.
I would look into using CoreAnimation; it's not that hard to use. Here's what it might look like for a fade:
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:.5];
seat.alpha = 1;
[UIView commitAnimations];
You can look into the callbacks that methods like these use to create some kind of pulsing animation ( Trigerring other animation after first ending Animation (Objetive-C) ) when combined with a condition you can probably set it up to loop forever. Also of note is that UIView Animations disable user input when they're running ( Infinitely looping animation ).
As for the bug you mentioned, if it's still happening, you could set up a timer that when fired, iterates through all the seats and checks for ones that aren't being displayed correctly. You might want to set this interval to be pretty infrequent so that it doesn't interrupt or impact the performance of your other animations.