jjzjj

ios - 以一定速度在屏幕上随机移动物体

coder 2023-09-14 原文

我需要让我的对象在屏幕上随机移动。当它们移动时,该对象正在寻找一定半径范围内的另一个对象。

我找到了下面的链接,并实现了 getDuration 函数。但是我遇到了与主题所有者相同的故障。我可以看到应该可以通过删除运行操作来修复。

Moving an object across the screen at a certain speed.(Sprite Kit)

视频:https://www.youtube.com/watch?v=jHE5RC-mvwU

但是我现在已经试了好几种方案了,还是不行。当我停止 Action 时,我的对象就会停止移动。

有人可以告诉我,在哪里终止我的 Action ,在 moveToWaypoint 中创建?我需要我的对象移动到一个随机的路径点,但如果一个特定的对象进入半径内,那么它应该为壁橱对象设置一个新的路径点并再次开始操作。

代码:

///Called in update method
func move(scene: GameplayScene) {
    checkForWaypoint()
    checkForFood(scene: scene)
    moveToWaypoint()
}

///Creates a new waypoint
func createWaypoint() {
     waypoint = CGPoint(x: randomBetweenNumbers(firstNum: minX, secondNum: maxX), y: randomBetweenNumbers(firstNum: minX, secondNum: maxX))
}

override func checkForFood(scene: GameplayScene) {
    var closesObject: SKNode? = nil

    scene.enumerateChildNodes(withName: "Food") {
        node, _ in
        let distance = node.position.distanceFromCGPoint(point: self.position)

        if distance < self.FOOD_RANGE  {
            closesObject = node
        }
    }

    if hungryState == HungryState.hungry {
        if closesObject != nil {
            waypoint = closesObject?.position
            moveState = MoveState.movingToFood
        } else {
            if moveState == MoveState.movingToFood {
                createWaypoint()
            }
        }
    }
}

///Moves the object to the waypoint
func moveToWaypoint () {
    let action = SKAction.move(to: waypoint!, duration: getDuration(pointA: position, pointB: waypoint!, speed: 25))
    self.run(action)
}

///Calcuate a speed between to cordinates
private func getDuration(pointA:CGPoint,pointB:CGPoint,speed:CGFloat)-> TimeInterval {
    let xDist = (pointB.x - pointA.x)
    let yDist = (pointB.y - pointA.y)
    let distance = sqrt((xDist * xDist) + (yDist * yDist));
    let duration : TimeInterval = TimeInterval(distance/speed)
    return duration
}

编辑:

Gamescene 类的更新函数

override func update(_ currentTime: TimeInterval) {
    moveFish()
}

private func moveFish() {
    for node in self.children {
        if node.name != nil {
            switch node.name {
            case "Fish"?:
                let fishToMove = node as! Fish

                fishToMove.move(scene: self)

            default:
                break
            }
        }
    }
}

最佳答案

您在这里尝试解决的问题可以通过两种方式完成。首先是使用物理学,其次当然是没有它。我决定不学习物理,因为这显然是您的做法。

简而言之,这些示例中的鱼在周围没有食物时使用 SKAction 移动。当食物在范围内时,鱼会使用 update: 方法移动到它们的目标。当鱼吃掉它的发现物时,它会继续使用 SKAction 移动。

此外,在这之前,这里有一些我从 Stackoverflow 借来的有用的扩展,您将来可能会发现它们很有用:

import SpriteKit
import GameplayKit

//Extension borrowed from here : https://stackoverflow.com/a/40810305
extension ClosedRange where Bound : FloatingPoint {
    public func random() -> Bound {
        let range = self.upperBound - self.lowerBound
        let randomValue = (Bound(arc4random_uniform(UINT32_MAX)) / Bound(UINT32_MAX)) * range + self.lowerBound
        return randomValue
    }
}
//Extension borrowed from here : https://stackoverflow.com/a/37760551
extension CGRect {
    func randomPoint() -> CGPoint {
        let origin = self.origin
        return CGPoint(x:CGFloat(arc4random_uniform(UInt32(self.width))) + origin.x,
                       y:CGFloat(arc4random_uniform(UInt32(self.height))) + origin.y)
    }
}

//Extension borrowed from here:  https://stackoverflow.com/a/33292919

extension CGPoint {
    func distance(point: CGPoint) -> CGFloat {
        return abs(CGFloat(hypotf(Float(point.x - x), Float(point.y - y))))
    }
}

