Hey so I am making this project in which the player has to jump platforms all the way to the top. Some monsters spawn randomly throughout the game. So the idea is to lose the game when you hit them from below, but can go on if you jump on them. I already did the part in which the player jumps on it and you destroy the monster but I am still stuck on that part to lose the game when you hit it from below. Any ideas on how I can manage to do this? For this project I followed Ray Wenderlich's tutorial on How To Make a Game Like Mega Jump.
So on my GameScene, I have the didBeginContact method:
func didBeginContact(contact: SKPhysicsContact) {
var updateHUD = false
let whichNode = (contact.bodyA.node != player) ? contact.bodyA.node : contact.bodyB.node
let other = whichNode as GameObjectNode
updateHUD = other.collisionWithPlayer(player)
if updateHUD {
lblStars.text = String(format: "X %d", GameState.sharedInstance.stars)
lblScore.text = String(format: "%d", GameState.sharedInstance.score)
}
}
Which then calls the method from the GameObjectNode Scene.
class MonsterNode: GameObjectNode {
var monsterType: MonsterType!
override func collisionWithPlayer(player: SKNode) -> Bool {
if player.physicsBody?.velocity.dy < 0 {
player.physicsBody?.velocity = CGVector(dx: player.physicsBody!.velocity.dx, dy: 450.0)
if monsterType == .Normal {
self.removeFromParent()
}
}
When the player jumps on top of the monster, the monster is removed from the parent. I was trying to set that if the player's velocity is greater than 0 when colliding with the monster, then the player is removed from parent. Then when I go back to my GameScene, I could declare something in my update method so that when the player is removed from the parent call the endGame() method.
override func update(currentTime: NSTimeInterval) {
if gameOver {
return
}
if Int(player.position.y) > endLevelY {
endGame()
}
if Int(player.position.y) < maxPlayerY - 500 {
endGame()
}
}
Of course I wasn't able to make that work, and I still can't. So if anyone could help me out on how I can manage to do this, or probably some tutorial which could guide me into doing this I would really appreciate it! Thank you in advance.
First you use the didBeginContact method to establish if a contact between player and monster has been made. Next you compare the y positions of the player and monster. If the monster's y position is greater than than the player's... BANG, player dies.
The code sample assumes you have multiple monsters, each with a unique name, and have them all stored in an array.
- (void)didBeginContact:(SKPhysicsContact *)contact {
uint32_t collision = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask);
if (collision == (CategoryMonster | CategoryPlayer)) {
for(SKSpriteNode *object in monsterArray) {
if(([object.name isEqualToString:contact.bodyB.node.name]) || ([object.name isEqualToString:contact.bodyA.node.name])) {
if(object.position.y > player.position.y) {
// moster is above player
}
}
}
}
Couple of notes... If you have more than one monster active at any one time, you will need to have a unique name for each one. Reason being that you will need to know which monster contacted the player and that can only happen if you can differentiate between monsters. Hence the unique name for each one.
The if check for the y position is a simple one and only needs +1 y to be fulfilled. If it is possible for your player to make side contact with a monster and not die, you can change the if condition to be something like if(object.position.y > player.position.y+50) to make sure that the contact was actually from the bottom.
(I am not all too proficient in Swift yet so code sample is in Obj-C.)
Related
Here is what I'm trying to do: In games, when approaching an NPC, players will be given an indicator to interact with the NPC. The indicator shows up when the player is within a certain distance of the npc. It also goes away when the player moves away from the NPC.
Here is what I tried: I had thought that it would be as easy as using the physics world methods of didBegin/didEnd contact and a transparent cylinder around the NPC as a contact trigger. This unfortunately didn't work because didBegin/didEnd methods are called every frame and not when contact is made (this is how I thought it worked).
I also tried to use PHYKit from GitHub but It didn't seem compatible to what I was trying to do.
I've thought about giving the NPC a Magnetic field and checking if player is within the scope of that field but it doesn't look like there is way to check for that (maybe I missed something).
I thought I could also use hitTestWithSegment but didn't understand how I can apply it to what I'm trying to do.
There also doesn't seem to be anything online to help with this (I've checked for the last three days so if there is anything I'm willing to see what it's about).
The Question: How can I check if a node is within a certain distance of another node and when it left that area?
I still think your physics answer works. Yeah it worked differently than I thought it did too, but you have to play around with it a bit and check it both ways:
func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact)
{
if(data.gameState == .run)
{
guard let nodeNameA = contact.nodeA.name else { return }
guard let nodeNameB = contact.nodeB.name else { return }
if(nodeNameA.prefix(5) == "Explo" && nodeNameB.prefix(5) == "Missi")
{
gameControl.enemyMissileHit(vIndex: Int(nodeNameB.suffix(4))!)
}
if(nodeNameA.prefix(5) == "Missi" && nodeNameB.prefix(5) == "Explo")
{
gameControl.enemyMissileHit(vIndex: Int(nodeNameA.suffix(4))!)
}
if(nodeNameA.prefix(5) == "Explo" && nodeNameB.prefix(5) == "Hive ")
{
gameControl.enemyHiveHit(vIndex: Int(nodeNameB.suffix(4))!)
}
if(nodeNameA.prefix(5) == "Hive " && nodeNameB.prefix(5) == "Explo")
{
gameControl.enemyHiveHit(vIndex: Int(nodeNameA.suffix(4))!)
}
}
}
Or - on a timer, check the node distance periodically:
func distance3D(vector1: SCNVector3, vector2: SCNVector3) -> Float
{
let x: Float = (vector1.x - vector2.x) * (vector1.x - vector2.x)
let y: Float = (vector1.y - vector2.y) * (vector1.y - vector2.y)
let z: Float = (vector1.z - vector2.z) * (vector1.z - vector2.z)
let temp = x + y + z
return Float(sqrtf(Float(temp)))
}
Updated Answer:
Here is a better answer and overall more effective than my old answer. It takes advantage of SCNActions with physicsWorld methods and when they are called.
When the first contact between nodes happens, the physicsWorld methods are called in this order:
beginsContact
updateContact
endContact
And as the node proceeds to pass through the other node it goes in this order every other moment:
beginsContact
endContact
updateContact
Once the node is fully inside the other node and moves about inside the node, only this method is called.
updateContact
Note: In my example below, when referring to nodes, I'm talking about the player node and the invisible chatRadius as the other node for an NPC.
Since the beginsContact method is only called when both nodes have their edges touching, I have it set a boolean value equal true.
Note: The bool value is what I use to show interaction indicator for the player and is interchangeable for whatever you what to use.
var interaction: Bool = false
func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
let node = (contact.nodeA.name == "player") ? contact.nodeB : contact.nodeA
if node.physicsBody!.categoryBitMask == control.CategoryInteraction && interaction {
interaction = true
}
}
The endContact method is almost always called after the didBegin contact method, so I have it wait for a while before setting the boolean value back to false.
Note: I make the action wait for the updateContact method to be called.
func physicsWorld(_ world: SCNPhysicsWorld, didEnd contact: SCNPhysicsContact) {
let node = (contact.nodeA.name == "player") ? contact.nodeB : contact.nodeA
if node.physicsBody!.categoryBitMask == control.CategoryInteraction && ViewManager.shared.interaction {
let wait = SCNAction.wait(duration: 0.1)
let leave = SCNAction.run { _ in
self.interaction = false
}
let sequence = SCNAction.sequence([wait, leave])
player.runAction(sequence, forKey: "endInteraction")
}
}
The update method is always called when the node moves around the edges or inside the other node. So I have it counter the endContact method by removing the action that would have set the bool value to false.
func physicsWorld(_ world: SCNPhysicsWorld, didUpdate contact: SCNPhysicsContact) {
if player.action(forKey: "endInteraction") != nil {
player.removeAction(forKey: "endInteraction")
}
}
The endContact method is also called when the two nodes stop touching (who would have guessed) and, in my testing, is always the last method to be called.
Old Answer
It's not at all a perfect answer and it still has room for improvement, but for now, this is how I did it.
For the first code section, I am checking to see if the player has collided with the chat radius (or is within a certain distance of the NPC). If the player is inside the radius, then show the indicator to interact with the NPC and add 1 to the count.
var count = 0
var previousCount = 0
func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
let node = (contact.nodeA.name == "Player") ? contact.nodeB : contact.nodeA
if node.physicsBody!.categoryBitMask == CategoryInteraction {
// Show chat indicator here.
count += 1
if count > 100 {
count = 0
}
}
}
For the second code section, I'm checking to see if the player has left the chat radius by making the previousCount equal to count. If the count is equal to previousCount, then the player has left the chat radius so hide the interaction indicator.
func physicsWorld(_ world: SCNPhysicsWorld, didEnd contact: SCNPhysicsContact) {
if contact.nodeA.name != "chatRadius" && contact.nodeB.name != "chatRadius" { return }
if previousCount != count {
previousCount = count
} else {
// Hide chat indicator here.
count = 0
previousCount = 0
}
}
I'm working on a project where I'll have a virtual joystick on the screen which moves a character around the board. I found a few good examples and the one I'm using currently is located # https://github.com/Ishawn-Gullapalli/SpritekitJoystick/tree/master/SpritekitJoystick
While the player is moved around just like I want, I'm unable to figure out how by using this method I can change the player animation based on the direction he's going. I'm able to determine direction but my usual methods of invoking a SKAction doesn't animate the player, it just moves him to the new location while pausing the idle animation. I'm moving my player in the update statement like below and am looking for tips on how to change animations appropriately.
I'm including my update statement below. I've also tried to pass in a CGPoint value with the same effect. Thanks in advance for looking.
override func update(_ currentTime: TimeInterval) {
if joystick.joyStickTouched == true {
mainHero.position.x += joystick.moveRight(speed: movementSpeed)
// Example of me trying to animate my player
if joystick.moveRight(speed: movementSpeed) > 0 {
walkRight()
}
mainHero.position.x -= joystick.moveLeft(speed: movementSpeed)
mainHero.position.y += joystick.moveUp(speed: movementSpeed)
mainHero.position.y -= joystick.moveDown(speed: movementSpeed)
joystick.update(currentTime)
}
}
func walkRight() {
mainHero.isPaused = false
let idleAnimation = SKAction(named: "MainWalkRight")!
let rightGroup = SKAction.group([idleAnimation])
mainHero.run(rightGroup, withKey: "Right")
}
I want a sprite to follow the player and I tried to achieve this with the Pathfinding of GameplayKit. This is working, but there's problem. I get the path from the enemy sprite to the player sprite with the following code:
func moveEnemy(to playerPos: CGPoint){
guard !moving else { return }
moving = true
let startNode = GKGraphNode2D(point: float2(Float(self.position.x), Float(self.position.y)))
let endNode = GKGraphNode2D(point: float2(Float(playerPos.x), Float(playerPos.y)))
PhysicsHelper.graph.connectUsingObstacles(node: startNode)
PhysicsHelper.graph.connectUsingObstacles(node: endNode)
let path = PhysicsHelper.graph.findPath(from: startNode, to: endNode)
guard path.count > 0 else{ return }
var actions = [SKAction]()
for node in path{
if let point2d = node as? GKGraphNode2D{
let point = CGPoint(x:CGFloat(point2d.position.x) , y:CGFloat(point2d.position.y))
let action = SKAction.move(to: point, duration: 1)
actions.append(action)
}
}
let seq = SKAction.sequence(actions)
self.run(seq) {
self.moving = false
}
}
This is called in another function of my enemy in this block:
case .follow:
if allowMovement{
if self.action(forKey: "Hurt") == nil{
// orientEnemy(to: playerPos)
moveEnemy(to: playerPos)
// animateWalk()
}
}
and this function is called in the update function of my GameScene in this block:
if enemy.checkCircularIntersection(player: player, node: enemy, radius: 40){
print("Enemy is close to player")
enemy.update(playerPos: player.position)
}
The checkCircularIntersection function only checks, if the player is near the enemy.
My problem is that the enemy follows the player but when the player moves, the enemy moves to the point where the player stands before, then it stops for a second and then it moves again to the point where the player stood. But the player is already moved away.
Is there a way to let the enemy permanently follow the player and avoid obstacles without stopping?
Your code specifically guards against moving again until the existing move is completed.
guard !moving else { return }
moving = true
If you want to use paths you will have to recompute and reapply the path movement regularly to account for the target's movement.
However, to achieve your aim, Paths are not the most suitable GameKit tool to use. GKAgents are specifically designed for that kind of thing. It will require a significant refactoring of your code, but take a look at Agents, Goals and Behaviours.
Code:
func jumpAction(_ JumpPos: CGPoint, _ clickPos: CGPoint) {
if jumpCount < 2 {
var disty = (clickPos.y - JumpPos.y)
var distx = clickPos.x
self.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
self.physicsBody?.applyImpulse(CGVector(dx: distx, dy: disty))
if onGround == false {
jumpCount += 1
}
}
}//
func didBegin(_ other: SKSpriteNode, _ contact: SKPhysicsContact) {
if contact.contactPoint.y < self.frame.minY + 10 {
jumpCount = 1
onGround = true
}
if other.physicsBody?.categoryBitMask == Mask.SpinPlat {
jumpCount = 0
}
}
func didEnd(_ contact: SKPhysicsContact) {
onGround = false
}
I have 3 functions above. The JumpAction is called when I press on the Jump Button on the screen. The didBegin and didEnd functions are called when contact with the top of a platform occurs with the player.
My issue:
So if player is on platform and I jump, the jump action is called before the didEnd function. This is why I have the jumpCount set to 1 when the player contacts the platform. This works fine...
The real issue is when my player lands on the platform, and he bounces slightly off (due to restitution) and then the jump button is pressed.
When this occurs, the onGround flag is flipped to false before the jump action is called and my player can only single jump.
I'm trying to find a simple way to get around this issue without having to implement a bunch of code, haven't found a way yet.
So this is why I am here. I want to be able to double jump even with that slight little bounce I explained earlier.
Thank you in advance!!!
Additional Info:
I use the onGround flag so that if the player is on the ground, he can slide along the ground if they press the jump button in the right location...
Have you tried simply delaying setting the "on ground" flag by a fraction of a second?
let waitAction = SKAction.wait(forDuration: 0.5)
scene.runAction(waitAction, completion: {
self.onGround = false
})
Obviously you could experiment with the exact duration to make sure it gives the "bounce" just enough time to end.
I'm working on a game with some monsters of different kinds (one kind = one subclass of SKSpriteNode) crossing the scene.
To each kind is given a specific sound.
One example is I can have at a specific point in time:
5 monsters A
2 monsters B
0 monster C
What I would like at any time, is to loop a sound for each class which is part of the scene (A, B) (and not for each sprite !) , and to stop playing a sound for each class absent from the scene(C).
My first idea was to store a specific sound into each class of monster and to add a SKAction to each sprite that would play loop the sound of its class.
But this would play loop as many sounds as sprites on scene, and this doesn't match with my expectations.
Do you have an idea on the best approach to adopt for this ?
Is there a possibility to implement a kind of observer that would be able to notify when an instance of class is on the scene and when it is not ?
Many thanks for your help !
As #Knight0fDragon said, "SKAction is not designed to play looped music"
Maybe you can do: (I diden't test it, now I'm on windows machine)
Class MonsterA : SKSpriteNode {
static var monsterAInScene = Set<Monster>()
static let audioAction: SKAction = SKAction.playSoundFileNamed("MonsterA-audio") //Change to AVAudio for loop
static let audioNode: SKNode()
init (scene:SKScene) {
//Your init
if let MonsterA.monsterAInScene.count == 0 {
//play audio
scene.addChild(MonsterA.audioNode)
MonsterA.playAudio()
}
MonsterA.monsterAInScene.insert(self)
}
static func playAudio() {
//Play audio code
if MonsterA.audioNode.actionFroKey("audio") != nil {return}
MonsterA.audioNode.runaction(MonsterA.audioAction, forKey "audio")
}
override removeFromParent() {
MonsterA.monsterAInScene.remove(self)
if let MonsterA.monsterAInScene.count == 0 {
//stop audio code
MonsterA.audioNode.removeActionWithKey("audio")
MonsterA.audioNode.removeFromParent()
}
super.removeFromParent()
}
}
#Knight0fDragon and #Alessandro Ornano are more expert than me, they can suggest if is the best way to do.
I have finally adopted SKAudioNode as advised bu KnightOfDragon.
At the beginning of the GameScene class, I have added an observer to check the actual number of monster of each kind (Here with kind A):
var monsterASound = SKAudioNode()
var currentNumberOfMonsterA = 0 {
didSet {
if currentNumberOfMonsterA > 0 {
monsterASound.runAction(SKAction.play())
} else{
monsterASound.runAction(SKAction.stop())
}
}
}
currentNumberOfMonsterA is updated each time I add/remove a monster
//Add a monster A
self.currentNumberOfMonsterA += 1
//Removing a monster A
self.currentNumberOfMonsterA -= 1
//Destroying a monster A
self.currentNumberOfMonsterA -= 1
Then in the didMoveToView, I have added the SKAudioNode to the scene
self.monsterASound = SKAudioNode(URL: NSBundle.mainBundle().URLForResource("monsterA", withExtension: "wav")!)
self.monsterASound.runAction(SKAction.stop())
addChild(helicoSound)