I am trying to draw a circle in my scene with :
- (void) draw
{
ccDrawColor4F(100, 100, 100, 255);
CGPoint center = ccp(winSize.width/2, winSize.height/2);
CGFloat radius = 10.f;
CGFloat angle = 0.f;
NSInteger segments = 10;
BOOL drawLineToCenter = YES;
ccDrawCircle(center, radius, angle, segments, drawLineToCenter);
}
Well, I get some white,small circle, not filled, with some line from the center to the circle (like a clock).
So, first why are the colors are not as requested, how can I fill it with my own color? whats the line in the middle?
And, what if i want to change the color after a second? whats the best way to do that? Set a timer with a global variable?
#import <OpenGLES/ES1/gl.h>
- (void) draw
{
glLineWidth(5*CC_CONTENT_SCALE_FACTOR()); //set the thickness to 5 pixels for example
ccDrawColor4F(0.4,0.4,0.4, 1);
ccDrawLine([self anchorPoint], _peg.position);
CGPoint center = ccp(winSize.width/2, winSize.height/2);
CGFloat radius = 10.0;
CGFloat angle = 360;
NSInteger segments = 360;
BOOL drawLineToCenter = NO;
ccDrawCircle(center, radius, angle, segments, drawLineToCenter);
}
If you want to update the circle's color every second, schedule the update method and implement it as follows :
-(void) update:(float)dt
{
if(startTime == 0){
startTime = [NSDate timeIntervalSinceReferenceDate];
}
double elapsedTime = [NSDate timeIntervalSinceReferenceDate] - startTime;
if(elapsedTime > 1){
//update ccDrawColor4F here, for example just changing the Red component:
ccDrawColor4F((float)arc4random() / UINT_MAX,0.4,0.4, 1);
startTime = [NSDate timeIntervalSinceReferenceDate];
}
}
This should fix your issue. Hope it helps.
The colour values are floats not bytes, so you need to pass in something more like
ccDrawColor4F(0.4f, 0.4f, 0.4f, 1.f);
You may also want to reset the colour back to white after you've called your draw methods just for the sake of consistency.
To get a filled circle, use this function instead
void ccDrawSolidCircle( CGPoint center, float r, float a, NSUInteger segs, ccColor4F color);
The circle is always drawn from the middle, so presumably if you choose 4 segments, you'll get 4 lines drawn from the middle. Although I'm not too sure on that, I don't use primitives very much at all. As for the colour change, yes I would opt for some form of timer to change the values passed into the ccDrawColor4F method. But bear in mind that you want as little non-drawing code in the draw method as possible, so perhaps work out the colour change in another update related function.
Related
I'm using a draw function to highlight the score inserted into a table of highscores but i find that it makes my app lag when i try to leave the highscore layer. I'm still relatively new to cocos2d so I was wondering if there is a better way so it doesn't cause any lag. I find if i comment out this function that the isn't any lag. Heres my code:
- (void)draw {
[super draw];
if(currentScorePosition < 0 || currentScore==0) return;
float w = 320.0f;
float h = 20.0f;
float x = (320.0f - w) / 2.0f;
float y = 230.0f - currentScorePosition * h;
CGPoint vertices[4];
vertices[0] = ccp(x, y);
vertices[1] = ccp(x+w, y);
vertices[2] = ccp(x+w, y+h);
vertices[3] = ccp(x, y+h);
CCDrawNode *draw = [[[CCDrawNode alloc] init] autorelease];
[draw drawPolyWithVerts:vertices count:4 fillColor:ccc4f(0.5, 0.5, 0.8, 0.5) borderWidth:2.0 borderColor:ccc4f(0.0, 0.0, 0.0, 0.0)];
[self addChild:draw z:0 ];
}
You're creating a new CCDrawNode every frame. Over time this will slow down the game as it has to draw more and more draw nodes.
Solution: create one draw node up front and add it as child. Keep a reference to it in an ivar. Perform drawing with just this single draw node.
Note that the draw methods of CCDrawNode are still additive. If you want to draw just this one polygon and update it over time, then you'll have to call clear before drawing:
[theDrawNode clear];
[theDrawNode drawPolyWithVerts:vertices
count:4
fillColor:ccc4f(0.5, 0.5, 0.8, 0.5)
borderWidth:2.0
borderColor:ccc4f(0.0, 0.0, 0.0, 0.0)];
Another note: you can use the draw node outside the draw method. In fact if you run the code like you did, the draw node won't be drawn until the next frame and thus it'll always lag 1 frame behind. Use a scheduled update method to update the draw node.
I have a simple oval shape (comprised of CGMutablePaths) from which I'd like the user to be able to drag an object around it. Just wondering how complicated it is to do this, do I need to know a ton of math and physics, or is there some simple built in way that will allow me to do this? IE the user drags this object around the oval, and it orbits it.
This is an interesting problem. We want to drag an object, but constrain it to lie on a CGPath. You said you have “a simple oval shape”, but that's boring. Let's do it with a figure 8. It'll look like this when we're done:
So how do we do this? Given an arbitrary point, finding the nearest point on a Bezier spline is rather complicated. Let's do it by brute force. We'll just make an array of points closely spaced along the path. The object starts out on one of those points. As we try to drag the object, we'll look at the neighboring points. If either is nearer, we'll move the object to that neighbor point.
Even getting an array of closely-spaced points along a Bezier curve is not trivial, but there is a way to get Core Graphics to do it for us. We can use CGPathCreateCopyByDashingPath with a short dash pattern. This creates a new path with many short segments. We'll take the endpoints of each segment as our array of points.
That means we need to iterate over the elements of a CGPath. The only way to iterate over the elements of a CGPath is with the CGPathApply function, which takes a callback. It would be much nicer to iterate over path elements with a block, so let's add a category to UIBezierPath. We start by creating a new project using the “Single View Application” template, with ARC enabled. We add a category:
#interface UIBezierPath (forEachElement)
- (void)forEachElement:(void (^)(CGPathElement const *element))block;
#end
The implementation is very simple. We just pass the block as the info argument of the path applier function.
#import "UIBezierPath+forEachElement.h"
typedef void (^UIBezierPath_forEachElement_Block)(CGPathElement const *element);
#implementation UIBezierPath (forEachElement)
static void applyBlockToPathElement(void *info, CGPathElement const *element) {
__unsafe_unretained UIBezierPath_forEachElement_Block block = (__bridge UIBezierPath_forEachElement_Block)info;
block(element);
}
- (void)forEachElement:(void (^)(const CGPathElement *))block {
CGPathApply(self.CGPath, (__bridge void *)block, applyBlockToPathElement);
}
#end
For this toy project, we'll do everything else in the view controller. We'll need some instance variables:
#implementation ViewController {
We need an ivar to hold the path that the object follows.
UIBezierPath *path_;
It would be nice to see the path, so we'll use a CAShapeLayer to display it. (We need to add the QuartzCore framework to our target for this to work.)
CAShapeLayer *pathLayer_;
We'll need to store the array of points-along-the-path somewhere. Let's use an NSMutableData:
NSMutableData *pathPointsData_;
We'll want a pointer to the array of points, typed as a CGPoint pointer:
CGPoint const *pathPoints_;
And we need to know how many of those points there are:
NSInteger pathPointsCount_;
For the “object”, we'll have a draggable view on the screen. I'm calling it the “handle”:
UIView *handleView_;
We need to know which of the path points the handle is currently on:
NSInteger handlePathPointIndex_;
And while the pan gesture is active, we need to keep track of where the user has tried to drag the handle:
CGPoint desiredHandleCenter_;
}
Now we have to get to work initializing all those ivars! We can create our views and layers in viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
[self initPathLayer];
[self initHandleView];
[self initHandlePanGestureRecognizer];
}
We create the path-displaying layer like this:
- (void)initPathLayer {
pathLayer_ = [CAShapeLayer layer];
pathLayer_.lineWidth = 1;
pathLayer_.fillColor = nil;
pathLayer_.strokeColor = [UIColor blackColor].CGColor;
pathLayer_.lineCap = kCALineCapButt;
pathLayer_.lineJoin = kCALineJoinRound;
[self.view.layer addSublayer:pathLayer_];
}
Note that we haven't set the path layer's path yet! It's too soon to know the path at this time, because my view hasn't been laid out at its final size yet.
We'll draw a red circle for the handle:
- (void)initHandleView {
handlePathPointIndex_ = 0;
CGRect rect = CGRectMake(0, 0, 30, 30);
CAShapeLayer *circleLayer = [CAShapeLayer layer];
circleLayer.fillColor = nil;
circleLayer.strokeColor = [UIColor redColor].CGColor;
circleLayer.lineWidth = 2;
circleLayer.path = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(rect, circleLayer.lineWidth, circleLayer.lineWidth)].CGPath;
circleLayer.frame = rect;
handleView_ = [[UIView alloc] initWithFrame:rect];
[handleView_.layer addSublayer:circleLayer];
[self.view addSubview:handleView_];
}
Again, it's too soon to know exactly where we'll need to put the handle on screen. We'll take care of that at view layout time.
We also need to attach a pan gesture recognizer to the handle:
- (void)initHandlePanGestureRecognizer {
UIPanGestureRecognizer *recognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handleWasPanned:)];
[handleView_ addGestureRecognizer:recognizer];
}
At view layout time, we need to create the path based on the size of the view, compute the points along the path, make the path layer show the path, and make sure the handle is on the path:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[self createPath];
[self createPathPoints];
[self layoutPathLayer];
[self layoutHandleView];
}
In your question, you said you're using a “simple oval shape”, but that's boring. Let's draw a nice figure 8. Figuring out what I'm doing is left as an exercise for the reader:
- (void)createPath {
CGRect bounds = self.view.bounds;
CGFloat const radius = bounds.size.height / 6;
CGFloat const offset = 2 * radius * M_SQRT1_2;
CGPoint const topCenter = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds) - offset);
CGPoint const bottomCenter = { topCenter.x, CGRectGetMidY(bounds) + offset };
path_ = [UIBezierPath bezierPath];
[path_ addArcWithCenter:topCenter radius:radius startAngle:M_PI_4 endAngle:-M_PI - M_PI_4 clockwise:NO];
[path_ addArcWithCenter:bottomCenter radius:radius startAngle:-M_PI_4 endAngle:M_PI + M_PI_4 clockwise:YES];
[path_ closePath];
}
Next we're going to want to compute the array of points along that path. We'll need a helper routine to pick out the endpoint of each path element:
static CGPoint *lastPointOfPathElement(CGPathElement const *element) {
int index;
switch (element->type) {
case kCGPathElementMoveToPoint: index = 0; break;
case kCGPathElementAddCurveToPoint: index = 2; break;
case kCGPathElementAddLineToPoint: index = 0; break;
case kCGPathElementAddQuadCurveToPoint: index = 1; break;
case kCGPathElementCloseSubpath: index = NSNotFound; break;
}
return index == NSNotFound ? 0 : &element->points[index];
}
To find the points, we need to ask Core Graphics to “dash” the path:
- (void)createPathPoints {
CGPathRef cgDashedPath = CGPathCreateCopyByDashingPath(path_.CGPath, NULL, 0, (CGFloat[]){ 1.0f, 1.0f }, 2);
UIBezierPath *dashedPath = [UIBezierPath bezierPathWithCGPath:cgDashedPath];
CGPathRelease(cgDashedPath);
It turns out that when Core Graphics dashes the path, it can create segments that slightly overlap. We'll want to eliminate those by filtering out each point that's too close to its predecessor, so we'll define a minimum inter-point distance:
static CGFloat const kMinimumDistance = 0.1f;
To do the filtering, we'll need to keep track of that predecessor:
__block CGPoint priorPoint = { HUGE_VALF, HUGE_VALF };
We need to create the NSMutableData that will hold the CGPoints:
pathPointsData_ = [[NSMutableData alloc] init];
At last we're ready to iterate over the elements of the dashed path:
[dashedPath forEachElement:^(const CGPathElement *element) {
Each path element can be a “move-to”, a “line-to”, a “quadratic-curve-to”, a “curve-to” (which is a cubic curve), or a “close-path”. All of those except close-path define a segment endpoint, which we pick up with our helper function from earlier:
CGPoint *p = lastPointOfPathElement(element);
if (!p)
return;
If the endpoint is too close to the prior point, we discard it:
if (hypotf(p->x - priorPoint.x, p->y - priorPoint.y) < kMinimumDistance)
return;
Otherwise, we append it to the data and save it as the predecessor of the next endpoint:
[pathPointsData_ appendBytes:p length:sizeof *p];
priorPoint = *p;
}];
Now we can initialize our pathPoints_ and pathPointsCount_ ivars:
pathPoints_ = (CGPoint const *)pathPointsData_.bytes;
pathPointsCount_ = pathPointsData_.length / sizeof *pathPoints_;
But we have one more point we need to filter. The very first point along the path might be too close to the very last point. If so, we'll just discard the last point by decrementing the count:
if (pathPointsCount_ > 1 && hypotf(pathPoints_[0].x - priorPoint.x, pathPoints_[0].y - priorPoint.y) < kMinimumDistance) {
pathPointsCount_ -= 1;
}
}
Blammo. Point array created. Oh yeah, we also need to update the path layer. Brace yourself:
- (void)layoutPathLayer {
pathLayer_.path = path_.CGPath;
pathLayer_.frame = self.view.bounds;
}
Now we can worry about dragging the handle around and making sure it stays on the path. The pan gesture recognizer sends this action:
- (void)handleWasPanned:(UIPanGestureRecognizer *)recognizer {
switch (recognizer.state) {
If this is the start of the pan (drag), we just want to save the starting location of the handle as its desired location:
case UIGestureRecognizerStateBegan: {
desiredHandleCenter_ = handleView_.center;
break;
}
Otherwise, we need to update the desired location based on the drag, and then slide the handle along the path toward the new desired location:
case UIGestureRecognizerStateChanged:
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled: {
CGPoint translation = [recognizer translationInView:self.view];
desiredHandleCenter_.x += translation.x;
desiredHandleCenter_.y += translation.y;
[self moveHandleTowardPoint:desiredHandleCenter_];
break;
}
We put in a default clause so clang won't warn us about the other states that we don't care about:
default:
break;
}
Finally we reset the translation of the gesture recognizer:
[recognizer setTranslation:CGPointZero inView:self.view];
}
So how do we move the handle toward a point? We want to slide it along the path. First, we have to figure out which direction to slide it:
- (void)moveHandleTowardPoint:(CGPoint)point {
CGFloat earlierDistance = [self distanceToPoint:point ifHandleMovesByOffset:-1];
CGFloat currentDistance = [self distanceToPoint:point ifHandleMovesByOffset:0];
CGFloat laterDistance = [self distanceToPoint:point ifHandleMovesByOffset:1];
It's possible that both directions would move the handle further from the desired point, so let's bail out in that case:
if (currentDistance <= earlierDistance && currentDistance <= laterDistance)
return;
OK, so at least one of the directions will move the handle closer. Let's figure out which one:
NSInteger direction;
CGFloat distance;
if (earlierDistance < laterDistance) {
direction = -1;
distance = earlierDistance;
} else {
direction = 1;
distance = laterDistance;
}
But we've only checked the nearest neighbors of the handle's starting point. We want to slide as far as we can along the path in that direction, as long as the handle is getting closer to the desired point:
NSInteger offset = direction;
while (true) {
NSInteger nextOffset = offset + direction;
CGFloat nextDistance = [self distanceToPoint:point ifHandleMovesByOffset:nextOffset];
if (nextDistance >= distance)
break;
distance = nextDistance;
offset = nextOffset;
}
Finally, update the handle's position to our newly-discovered point:
handlePathPointIndex_ += offset;
[self layoutHandleView];
}
That just leaves the small matter of computing the distance from the handle to a point, should the handle be moved along the path by some offset. Your old buddy hypotf computes the Euclidean distance so you don't have to:
- (CGFloat)distanceToPoint:(CGPoint)point ifHandleMovesByOffset:(NSInteger)offset {
int index = [self handlePathPointIndexWithOffset:offset];
CGPoint proposedHandlePoint = pathPoints_[index];
return hypotf(point.x - proposedHandlePoint.x, point.y - proposedHandlePoint.y);
}
(You could speed things up by using squared distances to avoid the square roots that hypotf is computing.)
One more tiny detail: the index into the points array needs to wrap around in both directions. That's what we've been relying on the mysterious handlePathPointIndexWithOffset: method to do:
- (NSInteger)handlePathPointIndexWithOffset:(NSInteger)offset {
NSInteger index = handlePathPointIndex_ + offset;
while (index < 0) {
index += pathPointsCount_;
}
while (index >= pathPointsCount_) {
index -= pathPointsCount_;
}
return index;
}
#end
Fin. I've put all of the code in a gist for easy downloading. Enjoy.
I am trying to apply a texture for my brush but i'm really having a hard time figuring how it is done.
Here's the image of my output.
I used an UIImage that just follows the touch on the screen but when i swipe it faster the result is on the right side "W", and on the left side that's the result when i swipe it slow.
i tried using the CGContextMoveToPoint and CGContextAddLineToPoint i don't know how apply the texture.
Is it possible to use UIImage for the stroke texture?
Here's my code
UIImage * brushImageTexture = [UIImage imageNamed:#"brushImage.png"];
[brushImagetexture drawAtPoint:CGPointMake(touchCurrentPosition.x, touchVurrentPosition.y) blendMode:blendMode alpha:1.0f];
You need to manually draw the image at each point along the line from the previous point to the current point.
CGPoint vector = CGPointMake(currentPosition.x - lastPosition.x, currentPosition.y - lastPosition.y);
CGFloat distance = hypotf(vector.x, vector.y);
vector.x /= distance;
vector.y /= distance;
for (CGFloat i = 0; i < distance; i += 1.0f) {
CGPoint p = CGPointMake(lastPosition.x + i * vector.x, lastPosition.y + i * vector.y);
[brushImageTexture drawAtPoint:p blendMode:blendMode alpha:1.0f];
}
I want to rotate five buttons on the circumference of an circle (circle with the center (120,120), which is actually center of square view i.e. 240*240) with radius 100. IS it possible to do so, with having interaction with the buttons which are rotating and a proper look.
i have tried '
x = round(cx + redious * cos(angl));
y = round(cy - redious * sin(angl));
NSString *X=[NSString stringWithFormat:#"%f",x];
[xval addObject:[NSString stringWithFormat:#"%#",X]];
NSString *Y=[NSString stringWithFormat:#"%f",y];
[yval addObject:[NSString stringWithFormat:#"%#",Y]];'
to calculate points and:
map.frame= CGRectMake([[fqx objectAtIndex:counter1]intValue],[[fqy objectAtIndex:counter1]intValue], 72, 37);
website.frame= CGRectMake([[fqx objectAtIndex:counter2]intValue],[[fqy objectAtIndex:counter2]intValue], 72, 37);
share.frame= CGRectMake([[fqx objectAtIndex:counter3]intValue],[[fqy objectAtIndex:counter3]intValue], 72, 37);
slideShow.frame= CGRectMake([[fqx objectAtIndex:counter4]intValue],[[fqy objectAtIndex:counter4]intValue], 72, 37);'
to rotate but it generate weird path.. in triangular way..
('map','share','slideShow','website') ar my buttons.. :P
This was too cute to pass up so here it is. This code is limited to one button rotating but I doubt you'll have trouble extending it to take multiple (hint: use ONE CADisplayLink and rotate all buttons at once in that).
- (void)setupRotatingButtons
{
// call this method once; make sure "self.view" is not nil or the button
// won't appear. the below variables are needed in the #interface.
// center: the center of rotation
// radius: the radius
// time: a CGFloat that determines where in the cycle the button is located at
// (note: it will keep increasing indefinitely; you need to use
// modulus to find a meaningful value for the current position, if
// needed)
// speed: the speed of the rotation, where 2 * M_PI is 1 lap a second
// b: the UIButton
center = CGPointMake(100, 100);
radius = 100;
time = 0;
speed = 2 * M_PI; // <-- will rotate CW 360 degrees per second (1 "lap"/s)
b = [[UIButton buttonWithType:UIButtonTypeRoundedRect] retain];
b.titleLabel.text = #"Hi";
b.frame = CGRectMake(0.f, 0.f, 100, 50);
// we get the center set right before we add subview, to avoid glitch at start
[self continueCircling:nil];
[self.view addSubview:b];
[self.view bringSubviewToFront:b];
CADisplayLink *dl = [CADisplayLink displayLinkWithTarget:self
selector:#selector(continueCircling:)];
[dl addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
We also need the actual "continueCircling:" method, which is simply:
- (void)continueCircling:(CADisplayLink *)dl
{
time += speed * dl.duration;
b.center = CGPointMake(center.x + radius * cosf(time),
center.y + radius * sinf(time));
}
I'm sure there are other more nifty ways to solve this, but the above works at least. :)
Edit: I forgot to mention, you will need to add QuartzCore framework and
#import <QuartzCore/QuartzCore.h>
for CADisplayLink.
Edit 2: Found the PI constant (M_PI), so replaced 3.1415 with that.
Can you let me know what is the best way to draw a line or rectangle on a scene layer using Cocos2d ios4 iphone.
So far have tried Texture2d, but it is more like a paint brush and is not so good. Tried drawing a line using draw method, but previous line disappears on drawing another line.
Basically want to draw multiple horizontal ,vertical, oblique beams. Please suggest. Any code would help a lot .
The code to draw using texture is below:
CGPoint start = edge.start;
CGPoint end = edge.end;
// begin drawing to the render texture
[target begin];
// for extra points, we'll draw this smoothly from the last position and vary the sprite's
// scale/rotation/offset
float distance = ccpDistance(start, end);
if (distance > 1)
{
int d = (int)distance;
for (int i = 0; i < d; i++)
{
float difx = end.x - start.x;
float dify = end.y - start.y;
float delta = (float)i / distance;
[brush setPosition:ccp(start.x + (difx * delta), start.y + (dify * delta))];
[brush setScale:0.3];
// Call visit to draw the brush, don't call draw..
[brush visit];
}
}
// finish drawing and return context back to the screen
[target end];
The rendering is not good esp. with oblique lines as the scaling affects the quality.
Cheers
You could create a separate layer and call the draw method like this:
-(void) draw
{
CGSize s = [[Director sharedDirector] winSize];
drawCircle( ccp(s.width/2, s.height/2), circleSize, 0, 50, NO);
It's for a circle but the principle is the same. This is from a project I made a while back and it worked then. Don't know if anything has changed since.
You need to add draw method to your layer:
-(void) draw {
// ...
}
Inside it you can use some openGL like functions and cocos2d wrapper methods for openGL.
Hint: other methods can be called inside draw method.
But keep in mind that using other name for method
containing openGL instructions, that's not called inside draw mentioned above simply won't work.
Even when called from update method or other method used by scheduleUpdate selector.
So you will end up with something like this:
-(void) draw {
glEnable(GL_LINE_SMOOTH);
glColor4ub(255, 0, 100, 255);
glLineWidth(4);
CGPoint verts[] = { ccp(0,200), ccp(300,200) };
ccDrawLine(verts[0], verts[1]);
[self drawSomething];
[self drawSomeOtherStuffFrom:ccp(a,b) to:ccp(c,d)];
[someObject doSomeDrawingAsWell];
}
For more information check out cocos2d-iphone programming guide :
http://www.cocos2d-iphone.org/wiki/doku.php/prog_guide:draw_update?s[]=schedule#draw