Using SKPhysicsBody to create a vehicle - macos

I am trying to make a vehicle (in this case a train) move based on player input. If I move the train via an SKAction, the wheels do not rotate. I could use the applyForce method on its physics body, but it I need more control. I need to make it move a certain distance over a certain amount of time. How can this be accomplished?
-(void)didMoveToView:(SKView *)view {
SKTexture *trainBodyTexture = [SKTexture textureWithImageNamed:#"levelselect_trainbody"];
SKSpriteNode *trainBody = [[SKSpriteNode alloc] initWithTexture:trainBodyTexture];
trainBody.zPosition = 0;
trainBody.position = CGPointMake(300, 500);
trainBody.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:trainBody.size];
[self addChild:trainBody];
SKTexture *trainWheelTexture = [SKTexture textureWithImageNamed:#"levelselect_trainwheel"];
SKSpriteNode *trainWheel1 = [[SKSpriteNode alloc] initWithTexture:trainWheelTexture];
trainWheel1.zPosition = 1;
trainWheel1.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:trainWheel1.size.width/2];
trainWheel1.physicsBody.allowsRotation = YES;
trainWheel1.position = CGPointMake(220, 400);
[self addChild:trainWheel1];
SKSpriteNode *trainWheel2 = [[SKSpriteNode alloc] initWithTexture:trainWheelTexture];
trainWheel2.zPosition = 1;
trainWheel2.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:trainWheel2.size.width/2];
trainWheel2.physicsBody.allowsRotation = YES;
trainWheel2.position = CGPointMake(380, 400);
[self addChild:trainWheel2];
SKShapeNode *dot = [SKShapeNode shapeNodeWithCircleOfRadius:10];
dot.zPosition = 2;
dot.fillColor = [NSColor redColor];
dot.position = CGPointMake(0, -20);
[trainWheel1 addChild:dot];
SKPhysicsJointPin *pin = [SKPhysicsJointPin jointWithBodyA:trainBody.physicsBody bodyB:trainWheel1.physicsBody anchor:trainWheel1.position];
SKPhysicsJointPin *pin2 = [SKPhysicsJointPin jointWithBodyA:trainBody.physicsBody bodyB:trainWheel2.physicsBody anchor:trainWheel2.position];
[self.scene.physicsWorld addJoint:pin];
[self.scene.physicsWorld addJoint:pin2];
//[trainWheel1 runAction:[SKAction moveByX:300 y:0 duration:3]];
[trainBody.physicsBody applyForce:CGVectorMake(3000, 0)];
}
UPDATE: Implemented With A Train Class (suggested by Jaffer Sheriff)
Train.h
#import <SpriteKit/SpriteKit.h>
#interface Train : SKSpriteNode
-(void) createPhysics;
-(void) moveLeft;
-(void) moveRight;
#end
Train.m
#import "Train.h"
#interface Train()
#property SKSpriteNode *trainBody, *trainWheelFront, *trainWheelRear;
#property SKPhysicsWorld *physicsWorld;
#end
#implementation Train
-(instancetype) init {
if (self = [super init]) {
[self initTrainBody];
[self initWheels];
}
return self;
}
-(void) initTrainBody {
SKTexture *trainBodyTexture = [SKTexture textureWithImageNamed:#"levelselect_trainbody"];
_trainBody = [[SKSpriteNode alloc] initWithTexture:trainBodyTexture];
_trainBody.zPosition = 0;
_trainBody.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:_trainBody.size];
[self addChild:_trainBody];
}
-(void) initWheels {
SKTexture *_trainWheelTexture = [SKTexture textureWithImageNamed:#"levelselect_trainwheel"];
_trainWheelFront = [[SKSpriteNode alloc] initWithTexture:_trainWheelTexture];
_trainWheelFront.zPosition = 1;
_trainWheelFront.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:_trainWheelFront.size.width/2];
_trainWheelFront.physicsBody.allowsRotation = YES;
_trainWheelFront.position = CGPointMake(-80, -82);
[self addChild:_trainWheelFront];
_trainWheelRear = [[SKSpriteNode alloc] initWithTexture:_trainWheelTexture];
_trainWheelRear.zPosition = 1;
_trainWheelRear.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:_trainWheelRear.size.width/2];
_trainWheelRear.physicsBody.allowsRotation = YES;
_trainWheelRear.position = CGPointMake(80, -82);
[self addChild:_trainWheelRear];
//dot used to see if wheels are rotating, no other point
SKShapeNode *dot = [SKShapeNode shapeNodeWithCircleOfRadius:10];
dot.zPosition = 2;
dot.fillColor = [NSColor redColor];
dot.position = CGPointMake(0, -20);
[_trainWheelFront addChild:dot];
}
//this method is called after the train node is added to the scene in GameScene otherwise will get error adding joints before node is in scene
-(void) createPhysics {
SKPhysicsJointPin *pin = [SKPhysicsJointPin jointWithBodyA:_trainBody.physicsBody bodyB:_trainWheelFront.physicsBody anchor:_trainWheelFront.position];
SKPhysicsJointPin *pin2 = [SKPhysicsJointPin jointWithBodyA:_trainBody.physicsBody bodyB:_trainWheelRear.physicsBody anchor:_trainWheelRear.position];
[self.scene.physicsWorld addJoint:pin];
[self.scene.physicsWorld addJoint:pin2];
}
-(void) moveLeft {
SKAction *rotateLeft = [SKAction rotateByAngle:6*M_PI duration:0.2];
[_trainWheelFront runAction:rotateLeft];
[_trainWheelRear runAction:rotateLeft];
}
-(void) moveRight {
SKAction *rotateRight = [SKAction rotateByAngle:-6*M_PI duration:0.2];
[_trainWheelFront runAction:rotateRight];
[_trainWheelRear runAction:rotateRight];
}
#end
GameScene.m
#import "GameScene.h"
#import "Train.h"
#interface GameScene()
#property Train *train;
#end
#implementation GameScene
-(void)didMoveToView:(SKView *)view {
[self initTrain];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyPressed:) name:#"KeyPressedNotificationKey" object:nil]; //using notifications and custom view class to handle key presses
}
-(void) initTrain {
_train = [[Train alloc] init];
_train.position = CGPointMake(500, 300);
[self addChild:_train];
[_train createPhysics];
}
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
}
-(void) keyPressed:(NSNotification*)notification {
NSNumber *keyCodeObject = notification.userInfo[#"keyCode"];
NSInteger keyCode = keyCodeObject.integerValue;
NSLog(#"keycode = %lu", keyCode);
switch (keyCode) {
case 123:
[self leftArrowPressed];
break;
case 124:
[self rightArrowPressed];
break;
}
}
-(void) leftArrowPressed {
SKAction *moveLeft = [SKAction moveByX:-200 y:0 duration:0.2];
[_train runAction:moveLeft];
[_train moveLeft];
}
-(void) rightArrowPressed {
SKAction *moveRight = [SKAction moveByX:200 y:0 duration:0.2];
[_train runAction:moveRight];
[_train moveRight];
}
#end
Note: This solution causes the entire train to flip and freak out when the left/right keys are pressed. It seems like my pin joints are incorrect, but they seem correct to me :/

Default Initializer of SkSpriteNode is - (instancetype)initWithTexture:(SKTexture *)texture color:(SKColor *)color size:(CGSize)size; Try this,
#interface Train : SKSpriteNode
- (instancetype)initTrainWithColor:(UIColor *) color andSize:(CGSize) size;
-(void) animateWheelsWithTime:(float) time;
#end
#interface Train ()
{
SKSpriteNode *wheel1;
SKSpriteNode *wheel2;
NSMutableArray *wheelsArray;
}
#end
#implementation Train
- (instancetype)initTrainWithColor:(UIColor *) color andSize:(CGSize) size
{
self = [super initWithTexture:nil color:color size:size];
if (self)
{
[self addWheels];
}
return self;
}
-(void)addWheels
{
wheelsArray = [[NSMutableArray alloc]init];
wheel1 = [SKSpriteNode spriteNodeWithImageNamed:#"wheel"];
[wheel1 setPosition:CGPointMake(-(self.frame.size.width/2.0f-wheel1.frame.size.width/2.0f), - (self.frame.size.height/2.0f - wheel1.frame.size.height/2.0f))];
[self addChild:wheel1];
wheel2 = [SKSpriteNode spriteNodeWithImageNamed:#"wheel"];
[wheel2 setPosition:CGPointMake((self.frame.size.width/2.0f-wheel2.frame.size.width/2.0f), - (self.frame.size.height/2.0f - wheel2.frame.size.height/2.0f))];
[self addChild:wheel2];
[wheelsArray addObject:wheel1];
[wheelsArray addObject:wheel2];
}
-(void) animateWheelsWithTime:(float) time
{
for (SKSpriteNode *wheel in wheelsArray)
{
SKAction *act = [SKAction rotateByAngle:3*M_PI duration:time];
[wheel runAction:act];
}
}
#end
In GameScene.m add train like this,
-(void) addTrain
{
Train *train1 = [[Train alloc]initTrainWithColor:[UIColor yellowColor] andSize:CGSizeMake(150, 100)];
[train1 setPosition:CGPointMake(self.size.width/2.0f, self.size.height/2.0f)];
[self addChild:train1];
SKAction *act = [SKAction moveTo:CGPointMake(self.size.width/2.0f, self.size.height - 50) duration:2];
[train1 runAction:act];
[train1 animateWheelsWithTime:2];
}
I tried and it works.

Related

EDITED: MapKit Annotation callouts. Adjust size of the UIPopoverController

Sorry, I have read a bunch of tutorials how to create a custom Callout for MapKit Annotation. It works with NSLog, but I cannot display the information in the Callouts.
I have two type of icons on the map. This is my viewForAnnotation method:
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
if (! [annotation isKindOfClass:[IGAMapAnnotation class]]) {
return nil;
}
IGAMapAnnotation *myLocation = (IGAMapAnnotation *) annotation;
self.typeIsFix = [myLocation.navaidType isEqualToString:#"FIX"];
self.typeIsPort = [myLocation.navaidType isEqualToString:#"PORT"];
int planeImageViewTag = 42;
NSString *reuseId;
if (self.typeIsPort)
reuseId = #"IGAMapAnnotationPort";
else if (self.typeIsFix)
reuseId = #"IGAMapAnnotationFix";
else
reuseId = #"IGAMapAnnotationOther";
MKAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:reuseId];
if (annotationView == nil)
{
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseId];
annotationView.enabled = YES;
UIButton *annotationInfo = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
annotationView.rightCalloutAccessoryView = annotationInfo;
annotationView.canShowCallout = YES;
if (self.typeIsPort)
{
annotationView.image = [UIImage imageNamed:#"mapPORT.png"];
annotationView.centerOffset = CGPointMake(0, 0);
}
else if (self.typeIsFix)
{
annotationView.image = [UIImage imageNamed:#"mapFIX.png"];
annotationView.centerOffset = CGPointMake(0, 0);
}
else
return nil;
}
else
{
annotationView.annotation = annotation;
}
return annotationView;
}
Then I have a calloutAccessoryControlTapped method
- (void)mapView:(MKMapView *)mapview annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
IGAAnnotationInfoViewController *popOverCallout = [[IGAAnnotationInfoViewController alloc]init];
UIPopoverController *popOver = [[UIPopoverController alloc] initWithContentViewController:popOverCallout];
popOver.popoverContentSize = CGSizeMake(300, 200);
[popOver presentPopoverFromRect:view.bounds
inView:view
permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
I have also created a UIViewController which I assigned to UIPopoverController.
Now, when I tap the button on my annotation I see a white space for text. Great. If I assign text to a label in UIViewController, it also works great (the following is my UIViewController.m):
- (void)viewDidLoad {
[super viewDidLoad];
txtCallout = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 0.0, 300, 200) ];
txtCallout.font = [UIFont fontWithName:#"Arial Rounded MT Bold" size:(14.0)];
txtCallout.numberOfLines = 0;
txtCallout.clipsToBounds = YES;
txtCallout.backgroundColor = [UIColor clearColor];
txtCallout.textColor = [UIColor blackColor];
txtCallout.textAlignment = NSTextAlignmentLeft;
txtCallout.text = #"text\ntext\ntext";
[self.view addSubview:txtCallout];
}
But how do I insert the text from my annotation method? Also the text must be different depending on the icon type, say #"PORT, PORT" or #"FIX,FIX". How do I do it?
EDIT:
I have managed to display callouts with the necessary information passed. My last problem is that sometimes my callout is 3 lines, sometimes -- as many as 15. How is it possible to make the callout adjust automatically to the number of lines in my string? Should I modify my popoverContentSize or my label size in the UIViewController?
Thank you so much!
I have figured out how to adjust the MK Annotation callout to a UILabel.
Implement the calloutAccessoryControlTapped method
- (void)mapView:(MKMapView *)mapview annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
// OPTIONAL: Deselecting Annotation View when Callout is tapped
//[mapview deselectAnnotation:view.annotation animated:YES];
NSString *calloutDetails;
IGAMapAnnotation *annotationTapped = (IGAMapAnnotation *)view.annotation;
calloutDetails = [NSString stringWithFormat:#"YOUR TEXT:\nYOURTEXT\n"];
// Declare and initialize the UIViewController that has the label to contain the callout information
IGAAnnotationInfoViewController *detailViewController = [[IGAAnnotationInfoViewController alloc]initWithText:calloutDetails];
UIPopoverController *popOver = [[UIPopoverController alloc] initWithContentViewController:detailViewController];
// Size of the UIPopoverController = size of the label + 40 pts
popOver.popoverContentSize = CGSizeMake(detailViewController.txtCallout.frame.size.width+40,detailViewController.txtCallout.frame.size.height+40);
// Show popover controller
[popOver presentPopoverFromRect:view.bounds
inView:view
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
}
Now, IGAAnnotationInfoViewController.h
#interface IGAAnnotationInfoViewController : UIViewController {
CGRect calloutSize;
}
#property (strong,nonatomic) NSString *calloutInformation;
#property (strong,nonatomic) IGACalloutLabel *txtCallout;
-(IGAAnnotationInfoViewController*) initWithText : (NSString*) calloutText;
IGAAnnotationInfoViewController.m
#implementation IGAAnnotationInfoViewController
#synthesize calloutInformation,txtCallout;
-(IGAAnnotationInfoViewController*) initWithText : (NSString*) calloutText {
self = [super init];
if ( self ) {
calloutInformation = calloutText;
// Creating a label that will display the callout information (passed from IGAAcarasViewController - Map Annotation)
txtCallout = [[IGACalloutLabel alloc] initWithFrame:CGRectMake(20, 20, 0, 0)];
txtCallout.lineBreakMode = NSLineBreakByWordWrapping;
txtCallout.numberOfLines=0;
txtCallout.backgroundColor = [UIColor clearColor];
txtCallout.textColor=[UIColor blueColor];
txtCallout.text = calloutInformation;
[txtCallout drawTextInRect:CGRectMake(10,10,0,0)];
[txtCallout sizeToFit];
[self.view addSubview:txtCallout];
}
return self;
}
Finally, subclass the UILabel class:
implementation IGACalloutLabel
#synthesize topInset, leftInset, bottomInset, rightInset;
- (void)drawTextInRect:(CGRect)rect
{
UIEdgeInsets insets = {topInset,leftInset,bottomInset,rightInset};
return [super drawTextInRect:UIEdgeInsetsInsetRect(rect, insets)];
}
Regards,

How to not run scenes in the background spritekit

I have a game with a menu scene, a play scene, and a game over scene. When I am on one of them is there a way to not run the others in the background. For example on my play scene when you make contact with something it switches to the game over scene. When I'm on the menu scene the play scene runs and when it makes contact it switches to the game over scene.
So My Question is: Can I make it so that the scene won't run in the background? Or a way to delay it until I press the play button on the menu?
Here is the code for the first scene:
#import "WEMenuScene.h"
#import "WEMyScene.h"
#implementation WEMenuScene
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
self.scaleMode = SKSceneScaleModeAspectFill;
SKSpriteNode* background = [SKSpriteNode spriteNodeWithImageNamed:#"landscape"];
background.position = CGPointMake(CGRectGetMidX(self.frame),CGRectGetMidY(self.frame));
background.zPosition = 1000;
[self addChild:background];
[self addChild:[self playButton]];
}
return self;
}
-(SKSpriteNode *) playButton {
SKSpriteNode* play = [SKSpriteNode spriteNodeWithImageNamed:#"Play"];
play.position = CGPointMake(CGRectGetMidX(self.frame), 300);
play.zPosition = 1200;
play.name = #"playButton";
return play;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode* node = [self nodeAtPoint:location];
if ([node.name isEqualToString:#"playButton"]) {
SKScene* playScene = [[WEMyScene alloc] initWithSize:self.size];
SKTransition* transitionPlay = [SKTransition doorsOpenVerticalWithDuration:0.5];
[self.view presentScene:playScene transition:transitionPlay];
}
}
#end
Here is the code for the second scene:
#import "WEMyScene.h"
#import "WECapturedScene.h"
#implementation WEMyScene
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
self.scaleMode = SKSceneScaleModeAspectFill;
[self performSelector:#selector(logs) withObject:nil afterDelay:3.0];
[self performSelector:#selector(moveBackground) withObject:nil afterDelay:0.0];
[self addChild:[self createCharacter]];
[self setUpActions];
}
return self;
}
-(SKSpriteNode *) createCharacter {
SKSpriteNode* holly = [SKSpriteNode spriteNodeWithImageNamed:#"holly1"];
holly.position = CGPointMake(CGRectGetMidX(self.frame), 185);
holly.name = #"holly";
holly.zPosition = 40;
return holly;
}
-(void) logs {
CGPoint startPoint = CGPointMake(480, 175);
SKSpriteNode* logs = [SKSpriteNode spriteNodeWithImageNamed:#"log"];
logs.position = CGPointMake(startPoint.x, startPoint.y);
logs.name = #"logs";
logs.zPosition = 40;
[self addChild:logs];
float spawnLog = arc4random_uniform(3)+ 1.4;
[self performSelector:#selector(logs) withObject:nil afterDelay:spawnLog];
}
-(void) setUpActions {
SKTextureAtlas *atlas = [SKTextureAtlas atlasNamed:#"Holly"];
SKTexture *movetex1 = [atlas textureNamed:#"holly1"];
SKTexture *movetex2 = [atlas textureNamed:#"holly2"];
SKTexture *movetex3 = [atlas textureNamed:#"holly3"];
SKTexture *movetex4 = [atlas textureNamed:#"holly4"];
SKTexture *movetex5 = [atlas textureNamed:#"holly5"];
SKTexture *movetex6 = [atlas textureNamed:#"holly6"];
SKTexture *movetex7 = [atlas textureNamed:#"holly7"];
NSArray *atlasTexture = #[movetex1, movetex2, movetex3, movetex4, movetex5, movetex6, movetex7];
SKAction* atlasAnimation =[SKAction repeatActionForever:[SKAction animateWithTextures:atlasTexture timePerFrame:0.08]];
hollyMovement = [SKAction sequence:#[atlasAnimation]];
SKSpriteNode* holly = (SKSpriteNode*)[self childNodeWithName:#"holly"];
holly.zPosition = 40;
[holly runAction:hollyMovement];
SKAction* moveUp = [SKAction moveByX:0 y:90 duration:0.50];
SKAction* wait = [SKAction moveByX:0 y:0 duration:0.4];
SKAction* moveDown = [SKAction moveByX:0 y:-90 duration:0.4];
SKAction* done = [SKAction performSelector:#selector(jumpDone) onTarget:self];
hollyUp = [SKAction sequence:#[moveUp, wait, moveDown, done]];
}
-(void) jumpDone {
isJumping = NO;
}
-(void) moveBackground {
CGPoint startPoint = CGPointMake(480, 230);
SKSpriteNode* landscape = [SKSpriteNode spriteNodeWithImageNamed:#"landscape"];
landscape.position = CGPointMake(startPoint.x, startPoint.y);
landscape.name = #"landscape";
landscape.zPosition = 1;
[self addChild:landscape];
float spawnbackground = 0.7;
[self performSelector:#selector(moveBackground) withObject:nil afterDelay:spawnbackground];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
if (isJumping == NO) {
isJumping = YES;
SKSpriteNode* holly = (SKSpriteNode*)[self childNodeWithName:#"holly"];
[holly runAction:hollyUp];
}
}
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
SKNode* holly = [self childNodeWithName:#"holly"];
[self enumerateChildNodesWithName:#"landscape" usingBlock:^(SKNode *node, BOOL *stop) {
if (node.position.x < 0 || node.position.y < 0) {
[node removeFromParent];
}else {
node.position = CGPointMake(node.position.x - 10, node.position.y);
}
}];
[self enumerateChildNodesWithName:#"logs" usingBlock:^(SKNode *node, BOOL *stop) {
if (node.position.x < 0 || node.position.y < 0) {
[node removeFromParent];
}else {
node.position = CGPointMake(node.position.x - 10, node.position.y);
}
if ([holly intersectsNode:node]) {
SKScene *capturedScene = [[WECapturedScene alloc] initWithSize:self.size];
SKTransition* transition = [SKTransition doorsOpenVerticalWithDuration:0.5];
[self.view presentScene:capturedScene transition:transition];
}
}];
[self enumerateChildNodesWithName:#"dogCatcher" usingBlock:^(SKNode *node, BOOL *stop) {
node.position = CGPointMake(node.position.x + 10, node.position.y);
}];
}
Here is the view controller:
#import "WEViewController.h"
#import "WEMyScene.h"
#import "WEMenuScene.h"
#implementation WEViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Configure the view.
SKView * skView = (SKView *)self.view;
skView.showsFPS = YES;
skView.showsNodeCount = YES;
// Create and configure the scene.
SKScene * scene = [WEMenuScene sceneWithSize:skView.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
// Present the scene.
[skView presentScene:scene];
}
- (BOOL)shouldAutorotate
{
return YES;
}
- (NSUInteger)supportedInterfaceOrientations
{
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
return UIInterfaceOrientationMaskAllButUpsideDown;
} else {
return UIInterfaceOrientationMaskAll;
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#end
#end

Cocos 2d: adding Sprite on a second loaded scene crashes

I have a cocos2d application for mac, with two scenes. In scene one, when I push a button it loads the second scene by using:
[[CCDirector sharedDirector] replaceScene:[CompetitiveScene node]];
Now, on this second CCScene, in the init method I load a texture using:
CCSpriteBatchNode *parentNodeBullet = [CCSpriteBatchNode batchNodeWithFile:#"bullet.png" capacity:100];
This makes the application crash on CCTextureCache.m : 276
dispatch_sync(_dictQueue, ^{
tex = [_textures objectForKey: path];
});
Apparently _textures has 0 key/values
I have no idea why is this happening, it's such a simple code! In scene1 I simply have one button and one label ...
It gets more strange because if I change my workflow and load the CompetitiveScene first, it works perfectly..
Any idea?
EDIT1
If I perform
CCSprite *player = [CCSprite spriteWithFile:#"bullet.png"]; instead of using the CCSpriteBatchNode, I have the exact same problem :/
EDIT2
If I perform CCTexture2D *texture = [[CCTextureCache sharedTextureCache] addImage:#"bullet.png"];, same problem
EDIT3
Here is the code of the project
First layer
// CompetitiveLayer.m
#import "CompetitiveLayer.h"
#import "cocos2d.h"
#import "CollectableIncreaseAmmo.h"
enum {
kTagParentNode = 1,
};
#interface CompetitiveLayer()
-(void) addNewSpriteAtPosition:(CGPoint)p;
#end
#implementation CompetitiveLayer
-(id) init
{
if( (self=[super init])) {
CGSize s = [CCDirector sharedDirector].winSize;
// enable events
self.mouseEnabled = YES;
// Use batch node. Faster
/*
CCSpriteBatchNode *parentNodeBullet = [CCSpriteBatchNode batchNodeWithFile:#"bullet.png" capacity:10];
spriteTextureBullet_ = [parentNodeBullet texture];
*/
NSImage *img = [NSImage imageNamed:#"bullet.png"];
if(img == nil) // log image is nil
{
CCLOG(#"NIL");
}
CCSpriteBatchNode *parentNodeBullet = [[[CCSpriteBatchNode batchNodeWithFile:#"bullet.png" capacity:15] retain] autorelease];
spriteTextureBullet_ = [parentNodeBullet texture];
[self addChild:parentNodeBullet z:0 tag:kTagParentNode];
[self addNewSpriteAtPosition:ccp(s.width/2, s.height/2)];
}
return self;
}
-(void) addNewSpriteAtPosition:(CGPoint)p
{
CCLOG(#"Add sprite %0.2f x %02.f",p.x,p.y);
/*
CCSprite *player = [CCSprite spriteWithFile:#"bullet.png"];
player.position = ccp(p.x, p.y);
[self addChild:player];
*/
CollectableIncreaseAmmo *sprite2 = [CollectableIncreaseAmmo spriteWithTexture:spriteTextureBullet_];
[sprite2 setPosition: ccp( p.x, p.y)];
[self addChild:sprite2];
/*
CCTexture2D *texture = [[CCTextureCache sharedTextureCache] addImage:#"bullet.png"];
CollectableIncreaseAmmo *sprite2 = [CollectableIncreaseAmmo spriteWithTexture:texture];
[sprite2 setPosition: ccp( p.x, p.y)];
[self addChild:sprite2];
*/
}
- (BOOL) ccMouseDown:(NSEvent *)event
{
CGPoint location = [(CCDirectorMac*)[CCDirector sharedDirector] convertEventToGL:event];
[self addNewSpriteAtPosition: location];
return YES;
}
- (void) dealloc
{
[super dealloc];
}
#end
Second layer
// TitleLayer.m
#import "TitleLayer.h"
#import "CompetitiveScene.h"
#import "cocos2d.h"
#interface TitleLayer()
-(void) createButtons;
-(void) createTitle;
#end
#implementation TitleLayer
-(id) init
{
if( (self=[super init])) {
// enable events
#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED
self.touchEnabled = YES;
self.accelerometerEnabled = YES;
#elif defined(__MAC_OS_X_VERSION_MAX_ALLOWED)
self.mouseEnabled = YES;
#endif
// Create buttons
[self createButtons];
// Create title
[self createTitle];
//[self scheduleUpdate];
}
return self;
}
-(void) createButtons
{
CCMenuItemLabel *competitiveGameButton = [CCMenuItemFont itemWithString:#"Fight" block:^(id sender){
/*
CCScene *s = [CCScene node];
id child = [CompetitiveLayer node];
[s addChild:child];
[[CCDirector sharedDirector] replaceScene: s];
*/
// [[CCDirector sharedDirector] replaceScene: [CCTransitionZoomFlipX transitionWithDuration:1 scene:[CompetitiveScene node] ]];
[[CCDirector sharedDirector] replaceScene:[CompetitiveScene node]];
// We can also push and pop the scene
}];
CCMenuItemLabel *coperativeGameButton = [CCMenuItemFont itemWithString:#"Coperate" block:^(id sender){
/*
CCScene *s = [CCScene node];
id child = [TitleLayer node];
[s addChild:child];
[[CCDirector sharedDirector] replaceScene: s];
*/
}];
CCMenu *menu = [CCMenu menuWithItems:competitiveGameButton, coperativeGameButton, nil];
[menu alignItemsVertically];
CGSize s = [[CCDirector sharedDirector] winSize];
menu.position = ccp(s.width/2, s.height/2);
[self addChild: menu z:-1];
}
-(void) createTitle
{
CGSize s = [CCDirector sharedDirector].winSize;
CCLabelTTF *label = [CCLabelTTF labelWithString:#"Select type of game" fontName:#"Marker Felt" fontSize:32];
[self addChild:label z:0];
[label setColor:ccc3(0,0,255)];
label.position = ccp( s.width/2, s.height-50);
}
- (void) update:(ccTime) time {
CCLOG(#"Testing update");
}
- (void) dealloc
{
[super dealloc];
}
#end

Terminate glView in cocos2d

I made an App using cocos2D that has a hierarchical structure.
And I used a UIButton on glView in a child layer.
When I get back to parent layer, the button still on it's position.
I want to terminate that.
How can I do this?
Here's the code.
MainHallLayer.m
- (id)init
{
self = [super init];
if (self != nil)
{
CCMenu *menuLists = [CCMenu menuWithItems: nil];
menuLists.position = ccp(screenSize.width/2, screenSize.height/2);
[self addChild:menuLists z:10];
NSString *fontName = [NSString stringWithFormat:#"Chalkboard SE"];
CGFloat fontSize = 28;
{
CCLabelTTF *label = [CCLabelTTF labelWithString:#"Touch Field : Getting
touches coordinates" fontName:fontName fontSize:fontSize];
[label setColor:ccc3(0.0, 0.0, 0.0)];
label.anchorPoint = ccp(0, 0.5f);
CCMenuItem *menuItem = [CCMenuItemLabel itemWithLabel:label block:^(id
sender)
{
CCScene *scene = [TouchField1Layer node];
[ReturningNode returnToNodeWithParent:scene];
[[CCDirector sharedDirector] replaceScene:scene];
}];
[menuLists addChild:menuItem];
}
}
return self;
}
ReturnToNode.m
- (id)initWithParentNode:(CCNode *)parentNode
{
self = [super init];
if (self != nil)
{
CGSize screenSize = [[CCDirector sharedDirector]winSize];
CCLabelTTF *label = [CCLabelTTF labelWithString:#" <= Return"
fontName:#"Gill Sans"
fontSize:30.0f];
label.color = ccMAGENTA;
id tint_1 = [CCTintTo actionWithDuration:0.333f red:1.0 green:.0f blue:.0f];
id tint_2 = [CCTintTo actionWithDuration:0.333f red:.5f green:.5f blue:.0f];
id tint_3 = [CCTintTo actionWithDuration:0.333f red:.0f green:1.0 blue:.0f];
id tint_4 = [CCTintTo actionWithDuration:0.333f red:.0f green:.5f blue:.5f];
id tint_5 = [CCTintTo actionWithDuration:0.333f red:.0f green:.0f blue:1.0];
id tint_6 = [CCTintTo actionWithDuration:0.333f red:.5f green:.0f blue:.5f];
id sequence = [CCSequence actions:tint_1,tint_2,tint_3,tint_4,tint_5,tint_6, nil];
id repeatAction = [CCRepeatForever actionWithAction:sequence];
[label runAction:repeatAction];
CCLayerColor *labelBackground = [CCLayerColor layerWithColor:ccc4(0.0, 0.0, 70, 40) width:label.contentSize.width + 20 height:label.contentSize.height + 20];
[label addChild:labelBackground z:-1];
CCMenuItem *menuItem = [CCMenuItemLabel itemWithLabel:label block:^(id sender)
{
[self removeFromParentAndCleanup:YES];
[[CCDirector sharedDirector] replaceScene:[TouchControllerLayer scene]];
}];
CCMenu *menu = [CCMenu menuWithItems:menuItem, nil];
[menu alignItemsVertically];
menu.position = CGPointMake(label.contentSize.width/2 , screenSize.height - 20);
[self addChild:menu];
[parentNode addChild:self z:1000];
}
return self;
}
+ (id)returnToNodeWithParent:(CCNode *)parentNode
{
return [[[self alloc] initWithParentNode:parentNode] autorelease];
}
TouchField1Layer.m
- (id)init
{
self = [super init];
if (self != nil)
{
BackgroundLayer *background = [BackgroundLayer node];
TouchField3Layer *layer = [TouchField3Layer node];
[self addChild:background z:3];
[self addChild:layer z:2];
[self preparingTools];
}
return self;
}
- (void)preparingTools
{
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame = CGRectMake(0, 0, 100, 40);
UIView *glView = [CCDirector sharedDirector].openGLView;
glView.tag = TAG_GLVIEW;
[glView addSubview:button];
}
Any advices, helps are welcome. Always thanks. Bless You.
If you add UIKit views you will have to remove them manually at the appropriate point in time. Cocos2D does not manage the lifetime of UIKit views, only classes derived from CCNode.
For example when changing scenes you will have to remove the UIButton from the glView either in -(void) dealloc or in -(void) cleanup.

XCODE - (iOS) Timing / synchronising a view behaving like a slide show to a video

This one has been doing my head in for months - So time to swallow my pride and reach out for a little help. At the moment this is being done in UIWebView as HTML5/JS controlled system. But UIWebview frankly sux and looking to make this last component native too.
I have a collection of videos and at specific timed points during the video, I am calling a page of instructions that relate to the timed period in the video. The video controls also act as a controller for the instructions pages. So whatever timed point is reached, the corresponding page is animated into place.
I've looked in many, many options, with the closest coming in with http video streaming and using timed metadata to initiate a view, but I am containing the videos locally on the device. And, as yet cannot find anything that looks like it will work. Seems simple enough in principle, but I'll be damned if I can find a decent solution...
Any ideas / pointers?
Here's the last attempt at going native with this before the remainder of my hair fell out - I think I may be seeing where I was heading in the wrong direction, but if you can spare a few moments, I'd really appreciate it!
OBJECTIVE is to have a shoutOut that lives below the video that contains a page of instructions. At x seconds, the content will be refreshed to correspond to that portion of the video and persist until the next shoutOut for fresh content. This I have managed to achieve successfully. Where I have been falling down (a lot) is when I scrub the video back to a previous section, the shoutOut content remains at the position from which I scrubbed and remains there permanently. Or as the code is below, simply doesn't re-apear as it is set to a timed visible duration.
Anyway, here's the code...
Header:
// START:import
#import <UIKit/UIKit.h>
// START_HIGHLIGHT
#import <MediaPlayer/MPMoviePlayerController.h>
#import "CommentView.h"
// END_HIGHLIGHT
// START:def
// START:wiring
#interface MoviePlayerViewController : UIViewController {
UIView *viewForMovie;
// END:wiring
// START_HIGHLIGHT
MPMoviePlayerController *player;
// END_HIGHLIGHT
// START:wiring
UILabel *onScreenDisplayLabel;
UIScrollView *myScrollView;
NSMutableArray *keyframeTimes;
NSArray *shoutOutTexts;
NSArray *shoutOutTimes;
}
#property (nonatomic, retain) IBOutlet UIView *viewForMovie;
// END:wiring
// START_HIGHLIGHT
#property (nonatomic, retain) MPMoviePlayerController *player;
// END_HIGHLIGHT
#property (nonatomic, retain) IBOutlet UILabel *onScreenDisplayLabel;
#property (nonatomic, retain) IBOutlet UIScrollView *myScrollView;
#property (nonatomic, retain) NSMutableArray *keyframeTimes;
// START_HIGHLIGHT
-(NSURL *)movieURL;
- (void)timerAction:(NSTimer*)theTimer;
- (void) playerThumbnailImageRequestDidFinish:(NSNotification*)notification;
- (void)handleTapFrom:(UITapGestureRecognizer *)recognizer;
- (IBAction) getInfo:(id)sender;
- (void)removeView:(NSTimer*)theTimer;
// END_HIGHLIGHT
// START:wiring
#end
// END:def
// END:wiring
// END:import
Main:
#implementation MoviePlayerViewController
// START:synth
#synthesize player;
#synthesize viewForMovie;
#synthesize onScreenDisplayLabel;
#synthesize myScrollView;
#synthesize keyframeTimes;
// END:synth
// Implement loadView to create a view hierarchy programmatically, without using a nib.
// START:viewDidLoad
// START:viewDidLoad1
- (void)viewDidLoad {
[super viewDidLoad];
keyframeTimes = [[NSMutableArray alloc] init];
shoutOutTexts = [[NSArray
arrayWithObjects:#"This is a test\nLabel at 2 secs ",
#"This is a test\nLabel at 325 secs",
nil] retain];
shoutOutTimes = [[NSArray
arrayWithObjects:[[NSNumber alloc] initWithInt: 2],
[[NSNumber alloc] initWithInt: 325],
nil] retain];
self.player = [[MPMoviePlayerController alloc] init];
self.player.contentURL = [self movieURL];
// END:viewDidLoad1
self.player.view.frame = self.viewForMovie.bounds;
self.player.view.autoresizingMask =
UIViewAutoresizingFlexibleWidth |
UIViewAutoresizingFlexibleHeight;
[self.viewForMovie addSubview:player.view];
[self.player play];
// START_HIGHLIGHT
[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(timerAction:) userInfo:nil repeats:YES];
// END_HIGHLIGHT
// START:viewDidLoad1
[self.view addSubview:self.myScrollView];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(movieDurationAvailable:)
name:MPMovieDurationAvailableNotification
object:nil];
}
// END:viewDidLoad
// END:viewDidLoad1
// START:movieURL
-(NSURL *)movieURL
{
NSBundle *bundle = [NSBundle mainBundle];
NSString *moviePath =
[bundle
pathForResource:#"BigBuckBunny_640x360"
ofType:#"m4v"];
if (moviePath) {
return [NSURL fileURLWithPath:moviePath];
} else {
return nil;
}
}
// END:movieURL
int position = 0;
- (void)timerAction:(NSTimer*)theTimer {
NSLog(#"hi");
int count = [shoutOutTimes count];
NSLog(#"count is at %d", count);
if (position < count) {
NSNumber *timeObj = [shoutOutTimes objectAtIndex:position];
int time = [timeObj intValue];
NSLog(#"time is at %d", time);
if (self.player.currentPlaybackTime >= time) {
CommentView *cview = [[CommentView alloc]
initWithText:[shoutOutTexts objectAtIndex:position]];
[self.player.view addSubview:cview];
position++;
[NSTimer scheduledTimerWithTimeInterval:4.0f target:self selector:#selector(removeView:) userInfo:cview repeats:NO];
}
}
}
- (void)removeView:(NSTimer*)theTimer {
UIView *view = [theTimer userInfo];
[view removeFromSuperview];
}
/*
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[super viewDidLoad];
}
*/
// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[super dealloc];
}
- (void) movieDurationAvailable:(NSNotification*)notification {
MPMoviePlayerController *moviePlayer = [notification object];
int duration = [moviePlayer duration];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(playerThumbnailImageRequestDidFinish:)
name:MPMoviePlayerThumbnailImageRequestDidFinishNotification
object:nil];
NSMutableArray *times = [[NSMutableArray alloc] init];
for(int i = 0; i < 20; i++) {
[times addObject:[NSNumber numberWithInt:5+i*((duration)/20)]];
}
[self.player requestThumbnailImagesAtTimes:times timeOption: MPMovieTimeOptionNearestKeyFrame];
}
int p = 0;
int ll=0;
- (void) playerThumbnailImageRequestDidFinish:(NSNotification*)notification {
NSDictionary *userInfo;
userInfo = [notification userInfo];
NSNumber *timecode;
timecode = [userInfo objectForKey: #"MPMoviePlayerThumbnailTimeKey"];
[keyframeTimes addObject: timecode];
UIImage *image;
image = [userInfo objectForKey: #"MPMoviePlayerThumbnailImageKey"];
int width = image.size.width;
int height = image.size.height;
float newwidth = 75 * ((float)width / (float)height);
self.myScrollView.contentSize = CGSizeMake((newwidth + 2) * 20, 75);
UIImageView *imgv = [[UIImageView alloc] initWithImage:image];
[imgv setUserInteractionEnabled:YES];
[imgv setFrame:CGRectMake(ll, 0, newwidth, 75.0f)];
ll+=newwidth + 2;
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(handleTapFrom:)];
[tapRecognizer setNumberOfTapsRequired:1];
[imgv addGestureRecognizer:tapRecognizer];
[tapRecognizer release];
[myScrollView addSubview:imgv];
}
- (void) getInfo:(id)sender
{
MPMovieMediaTypeMask mask = self.player.movieMediaTypes;
NSMutableString *mediaTypes = [[NSMutableString alloc] init];
if (mask == MPMovieMediaTypeMaskNone) {
[mediaTypes appendString:#"Unknown Media Type"];
} else {
if (mask & MPMovieMediaTypeMaskAudio) {
[mediaTypes appendString:#"Audio"];
}
if (mask & MPMovieMediaTypeMaskVideo) {
[mediaTypes appendString:#"Video"];
}
}
MPMovieSourceType type = self.player.movieSourceType;
NSMutableString *sourceType = [[NSMutableString alloc] initWithString:#""];
if (type == MPMovieSourceTypeUnknown) {
[sourceType appendString:#"Source Unknown"];
} else if (type == MPMovieSourceTypeFile) {
[sourceType appendString:#"File"];
} else if (type == MPMovieSourceTypeStreaming) {
[sourceType appendString:#"Streaming"];
}
CGSize size = self.player.naturalSize;
onScreenDisplayLabel.text = [NSString stringWithFormat:#"[Type: %#] [Source: %#] [Time: %.1f of %.f secs] [Playback: %.0fx] [Size: %.0fx%.0f]",
mediaTypes,
sourceType,
self.player.currentPlaybackTime,
self.player.duration,
self.player.currentPlaybackRate,
size.width,
size.height];
}
- (void)handleTapFrom:(UITapGestureRecognizer *)recognizer {
NSArray *subviews = [myScrollView subviews];
for (int i = 0; i < 20; i++) {
if (recognizer.view == [subviews objectAtIndex:i]) {
NSNumber *num = [keyframeTimes objectAtIndex:i];
self.player.currentPlaybackTime = [num intValue];
return;
}
}
}
#end
The Comment View Header:
#import <UIKit/UIKit.h>
#interface CommentView : UIView {
}
- (id)initWithFrame:(CGRect)frame andText:(NSString *) text;
- (id)initWithText:(NSString *) text;
#end
The Comment View Main:
#import "CommentView.h"
#implementation CommentView
- (id)initWithFrame:(CGRect)frame andText:(NSString *) text {
if ((self = [super initWithFrame:frame])) {
UIImage *image = [UIImage imageNamed:#"comment.png"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
[self addSubview:imageView];
CGRect rect = CGRectMake(20, 20, 200.0f, 90.0f);
UILabel *label = [[UILabel alloc] initWithFrame:rect];
label.text = text;
label.numberOfLines = 3;
label.adjustsFontSizeToFitWidth = YES;
label.textAlignment = UITextAlignmentCenter;
label.backgroundColor = [UIColor clearColor];
[self addSubview:label];
}
return self;
}
- (id)initWithText:(NSString *) text {
if ((self = [super init])) {
UIImage *image = [UIImage imageNamed:#"comment.png"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
[self addSubview:imageView];
CGRect rect = CGRectMake(20, 20, 200.0f, 90.0f);
UILabel *label = [[UILabel alloc] initWithFrame:rect];
label.text = text;
label.numberOfLines = 3;
label.adjustsFontSizeToFitWidth = YES;
label.textAlignment = UITextAlignmentCenter;
label.backgroundColor = [UIColor clearColor];
[self addSubview:label];
}
return self;
}
- (void)dealloc {
[super dealloc];
}
#end
Thoughts anyone?
Cheers!
What's wrong with monitoring currentPlaybackTime at regular intervals (assuming you are using an instance that implements MPMediaPlayback for playback).

Resources