Struggling with how to iterate through an array of SKShapeNodes. I seem to be able to go through it in DidMoveToView(), but, not WhenTouchesBegan().
From GameScene.swift:
class GameScene: SKScene {
...
var areaTwo = SKShapeNode()
var areaThree = SKShapeNode()
var areaFour = SKShapeNode()
var currentArea = SKShapeNode()
//CGPaths as UIBezierPaths set here
var areas = [SKShapeNode]()
override func didMoveToView(view: SKView) {
...
areaTwo = SKShapeNode(path: areaTwoPath.CGPath)
areaThree = SKShapeNode(path: areaThreePath.CGPath)
areaFour = SKShapeNode(path: areaFourPath.CGPath)
let areas = [areaTwo, areaThree, areaFour]
...
//this works
for area in areas {
area.lineWidth = 4
addChild(area)
}
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
for touch in touches {
let location = touch.locationInNode(self)
currentArea.fillColor = UIColor.clearColor()
//this does not work! No errors thrown. Just doesn't seem to do anything.
for area in areas{
currentArea = area
if currentArea.containsPoint(location) {
currentArea.fillColor = UIColor.redColor()
}
}
}
}
What is frustrating is if I use a series of if...else if...else if I can check every single area, but, can't check them through the array.
not quite clear about your target. if you simply want a way to iterate child nodes, you can try
//init child nodes
for i in 1...2{
let areaNode = SKShapeNode()
...
areaNode.name = "area"
parentNode.addChild(areaNode)
}
//iteration
for node in parentNode.children{
if node.name == "area"{
print("here we find a child area")
}else{
print("some irrelevant node found")
}
}
Btw, the reason why your code in didMoveToView() work is you declared an new areas array, which in fact is an in-method variable replaced the role of previous class property areas
Related
I am trying Vision kit for iOS 11. I can use Vision and I can find boundbox values face. But I don't know how can I draw a rectangle using this points. I hope so my question is clear.
Hope you were able to use VNDetectFaceRectanglesRequest and able to detect faces. To show rectangle boxes there are lots of ways to achieve it. But simplest one would be using CAShapeLayer to draw layer on top your image for each face you detected.
Consider you have VNDetectFaceRectanglesRequest like below
let request = VNDetectFaceRectanglesRequest { [unowned self] request, error in
if let error = error {
// somthing is not working as expected
}
else {
// we got some face detected
self.handleFaces(with: request)
}
}
let handler = VNImageRequestHandler(ciImage: ciImage, options: [:])
do {
try handler.perform([request])
}
catch {
// catch exception if any
}
You can implement a simple method called handleFace for each face detected and use VNFaceObservation property to draw a CAShapeLayer.
func handleFaces(with request: VNRequest) {
imageView.layer.sublayers?.forEach { layer in
layer.removeFromSuperlayer()
}
guard let observations = request.results as? [VNFaceObservation] else {
return
}
observations.forEach { observation in
let boundingBox = observation.boundingBox
let size = CGSize(width: boundingBox.width * imageView.bounds.width,
height: boundingBox.height * imageView.bounds.height)
let origin = CGPoint(x: boundingBox.minX * imageView.bounds.width,
y: (1 - observation.boundingBox.minY) * imageView.bounds.height - size.height)
let layer = CAShapeLayer()
layer.frame = CGRect(origin: origin, size: size)
layer.borderColor = UIColor.red.cgColor
layer.borderWidth = 2
imageView.layer.addSublayer(layer)
}
}
More info can be found here in Github repo iOS-11-by-Examples
Here is easy and simple way to draw boxes.
let faceRequest = VNDetectFaceRectanglesRequest(completionHandler:self.faceDetection)
func faceDetection (request: VNRequest, error: Error?) {
guard let observations = request.results as? [VNFaceObservation]
else { print("unexpected result type from VNFaceObservation")
return }
guard observations.first != nil else {
return
}
// Show the pre-processed image
DispatchQueue.main.async {
self.resultImageView.subviews.forEach({ (subview) in
subview.removeFromSuperview()
})
for face in observations
{
let view = self.CreateBoxView(withColor: UIColor.red)
view.frame = self.transformRect(fromRect: face.boundingBox, toViewRect: self.analyzedImageView)
self.analyzedImageView.image = self.originalImageView.image
self.resultImageView.addSubview(view)
}
}
}
//MARK - Instance Methods
func boxView(withColor : UIColor) -> UIView {
let view = UIView()
view.layer.borderColor = withColor.cgColor
view.layer.borderWidth = 2.0
view.backgroundColor = UIColor.clear
return view
}
//Convert Vision Frame to UIKit Frame
func transformRect(fromRect: CGRect , toViewRect :UIView) -> CGRect {
var toRect = CGRect()
toRect.size.width = fromRect.size.width * toViewRect.frame.size.width
toRect.size.height = fromRect.size.height * toViewRect.frame.size.height
toRect.origin.y = (toViewRect.frame.height) - (toViewRect.frame.height * fromRect.origin.y )
toRect.origin.y = toRect.origin.y - toRect.size.height
toRect.origin.x = fromRect.origin.x * toViewRect.frame.size.width
return toRect
}
I use a function (shown below) to spawn a coin node in specific locations at random using an array.
Using this function, I am trying to incorporate more than one coin node (that are slightly different from one another) into this function so that multiple nodes can use this array to spawn and function just like the first coin node.
The problem that I have is that when I incorporate another node into this function or make a new but similar function for the 2nd node I get a Thread 1 SIGABERT error after the game crashes.
I currently have two separate functions for each node that are very similar, but with slight differences to accommodate each node.
func generateCoinZero() {
if(self.actionForKey("spawningCoinZero") != nil){return}
let coinTimerZero = SKAction.waitForDuration(2, withRange: 7)
let spawnCoinZero = SKAction.runBlock {
let coinZeroTexture = SKTexture(imageNamed: "coinZero")
self.coinZero = SKSpriteNode(texture: coinZeroTexture)
self.coinZero.physicsBody = SKPhysicsBody(circleOfRadius: self.coinZero.size.height / 12)
self.coinZero.physicsBody?.dynamic = false
self.coinZero.physicsBody?.allowsRotation = false
self.coinZero.zPosition = 1
self.coinZero.physicsBody?.categoryBitMask = ColliderType.coinZeroCategory
self.coinZero.physicsBody?.contactTestBitMask = ColliderType.playerCategory
self.coinZero.physicsBody?.collisionBitMask = 0
self.player.physicsBody?.categoryBitMask = ColliderType.playerCategory
self.player.physicsBody?.contactTestBitMask = 0
self.player.physicsBody?.collisionBitMask = ColliderType.boundary
var coinPositionZero = Array<CGPoint>()
coinPositionZero.append((CGPoint(x:250, y:139)))
coinPositionZero.append((CGPoint(x:790, y:298)))
coinPositionZero.append((CGPoint(x:225, y:208)))
coinPositionZero.append((CGPoint(x:220, y:237)))
let spawnLocationZero = coinPositionZero[Int(arc4random_uniform(UInt32(coinPositionZero.count)))]
let actionZero = SKAction.repeatActionForever(SKAction.moveToX(+self.xScale, duration: 2.0))
self.coinZero.runAction(actionZero)
self.coinZero.position = spawnLocationZero
self.addChild(self.coinZero)
print(spawnLocationZero)
}
let sequenceZero = SKAction.sequence([coinTimerZero, spawnCoinZero])
self.runAction(SKAction.repeatActionForever(sequenceZero), withKey: "spawningCoinZero")
}
func generateCoinTwo() {
if(self.actionForKey("spawnCoinTwo") != nil){return}
let coinTimerTwo = SKAction.waitForDuration(2, withRange: 7)
let spawnCoinTwo = SKAction.runBlock {
let coinTwoTexture = SKTexture(imageNamed: "coinTwo")
self.coinTwo = SKSpriteNode(texture: coinTwoTexture)
self.coinTwo.physicsBody = SKPhysicsBody(circleOfRadius: self.coinTwo.size.height / 12)
self.coinTwo.physicsBody?.dynamic = false
self.coinTwo.physicsBody?.allowsRotation = false
self.coinTwo.zPosition = 1
self.addChild(self.coinTwo)
var coinPositionTwo = Array<CGPoint>()
coinPositionTwo.append((CGPoint(x:250, y:139)))
coinPositionTwo.append((CGPoint(x:790, y:298)))
coinPositionTwo.append((CGPoint(x:225, y:208)))
coinPositionTwo.append((CGPoint(x:220, y:237)))
let spawnLocationTwo = coinPositionTwo[Int(arc4random_uniform(UInt32(coinPositionTwo.count)))]
let actionTwo = SKAction.repeatActionForever(SKAction.moveToX(+self.xScale, duration: 2.0))
self.coinTwo.runAction(actionTwo)
self.coinTwo.position = spawnLocationTwo
self.addChild(self.coinTwo)
print(spawnLocationTwo)
}
let sequenceTwo = SKAction.sequence([coinTimerTwo, spawnCoinTwo])
self.runAction(SKAction.repeatActionForever(sequenceTwo), withKey: "spawnCoinTwo")
}
OK, there are quite a lot of issues here, the main ones being the extreme duplication of code and having your generateCoin...-functions doing way too much. So here goes:
You state in the comments that the scene should have two coins spawning at different times at one of four possible positions. If the scene has two coins, then the scene has two coins. Let's just create these as properties and be done with it:
// Your two coin properties
let coin1 = coinNode()
let coin2 = coinNode()
// the function from which they are created
func coinNode() -> SKSpriteNode {
let coinNode = SKSpriteNode(imageNamed: "coinZero")
coinNode.physicsBody = SKPhysicsBody(circleOfRadius: coinNode.size.height / 2)
coinNode.physicsBody?.dynamic = false
coinNode.physicsBody?.allowsRotation = false
coinNode.zPosition = 1
coinNode.physicsBody?.categoryBitMask = ColliderType.coinZeroCategory
coinNode.physicsBody?.contactTestBitMask = ColliderType.playerCategory
coinNode.physicsBody?.collisionBitMask = 0 // A ColliderType.none would be lovely...
return coinNode
}
Now, these coins are not yet added to the scene nor do they have a proper position, this sounds like a fitting scope for another function:
func addCoin() {
let positions = [ CGPoint(x:250, y:139), CGPoint(x:790, y:298), CGPoint(x:225, y:208), CGPoint(x:220, y:237)]
let position = positions[Int(arc4random_uniform(UInt32(positions.count)))]
if coin1.parent == nil {
coin1.position = position
addChild(coin1)
} else if coin2.parent == nil {
coin2.position = position
addChild(coin2)
}
}
Finally you want to have this function being called so do the following in your scene's init or setup:
let delay = SKAction.waitForDuration(1) // or however long you want it to be between each spawn
let addCoinCall = SKAction.runBlock({ self.addCoin() })
let spawnSequence = SKAction.sequence([delay, addCoinCall])
runAction(SKAction.repeatActionForever(spawnSequence))
You can't addChild twice, put addChild out of runBlock, and make sure that you are addChild once.
If you want put multiple coin, is better to copy your node and add on the scene.
You can create a node like coinZero out of function, and then inside the function do something like:
let coinToAdd = coinZero.copy()
I'm attempting to make a small game where the user mouses over the circles that fall from the ceiling for points. The circles are added to a container and pushed into an array to hold them, and are removed and spliced when they are mouse-over'd or go off stage.
Everything works fine, until two circles are removed at nearly the same time, whether it be from falling off stage at the same time or mousing over two of them extremely fast. When this happens, the child on stage is removed, but the object is still left in the array, meaning another circle cannot take its place, leaving one less circle spawning every time the issue happens.
Code on main timeline:
import flash.events.Event;
import flash.events.MouseEvent;
import flash.display.MovieClip;
import flash.display.Sprite;
var ballContainer:Sprite = new Sprite();
addChild(ballContainer);
var maxBalls:uint = 10;
var balls:Array = [];
var ballTypes:Array = [GreenBall];
var ballChances:Array = [800];
var ballVelocities:Array = [1.5];
var ballAccelerations:Array = [1.02];
stage.addEventListener(Event.ENTER_FRAME, onTick);
function onTick(e:Event):void {
while (balls.length < maxBalls){
addBall();
}
}
function addBall():void {
var ballType = ballTypes[0];
var ball = new ballType;
ball.x = Math.ceil(Math.random()*(stage.stageWidth - ball.width));
ball.y = 0 - (ball.height*1.5);
ballContainer.addChild(ball);
balls.push(ball);
}
Code in GreenBall:
import flash.events.Event;
var mainStage = Sprite(root);
var index = mainStage.balls.indexOf(this);
var velocity:Number = mainStage.ballVelocities[0]*randomNumber(0.5, 1.5);
var acceleration:Number = mainStage.ballAccelerations[0];
this.addEventListener(MouseEvent.MOUSE_OVER, onMouseOver);
function onMouseOver(e:MouseEvent):void {
this.removeEventListener(MouseEvent.MOUSE_OVER, onMouseOver);
removeBall();
}
this.addEventListener(Event.ENTER_FRAME, onTick);
function onTick(e:Event):void {
this.y += velocity;
velocity = velocity*acceleration;
if (this.y > stage.stageHeight + this.height){
this.removeEventListener(MouseEvent.MOUSE_OVER, onMouseOver);
removeBall();
}
}
function removeBall():void {
mainStage.balls.splice(index, 1);//doesn't get spliced if balls are removed too quickly
mainStage.ballContainer.removeChild(this);
this.removeEventListener(Event.ENTER_FRAME, onTick);
}
function randomNumber(min:Number, max:Number):Number {
return Math.random()*(max - min) + min;
}
So what's going on? Did I set something up incorrectly? How can I go about fixing this issue?
Any help would be appreciated greatly.
Your logic is flawed - the index should be calculated when the removal occurs. When you remove objects from an array via splice, the index of all the elements after the one you removed is decreased by one.
This means that if you have 10 balls and remove the first, the index value you have for every other ball will be incorrect and you'll be removing the wrong ball from your array on subsequent removals.
Moving the indexOf statement to the removeBall method should solve the issue:
function removeBall():void
{
var index:int = mainStage.balls.indexOf(this);
if(index >= 0)
{
mainStage.balls.splice(index, 1);
mainStage.ballContainer.removeChild(this);
this.removeEventListener(Event.ENTER_FRAME, onTick);
}
}
To make it easy on yourself, you could extend Array and make a remove function:
public dynamic class List extends Array
{
public function remove(item:*):void
{
var i:int = indexOf(item);
if(i >= 0) splice(i, 1);
}
}
I am not sure how to make this clear but : Is there a way to allow mouse event register with objects in an array? I have multiple objects being added to stage from an array and i would like to call different functions after said objects are clicked on ? I have this:
function makeEnemies():void
{
//create humans
var tempEnemy:MovieClip;
var wolf:MovieClip;
tempEnemy = new Enemy2();
tempEnemy.cacheAsBitmap = true;
tempEnemy.speed = 20;
tempEnemy.x = Math.round(Math.random() * 800);
tempEnemy.y = Math.round(Math.random() * 480);
addChild(tempEnemy);
enemies.push(tempEnemy);
}
function moveEnemies():void
{
var tempEnemy:MovieClip;
for (var i:int =enemies.length-1; i>=0; i--)
{
tempEnemy = enemies[i];
if (tempEnemy.x > stage.stageWidth)
{
tempEnemy.x = stage.stageWidth;
}
if (tempEnemy.y > stage.stageHeight)
{
tempEnemy.y = stage.stageHeight;
}
tempEnemy.x += Math.round(Math.random() * tempEnemy.speed);
tempEnemy.y -= Math.round(Math.random() * tempEnemy.speed);
tempEnemy.addEventListener(MouseEvent.CLICK, scoreM);
function scoreM(event:MouseEvent):void
{
makeBite(tempEnemy.x, tempEnemy.y);
removeEnemy(i);
score++;
score_txt.text = String(score);
}
function removeEnemy(idx:int)
{
removeChild(enemies[idx]);
enemies.splice(idx,1);
}
And i get an error
TypeError: Error #2007: Parameter child must be non-null.
at flash.display::DisplayObjectContainer/removeChild()
at veinsVtest_fla::MainTimeline/removeEnemy()
at MethodInfo-67()
You won't need an Array for this job. Like Man of Snow said. Use event.currentTarget will point to the "clicked on Enemy"
tempEnemy.addEventListener(MouseEvent.CLICK, scoreM);
function scoreM(event:MouseEvent):void
{
var clickedOnEnemy:MovieClip = event.currentTarget as MovieClip;
//Now you have your enemy, do whatever you please with him.
makeBite(clickedOnEnemy.x, clickedOnEnemy.y);
//And farewell, my enemy ... time to remove him.
removeChild(clickedOnEnemy);
clickedOnEnemy = null;
score++;
score_txt.text = String(score);
}
* EDIT **
There are several ways to implement "removeAllEnemies".
One way to do it is to use have another MovieClip to hold all enemies created.
So create a movieClip and add it to stage.
var enemiesWrapper : MovieClip = new MovieClip();
addChild(enemiesWrapper);
And then instead of add enemy to root
addChild(tempEnemy); //Instead of doing this
Add them to this MovieClip instead.
enemiesWrapper.addChild(tempEnemy); //Do this instead
Note that your removeChild has to be updated accordingly
enemiesWrapper.removeChild(clickedOnEnemy);
And for "removeAllEnemies" function
function removeAllEnemies() {
while(enemiesWrapper.numChildren > 0) {
enemiesWrapper.removeChildAt(0);
}
}
Replace removeEnemy(i); with removeEnemy(event.currentTarget);
You cannot remove an integer, because it isn't a child. However, I'm assuming you want to remove the MovieClip that was clicked, and it looks like removeEnemy calls removeChild() on the parameter. If not, do you mind showing your removeEnemy function?
I'm trying to get my SWF loader to work with an array so I can call my swf files via one code, using buttons.
This is the problem I am getting:
Scene 1, Layer 'actions', Frame 2, Line 68 1067: Implicit coercion of a value of type Array to an unrelated type String.
I am not too good with arrays, or strings, or coding tbh, i'm not too sure what the problem is, I understand it, my array and my string don't work together,basically, but I don't know how to fix it, if it can be fixed/work with the code I am using.
just some help and being pointed in the right direction would be a treat
var swfList:Array = ["imagegallery.swf", "videoplayer.swf"];
var SWFLoader = new Loader;
var SWFRequest = new URLRequest (swfList) ;
SWFLoader.load (SWFRequest) ;
function loadSWF(file:String, container:MovieClip=null):void
{
if(container == null) container = MovieClip(root);
if(SWFLoader != null)
{
if(SWFLoader.parent) SWFLoader.parent.removeChild(SWFLoader);
}
addChild (SWFLoader);
}
vidPlayer_btn.addEventListener (MouseEvent.CLICK, goVidPlayer);
function goVidPlayer (e:MouseEvent):void
{
loadSWF("videoplayer.swf");
}
imageGallery_btn.addEventListener(MouseEvent.CLICK, goImageGallery);
function goImageGallery(e:MouseEvent):void
{
loadSWF("imagegallery.swf");
}
To access items within an array use this format:
var SWFRequest = new URLRequest(swfList[i]);
Where i is the position in array (starting at zero).
For instance:
var SWFRequest = new URLRequest(swfList[0]);
gives the same result as:
var SWFRequest = new URLRequest("imagegallery.swf");
Did away with the array but still "have one code for both buttons instead of two separate codes".
// Looks unnecessary
// var swfList:Array = ["imagegallery.swf", "videoplayer.swf"];
// Transfer inside loadSWF()
// var SWFRequest = new URLRequest (swfList); // needs a url String parameter
// SWFLoader.load (SWFRequest);
var SWFLoader = new Loader(); // Don't forget the parenthesis
vidPlayer_btn.addEventListener (MouseEvent.CLICK, goVidPlayer);
imageGallery_btn.addEventListener(MouseEvent.CLICK, goImageGallery);
function goVidPlayer (e:MouseEvent):void
{
loadSWF("videoplayer.swf");
}
function goImageGallery(e:MouseEvent):void
{
loadSWF("imagegallery.swf");
}
function loadSWF(file:String, container:MovieClip=null):void
{
// What for?
// if(container == null) container = MovieClip(root);
if(SWFLoader != null)
if(SWFLoader.parent)
SWFLoader.parent.removeChild(SWFLoader);
var SWFRequest = new URLRequest (file) ;
SWFLoader.load (SWFRequest);
addChild (SWFLoader);
}