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
Related
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.
I am creating an app where there will be ~10 circles on screen, that can all be pushed and dragged around the screen via touch movements. These circles (which are simply UIView's with a circle drawn in them) also have collision detection on them, and the idea is that they push other circles out of the way when they collide. There is also no acceration or gravity in this app, it's more like pushing coins around, where they are simply pushed out of the way, and when you stop moving, so do they.
I have touch-to-circle interaction working, and the fundamentals of circle-to-circle working. The code however gives Circle01 all the power. IE, it can be pushed into other circles and they will move out of the way as hoped. But when the other circles are pushed into it, they move around Circle01, instead of pushing it out of the way.
I've read dozens of pages about collision detection, but I'm just stumped as to how I'd modify this code to give all the circles equal power.
Relevant code:
MyCircle.m
- (void)drawRect:(CGRect)rect
{
CGPoint viewCenter = CGPointMake(50,50);
bpath = [UIBezierPath bezierPathWithArcCenter:viewCenter radius:50 startAngle:0 endAngle:DEGREES_TO_RADIANS(360) clockwise:YES];
[[UIColor blueColor] setFill];
[bpath fill];
}
CircleVC.m
- (void)viewDidLoad
{
Circle01 = [[MyCircle alloc] initWithFrame:CGRectMake(200, 200, 100, 100)];
Circle01.backgroundColor = [UIColor redColor];
Circle01.alpha = 0.5;
[self.view addSubview:Circle01];
}
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
// Testing circle collision detection
for (UIView *aView in self.view.subviews)
{
if (Circle01 == anotherView)
continue;
if (CGRectIntersectsRect(Circle01.frame, aView.frame))
{
float xDistance = aView.center.x - Circle01.center.x;
float yDistance = aView.center.y - Circle01.center.y;
float distance = xDistance * xDistance + yDistance * yDistance;
float angle = atan2(yDistance, xDistance);
CGPoint newCenter;
if (distance < 100 * 100)
{
newCenter.x = Circle01.center.x + (Circle01.frame.size.width * cos(angle));
newCenter.y = Circle01.center.y + (Circle01.frame.size.width * sin(angle));
aView.center = newCenter;
}
}
}
}
Thanks for any help.
It may be easier to offload this to a physics engine such as Box2d. Cocos2d is packaged with Box2d, but you will want the stand alone version since you are only using UIView based animations
_
Option 1
Here is a good, though slightly dated tutorial on using Box2d with UIKit.
http://www.cocoanetics.com/2010/05/physics-101-uikit-app-with-box2d-for-gravity/
Some caveats: if you are using ARC you will need to use the god-awful bridging compiler hints that tell the ARC pre-compiler when your C objects are being put into and out of its control.
For example:
bodyDef.userData = (__bridge void *)physicalView;
UIView *oneView = (__bridge UIView*)b -> GetUserData();
_
Option 2
You can use Chipmunk (which would also require the above "god-awful" bridging to ARC statements). Here is a good tutorial: http://www.alexandre-gomes.com/articles/chipmunk/
_
Option 3 - Easiest (Not Cheapest)
Alternatively, you could use the Objective-Chipmunk library that comes with Chipmunk Pro which does work with ARC. Here is a recent and "to-the-point" tutorial on using Obj-Chipmunk with UIImageViews. This would be the same for any UIView based code. http://www.ayarsanimation.com/frankayars/chipmunk
You can buy Obj-Chipmunk costs $89 when you by an Indie version license for Chipmunk Pro.
http://chipmunk-physics.net/chipmunkPro.php
I am not advocating a paid solution, but it may save you time.
It seems pretty simple, unless I'm missing something. Instead of hard-coding Circle01 in your touchesMoved, do your hit detection based on the view that the user touched. That way, whatever view they are dragging will get priority.
I want to create a simple tool for drawing. The purpose is to draw a line that follows the accelerometer of the iPhone & iPad, so if the user tilts the device a line will be draw in the direction the device was moved.
I am able to register acceleration and drawing lines. My problem is that as soon as I draw a line the old one disappears. One possible solution would be to save to points already drawn and then re-draw everything, but I would think there are better solutions?
All help is appreciated!
My drawRect is at the moment like this:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 20.0);
CGContextSetStrokeColorWithColor(context, [UIColor yellowColor].CGColor);
CGContextMoveToPoint(context, fromPoint.x, fromPoint.y);
CGContextAddLineToPoint(context, toPoint.x, toPoint.y);
CGContextStrokePath(context);
}
A different method is responsible for refreshing. This method is also called from the uiviewcontroller with certain intervals. Right now it shows a "trail" (or what I should call it) in the direction the device was moved. Not exactly what I am looking for:
- (void)drawNewLine:(CGPoint)to {
// calculate trail behind current point
float pointDifferenceX = ((toPoint.x - to.x) * 9);
float pointDifferenceY = ((toPoint.y - to.y) * 9);
fromPoint = CGPointMake(toPoint.x + pointDifferenceX, toPoint.y + pointDifferenceY);
toPoint = to;
[self setNeedsDisplay];
}
I can think of two options:
Either save all points and redraw the lines whenever the screen needs to be refreshed (as you mentioned)
Draw the lines into an off-screen pixelmap and refresh the screen from there
In either case, respect the Hollywood principle: Don't call, you will be called. That means don't just draw to the screen but wait for until drawRect: of your UIView is called. (You can trigger this by calling setNeedsDisplay.)
I have an iphone version of an App that draws lines on the screen from CGPoints that are stored in a Core Data. all of the drawings are line based without any fill, so basically it draws a line from given point to the next point etc.
Now i am making an iPad version and i want to use the same points (The points were collected with a function I build for tracking the screen and it was a lot of work so I wish to reuse the same points i have).
Does any body has an idea, algorithm or function for drawing the same lines' from the same points but X2 size ?
That is the draw method:(took it from the GLPaint example of apple)
- (void) playback:(NSNumber*)index
{
if (p==0) {
pointsCount=[[localpoints objectAtIndex:[index intValue]] count]-1;
}
isPlayBackOn = YES;
LetterPoint *point1 = (LetterPoint*)[[localpoints objectAtIndex:[index intValue]] objectAtIndex:p];
CGPoint p1 = CGPointFromString(point1.float_point);
LetterPoint *point2 = (LetterPoint*)[[localpoints objectAtIndex:[index intValue]] objectAtIndex:p+1];
CGPoint p2 = CGPointFromString(point2.float_point);
[self renderLineFromPoint:p1 toPoint:p2];
p++;
if(p<pointsCount){
[self performSelector:#selector(playback:) withObject:index afterDelay:0.03];
}else {
p=0;
isPlayBackOn = NO;
}
}
thanks
shani
Create an affine transform matrix with a scale factor of 2.0 (and possibly a translation if you want to move the origin of the drawing). Then apply that transform to every point with CGPointApplyAffineTransform() and use the resulting points for drawing.
well, just double p1.x and p1.y...
transform it just with this:
instead of:
CGPoint p1 = CGPointFromString(point1.float_point);
do this:
CGPoint p1Temp = CGPointFromString(point1.float_point);
CGPoint p1 = CGPointMake(p1Temp.x * 2, p1Temp.y * 2);
and the same for p2...
I am struggling to get my custom drawing code to render at the proper scale for all iOS devices, i.e., older iPhones, those with retina displays and the iPad.
I have a subclass of UIView that has a custom class that displays a vector graphic. It has a scale property that I can set. I do the scaling in initWithCoder when the UIView loads and I first instantiate the vector graphic. This UIView is shown when the user taps a button on the home screen.
At first I tried this:
screenScaleFactor = 1.0;
if ([UIScreen instancesRespondToSelector:#selector(scale)]) {
screenScaleFactor = [[UIScreen mainScreen] scale];
}
// and then I multiply stuff by screenScale
... which worked for going between normal iPhones and retina iPhones, but chokes on the iPad. As I said, you can get to the UIView at issue by tapping a button on the home screen. When run on the iPad, if you display the UIView when at 1X, it works, but at 2X I get a vector graphic that twice as big as it should be.
So I tried this instead:
UPDATE: This block is the one that's right. (with the corrected spelling, of course!)
screenScaleFactor = 1.0;
if ([self respondsToSelector:#selector(contentScaleFactor)]) { //EDIT: corrected misspellng.
screenScaleFactor = (float)self.contentScaleFactor;
}
// again multiplying stuff by screenScale
Which works at both 1X and 2X on the iPad and on the older iPhones, but on a retina display, the vector graphic is half the size it should be.
In the first case, I query the UIScreen for its scale property and in the second case, I'm asking the parent view of the vector graphic for its contentsScaleFactor. Neither of these seem to get me where I want for all cases.
Any suggestions?
UPDATE:
Here's the method in my subclassed UIView (it's called a GaugeView):
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGAffineTransform t0 = CGContextGetCTM(context);
t0 = CGAffineTransformInvert(t0);
CGContextConcatCTM(context, t0);
[needle updateBox];
[needle draw: context];
}
needle is of class VectorSprite which is a subclass of Sprite which is subclassed from NSObject. These are from a programming book I'm working through. needle has the scale property that I set.
updateBox comes from Sprite and looks like this:
- (void) updateBox {
CGFloat w = width*scale;
CGFloat h = height*scale;
CGFloat w2 = w*0.5;
CGFloat h2 = h*0.5;
CGPoint origin = box.origin;
CGSize bsize = box.size;
CGFloat left = -kScreenHeight*0.5;
CGFloat right = -left;
CGFloat top = kScreenWidth*0.5;
CGFloat bottom = -top;
offScreen = NO;
if (wrap) {
if ((x+w2) < left) x = right + w2;
else if ((x-w2) > right) x = left - w2;
else if ((y+h2) < bottom) y = top + h2;
else if ((y-h2) > top) y = bottom - h2;
}
else {
offScreen =
((x+w2) < left) ||
((x-w2) > right) ||
((y+h2) < bottom) ||
((y-h2) > top);
}
origin.x = x-w2*scale;
origin.y = y-h2*scale;
bsize.width = w;
bsize.height = h;
box.origin = origin;
box.size = bsize;
}
Sprite also has the draw and drawBody methods which are:
- (void) draw: (CGContextRef) context {
CGContextSaveGState(context);
// Position the sprite
CGAffineTransform t = CGAffineTransformIdentity;
t = CGAffineTransformTranslate(t,x,y);
t = CGAffineTransformRotate(t,rotation);
t = CGAffineTransformScale(t,scale,scale);
CGContextConcatCTM(context, t);
// draw sprite body
[self drawBody: context];
CGContextRestoreGState(context);
}
- (void) drawBody: (CGContextRef) context {
// Draw your sprite here, centered
// on (x,y)
// As an example, we draw a filled white circle
if (alpha < 0.05) return;
CGContextBeginPath(context);
CGContextSetRGBFillColor(context, r,g,b,alpha);
CGContextAddEllipseInRect(context, CGRectMake(-width/2,-height/2,width,height));
CGContextClosePath(context);
CGContextDrawPath(context,kCGPathFill);
}
How, exactly, are you rendering the graphic?
This should be handled automatically in drawRect: (the context you get should be already 2x). This should also be handled automatically with UIGraphicsBeginImageContextWithOptions(size,NO,0); if available (if you need to fall back to UIGraphicsBeginImageContext(), assume a scale of 1). You shouldn't need to worry about it unless you're drawing the bitmap yourself somehow.
You could try something like self.contentScaleFactor = [[UIScreen mainScreen] scale], with appropriate checks first (this might mean if you display it in an iPad at 2x, you'll get high-res graphics).
Fundamentally, there's not much difference between an iPad in 2x mode and a "retina display", except that the iPad can switch between 1x and 2x.
Finally, there's a typo: #selector(contentsScaleFactor) has an extra s.