Randomizing Node Movement Duration

Randomizing node movement duration

When you run actions like from your example and randomize duration parameter with something like arc4Random this is actually happening:

  • Random duration is set and stored in action.
  • Then action is reused in a sequence with a given duration.

Because the action is reused as it is, duration parameter remains the same over time and moving speed is not randomized.

One way to solve this (which I prefer personally) would be to create a "recursive action", or it is better to say, to create a method to run desired sequence and to call it recursively like this :

import SpriteKit

class GameScene: SKScene {

let shape = SKSpriteNode(color: UIColor.redColor(), size: CGSize(width: 20, height: 20))

override func didMoveToView(view: SKView) {

shape.position = CGPointMake(CGRectGetMidX(self.frame) , CGRectGetMidY(self.frame)+60 )

self.addChild(shape)

move()
}

func randomNumber() ->UInt32{

var time = arc4random_uniform(3) + 1
println(time)
return time
}

func move(){

let recursive = SKAction.sequence([

SKAction.moveByX(frame.size.width/2.8, y: 0, duration: NSTimeInterval(randomNumber())),
SKAction.moveByX(-frame.size.width/2.8, y: 0, duration: NSTimeInterval(randomNumber())),
SKAction.runBlock({self.move()})])

shape.runAction(recursive, withKey: "move")
}

}

To stop the action, you remove its key ("move").

JavaFX node random movement

You can use the PathTransition's setOnFinished method and add a new path in there and play the path transition again.

I set the cycle count to 2. Cycle 1 is one direction, but since you have autoreverse enabled, cycle 2 is the direction back to the origin.

When that is finished, a new path is set and the transition is played again.

import javafx.animation.PathTransition;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class Java2 extends Application {

public static final int PANEL_WIDTH = 600;
public static final int PANEL_HEIGHT = 600;

Random ran = new Random();

@Override
public void start(Stage primaryStage) {

Rectangle rekt = new Rectangle(20, 20);

Pane root = new Pane();

root.getChildren().add(rekt);

Scene scene = new Scene(root, PANEL_WIDTH, PANEL_HEIGHT);

PathTransition pathTransition = new PathTransition();

pathTransition.setDuration(javafx.util.Duration.millis(500));
pathTransition.setPath(createPath());
pathTransition.setNode(rekt);
pathTransition.setOrientation(PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);
pathTransition.setCycleCount(2);
pathTransition.setAutoReverse(true);
pathTransition.setOnFinished(e -> {

pathTransition.setPath(createPath());
pathTransition.play();

});
pathTransition.play();

primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}

private Path createPath() {

int loc = ran.nextInt(600 - 300 + 1) + 300; // min=300 , max=600

Path path = new Path();

path.getElements().add(new MoveTo(20, 20));
path.getElements().add(new LineTo(loc, 600));

return path;

}

public static void main(String[] args) {
launch(args);
}

}

How to randomize (shuffle) a JavaScript array?

The de-facto unbiased shuffle algorithm is the Fisher-Yates (aka Knuth) Shuffle.

You can see a great visualization here (and the original post linked to this)

function shuffle(array) {
let currentIndex = array.length, randomIndex;

// While there remain elements to shuffle.
while (currentIndex != 0) {

// Pick a remaining element.
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;

// And swap it with the current element.
[array[currentIndex], array[randomIndex]] = [
array[randomIndex], array[currentIndex]];
}

return array;
}

// Used like so
var arr = [2, 11, 37, 42];
shuffle(arr);
console.log(arr);

Is it Possible to Randomize an Animation in CSS?

You can use different animation-delay and animation-duration values for each button like below:

/**

* Icon

*/

.icon {

position: relative;

overflow: hidden;

width: 50px;

height: 50px;

display: inline-block;



margin: 25px 0 25px 25px;

border-radius: 5px;

color: #fff;

text-decoration: none;

text-align: center;

line-height: 50px;

font-size: 12px;

font-family: sans-serif;

}

.icon:nth-child(1) { background: cornflowerblue; }

.icon:nth-child(2) { background: salmon; }

.icon:nth-child(3) { background: gray; }

/**

* The "shine" element

*/

.icon:after {



animation: shine 1s ease-in-out alternate infinite;

animation-fill-mode: forwards;

content: "";

position: absolute;

top: -110%;

left: -210%;

width: 200%;

height: 200%;

transform: rotate(30deg);



background: rgba(255, 255, 255, 0.13);

background: linear-gradient(

to right,

rgba(255, 255, 255, 0.13) 0%,

rgba(255, 255, 255, 0.13) 77%,

rgba(255, 255, 255, 0.5) 92%,

rgba(255, 255, 255, 0.0) 100%

);

}

.icon:nth-child(1):after { animation-delay: .1s; }

.icon:nth-child(2):after { animation-delay: .3s; }

.icon:nth-child(3):after { animation-delay: .5s; }

