I am having a little trouble doing something that is supposed to be very simple.
I cannot get the floor of my tiles to display above a background picture. However I can get all my other game objects to show from my control pad, to my HUD to even coins and monsters set up in the same tile map. Basically everything appears in front the background like I expect the floor of my tilemap so it looks like im walking on air. I have tried many things like changing which layer i add the background picture or the tilemap floor too, or even tried setting it the same way i set my characters but same results. Tilemap floor is always at the back.
Adding my Set up code, Hope it is helpful too solving the problem.
I created this BG sprite since, I wanted my tilemap to scroll vertically or horzi. automatically. So the easiest way I found to do that was to make the tilemap the child of the "bg" and scroll the "bg" hence scrolling the tile map. However, I have tried setting the background as the child of the bg and setting the Z for both of them but that didnt seem to help.
Thanks in advance for any help in solving this
#implementation GameLevelScene
{
SKNode *_worldNode;
SKSpriteNode *bg;
SKSpriteNode *bkg;
}
Init
-(id)initWithSize:(CGSize)size level:(int)level {
if (self = [super initWithSize:size]) {
// [self showBackground];
NSDictionary *levelData = config[#"levels"][level];
//[show background];
if (levelData[#"tmxFile"]) {
[self showBackground];
_tileMap = [ JSTileMap mapNamed:levelData[#"tmxFile"]];
}
//self.backgroundColor = [SKColor colorWithRed:.4 green:.4 blue:.95 alpha:1.0];
// UIImage *bkgb =[UIImage imageNamed:#"land.jpg"];
// self.position=CGPointZero;
// self.anchorPoint=CGPointZero;
// self.backgroundColor=[UIColor colorWithPatternImage:bkgb];
//Above code shows no picture but it changes the color
[self setUpWorld];
[self createChar];
[self controlPadNode];
//[show background];
}
return self;
}
setUpWorld
- (void)setUpWorld
{
bg = [SKSpriteNode spriteNodeWithImageNamed:#"bg3"];
bg.userInteractionEnabled=NO;
bg.anchorPoint = CGPointZero;
bg.zPosition=0;
bg.position = CGPointZero;
bg.name = #"bg";
[self addChild:bg];
_worldNode = [SKNode node];
if (_tileMap) {
[bg addChild:_tileMap];
}
[bg addChild:_worldNode];
self.physicsWorld.contactDelegate = self;
}
create char
- (void)createChar
{
_Layer = [[TmxTileMapLayer alloc]
initWithTmxObjectGroup:[_tileMap
groupNamed:#"LevelOneObjects"]
tileSize:_tileMap.tileSize
gridSize:_bgLayer.gridSize];
[self addChild:_Layer];
}
Create Control
- (SKSpriteNode *)controlPadNode
//-(void)controlPad
{
SKSpriteNode *controlPadNode = [SKSpriteNode spriteNodeWithImageNamed:#"controller.png"];
controlPadNode.position = CGPointMake(100,50);
controlPadNode.name = #"controlPadNode";
controlPadNode.zPosition = 1.0;
[self addChild:controlPadNode];
}
background
-(void)showBackground
{
bkg = [SKSpriteNode spriteNodeWithImageNamed:#"desert_land.jpg"];
bkg.userInteractionEnabled=NO;
bkg.anchorPoint = CGPointZero;
bkg.position = CGPointZero;
bkg.zPosition=-1;
bkg.name = #"bkg";
// [self addChild:bkg];
//[_tileMap addChild:bkg];
// [_worldNode addChild:bkg];
[bg addChild:bkg];
}
Set your bkg.zposition to 1 and set your bg.zPosition to 2.
Also, the whole point of having a tile map is NOT to use a gigantic background picture but to instead use tiles.
** Update **
I just tested your code and ran a sample project myself. I assume you are using the Tiled app for your tiles. I ran variations on parents (self, worldNode, etc...) and zPositions. The bottom line is you cannot do it. Tiled does not have an alpha channel for its background color options. So either the background image is covered by the tile map or, in your case, the background image covers the tile map.
Your possible solutions are to either not use Tiled and place your tiles manually or to look for another tile app which has an alpha channel.
I noticed that where you define the wall properties of the tile if you say
SKSpriteNode* wall =
[SKSpriteNode spriteNodeWithColor:[SKColor greenColor]
size:CGSizeMake(w, h)];
the green color will be uptop of the background. Bit of a dirty fix than an answer
Related
I develop an iOS game using SpriteKit (such a helpful framework to quickly make a game). I add texture and configure a physical body for a main character as image
The green rectangle is the frame of the physical body. I'm using the following code to create it
#interface MainCharacter : SKSpriteNode
#end
#implementation MainCharacter
+ (instancetype)mainCharacterAtPosition:(CGPoint)pos {
MainCharacter* mainChar = [[MainCharacter alloc] initWithTexture:[SKTexture textureWithImageNamed:#"stand_up"]];
mainChar.position = pos;
mainChar.xScale = 0.5f;
mainChar.yScale = 0.5f;
return mainChar;
}
- (instancetype)initWithTexture:(SKTexture *)texture {
if (self = [super initWithTexture:texture]) {
self.name = kCharacterName;
self.anchorPoint = CGPointMake(0.5f, 0.0f);
[self standup];
CGSize spriteSize = self.size;
CGPoint center = CGPointMake(spriteSize.width*(self.anchorPoint.x-0.5f), spriteSize.height*(0.5f-self.anchorPoint.y));
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:spriteSize center:center];
self.physicsBody.dynamic = NO;
self.physicsBody.categoryBitMask = kCharacterCategory;
self.physicsBody.contactTestBitMask = 0x0;
self.physicsBody.collisionBitMask = 0x0;
}
return self;
}
- (void)standup {
SKAction* standupAction = [SKAction setTexture:self.standupTexture resize:YES];
[self runAction:standupAction];
}
- (void)standdown {
SKAction* standownAction = [SKAction setTexture:self.standdownTexture resize:YES];
[self runAction:standownAction completion:^{
}];
[self performSelector:#selector(standup) withObject:nil afterDelay:1.0f];
}
MainCharacter is a class that inherits from SKSPriteNode, just an convienient class to manage a main character. Stand Up is a first state of the character. I have another state, temporarily called stand down (demonstrate as following image)
I add a swipe down gesture to make character stand down.
The green rectangle also the physical body but it's too large for the character. I want to make a physical body frame as the red rectangle.
Can anyone help me how to make the physical body smaller when my character stand down and enlarge the physical body after it stands up
You can destroy the current physics body self.physicsBody = nil; and then simply create a new one with the new size requirements.
I solve this problem by using 2 nodes for 2 states (as a suggestion): stand up state and stand down state. I named it
standupNode and standdownNode
First, add the standupNode to the game scene. If swipe donw gesture recognize, I remove the standupNode from game scene and add the standdownNode instead. On contrary, removing the standdownNode from the game scene then add the standupNode if character stands up
I'm programming a little Game. For this I need some Walls. Therefor I have used:
Wall[w] = [[SKShapeNode alloc] init];
Wall[w].path = WallPos[w];
Wall[w].lineWidth = 4;
Wall[w].strokeColor = [UIColor whiteColor];
Wall[w].zPosition = 3;
[self addChild: Wall[w]];
Wall is an Array of SKShapeNodes and is set in #interface, so I can use it in every method. WallPos contains CGMutablePathRefs.
In TouchesBegan and TouchesMoved I'm calling a method which should check if you have touched one of the walls.
I have also some SKShapeNodes which are Rectangles, and to check if they are touched, I have used
if ([SomeShape containsPoint: Position] {
//Do some stuff
}
But with a line it's not working. Sometimes I'm touching on the line and nothing happens. Then I've seen this: Detecting Touch on SKShapeNode that is a Line and I have tried to do it on that way:
for (int i = 0; i < NrWalls; i++) {
if (CGRectContainsPoint(Wall[i].frame, Position)) {
[self GameOver];
}
}
But now every Point I touch sets a "Game Over" to me!!
Has anyone an Idea, how could I check if the line is touched?
Thanks for your help!
DXXL
Not sure why you want to use SKShapeNodes for walls and rectangles. To answer your question, you can attach a physics body to your shape node and use the contact methods to check for possible contacts. However, assigning a physics body to a shape node could be a tricky undertaking due to the anchor points and getting a desired alignment.
Seeing that you are really only drawing rectangles for your walls, I suggest you use a SKSpriteNode with a physics body instead. Something like this:
SKSpriteNode *myNode = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(5, 100)];
myNode.position = CGPointMake(100, 100);
myNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:myNode.size];
myNode.physicsBody.dynamic = NO;
myNode.physicsBody.categoryBitMask = CategoryWall;
[self addObject:myNode];
If you need, you can read up on SKPhysicsBody here.
I'm having a little trouble implementing a background change when the player hits a certain score.
I'm using the if(self.score>=10) line to tell my game to change the background but it doesn't seem to be working. I have no errors with this line but no results either.
What I have:
#implementation Scene{
SKScrollingNode * floor;
SKScrollingNode * back;
SKLabelNode * scoreLabel;
}
- (void) createBackground
{
back = [SKScrollingNode scrollingNodeWithImageNamed:#"back" inContainerWidth:WIDTH(self)];
if(self.score>=10){
back = [SKScrollingNode scrollingNodeWithImageNamed:#"image2" inContainerWidth:WIDTH(self)];
[back setAnchorPoint:CGPointZero];
[back setPhysicsBody:[SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame]];
back.physicsBody.categoryBitMask = backBitMask;
back.physicsBody.contactTestBitMask = birdBitMask;
[self addChild:back];
}
there are certain points you have to keep in your mind i think your both background contains physics body
1)may be and the time of transition they first background colliding with second background and they preventing each other to swap
change your collisionBitMask for both background ->prevent them for colliding
for eg
bg.physicsBody.categoryBitMask=bg1;
bg.physicsBody.collisionBitMask=0;
bg.physicsBody.contactTestBitMask=bg2;
bg.physicsBody.categoryBitMask=bg2;
bg.physicsBody.collisionBitMask=0;
bg.physicsBody.contactTestBitMask=bg1;
or use skaction to swap backgrounds
The way i would do this would be to create a brand new SKScene entirely with a new background.
Or if you want them to scroll to the next background. I would create two backgrounds.
Ex.
//this syntax is wrong (dont remember)
//initializing
SKSpriteNode *bg1 = [SKSpritenode spritenodefromImage:#"bg1"];
SKSpriteNode *bg1 = [SKSpritenode spritenodefromImage:#"bg1"];
bg1.position = CGPointMake(self.size.width/2, self.size.height/2);
bg2.position = CGPointMake(self.size.width/2 + bg1.size.width, self.size.height/2);
//////////////////////////////
//then when you want to scroll them
-(void)scrollBackgrounds
{
bg1.position = CGPointMake(bg1.position.x - 5, self.size.height/2);
bg2.position = CGPointMake(bg2.position.x -5 , self.size.height/2);
}
I've made a CCLayer that holds CCSprite and two CCLabelBMFont's. My goal is to create a customized "button" which will scale down when pressed. I've ran into problems with touch and scaling of this layer.
First is the touch, I can't touch the layer bounding box accurately even if I convert the touch like this:
CGPoint currentTouchLocation = [self convertTouchToNodeSpace:touch];
Touch is handled like this:
// Touching shop item?
if(CGRectContainsPoint([self boundingBox], currentTouchLocation)) {
NSLog(#"Pressing item");
mShopItemPushed = true;
return true;
}
return false;
Seems like there is no realistic size boundingBox for a CCLayer with it's contents by default so I figure I need to overwrite one based on the CCLayer contents? Any ideas how I can do this correctly?
Second problem is the scaling of this CCLayer based "button". If I get a touch handling to work somehow, scaling the layer down by half causes the scaled layer to move off tens of pixels from the original position. There are no anchors set but still moves the layer quite a bit to the side and up when scaling. How can I prevent this behavior?
Here is some code of the CCLayer based button:
+(id) shopItem:(NSString*)fileName : (CGPoint)position : (NSString*)itemName : (int)itemPrice
{
return [[[self alloc] initWithShopItemData:fileName:position:itemName:itemPrice] autorelease];
}
-(id) initWithShopItemData:(NSString*)fileName : (CGPoint)position : (NSString*)itemName : (int)itemPrice
{
self = [super init];
[self setPosition:position];
mShopItemPushed = false;
mPicture = [CCSprite spriteWithSpriteFrameName:fileName];
[mPicture setPosition:CGPointMake(position.x - (3.0f * [DeviceSpecific cellSize]), position.y)];
[self addChild:mPicture z:1];
// Make price string
NSString* price = [NSString stringWithFormat:#"%d", itemPrice];
mItemPrice = [CCLabelBMFont labelWithString:price fntFile:[DeviceSpecific scoreAndCoinFont]];
[mItemPrice setScale:0.5f];
[mItemPrice setAnchorPoint:CGPointMake(1.0f, 0.5f)];
[mItemPrice setPosition:CGPointMake(position.x + (3.5f * [DeviceSpecific cellSize]), position.y)];
[self addChild:mItemPrice z:1];
mItemName = [CCLabelBMFont labelWithString:itemName fntFile:[DeviceSpecific scoreAndCoinFont]];
[mItemName setScale:0.5f];
[mItemName setAnchorPoint:CGPointMake(0.0f, 0.5f)];
[mItemName setPosition:CGPointMake(mPicture.position.x + [DeviceSpecific cellSize], mPicture.position.y)];
[self addChild:mItemName z:1];
self.isTouchEnabled = YES;
return self;
}
The [DeviceSpecific cellSize] is just a measuring unit to keep the distances correct on different devices.
I solved this by overwriting the boundingBox -function with a rect based on the outer limits of the items in this layer. Scaling problem remained so I just made another indicator for received touches.
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).