I'm trying to use a UIPanGestureRecognizer to move an object around the screen. My panning code seems to work fine. I can move my object around with ease. But when I let go of the object it snaps back to its original position; it doesn't stay where I pan it.
The pan-able object is a simple UIView, and it's being moved around in my main self.view field. I added the UIPanGesture using the objects palate in XCode, not manually in code in viewDidLoad for example.
In state = began, I apply a transform to bump the scale up slightly. (This ALSO doesn't appear to be working, but that's another story).
Can anyone please tell me where I'm going wrong?
- (IBAction)bucketTouch:(UIPanGestureRecognizer *)recognizer {
switch ([recognizer state]) {
case UIGestureRecognizerStateBegan:{
[UIView animateWithDuration:.2 animations:^{
self.pail.transform = CGAffineTransformMakeScale(1.25, 1.25);
}];
}
break;
case UIGestureRecognizerStateChanged:{
CGPoint centerPoint = self.pail.center;
CGPoint newPoint = [recognizer translationInView:self.view];
CGPoint finalPoint = CGPointMake(centerPoint.x + newPoint.x, centerPoint.y + newPoint.y);
self.pail.center = finalPoint;
[recognizer setTranslation:CGPointZero inView:self.view];
}
break;
case UIGestureRecognizerStateEnded:{
[self.view sendSubviewToBack:self.pail];
[UIView animateWithDuration:.2 animations:^{
self.pail.transform = CGAffineTransformIdentity;
}];
}
break;
default:
break;
}
}
Thanks in advance. :-)
I figured out that the view WAS indeed moving properly, but the CGAffineTransformIdentity was placing it back to its original frame position upon recognizer.state = ended.
Related
I am building SDL2 applications for macOS using C++. I need some "basic" scrolling for an app (like a web browser). I achieve that using the SDL_MouseWheel event, which gives me a fully functional "windows-like" scrolling. I am using a Macbook Pro and I want to bring the trackpad's functionality in.
Simply, I am asking for a better scrolling algorithm (Macbook's trackpad scrolling, inertial scrolling)
I know about the SDL_MultiGesture event, but I don't really know how to put things together to achieve the result I want.
I ran into the same issue, and it turns out that SDL opts out of momentum scroll events by turning off the AppleMomentumScrollSupported default in the user defaults system.
You can turn this back on in your application with the following bit of Objective-C++, and your SDL_MouseWheel events will become smoothed.
NSDictionary *appDefaults = [[NSDictionary alloc] initWithObjectsAndKeys:
[NSNumber numberWithBool:YES], #"AppleMomentumScrollSupported",
nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:appDefaults];
[appDefaults release];
Okay, here's the answer to the problem.
First of all, you will have to do it manually.
I also assume that you know SDL2 and C++.
*Note: I'm doing scrolling only for Y-Axis (you can do it for both if you want).
Firstly we will need some variables:
int scrolling; // flag (scrolling or not)
int scroll_sensitivity = 40; // how fast we want to scroll
double scroll_Y = 0; // current scrolling amount (on Y-Axis)
double scroll_acceleration; // scrolling speed
double scroll_friction = 0.001; // how fast we decelerate
double scroll_prev_pos; // previous event's position
After that you will need to handle the SDL_MultiGesture event:
case SDL_MULTIGESTURE:{
if(event.mgesture.numFingers == 2){
if(scrolling == 0){
scrolling = 1;
scroll_prev_pos = event.mgesture.y;
} else{
double dy = event.mgesture.y - scroll_prev_pos;
scroll_acceleration = dy * 40;
scroll_prev_pos = event.mgesture.y;
scrolling = 1;
}
}
break;
}
Also, we need to stop scrolling on SDL_FingerDown event:
case SDL_FINGERDOWN:{
scrolling = 0;
break;
}
Next, we want to update scroll_Y (put it in your "update" function):
if(scrolling){
if(scroll_acceleration > 0) scroll_acceleration -= scroll_friction;
if(scroll_acceleration < 0) scroll_acceleration += scroll_friction;
if(abs(scroll_acceleration) < 0.0005) scroll_acceleration = 0;
scroll_Y += scroll_sensitivity * scroll_acceleration;
// Here you have to set your scrolling bounds i.e. if(scroll_Y < 0) scroll_Y = 0;
}
Finally, we want to render according to our scroll values:
SDL_Rect rect = {some_x, some_y + scroll_Y, some_w, some_h};
SDL_RenderCopy(renderer, some_texture, NULL, &rect);
This is it!
I have a fully working app with the above code, so I'm 100% sure that this works as supposed. If you have any problems (because it's not actual code, it's more like an algorithm) contact me. As I mentioned before, I assume that you already know good enough SDL and C++, so I believe that you are able to understand the implementation.
Also, I know that this solution can become better, so if you have anything to add / change, just say it!
Have a nice day!
For some reason, #Aleski's answer doesn't work anymore. The new way to hack this feature back on is:
[[NSUserDefaults standardUserDefaults] setBool: YES
forKey: #"AppleMomentumScrollSupported"];
Thanks for taking the time to look at my noob question and any help will be greatly appreciated.
Firstly I am trying to make an object 'bounce' from one side of the screen to the other using bezier curves (don't want to use box2d).
The object has 4 states representing the 4 movements, when the object hits (reacts to) the floor it moves from the previous state to a new state, please see picture below...
**My problem is that I can not get it to transition from state 1 to state 4 using CGRect correctly. I can currently get the object to move states but then one of two things happen..
1) The below code will allow the object to change from state 1 to state 2 correctly but it will then not react with the floor (it just moves through it) at the end of state 2 so does not change to state 3.
-(void)updateStateWithDeltaTime:(ccTime)deltaTime andListOfGameObjects:(CCArray *)listOfGameObjects {
[self setDebugLabelTextAndPosition];
if ([self isOutsideOfScreen])
return;
CGPoint currentSpitePosition = [self position];
floorCharacter = (GameCharacter*)[[self parent]
getChildByTag:kfloorSpriteTagValue];
CGRect floorBoudingBox = [floorCharacter adjustedBoundingBox];
CGRect ballBoundingBox = [self adjustedBoundingBox];
CGRect characterBox = [character adjustedBoundingBox];
if (CGRectIntersectsRect(floorBoudingBox, ballBoundingBox)) {
if (characterState == kStateTravelling) {
[self changeState:kStateTravellingPoint2];
}
if (characterState == kStateTravellingPoint2) {
[self changeState:kStateTravellingPoint3];
}
}
2) The below code will stop the object when it first hits the floor but if I remove the floor for a second while the program is running the object will then move to state 3 and once again stop when it hits the floor, if I remove the floor again it will once again move to state 4, this is without adding the other states (as you will see below) so it is just repeating state 2 each time. This I am guessing is because it returns TRUE as soon as it hits the floor and while it is TRUE it will not action.
-(void)updateStateWithDeltaTime:(ccTime)deltaTime andListOfGameObjects:(CCArray *)listOfGameObjects {
[self setDebugLabelTextAndPosition];
if ([self isOutsideOfScreen])
return;
CGPoint currentSpitePosition = [self position];
floorCharacter = (GameCharacter*)[[self parent]
getChildByTag:kfloorSpriteTagValue];
CGRect floorBoudingBox = [floorCharacter adjustedBoundingBox];
CGRect ballBoundingBox = [self adjustedBoundingBox];
isfloorWithinBoundingBox =
CGRectIntersectsRect(floorBoudingBox, ballBoundingBox) ?
YES : NO;
if (isfloorWithinBoundingBox) {
[self changeState:kStateTravellingPoint2];
}
What I want it to do is move between the 4 states if the floor is active in a 'bounce' movement, so as soon as state 1 is complete it will hit the floor and move into state 2 until it completes the curve movement and hits the floor again and then moves into state 3 etc.
Right I have got the above to work but do not completely understand how I have done it!
I tried using screenSize to determine where the object was and base my CGRect from that but it did not work at first. I then kept the code but added 'CGSize screenSize = [CCDirector sharedDirector].winSize;' to the method and then to object performed just how I wanted.
I now however have the following errors when I use screenSize..
'Local declaration of 'screenSize' hide instance variable'
What I would like to know is why its not working using screenSize until I declare it in the method (as its already been declared in another class that is imported into this one).
Thanks again to anyone that has taken the time to read this.
my code is below...
-(void)updateStateWithDeltaTime:(ccTime)deltaTime andListOfGameObjects:(CCArray *)listOfGameObjects {
[self setDebugLabelTextAndPosition];
if ([self isOutsideOfScreen])
return;
CGPoint currentSpitePosition = [self position];
floorCharacter = (GameCharacter*)[[self parent]
getChildByTag:kfloorSpriteTagValue];
CGRect floorBoudingBox = [floorCharacter adjustedBoundingBox];
CGRect ballBoundingBox = [self adjustedBoundingBox];
CGPoint floorPosition = [floorCharacter position];
*Below is where I have added screenSize*
CGSize screenSize = [CCDirector sharedDirector].winSize;
isfloorWithinBoundingBox =
CGRectIntersectsRect(floorBoudingBox, ballBoundingBox) ?
YES : NO;
if ((characterState == kStateTravelling) && (isfloorWithinBoundingBox == YES)) {{
[self changeState:kStateTravellingPoint2];
}
return;
}
if ((characterState == kStateTravellingPoint2)&&(isfloorWithinBoundingBox == YES) && (floorPosition.x > screenSize.width*0.32f) && (floorPosition.x < screenSize.width*0.45f)) {{
[self changeState:kStateTravellingPoint3];
}
return;
}
if ((characterState == kStateTravellingPoint3)&&(isfloorWithinBoundingBox == YES) && (floorPosition.x > screenSize.width*0.45f) && (floorPosition.x < screenSize.width*0.55f)) {{
[self changeState:kStateTravellingPoint4];
}
return;
}
I think I'm just understanding scaling/positioning nodes/layers incorrectly. I'm setting up my node like this (node class is derived from CCNode):
-(id) init
{
if ((self = [super init]))
{
// Create parallax background node.
background = [BackgroundNode node];
[self addChild:background z:0];
// Create foreground node.
foreground = [ForegroundLayer node];
[self addChild:foreground z:0];
self.position.y = 500.0f;
self.scaleX = 1.5f;
self.scaleY = 1.5f;
}
return self;
}
It doesn't seem to matter what I set the self.position.y to - the scaled node is always displayed as though it was positioned in the bottom-left of the screen. I've tried playing around with anchorPoint as well, but it doesn't really seem to change anything.
The reason I'm asking is because I'd like to be able to pan vertically when I'm zoomed in - this doesn't seem to really be the right way to accomplish it, though. Any ideas?
self.position.y = 500.0f;
doesn't work. It should be
self.position = ccp(self.position.x, 500.0f);
Please refer to "Cocoa Objective-c Property C structure assign fails".
I have one question when infinite background scrolling is done, is the object remain fixed(like doodle in doodle jump, papy in papi jump) or these object really moves.Is only background move or both (background and object )move.plz someone help me.I am searching for this solution for 4/5 days,but can't get the solution.So plz someone help me. And if object does not move how to create such a illusion of object moving.
If you add the object to the same layer as the scrolling background, then it will scroll as the background scrolls.
If your looking for an effect like the hero in doodle jump, you may want to look at having two or more layers in a scene.
Layer 1: Scrolling Background Layer
Layer 2: Sprite layer
SomeScene.m
CCLayer *backgroundLayer = [[CCLayer alloc] init];
CCLayer *spriteLayer= [[CCLayer alloc] init];
[self addChild:backgroundLayer z:0];
[self addChild:spriteLayer z:1];
//Hero stays in one spot regardless of background scrolling.
CCSprite *squidHero = [[CCSprite alloc] initWithFile:#"squid.png"];
[spriteLayer addChild:squidHero];
If you want objects to scroll with the background add it to the background layer:
//Platform moves with background.
CCSprite *bouncePlatform= [[CCSprite alloc] initWithFile:#"bouncePlatform.png"];
[backgroundLayer addChild:bouncePlatform];
Another alternative is to use a CCFollow action. You would code as if the background is static (which it will be) and the player is moving (which it will be), but add a CCFollow action to the player. This essentially moves the camera so that it tracks your player.
You can also modify the classes so that you can get the CCFollow action to follow with an offset (i.e., so the player is not in the middle of the screen) as well as to have a smoothing effect to it, so that when the player moves, the follow action is not jerky. See the below code:
*NOTE I am using cocos2d-x, the c++ port. The methods are similar in cocos2d, and you should be able to modify these to fit the cocos2d syntax. Or search around -- I found these for cocos2d and then ported to c++.
//defines the action to constantly follow the player (in my case, "runner.p_sprite is the sprite pointing to the player)
FollowWithOffset* followAction = FollowWithOffset::create(runner.p_sprite, CCRectZero);
runAction(followAction);
And separately, I have copied the class definition for CCFollow to create my own class, CCFollowWithAction. This also has a smoothing effect (you can look this up more online) so that when the player moves, the actions are not jerky. I modified "initWithTarget," to take into account an offset, and "step," to add a smoothing action. You can see the modifications in the comments below.
bool FollowWithOffset::initWithTarget(CCNode *pFollowedNode, const CCRect& rect/* = CCRectZero*/)
{
CCAssert(pFollowedNode != NULL, "");
pFollowedNode->retain();
m_pobFollowedNode = pFollowedNode;
if (rect.equals(CCRectZero))
{
m_bBoundarySet = false;
}
else
{
m_bBoundarySet = true;
}
m_bBoundaryFullyCovered = false;
CCSize winSize = CCDirector::sharedDirector()->getWinSize();
m_obFullScreenSize = CCPointMake(winSize.width, winSize.height);
//m_obHalfScreenSize = ccpMult(m_obFullScreenSize, 0.5f);
m_obHalfScreenSize = CCPointMake(m_obFullScreenSize.x/2 + RUNNER_FOLLOW_OFFSET_X,
m_obFullScreenSize.y/2 + RUNNER_FOLLOW_OFFSET_Y);
if (m_bBoundarySet)
{
m_fLeftBoundary = -((rect.origin.x+rect.size.width) - m_obFullScreenSize.x);
m_fRightBoundary = -rect.origin.x ;
m_fTopBoundary = -rect.origin.y;
m_fBottomBoundary = -((rect.origin.y+rect.size.height) - m_obFullScreenSize.y);
if(m_fRightBoundary < m_fLeftBoundary)
{
// screen width is larger than world's boundary width
//set both in the middle of the world
m_fRightBoundary = m_fLeftBoundary = (m_fLeftBoundary + m_fRightBoundary) / 2;
}
if(m_fTopBoundary < m_fBottomBoundary)
{
// screen width is larger than world's boundary width
//set both in the middle of the world
m_fTopBoundary = m_fBottomBoundary = (m_fTopBoundary + m_fBottomBoundary) / 2;
}
if( (m_fTopBoundary == m_fBottomBoundary) && (m_fLeftBoundary == m_fRightBoundary) )
{
m_bBoundaryFullyCovered = true;
}
}
return true;
}
void FollowWithOffset::step(float dt)
{
CC_UNUSED_PARAM(dt);
if(m_bBoundarySet){
// whole map fits inside a single screen, no need to modify the position - unless map boundaries are increased
if(m_bBoundaryFullyCovered)
return;
CCPoint tempPos = ccpSub( m_obHalfScreenSize, m_pobFollowedNode->getPosition());
m_pTarget->setPosition(ccp(clampf(tempPos.x, m_fLeftBoundary, m_fRightBoundary),
clampf(tempPos.y, m_fBottomBoundary, m_fTopBoundary)));
}
else{
//custom written code to add in support for a smooth ccfollow action
CCPoint tempPos = ccpSub( m_obHalfScreenSize, m_pobFollowedNode->getPosition());
CCPoint moveVect = ccpMult(ccpSub(tempPos,m_pTarget->getPosition()),0.25); //0.25 is the smooth constant.
CCPoint newPos = ccpAdd(m_pTarget->getPosition(), moveVect);
m_pTarget->setPosition(newPos);
}
}
I've got a CCSprite with three animations: idle, walk and attack. I want to switch between idle and walk depending on whether or not the sprite is moving (if the joystick is beeing used). All of this works great.
For the attacking animation, I want it to run once, and then return to the previous animation when done (ex.: idle) how do I detect when the animation is done?
thanks,
Dave
Alright, so here's what i've done, and it works, although I have no clue if its the right or the best way:
1) store the current animation.
2) If the currentAnimation is attacking, do nothing.
3) when switching between animations, if the new animation is attacking, run a sequence on the sprite, the second action being a callback to a "onDoneAttacking"
4) in the callback, change the current animation
But this isn't veery smooth and it doesn't allow to attack very fast.
Here's what it looks like:
-(void) changeAnimation:(NSString*)name forTime:(int) times {
if(currentAnimation != #"attack" )
{
CCFiniteTimeAction *action = [CCAnimate actionWithAnimation:[self animationByName:name]];
CCRepeat *repeatAction = [CCRepeat actionWithAction:action times:1];
if(name == #"attack") {
id doneAttacking = [CCCallFunc actionWithTarget:self selector:#selector(onDoneAttacking)];
[self runAction:[CCSequence actionOne:repeatAction two:doneAttacking]];
}
else {
[self runAction:repeatAction];
}
currentAnimation = name;
}
}
-(void) onDoneAttacking {
currentAnimation = #"idle";
}