SpriteKit可摧毁物理场景的进一步完善

例子举了一个早期DOS下的双人游戏,类似于百战天虫类型。不过有趣的是游戏中实现了可摧毁的物理场景,而且只用了很少的代码:

游戏实现起来十分巧妙和简单,利用了CoreGraphic中的clear混合模式,将香蕉炸弹以中心位置的纹理全部消除,从而实现“摧毁”效果。

游戏中为建筑物单独创建一个类,继承于SKSpriteNode,其中有一个currentImage用来存放当前楼体的纹理:

class BuildingNode: SKSpriteNode {
    var currentImage:UIImage!
}

当香蕉炸弹触碰楼体时,我们根据实际接触的中心点制作出爆炸摧毁效果:

func hitAt(point:CGPoint){
        let convertedPoint = CGPoint(x: point.x + size.width/2.0, y: abs(point.y - (size.height/2.0)))

        UIGraphicsBeginImageContextWithOptions(size, false, 0)
        let ctx = UIGraphicsGetCurrentContext()

        currentImage.draw(at: CGPoint(x: 0, y: 0))
        ctx?.addEllipse(in: CGRect(x: convertedPoint.x - 32, y: convertedPoint.y - 32, width: 64, height: 64))
        ctx?.setBlendMode(.clear)
        ctx?.drawPath(using: .fill)

        let img = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()

        texture = SKTexture(image: img)
        currentImage = img

        configurePhysics()
    }

最后一句代码configurePhysics用来重建楼体的物理像素精确物理外观,实现纹理和物理边界相符。

虽然作者的构思很棒,不过游戏有一个小小的不足,就是当香蕉炸弹触碰到多于一个楼体的时候,炸弹只会消除第一个碰到的楼体外观,这显得不够真实。

所以本猫在这里就带领大家修复这个bug ;)

我们有两种思路,一是当香蕉碰到楼体时不立即将其销毁,而是给它一定耐久度,只有当耐久度为0时才把它销毁。另外一种思路是遍历所有楼体查找香蕉炸弹爆炸时半径涉及的楼体,然后依次重绘。两种方法都很简单,我们依次来看看。

增加香蕉炸弹耐久度

在创建香蕉时加入如下代码:

banana = SKSpriteNode(imageNamed: "banana")
banana.name = "banana"
banana.physicsBody = SKPhysicsBody(circleOfRadius: banana.size.width/2)
banana.physicsBody!.categoryBitMask = CollisionTypes.banana.rawValue
banana.physicsBody!.collisionBitMask = CollisionTypes.building.rawValue|CollisionTypes.player.rawValue
banana.physicsBody!.contactTestBitMask = banana.physicsBody!.collisionBitMask
banana.physicsBody!.usesPreciseCollisionDetection = true
addChild(banana)
//新加如下代码
banana.userData = ["persistence":2]

在香蕉接触到楼体的方法中加上耐久度处理的代码:

var persistence = (banana.userData as! [String:Int])["persistence"]!
persistence -= 1

banana.userData = ["persistence":persistence]

if persistence == 0{

    if timer != nil{
        timer.invalidate()
        timer = nil
    }

    banana.name = ""
    banana.removeFromParent()
    banana = nil

    changePlayer()
}

运行App试试看,效果还不错,不过有个问题,当你大力出奇迹甩出香蕉时可能啥楼体都碰不到,直接甩到天涯海角去了,这是你的游戏就会傻傻的萌呆在那里,啥都做不了啊。

所以我们要再加一个超时判断:甩出香蕉后5秒若是无事发生就强制销毁炸弹并且切换用户控制,在GameScene中添加一个属性:

var timer:Timer!

在发射出香蕉的launch方法中加上相关逻辑:

timer = Timer(fire: Date().addingTimeInterval(5.0), interval: 5.0, repeats: false){[unowned self] _ in

    DispatchQueue.main.async {
        print("timer done!")
        self.timer.invalidate()
        self.timer = nil

        self.banana.name = ""
        self.banana.removeFromParent()
        self.banana = nil

        self.changePlayer()
    }
}

运行App,将香蕉甩到天边看看!咦!怎么过了许久也没反应啊!原来当前消息环并不是空闲的,它会被SpriteKit引擎所占用,你的定时器会无限挂起,所以你必须告诉消息环,爷可不是好惹的:

RunLoop.current.add(timer, forMode: .commonModes)

遍历所有爆炸半径内的楼体

我们现在来看看第二种思路,首先我们需要确定炸弹爆炸半径,为了简单其实我取的是爆炸矩形而不是圆形:

let rect = CGRect(x: point.x - 32, y: point.y - 32, width: 64, height: 64)

因为SpriteKit默认Node位置在中心,所以你必须将其转换为UIKit的坐标。

我们新建一个checkHitBuildingAt方法,该方法依次取出场景中所有楼房,计算是否处在爆炸半径内,如果是则将其摧毁 ;)

func checkHitBuildingsAt(_ point:CGPoint){
   let rect = CGRect(x: point.x - 32, y: point.y - 32, width: 64, height: 64)
   for building in buildings{
       if building.frame.intersects(rect){
           print("Find hit building : \(building.frame)")
           let buildingLocation = convert(point, to: building)
           building.hitAt(point: buildingLocation)
       }
   }
}

