Firstly thanks for taking the time to look at this question.
The problem I am having is with Cocos2d and the Z values which determine where the sprites are in relation to other objects.
I have a sprite ('Ball') and also a score label ('scoreLabel') using CCLabelBMFont and I want the 'Ball' sprite to appear in front of the score label. I am currently attempting this via the z values but it is not working.
I am not sure if its because one is a sprite and the other is not as the below will work when the other object is a sprite but if anyone can point me in the direction of where I am going wrong it would be greatly appreciated.
Relevant code is listed below.
// Constants.h
// TestGame
//#ifndef SpaceViking_Constants_h
//#define SpaceViking_Constants_h
#define kBallSpriteZValue 10
#define kBallSpriteTagValue 0
#define kNewScoreTagValue 0
#define kNewScoreZValue 25
// GamePlayLayer.m
// TestGame
#import "Constants.h"
-(id)init {
self = [super init];
if (self !=nil) {
Ball *ball = [[Ball alloc]
initWithSpriteFrame:[[CCSpriteFrameCache
sharedSpriteFrameCache]
spriteFrameByName:#"Ball_1.png"]];
[ball setPosition:ccp(screenSize.width * 0.19f,
screenSize.height * 0.19f)];
[sceneSpriteBatchNode
addChild:ball
z:kBallSpriteZValue
tag:kBallSpriteTagValue];
scoreLabel = [CCLabelBMFont labelWithString:#"0"
fntFile:#"Test.fnt"];
scoreLabel.position = ccp(screenSize.width * 0.5f, screenSize.height * 0.9f);
[self addChild:scoreLabel
z:kNewScoreZValue tag:kNewScoreTagValue];
}
return self;
}
Things you need to know
z ordering only matters if you nodes are siblings (they have the same parent); the z order is not global in Cocos2d
If no z order is defined than the order of adding the children is used to determine in which order they are rendered
What this means for your use case
Setting the z order for your to CCNode objects as is will make no difference, because they are not siblings.
The ball is the the child of sceneSpriteBatchNode but the label is the child of self.
This means that the ball will already show behind the text, assuming this is your hierarchy:
->sceneSpriteBatchNode->ball
->label
Bonus stuff
Instead of using defines in your code, consider using an enum like so
typedef NS_ENUM(NSUInteger, UTzOrder) {
ballsZorder = 0,
labelsZorder = 1,
otherStuffZorder = 2
};
This is better because the compiler knows that you are using the right types, it is prettier and easier to change.
Remember this is only when they are siblings.
looking at your code I see that you have given higher z value to your label. Instead of giving higher value to your sprite, you should give it to your sprite(ball) so it will be placed on the label.
Whenever you create any objects in cocos2d it will be placed at default z=0 , you can set z value to put object behind current object or above current object. you can set negative z value also to put your label behind your sprite.
ex:
define kBallSpriteZValue 0 //this value should be greater in your case
define kNewScoreZValue -1
you can also reorder your z values later
[self reorderChild:spritename z:newzvalue];
Related
Using cocos2d, I'm trying to replace a sprite (item in my code) with a particle system. This code is placed in my board class. This one works:
// Draw the particles
CCParticleSystem *particles = [[CCParticleSystem alloc] initWithDictionary:_popParticles];
particles.position = ccpSub(item.position,ccp(160,160));
particles.autoRemoveOnFinish = TRUE;
[self addChild:particles];
This one doesn't:
// Draw the particles
CCParticleSystem *particles = [[CCParticleSystem alloc] initWithDictionary:_popParticles];
particles.position = item.position;
particles.autoRemoveOnFinish = TRUE;
[self addChild:particles];
I tried player with this but without success:
particles.positionType = CCPositionTypeMake(CCPositionUnitUIPoints, CCPositionUnitUIPoints, CCPositionReferenceCornerBottomLeft);
My board is a 320x320 points CCSprite with anchor point set at 0.5, 0.5
When I log my item.position value, I get something relative to the bottom left corner of my board (from 30,30 to 290,290)
Is using ccpSub the correct way ?
When you destroy a node you also destroy all of it's children nodes, you said you add your particle to 'item' then you destroy that 'item', which means you have no particle anymore.
I have set up a scene in SceneKit and have issued a hit-test to select an item. However, I want to be able to move that item along a plane in my scene. I continue to receive mouse drag events, but don't know how to transform those 2D coordinates into 3D coordinate in the scene.
My case is very simple. The camera is located at 0, 0, 50 and pointed at 0, 0, 0. I just want to drag my object along the z-plane with a z-value of 0.
The hit-test works like a charm, but how do I translate the mouse point from a drag event into a new position in the scene for the 3D object I am dragging?
You don't need to use invisible geometry — Scene Kit can do all the coordinate conversions you need without having to hit test invisible objects. Basically you need to do the same thing you would in a 2D drawing app for moving an object: find the offset between the mouseDown: location and the object position, then for each mouseMoved:, add that offset to the new mouse location to set the object's new position.
Here's an approach you could use...
Hit-test the initial click location as you're already doing. This gets you an SCNHitTestResult object identifying the node you want to move, right?
Check the worldCoordinates property of that hit test result. If the node you want to move is a child of the scene's rootNode, these is the vector you want for finding the offset. (Otherwise you'll need to convert it to the coordinate system of the parent of the node you want to move — see convertPosition:toNode: or convertPosition:fromNode:.)
You're going to need a reference depth for this point so you can compare mouseMoved: locations to it. Use projectPoint: to convert the vector you got in step 2 (a point in the 3D scene) back to screen space — this gets you a 3D vector whose x- and y-coordinates are a screen-space point and whose z-coordinate tells you the depth of that point relative to the clipping planes (0.0 is on the near plane, 1.0 is on the far plane). Hold onto this z-coordinate for use during mouseMoved:.
Subtract the position of the node you want to move from the mouse location vector you got in step 2. This gets you the offset of the mouse click from the object's position. Hold onto this vector — you'll need it until dragging ends.
On mouseMoved:, construct a new 3D vector from the screen coordinates of the new mouse location and the depth value you got in step 3. Then, convert this vector into scene coordinates using unprojectPoint: — this is the mouse location in your scene's 3D space (equivalent to the one you got from the hit test, but without needing to "hit" scene geometry).
Add the offset you got in step 3 to the new location you got in step 5 - this is the new position to move the node to. (Note: for live dragging to look right, you should make sure this position change isn't animated. By default the duration of the current SCNTransaction is zero, so you don't need to worry about this unless you've changed it already.)
(This is sort of off the top of my head, so you should probably double-check the relevant docs and headers. And you might be able to simplify this a bit with some math.)
As an experiment I implemented Mr Bishop's helpful answer. The drag doesn't quite work (the object - a chess piece - jumps off screen) because of differences in the coordinate magnitudes between the mouse click and the 3-D world. I've inserted log outputs here and there among the code.
I asked on the Apple forums if anyone knew the secret sauce to homogenize the coordinates but didn't get a decisive answer. One thing, I had made some experimental changes to Mr Bishop's method and the forum members advised me to return to his technique.
Despite my code's failings, I thought someone might find it a useful starting point. I suspect there are only one or two small problems with the code.
Note that the log of the world transform matrix of the object (chess piece) is not part of the process but one Apple forum member advised me that the matrix often offers a useful 'sanity check' - which indeed it did.
- (NSPoint)
viewPointForEvent: (NSEvent *) event_
{
NSPoint windowPoint = [event_ locationInWindow];
NSPoint viewPoint = [self.view convertPoint: windowPoint
fromView: nil];
return viewPoint;
}
- (SCNHitTestResult *)
hitTestResultForEvent: (NSEvent *) event_
{
NSPoint viewPoint = [self viewPointForEvent: event_];
CGPoint cgPoint = CGPointMake (viewPoint.x, viewPoint.y);
NSArray * points = [(SCNView *) self.view hitTest: cgPoint
options: #{}];
return points.firstObject;
}
- (void)
mouseDown: (NSEvent *) theEvent
{
SCNHitTestResult * result = [self hitTestResultForEvent: theEvent];
SCNVector3 clickWorldCoordinates = result.worldCoordinates;
log output: clickWorldCoordinates x 208.124578, y -12827.223365, z 3163.659073
SCNVector3 screenCoordinates = [(SCNView *) self.view projectPoint: clickWorldCoordinates];
log output: screenCoordinates x 245.128906, y 149.335938, z 0.985565
// save the z coordinate for use in mouseDragged
mouseDownClickOnObjectZCoordinate = screenCoordinates.z;
selectedPiece = result.node; // save selected piece for use in mouseDragged
SCNVector3 piecePosition = selectedPiece.position;
log output: piecePosition x -18.200000, y 6.483060, z 2.350000
offsetOfMouseClickFromPiece.x = clickWorldCoordinates.x - piecePosition.x;
offsetOfMouseClickFromPiece.y = clickWorldCoordinates.y - piecePosition.y;
offsetOfMouseClickFromPiece.z = clickWorldCoordinates.z - piecePosition.z;
log output: offsetOfMouseClickFromPiece x 226.324578, y -12833.706425, z 3161.309073
}
- (void)
mouseDragged: (NSEvent *) theEvent;
{
NSPoint viewClickPoint = [self viewPointForEvent: theEvent];
SCNVector3 clickCoordinates;
clickCoordinates.x = viewClickPoint.x;
clickCoordinates.y = viewClickPoint.y;
clickCoordinates.z = mouseDownClickOnObjectZCoordinate;
log output: clickCoordinates x 246.128906, y 0.000000, z 0.985565
log output: pieceWorldTransform:
m11 = 242.15889219510001, m12 = -0.000045609300002524833, m13 = -0.00000721691076126, m14 = 0,
m21 = 0.0000072168760805499971, m22 = -0.000039452697396149999, m23 = 242.15890446329999, m24 = 0,
m31 = -0.000045609300002524833, m32 = -242.15889219510001, m33 = -0.000039452676995750002, m34 = 0,
m41 = -4268.2349924762348, m42 = -12724.050221935429, m43 = 4852.6652710104272, m44 = 1)
SCNVector3 newPiecePosition;
newPiecePosition.x = offsetOfMouseClickFromPiece.x + clickCoordinates.x;
newPiecePosition.y = offsetOfMouseClickFromPiece.y + clickCoordinates.y;
newPiecePosition.z = offsetOfMouseClickFromPiece.z + clickCoordinates.z;
log output: newPiecePosition x 472.453484, y -12833.706425, z 3162.294639
selectedPiece.position = newPiecePosition;
}
I used the code written by Steve and with little modification it worked for me.
On mouseDown I save clickWorldCoordinates on a property called startClickWorldCoordinates.
On mouseDragged I calculate the selectedPiece position in this way:
SCNVector3 worldClickCoordinate = [(SCNView *) self.view unprojectPoint:clickCoordinates.x];
newPiecePosition.x = selectedPiece.position.x + worldClickCoordinate.x - startClickWorldCoordinates.x;
newPiecePosition.y = selectedPiece.position.y + worldClickCoordinate.y - startClickWorldCoordinates.y;
newPiecePosition.z = selectedPiece.position.z + worldClickCoordinate.z - startClickWorldCoordinates.z;
selectedPiece.position = newPiecePosition;
startClickWorldCoordinates = worldClickCoordinate;
So I have a game in Windows Phone's version of cocos2dx. There is a background (CCLayerColor) and in the middle of it I place another CCLayerColor. The problem is that, when moving a layer with sprites inside that middle layer, the movement is done relative to the whole screen, not to the layer in middle.
The code for creating this CClayerColor in the middle of the screen is:
mWheelMachine = WheelMachineView::create(symbolMap, path);
mWheelMachine->setContentSize(CCSize(WHEEL_MACHINE_WIDTH , WHEEL_MACHINE_HEIGHT));//values equaling one third of screen size
mWheelMachine->setPosition(ItemManager::sharedItemManager()->getItemPosition(WHEEL_MACHINE_TAG
addChild(mWheelMachine, THEME_WHEEL_MACHINE_ORDER);//position in the middle of the screen
"WheelMachineView" is a subclass of "CCLayerColor", while create just overrides the correspondent of CCLayerColor.
Inside this class, I have another layer that moves along with its CCSprite objects drawn inside.
unsigned short o;
for (unsigned short i = 0; i < NUMBER_OF_WHEELS; i++)
{
WheelView* wheelLayer = WheelView::create();
wheelLayer->setIsRelativeAnchorPoint(true);
wheelLayer->setAnchorPoint(ccp(0,0));
wheelLayer->setPosition(i * WHEEL_WIDTH, -100 );
wheelLayer->setContentSize(CCSize(WHEEL_WIDTH, WHEEL_HEIGHT)); //large height value, to have room for making an animation with moving symbols
addChild(wheelLayer);
mWheels.push_back(wheelLayer);
/* Get the wheel symbols */
list<Symbol*> wheelSymbols = mWheelModel->getWheelSymbols(i);
/* Index */
o = 0;
for (list<Symbol*>::reverse_iterator it = wheelSymbols.rbegin(); it != wheelSymbols.rend(); it++)
{
CCSprite* symbol = CCSprite::spriteWithSpriteFrameName((*it)->getName().c_str());
symbol->setPosition(ccp(WHEEL_WIDTH / 2, SYMBOL_HEIGHT/2 + (o++ * SYMBOL_HEIGHT) - SYMBOL_HEIGHT));
symbol->setAnchorPoint(ccp(0.5f, 0.5f));
symbol->setScale(SYMBOL_SCALE_FACTOR);
wheelLayer->addChild(symbol, 10, o);
}
}
So, when moving 'wheelLayer' to a position outside the content size of 'mWheelMachine', it will move over the entire screen, thus drawing the symbols outside the middle designated area (mWheelMachine) for that. As it has a larger size than its parent, it draws symbols outside the parent.
Why is this happening? How can I make it to only use mWheelMachine's content size?
You can imagine cclayer is a node, it does not have the concept of size and anchor and set its content size does not have a role on the show's content, it will only affect the control area.
I think you should just hope that some of the pictures to be displayed in a region. Using a mask will be a good solution.
I am having an issue in Kobold2d-Cocos2d regarding the layering of CCSprite objects.
All of my code works fine, except that a certain tile (a CCSprite), held in an array, is supposed to always be on top. The code has an array of references to CCSprites (Tile) and goes along all the tiles swapping them with each other. So element 0 and 1 switch (in the array and on screen using a moveTo), then the new element 1 and 2 switch, then the new element 2 and 3 switch etc...
The idea is the Tile at the start of the chain leapfrogs to the front of the line!
I always want that first tile to stay on top, but it isn't. It only does sometimes (when it's above the other Tile it's switching places with on the screen, implying it was added later)
Here's the CCScene code in question:
//reference first CCSprite in array -works
Tile *firstTile = [tileArray objectAtIndex:(int)[[[selTracker oldSelectionArray] objectAtIndex:0]element]];
//How much time should be spend leapfrogging each two tiles broken up over 1 second.
float intervalTime = .75/(float)[[selTracker oldSelectionArray] count];
//Will use this to count each tile set we leapfrog
float currentPass = 0;
//TODO: destroy this wrapper class that holds number, use NSNumber. Fine for now.
SimpCoord *lastCord;
//Make moving sprite higher than all others. DOESN'T WORK?
[self reorderChild:firstTile z:100];
//Loop through all the tiles that were selected
for (SimpCoord *coord in [selTracker oldSelectionArray])
{
if (lastCord)
{
//Queue each tile to leapfrog with our moving tile
if ([self respondsToSelector:#selector(switchTilesFuncWrapper:)])
{
NSArray *argArray = [NSArray arrayWithObjects:
[NSNumber numberWithInt:[lastCord element]],
[NSNumber numberWithInt:[coord element]],
[NSNumber numberWithFloat:(intervalTime)], nil];
[self performSelector:#selector(switchTilesFuncWrapper:) withObject:argArray afterDelay:((currentPass) * intervalTime)];
}
currentPass++;
}
lastCord = coord;
}
`
That calls the following code by the way that actually swaps the two Tiles (I excluded the function wrapper middleman I needed for multiple arguments):
(In case it makes it easier to understand, rather than use a 2d array to hold all my tiles I just only draw 10 per line, hence the tileCoordsByElement method - but that shouldn't be important)
//
// Switch two tiles on the screen and in the holder array
// onlySwitchArray pass in true to only swap tiles in organization array
// but provide no animation.
- (void) switchTiles:(NSNumber*)elePosVal secPos:(NSNumber*)elePos2Val timeToSwitch:(NSNumber*)time
{
float switchTime = [time floatValue];
int elePos = [elePosVal intValue];
int elePos2 = [elePos2Val intValue];
Tile *tmpTile = [tileArray objectAtIndex:elePos];
Tile *tmpTile2 = [tileArray objectAtIndex:elePos2];
//Move actual tiles on screen
//Move Tile is elePos (1) to elePos2
int curCol = 0;
int curRow = 0;
[self tileCoordsByElement:elePos2 x:&curRow y:&curCol];
CCSequence *tmpTile1_Seq = [CCSequence actions:
[CCMoveTo actionWithDuration:switchTime position:ccp((curRow-1)*tmpTile.width + tmpTile.width/2,
(curCol-1)*tmpTile.height + tmpTile.height/2)], nil];
[tmpTile runAction:[CCRepeat actionWithAction:tmpTile1_Seq times:1]];
//Move Tile that is elePos2 to elePos(1)
[self tileCoordsByElement:elePos x:&curRow y:&curCol];
CCSequence *tmpTile1_Seq2 = [CCSequence actions:
[CCMoveTo actionWithDuration:switchTime position:ccp((curRow-1)*tmpTile.width + tmpTile.width/2,
(curCol-1)*tmpTile.height + tmpTile.height/2)], nil];
[tmpTile2 runAction:[CCRepeat actionWithAction:tmpTile1_Seq2 times:1]];
//Swap tiles in array second
[tileArray replaceObjectAtIndex:elePos withObject:tmpTile2];
[tileArray replaceObjectAtIndex:elePos2 withObject:tmpTile];
}
Alright.
I'm aware some things I'm doing aren't exactly efficient and if it's not relevant I don't really want to focus to heavily on them. This is just a learning exercise for me - I just honestly don't understand why the origional Tile won't stay on top.
I've tried every resource or example I possibly can find for answers (sample included code, tutorials online, terrible book I bought, random somewhat related stackoverflow articles) and I'm getting nothing :\
In case it matters this is how I origionally added my sprites to the scene:
//Set up A CCSpriteBatchNode to load in pList/pngs of images
[[CCSpriteFrameCache sharedSpriteFrameCache]
addSpriteFramesWithFile:#"zAtl_BasicImages_64.plist"];
nodeBasicImages = [CCSpriteBatchNode batchNodeWithFile:#"zAtl_BasicImages_64.png" capacity:100];
//Initialize array used to track tiles in scene
tileArray = [[NSMutableArray alloc] init];
for (int i = 0; i<100; i++)
{
Tile *tmpTile = [[Tile alloc] init];
[tileArray addObject:tmpTile];
[self reorderChild:tmpTile z:-1];
}
Note: I checked and the zOrder on both Tiles is correct on the line before the MoveTo animation begins.
I added my sprites not to my layer, but to an image node because I was using a Sprite Atlas. So I was trying to access sprites that weren't even there. - I'm a new user so going to have to wait 8 hours until I can close. Peace. Why it didn't throw an error I'll never know.
how can i clip/crop/mask or just set the frame of a CCSprite in Cocos2D?
Something similar to:
setting the frame for UIView, with clipping subviews = TRUE
My CCSprite Main Sprite have multiple Child Sprite added to it.
I only want Mask part of that Main Sprite Sprite visible.
Is there a way to clip or use a mask for CCSprite?
I could cut the background and layer that on top, leaving only that visible area, but is that the only way?!
here's a sample image demonstrating what I'm trying to achieve:
(source: dnamique.com)
I ended up using GL_SCISSOR.
in MainSprite I impemented:
- (void) visit
{
if (!self.visible) {
return;
}
glEnable(GL_SCISSOR_TEST);
glScissor(x, y, width, height);
[super visit];
glDisable(GL_SCISSOR_TEST);
}
This will clip or mask the specified area.
The only tricky bit is that in Landscape mode Cocos2D has 0,0 at the bottom-left side of the screen, while OpenGL has it at the bottom-right corner as it doesn't consider the orientation of the screen.
In other words, for OpenGL consider you have a rotated portrait Screen.
I wrote a ClippingNode class which does exactly that. You can add other nodes (sprites, labels, etc.) to the ClippingNode and they will only be drawn in the region specified by the ClippingNode. It also takes device rotation into account.
Internally it uses GL_SCISSOR_TEST like in Bach's answer.
http://www.learn-cocos2d.com/2011/01/cocos2d-gem-clippingnode/
I tried using Steffen Itterheim's ClippingNode, but was unable to get to work in a sufficiently robust
enough fashion for my needs.
Believe it or not, the below code works fairly well and should be code complete. It handles device orientation changes, anchorPoint, position, scale (scaleX, scaleY). For cocos2d v2, you may just need to
comment out the glPushMatrix and glPopMatrix calls..
To use, simply set the position and contentSize properties and add the child/children you want clipped to this ClippingNode instance. The contentSize property is used to define the dimensions of the clipping region.
example of usage:
ClippingNode *clipNode = [[ClippingNode alloc] init];
clipNode.anchorPoint = ccp(0.5f, 0);
clipNode.position = ccp(100, 25);
clipNode.contentSize = CGSizeMake(120, 120);
// add clipNode to your node hierarchy.
[parentNode addChild:clipNode];
// add one or more children to your clipNode:
[clipNode addChild:child1];
// ClippingNode.h
// CC0 - (public domain. Use in anyway you see fit.)
// No warranty of any kind is expressed or implied.
//
// by UChin Kim.
//
// the caller can simply set the regular cocos2d
// properties: position and contentSize to define the clipping region implicitly (i.e. the
// position and contentSize of the ClippingNode is the clipping region to be used).
// as an added bonus, position seems to work as expected (relative to parent node, instead of
// requiring absolute positioning).
//
// also, anchorPoint and scale properties seem to work as expected as well..
// no special code is neeed to handle device orientation changes correctly..
//
// To visually see exactly what is being clipped, set the following #define
// #define SHOW_CLIPPED_REGION_IN_LIGHT_RED 1
//
#import "cocos2d.h"
#interface ClippingNode : CCNode
#end
//
// ClippingNode.m
//
#import "ClippingNode.h"
#implementation ClippingNode
-(void) visit
{
CGPoint worldOrg = [self convertToWorldSpace:ccp(0, 0)];
CGPoint dest = [self convertToWorldSpace:ccp(self.contentSize.width, self.contentSize.height)];
CGPoint dims = ccpSub(dest, worldOrg);
glPushMatrix();
glEnable(GL_SCISSOR_TEST);
glScissor(worldOrg.x, worldOrg.y, dims.x, dims.y);
#if SHOW_CLIPPED_REGION_IN_LIGHT_RED
glColor4ub(64, 0, 0, 128);
ccDrawSolidRect(ccp(0, 0), ccp(1024, 1024));
#endif
[super visit];
glDisable(GL_SCISSOR_TEST);
glPopMatrix();
}
#end