SpriteKit: Coloring the Background - swift

i still try to learn Swift and SpriteKit and i have a new question.
So i am following this Tutorial/Video: Noise Field
My project now looks like this: My Project
I have 100 Particle Objects moving around and i want to blend some color of the particles to the white background. Inside the tutorial it is quiet easy. You just create the Background once, and inside the draw function (in SpriteKit this would be the update() function) you give your objects an alpha value like 0.1.
SpriteKit works quiet different. If i change the alpha value under draw my Particles are now almost hidden, but the color is not being "drawn" on the background.
I know this is because SpriteKit works different then the p5 library for javascript. But i wonder how i could get the same effect inside SpriteKit..
So inside the update function i have 2 loops, one for columns and one for rows. I have x columns and y rows - and for each "cell" i create a random CGVector. Now my Particles are moving around based on the CGVector of the cell which is the nearest to the Particles position.
My Particle Class looks like this:
class Particle: SKShapeNode{
var pos = CGPoint(x: 0, y: 0)
var vel = CGVector(dx: 0, dy: 0)
var acc = CGVector(dx: 0, dy: 0)
var radius:CGFloat
var maxSpeed:CGFloat
var color: SKColor
And i have a function which looks like this to show the Particles:
func show(){
self.position = self.pos
let rect = CGRect(origin: CGPoint(x: 0.5, y: 0.5), size: CGSize(width: self.radius*2, height: self.radius*2))
let bezierP = UIBezierPath(ovalIn: rect)
self.path = bezierP.cgPath
self.fillColor = self.color
self.strokeColor = .clear
}
And in the update function i have this loop:
for particle in partikelArr{
particle.updatePos()
particle.show()
}
How could i now "colorize" the white background or "draw" my particle on the background based on the particles position, color, shape and size?
Thanks and best regards
EDIT:
So i know create for each particle a new SKShapeNode inside the update function, so this works and it looks like the Particles have colorized the background:
This was created like this inside the update function:
for particle in partikelArr{
let newP = SKShapeNode(path: particle.path!)
newP.position = particle.pos
newP.fillColor = particle.farbe
newP.strokeColor = .clear
newP.zPosition = 1000
newP.alpha = 0.1
self.addChild(newP)
}
But i do not want to create SKShapeNodes for each Particle (on the screenshot i have used 5 Particles, after some seconds i already have over 800 nodes on the scene). I would like to let my 100 Particles really "draw" their Shape and Color on the white Background node.

Related

Using particle effects or animated sks files to create filled letters

I have one .png image of a single star. I'd like to use this image to create animated star-filled letters. This is what I'd like to do (but the stars would be sort of animated within this, which I imagine can be done with particle effects):
Could I do this by using potentially several sks files for each letter and then loading them into one larger scene? In addition, if I just wanted to fill the label node with a static texture of several stars, is there an alternate way of doing this?
Not ideal, but an easy to achieve:
override func didMove(to view: SKView) {
if let nodeToMask = SKEmitterNode(fileNamed: "firelfies") {
backgroundColor = .black
let cropNode = SKCropNode()
cropNode.position = CGPoint(x: frame.midX, y: frame.midY)
cropNode.zPosition = 1
let mask = SKLabelNode(fontNamed: "ArialMT")
mask.text = "MASK"
mask.fontColor = .green
mask.fontSize = 185
cropNode.maskNode = mask
nodeToMask.position = CGPoint(x: 0, y: 0)
nodeToMask.name = "character"
cropNode.addChild(nodeToMask)
addChild(cropNode)
}
}
I think the code is self-explanatory, but basically, you just use text as a mask of a crop node, and you mask an emitter. Here is the result:
The thing with this implementation is that sparkles don't go outside of the letter bounds.

SpriteKit: using SKView in UIView instead of initializing a Game project

Completely new to SpriteKit. Currently I have a UIView, and I want to add a sprite node to it (like a small UIImageView, but I want animation for it so using SpriteKit). Therefore I didn't initialize my project to be a game project, as found in almost all of tutorials for SpriteKit. I've found a note here: link and what I have now is sth like:
func initializeImage() {
let imageView = SKView()
imageView.frame = CGRect(x: self.frame.width / 2 - Constants.imageWidth / 2, y: self.frame.height - Constants.imageHeight, width: Constants.imageWidth, height: Constants.imageHeight)
// so place it somewhere in the bottom middle of the whole frame
let sheet = SpriteSheet(texture: ...)
let sprite = SKSpriteNode(texture: sheet.itemFor(column: 0, row: 0))
sprite.position = imageView.center //basically the same position as the imageView.frame's x and y value
let scene = SKScene(size: imageView.frame.size)
scene.backgroundColor = SKColor.clear
scene.addChild(sprite)
imageView.presentScene(scene)
self.frame.addSubview(imageView)
}
The SpriteSheet is similar to this: sprite sheet; it's essentially cutting an image atlas and divide it into smaller images. I tracked the process and this step is indeed giving the smaller image (the var 'sprite'). But if running I only have a black square now (should be the size as defined by Constants). If I set scene.backgroundColor to be white then it's white. May I know how I should proceed from here, as how should I make the sprite showing up?
All of your code looks good except for this:
sprite.position = imageView.center // basically the same position as the imageView.frame's x and y value
That is basically not the position you think it is. The coordinate system in SpriteKit is a) relative to the (SK)scene, not to whatever view the SKView is contained in, and b) flipped vertically relative to the UIKit coordinate system. If you want a sprite centered in the scene, you probably want to set its position based on the scene's size:
sprite.position = CGPoint(x: scene.size.width / 2, y: scene.size.height / 2)
By the way, the external SpriteSheet code might not be needed (and you're more likely to benefit from Apple's optimizations) if you slice up your sprite sheet and put it in an Xcode asset catalog.

