CALayer -hitTest: not respecting containsPoint: overload - iphone

Back again and have a different question with the same function that I posted before:
- (AIEnemyUnit *) hitTestForEnemyUnit:(CGPoint)where {
CALayer * layer = [self hitTest:where];
while (layer) {
if ([layer isKindOfClass:[AIEnemyUnit class]]) {
return (AIEnemyUnit *)layer;
} else {
layer = layer.superlayer;
}
}
return nil;
}
I have a bomb that the user drags on top of the enemy so that it is displayed directly above the AIEnemyUnit. For this bomb I implemented the CALayer -containsPoint: to return NO during a drag to allow -hitTest: to pass through the layer. Basically this type of hit testing was working fine with these "pass-through" layers as long as I only used CGImageRef contexts. However once I started implementing sublayers for additional effects on the bomb, -hitTest: immediately broke. It was obvious, the new layers were capturing the -hitTest:. I tried implementing the same technique by overloading -containsPoint: for these layers, but it was still returning the bomb's generic CALayer subclass instead of passing through.
Is there a better way?

Maybe the "where" point is not relative to your "self" layer. You need to convert these points between the layers coordinate systems using:
– convertPoint:fromLayer: or
– convertPoint:toLayer:
See http://developer.apple.com/library/ios/documentation/GraphicsImaging/Reference/CALayer_class/Introduction/Introduction.html#//apple_ref/occ/instm/CALayer/convertPoint:fromLayer:

I resolved this by putting everything on a second "root" layer (called "gameLayer") the same size as the original. Then during the UIPanGestureRecognizer, I move the bomb element from "gameLayer" into my UIView.layer. Then while I am testing for a AIEnemyUnit, I only run the hitTest on the "gameLayer".
- UIView.layer --------- gameLayer
| |
dragObj(bomb) gameElements
|
bomb

Related

Dual Virtual Joystick for iOS simultaneous use

