SpriteKit Game Development [6] using physical engines (I) and spritekit Game Development
/* Above,
The Valentine's Day of the past two days has always been plain. This blog was intended to be written on that day, but it has no mood on that day.
One year ago, on Valentine's Day, I made a "geek chicken" and claimed that if a player saw "geek chicken 2" one day, it would mean that the author was stripped away.
There are only two goals for graduate students. One is to catch up with the girl who liked the undergraduate course, and the other is to win millions of downloads for a single game. Since the first goal was broken, I can't afford to raise my mind any more. After all, my graduate goal is doomed to fail...
On Valentine's Day this year, I watched Liangzi's sister's "Triangle in sight" all night on Site B. I like the Accompaniment in the drama very much. Now I can't stop listening to the "cocoon.
*/
Spritekit's physical engine will be introduced through three blogs.
The first blog mainly introduces the basic attributes and usage of SKPhysicsBody through chestnuts. It will particularly mention the combination of physical engines and Sprite actions.
Let's first look at how the physical engine works:
In a game update cycle, the system mainly performs the following four steps:
1. the system first calls the Update method rewritten by the user to Update the game logic,
2. the system automatically processes the Sprite action (SKAction), leaving the didEvaluateActions Method for developers to rewrite the update logic that can be executed only after the Sprite action is processed,
3. the system automatically processes physical simulations between physical elements, leaving the didSimulatePhysics Method for developers to rewrite the update logic that must be executed after the physical simulation is completed,
4. the system automatically processes the "constraints" between physical elements, leaving... (same as above, you know ~)
It should be emphasized that the physical computing of Spritekit starts only after the calculation of Action.
Let's get started (Xcode 6.1.1, iPhone5S simulation)
-> Create an iOS SpriteKit universal project named UsePhysics.
-> Set the portrait screen (for details about how to set the portrait screen, refer to [set portrait screen])
-> Replace the content of the class GameViewController with the following content: (for details about how to adapt to the screen, refer to [adapt to the screen])
Class GameViewController: UIViewController {override func viewDidLoad () {super. viewDidLoad () let scene = GameScene (size: CGSize (width: 1536, height: 2048) // adapt to the screen size let skView = self. view as SKView skView. showsPhysics = true // displays the physical simulation information skView. showsFPS = true skView. showsNodeCount = true skView. ignoresSiblingOrder = true scene. scaleMode =. aspectFill // adapt to the screen mode skView. presentScene (scene)} override func prefersStatusBarHidden ()-> Bool {// hide the system title bar return true }}
Note that the showsPhysics of skView is set to true, so that the physical simulation information is displayed.
-> Replace the content of the class GameScene:
Class GameScene: SKScene {var playableRect: CGRect! Override func didMoveToView (view: SKView) {// ---------- set the valid game region for the screen adaptation --------- // var tempWidth = size. height/16.0*9.0 var tempMargin = (size. width-tempWidth)/2.0 playableRect = CGRect (x: tempMargin, y: 0, width: tempWidth, height: size. height) // ---------- display the valid game region ---------- // let playableArea = SKShapeNode (rect: playableRect) playableArea. strokeColor = SKColor. redColor () playableArea. lineWidth = 10 addChild (playableArea) // TODO backgroundColor = SKColor. grayColor ()} override func touchesBegan (touches: NSSet, withEvent event: UIEvent) {for touch: AnyObject in touches {let location = touch. locationInNode (self) // TODO} override func update (currentTime: CFTimeInterval) {// TODO }}
Among them, playableRect is an effective area for games that adapt to the screen. You can see the effects of the game running under iPhone 5s and iPhone 4S:
-> Next, add game elements to the scene: (for example, sand.png, Bullet.png, Floor.png, FloorMask.png, and Circle.png)
Add the definition of game elements to GameScene:
// ---------- Game element ---------- // var floorElement: SKSpriteNode! // Rotate the water pipe var circleElement: SKSpriteNode! // Circle var bulletElement: SKSpriteNode! // Var bulletBoxElement: SKSpriteNode! // Bottom pipe
Add the following content at the bottom of didMoveToView:
// Util let centerX: CGFloat = size. width/2 let centerY: CGFloat = size. height/2 // Floor floorElement = SKSpriteNode (imageNamed: "Floor") floorElement. position = CGPoint (x: centerX, y: centerY + 300) floorElement. zPosition = 1 // bottom layer // Circle circleElement = SKSpriteNode (imageNamed: "Circle") circleElement. position = CGPoint (x: centerX, y: centerY) circleElement. zPosition = 2 // top floor // Bullet bulletElement = SKSpriteNode (imageNamed: "Bullet") bulletElement. position = CGPoint (x: centerX, y: 300) bulletElement. zPosition = 0 // bottom layer // BulletBox bulletBoxElement = SKSpriteNode (imageNamed: "Floor") bulletBoxElement. position = CGPoint (x: centerX, y: 0) bulletBoxElement. zPosition = 1 // Add to Scene addChild (floorElement) addChild (circleElement) addChild (bulletElement) addChild (bulletBoxElement)
The result is as follows:
-> Take a break ~ Now the carpet is basically arranged, and the hot physical simulation is about to begin ~
-> Set the physical boundaries of the environment and add the following at the bottom of didMoveToView:
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: playableRect)
PhysicsBody is actually the object that represents the physical operation of the object. The physical boundary here is instantiated through a rectangle.
Open SKPhysicsBody and you can see its definition:
init(circleOfRadius r: CGFloat) -> SKPhysicsBody init(circleOfRadius r: CGFloat, center: CGPoint) -> SKPhysicsBody init!(rectangleOfSize s: CGSize) -> SKPhysicsBody init!(rectangleOfSize s: CGSize, center: CGPoint) -> SKPhysicsBody init!(polygonFromPath path: CGPath!) -> SKPhysicsBody init(edgeFromPoint p1: CGPoint, toPoint p2: CGPoint) -> SKPhysicsBody init(edgeChainFromPath path: CGPath!) -> SKPhysicsBody init(edgeLoopFromPath path: CGPath!) -> SKPhysicsBody init(edgeLoopFromRect rect: CGRect) -> SKPhysicsBody init!(texture: SKTexture!, size: CGSize) -> SKPhysicsBody init!(texture: SKTexture!, alphaThreshold: Float, size: CGSize) -> SKPhysicsBody init(bodies: [AnyObject]) -> SKPhysicsBody
It can be seen that SKPhysicsBody can
Initialize the Circular Boundary through the edge length;
Initializes the rectangle boundary by the rectangle size;
Initialize the texture boundary through the texture mask;
Initialize shape boundaries through shape paths;
...
It is worth mentioning that texture boundary is initialized through texture (will be mentioned later)
-> To view the physical effect, add some sand to the physical world ~
Add the attribute rand100 to GameScene to obtain the random number:
// Random number (0,100] var rand100: CGFloat {get {return 1 + CGFloat (arc4random () % 100 )}}
Add the method spawnSand to GameScene to randomly generate sand:
Func spawnSand () {var sand = SKSpriteNode (imageNamed: "Sand") sand. name = "sand" sand. position = CGPoint (x: playableRect. minX + rand100 * playableRect. width/100, y: rand100 * playableRect. height/100) sand. physicsBody = SKPhysicsBody (circleOfRadius: 5.0) // before adding a physical engine, you can set it as sand. setScale (3) sand. physicsBody ?. Restitution = 0.5 // default 0.2 sand. physicsBody ?. Density = 10.0 // The default density is 1 sand. physicsBody ?. Friction = 0.5 // default friction 0.2 sand. physicsBody ?. Dynamic = true // dynamic sand. physicsBody ?. UsesPreciseCollisionDetection = false // enable precise conflict detection sand. physicsBody ?. LinearDamping = 0.1 // linear speed damping motion default 0.1 angularDamping sand. physicsBody ?. AffectedByGravity = true // is the sand. physicsBody affected by gravity ?. Mass = 0.5 println ("sand \ (sand. physicsBody ?. Mass) ") addChild (sand )}
Add the following content at the bottom of didMoveToView:
for i in 1...300 { spawnSand() }
You can see the following code when you run the program:
Is it safe to see the sand piled up at the bottom ~
Common physical attributes are listed in the spawnSand method:
Restitution is the percentage of remaining energy after an object collision. The value range is [0, 1] and the default value is 0.2. (If the value is greater than 1, you will find that the sand hits more and more fiercely, all touched ...)
Density is the object density. The default value is 1;
Mass is the object quality. It is calculated by default based on the object area;
Friction is the friction coefficient. The value is [0, 1]. The default value is 0.2;
Dynamic is the "dynamic" of objects ". For example, the dynamic of physicsbody in scene is static and does not move because of the force, as in a fixed wall (but can be moved using Action or directly modifying Position ), the dynamic nature of the sand is dynamic.
UsePreciseCollisionDetection is called to start precise collision detection. In fact, if an object moves too fast within one frame, it may pass through another object directly. To avoid this situation, this function can be enabled, but the performance cost is relatively high.
AffectByGravity indicates whether it is affected by gravity.
You may wish to modify the values of these attributes to see what changes will take place.
In fact, here I want to introduce these two lines of code:
Sand. physicsBody = SKPhysicsBody (circleOfRadius: 5.0) // before adding a physical engine, set the physical attribute to sand. setScale (3)
If we add a physical simulation to an element and modify its size, the physical boundary of the object changes accordingly (this is correct)
If we first modify the object size and then add a physical simulation for it, the physical boundary of the object is still the original size (which is incorrect)
So remember-do not set the element size before adding a physical simulation! (This problem is not a bug, but it is uncomfortable to use. I hope the SpriteKit version will be more refined and fix it. However, this will produce good and special results)
-> Next, add the corresponding physical simulation for several game elements: (Add the following code at the bottom of didMoveToView)
let floorMask = SKTexture(imageNamed: "FloorMask") floorElement.physicsBody = SKPhysicsBody(texture: floorMask, size: floorMask.size()) floorElement.physicsBody?.dynamic = false circleElement.physicsBody = SKPhysicsBody(circleOfRadius: 300.0) circleElement.physicsBody?.dynamic = false bulletBoxElement.physicsBody = SKPhysicsBody(rectangleOfSize: bulletBoxElement.size) bulletBoxElement.physicsBody?.dynamic = false bulletElement.physicsBody = SKPhysicsBody(rectangleOfSize: bulletElement.size) bulletElement.physicsBody?.dynamic = true
-> Now this is only a scenario with physical simulation. It cannot interact with players, so we set it like this: click on the screen and bullet) jump up (get an upward momentum) and override the TouchBegin method:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) { bulletElement.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 500)) }
Can I skip the program at will now? (Bad, I was trying to make a shot, but now I can't help making it flappy * ird)
-> The current scenario is not cool enough. Here we want to rotate the floor around the circle. For details about the rotation and AnchorPoint, see [Rotation & AnchorPoint]. here we need to do this:
Add the RotateByPoint method to GameScene. This method allows the element to rotate rad radians around the point.
func RotateByPoint(element:SKNode,point:CGPoint,radio:CGFloat) { let tempPos = element.position let resultX = point.x + (tempPos.x - point.x) * cos(radio) - (tempPos.y - point.y) * sin(radio) let resultY = point.y + (tempPos.x - point.x) * sin(radio) + (tempPos.y - point.y) * cos(radio) element.position.x = resultX element.position.y = resultY }
Then rewrite the Update method:
override func update(currentTime: CFTimeInterval) { //TODO let rotateSpeed = CGFloat(M_PI / 120) RotateByPoint(floorElement, point: circleElement.position, radio: rotateSpeed) floorElement.zRotation += rotateSpeed }
Run the program to see the continuously rotating pipe and bird, and so on. This is not...
-> For more information, see the next blog ~
Now, although the pipe is rotated, it is obviously not uniform. In fact, what this blog wants to introduce is the relationship and impact between SKAction and physical computing.
The following is a summary of the code of this blog (the above content has been modified)
//// GameScene. swift // UsePhysics /// Created by samurai54 on 15/2/16. // Copyright (c) JiNG. all rights reserved. // import SpriteKitclass GameScene: SKScene {// ---------- game element ---------- // var floorElement: SKSpriteNode! // Rotate the water pipe var circleElement: SKSpriteNode! // Circle var bulletElement: SKSpriteNode! // Var bulletBoxElement: SKSpriteNode! // Var lastRotation: CGFloat = 0.0 var deltaRotation: CGFloat = 0.0 // random number (0,100] var rand100: CGFloat {get {return 1 + CGFloat (arc4random () % 100)} var playableRect: CGRect! Func spawnSand () {var sand = SKSpriteNode (imageNamed: "Sand") sand. name = "sand" sand. position = CGPoint (x: playableRect. minX + rand100 * playableRect. width/100, y: rand100 * playableRect. height/100) sand. physicsBody = SKPhysicsBody (circleOfRadius: 5.0) // before adding a physical engine, you can set it as sand. setScale (3) sand. physicsBody ?. Restitution = 0.5 // default 0.2 sand. physicsBody ?. Density = 10.0 // The default density is 1 sand. physicsBody ?. Friction = 0.5 // default friction 0.2 sand. physicsBody ?. Dynamic = true // dynamic sand. physicsBody ?. UsesPreciseCollisionDetection = false // enable precise conflict detection sand. physicsBody ?. LinearDamping = 0.1 // linear speed damping motion default 0.1 angularDamping sand. physicsBody ?. AffectedByGravity = true // is the sand. physicsBody affected by gravity ?. Mass = 0.5 println ("sand \ (sand. physicsBody ?. Mass) ") addChild (sand)} override func didMoveToView (view: SKView) {// ---------- set the valid game region for matching the screen --------- // var tempWidth = size. height/16.0*9.0 var tempMargin = (size. width-tempWidth)/2.0 playableRect = CGRect (x: tempMargin, y: 0, width: tempWidth, height: size. height) // ---------- display the valid game region ---------- // let playableArea = SKShapeNode (rect: playableRect) playableArea. strokeColor = SKColor. redColor () PlayableArea. lineWidth = 10 addChild (playableArea) // TODO backgroundColor = SKColor. grayColor () // Util let centerX: CGFloat = size. width/2 let centerY: CGFloat = size. height/2 // Floor floorElement = SKSpriteNode (imageNamed: "Floor") floorElement. position = CGPoint (x: centerX, y: centerY + 300) floorElement. zPosition = 1 // bottom layer // Circle circleElement = SKSpriteNode (imageNamed: "Circle") circleEle Ment. position = CGPoint (x: centerX, y: centerY) circleElement. zPosition = 2 // top floor // Bullet bulletElement = SKSpriteNode (imageNamed: "Bullet") bulletElement. position = CGPoint (x: centerX, y: 1600) bulletElement. zPosition = 0 // bottom layer // BulletBox bulletBoxElement = SKSpriteNode (imageNamed: "Floor") bulletBoxElement. position = CGPoint (x: centerX, y: 0) bulletBoxElement. zPosition = 1 // Add to the bullet Scene addChild (floorElement) addChild (circleElement) addChild (bulletElement) addChild (bulletBoxElement) // Add physical simulation self. physicsBody = SKPhysicsBody (edgeLoopFromRect: playableRect) for I in 1... 300 {spawnSand ()} let floorMask = SKTexture (imageNamed: "FloorMask") floorElement. physicsBody = SKPhysicsBody (texture: floorMask, size: floorMask. size () floorElement. physicsBody ?. Dynamic = false/* --------------- what will happen? ------------- * // floorElement. physicsBody ?. Dynamic = true // floorElement. physicsBody ?. AffectedByGravity = false circleElement. physicsBody = SKPhysicsBody (circleOfRadius: 300.0) circleElement. physicsBody ?. Dynamic = false bulletBoxElement. physicsBody = SKPhysicsBody (rectangleOfSize: bulletBoxElement. size) bulletBoxElement. physicsBody ?. Dynamic = false bulletElement. physicsBody = SKPhysicsBody (rectangleOfSize: bulletElement. size) bulletElement. physicsBody ?. Dynamic = true floorElement. runAction (SKAction. repeatActionForever (SKAction. rotateByAngle (CGFloat (M_PI * 2), duration: 4)} override func touchesBegan (touches: NSSet, withEvent event: UIEvent) {bulletElement. physicsBody ?. ApplyImpulse (CGVector (dx: 0, dy: 300)} override func update (currentTime: CFTimeInterval) {} override func didEvaluateActions () {deltaRotation = floorElement. zrtation-lastRotation = floorElement. rotateByPoint (floorElement, point: CGPoint (x: size. width/2, y: size. height/2), radio: deltaRotation)} override func didSimulatePhysics () {bulletElement. position. x = size. width/2 bulletElement. zrtation = CGFloat (M_PI/2)} func RotateByPoint (element: SKNode, point: CGPoint, radio: CGFloat) {let tempPos = element. position let resultX = point. x + (tempPos. x-point. x) * cos (radio)-(tempPos. y-point. y) * sin (radio) let resultY = point. y + (tempPos. x-point. x) * sin (radio) + (tempPos. y-point. y) * cos (radio) element. position. x = resultX element. position. y = resultY }}
Above.