I'm having abit of trouble accessing my layers and its driving me insane. Basically, my layer-scene hierachy is as follows:
Compiler.m - CCLayer - holds the +(CCScene) method and loads all the other CCLayers.
Construct.m - CCLayer - holds the box2d engine and its components
Background.m - CCLayer - holds the Background.
Hud.m - CCLayer - holds the HUD.
within the Compiler implementation class, I add the Scene and all the relevant nodes to the Compiler CCLayer:
#implementation Compiler
+(CCScene *) scene{
Compiler *compiler = [CompileLayer node];
CCScene *scene = [CCScene node];
[scene addChild: compiler];
//Add A Background Layer.
Background *layerBackground = [Background node];
layerBackground.position = CGPointMake(100,100);
[compiler addChild: layerBackground z: -1 tag:kBackground];
//Add The Construct.
Construct *construct = [Construct node];
[compiler addChild: construct z: 1];
//Add A Foreground Layer Z: 2
//After background is working.
//Add the HUD
HudLayer *hud = [Hud node];
[compiler addChild: hud z:3];
}
This all works fine, my layers are added to the compiler and the compiler's scene is accessed by the delegate as predicted.
My problem is that im trying to access my Background CCLayers - CCsprite *background, inside the Construct layer so that I can move it according to the position of my Construct game hero.
I've tried many different methods, but I've currently decided to use a class method rather than an instance method to define CCSprite *background so that I can access it in my Construct Layer.
I've also tried accessing with #properties and initialising an instance variable of the class.
Here is my Background CCLayer:
#implementation Background
-(id) init
{
self = [super init];
if (self != nil)
{
CCSprite *temp = [Background bk];
[self addChild:temp z:0 tag:kBackGround];
}
return self;
}
+(CCSprite *)bk{
//load the image files
CCSprite *background = [CCSprite spriteWithFile:#"background.jpg"];
//get the current screen dimensions
//CGSize size = [[CCDirector sharedDirector] winSize];
background.anchorPoint = CGPointMake(0,0);
background.position = ccp(0,0);
return background;
}
#end
This works, it loads the image to the background layer.
Then finally, I try to access the background image from the Construct Layer.
#interface Construct : CCLayer{
CCSprite *tempBack;
}
#end
#implementation Construct
-(id) init{
tempBack = [Background bk]; //The background equals an instance variable
}
-(void) tick:(ccTime)dt {
tempBack.position.x ++; // To do Something here.
tempBack.opacity = 0.5; // Some more stuff here.
}
#end
This doesnt work, I receive a 'nil' pointer in some respects, tempBack doesnt access the background properly, or at all.
How can I access and modify the Background CCLayers Class Variable +(CCSprite) bk??
It probably doesn't work because your tempBack iVar is autoreleased in compiler layer and you don't retain it. Here's how your init method should look like:
-(id) init{
tempBack = [[Background bk] retain]; //The background equals an instance variable
}
Also - don't forget to release it in dealloc. And I am not sure if it will work either since you'll have different background sprite returned by bk class method (try printing temp and tempBack variable addresses and you'll see).
Update for notifications
In background layer, create the sprite and add it to the layer. Then afterwards add this snippet of code to subscribe to notifications:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateBackground:) name:#"PlayerChangedPosition" object:nil];
Then in construct layer's update method (or any other scheduled method) send this notification with new player coordinates:
[[NSNotificationCenter defaultCenter] postNotificationName:#"PlayerChangedPosition" object:player];
Now in background's layer, implement the updateBackground:(NSNotification *)aNotification method:
- (void)updateBackground:(NSNotification *)aNotification {
// object message returns player object passed when posting notification
CGPoint newCoords = [[aNotification object] position];
// adjust background position according to player position
}
Related
I have a custom class Object derived from SKSpriteNode with an SKLabelNode member variable.
#import <SpriteKit/SpriteKit.h>
#interface Object : SKSpriteNode {
SKLabelNode* n;
}
#end
In the implementation, I set up the SKLabelNode.
#import "Object.h"
#implementation Object
- (instancetype) initWithImageNamed:(NSString *)name {
self = [super initWithImageNamed:name];
n = [[SKLabelNode alloc] initWithFontNamed:#"Courier"];
n.text = #"Hello";
n.zPosition = -1;
//[self addChild:n];
return self;
}
Note: I have not yet added the SKLabelNode as a child to Object. I have the line commented out.
I have a separate class derived from SKScene. In this class, I add an instance of Object as a child. This is my touchesBegan:withEvent: method in this class:
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch* touch = [touches anyObject];
CGPoint loc = [touch locationInNode:self];
NSArray* nodes = [self nodesAtPoint:loc]; //to find all nodes at the touch location
SKNode* n = [self nodeAtPoint:loc]; //to find the top most node at the touch location
NSLog(#"TOP: %#",[n class]);
for(SKNode* node in nodes) {
NSLog(#"%#",[node class]);
}
}
When I tap on the instance of Object in my SKScene class, it works as I expected. It detects the node is an instance of Object. It logs:
TOP: Object
Object
Now if I go back to my Object class and uncomment [self addChild:n] so that the SKLabelNode is added as a child to Object, this is logged:
TOP: SKLabelNode
Object
SKLabelNode
SKSpriteNode
Why does adding an SKLabelNode as a child to a class derived from SKSpriteNode cause the touch to detect the object itself, along with an SKLabelNode and an SKSpriteNode?
Furthermore, why is the SKLabelNode on top? I have its zPosition as -1 so I would assume that in touchesBegan:withEvent: of another class, the Object would be detected as on top?
Surely, I am not understanding an important concept.
If you use nodeAtPoint to check for the node in -touchesEnded:, it will definitely return the topmost node at the point of the touch, which in this case, is the SKLabelNode. There are a few ways you can go around this:
Making the subclass detect it's own touches
The cleanest way would be to make the Object subclass detect it's own touches. This can be done by setting it's userInteractionEnabled property to YES. This can be done in the init method itself.
-(instanceType) init {
if (self = [super init]) {
self.userInteractionEnabled = YES;
}
return self;
}
Now, implement the touch delegate methods in the class itself. Since the LabelNode has its userInteractionEnabled property set by default as NO, the touch delegates will still be triggered when it is tapped.
You can use either delegation, NSNotificationCenter or a direct message to the parent in the touch delegate.
-(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
//Send a message to the delegate, which is the SKScene, should be included in protocol
[self.delegate handleTouchOnObjectInstance:self];
//or, send a message directly to the SKScene, the method should be included in the interface.
MyScene *myScene = (MyScene*)self.scene;
[myScene handleTouchOnObjectInstance:self];
}
Checking for the node's parent
In the touch delegate of the SKScene, you can check whether the parent node of the SKLabelNode returned from nodeAtPoint is an instance of the C class.
//Inside the touch delegate, after extracting touchPoint
SKNode *node = [self nodeAtPoint: touchPoint];
if ([node isKindOfClass: [SKLabelNode class]]) {
if ([node.parent isKindOfClass: [Object class]])
[self performOperationOnCInstance: (Object*)node.parent];
} else if ([node isKindOfClass: [Object class]])
[self performOperationOnCInstance: (Object*)node];
Use nodesAtPoint instead of nodeAtPoint
This method returns an array of all nodes detected at a certain point. So if the SKLabelNode is tapped, the nodesAtPoint method will return the Object node as well.
//Inside the touch delegate, after extracting touchPoint
NSArray *nodes = [self nodesAtPoint: touchPoint];
for (SKNode *node in nodes) {
if ( [node isKindOfClass: [Object class]]) {
[self performOperationOnObjectInstance: (Object*)node];
break;
}
}
EDIT:
There is a difference between the zPosition and the node tree.
The zPosition only tells the scene to render nodes in a certain order, whatever their child order may be. This property was introduced to make it easier to specify the order in which a node's children are drawn. If the same were to be achieved by adding the nodes to the parent in a specific order, it becomes difficult to manage the nodes.
When the nodesAtPoint method is called, the scene conducts a depth-first search through the node tree to see which nodes intersect the given touch point. This explains the order in which the array is populated.
The nodeAtPoint method returns the deepest node according to zPosition which the scene could find.
The following section from Apple's documentation explains it all extensively: Creating The Node Tree
a few noobie questions here first time ever I've tried to pause a Scene/Nodes hoping someone can please help me understand what is happening.
I have 2 VC's
GameViewController.m (contains my GameUI related code)
GameScene.m (contains my gameplay related code and my main node called "myWorld").
From GameViewController.m
If I run
_scene.paused = YES;
everything is paused on the screen by the looks of it and nothing moves, but if I unpause the game via
_scene.paused = NO;
you can tell my GameScene.m "myWorld" node has continued to run because when I unpause the game goes into super fast forward and catches up to the position it would have been if I did not pause.
I tried a few other lines like
self.view.paused = YES;
then Xcode suggested that I change to
self->_skView.paused = YES;
When I did this it works! and successfully paused all of the contents including the "myWorld" code inside GameScene.m. But how? I don't understand?
Can someone please tell me what does "->" do here???
Also in my other VC GameScene.m I have a line of code in my update method that is waiting for the game to paused via
if (self.paused==YES)
but when I run this new code from GameViewController.m "self->_skView.paused = YES;"
that if (self.paused==YES) "if" condition is not met inside here.
GameScene.m
-(void)update:(CFTimeInterval)currentTime {
if (self.paused==YES) {
//do stuff
How do I write that the "if" condition so that when GameViewController.m "self->_skView.paused = YES;" the "if" condition is met in GameScene.m?
I guess what I am asking is how do I talk from one VC to the other? Or how do I tell both VC's properly that the game is paused etc?
Any help would be great my code below
GameViewController.m
#implementation GameViewController {
SKView *_skView;
GameScene *_scene;
}
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
if (!_skView) {
_skView =
[[SKView alloc] initWithFrame:self.view.bounds];
GameScene *scene =
[[GameScene alloc] initWithSize:_skView.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
[_skView presentScene:scene];
[self.view addSubview:_skView];
[self.view sendSubviewToBack:_skView];
_scene = scene;
__weak GameViewController *weakSelf = self;
}
}
//My pause method
//was
//_scene.paused = YES;
//now
self->_skView.paused = YES;
//My unpause method
//was
//_scene.paused = NO;
//now
self->_skView.paused = NO;
GameScene.m
-(void)didMoveToView:(SKView *)view {
SKNode *myWorld = [SKNode node];
[self addChild:myWorld];
-(void)update:(CFTimeInterval)currentTime {
if (self.paused==YES) {
//do stuff
I suggest you avoid creating an instance variable (ivar) for your SKScene subclass. If you don't set the ivar to nil at some point, SpriteKit won't release the scene when you transition to another scene. Also, you don't need the ivar for the SKView, since you can access it through self. Here's an example of how to pause the SKView from the view controllers and scene subclasses:
In your view controllers,
pause the SKView with
((SKView *)self.view).paused = YES;
and check if the view is paused with
if (((SKView *).self.view).paused) {
// Do something
}
In your SKScene subclasses (e.g., GameScene),
pause the SKView with
self.view.paused = YES;
and to check if the view is paused with
if (self.view.paused) {
// Do something
}
You can use the delegation mechanism to tell a view controller to pause its view or ask a VC if its view is paused.
I have a CCLayer class that is a child of a base class I created that is, itself, a child of CCLayer.
After creating this type of class hierarchy, I can not get the onEnter method of my child class to successfully schedule a method call. How can I fix this?
I am trying to get started on a gaming project but I've hit a bit of a brick wall. This seems to be a common problem, however, I can't find a scenario similar to mine and, thus, can't derive a solution.
I have a child CCLayer class that I've created, and it is essentially the exact same thing as the IntroLayer in the Cocos2D, starter iPhone template. In fact, my class is the exact same class with one major exception-- I created a base class that I derived from CCLayer, and that is used as my parent of my IntroLayer.
My problem is that in the onEnter method of my child layer, I see that my transition method is being set in my scheduleOnce assignment. The problem, though, is that my transition method is never being called?
I'm not sure what I am doing incorrectly. The code posted below is my .m file for a my IntroLayer.
IntroLayer.m
#implementation IntroLayer //Child of "MyBaseCCLayer"
+(CCScene *) scene
{
CCScene *scene = [CCScene node];
IntroLayer *layer = [IntroLayer node];
[scene addChild: layer];
return scene;
}
-(void) onEnter
{
//This is calling an overridden onEnter of my base CCLayer.
// See snippet below.
[super onEnter];
CGSize size = [[CCDirector sharedDirector] winSize];
CCSprite *background;
background = [CCSprite spriteWithFile:#"Default.png"];
[background setPosition:ccp(size.width/2, size.height/2)];
[background setZOrder: Z_BACKGROUND];
[self addChild: background];
//In one second transition to the new scene
// I can put a break point here, and this being set.
// It never gets called, though.
[self scheduleOnce:#selector(makeTransition:) delay:1.0];
}
-(void) makeTransition:(ccTime)delay
{
//Here is where I would transition to my menu.
// This is a fresh project, so it's going to a test screen.
// The problem is this method never fires.
[[CCDirector sharedDirector]
replaceScene:[CCTransitionFade
transitionWithDuration:0.5
scene:[TestLayer scene]
withColor:ccBLACK]];
}
-(void) dealloc
{
[super dealloc];
}
#end
MyBaseCCLayer.m
//^boilerplate snip^
-(void) onEnter
{
//Setup overlay
CGSize size = [[CCDirector sharedDirector] winSize];
ccTexParams tp = {GL_LINEAR, GL_LINEAR, GL_REPEAT, GL_REPEAT};
//overlay is a class variable, CCSprite object.
overlay = [CCSprite spriteWithFile:#"tile_overlay.png" rect:CGRectMake(0, 0, size.width, size.height)];
[overlay retain];
[overlay setPosition: ccp(size.width/2, size.height/2)];
[overlay.texture setTexParameters:&tp];
//Always on top!
[overlay setZOrder:Z_OVERLAY];
}
-(void) dealloc
{
[overlay release];
[super dealloc];
}
As you can see, there is nothing ground breaking going on here. I created this base class because I want to apply the overlay sprite to ever CCLayer in my application. This is why it made sense to create my own base, CCLayer. However, now that I've created my own base class, my IntroLayer won't call my scheduled method that I'm setting in onEnter. How can I get this to work?
Your custom class MyBaseCCLayer is overriding the onEnter method, but you omitted the call to [super onEnter]. It's easy to forget sometimes, but calling [super onEnter] is critical to the custom class. Here is the description of the method from the Cocos2D documentation:
Event that is called every time the CCNode enters the 'stage'. If the
CCNode enters the 'stage' with a transition, this event is called when
the transition starts. During onEnter you can't access a sibling
node. If you override onEnter, you shall call [super onEnter].
If you take a look inside CCNode.m, you will see what happens inside onEnter:
-(void) onEnter
{
[children_ makeObjectsPerformSelector:#selector(onEnter)];
[self resumeSchedulerAndActions];
isRunning_ = YES;
}
So you can see that if you override the onEnter method in your custom class, and you don't call [super onEnter], you miss out on this functionality. Essentially your custom node will remain in a 'paused' state.
I'm trying to do draw a fancy UIButton using Quartz. I've declared my own button class like this:
#interface MyButton : UIButton
#end
In the .m file I'm constructing the button:
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
// Initialization code
CALayer *buttonLayer = self.layer;
buttonLayer.masksToBounds = YES;
CALayer *customDrawn = [CALayer layer];
customDrawn.delegate = self;
customDrawn.masksToBounds = YES;
[buttonLayer insertSublayer:customDrawn atIndex:0];
[customDrawn setNeedsDisplay];
}
return self;
}
But this results in some kind of recursion and finally fails with a EXC_BAD_ACCESS.
I've implemented a method drawLayer: inContext:, but it still crashes. The only way I can avoid the crash is by removing the delegate-assignment, but then I can't do any of the custom drawing I want to implement.
How can I make this work?
As described in the question mentioned by Kurt Revis, a UIView (such as UIButton) can't be used as a delegate for a sublayer. (It is already the delegate for the "main" layer of the UIView and can't be used as a delegate for another layer.) The solution is to use another object as a delegate which can be a "private" class just used to implement the drawLayer: inContext: method.
I'm trying to draw two sprites on the same screen. I have defined the two sprite objects in two separate class files.
If I comment out two lines (see "item 1" comment below) then I get a display as [a backgroundimage[background2.jpg] with a sprite[grossini.png] on the left side.
If I uncomment the two lines I do not get the background image and sprite of (gameScreen.m). I get only the sprite [grossinis_sister1.png] defined in (enemy.m).
But what I need is a [backgroundimage[background2.jpg]], sprite[grossini.png] and sprite [grossinis_sister1.png] in one screen.
This is implementation file of my first class:
#import "gameScreen.h"
#import "enemy.h"
#implementation gameScreen
-(id)init
{
if((self = [super init]))
{
CCSprite *backGroundImage = [CCSprite spriteWithFile:#"background2.jpg"];
backGroundImage.anchorPoint = ccp(0,0);
CCParallaxNode *voidNode = [CCParallaxNode node];
[voidNode addChild:backGroundImage z:-1 parallaxRatio:ccp(0.0f,0.0f) positionOffset:CGPointZero];
[self addChild:voidNode];
CGSize windowSize = [[CCDirector sharedDirector] winSize];
CCSprite *player = [CCSprite spriteWithFile:#"grossini.png"];
player.position = ccp(player.contentSize.width/2, windowSize.height/2);
[self addChild:player z:0];
//eSprite = [[enemy alloc]init]; //<-- see item 1
//[self addChild:eSprite];
}
return self;
}
This is my implementation file of my second class:
#import "enemy.h"
#import "gameScreen.h"
#implementation enemy
-(id)init
{
if ((self = [super init]))
{
CGSize windowSize = [[CCDirector sharedDirector] winSize];
CCSprite *enemySprite = [CCSprite spriteWithFile:#"grossinis_sister1.png" ];
enemySprite.position = ccp(windowSize.width/2, windowSize.height/2);
[self addChild:enemySprite];
}
return self;
}
#end
The high level understanding you need is this. A screen contains 1 or many layers a layer contains sprites.
So create a screen and then add a layer to it and add the sprites to the layer you created. Of course one can have many screens and a screen can consists of many layers. But in a simple demo game create 1 screen, 1 layer, and add sprites to that layer.
See this link for more detail http://www.cocos2d-iphone.org/wiki/doku.php/prog_guide:basic_concepts.
I am unfamiliar with Cocos2d but the normal pattern is to have a object that would own both gamescreen and enemy sprites and manage both.