First of you should know, the area where the player is allowed to play in is a square, that is 320x320 which is centered in the screen. That 320x320 square is a 5x5 grid [64x64 each tile], and the player is always centered in the tiles of the grid.
When the player moves, no matter the direction, its always a 64 pixel move.[1/5 of the grid's size].
func move(node: SKSpriteNode, direction: String) {
//'node' determines what spritekitnode to move.
//'direction' classifies which statement to run.
if node.name == "player" {
if direction == "left" {
if node.position.x - 64 >= map.position.x - 160 {
//Checks if the Node can move without
//moving off the grid [map, the 5x5 grid].
node.runAction(moveLeft)
//Moves the Node
}
}
if direction == "right" {
if node.position.x + 64 <= map.position.x + 160 {
node.runAction(moveRight)
}
}
if direction == "up" {
if node.position.y + 64 <= map.position.y + 160 {
node.runAction(moveUp)
}
}
if direction == "down" {
if node.position.y - 64 >= map.position.y - 160 {
node.runAction(moveDown)
}
}
if direction == nil {
print("[move() function] : No direction specified!")
}
}
}
My problem, is when the player spams or quickly presses the button to move multiple times
consecutively, the player ends up being able to move off the grid.
Example, when the player is on the bottom tile of the 5x5 grid, and moves down 2 times fast
enough, the player goes off the grid.
This is because RunAction does not remove the previous action, so when they spam, they queue up multiple actions to run concurrently. You need to either have your move functions not allow the bounds to be broken, or call removeAllActions before you run a new action
Related
I am building a spriteKit game in Xcode 10.2.1 with swift 5, where the player has to reach the end of a large background (avoiding enemies, obstacles etc.) in order to complete the level. The camera is tied to the player spriteNode's position.x and the player can move the spriteNode in any direction with swipes.
I am using a cobbled together UIPanGestureRecognizer to enable this movement, and this seems to work reasonably well.
#objc func handlePan(recognizer: UIPanGestureRecognizer) {
let transformerX = 1024/self.view!.frame.size.width
let transformerY = 768/self.view!.frame.size.height
if recognizer.state == .began {
lastSwipeBeginningPoint = recognizer.location(in: self.view)
} else if (recognizer.state == .ended) {
if scrolling { // this is controlls whether player can be moved - scrolling is set to true once introductory elements have run
if playerDamage < 4 { //player cannot be controlled and falls off screen if it has 4 damage
let velocity = recognizer.velocity(in: self.view)
player.physicsBody?.applyImpulse(CGVector(dx: velocity.x * transformerX / 5.4, dy: velocity.y * transformerY * -1 / 5.4)) //-1 inverts y direction so it moves as expected
}
}
}
}
This does allow movement around the level, but not in as precise and controlled a way as I would like. The player moves off at the set speed but any further swipes (to change direction/avoid obstacles) seem to multiply the speed and generally make it difficult to control accurately.
Is there a way of choking off the speed, so that it starts off at the set speed but then starts to lose momentum, or is there a better way altogether of controlling multi-direction movement?
I have this Sprite Kit game that passes some balls across the screen and the user has to tap them. If the user misses a ball and it goes off the screen then they lose.
The game works perfect when testing it on my iPhone but when I run it on my iPad it doesn't work as it's supposed to. It also doesn't work on iPhone X simulator. When the game first starts up it goes to the Start Scene, but when the user clicks "Start Game" it goes to the Game Scene and then automatically cuts to the game over scene without spawning any balls. After I hit "Play again" it will spawn the balls as it should but the measurements of detecting if the balls are out of bounds are not correct (which they are when testing on my iPhone 8 Plus).
Here are the areas where I think the problem may be coming. This is where the widths for things come from.
EnumerateChildNodes function to remove the balls once of the scene:
func searchChildNodes() {
self.enumerateChildNodes(withName: "BALL") { (node: SKNode, nil) in
if node.position.x < -25 || node.position.x > self.size.width + 25 {
print("balls Out")
node.removeFromParent()
let transition = SKTransition.fade(withDuration: 1)
self.gameScene = SKScene(fileNamed: "GameOverScene")
self.gameScene.scaleMode = .aspectFit
self.view?.presentScene(self.gameScene, transition: transition)
}
}
}
Specifically in the if statement:
if node.position.x < -25 || node.position.x > self.size.width + 25
If remove the part after the || operator then the game lets the balls do what they are supposed, only without detecting if the are out of bounds on the right side of the screen.
Update
Here is the code that sets the position of the balls when they are spawned:
if ballDirection == "right" {
player?.position.x = 0
moveRight()
}
else {
player?.position.x = (self.scene!.frame.size.height)
moveLeft()
}
Like in a Mario game where if you jump and land on top of a monster, the monster gets knocked out.
I'm using CGRectIntersectsRect between the two objects (player and monster); however, the monster will get knocked out from any direction.
How do I intersect two objects at specific points of the objects?
I actually created separate blank objects in each direction for this to work. Is there a more efficient solution?
Instead of CGRectIntersectsRect you can use CGRectIntersection to get a new CGRect of the area of intersection. If the player hit from the side, then this rectangle will be taller than it is wide. If the player hit from the top the rectangle will be wider than it is tall, but you will need to check for top vs. bottom maybe. In that case, you can compare the Y values of the enemy rectangle with the intersection. If the player hit from the top, the Y values of the intersection and the enemy rectangle will be equal. Otherwise, the player hit from the bottom.
typedef NS_ENUM(NSUInteger, IntersectFrom) {
IntersectFromNotIntersect,
IntersectFromTop,
IntersectFromBottom,
IntersectFromLeft,
IntersectFromRight
};
IntersectFrom CGRectGetIntersectFrom(CGRect rectPlayer, CGRect rectMonster) {
CGRect rectIntersect = CGRectIntersection(rectPlayer, rectMonster);
if(!CGRectIsNull(rectIntersect)) {
if(CGRectGetWidth(rectIntersect) < CGRectGetHeight(rectIntersect)) {
// LEFT or RIGHT
if(CGRectGetMinX(rectIntersect) == CGRectGetMinX(rectMonster)) {
return IntersectFromLeft;
}
else {
return IntersectFromRight;
}
}
else {
// TOP or BOTTOM
if(CGRectGetMinY(rectIntersect) == CGRectGetMinY(rectMonster)) {
return IntersectFromBottom;
}
else {
return IntersectFromTop;
}
}
}
else {
return IntersectFromNotIntersect;
}
}
I have this iOS game where the frog jumps from lily pad to lily pad. The game is in the vertical orientation, and the perspective I'm trying to achieve is an aerial view, where the user looks at the frog and scenery from the top. The frog jumps from lily pad to lily pad and the code is the following:
Bounce Method:
-(void)bounce {
[self jumpSound];
if (frog.center.y > 450) {
upMovement = 2;
}
else if (frog.center.y > 350) {
upMovement = 1.5;
}
else if (frog.center.y > 250) {
upMovement = 0.5;
}
}
and the lilypad movement:
-(void)lilyPadMovement {
if (CGRectIntersectsRect(frog.frame, lily.frame) && (upMovement <=-1)){
[self bounce];
[self lilyPadFall];
if (lilyPadUsed == NO) {
addedScore = 1;
lilyPadUsed = YES;
}
}
}
Essentially what I'm trying to fix is the frogs bouncing movement. When the frog lands in the middle of a lily pad and bounces it doesn't look bad, but at times the frog will simply touch the sides of the lil pad and the bounce method will be called because the rectangles intersected. I tried CGRectContainsRect but it made the game to hard, because it delayed the speed of the game. So I'm not sure how to fix it. Any suggestions?
You really want to know if there's a sufficient overlap to enable the frog to push off, rather than fall in the water. So maybe the distance between the centers has to be less than X, where you tune X based on what looks good.
For example ( using the Pythagorean formula) :
//having defined this function:
inline float square (float a) { return a*a; }
//change intersect test to:
if (((square(frog.center.x-lily.center.x) + square(frog.center.y-lily.center.y)) < 10000) &&
(upMovement <= -1)) { ...
Im using a technique to control a sprite by rotating left/right and then accelerating forward. I have 2 questions regarding it. (The code it pasted together from different classes due to polymorphism. If it doesn't make sense, let me know. The movement works well and the off screen detection as well.)
When player moves off screen i call the Bounce method. I want the player not to be able to move off screen but to change direction and go back. This works on top and bottom but left and right edge very seldom. Mostly it does a wierd bounce and leaves the screen.
I would like to modify the accelerate algorithm so that i can set a max speed AND a acceleration speed. Atm the TangentalVelocity does both.
float TangentalVelocity = 8f;
//Called when up arrow is down
private void Accelerate()
{
Velocity.X = (float)Math.Cos(Rotation) * TangentalVelocity;
Velocity.Y = (float)Math.Sin(Rotation) * TangentalVelocity;
}
//Called once per update
private void Deccelerate()
{
Velocity.X = Velocity.X -= Friction * Velocity.X;
Velocity.Y = Velocity.Y -= Friction * Velocity.Y;
}
// Called when player hits screen edge
private void Bounce()
{
Rotation = Rotation * -1;
Velocity = Velocity * -1;
SoundManager.Vulture.Play();
}
//screen edge detection
public void CheckForOutOfScreen()
{
//Check if ABOVE screen
if (Position.Y - Origin.Y / 2 < GameEngine.Viewport.Y) { OnExitScreen(); }
else
//Check if BELOW screen
if (Position.Y + Origin.Y / 2 > GameEngine.Viewport.Height) { OnExitScreen(); }
else
//Check if RIGHT of screen
if (this.Position.X + Origin.X / 2 > GameEngine.Viewport.Width) { OnExitScreen(); }
else
//Check if LEFT of screen
if (this.Position.X - Origin.X / 2 < GameEngine.Viewport.X) { OnExitScreen(); }
else
{
if (OnScreen == false)
OnScreen = true;
}
}
virtual public void OnExitScreen()
{
OnScreen = false;
Bounce();
}
Let's see if I understood correctly. First, you rotate your sprite. After that, you accelerate it forward. In that case:
// Called when player hits screen edge
private void Bounce()
{
Rotation = Rotation * -1;
Velocity = Velocity * -1; //I THINK THIS IS THE PROBLEM
SoundManager.Vulture.Play();
}
Let's suposse your sprite has no rotation when it looks up. In that case, if it's looking right it has rotated 90º, and its speed is v = (x, 0), with x > 0. When it goes out of the screen, its rotation becomes -90º and the speed v = (-x, 0). BUT you're pressing the up key and Accelerate method is called so immediately the speed becomes v = (x, 0) again. The sprite goes out of the screen again, changes its velocity to v = (-x, 0), etc. That produces the weird bounce.
I would try doing this:
private void Bounce()
{
Rotation = Rotation * -1;
SoundManager.Vulture.Play();
}
and check if it works also up and bottom. I think it will work. If not, use two different Bounce methods, one for top/bottom and another one for left/right.
Your second question... It's a bit difficult. In Physics, things reach a max speed because air friction force (or another force) is speed-dependent. So if you increase your speed, the force also increases... at the end, that force will balance the other and the speed will be constant. I think the best way to simulate a terminal speed is using this concept. If you want to read more about terminal velocity, take a look on Wikipedia: http://en.wikipedia.org/wiki/Terminal_velocity
private void Accelerate()
{
Acceleration.X = Math.abs(MotorForce - airFriction.X);
Acceleration.Y = Math.abs(MotorForce - airFriction.Y);
if (Acceleration.X < 0)
{
Acceleration.X = 0;
}
if (Acceleration.Y < 0)
{
Acceleration.Y = 0;
}
Velocity.X += (float)Math.Cos(Rotation) * Acceleration.X
Velocity.Y += (float)Math.Sin(Rotation) * Acceleration.Y
airFriction.X = Math.abs(airFrictionConstant * Velocity.X);
airFriction.Y = Math.abs(airFrictionConstant * Velocity.Y);
}
First, we calculate the accelartion using a "MotorForce" and the air friction. The MotorForce is the force we make to move our sprite. The air friction always tries to "eliminate" the movement, so is always postive. We finally take absolute values because the rotation give us the direction of the vector. If the acceleration is lower than 0, that means that the air friction is greater than our MotorForce. It's a friction, so it can't do that: if acceleration < 0, we make it 0 -the air force reached our motor force and the speed becomes constant.
After that, the velocity will increase using the acceleration. Finally, we update the air friction value.
One thing more: you may update also the value of airFriction in the Deccelarate method, even if you don't consider it in that method.
If you have any problem with this, or you don't understand something (sometimes my English is not very good ^^"), say it =)