/* Hover state - trigger effect */

/* Active state */

@keyframes shine{

60% {

top: -30%;

left: -30%;

}

100% {

opacity: 0;

top: -30%;

left: -30%;

}

}
<a href="#" class="icon">let</a>

<a href="#" class="icon">it</a>

<a href="#" class="icon">shine</a>

<!--

Forked by:

Nicolas Gallagher - http://jsfiddle.net/KbNq7/

Chris Coyier - http://jsfiddle.net/chriscoyier/hk6z9/1/

-->

SKAction.repeatActionForever only calculates random number on first call

So this is one way (and there are many ways) to the same thing.

What this code does is:

  • create an obstacle
  • move it from the left side, to the right side of the screen
  • remove the obstacle when off-screen
  • randomize obstacle's y coordinate on every obstacle creation

Just copy and paste to see how it works:

    import SpriteKit

class GameScene: SKScene, SKPhysicsContactDelegate {

//MARK: - Sprite kit functionality
override func didMoveToView(view: SKView) {

startSpawning()
}

func startSpawning(){

let wait = SKAction.waitForDuration(1.5)
let block = SKAction.runBlock({[unowned self] in self.spawnObstacle()})
let sequence = SKAction.sequence([wait, block])

runAction(SKAction.repeatActionForever(sequence), withKey: "spawning")
}

func spawnObstacle(){

// 1) create an obstacle
let obstacle = SKSpriteNode(color: .purpleColor(), size: CGSize(width: 50, height: frame.size.height/2.0))

// 2) Place it outside the screen, next to the right edge and randomize the y position each time
let lowerBound:CGFloat = -200 //Calcluate this to match your needs
let upperBound:CGFloat = 200 //Calcluate this to match your needs
let obstacleRandomY = randomBetweenNumbers(lowerBound, secondNum: upperBound)
obstacle.position = CGPoint(x: frame.maxX + obstacle.size.width, y: obstacleRandomY)

// 3) Add it to the scene
addChild(obstacle)

// 4) Move the obstacle from the right side, to the left side of a screen
let move = SKAction.moveToX(-obstacle.calculateAccumulatedFrame().width, duration: 3)

// 5) Remove the obstacle from the scene when off-screen
let moveAndRemove = SKAction.sequence([move, SKAction.removeFromParent()])

obstacle.runAction(moveAndRemove)
}

func randomBetweenNumbers(firstNum: CGFloat, secondNum: CGFloat) -> CGFloat{
return CGFloat(arc4random()) / CGFloat(UINT32_MAX) * abs(firstNum - secondNum) + min(firstNum, secondNum)
}

}

You can add another obstacle (upper pipe) and adjust a bit lowerBound and upperBound variables, so you will have an effect like in Flappy Birds...

How to adjust randomization level javascript

The way to vary the difficulty would be to reduce or increase the number of moves required to solve it.

I think the best way to go about this is to implement the shuffling algorithm as a reverse of the way it will be solved: while shuffling, only pick legal moves (move a piece into an adjacent gap), and repeat this randomly for a certain number of moves until it's sufficiently shuffled.

For easy mode, only do about 5 moves. For hard, do 30 moves. A puzzle that requires 5 moves to solve will be a lot easier.

Moving objects random across the screen with a certain speed

What you are trying to solve here could be done in two ways. First is using physics, and second is without it of course. I have decided to go without physics because that is obviously how you are doing it.

In short, fish in these example move using SKAction while there is no food around. When food is in range, fish are moved to their targets using update: method. When fish eat its found it continues to move around using SKAction.

Also before everything, here are some useful extensions that I have borrowed here from Stackoverflow that you might find useful in the future:

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))))
}
}

Now there is a Fish class like yours, which has few methods and a physics body which is used just to detect contact between food and a fish, but that's all from the physics. Here is the Collider struct just in case that you want to know how I have defined it:

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

Now back to the Fish class...I have put comments in the code, so I guess it is not needed to explain what those methods do. Here is the code:

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)
}
}
}

So basically, there are methods to move the fish around while it is not in a chase for a food. Also there is method which stop this (infinite) action (SKAction). The most important method is the chase(within rect:) method. That method is called in scene's update() method and defines how and when fish will (try to) chase the food.

Now the 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)
}
}
}
}

And that's it. Here we setup our nodes, resolve contact detections and tell which fish should chase which food node. I left the comments, so everything is there. I hope that methods are pretty much self explanatory, but of course you can ask me for details about how things works.

And here is a short video of how this works:

Sample Image

and the longer version because I can only upload two 2 megabytes: screen recording

Basically, fish don't chase the food node if it is not its defined range. Still, fish will chase the locked node until it eats it. But if there is some other food which is more close, the fish will chase for that food node. Of course this is not a must, and you can tweak it as you like (check isChasing:) method.



Related Topics



Leave a reply



Submit