现在有一个像您这样的 Fish 类,它只有很少的方法和一个物理体,仅用于检测食物和鱼之间的接触,但那都是来自物理学的。这是 Collider 结构,以防您想知道我是如何定义它的:

struct Collider{
            static let food : UInt32 = 0x1 << 0
            static let fish : UInt32 = 0x1 << 1
            static let wall : UInt32 = 0x1 << 2
        }

现在回到 Fish 类...我已经在代码中添加了注释,所以我想不需要解释这些方法的作用。这是代码:

class Fish:SKSpriteNode{
    private let kMovingAroundKey = "movingAround"
    private let kFishSpeed:CGFloat = 4.5
    private var swimmingSpeed:CGFloat = 100.0
    private let sensorRadius:CGFloat = 100.0
    private weak var food:SKSpriteNode! = nil //the food node that this fish currently chase

    override init(texture: SKTexture?, color: UIColor, size: CGSize) {
        super.init(texture: texture, color: color, size: size)

        physicsBody = SKPhysicsBody(rectangleOf: size)
        physicsBody?.affectedByGravity = false
        physicsBody?.categoryBitMask = Collider.fish
        physicsBody?.contactTestBitMask = Collider.food
        physicsBody?.collisionBitMask = 0x0 //No collisions with fish, only contact detection
        name = "fish"

        let sensor = SKShapeNode(circleOfRadius: 100)
        sensor.fillColor = .red
        sensor.zPosition = -1
        sensor.alpha = 0.1
        addChild(sensor)
    }

    func getDistanceFromFood()->CGFloat? {


        if let food = self.food {

            return self.position.distance(point: food.position)
        }
        return nil

    }

    func lock(food:SKSpriteNode){

        //We are chasing a food node at the moment
        if let currentDistanceFromFood = self.getDistanceFromFood() {

            if (currentDistanceFromFood > self.position.distance(point: food.position)){
                //chase the closer food node
                 self.food = food
                self.stopMovingAround()
            }//else, continue chasing the last locked food node

        //We are not chasing the food node at the moment
        }else{
             //go and chase then
             if food.position.distance(point: self.position) <= self.sensorRadius {

                self.food = food
                self.stopMovingAround()
            }
        }
    }

    //Helper method. Not used currently. You can use this method to prevent chasing another (say closer) food while already chasing one
    func isChasing(food:SKSpriteNode)->Bool{

        if self.food != nil {

            if self.food == food {
                return true
            }
        }

        return false
    }

    func stopMovingAround(){

        if self.action(forKey: kMovingAroundKey) != nil{
           removeAction(forKey: kMovingAroundKey)
        }
    }


    //MARK: Chasing the food
    //This method is called many times in a second
    func chase(within rect:CGRect){

        guard let food = self.food else {

            if action(forKey: kMovingAroundKey) == nil {
                self.moveAround(within: rect)
            }
            return
        }

        //Check if food is in the water
        if rect.contains(food.frame.origin) {

            //Take a detailed look in my Stackoverflow answer of how chasing works : https://stackoverflow.com/a/36235426

            let dx = food.position.x - self.position.x
            let dy = food.position.y - self.position.y

            let angle = atan2(dy, dx)

            let vx = cos(angle) * kFishSpeed
            let vy = sin(angle) * kFishSpeed

            position.x += vx
            position.y += vy

        }
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func moveAround(within rect:CGRect){

        if scene != nil {

            //Go randomly around the screen within view bounds
            let point = rect.randomPoint()

            //Formula: time = distance / speed
            let duration = TimeInterval(point.distance(point: position) / self.swimmingSpeed)
            let move = SKAction.move(to: point, duration: duration)
            let block = SKAction.run {
                [unowned self] in

                self.moveAround(within: rect)
            }
            let loop = SKAction.sequence([move,block])

            run(loop, withKey: kMovingAroundKey)
        }
    }
}

所以基本上,有一些方法可以在鱼不追逐食物的时候四处移动。还有一种方法可以停止这个(无限) Action (SKAction)。最重要的方法是 chase(within rect:) 方法。该方法在场景的 update() 方法中调用,并定义鱼将如何以及何时(尝试)追逐食物。

现在是 GameScene:

//MARK: GameScene
class GameScene: SKScene, SKPhysicsContactDelegate {

    private var nodesForRemoval:[SKNode] = []
    private var water = SKSpriteNode()

