I'm in the process of making a space invaders style game, and would like to make it so that an enemy must be hit three times before exploding.
The tutorial I am using only shows how to make enemies explode after only one hit. It uses a function called didBegin(_ contact: SKPhysicsContact)
https://www.youtube.com/watch?v=F0kcw6eryJs&t=617s
Now, this function, from my understanding, evaluates each instance of contact, assigns two bodies two distinct roles, and then decides what to do with each body. I assume to have an enemy take three hits, a variable containing their remaining health would be used. Then, with each instance of contact, the variable would decrease by one, until if it is zero, the enemy is removed.
However, there is a deep problem in this. SincedidBegin(_ contact: SKPhysicsContact) evaluates only one instance of contact, it has no knowledge of other previous instances of contact. Essentially, when an enemy gets hit, there is no way to know if the enemy was hit before, has not been hit at all, or has been hit too many times. If there was only one enemy, then I could use one variable to keep track of its health. This is not the case, though, as there are multiple enemies on screen at once, and every time contact is made, there is no way to know if the previous hit was on this enemy, or another enemy.
If there is one enemy on screen, it is simple because every time contact is made it would have to be that one enemy. But if there are multiple entities on screen, then there is no way to know which enemy a contact applies to.
I believe this would require some sort of identification for each instance of an enemy, though I an unsure of how to do this. For those of you reading, I have many thanks for dropping in, and I am very grateful if you can help.
There is no need to keep separate dictionaries to track states of a sprite
Every SKNode has userData, and you can track an enemies life with it
Example:
let invader = SKSpriteNode(imageNamed:"invader")
invader.userData = ["health":3]
...
func didBeginContact(...)
{
...
//when a contact happens
contact.body_.node.userData["health"] -= 1
...
}
Now keep in mind, 1 bullet can hit 1 enemy two times. You do not want to trigger 2 losses of life here, so you are going to need to have a temporary variable (which you can also save in userData) that lets you know if a particular bullet has already made contact with an invader.
You are absolutely right, you somehow need to track your enemies and their health on your own. There are probably a bunch of ways to do it but here is one way of achieving what your aiming for:
Use a dictionary of enemies with their health. Every enemy will be given an id of some sort to identify them. The health could be represented as an Int. This dictionary can also be used to easily tell how many enemies are still around.
var enemyHealth = [String:Int]()
Nodes have a name property that you can set any string value to. This is how you'll be able to identify your nodes. When creating an enemy, you'll create an id (for instance generating a random number), set the name of the node as the id and add the enemy to the enemyHealth dictionary with a default health value.
let enemyNode = SKSpriteNode(color: UIColor.white, size: CGSize.zero)
let enemyId = "\(arc4random())"
enemyNode.name = enemyId
enemyHealth[enemyId] = 10
...
In didBegin(_ contact: SKPhysicsContact) you can access the involved nodes in the collision using the contact parameter of the function (contact.bodyA.node or contact.bodyB.node). By comparing the names of the nodes with the keys in enemyHealth, you can figure out which collision body was the enemy and what health it has.
let enemy:SKSpriteNode
if enemyHealth.keys.contains(contact.bodyA.node?.name ?? "") {
enemy = contact.bodyA.node as! SKSpriteNode
} else if enemyHealth.keys.contains(contact.bodyB.node?.name ?? "") {
enemy = contact.bodyB.node as! SKSpriteNode
} else {
return //none of the collision partners are an enemy
}
let health = enemyHealth[enemy.name!]!
...
Now you can do whatever you want with the enemy. When the enemy is killed/removed then also remove it from the enemyHealth dictionary.
Related
I am working on a car multiplayer game and i want if the player 1 and player2 is in circle and the other players (player 3,4,5) are outside the circle then the outside players (player 3,4,5) destroy and the circle players (player 1,2) won.. I dont understand hows it possible
I use ontrigger but i dont understand how can I apply this
Well with the limited knowledge of your project, the best advice I can give is have a collider for that circle, make it a trigger, and make a Collision script for it where you have the following method
void OnTriggerEnter (Collider other)
{
if (other.gameObject.name(or tag) == "Whatever the tag or name you want")
{
// check here for some field or identifier for the specific instance
// of the object to see which player entered the circle
carMasterScript.DestroyEverythingExcept(other);
}
}
Once you replace the vague things here with what you have in your scripts, and possibly make some Master script so you can have a single instance that can reach and access every car/player instance, this should trigger when another trigger object(make the cars trigger rigidbodies or kinematic bodies as well) and check to see if the thing that collided is a car, then protects that instance of the car while destroying every other one
This is sort of a stupid question, but what does the function self.addChild() do?
I am familiar with this function, and how to use it, but I am not exactly sure what it adds the child to.
For instance, I have created and designed an SKShapeNode called spinnyNode. Then I call the function:
func touchDown(atPoint pos : CGPoint) {
if let n = self.spinnyNode?.copy() as! SKShapeNode? {
n.position = pos
n.strokeColor = SKColor.black
self.addChild(n)
}
What is the parent in this situation? Is it the view that the node is being created in?
Thank you so much for your time, and your answering of stupid questions.
Lets break it down:
self in this case, refers to the SKScene which your are currently in. So if you're in your gameScene, the node will be added to the gameScene. Note that you can add nodes to other nodes, so if you have an SKNode named gameLayer, you could add a node to gameLayer, which would then be added to the scene. (That would look like this: gameLayer.addChild(node)) If there is no specified location for the node, it default chooses self
addChild(node) is the function that actually adds the specified node to the specified location (see above). You tell the compiler which node to add to the scene by putting its declared name in the brackets.
Make sure you're setting the nodes attributes (position, size, etc...), as they stay the default values until you change them.
The parent is essentially the scene, and what you are doing is adding that node to the scene. Without this call, the node would be created, but never passed to the scene, so it wouldn't spawn.
Think of it as doing some homework for a teacher, then the addChild submits it. If you don't submit it, it isn't used. So whatever class your function is in, be it SKScene etc, that is the parent the node is being passed to.
And don't worry, it isn't a stupid question, learning the foundations of how these functions work is a great way to build better apps!
Is there some way in Swift that I can tell when an SKSpriteNode has actually been removed from the scene? I don't think it's actually done when removeFromParent is called, but instead I think it's done later, when Sprite-Kit thinks it's convenient to do so.
I'm trying to understand the full life cycle and I've noticed that a sprite can still be involved in contact and collisions in didBeginContact even after that sprite has been removed.
If I print out the contents of children (i.e. the array holding all the children of the scene) I see that the sprite is removed as soon as removeFromParent called, but the sprite is still available (at least, for this execution of the SK game loop).
Edit: This question came out from an earlier question of mine concerning didBeginContact being called multiple times for one contact (Sprite-Kit registering multiple collisions for single contact) and discovering that removing the sprite during the first contact did not prevent the subsequent contact(s). (Because SK has 'queued' to contacts in advance.) so I was wondering when the sprite is actually removed.
Am I missing the obvious? So even after removeFromParent the sprite is still around. However, it might well be because I have assigned the node to a temporary SKSpriteNode variable, then as long as that variable is around, there is a strong reference to the node, so it won't be deallocated. Also the SKPhysicsContact object itself will retain a reference to the physicsBody, which has a reference to the node which I think will also prevent allocation.
Update
To see when a sprite is actually released, use the deinit() method:
deinit {
print("Invader of type \(type) deinitialised")
}
I think this can only be added in a subclass definition, not via an extension.
Having a variable with a strong reference to the node being removed will prevent the node from being de-allocated until that variable is itself removed or changed to refer to something else.
If I've understand your question, I think it's not needed because you can always do:
if let myNode = self.childNode(withName: "//myNode") {
// ok myNode exist
}
Hope it helps you, you can write this code wherever you think is necessary.
Update:
About the reformulation of your question take a look below to these comments.
I have a suggestion after reading through the comments... move the node to a place outside of the playable area of your game, then remove it from parent. At this point you don't have to worry about when the physics body gets removed or when SK handles it. Or you could set the physicsBody? to nil at the same time, or use a bitmask flag as KoD suggested.
You can override all of the functions in the SK loop and check to see exactly when your pb is removed: https://developer.apple.com/library/content/documentation/GraphicsAnimation/Conceptual/SpriteKit_PG/Actions/Actions.html
I am making a platformer in Unity using unityscript.
I have a Player parent object with a character controller and various child objects. I have a similar enemy with a box collider. I'm struggling to differentiate between the collision happening when the player walks into the enemy and when the the player jumps and collides with it from above.
I've tried tagging the child objects but they don't have colliders. If I add colliders to the child objects, it messes up my character movement. I've also tried to test the position of the player:
if(col.transform.position.y >= transform.position.y){ killThyself(); }
But this doesn't work either - should I add the height of the enemy? If so how do I do that?
Any suggestions happily received.
Mmmm, I would use a boolean variable. Make it true when you press the jump button, and false once the jump has finished (when the player touches the floor). Declare the boolean as a public variable.
After that, in the OnCollision method, check the variable value.
I recommend you to read this to understand how to do that:
http://docs.unity3d.com/Documentation/ScriptReference/index.Accessing_Other_Game_Objects.html
Well, in your case I would assign the "otherScript" (the player script) doing this in the enemy control script:
var player : GameObject;
var playerScript: PlayerScript;
//I'm assuming "PlayerScript" is the name of the control script of your player,
//where you defined the "jump" variable.
function Start()
{
player = GameObject.FindGameObjectWithTag("Player");
//You also can use GameObject.Find("PlayerName")
playerScript = player.GetComponent(PlayerScript);
//Get the script from the player
}
function OnCollisionEnter(col : Collision)
{
if (playerScript.jump == true)
{
killThyself();
}
}
I'm finding the player on the function Start, to find it only ONCE, in the level load. Now, I assign the player script into a variable, so, when the player hit the enemy, this enemy will check if the player is jumping using his script. Remember to declare this variable as a public variable or this won't work.
Please comment if you have any problem using this method =)
I would use a second collider at the upper part of the enemy let's called it headCollider. Set isTrigger = true and maybe use a special physics material. If this headCollider is the first one to get triggered you know that the player chararacter is jumping on the enemy or is falling on it from above.
In OnTriggerEnter (Collider other) you can prepare some status variables (and maybe a timer for resetting the status). Also the current jump status like in V_Programmer's answer suggested should be useful for evaluation.
As Unity does not allow you to attach 2 colliders of the same kind, you have to use box and sphere or create an empty child and use that one.
I am working on a game. There are balls that fall from the top of the screen, and the player has to catch them, as the are caught they stack ontop of one another. I have a method that creates each new ball and adds it to an Array that i use to move the sprites. Problem is that the after they collide I need them to stop moving, since the array is called on to move them they all move. And if i try to make them stop they all stop. So I was hoping to create a pointer attribute if ther is such a think, for example "sprite.position" I need a new attribute that i can check like a boolean. I was hoping to create a attribute like sprite.hasCollided and if it returns YES then the ball should no longer move. Is this possible or is there a better way to do it?
Thanks
Tanner
I would suggest you create a ball object. And add the boolean as as part of the object.
CCNodes (and, by inheritence, CCSprites) have a userData property, which is a void*. You can use this to relate a custom object to a cocos2d object. Keep in mind if you use the userData option, you will, in most cases, need to allocate any memory when you create/assign the sprite, and release it when you are done.
int* myInt = (int*)malloc(sizeof(int));
*myInt = 0;
sprite.userData = myInt;
//some time later, when you are done with the sprite
free(sprite.userData);
As an improvement on the userData property, you can do what xuanweng suggests and create a ball object containing various game-related properties for the balls, and assign an instance of this to each of your ball CCSprites using the method above.