I'm using a virtual joystick for SpriteKit, and it works great. I have tried either JCInput version, or SpriteKit-Joystick version. Both were used for the movement, and used them on the left.
This is the code I used, although the documentation is very good in the gitHub anyway:
For JCInput version:
self.joystick = [[JCJoystick alloc] initWithControlRadius:40 baseRadius:45 baseColor:[SKColor blueColor] joystickRadius:25 joystickColor:[SKColor redColor]];
[self.joystick setPosition:CGPointMake(70,70)];
[self addChild:self.joystick];
and the update function:
-(void)update:(CFTimeInterval)currentTime {
[self.myLabel1 setPosition:CGPointMake(self.myLabel1.position.x+self.joystick.x, self.myLabel1.position.y+self.joystick.y)];
[self.myLabel2 setPosition:CGPointMake(self.myLabel2.position.x+self.imageJoystick.x, self.myLabel2.position.y+self.imageJoystick.y)];
/* Called before each frame is rendered */
}
And for the SpriteKit-Joystick version:
SKSpriteNode *jsThumb = [SKSpriteNode spriteNodeWithImageNamed:#"joystick"];
[jsThumb setScale:0.5f];
SKSpriteNode *jsBackdrop = [SKSpriteNode spriteNodeWithImageNamed:#"dpad"];
[jsBackdrop setScale:0.5f];
self.joystick = [Joystick joystickWithThumb:jsThumb andBackdrop:jsBackdrop];
self.joystick.position = CGPointMake(50,50);
[self addChild:self.joystick];
//I've already declared it in the header file
velocityTick = [CADisplayLink displayLinkWithTarget:self selector:#selector(joystickMovement)];
[velocityTick addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
and the update function:
-(void)joystickMovement
{
if (self.joystick.velocity.x != 0 || self.joystick.velocity.y != 0)
{
[self.myLabel1 setPosition:CGPointMake(self.myLabel1.position.x+.1 *self.joystick.velocity.x, self.myLabel1.position.y+.1 * self.joystick.velocity.y)];
}
}
Now everything works perfectly, and don't have any issue with it. But I need to add another one to rotate my character (let's call it the self.myLabel1). I tried duplicating the object creation (with unique names, parameters and positions to put them to the right side of the screen, but other bits of code being exactly the same as what I used above).
They also work, but the problem is that they don't work simultaneously. I can either use the left one or the right one at any given time, and not together. Do I need to run them on two separate thread? I've tried using two CADisplayLinks with two separate Selectors, and nothing. I tried using the same one, and nothing.
Can anyone shed some light on this shadow?
Thanks a lot in advance.
You should be overriding update in your SKScene instead of using CADisplayLink. You can call joystickMovement from update and achieve the desired effect.
You can read more about the different methods called as SKScene processes each frame in the SKScene class reference.
If you haven't already, you'll also need to set multipleTouchEnabled to true in your SKScene's view. You can do this from your GameViewController with
self.view.multipleTouchEnabled = YES;

How can I check if a SKSpriteNode that has been touched also of some other type that inherits from SKSpriteNode?

I need to check which of my custom - class SKSpriteNode inherited objects is touched. I have some elements in my game like:
#interface Hero : SKSpriteNode
and a non playing character element:
#interface StaticLevelElement : SKSpriteNode
and I need to check which one has been touched and to invoke the proper method accordingly (either the method form the Hero or from the StaticLevelElement class).
Which method could I use to differentiate between the specific types of classes?
You can do something like this :
if ([node isKindOfClass:[StaticLevelElement class]]){
NSLog(#"Touched node is StaticLevelElement");
}else if([node isKindOfClass:[Hero class]]){
NSLog(#"Touched node is Hero");
}
From the docs about isKindOfClass: method:
Returns a Boolean value that indicates whether the receiver is an
instance of given class or an instance of any class that inherits from
that class.
Look at this part:
...or an instance of any class that inherits from that class
You should be careful, because if you subclass StaticLevelElement like this:
#interface SubclassOfStaticLevelElement : StaticLevelElement
#end
And inside touchesBegan do this:
if ([node isKindOfClass:[StaticLevelElement class]]){
NSLog(#"Touched node is StaticLevelElement");
}
The method will return true if you touch both, instances of StaticLevelElement and instances of SubclassOfStaticLevelElement. This is because SubclassOfStaticLevelElement inherits from StaticLevelElement.
So I guess, this is not the optimal method for you. In SpriteKit, in situations like yours, people often use the method below (by setting the SKNode's name property).
Inside your Hero's initializer (or you can name your nodes upon creation inside your scene):
self.name = #"hero";
Then in your touchesBegan:
if ([node.name isEqualToString:#"hero"]){
NSLog(#"Touched node is hero");
}
You could just override the touchesBegan method inside of your SKSpriteNode sub classes, and execute their functions accordingly. This leaves the least amount of change for error, because this method will only be called when the system has determined that the particular node has been touched.
If you need to call some kind of method from the main scene, you could then use NSNotificationCenter to send a message to the scene (This will also work if you need to communicate with many different objects,) you could call the scene methods by having the hero have a property that stores the instance of the scene, or you could have a property called isTouched, which you set to true (Yes) when you are in touchesBegan and false (No) when you are in touchesEnded and touchesCancelled, along with some kind of identification property to eliminate the need to do extensive searching, and pretty much do a call like
if(node.isTouched && node.identifier == "StaticLevelElement")
{
//do stuff pertaining to StaticLevelElement
}

C4: Add panning to an object other than "self"

I watched the C4 tutorial on adding a pan gesture to an object and animating it to return to its original position when the panning is finished. I'm trying to add this to three individual objects. I have it working with one object so far to move it and reset it to a CGPoint, but for it to work, I have to add the pan gesture to "self", not the object. For reference, I'm pretty much using the code from here:
http://www.c4ios.com/tutorials/interactionPanning
If I add the gesture to the object itself, sure, it pans around, but then it just leaves itself at the last touch location. However, I'm assuming that leaving the gesture on "self" will affect more than just the object I want to move, and I want to be able to move the three objects individually.
I'm using roughly the same modification to the "move" method that's used in the example:
-(void)move:(UIPanGestureRecognizer *)recognizer {
[character move:recognizer];
if (recognizer.state == UIGestureRecognizerStateEnded) {
[character setCenter: charStartOrigin];
}
}
And then a new method to spawn the object:
-(void)createCharacters {
character = [C4Shape ellipse:charStart];
[character addGesture:PAN name:#"pan" action:#"move:"];
[self.canvas addShape:character];
}
The example link you are working from is sneaky. Since I knew that there was only going to be one object on the canvas I knew I could make it look like I was panning the label. This won't work for multiple objects, as you have already figured out.
To get different objects to move independently, and recognize when they are done being dragged, you need to subclass the objects and give them their own "abilities".
To do this I:
Subclass C4Shape
Add custom behaviour to the new class
Create subclassed objects on the canvas
The code for each step looks like the following:
subclassing
You have to create a subclass that gives itself some behaviour. Since you're working with shapes I have done it this way as well. I call my subclass Character, its files look like this:
Character.h
#import "C4Shape.h"
#interface Character : C4Shape
#property (readwrite, atomic) CGPoint startOrigin;
#end
I have added a property to the shape so that I can set its start origin (i.e. the point to which it will return).
Character.m
#import "Character.h"
#implementation Character
-(void)setup {
[self addGesture:PAN name:#"pan" action:#"move:"];
}
-(void)move:(UIGestureRecognizer *)sender {
if(sender.state == UIGestureRecognizerStateEnded) {
self.center = self.startOrigin;
} else {
[super move:sender];
}
}
#end
In a subclass of a C4 object, setup gets called in the same way as it does for the canvas... So, this is where I add the gesture for this object. Setup gets run after new or alloc/init are called.
The move: method is where I want to override with custom behaviour. In this method I catch the gesture recognizer, and if it's state is UIGestureRecognizerStateEnded then I want to animate back to the start origin. Otherwise, I want it to move: like it should so I simply call [super move:sender] which runs the default move: method.
That's it for the subclass.
Creating Subclassed Objects
My workspace then looks like the following:
#import "C4WorkSpace.h"
//1
#import "Character.h"
#implementation C4WorkSpace {
//2
Character *charA, *charB, *charC;
}
-(void)setup {
//3
CGRect frame = CGRectMake(0, 0, 100, 100);
//4
frame.origin = CGPointMake(self.canvas.width / 4 - 50, self.canvas.center.y - 50);
charA = [self createCharacter:frame];
frame.origin.x += self.canvas.width / 4.0f;
charB = [self createCharacter:frame];
frame.origin.x += self.canvas.width / 4.0f;
charC = [self createCharacter:frame];
//5
[self.canvas addObjects:#[charA,charB,charC]];
}
-(Character *)createCharacter:(CGRect)frame {
Character *c = [Character new];
[c ellipse:frame];
c.startOrigin = c.center;
c.animationDuration = 0.25f;
return c;
}
#end
I have added a method to my workspace that creates a Character object and adds it to the screen. This method creates a Character object by calling its new method (I have to do it this way because it is a subclass of C4Shape), turns it into an ellipse with the frame I gave it, sets its startOrigin, changes its animationDuration.
What's going on with the rest of the workspace is this (NOTE: the steps are marked in the code above):
I #import the subclass so that I can create objects with it
I create 3 references to Character objects.
I create a frame that I will use to build each of the new objects
For each object, I reposition frameby changing its origin and then use it to create a new object with the createCharacter: method I wrote.
I add all of my new objects to the canvas.
NOTE: Because I created my subclass with a startOrigin property, I am able within that class to always animate back to that point. I am also able to set that point from the canvas whenever I want.

How exactly to subclass CALayer and use a custom property?

I am trying to create a subclass of CALayer with a custom index property that I can both animate and change directly in order to display a different picture based on the index.
In the header, I declared:
#property NSUInteger index;
In the implementation, I overrode needDisplayForKey:
+ (BOOL)needsDisplayForKey:(NSString *)key
{
if ([key isEqualToString:#"index"])
return YES;
else
return [super needsDisplayForKey:key];
}
Now, in the display method, I want to display a picture based on the index. However, during an animation, the value of self.index never changes, so I have to query the value of the presentation layer, contrary to the example on Providing CALayer Content by Subclassing:
- (void)display
{
NSUInteger presentationIndex = [(CustomLayer *)[self presentationLayer] index];
self.contents = (id)[[images objectAtIndex:presentationIndex] CGImage];
}
The problem is, if I do that, I cannot set the index value directly outside of an animation, because it will only change the model layer, and the display method explicitly queries the presentation layer.
If I add an initialization method that copies the value of index, it works:
- (id)initWithLayer:(id)layer
{
self = [super initWithLayer:layer];
if (self) {
if ([layer isKindOfClass:[CustomLayer class]])
self.index = [(CustomLayer *)layer index];
}
return self;
}
However, after or before an animation, there is always a 1 image glitch because the presentation or the model value don't match (depending if I set index to the destination value or not).
Surprisingly, it seems like the drawInContext: method always has
the right value for [self index], but it is not the method I want to use since I just set the content property with an image.
I get different behaviors depending on the way I implement the index property. If I use #dynamic index (which works, even though the documentation doesn't say that custom property getters/setters would be dynamically implemented), display is called every time the value of index is changed. If I use #synthesize or implement a setter, display is not called, so I would need to change content in the setter too.
Should I use an instance variable? Should I use the dynamic implementation? Or should I use setValue:forKey: instead?
As you can see, I am a bit confused about how to get the result I want, and how to correctly implement a subclass of CALayer with a custom property. Any help, and explanations, would be appreciated!
There is an excellent tutorial on the web that explains how to create a custom CALayer subclass with animatable properties. I haven't had occasion to try it yet, but I bookmarked it. Here is the link:
Animating Pie Slices Using a Custom CALayer
It looks like the main tricks are:
Use #dynamic, not #synthesize for the animatable properties of your custom CALayer.
Override actionForKey:, initWithLayer:, and needsDisplayForKey:, and perhaps drawInContext:

Adding object to multiple views

Im have a subclass of UIView, called PinView, which contains an image. Basically PinView gets added multiple times to my app, and then I perform a transform on PinView objects. The issue is that when I add a lot of PinViews, my app gets sluggish because I am transforming each PinView.
Ideally, I want to have one 'static' PinView that gets added to a UIView multiple times but i only have to transform it once. Currently this doesn't seem to work. Every time I add the static PinView to my UIView, it will only ever appear in the UIView once (due to it only being able to have one superview I'm guessing).
Im struggle to find out the best way to go about solving this problem - how do I use a single pointer to a PinView, add it multiple times to a UIView and be able to perform a transform on the PinView which gets passed on to PinViews displayed in my UIView? (by the way, the transform is always the same for all the PinViews).
Im assuming this will be the best way to get a performance improvement, if this is not the case please let me know.
UPDATE:
- (void)layoutSubviews
{
CGAffineTransform transform = CGAffineTransformMakeScale(1.0/self.zoomValue, 1.0/self.zoomValue);
NSMutableArray *mut = nil;
PinView *pinView = nil;
CallOutView *callOut = nil;
//get all dictionary entries
for(NSString *identifier in self.annotationsDict.allKeys){
//get the array from dictionary
mut = [(NSArray *)([self.annotationsDict objectForKey:identifier]) mutableCopy];
//change layout if not nil
if([[mut objectAtIndex:PIN] isKindOfClass:[PinView class]]){
pinView = ((PinView *)[mut objectAtIndex:PIN]);
pinView.transform = transform;
[mut replaceObjectAtIndex:PIN withObject:pinView];
}
if([[mut objectAtIndex:CALL_OUT] isKindOfClass:[CallOutView class]]){
callOut = ((CallOutView *)[mut objectAtIndex:CALL_OUT]);
callOut.transform = transform;
[mut replaceObjectAtIndex:CALL_OUT withObject:callOut];
if(pinView !=nil)callOut.center = CGPointMake(pinView.center.x, pinView.center.y - pinView.frame.size.height);
}
[self updateAnnotationsKey:identifier forObject:[NSArray arrayWithArray:mut]];
mut = nil;
pinView = nil;
callOut = nil;
}
}
UPDATE:
Removed the above and now just have:
- (void)layoutSubviews
{
CGAffineTransform transform = CGAffineTransformMakeScale(1.0/self.zoomValue, 1.0/self.zoomValue);
for(UIView *view in self.subviews){
view.transform = transform;
}
}
This can't be done I'm afraid. Each UIView instance can only be added to the screen once.
If all your views have similar transforms, you might have more luck using something like CAReplicatorLayer, which is a system for automatically creating duplicates of CALayers with different transforms.
That will only works if your views are all arranged in a grid or circle or something though. If they are just dotted randomly, it won't help.
If you are trying to draw more than 100 views, you're probably just bumping up against the fundamental performance ceiling of Core Animation on iOS.
The next approach to try would be to use OpenGL to draw your pins, perhaps using a library like Sparrow or Cocos2D to simplify drawing multiple transformed images with OpenGL (I'd recommend Sparrow as it integrates better with other UIKit controls - Cocos is more appropriate for games).
UPDATE:
This code is unnecessary:
mut = [(NSArray *)([self.annotationsDict objectForKey:identifier]) mutableCopy];
if([[mut objectAtIndex:PIN] isKindOfClass:[PinView class]]){
pinView = ((PinView *)[mut objectAtIndex:PIN]);
pinView.transform = transform;
[mut replaceObjectAtIndex:PIN withObject:pinView];
}
The code below is sufficient, because setting the transform doesn't modify the pointer to the object, so it will update the object in the array even if the array isn't mutable, and array objects are declared as 'id' so they don't need to be cast if you assign them to a variable of a known type.
mut = [self.annotationsDict objectForKey:identifier];
if([[mut objectAtIndex:PIN] isKindOfClass:[PinView class]]){
pinView = [mut objectAtIndex:PIN];
pinView.transform = transform;
}
I would also think you can remove the isKindOfClass: check if you only ever use those array indices for those object types. It may seem like a good precaution, but it carries a performance penalty if you're doing it over and over in a loop.
But for 10 views, I just wouldn't expect this to be that slow at all. Have you tried it without moving the callout centres. Does that perform better? If so, can you limit that to just the callouts that are currently visible, or move them using CGAffineTransformTranslate instead of setting the centre (which may be a bit quicker).