How to generate a new node when another one hits a certain y value

I have a circle moving up a line, and when that circle reaches a certain y point, how can I make it so that another node would generate from below?
Here is the code I currently have for populating the circles, but I am not able to use it with a physics body, as it generates too many nodes and slows down my app:
func createCirclesOnLine(line: CGFloat) {
var currentY : CGFloat = -110
let maxY = self.size.width * 15
let spacing : CGFloat = 120
while currentY < maxY {
let circle = SKSpriteNode(imageNamed: "first#2x")
circle.physicsBody?.dynamic = false
circle.position = CGPointMake(line, currentY)
//circle.physicsBody?.restitution = -900
circle.size = CGSizeMake(75, 75)
// circle.physicsBody = SKPhysicsBody(rectangleOfSize: circle.size)
let up = SKAction.moveByX(0, y: 9000, duration: 90)
circle.runAction(up)
foregroundNode.addChild(circle)
currentY += CGFloat((random() % 400) + 70)
}
Will post more code if necessary.
There are two ways you can go about this. One is to simply check every circle's y position to see if it's above the screen. You'll need a reference to the circles so...
class GameScene: SKScene {
var circles = Array<SKSpriteNode>()
...
In your createCirlcesOnLine function, add each circle to the array as you create it.
...
self.addChild(circle)
circles.append(circle)
Then, in your update method, enumerate through the circles to see if any of them are above the top of the screen.
override func update(currentTime: NSTimeInterval) {
for circle in circles {
if circle.position.y > self.size.height + circle.size.height/2 {
//Send circle back to the bottom using the circle's position property
}
}
}
This solution will work but causes a lot of unnecessary checks on every update cycle.
A second more efficient (and slightly more complicated) recommendation is to add an invisible node above the top of the screen that stretches the screen width. When the circle collides with it, just move it to the bottom of the screen. Look into implementing the SKPhysicsContactDelegate protocol and what needs to happen for that to work. If you run into problems with this solution, post a separate question with those issues.

Get a list of nodes in an specific area?

I'm working in a side-scolling game and I need to know what nodes are in an area to implement something like "line of sight". Right now I'm trying using enumerateBodyiesInRect() however it's detecting bodies that are 20px or more from the evaluated rect and I cannot figure out why it's so imprecise.
This is what I'm trying now:
import SpriteKit
import CoreMotion
class GameScene: SKScene, SKPhysicsContactDelegate
{
var player = SKShapeNode()
var world = SKShapeNode()
var rShape = SKShapeNode()
override func didMoveToView(view: SKView) {
self.physicsWorld.contactDelegate = self
self.scaleMode = SKSceneScaleMode.AspectFit
self.size = view.bounds.size
// Add world
world = SKShapeNode(rectOfSize: view.bounds.size)
world.physicsBody = SKPhysicsBody(edgeLoopFromPath: world.path)
world.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2) // Move camera
self.addChild(world)
// Add player
player = SKShapeNode(rectOfSize: CGSize(width: 25, height: 25))
player.physicsBody = SKPhysicsBody(rectangleOfSize: player.frame.size)
player.physicsBody.dynamic = false
player.strokeColor = SKColor.blueColor()
player.fillColor = SKColor.blueColor()
player.position = CGPointMake(90, -50)
world.addChild(player)
}
override func update(currentTime: CFTimeInterval) {
// Define rect position and size (area that will be evaluated for bodies)
var r : CGRect = CGRect(x: 200, y: 200, width: 25, height: 25)
// Show rect for debug
rShape.removeFromParent()
rShape = SKShapeNode(rect: r)
rShape.strokeColor = SKColor.redColor()
self.addChild(rShape)
// Evaluate rect
rShape.fillColor = SKColor.clearColor()
self.physicsWorld.enumerateBodiesInRect(r) {
(body: SKPhysicsBody!, stop: UnsafePointer<ObjCBool>) in
self.rShape.fillColor = SKColor.redColor() // Paint the area blue if it detects a node
}
}
}
This code should show the evaluated rect and ray on the screen (for debugging purposes) and paint them red if they contact the player node. However you can see in the screenshot how it turns red when the player is 25px or more away from it, it's like if the drawing is a little bit off, or smaller than the actual area being evaluated. You can copy paste it to a project to duplicate the problem.
Could this be because this is just beta or am I doing something wrong?
You are creating a physical world where there is a specific rectangle that has 'special properties' - this is the rectangle that you use in enumerateBodiesInRect(). Why not create an invisible, inert physical body with the required rectangular dimension and then use SKPhysicsBody to check for collisions and/or contacts? You could then use allContactedBodies() or some delegate callbacks to learn what other bodies are inside your special rectangle.
Think of it like a 'tractor beam' or a 'warp rectangle'.
I believe you want SKPhysicsWorld's enumerateBodyiesInRect() instance method, which will iterate over all nodes in a given rectangle. If you're looking to get at the physics world through your scene, usage could look like this:
self.physicsWorld.enumerateBodiesInRect(CGRect(x: 0, y: 0, width: 50, height: 50)) {(body: SKPhysicsBody!, stop: UnsafePointer<ObjCBool>) in
// enumerates all nodes in given frame
}
I've experimented quite a bit with enumerateBodiesInRect now, and I've found it to be incredibly inaccurate. It seems to not have any of the claimed functionality, and instead produces random results. I honestly cannot even determine any pattern from its products.
enumerateBodiesAlongRay seems better, but still very buggy. The problem with that function seems to be the conversion between Screen and PhysicsWorld coordinates. I would avoid that one, as well.
I think your solution should simply be to use the existing contact detection system. All of your desired functionality can be written in the didBeginContact() and didEndContact() functions. This has the added benefit of allowing you to specify distinct functionality for both entering and leaving the area. You can also add particle effects, animations, and similar, as well as intentionally ignoring specific types of nodes.
The only thing to ensure success with this method is to clarify that the contact area has a unique category, that the contactTestBitMask contains all desired nodes and the collisionBitMask is set to 0.
The enumerateBodiesInRect method of SKPhysicsWorld expects the rect parameter to be in scene coordinates. This is important. If you have a scene hierarchy of nodes, you need to convert the rect you calculate from a reference node to the scene coordinates.
I faced a lot of issues with this method returning bodies that were off by values like 30px to the left etc. and finally realized the issue was because of the rect parameter not defined in scene coordinate space.
In my case, I had a worldNode inside my scene, and all objects were created in the worldNode. My camera was moving the worldNode about, and applying scaling to it for zooming out and in.
In order to use enumerateBodiesInRect correctly, I had to do something as follows:
// get your world rect based on game logic
let worldRect = getWorldRect()
// calculate the scene rect
let sceneRectOrigin = scene.convertPoint(worldRect.origin, fromNode:scene.worldNode)
let worldScale = scene.worldNode.xScale // assert this is not 0
// now to get the scene rect relative to the world rect, in scene coordinates
let sceneRect = CGRectMake( sceneRectOrigin.x, sceneRectOrigin.y, worldRect.width / worldScale, worldRect.height / worldScale)
world.physicsWorld.enumerateBodiesInRect(sceneRect) {
// your code here
}
Hope this helps.
I am not sure if this is a good practice. Correct me if not. But I am using
let shapeNode = SKShapeNode()
shapeNode.intersects(playerNode)
I checked selected nodes with simple loop if they intersect the player. Additionally I created SKShapeNodes which are drawn in front of nodes representing view sight of other actors in the game. They are moved along those actors.
There is only nodesAtPoint: method.
To achieve what you want you'd better to store all enemies in an array and have an int variable, something like nextEnemyIndex. This approach lets you to easily return the next enemy node, it's much more efficient than trying to find a node on the scene.
yes problem may occur because of your player's image, for example try to use 10px smaller body size:
player.physicsBody = SKPhysicsBody(rectangleOfSize: CGRectMake(self.frame.origin.x, self.frame.origin.y, self.size.width-10, self.size.height-10)));

SKShapeNode ellipseInRect, sprite does not appear in scene

I'm trying to create an ellipse in the center of my scene:
let center = (CGRectGetMidX(view.scene.frame), CGRectGetMidY(view.scene.frame))
let size = (view.scene.frame.size.width * 0.3, view.scene.frame.size.height * 0.3)
let ellipse = SKShapeNode (ellipseInRect: CGRectMake(center.0, center.1, size.0, size.1))
ellipse.strokeColor = UIColor.blackColor()
ellipse.position = CGPointMake(center)
self.addChild(ellipse)
This was added to didMoveToView, and the node count on the view shows 1, but I do not see the path. How do I add an ellipse to my scene using the SKShapeNode ellipseInRect API?
The problem lies in ellipse.position = CGPointMake(center). For some reason, this changes the position of the ellipse relative to itself rather than relative to the view - so if you did ellipse.position = CGPoint(x: 100, y: 100) then it would set the position to 100 up and 100 to the right of the ellipse itself as opposed to 100,100 on the scene. If you comment out this line, then you should be able to see you ellipse on the screen - I certainly could when it tried it. Hope that helps you position it to where you want.