So... I am working on this small Mario-like game for iOS and have run into a problem that I cannot seem to fix.
When using my controls (see the screenshot) they sometime get "stuck" as in, the iOS device did not notice that I let go of a button. This causes the player to keep on moving in a direction after you have let go of the button.
Edit: It seems that the malfunction happens when I click two buttons at the exact same time.
Hopefully, someone will be able to spot what I can do differently in the following code:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch *touch in touches) {
CGPoint touchLocation = [touch locationInNode:self];
[self touchStateChanged:touchLocation buttonState:YES];
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch *touch in touches) {
CGPoint prevTouchLocation = [touch previousLocationInNode:self];
[self touchStateChanged:prevTouchLocation buttonState:NO];
CGPoint touchLocation = [touch locationInNode:self];
[self touchStateChanged:touchLocation buttonState:YES];
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch *touch in touches) {
// Some other logic... {...}
[self touchStateChanged:touchLocation buttonState:NO];
}
}
-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch *touch in touches) {
CGPoint touchLocation = [touch locationInNode:self];
[self touchStateChanged:touchLocation buttonState:NO];
}
}
-(void)touchStateChanged:(CGPoint)location buttonState:(BOOL)state
{
SKNode *n = [self nodeAtPoint:location];
if ([n.name isEqualToString:#"LeftMove"]) {
self.player.moveLeft = state;
}
else if ([n.name isEqualToString:#"RightMove"]) {
self.player.moveRight = state;
}
else if ([n.name isEqualToString:#"Jump"]) {
self.player.didJump = state;
}
}
Screenshot showing buttons:
Thanks :-)
Partly Solved...
Current solution:
Added an instance variable called _touchEvent of type UIEvent and added the following line to touchesEnded:
_touchEvent = event;
Added the following code to my update method:
if (_touchEvent && (int)[[_touchEvent allTouches]count] == 0) {
self.player.moveLeft = NO;
self.player.moveRight = NO;
self.player.didJump = NO;
}
if (_touchEvent && (int)[[_touchEvent allTouches]count] == 1) {
for (UITouch *touch in [_touchEvent allTouches]) {
SKNode *n = [self nodeAtPoint:[touch locationInNode:self]];
if ([n.name isEqualToString:#"Jump"]) {
self.player.moveLeft = NO;
self.player.moveRight = NO;
}
}
}
I think the problem is the location of touch (in -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event method) is outside any node.
Try to implement something like this
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
for (SKNode *node in yourButtonsArray) {
// Some other logic... {...}
[node setState:NO];
}
}
Or you should take a look at KKButtonBehaviour from KoboldKit framework.
Related
I have added a UITableView as a subview on my UITableView.
I have troubling 'passing through' touches on my UITableView (subview) to the view of my UIViewController (where my touchesBegan: method works as expected).
I have tried:
Subclassing my UITableView and passing touchesBegan: to the superview from there
pointInside: in superview
hitTest: in subclassed tableView
Also: Disabling userInteraction on my tableView works, but of course, I still want to maintain the cellDidSelectRow.. functionality.
My Code implementation in ViewController:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"touchesBegan popup");
if (!self.draggable) {
//[super touchesBegan:touches withEvent:event];
return;
}
UITouch *touch = [touches anyObject];
if (!_moving) {
//(touch.view == self || touch.view.superview == self) &&
_moving = YES;
_movingStartY = [touch locationInView:self.view].y;
NSLog(#"moving popup");
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
if (!self.draggable) {
//[super touchesMoved:touches withEvent:event];
return;
}
if (_moving) {
NSLog(#"touchesMoved popup");
UITouch *touch = [touches anyObject];
float offset = [touch locationInView:self.view].y - _movingStartY;
if ([self.touchEventDelegate respondsToSelector:#selector(popupNavigationBar:touchDidMoveWithOffset:)]) {
[self.touchEventDelegate popupNavigationBar:self touchDidMoveWithOffset:offset];
}
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
if (!self.draggable) {
//[super touchesCancelled:touches withEvent:event];
return;
}
if (_moving) {
UITouch *touch = [touches anyObject];
float offset = [touch locationInView:self.view].y - _movingStartY;
[self movingDidEndWithOffset:offset];
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if (!self.draggable) {
//[self touchesEnded:touches withEvent:event];
return;
}
if (_moving) {
UITouch *touch = [touches anyObject];
float offset = [touch locationInView:self.view].y - _movingStartY;
[self movingDidEndWithOffset:offset];
}
}
- (void)movingDidEndWithOffset:(float)offset
{
_moving = NO;
if ([self.touchEventDelegate respondsToSelector:#selector(popupNavigationBar:touchDidEndWithOffset:)]) {
[self.touchEventDelegate popupNavigationBar:self touchDidEndWithOffset:offset];
}
}
How can I accomplish this?
In my current ios project, I have dedicated one side of the screen to one object and the other side of the screen to the other object and i have made it so that if you swipe your finger across one side of the screen on the designated object would move. However, I want to make it so that you can move both objects at the same time in different movement but i cannot figure out how to do so. Below is my current code.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:touch.view];
if (location.x <= 270 ) {
[Person setCenter:CGPointMake(location.x, Person.center.y)];
}
else {
[Person1 setCenter:CGPointMake(location.x, Person1.center.y)];
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:touch.view];
if (location.x <= 270 ) {
[Person setCenter:CGPointMake(location.x, Person.center.y)];
}
else {
[Person1 setCenter:CGPointMake(location.x, Person1.center.y)];
}
}
you should start handling multiple touches that are delivered in the touches set - loop through all the UITouch objects and do your handling.
Edit:
here's your code:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for(UITouch *touch in [event allTouches]) {
CGPoint location = [touch locationInView:touch.view];
if (location.x <= 270 ) {
[Person setCenter:CGPointMake(location.x, Person.center.y)];
}
else {
[Person1 setCenter:CGPointMake(location.x, Person1.center.y)];
}
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
for(UITouch *touch in [event allTouches]) {
CGPoint location = [touch locationInView:touch.view];
if (location.x <= 270 ) {
[Person setCenter:CGPointMake(location.x, Person.center.y)];
}
else {
[Person1 setCenter:CGPointMake(location.x, Person1.center.y)];
}
}
}
If you move the -touchesBegan & touchesMoved code into the Person view class rather than the view/or viewController class it is in currently then those views can handle touches independent of each other and simultaneously.
*Edit: More info:
Currently you are handling touches events (in I'm guessing a UIViewController) using the code you pasted above, if you moved that code into your Person class you have created you could make the code simpler and achieve the result you desire.
This will have the effect that the Person will decide if it is being touched and where and will move itself accordingly.
In your Person.m file add this code,
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:self.superview];
[self moveToLocation:location];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:self.superview];
[self moveToLocation:location];
}
-(void)moveToLocation:(CGPoint)location{
CGFloat halfOfPersonWidth = self.bounds.size.width /2.0f;
CGFloat halfOfSuperviewWidth = self.superview.bounds.size.width/2.0f;
// Stop Person from going off left screen edge
if ((location.x - halfOfPersonWidth) <= 0.0f){
return;
} // Stop Person from going off right screen edge
else if ((location.x + halfOfPersonWidth) >= self.superview.bounds.size.width){
return;
}
if (self.center.x < halfOfSuperviewWidth) {
// Person is on the left of the screen and should not move to right side
if ((location.x + halfOfPersonWidth) > halfOfSuperviewWidth) {
return;
}
} else{
// Person is on the right of the screen and should not move to left side
if ((location.x - halfOfPersonWidth) < halfOfSuperviewWidth) {
return;
}
}
// If we have made it this far then we can move the Person
// move to touch location on x axis only
[self setCenter:CGPointMake(location.x, self.center.y)];
}
Now in your View Controller class you can delete the original -touchesBegan & -touchesMoved code that you pasted here, Or just comment it out if you want to be cautious
/* put slash asterisks above the code and asterisks slash below the code to comment out */
If you build and run you should be able to move each Person view around as before but if you put a finger on each Person view at the same time you should be able to move them simultaneously.
In my app, once the gamepieces are stacked to a certain height, the screen starts to move down to allow more room for building. The problem is that everytime the screen moves down, the gamepieces shake so much that they topple over.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
SKSpriteNode *gamePiece = [self pickObject];
gamePiece.position = location;
gamePiece.physicsBody.dynamic = NO;
[self addChild:gamePiece];
_currentTouch = touch;
currentGamePiece = gamePiece;
CGPoint touchLocation = [touch locationInNode:self.scene];
if(touchLocation.y > 350)
{
_bg.position = CGPointMake(_bg.position.x, _bg.position.y-2);
}
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
if ([touch isEqual:_currentTouch])
{
currentGamePiece.position = location;
}
CGPoint touchLocation = [touch locationInNode:self.scene];
if(touchLocation.y > 350)
{
_bg.position = CGPointMake(_bg.position.x, _bg.position.y-2);
}
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
for (UITouch *touch in touches)
{
CGPoint location = [touch locationInNode:self];
if ([touch isEqual:_currentTouch])
{
currentGamePiece.position = location;
currentGamePiece.physicsBody.dynamic = YES;
}
}
}
I have a node that acts like an UIButton and a node that performs an action when the screen gets touched.
The problem is that the lion jump action does not get fired.
In addition: both names of the nodes are correct.
My code for the UIEvent:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode: self];
SKNode *node = [self nodeAtPoint:location];
UITouch *lionTouch = [touches anyObject];
CGPoint locationLion = [lionTouch locationInView:[self view]];
SKNode *lionNode = [self nodeAtPoint:locationLion];
if ([lionNode.name isEqualToString:#"veggieLion"]) {
if (!lionIsJumping) {
[self lionJump];
lionIsJumping = YES;
}
}
if ([node.name isEqualToString:#"repalyButton"]) {
// Button action
}
}
Apple documentation for SKNode says the method nodeAtPoint "Returns the deepest descendant that intersects a point."
I've had this trouble in the past. where the method would return the SKSpriteNode that I had used for the background, even if I were definitely tapping the target sprite/node!
Since you are checking for two nodes, I would approach it in the following way:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode: self];
SKNode * lionNode = [self childNodeWithName:#"veggieLion"];
SKNode * replayButton = [self childNodeWithName:#"repalyButton"];
if ([self.lionNode containsPoint:location]) {
if (!lionIsJumping) {
[self lionJump];
lionIsJumping = YES;
}
return;
}
if ([self.replayButton containsPoint:location]) {
// Button action
return;
}
/* test for other targets */
}
I found this code on a tutorial which pretty much sets a left or right side BOOL to YES if the touch started on a specific side of the screen and then checks when the touch moves to see if it changes sides on the screen in order to make the other BOOL yes.
So I am now trying to implement multi-touch but I am not sure how it would work with the following code? Does anyone have any idea how I would go upon it?
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
touchStartPoint = [touch locationInView:self.view];
if (touchStartPoint.x < 160.0) {
touchLeftDown = TRUE;
}
else if (touchStartPoint.x > 160.0) {
touchRightDown = TRUE;
}
}
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint currentTouchPoint = [touch locationInView:self.view];
if (touchStartPoint.x < 160.0 && currentTouchPoint.x < 160.0) {
touchLeftDown = TRUE;
}
else if (touchStartPoint.x > 160.0 && currentTouchPoint.x > 160.0)
{
touchRightDown = TRUE;
}
}
-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
touchLeftDown = touchRightDown = FALSE;
}
Thanks!
Edit1:
These are what the bools are doing in the game loop, pretty much what I am trying to achieve is that if there is a touch on both sides at the same time, the TOUCH_INCREMENT will be 0 since the touches on each side will cancel each other out. How would I achieve that? Anyway this is the code I am talking about:
if (touchLeftDown == TRUE) {
touchStep -= TOUCH_INCREMENT;
}
else if (touchRightDown == TRUE) {
touchStep += TOUCH_INCREMENT;
}
else {
touchStep = SLOWDOWN_FACTOR * touchStep;
}
touchStep = MIN(MAX(touchStep, -MAX_ABS_X_STEP), MAX_ABS_X_STEP);
pos.x += touchStep;
You can probably make this work without the touchStartPoint (i)var. The important thing is to not use -anyObject and instead to inspect each touch. The following code modifications might work for you:
-(void) countTouches:(NSSet *)touches withEvent:(UIEvent *)event{
int leftTouches=0;
int rightTouches=0;
for (UITouch *touch in touches)
{
CGPoint location = [touch locationInView:touch.view];
//just in case some code uses touchStartPoint
touchStartPoint=location;
if (location.x < 160.0) {
leftTouches++;
}
else if (location.x > 160.0) {
rightTouches++;
}
}
//reset touch state
touchLeftDown=FALSE;
//set touch state if touch found
if(leftTouches>0){
touchLeftDown=TRUE;
}
touchRightDown=FALSE;
if(rightTouches>0){
touchRightDown=TRUE;
}
}
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self countTouches:touches withEvent:event];
}
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[self countTouches:touches withEvent:event];
}
-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
touchLeftDown = touchRightDown = FALSE;
}
Here I have created a function that is called by touchesBegan and touchesMoved because they need to implement the same logic. You may see some unintended side effects if touchStartPoint is being used in the code somehow.