• iFIERO


    开始游戏教程前,首先介绍一下SpriteKit是什么?
    SpriteKit提供了一个图形渲染和动画的基础结构,你可以使用它让任意类型的纹理图片或者精灵动起来。SpriteKit使用渲染循环,利用图形硬件渲染动画的每一帧。

     
    SpriteKit框架渲染每一帧的周期流程原理图

    在iOS传统的view的系统中,view的内容被渲染一次后就将一直等待,直到需要渲染的内容发生改变(比如用户发生交互,view的迁移等)的时候,才进行下一次渲染。这主要是因为传统的view大多工作在静态环境下,并没有需要频繁改变的需求。而对于SpriteKit来说,其本身就是用来制作大多数时候是动态的游戏的,为了保证动画的流畅和场景的持续更新,在SpriteKit中view将会循环不断地重绘。

    动画和渲染的进程是和SKScene对象绑定的,只有当场景被呈现时,这些渲染以及其中的action才会被执行。SKScene实例中,一个循环按执行顺序包括:

    每一帧开始时,SKScene的-update:方法将被调用,参数是从开始时到调用时所经过的时间。在该方法中,我们应该实现一些游戏逻辑,包括AI,精灵行为等等,另外也可以在该方法中更新node的属性或者让node执行action
    在update执行完毕后,SKScene将会开始执行所有的action。因为action是可以由开发者设定的(还记得runBlock:么),因此在这一个阶段我们也是可以执行自己的代码的。

    在当前帧的action结束之后,SKScene的-didEvaluateActions将被调用,我们可以在这个方法里对结点做最后的调整或者限制,之后将进入物理引擎的计算阶段。

    然后SKScene将会开始物理计算,如果在结点上添加了SKPhysicsBody的话,那么这个结点将会具有物理特性,并参与到这个阶段的计算。根据物理计算的结果,SpriteKit将会决定结点新的状态。

    然后-didSimulatePhysics会被调用,这类似之前的-didEvaluateActions。这里是我们开发者能参与的最后的地方,是我们改变结点的最后机会。
    一帧的最后是渲染流程,根据之前设定和计算的结果对整个呈现的场景进行绘制。完成之后,SpriteKit将开始新的一帧。

    在了解了一些SpriteKit的基础概念后,就跟着iFIERO来创建一个简单的游戏作为开启游戏入门之旅吧。

    此《宇宙大战 Space Battle》教程共分为三个章节系列,

    (一)宇宙大战 Space Battle — 新建场景Scene、导入各个SpriteNode精灵、Particle粒子节点及建立背景音乐(你正在此处进行学习)

    (二)宇宙大战 Space Battle — 创建无限循环的背景Endless、监测精灵之间的物体碰撞及物理引擎Accleroation

    (三)宇宙大战 Space Battle — 各个场景SCENE之间的切换、利用UserDefaults统计分数

    你将在此教程中的三个系列当中学到如下的技能:

    • SpaceBattle 宇宙大战 在此游戏中您将获得如下技能:

    • 1、LaunchScreen 学习如何设置游戏启动画面;

    • 2、Scenes 学习如何切换不同的场景 主菜单+游戏场景+游戏结束场景;

    • 3、Accleroation 利用重力加速度 让飞船左右移动;

    • 4、Endless Background 无限循环背景;

    • 5、Scene Edit 直接使用可见即所得操作;

    • 6、UserDefaults 保存游戏分数、最高分;

    • 7、Random 利用可复用的随机函数生成Enemy;

    • 8、Background Music 如何添加背景音乐;

    • 9、Particle 粒子爆炸特效;

    应用以上各项SpriteKit与Swift技能,你开发出来的最终手机游戏的效果为如下所示:

     
    image

    一、教程开始 Getting Started

    游戏开始前,请下载本教程的初始项目(http://www.ifiero.com/uploads/SpaceBattle-01Starter.zip)。本游戏是由SpriteKit框架、Swift语言,XCODE开发工具进行开发的。

    1、打开XCODE(请用正式版,非Beta版),选择Create a new Xcode project,选择iOS->Game,输入Product Name(这里命名为SpaceBattle),开发语言Language选择Swfit,点击Next,工程即新建完毕

     
    xcode.png
     
    iosGame.png
     
    03.png

    2、选择Genrnal面板,因为此Space Battle游戏为竖屏游戏,所以去除勾选Deployment Info -> Device Orientation中的Landscape Left 与Landscape Right,我们不需要横屏效果

     
    04.png

    3、删除XCode左侧目录中的 Action.sks(暂时没有用到),修改GameScene.swift及GameViewController.swift的相关代码

     
    删除Action.sks
     
    修改GameScene.swift的代码
     
    设置Scene的尺寸为CGSize(x:1536,y:2048)

    二、可视化编辑场景 Introducing the Sprite Kit Visual Editor

    1.首先需要编辑场景.sks文件,打开GameScene.sks文件,设置场景的尺寸为iPAD4:3的比例(W:1536,H:2048),并删除场景中的文字。

     
    点击Color面板,可修改Scene的场景背景颜色

    2.拖动音乐文件到导航栏navigator->SpaceBattle文件夹,勾选Copy items if needed,Added folders选择Create groups,Add to targets勾选SpaceBattle

     
    拖动音乐文件

    3.拖动游戏工程所需要的图片到Assets.xcassets文件夹

     
    Assets.xcassets

    4.资源导入后,左侧的导航栏navigator如下图所示

     
    左侧的导航栏navigator样式

    5.Assets图库中的图片尺寸分为1x,2x,3x,你只需设置1x的图片尺寸大小即可,SpriteKit会自动根据你运行的device设备尺寸(iPhoneX,iPhone,iPhone Plus,iPad)进行相应比例的调整。

     
    Assets图库中的图片尺寸分为1x,2x,3x,只需设置1x图片即可

    非常的棒,你已经学会如何导入Mac电脑中的资源文件(图片、音乐、粒子)到SpaceBattle游戏工程内中了。
    那么,现在我们就来学习如何新建SpriteKit精灵节点吧!

    6.选择左侧导航栏Navigator的GameScene.sks,直接拖动一个Color Sprite到场景中,选中精灵,设置Position(0,0),修改texture为BG_SpaceBattle_planet(AssetsAssets.xcassets文件夹的名称),并命名精灵节点的名称 Name为bg。

     
    拖动一个Color Sprite到场景中
     
    命名精灵节点的名称 Name为bg

    7.现在你可以运行模拟器(XCode -> Product -> Run),看看你的游戏是否正确显示你刚刚建立的精灵节点。

     
    选择device设备
     
    Run运行
     
    Simulator模拟器

    棒棒哒! 你已学会了如何在场景中建立精灵节点及如何运行模拟器进行调试!

    三、SpriteKit Physics 物理引擎

    1.Spritekit提供了一个默认的物理模拟系统,用来模拟真实物理世界,可以使得编程者将注意力从力学碰撞和重力模拟的计算中解放出来,通过简单地代码来实现物理碰撞的模拟,而将注意力集中在更需要花费精力的地方。现在,让我们来学习这个系统的使用吧。

    首先需要认识两个类,一个是场景scene的属性类SKPhysicsWorld,这个类基于场景,只能被修改但是不能被创建,这个类负责提供重力和检查碰撞(碰撞需要实现SKPhysicsContactDelegate代理协议),另一个就是SKPhysicsBody类,你可以对你的SKNode节点添加物理体属性,来让他们可以参与物理模拟的相关计算。

    SpriteKit SKPhysicsBody类物理体的属性图表

    属 性功 能图示
    mass 它决定力是如何影响主体,以及当主体参与碰撞时它有多大的动量,以千克为单位
     
    mass
    friction 它决定了物体的光滑程度.取值范围为从0.0(表面光滑,物体滑动很顺畅,就像小冰块似的)到1.0(在表面滑动是,物体会很快停止) --
    linearDamping 物体的线性阻尼.取值范围为0.0(速度从不衰减)到1.0(速度立即衰减).默认值为0.1该属性被用于模拟水流或者空气的阻力.
     
    linearDamping
    angularDamping 物体的角速度阻尼.取值范围为0.0(速度从不衰减)到1.0(速度立即衰减).默认值为0.1该属性被用于模拟水流或者空气的阻力.
     
    angularDamping
    restitution 描述了当物理实体从另外一个物体上弹出时,还拥有多少能量.基本上我们称之为"反弹力".它的取值介于0.0(完全不反弹)到1.0(和物体碰撞反弹是所受的力与刚开始碰撞时的力的大小相同)之间.默认值为0.2
     
    restitution
    density 物体的密度,以千克每立方米为单位.密度是根据单位体积的质量来定义的.密度越高,体积越大,物体也就会越重.密度的默认值为1.0 --
    affectedByGravity 设置物体是否受重力的影响.所有的物体默认的情况都是受重力影响,但是开发者可以简单的吧这个标记设置为NO,使其不受重力影响.
     
    affectedByGravity
    allowsRotation 设置物体是否受到一个旋转力的影响,默认为YES,如果该值设置为NO,物理体将忽略施加在它身上所有的力 --
    resting 设置物理体是否在休息.物理引擎对于一段时间内没有移动过的物体做了一个优化,把他们标记为"正在休息(resting)",这样,物理引擎就不需要对它们进行计算了.如果你想要手动的唤醒一个正在休息的物体,简单的把resting设置为NO即可 --
    categoryBitMask 一个16进制数,定义了物体的类别.场景中每一个物理体都可以分配到超过32个不同的种类里面,每个对应位中的值
     
    categoryBitMask
    collisionBitMask 一个16进制数,定义哪种类别的物理体可以与之发生碰撞.当两个物体相关联的时候,就可能发生一个碰撞.这个物体的位相对于其他物体的类别做一个逻辑上的加法操作.如果结果是一个非零的值,则该物体收到碰撞的影响 --
    contactTestBitMask 一个16进制数,两个物体碰撞后发出通知 didBegin可接收到通知 --
    usesPreciseCollisionDetection 设置物体是否使用更精准的碰撞算法.默认情况下,除非确实有必要,Sprite Kit并不会启动精确的冲突检测,因为这样运行效率更高.但是不启动精确的冲突检测会有一个副作用,如果一个物体移动的非常快(比如一个子弹),它可能会直接穿过其他物体.如果这种情况确实发生了,你就应该尝试启动更精准的冲突检测了 --
    velocity 物理体的速度矢量
     
    velocity
    angularVelocity 物理体的角速度.角速度是一个围绕着一个轴矢量(0.0,0.0,1.0)的速度,单位是弧度每秒
     
    angularVelocity

    以上图表感谢简书作者的收集整理:https://www.jianshu.com/p/4046bab3a63d

    2.对SpriteKit PhysicsBody类的基础的概念了解后,我们现在就来新建player玩家飞船节点playerNode还有alien外星飞船精灵节点,并设置他们的物理属性。

     
    新建player玩家飞船节点 属性面板中Sprite Name命名为playerNode
    class GameScene: SKScene,SKPhysicsContactDelegate {
        
        private var playerNode:SKSpriteNode!  /// 玩家 宇宙飞船
        
        override func didMove(to view: SKView) {
            
            physicsWorld.gravity = CGVector(dx: 0, dy: -9.8) /// 建立物理世界 重力向下
            physicsWorld.contactDelegate = self              /// 碰撞接触代理为当前scene (GameScene)
            setupPlayer()
        }
        
        //MARK: - 玩家 宇宙飞船
        func setupPlayer(){
            playerNode = childNode(withName: "playerNode") as! SKSpriteNode
            playerNode.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "Player"), size: SKTexture(imageNamed: "Player").size())
            playerNode.physicsBody?.affectedByGravity = false // 不受物理世界的重力影响
            playerNode.physicsBody?.isDynamic = true
            playerNode.physicsBody?.categoryBitMask    = PhysicsCategory.SpaceShip /// 唯一标识
            playerNode.physicsBody?.collisionBitMask   = PhysicsCategory.None      /// 碰撞后要弹开吗
            playerNode.physicsBody?.contactTestBitMask = PhysicsCategory.Alien     /// 碰撞后发出通知
        }
        
        override func update(_ currentTime: TimeInterval) {
            // Called before each frame is rendered
        }
    }
    

    随机生成alien精灵节点

      //MARK: -  生成随机Alien
        @objc func spawnAlien() {
            // 1 or 2
            let i = Int(CGFloat(arc4random()).truncatingRemainder(dividingBy: 2) + 1)
            
            let imageName = "Enemy0(i)"
            let alien  = SKSpriteNode(imageNamed: imageName)
            alien.anchorPoint = CGPoint(x: 0.5, y: 0.5)
            alien.zPosition   = 1
            alien.name = "Alien"
            var xPosition:CGFloat = 0.0
            // 生成随机的x-Axis轴的位置
            xPosition = CGFloat.random(min: -self.frame.size.width+alien.size.width, max: self.frame.size.width - alien.size.width)
            alien.position = CGPoint(x: xPosition, y: self.frame.size.height + alien.size.height * 2)
            self.addChild(alien)
            // 物理体 PhysicsBody
            alien.physicsBody = SKPhysicsBody(circleOfRadius: alien.size.width / 2)  /// 设置物理身体
            alien.physicsBody?.affectedByGravity = false /// 不受重力影响,自定义飞船移动速度;
            alien.physicsBody?.categoryBitMask   = PhysicsCategory.Alien /// 1.设置唯一属性
            alien.physicsBody?.contactTestBitMask = PhysicsCategory.BulletBlue | PhysicsCategory.SpaceShip /// 2.和哪些节点Node发生碰撞后发出通知
            alien.physicsBody?.collisionBitMask   = PhysicsCategory.None /// 3.碰撞后是否弹开
            
            let duration   = CGFloat.random(min: CGFloat(1.0), max: CGFloat(3.8))  ///随机函数 返回二个数之间的随机数
            let actionDown = SKAction.move(to: CGPoint(x: xPosition, y: -self.frame.size.height), duration: TimeInterval(duration))
            alien.run(SKAction.sequence([actionDown,
                                         SKAction.run({
                                            alien.removeFromParent() // 移除节点;
                                         })]))
            
        }
    

    CGFloat.random 拓展函数 返回二个数之间的随机数

    import CoreGraphics
    import SpriteKit
    
    public extension CGFloat {
        
        #if !(arch(x86_64) || arch(arm64))
        func sqrt(a: CGFloat) -> CGFloat {
        return CGFloat(sqrtf(Float(a)))
        }
        #endif
        
        public static func random() -> CGFloat {
            return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
        }
        
        public static func random(min: CGFloat, max: CGFloat) -> CGFloat {
            assert(min < max)
            return CGFloat.random() * (max - min) + min
        }
        
    }
    
    

    在override func didMove(to view: SKView) {}内应用Timer.scheduledTimer间隔0.5秒生成Alien

    // spawnAlien()
            Timer.scheduledTimer(timeInterval: TimeInterval(0.5), target: self, selector: #selector(GameScene.spawnAlien), userInfo: nil, repeats: true)
    

    现在Command+R 运行工程下(或选择XCODE -> Product-> Run),你在模拟器中应可以看到源源不断的alien外星飞船正向下俯冲

     
    alien外星飞船正向下俯冲

    3.生成子弹及粒子效果

     // MARK: - 生成子弹; 点击屏幕后才发射
        func spawnBulletAndFire(){
            /// 子弹
            let bulletNode = SKSpriteNode(imageNamed: "BulletBlue")
            bulletNode.position.x = playerNode.position.x
            // 子弹的Y轴位置 因为playNode的AnchorPoit位于飞船中心 所以子弹发射时的瞬间位置位于飞船正中心,要加上飞船的半径,位于枪口;
            bulletNode.position.y = playerNode.position.y + playerNode.size.height / 2
            bulletNode.zPosition = 1
            self.addChild(bulletNode)
            bulletNode.physicsBody = SKPhysicsBody(circleOfRadius: bulletNode.size.width / 2)
            bulletNode.physicsBody?.affectedByGravity = false // 子弹不受重力影响;
            bulletNode.physicsBody?.categoryBitMask   =  PhysicsCategory.BulletBlue
            bulletNode.physicsBody?.contactTestBitMask = PhysicsCategory.Alien
            bulletNode.physicsBody?.collisionBitMask  = PhysicsCategory.None
            bulletNode.physicsBody?.usesPreciseCollisionDetection = true ///子弹飞速运动,设置探测精细碰撞
            
            /// 把子弹往上移出屏幕
            let moveTo = CGPoint(x: playerNode.position.x, y: playerNode.position.y + self.frame.size.height)
            
            /*
             * 粒子效果
             * 1.新建一个SKNODE => trailNode
             * 2.新建粒子效果SKEmitterNode,设置tragetNode = trailNode
             * 3.子弹加上emitterNode
             */
            let trailNode = SKNode()
            trailNode.zPosition = 1
            trailNode.name = "trail"
            addChild(trailNode)
            
            let emitterNode = SKEmitterNode(fileNamed: "ShootTrailBlue")! // particles文件夹存放粒子效果
            emitterNode.targetNode = trailNode  /// 设置粒子效果的目标为trailNode => 跟随新建的trailNode
            bulletNode.addChild(emitterNode)    /// 在子弹节点Node加上粒子效果;
            
            bulletNode.run(SKAction.sequence([
                SKAction.move(to: moveTo, duration: TimeInterval(0.5)),
                SKAction.run({
                    bulletNode.removeFromParent() /// 移除 子弹bulltedNode
                    trailNode.removeFromParent()  /// 移除 trailNode
                })]))
        }
    

    我们将在第二章节学习飞船子弹的发射以及粒子效果的知识

    四、到此,此章节就接近尾声了

    我们已经学会了很多技能,包括如何新建工程,如何建立Sprite精灵节点,还有如何应用SpriteKit Physics物理引擎。你可以在此下载此章节的工程完整代码。(http://www.iFIERO.com/uploads/SpaceBattle-01final.zip)

    五、更多内容

    在下一章节当中,(二)宇宙大战 Space Battle — 创建无限循环的背景Endless、监测精灵之间的物体碰撞及物理引擎Accleroation,我们将学习如何监测SpriteKit Physics物理之间碰撞,如何销毁对象,如何监测屏幕Scene的点击事件以及物理引擎Accleroation的相关知识。

    请注意,此《宇宙大战 Space Battle》教程共分为三个章节系列:

    (一)宇宙大战 Space Battle — 新建场景Scene、导入各个SpriteNode精灵、Particle粒子节点及建立背景音乐(你正在此处进行学习)

    (二)宇宙大战 Space Battle — 创建无限循环的背景Endless、监测精灵之间的物体碰撞及物理引擎Accleroation

    (三)宇宙大战 Space Battle — 各个场景SCENE之间的切换、利用UserDefaults统计分数

    更多游戏教学:iFIERO.COM -- 开源手机游戏教程网,让手机游戏开发变得简单!

     
    iFiero.com
  • 相关阅读:
    android中的Cursor类
    设计模式之UML类图的常见关系(一)
    关于form 上传文件时的小问题
    Javascript中最常用的61段经典代码
    自我提升mysql
    为mysql在表的某一位置增加一列
    由<a>标签的onclick影出来的问题
    第一博客
    异常的出理
    2016.7.17内部类
  • 原文地址:https://www.cnblogs.com/apiapia/p/9253909.html
Copyright © 2020-2023  润新知