Preferred Area

Once the Denizens populate the beach and dot the landscape, they'll undoubtedly need something to do. It would seem that the most obvious behavior a beachgoer might exhibit is that of moving about; they'd wander here and there, up and down the shoreline, and in some further, more exciting, but as-of-yet undetermined way, interact with the environment.

At its most basic, the interaction model of "meandering about" would be: within some discrete area, pick a destination, move there (at some speed) and upon arrival, generate a new destination. Repeat as necessary.


The Model.

Denizen's will need to have some ways to check and set their destination(s).

class Denizen: SKNode {
  var currentPosition: CGPoint!
  var targetPosition: CGPoint!

They'll also need some visual representation of themselves and some knowledge of the update loop.

var sprite: SKSpriteNode!
var lastUpdateTime: NSDate!

A new targetPosition can be composed of random X and Y coordinates within whatever range we'd like.

  func randomInRange(min: CGFloat, max: CGFloat) -> CGFloat {
    return round(CGFloat(arc4random()) / CGFloat(UINT32_MAX) * abs(min - max) + min)
  }

And then the targetPosition updated:

let newX = randomInRange(100, max: 500)
let newY = randomInRange(200, max: 300)
targetPosition = CGPoint(x: newX, y: newY)
self.runAction(SKAction.moveTo(targetPosition, duration: 0.5))

But...

They should also know where in the scene they're allowed to set as a destination. If the range of X and Y values were simply the size of the scene, there would be denizens floating in the sky.

var preferredArea: CGRect!

The Areas.

We'll need to section off the scene into CGRect's which can then be used by the the instances of Denizen to find a new targetPosition.

struct PreferredArea {
  enum Name {
    case NearShore
    case Garden
    case ByTheBar
  }
  var x: CGFloat
  var y: CGFloat
  var width: CGFloat
  var height: CGFloat
  
   init(area: Name) {
    switch area {
    case .NearShore:
      self.x = 100
      self.y = 100
      self.width = 400
      self.height = 100
    case .Garden:
      self.x = 200
      self.y = 300
      self.width = 100
      self.height = 300
    case .ByTheBar:
      self.x = 30
      self.y = 50
      self.width = 300
      self.height = 600
    }
}

These are arbitrary values and names, but hopefully, one gets the idea.


The Class.

Now the Denizen's .preferredArea can be set when initializing a new instance.

  init(pref: PreferredArea) {
    self.preferredArea = CGRect(x: pref.x, y: pref.y, width: pref.width, height: pref.height)
    self.sprite = SKSpriteNode(color: UIColor.redColor(), size: CGSize(width: 50, height: 50))
    super.init()
    self.addChild(sprite)
  }

When a new destination is needed, the calculations to acquire one look like this: (This is extremely verbose for explanatory purposes)

  private func newTargetPosition() -> CGPoint {
    let x = preferredArea.minX
    let width: CGFloat = preferredArea.width
    let y = preferredArea.minY
    let height: CGFloat = preferredArea.height
    let maxX = x + width
    let maxY = y + height
    let newX = randomInRange(x, max: maxX)
    let newY = randomInRange(y, max: maxY)
    return CGPoint(x: newX, y: newY)
  }
func update() {
    let now = NSDate()
    if lastUpdateTime == nil {
      lastUpdateTime = now
    }
    let timeSinceUpdate = now.timeIntervalSinceDate(lastUpdateTime)
    if timeSinceUpdate >= 1.0 {
      // get a new place to walk to based off of the preferred area.
      currentPosition = self.position
      if currentPosition == targetPosition {
        targetPosition = newTargetPosition()
        self.runAction(SKAction.moveTo(targetPosition, duration: 3.5))
        println(targetPosition)
      }
      lastUpdateTime = now
    }
  }

Overflow.

The white box below has the dimensions of the .preferredArea which was used in the instantiation of the Denizen.

Since the red square (the SKSpriteNode which represents the Denizen at this moment) has the default .anchorPoint of CGPoint(x: 0.5, y: 0.5), there will exist the possibility that some new targetPosition would be created with X and Y values which place it outside of the PreferredArea.

So, we'll have to keep that in mind when creating the individual preferred areas' dimensions, the size of the sprite which will eventually inhabit them must be accounted for.

self.sprite = SKSpriteNode(color: UIColor.redColor(), size: CGSize(width: 50, height: 50))

As the red square is 50x50 pts and the anchorPoint is at the center, the overflow would be 25 pts on all sides. Here it is with the 25 pts overlaid in yellow.

Animation.

Instead of using a red square, here's Albatross from Rolling Thunder.