I've created an PowerUpTrail.sks (particle thingy)
And I'm trying to attach it to a moving object
func SpawnPowerUp(){
let PowerUp = SKSpriteNode(imageNamed: "PowerUp.png")
let MinValue = self.size.width / 8
let MaxValue = self.size.width - 20
let SpawnPoint = UInt32(MaxValue - MinValue)
PowerUp.position = CGPoint(x: CGFloat(arc4random_uniform(SpawnPoint)), y: self.size.height - 100)
//Physics
PowerUp.physicsBody = SKPhysicsBody(rectangleOfSize: PowerUp.size)
PowerUp.physicsBody?.categoryBitMask = PhysicsCategory.PowerUp //Sätter unikt ID för Enemy, hämtat från PhysicsCategory som vi gjorde där uppe
PowerUp.physicsBody?.contactTestBitMask = PhysicsCategory.PowerUp //Kolla om Enemy nuddar Bullet
PowerUp.physicsBody?.affectedByGravity = false
PowerUp.physicsBody?.dynamic = true
let action = SKAction.moveToY(-128, duration: 5)
let actionDone = SKAction.removeFromParent()
PowerUp.runAction(SKAction.sequence([action, actionDone]))
PowerUp.runAction(SKAction.rotateByAngle(5, duration: 5))
/* this does not work!
let PowerUpTrail = SKEmitterNode(fileNamed: "PowerUpTrail.sks")
PowerUpTrail!.targetNode = self
PowerUpTrail!.position = PowerUp.position
self.addChild(PowerUpTrail!)
*/
self.addChild(PowerUp)
}
The particlethingy spawns but it doesnt follow the object. How do I solve this?
The simplest way to make one node's position track another is to use the node hierarchy: make the particle emitter a child node of the powerup sprite.
let powerUp = SKSpriteNode(imageNamed: "PowerUp.png")
// ...
let powerUpTrail = SKEmitterNode(fileNamed: "PowerUpTrail.sks")!
powerUpTrail.targetNode = self
powerUp.addChild(powerUpTrail)
(Also note some Swift style tips: initial-cap is conventionally for type names only; use initial-lowercase for variable names. And unwrap your optional from SKEmitterNode(fileNamed:) once when you create it instead of every time you use it thereafter.)
Related
Relatively new to swift so apologies if I'm being stupid.
I'm using Xcode 13.3 beta 3
I have some code which spawns in a sprite named Monster and moves it from the right of the screen to the left at random speeds and at a random y location.
When it moves off screen it removes from parent and transitions to a loose screen.
what I want it to do when it leaves the screen is for it to add to a variable I've defined at the top of the class:
var pass = 0
here's the code for the monsters:
func addMonster() {
let monster = SKSpriteNode(imageNamed: "monster")
monster.physicsBody = SKPhysicsBody(rectangleOf: monster.size)
monster.physicsBody?.isDynamic = true
monster.physicsBody?.categoryBitMask = PhysicsCatagory.monster
monster.physicsBody?.contactTestBitMask = PhysicsCatagory.projectile
monster.physicsBody?.collisionBitMask = PhysicsCatagory.none
let actualY = random(min: monster.size.height/2, max: size.height - monster.size.height/2)
monster.position = CGPoint(x: size.width + monster.size.width/2, y: actualY)
addChild(monster)
let actualDuration = random(min: CGFloat(1.65), max: CGFloat(5.5))
let actionMove = SKAction.move(to: CGPoint(x: -monster.size.width/2, y: actualY), duration: TimeInterval(actualDuration))
let actionMoveDone = SKAction.removeFromParent()
let loseAction = SKAction.run() { [weak self] in
guard let `self` = self else {return}
let end = SKTransition.crossFade(withDuration: 0.5)
let gameOverScene = GameOverScene(size: self.size, won: false)
self.view?.presentScene(gameOverScene, transition: end)
}
monster.run(SKAction.sequence([actionMove,loseAction, actionMoveDone]))
}
what I want is the loose action to just add +1 to the pass variable then have an if statement for when pass is greater than 3 to run the code that goes to the loose screen.
Hope that all makes sense
any help would be much appreciated!
I'm trying to incorporate GameplayKit into a SpriteKit project.
Specifically, I'm trying to use GameplayKit's seek-and-avoid behavior wherein an "enemy" character chases a moving "player" character while avoiding obstacles.
I have managed to make the enemy seek the player, but the enemy's movement is weird/undesirable: The enemy's speed should adjust so that it comes to a halt at the desired end point, however its speed does not change, which makes it overshoot its target considerably.
Think of a fast-moving train: Once it gets up to speed, it's hard to stop; it wants to just keep on moving. It's the same phenomenon with the enemy character sprite. It moves like a heavy, lumbering object that can't stop quickly enough.
Here's what I'm doing (please assume that properties such as node are defined):
Configure the player character's GameplayKit stuff:
let entity1 = GKEntity()
heroAgent = GKAgent2D()
heroComponent = GKSKNodeComponent(node: node)
heroAgent.delegate = self
node.entity = entity1
heroEntity = entity1
if let comp = heroComponent {
entity1.addComponent(comp)
entity1.addComponent(heroAgent)
}
Configure the enemy character's GameplayKit stuff:
let entity = GKEntity()
let agent = GKAgent2D()
let avoid = GKGoal(toAvoid: obstaclesForThisLevel!, maxPredictionTime: 1000.0)
let pursue = GKGoal(toInterceptAgent: heroAgent, maxPredictionTime: 0.0)
nodeComponent = GKSKNodeComponent(node: node)
agent.maxSpeed = 100.0
agent.maxAcceleration = 50.0
agent.position = vector_float2(x: Float(levelData.enemies[i].x), y: Float(levelData.enemies[i].y))
agent.radius = Float(node.size.width*0.5)
agent.behavior = GKBehavior(goals: [avoid, pursue], andWeights: [100.0, 100.0])
agent.delegate = self
node.entity = entity
enemyAgent = agent
if let comp = nodeComponent {
entity.addComponent(comp)
entity.addComponent(agent)
}
nodeEntity = entity
In the SKScene's update(_:) method, set the player agent's position and call the enemy character's update(deltaTime:) method:
//Coordinate the hero agent's position with the hero's actual position. Without this, the values in agentDidUpdate() are nan.
heroAgent.position = vector_float2(x: Float(mainCharacter?.position.x ?? 0.0), y: Float(mainCharacter?.position.y ?? 0.0))
nodeComponent?.node.entity?.update(deltaTime: currentTime-lastFrameTime)
Set the enemy character's position in agentDidUpdate(_:):
func agentDidUpdate(_ agent: GKAgent) {
if let a = agent as? GKAgent2D {
nodeComponent?.node.position = CGPoint(x: CGFloat(a.position.x), y: CGFloat(a.position.y))
}
}
Question: Why is the enemy character overshooting its designated target point instead of managing its speed correctly, and how do I fix the issue?
Thank you!
I decided to only use GameplayKit for the purpose of generating a path around the obstacles (findPath(from:to:) is very helpful), and to simply move the enemy character along that path via SKAction.follow(_:asOffset:orientToPath:speed:) when I want to send the enemy back to its starting position.
So, to accomplish the "seeking" behavior, I'm just doing the following from inside my SKScene's update(_:) method:
let dx = mainCharacter.position.x - enemy.position.x
let dy = mainCharacter.position.y - enemy.position.y
let angle = atan2(dy, dx)
let vx = cos(angle) * enemySpeed
let vy = sin(angle) * enemySpeed
enemy.physicsBody?.velocity.dx = vx
enemy.physicsBody?.velocity.dy = vy
This seeking behavior does not consider obstacles, so the enemy can get kind of stuck behind an obstacle that's located between the enemy and the player. So, it's not ideal, but I think it's going to work for me because of other factors in my game.
To generate a path that does consider obstacles, for use with SKAction, I'm doing the following:
let graph = GKObstacleGraph(obstacles: obstaclesForThisLevel, bufferRadius: 0.0)
let startingPoint = GKGraphNode2D(point: vector_float2(x: Float(enemy.position.x), y: Float(enemy.position.y)))
let endPoint = GKGraphNode2D(point: vector_float2(x: Float(enemy.enemyOriginalPosition.x), y: Float(enemy.enemyOriginalPosition.y)))
graph.connectUsingObstacles(node: startingPoint)
graph.connectUsingObstacles(node: endPoint)
let path = (graph as GKGraph).findPath(from: startingPoint, to: endPoint)
if path.count >= 2 {
let realPath = GKPath(graphNodes: path, radius: 100.0)
let myPath: UIBezierPath = UIBezierPath()
for j in 1..<realPath.numPoints {
let previousPoint = realPath.float2(at: j-1)
let nextPoint = realPath.float2(at: j)
if j == 1 {
myPath.move(to: CGPoint(x: CGFloat(previousPoint.x), y: CGFloat(previousPoint.y)))
}
myPath.addLine(to: CGPoint(x: CGFloat(nextPoint.x), y: CGFloat(nextPoint.y)))
}
}
And finally:
let followPath = SKAction.follow(myPath.cgPath, asOffset: false, orientToPath: false, speed: enemySpeed)
enemy.run(followPath)
I am making a platforming game in Swift with SpriteKit involving a main character that jumps around. However, once the level loads the player immediately falls through the ground. You can see it in action here.
I am using SKTilemapNode to create the ground, and looping through the tiles when a level loads to create an SKPhysicsBody on a child node of the tile map. This is very similar to what is demoed in the "What's new in SpriteKit" video at WWDC 2016:
So, here we've got a little platform that I built. A little guy that can run around. And you can see that I got the parallax scrolling going on in the background. And you'll note that I'm colliding with the tiles here. And I achieve this by leveraging custom user data that we can put on each of our tiles. Here, I'll show you in our tile set. Select one of the variants here.
And you can see that we have some user data over here. And I just have a value called edgeTile which is a Boolean, and I set to 1.
So, in code, I'm going through the tile map in our platform demo here, and I'm looking for all of these edge tiles.
And whenever I find one, I create some physics data to allow the player to collide with it.
My function to create a physics body based off of an SKTilemapNode is as follows:
extension SKTileMapNode {
//In order for this to work, edge tile definitions must have the "edge" property in user data
func createPhysicsBody() -> SKPhysicsBody {
var physicsBodies = [SKPhysicsBody]()
for row in 0 ..< self.numberOfRows {
for column in 0 ..< self.numberOfColumns {
if self.tileDefinition(atColumn: column, row: row)?.userData?["edge"] != nil {
physicsBodies.append(SKPhysicsBody(rectangleOf: self.tileSize, center: self.centerOfTile(atColumn: column, row: row)))
}
}
}
let body = SKPhysicsBody(bodies: physicsBodies)
body.affectedByGravity = false
body.isDynamic = false
body.allowsRotation = false
body.pinned = true
body.restitution = 0
body.collisionBitMask = 0b1111
body.categoryBitMask = 0b1111
body.contactTestBitMask = 0b1000
return body
}
func initializePhysicsBody() {
let node = SKNode()
node.name = "Tilemap"
node.physicsBody = createPhysicsBody()
addChild(node)
}
}
So, in my scene setup all I have to do is call tileMap.initializePhysicsBody() to do everything that I need.
The SKPhysicsBody for my player is as follows:
let rect = CGSize(width: 16 * xScale, height: 24 * yScale)
let physics = SKPhysicsBody(rectangleOf: rect)
physics.isDynamic = true
physics.allowsRotation = false
physics.pinned = false
physics.affectedByGravity = true
physics.friction = 0
physics.restitution = 0
physics.linearDamping = 0
physics.angularDamping = 0
physics.density = 100
physics.categoryBitMask = 0b0001
physics.collisionBitMask = 0b0001
physics.contactTestBitMask = 0b0011
physics.usesPreciseCollisionDetection = true
physicsBody = physics
I'm not sure what the problem is here, but if I set the SKTilemapNode's physics body to be dynamic, it works. This is how I had the game working up until this point, however, this creates a lot of jitter in the ground because it's moving as a result of the player hitting it. So, thanks for reading this far at least, and any suggestions would be appreciated.
EDIT.
I think the ERROR Here is not using UInt 32
body.categoryBitMask: UInt32 = 2
body.collisionBitMask: UInt32 = 1
body.contactTestBitMask: UInt32 = 1
And Player
physics.categoryBitMask: UInt32 = 1
physics.collisionBitMask: UInt32 = 2
physics.contactTestBitMask: UInt32 = 2
This should definitely Work
Also try this way for the tileMapNode (given below) rather than creating the extension. This was given in a apple developer forum by dontangg
self.tileMap = self.childNode(withName: "Tile Map") as? SKTileMapNode
guard let tileMap = self.tileMap else { fatalError("Missing tile map for the level") }
let tileSize = tileMap.tileSize
let halfWidth = CGFloat(tileMap.numberOfColumns) / 2.0 * tileSize.width
let halfHeight = CGFloat(tileMap.numberOfRows) / 2.0 * tileSize.height
for col in 0..<tileMap.numberOfColumns {
for row in 0..<tileMap.numberOfRows {
let tileDefinition = tileMap.tileDefinition(atColumn: col, row: row)
let isEdgeTile = tileDefinition?.userData?["edgeTile"] as? Bool
if (isEdgeTile ?? false) {
let x = CGFloat(col) * tileSize.width - halfWidth
let y = CGFloat(row) * tileSize.height - halfHeight
let rect = CGRect(x: 0, y: 0, width: tileSize.width, height: tileSize.height)
let tileNode = SKShapeNode(rect: rect)
tileNode.position = CGPoint(x: x, y: y)
tileNode.physicsBody = SKPhysicsBody.init(rectangleOf: tileSize, center: CGPoint(x: tileSize.width / 2.0, y: tileSize.height / 2.0))
tileNode.physicsBody?.isDynamic = false
tileNode.physicsBody?.collisionBitMask = playerCollisionMask | wallCollisionMask
tileNode.physicsBody?.categoryBitMask = wallCollisionMask
tileMap.addChild(tileNode)
}
}
}
I need help with coming up with ways to make to make the newly spawned enemy ships all shoot bullets. The problem is that I don't actually know how I am going to do that. I tried using a variable timer but, it only shoots one straight line and I can't really control the location it spawns at.
Game Picture: There are more enemies spawning
What I Tried:
var EnemyTimer = Foundation.Timer.scheduledTimer(timeInterval: 0.3, target: self, selector: #selector(GameScene.spawnBullet3), userInfo: nil, repeats: true)
Also Tried:
}
func spawnBullet3(){
let Bullet = SKSpriteNode(imageNamed: "eBullet.png")
Bullet.setScale(1)
Bullet.zPosition = -1
Bullet.position = CGPoint(x: enemy.position.x, y: enemy.position.y)
let action = SKAction.moveTo(y: self.size.height + 100, duration: 0.5)
let actionDone = SKAction.removeFromParent()
Bullet.run(SKAction.sequence([action, actionDone]))
Bullet.physicsBody = SKPhysicsBody(rectangleOf: Bullet.size)
Bullet.physicsBody?.categoryBitMask = PhysicsCategories.Bullet
Bullet.physicsBody!.collisionBitMask = PhysicsCategories.None
Bullet.physicsBody?.contactTestBitMask = PhysicsCategories.Enemy
Bullet.physicsBody?.affectedByGravity = true
Bullet.physicsBody?.isDynamic = false
self.addChild(Bullet)
}
Well, for starters, (as LearnCocos2D explained wonderfully here) never user a Timer, performSelector:afterDelay:, or Grand Central Dispatch (GCD, ie any dispatch_... method.
Before anything, you must set up a couple of things:
First, replace add an argument to your spawnBullet3 function (If you use a custom class replace enemy:SKSpriteNode with enemy:yourCustomClass).
func spawnBullet3(enemy:SKSpriteNode){
let Bullet = SKSpriteNode(imageNamed: "eBullet.png")
Bullet.setScale(1)
Bullet.zPosition = -1
Bullet.position = CGPoint(x: enemy.position.x, y: enemy.position.y)
let action = SKAction.moveTo(y: self.size.height + 100, duration: 0.5)
let actionDone = SKAction.removeFromParent()
Bullet.run(SKAction.sequence([action, actionDone]))
Bullet.physicsBody = SKPhysicsBody(rectangleOf: Bullet.size)
Bullet.physicsBody?.categoryBitMask = PhysicsCategories.Bullet
Bullet.physicsBody!.collisionBitMask = PhysicsCategories.None
Bullet.physicsBody?.contactTestBitMask = PhysicsCategories.Enemy
Bullet.physicsBody?.affectedByGravity = true
Bullet.physicsBody?.isDynamic = false
self.addChild(Bullet)
}
Second, create a public enemies array (Again, if you use a custom class replace SKSpriteNode with your custom class name).
var enemies:[SKSpriteNode] = []
Next, every time you create a new enemy, append the enemy to the array
enemies.append(newEnemy)
Now you have two options:
Option 1: Use a SKAction
Use a SKAction timer to automate every enemy shooting every couple of seconds (Replace SKAction.waitForDuration(2.5) with SKAction.waitForDuration(yourAmountOfSeconds)).
for e in enemies
{
var wait = SKAction.waitForDuration(2.5)
var run = SKAction.runBlock {
spawnBullet3(e)
}
e.runAction(SKAction.repeatActionForever(SKAction.sequence([wait, run])))
}
Option 2: Use the update function
Use the currentTime from the update function (Replace currentTime.truncatingRemainder(dividingBy: 3) with currentTime.truncatingRemainder(dividingBy: yourAmountOfSeconds)).
override func update(_ currentTime: TimeInterval) {
[...]
if currentTime.truncatingRemainder(dividingBy: 3) == 0
{
for e in enemies
{
spawnBullet3(e)
}
}
}
I currently have two SKSpriteNodes that I have added SKPhysicsBodies to. When they have no SKJoint attached, they collide as expected. As soon as I add the SKPhysicsJoint, they just pass right through each other. Any joint I add functions properly, but the SKPhysicsJointLimit only limits the extent to which the nodes can travel apart from each other, not how close they can get. How can I fix this?
Here is code I am using for the joint:
let joint = SKPhysicsJointLimit.joint(withBodyA: object1.physicsBody!, bodyB: object2.physicsBody!, anchorA: CGPoint(x: object1.position.x + iconController.position.x, y: object1.position.y + iconController.position.y), anchorB: CGPoint(x: object2.position.x + iconController.position.x, y: object2.position.y + iconController.position.y))
joint.maxLength = screen.height * 0.4
physicsWorld.add(joint)
PhysicsBody of both nodes:
self.physicsBody = SKPhysicsBody(circleOfRadius: self.size.width / 2)
self.physicsBody?.allowsRotation = false
self.physicsBody?.friction = 0
self.physicsBody?.mass = 0.1
I have tested it with different values for the above modifications of the SKPhysicsBody and it performs the same.
An SKPhysicsJoint object connects two physics bodies so that they are simulated together by the physics world.
You can use also SKPhysicJointPin:
A pin joint allows the two bodies to independently rotate around the
anchor point as if pinned together.
If your objects work well before the SKPhysicsJoint addition with the physic engine so they fired the didBeginContact as you wish and as you have setted, I think your problem is simply a wrong anchor. Try to add:
let skView = self.view as! SKView
skView.showsPhysics = true
to your scene initialization code: you will see an outline of the physic bodies and maybe you'll see the issue immediatly.
To help you I'll try to make an example of elements configured to collide each other:
enum CollisionTypes: UInt32 {
case Boundaries = 1
case Element = 2
}
class GameScene: SKScene,SKPhysicsContactDelegate {
private var elements = [SKNode]()
override func didMoveToView(view: SKView) {
physicsWorld.gravity = CGVector(dx: 0, dy: 0)
self.physicsWorld.contactDelegate = self
let boundariesFrame = CGRectMake(20, 20, 200, 400)
let boundaries = SKShapeNode.init(rect: boundariesFrame)
boundaries.position = CGPointMake(350,150)
let boundariesBody = SKPhysicsBody.init(edgeLoopFromRect: boundariesFrame)
boundariesBody.dynamic = false
boundariesBody.categoryBitMask = CollisionTypes.Boundaries.rawValue
boundariesBody.contactTestBitMask = CollisionTypes.Element.rawValue
boundaries.physicsBody = boundariesBody
addChild(boundaries)
for index in 0..<5 {
let element = SKShapeNode(circleOfRadius: 10)
let body = SKPhysicsBody(circleOfRadius: 10)
body.linearDamping = 0
// body.mass = 0
body.dynamic = true
body.categoryBitMask = CollisionTypes.Element.rawValue
body.contactTestBitMask = CollisionTypes.Boundaries.rawValue | CollisionTypes.Element.rawValue
body.collisionBitMask = CollisionTypes.Boundaries.rawValue | CollisionTypes.Element.rawValue
element.physicsBody = body
element.position = CGPoint(x: size.width / 2, y: size.height / 2 - 30 * CGFloat(index))
elements.append(element)
addChild(element)
}
}
}
Hope it can help you to find your issue.