I have a sprite that I want to apply a constant force to, as opposed to reapplying that force every update, which is giving me strange results.
I have a sprite that has a mass of 60 kg and is affected by gravity. I want to apply a force that is equal to the force of gravity constantly. The way I'm doing it still causes the sprite to fall of the screen really fast because the force of gravity is somehow greater. I'm wondering how and when the force of gravity is applied, and if it is possible fore me to set a constant force on the object, similar to the force of gravity.
Currently, my scene looks like this:
#import "MyScene.h"
#interface MyScene ()
#property (nonatomic, strong)SKSpriteNode *parachutist;
#end
#implementation MyScene
- (id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
CGSize spriteSize = CGSizeMake(80.0f, 80.0f);
self.parachutist = [SKSpriteNode spriteNodeWithTexture:[SKTexture textureWithImageNamed:#"parachutist.png"] size:spriteSize];
[self addChild:self.parachutist];
self.parachutist.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
self.parachutist.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:spriteSize];
self.parachutist.physicsBody.dynamic = YES;
self.parachutist.physicsBody.mass = 60.0; // kg
self.parachutist.physicsBody.affectedByGravity = YES;
}
return self;
}
- (void)update:(NSTimeInterval)currentTime
{
// this ought to match the force of gravity
[self.parachutist.physicsBody applyForce:CGVectorMake(0.0f, 60.0f * 9.8f)];
}
#end
Fixed:
#import "MyScene.h"
#define WEIRD_FACTOR 150.0f
#define GRAVITY -9.8f
#interface MyScene ()
#property (nonatomic, strong)SKSpriteNode *parachutist;
#end
#implementation MyScene
- (id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
CGSize spriteSize = CGSizeMake(80.0f, 80.0f);
self.physicsBody.affectedByGravity = YES;
self.physicsWorld.gravity = CGVectorMake(0.0f, GRAVITY / WEIRD_FACTOR);
self.parachutist = [SKSpriteNode spriteNodeWithTexture:[SKTexture textureWithImageNamed:#"Parachutist.png"] size:spriteSize];
[self addChild:self.parachutist];
self.parachutist.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
self.parachutist.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:spriteSize];
self.parachutist.physicsBody.dynamic = YES;
self.parachutist.physicsBody.mass = 60.0; // kg
self.parachutist.physicsBody.affectedByGravity = YES;
}
return self;
}
- (void)update:(NSTimeInterval)currentTime
{
[self.parachutist.physicsBody applyForce:CGVectorMake(0.0f,
self.parachutist.physicsBody.mass * -GRAVITY)];
}
#end
See this question for an explanation: applyForce(0, 400) - SpriteKit inconsistency
Doctordoder:
I am new to sprite kit myself but am currently working on a game as well. I believe that you need to make the vector's Y component be negative.
[self.parachutist.physicsBody applyForce:CGVectorMake(0.0f, -9.8)];
that should apply force straight down at -9.8 units. However unlike gravity I do not believe this to be cumulative. gravity is -9,8 meters per sec squared.
By multiplying the gravity by 60 your essentially making gravity 60 times what it is normally.
Alternatively you could adjust the property for physicsWorld Gravity and that would apply the gravity to any object with a physics body not set to physicsBody.dynamic = YES
self.physicsWorld.gravity = CGVectorMake(0, -9.8 );
edit: grammer and punctuation.
Related
I thought I understand the concepts of bitmasks, CategoryBitMasks and CollisionBitMasks, but it turns out I am not ;-(
but I dont understand why.
I set the bit Masks for collision detection, I add the category Bit mask to the frame, and I add the categoryBitMask to my object (in this case a taxi). But the taxi just drops down the screen :-/
Any ideas why this is like that?
#import "MyScene.h"
#import "SKSpriteNode+DebugDraw.h"
// Define Bit Masks for Collision Detection
typedef NS_OPTIONS(uint32_t, CNPhysicsCategory) {
CNPhysicsCategoryEdge = 1 <<0,
CNPhysicsCategoryTaxi = 1 <<1,
};
#interface MyScene() <SKPhysicsContactDelegate>
#end
#implementation MyScene{
SKNode *_gameNode;
SKSpriteNode *_taxiNode;
}
-(instancetype)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
[self initializeScene];
}
return self;
}
-(void)initializeScene{
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
self.physicsWorld.contactDelegate = self;
self.physicsBody.categoryBitMask = CNPhysicsCategoryEdge;
SKSpriteNode* bg = [SKSpriteNode spriteNodeWithImageNamed:#"background.png"];
bg.position = CGPointMake(self.size.width/2, self.size.height/2);
[self addChild: bg];
[self addTaxi];
_gameNode = [SKNode node];
[self addChild:_gameNode];
}
-(void)addTaxi{
_taxiNode = [SKSpriteNode spriteNodeWithImageNamed:#"taxi.png"];
_taxiNode.position = CGPointMake(self.size.width/2, self.size.height/2);
[self addChild:_taxiNode];
CGSize contactSize = CGSizeMake(_taxiNode.size.width, _taxiNode.size.height);
_taxiNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize: contactSize];
[_taxiNode attachDebugRectWithSize:contactSize];
_taxiNode.physicsBody.categoryBitMask = CNPhysicsCategoryTaxi;
}
I finally managed it - the problem was not the collision detection, but it was the layout of the phone, since I did not replace the viewDidLoad method in the ViewController with viewWillLayoutSubviews method.
Now everything works fine.
The significant post was the following: bodyWithEdgeLoopFromRect not working in landscape
Thanks for your support!
I'm trying to add a simple particle effect overlay to one of my UIViewController instances. I've followed a couple of tutorials I found, but neither of them deal with view controllers and their storyboards are just a view out on its own with no controller, which is confusing.
Here's my code, I'm just trying to figure out why I can't see the particle effects. What am I missing? The view itself is definitely there and added (if I change its color or something I can see it), it's just empty, and isn't showing any particle effects. The image file referenced is definitely in the project and target, so what else have I done wrong here? Do I need to add the CAEmitterLayer to rainView somehow? The tutorials didn't offer any help on this part!
RainfallOverlay.h
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#interface RainfallOverlay : UIView {
CAEmitterLayer *rainEmitter;
}
#end
RainfallOverlay.m
#import "RainfallOverlay.h"
#implementation RainfallOverlay
- (void) awakeFromNib
{
rainEmitter = (CAEmitterLayer *) self.layer;
rainEmitter.emitterPosition = CGPointMake(160, 100);
rainEmitter.emitterSize = CGSizeMake(10, 10);
rainEmitter.renderMode = kCAEmitterLayerAdditive;
CAEmitterCell *rain = [CAEmitterCell emitterCell];
rain.birthRate = 200;
rain.lifetime = 2.0;
rain.lifetimeRange = 1.5;
rain.color = [[UIColor colorWithRed: 0.2 green: 0.4 blue: 0.8 alpha: 0.1] CGColor];
rain.contents = (id) [[UIImage imageNamed: #"Particles_rain.png"] CGImage];
rain.name = #"rain";
rain.velocity = 150;
rain.velocityRange = 100;
rain.emissionRange = M_PI_2;
rain.emissionLongitude = 0.025 * 180 / M_PI;
rain.scaleSpeed = 0;
rain.spin = 0.5;
rainEmitter.emitterCells = [NSArray arrayWithObject: rain];
}
#end
ViewController.m viewDidLoad
RainfallOverlay *rainView = [[RainfallOverlay alloc] initWithFrame: CGRectMake(0, 0, 320, 250)];
[rainView setUserInteractionEnabled: NO];
[self.view bringSubviewToFront: rainView];
[self.view addSubview: rainView];
There are 2 problems:
awakeFromNib would only be called if the view is loaded from a nib file. In your case, you have to implement initWithFrame.
You have to override layerClass to that self.layer returns a CAEmitterLayer. Just casting the layer to CAEmitterLayer does not work.
So your RainfallOverlay implementation should look like this:
+ (Class)layerClass
{
return [CAEmitterLayer class];
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
rainEmitter = (CAEmitterLayer *) self.layer;
// ... remaining setup
}
return self;
}
What would be the best way to implament a simple line graph inside my iPad app. Im only looking to plot 4-8 points and the graph does need to be fancy looking. HTML5 will be good enough to use for what I need.
How would I go about implementing HTML code to plot a line graph?
I have been looking at using Google Charting Tools, but any other suggestions that I could use?
Here is a very basic class that will give you the basic line. It takes an NSArray of NSNumbers for the Y axis. It should provide a good starting point for you as it is as simple as it gets. You might consider adding labels to the points and axis markers to give a sense of scale.
Header:
#import <UIKit/UIKit.h>
#interface SOGenericGraphView : UIView {
NSArray *yValues;
}
#property (strong, atomic) NSArray *yValues;
#end
Implementation:
#import "SOGenericGraphView.h"
#implementation SOGenericGraphView
#synthesize yValues;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.backgroundColor = [UIColor whiteColor];
}
return self;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
[super drawRect:rect];
CGRect insetRect = CGRectInset(self.frame, 10, 15);
double maxSpeed = [[yValues valueForKeyPath:#"#max.doubleValue"] doubleValue];
CGFloat yRatio = insetRect.size.height/maxSpeed;
CGFloat xRatio = insetRect.size.width/(yValues.count-1);
UIBezierPath *sparkline = [UIBezierPath bezierPath];
for (int x = 0; x< yValues.count; x++) {
CGPoint newPoint = CGPointMake(x*xRatio + insetRect.origin.x, insetRect.size.height - (yRatio*[[yValues objectAtIndex:x] doubleValue] - insetRect.origin.y));
if (x == 0) {
[sparkline moveToPoint:newPoint];
}
else {
[sparkline addLineToPoint:newPoint];
}
}
[[UIColor redColor] set];
[sparkline stroke];
}
#end
I´ve seen tons of people having this problem but I can´t seem to find a solution for my case. Like the title explains I have a scrolling background, using two sprites containing the same image. I´ve set the height to 321 (321x480) believing that would fix the problem, boy, it did not.
Well, this is my setup in the init:
background = [CCSprite spriteWithFile:#"level1BG.png"];
background.position = ccp(background.contentSize.width/2, background.contentSize.height/2);
[self addChild:background];
background2 = [CCSprite spriteWithFile:#"level1BG.png"];
background2.position = ccp(background2.contentSize.width/2, -background2.contentSize.height/2);
[self addChild:background2];
Nothing fancy here, just an setup.
And this is my schedule scroll(with has a ccTime parameter of course):
Oh, the background scrolls upwards, increasing the y-value.
-(void)scroll:(ccTime)dt{
background.position = ccp(background.position.x, background.position.y + GAME_SPEED*dt);
background2.position = ccp(background2.position.x, background2.position.y + GAME_SPEED*dt);
if(background.position.y >= background.contentSize.width){
background.position = ccp(background.position.x, -background.contentSize.height/2 + 1);
}else if(background2.position.y >= background2.contentSize.width){
background2.position = ccp(background2.position.x, -background2.contentSize.height/2 + 1);
}
}
GAME_SPEED is defined to 50.0. I´ve added the "+ 1" believing THAT would fix the problem, wrong again though!
So, to the question, does anyone know a way to remove the gap in this case? Would be forever grateful!
Regards
Here is some old code that i use to make clouds appears over an scene
Clouds.h
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#interface Clouds : CCLayer {
CCSprite *first;
CCSprite *second;
float firstX0;
float secondX0;
float firstXf;
float secondXf;
float height;
float firstWidth;
float secondWidth;
float Yvalue;
float duration;
id moveFirstDone;
id moveSecondDone;
}
#end
Clouds.m
#import "Clouds.h"
#implementation Clouds
-(id) init{
if( (self=[super init] )) {
first = [CCSprite spriteWithFile:#"clouds_1.png"];
firstWidth = first.contentSize.width;
height = first.contentSize.height;
second = [CCSprite spriteWithFile:#"clouds_2.png"];
secondWidth = second.contentSize.width;
Yvalue = 220.0f;
duration = 200.0f;
CGSize size = [[CCDirector sharedDirector] winSize];
firstXf = -1 * (secondWidth - size.width + firstWidth/2);
secondXf = -1 * (firstWidth + secondWidth/2);
firstX0 = size.width + firstWidth/2;
secondX0 = size.width + secondWidth/2;
moveFirstDone = [CCCallFuncN actionWithTarget:self selector:#selector(callFirstDone:)];
moveSecondDone = [CCCallFuncN actionWithTarget:self selector:#selector(callSecondDone:)];
first.position = ccp(firstX0 + -1*firstX0,Yvalue);
second.position = ccp(secondX0,Yvalue);
[self addChild:first];
[self addChild:second];
[first runAction:[CCSequence actions:[CCMoveTo actionWithDuration:duration position:ccp(firstXf,Yvalue)],moveFirstDone,nil]];
[second runAction:[CCSequence actions:[CCMoveTo actionWithDuration:duration*2 position:ccp(secondXf,Yvalue)],moveSecondDone,nil]];
}
return self;
}
-(void)callSecondDone:(id)sender{
second.position = ccp(secondX0,Yvalue);
[second runAction:[CCSequence actions:[CCMoveTo actionWithDuration:duration*2 position:ccp(secondXf,Yvalue)],moveSecondDone,nil]];
}
-(void)callFirstDone:(id)sender{
first.position = ccp(firstX0,Yvalue);
[first runAction:[CCSequence actions:[CCMoveTo actionWithDuration:duration*2 position:ccp(firstXf,Yvalue)],moveFirstDone,nil]];
}
#end
Using
Clouds *clouds = [Clouds node];
[self addChild: clouds z:0];
you add the node to your main layer/scene
I don't know if this is exactly what you need but maybe it helps. See a video of the effect that i get using this approach https://rapidshare.com/files/3478655240/2012-02-14_1458.swf
Just for clarification, when you set height to 321 pixels, do you adjust position?
background.position = ccp(background.contentSize.width/2, (background.contentSize.height - 1)/2);
background2.position = ccp(background2.contentSize.width/2, -(background2.contentSize.height - 1)/2);
(Updated)
For clouds case I would propose alternative: use one sprite with tiled in horizontal direction cloud texture (texture must be with power-of-two size). Then scroll texture coordinates instead of moving sprite. But looks like this functionality is not present in official cocos2d sources, so you need to get it from here and merge with CCTextureNode class. Then:
set GL_TEXTURE_WRAP_S texture parameter for cloud texture via [cloudTexture setTexParameters: ...]
scroll texture with TextureMoveBy action, or by adjusting cloudTexture.texPosition in update method
I'm searching for a way to implement something like reusable cells for UI/NSTableView but for NSScrollView. Basically I want the same like the WWDC 2011 video "Session 104 - Advanced Scroll View Techniques" but for Mac.
I have several problems realizing this. The first: NSScrollView doesn't have -layoutSubviews. I tried to use -adjustScroll instead but fail in setting a different contentOffset:
- (NSRect)adjustScroll:(NSRect)proposedVisibleRect {
if (proposedVisibleRect.origin.x > 600) {
// non of them work properly
// proposedVisibleRect.origin.x = 0;
// [self setBoundsOrigin:NSZeroPoint];
// [self setFrameOrigin:NSZeroPoint];
// [[parentScrollView contentView] scrollPoint:NSZeroPoint];
// [[parentScrollView contentView] setBoundsOrigin:NSZeroPoint];
}
return proposedVisibleRect;
}
The next thing I tried was to set a really huge content view with a width of millions of pixel (which actually works in comparison to iOS!) but now the question is, how to install a reuse-pool?
Is it better to move the subviews while scrolling to a new position or to remove all subviews and insert them again? and how and where should I do that?
As best I can tell, -adjustScroll: is not where you want to tap into the scrolling events because it doesn't get called universally. I think -reflectScrolledClipView: is probably a better hookup point.
I cooked up the following example that should hit the high points of one way to do a view-reusing scroll view. For simplicity, I set the dimensions of the scrollView's documentView to "huge", as you suggest, rather than trying to "fake up" the scrolling behavior to look infinite. Obviously drawing the constituent tile views for real is up to you. (In this example I created a dummy view that just fills itself with red with a blue outline to convince myself that everything was working.) It came out like this:
// For the header file
#interface SOReuseScrollView : NSScrollView
#end
// For the implementation file
#interface SOReuseScrollView () // Private
- (void)p_updateTiles;
#property (nonatomic, readonly, retain) NSMutableArray* p_reusableViews;
#end
// Just a small diagnosting view to convince myself that this works.
#interface SODiagnosticView : NSView
#end
#implementation SOReuseScrollView
#synthesize p_reusableViews = mReusableViews;
- (void)dealloc
{
[mReusableViews release];
[super dealloc];
}
- (NSMutableArray*)p_reusableViews
{
if (nil == mReusableViews)
{
mReusableViews = [[NSMutableArray alloc] init];
}
return mReusableViews;
}
- (void)reflectScrolledClipView:(NSClipView *)cView
{
[super reflectScrolledClipView: cView];
[self p_updateTiles];
}
- (void)p_updateTiles
{
// The size of a tile...
static const NSSize gGranuleSize = {250.0, 250.0};
NSMutableArray* reusableViews = self.p_reusableViews;
NSRect documentVisibleRect = self.documentVisibleRect;
// Determine the needed tiles for coverage
const CGFloat xMin = floor(NSMinX(documentVisibleRect) / gGranuleSize.width) * gGranuleSize.width;
const CGFloat xMax = xMin + (ceil((NSMaxX(documentVisibleRect) - xMin) / gGranuleSize.width) * gGranuleSize.width);
const CGFloat yMin = floor(NSMinY(documentVisibleRect) / gGranuleSize.height) * gGranuleSize.height;
const CGFloat yMax = ceil((NSMaxY(documentVisibleRect) - yMin) / gGranuleSize.height) * gGranuleSize.height;
// Figure out the tile frames we would need to get full coverage
NSMutableSet* neededTileFrames = [NSMutableSet set];
for (CGFloat x = xMin; x < xMax; x += gGranuleSize.width)
{
for (CGFloat y = yMin; y < yMax; y += gGranuleSize.height)
{
NSRect rect = NSMakeRect(x, y, gGranuleSize.width, gGranuleSize.height);
[neededTileFrames addObject: [NSValue valueWithRect: rect]];
}
}
// See if we already have subviews that cover these needed frames.
for (NSView* subview in [[[self.documentView subviews] copy] autorelease])
{
NSValue* frameRectVal = [NSValue valueWithRect: subview.frame];
// If we don't need this one any more...
if (![neededTileFrames containsObject: frameRectVal])
{
// Then recycle it...
[reusableViews addObject: subview];
[subview removeFromSuperview];
}
else
{
// Take this frame rect off the To-do list.
[neededTileFrames removeObject: frameRectVal];
}
}
// Add needed tiles from the to-do list
for (NSValue* neededFrame in neededTileFrames)
{
NSView* view = [[[reusableViews lastObject] retain] autorelease];
[reusableViews removeLastObject];
if (nil == view)
{
// Create one if we didnt find a reusable one.
view = [[[SODiagnosticView alloc] initWithFrame: NSZeroRect] autorelease];
NSLog(#"Created a view.");
}
else
{
NSLog(#"Reused a view.");
}
// Place it and install it.
view.frame = [neededFrame rectValue];
[view setNeedsDisplay: YES];
[self.documentView addSubview: view];
}
}
#end
#implementation SODiagnosticView
- (void)drawRect:(NSRect)dirtyRect
{
// Draw a red tile with a blue border.
[[NSColor blueColor] set];
NSRectFill(self.bounds);
[[NSColor redColor] setFill];
NSRectFill(NSInsetRect(self.bounds, 2,2));
}
#end
This worked pretty well as best I could tell. Again, drawing something meaningful in the reused views is where the real work is here.
Hope that helps.