    override func didMove(to view: SKView) {

        physicsWorld.contactDelegate = self
        physicsWorld.gravity = CGVector(dx: 0.0, dy: -0.5)
        physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
        physicsBody?.categoryBitMask = Collider.wall
        physicsBody?.contactTestBitMask = 0x0
        physicsBody?.collisionBitMask = Collider.fish | Collider.food
        self.backgroundColor = .white

        //Water setup
        water = SKSpriteNode(color: .blue, size: CGSize(width: frame.width, height: frame.height - 150))
        water.position = CGPoint(x: 0, y: -75)
        water.alpha = 0.3
        addChild(water)
        water.zPosition = 4

        //Fish one
        let fish = Fish(texture: nil, color: .black, size:CGSize(width: 20, height: 20))
        addChild(fish)
        fish.position = CGPoint(x: frame.midX-50, y: frame.minY + 100)
        fish.zPosition = 5

        fish.moveAround(within: water.frame)

        //Fish two
        let fish2 = Fish(texture: nil, color: .black, size:CGSize(width: 20, height: 20))
        addChild(fish2)
        fish2.position = CGPoint(x: frame.midX+50, y: frame.minY + 100)
        fish2.zPosition = 5

        fish2.moveAround(within: water.frame)

    }


    func feed(at position:CGPoint, with food:SKSpriteNode){

        food.position = CGPoint(x: position.x, y: frame.size.height/2 - food.frame.size.height)
        addChild(food)
    }

    //MARK: Food factory :)
    func getFood()->SKSpriteNode{

        let food = SKSpriteNode(color: .purple, size: CGSize(width: 10, height: 10))

        food.physicsBody = SKPhysicsBody(rectangleOf: food.frame.size)
        food.physicsBody?.affectedByGravity = true
        food.physicsBody?.categoryBitMask = Collider.food
        food.physicsBody?.contactTestBitMask =  Collider.fish
        food.physicsBody?.collisionBitMask = Collider.wall
        food.physicsBody?.linearDamping = (0.1 ... 0.95).random()
        food.name = "food"
        return food
    }

    //MARK: Feeding
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

        if let touch = touches.first {

            let location = touch.location(in: self)

            let food = getFood()
            feed(at: location, with: food)
        }
    }

    //MARK: Eating
    func didBegin(_ contact: SKPhysicsContact) {


        guard let nodeA = contact.bodyA.node, let nodeB = contact.bodyB.node else {

            //Silliness like removing a node from a node tree before physics simulation is done will trigger this error
            fatalError("Physics body without its node detected!")
        }

        let mask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask

        switch mask {

           //Contact between fish and a food
          case Collider.fish | Collider.food:

            if let food = (contact.bodyA.categoryBitMask == Collider.food ? nodeA : nodeB) as? SKSpriteNode
            {
                self.nodesForRemoval.append(food)
            }

        default:
            //some unknown contact occurred
            break
        }
    }

    //MARK: Removing unneeded nodes
    override func didSimulatePhysics() {

        for node in self.nodesForRemoval {
            node.removeFromParent()
        }
        self.nodesForRemoval.removeAll()
    }

    //MARK: Chasing the food
    override func update(_ currentTime: TimeInterval) {

        self.enumerateChildNodes(withName: "fish") {
            [unowned self] node, stop in

            if let fish = node as? Fish {

                self.enumerateChildNodes(withName: "food") {
                    node, stop in

                    fish.lock(food: node as! SKSpriteNode)
                }

                fish.chase(within: self.water.frame)
            }
        }
    }
}

就是这样。在这里,我们设置我们的节点,解决接触检测并告诉哪些鱼应该追逐哪个食物节点。我留下了评论,所以一切都在那里。我希望这些方法几乎是不言自明的,但是您当然可以向我询问有关其工作原理的详细信息。

这里是一个简短的视频,展示了它是如何工作的:

和更长的版本,因为我只能上传两个 2 兆字节:screen recording

基本上,如果食物节点不在它定义的范围内,鱼是不会追逐它的。尽管如此,鱼还是会追逐锁定的节点,直到它吃掉为止。但如果附近有其他食物,鱼就会追逐那个食物节点。当然这不是必须的,您可以根据需要调整它(检查 isChasing:)方法。

关于ios - 以一定速度在屏幕上随机移动物体,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44176202/