修改香蕉炸弹与楼房触碰的代码如下:

func bananaHitBuilding(_ building:BuildingNode,at point:CGPoint){

        checkHitBuildingsAt(point)
}

运行游戏看看,是不是很有成就感呢?

时间: 2024-12-22 22:16:15

SpriteKit可摧毁物理场景的进一步完善的相关文章

02.SpriteKit前瞻之视图场景

SpriteKit前瞻之视图场景 绘制你的"世界"--视图中呈现场景 强调两个单词以及框架说明: 视图:View 场景:Scene 框架:Sprite Kit 简称SK,框架中的视图,就叫做SKView;场景,叫做SKScene等,SK作为前缀被方便记忆. 本文只是前瞻,因此你只需要报着欣赏的态度去阅读即可,详细内容会在后文给出. 记住:动画和渲染都由SKView对象执行.拿出你的iPhone或者iPad,屏幕就好比一个窗口(Window),我们将视图(View)放入进行内容渲染.作为

进一步完善之后的一元N次方程求导算法

祝大家节日快乐.......写代码就是过节.... package com.system.Tools; /** * 这个类,实现对函数的求导算法 * 最大目标  实现对任意多元函数的偏导数和全导数的求导算法 * 最小目标  实现对一元N次函数的求导算法 *  * @author Administrator */public class SystemMathTools { /* *  还不是很完善,需要进一步修改...     *      *  by comsci 2019.2.4 经过进一步的

元素和结点的区别(待进一步完善)

1.区别介绍 Element是Node的扩展,所以也更实用一些. 例如,用Element可以方便的获得Node的属性getAttribute(String attrName),如果用Node,可以得到一个属性集,还要进一步检索才可得到想要的属性. 一个结点不一定是一个元素,而一个元素一定是一个结点.Element是Node的子集,XmlNode表示一个节点,包括XmlElement(元素)和XmlAttribute(属性)等.如: <Alarm lock="true"> /

第三次作业---四则运算的进一步完善

这次作业的要求是让对上次作业的四则运算进一步改善,不能在减法中出现负数,不能出现除不尽的情况. 就像这种情况,不知道怎么规定一个数值必须是正整数,所以我们就采用最笨的方法来做,定义一个f,其值为e,d的倍数. 而d做为除数,f做为被除数,所以无论如何f都是d的e倍,而我们定义的e的取值范围又为(1,10)之间的正整数,所以不管怎么随机,f都是d的正整数倍数,不会存在有余数的情况. using System;using System.Collections.Generic;using System

等价类测试——进一步完善的Web输入合法验证

问题描述: 在等价类测试——Web开发中对于用户名输入的合法性验证(http://www.cnblogs.com/iProg/p/4356599.html)的基础上进行的进一步拓展,增加两个输入框,使三个输入同时进行等价类判断,只有当三个输入全部有效时才可判定成功,若存在某个输入或者多个输入非法,则判定失败,为了完成该目标,测试用例要同时考虑到三个输入的情况. 等价类划分: 有效等价类 无效等价类 length:1-6 length: ..-0&&7-..     char:  a-z&a

SpriteKit:检测当新场景显示以后

Detecting When a New Scene Is Presented Sprite Kit在SKScene类中提供2个可以重载的方法用来检测当一个场景过渡出去或过渡进来的时候. 第一个方法是SKScene的willMoveFromView()方法,该方法在一个SKScene被view删除时调用,为了重载该方法,你可以添加如下代码到你的SKScene实现中去: override func willMoveFromView(view: SKView) { // insert code }

阿里巴巴宣布 Sentinel 开源,进一步完善 Dubbo 生态

摘要: 1.当服务量大到一定程度,流量扛不住的时候,该如何处理? 2.应用之间相互依赖,当应用A出现响应时间过长,影响到应用B的响应,进而产生连锁反应影响整个依赖链上的所有应用,该如何处理? 随着微服务的流行,服务和服务之间的稳定性变得越来越重要.Sentinel以流量为切入点,从流量控制.熔断降级.系统负载保护等多个维度保护服务的稳定性.Sentinel 作为阿里巴巴"大中台.小前台"架构中的基础模块,覆盖了阿里的所有核心场景,也因此积累了大量的流量归整场景以及生产实践. 在近期的A

一个简单线程池的实现---需进一步完善

1.定义一个任务结构体,和一个线程池结构体 struct task{ void *(*p)(void*);//需要实现的函数: void *arg;//函数所带的参数 struct task *next;};struct pthread_pool{ pthread_mutex_t mutex;//线程锁 pthread_cond_t cond;//条件变量 pthread_t *tids;//线程id int thread_nums;//需创建的线程的数量 struct task *head;任

nodejs TLS 只加密,未授权,进一步完善

const tls = require('tls'); const fs = require('fs'); const options = { key: fs.readFileSync('my_key.pem'), cert: fs.readFileSync('my_cert.pem'), // This is necessary only if using the client certificate authentication. requestCert: false, // This is