有关ios - 以一定速度在屏幕上随机移动物体的更多相关文章

  1. ruby - 多次弹出/移动 ruby​​ 数组 - 2

    我的代码目前看起来像这样numbers=[1,2,3,4,5]defpop_threepop=[]3.times{pop有没有办法在一行中完成pop_three方法中的内容?我基本上想做类似numbers.slice(0,3)的事情,但要删除切片中的数组项。嗯...嗯,我想我刚刚意识到我可以试试slice! 最佳答案 是numbers.pop(3)或者numbers.shift(3)如果你想要另一边。 关于ruby-多次弹出/移动ruby​​数组,我们在StackOverflow上找到一

  2. ruby - 如何验证 IO.copy_stream 是否成功 - 2

    这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

  3. ruby - 匹配大写字母并用后续字母填充,直到一定的字符串长度 - 2

    我有一个驼峰式字符串,例如:JustAString。我想按照以下规则形成长度为4的字符串:抓取所有大写字母;如果超过4个大写字母,只保留前4个;如果少于4个大写字母,则将最后大写字母后的字母大写并添加字母,直到长度变为4。以下是可能发生的3种情况:ThisIsMyString将产生TIMS(大写字母);ThisIsOneVeryLongString将产生TIOV(前4个大写字母);MyString将生成MSTR(大写字母+tr大写)。我设法用这个片段解决了前两种情况:str.scan(/[A-Z]/).first(4).join但是,我不太确定如何最好地修改上面的代码片段以处理最后一种

  4. ruby-on-rails - 如何重命名或移动 Rails 的 README_FOR_APP - 2

    当我在我的Rails应用程序根目录中运行rakedoc:app时,API文档是使用/doc/README_FOR_APP作为主页生成的。我想向该文件添加.rdoc扩展名,以便它在GitHub上正确呈现。更好的是,我想将它移动到应用程序根目录(/README.rdoc)。有没有办法通过修改包含的rake/rdoctask任务在我的Rakefile中执行此操作?是否有某个地方可以查找可以修改的主页文件的名称?还是我必须编写一个新的Rake任务?额外的问题:Rails应用程序的两个单独文件/README和/doc/README_FOR_APP背后的逻辑是什么?为什么不只有一个?

  5. Ruby 文件 IO 定界符? - 2

    我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的

  6. 屏幕录制为什么没声音?检查这2项,轻松解决 - 2

    相信很多人在录制视频的时候都会遇到各种各样的问题,比如录制的视频没有声音。屏幕录制为什么没声音?今天小编就和大家分享一下如何录制音画同步视频的具体操作方法。如果你有录制的视频没有声音,你可以试试这个方法。 一、检查是否打开电脑系统声音相信很多小伙伴在录制视频后会发现录制的视频没有声音,屏幕录制为什么没声音?如果当时没有打开音频录制,则录制好的视频是没有声音的。因此,建议在录制前进行检查。屏幕上没有声音,很可能是因为你的电脑系统的声音被禁止了。您只需打开电脑系统的声音,即可录制音频和图画同步视频。操作方法:步骤1:点击电脑屏幕右下侧的“小喇叭”图案,在上方的选项中,选择“声音”。 步骤2:在“声

  7. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  8. [Vuforia]二.3D物体识别 - 2

    之前说过10之后的版本没有3dScan了,所以还是9.8的版本或者之前更早的版本。 3d物体扫描需要先下载扫描的APK进行扫面。首先要在手机上装一个扫描程序,扫描现实中的三维物体,然后上传高通官网,在下载成UnityPackage类型让Unity能够使用这个扫描程序可以从高通官网上进行下载,是一个安卓程序。点到Tools往下滑,找到VuforiaObjectScanner下载后解压数据线连接手机,将apk文件拷入手机安装然后刚才解压文件中的Media文件夹打开,两个PDF图打印第一张A4-ObjectScanningTarget.pdf,主要是用来辅助扫描的。好了,接下来就是扫描三维物体。将瓶

  9. Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting - 2

    1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里

  10. ruby-on-rails - rbenv:从 RVM 移动到 rbenv 后,在 Jenkins 执行 shell 中找不到命令 - 2

    我从Ubuntu服务器上的RVM转移到rbenv。当我使用RVM时,使用bundle没有问题。转移到rbenv后,我在Jenkins的执行shell中收到“找不到命令”错误。我内爆并删除了RVM,并从~/.bashrc'中删除了所有与RVM相关的行。使用后我仍然收到此错误:rvmimploderm~/.rvm-rfrm~/.rvmrcgeminstallbundlerecho'exportPATH="$HOME/.rbenv/bin:$PATH"'>>~/.bashrcecho'eval"$(rbenvinit-)"'>>~/.bashrc.~/.bashrcrbenvversions

随机推荐