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()
Related
Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 10 months ago.
Improve this question
I am trying to integrate a slightly modified version of Boids flocking Sim into my React website. I have tried with both wrapper and p5-React npm's, both work on a smaller scale (i.e. with the examples in the descriptions), but when I try to apply it to the boids sim it just shows "loading...". I have watched the youtube tutorial and fully understand the logic, but I am a bit new to p5. Here is what I am trying to include: https://codepen.io/stephenleemorrow/pen/QWaxKqY
Here is my VS Code:
let flock;
let img;
let bg;
p5.preload = () => {
img = p5.loadImage("src\pngbyte.com-Open-Window-Window-Png-Image-window-png-images-open-bar.png");
}
p5.setup = () => {
bg = p5.loadImage("src\pixle-cloud-landscape.webp");
p5.createCanvas(568, 320);
flock = new Flock();
// Add an initial set of boids into the system
for (let i = 0; i < 20; i++) {
let b = new Boid(p5.width / 2, p5.height / 2);
flock.addBoid(b);
}
}
p5.draw = (p5) => {
p5.background(51);
flock.run();
p5.image(img, -23, -10, 610, 340);
}
// Add a new boid into the System
p5.mouseDragged = () => {
flock.addBoid(new Boid(p5.mouseX, p5.mouseY));
}
// The Nature of Code
// Daniel Shiffman
// http://natureofcode.com
// Flock object
// Does very little, simply manages the array of all the boids
function Flock() {
// An array for all the boids
this.boids = []; // Initialize the array
}
Flock.prototype.run = function () {
for (let i = 0; i < this.boids.length; i++) {
this.boids[i].run(this.boids); // Passing the entire list of boids to each boid individually
}
}
Flock.prototype.addBoid = function (b) {
this.boids.push(b);
}
// The Nature of Code
// Daniel Shiffman
// http://natureofcode.com
// Boid class
// Methods for Separation, Cohesion, Alignment added
function Boid(x, y) {
this.acceleration = p5.createVector(0, 0);
this.velocity = p5.createVector(p5.random(-1, 1), p5.random(-1, 1));
this.position = p5.createVector(x, y);
this.r = 3.0;
this.maxspeed = 3; // Maximum speed
this.maxforce = 0.05; // Maximum steering force
}
Boid.prototype.run = function (boids) {
this.flock(boids);
this.update();
this.borders();
this.render();
}
Boid.prototype.applyForce = function (force) {
// We could add mass here if we want A = F / M
this.acceleration.add(force);
}
// We accumulate a new acceleration each time based on three rules
Boid.prototype.flock = function (boids) {
let sep = this.separate(boids); // Separation
let ali = this.align(boids); // Alignment
let coh = this.cohesion(boids); // Cohesion
// Arbitrarily weight these forces
sep.mult(1.5);
ali.mult(1.0);
coh.mult(1.0);
// Add the force vectors to acceleration
this.applyForce(sep);
this.applyForce(ali);
this.applyForce(coh);
}
// Method to update location
Boid.prototype.update = function () {
// Update velocity
this.velocity.add(this.acceleration);
// Limit speed
this.velocity.limit(this.maxspeed);
this.position.add(this.velocity);
// Reset accelertion to 0 each cycle
this.acceleration.mult(0);
}
// A method that calculates and applies a steering force towards a target
// STEER = DESIRED MINUS VELOCITY
Boid.prototype.seek = function (target) {
let desired = p5.Vector.sub(target, this.position); // A vector pointing from the location to the target
// Normalize desired and scale to maximum speed
desired.normalize();
desired.mult(this.maxspeed);
// Steering = Desired minus Velocity
let steer = p5.Vector.sub(desired, this.velocity);
steer.limit(this.maxforce); // Limit to maximum steering force
return steer;
}
Boid.prototype.render = function () {
// Draw a triangle rotated in the direction of velocity
let theta = this.velocity.heading() + p5.radians(90);
p5.fill(50);
p5.stroke(100);
p5.push();
p5.translate(this.position.x, this.position.y);
p5.rotate(theta);
p5.beginShape();
p5.vertex(0, -this.r * .5);
p5.vertex(-this.r, this.r * .5);
p5.vertex(this.r, this.r * .5);
p5.endShape(p5.CLOSE);
p5.pop();
}
// Wraparound
Boid.prototype.borders = function () {
if (this.position.x < -this.r) this.position.x = p5.width + this.r;
if (this.position.y < -this.r) this.position.y = p5.height + this.r;
if (this.position.x > p5.width + this.r) this.position.x = -this.r;
if (this.position.y > p5.height + this.r) this.position.y = -this.r;
}
// Separation
// Method checks for nearby boids and steers away
Boid.prototype.separate = function (boids, p5) {
let desiredseparation = 25.0;
let steer = p5.createVector(0, 0);
let count = 0;
// For every boid in the system, check if it's too close
for (let i = 0; i < boids.length; i++) {
let d = p5.Vector.dist(this.position, boids[i].position);
// If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself)
if ((d > 0) && (d < desiredseparation)) {
// Calculate vector pointing away from neighbor
let diff = p5.Vector.sub(this.position, boids[i].position);
diff.normalize();
diff.div(d); // Weight by distance
steer.add(diff);
count++; // Keep track of how many
}
}
// Average -- divide by how many
if (count > 0) {
steer.div(count);
}
// As long as the vector is greater than 0
if (steer.mag() > 0) {
// Implement Reynolds: Steering = Desired - Velocity
steer.normalize();
steer.mult(this.maxspeed);
steer.sub(this.velocity);
steer.limit(this.maxforce);
}
return steer;
}
// Alignment
// For every nearby boid in the system, calculate the average velocity
Boid.prototype.align = function (boids) {
let neighbordist = 50;
let sum = p5.createVector(0, 0);
let count = 0;
for (let i = 0; i < boids.length; i++) {
let d = p5.Vector.dist(this.position, boids[i].position);
if ((d > 0) && (d < neighbordist)) {
sum.add(boids[i].velocity);
count++;
}
}
if (count > 0) {
sum.div(count);
sum.normalize();
sum.mult(this.maxspeed);
let steer = p5.Vector.sub(sum, this.velocity);
steer.limit(this.maxforce);
return steer;
} else {
return p5.createVector(0, 0);
}
}
// Cohesion
// For the average location (i.e. center) of all nearby boids, calculate steering vector towards that location
Boid.prototype.cohesion = function (boids) {
let neighbordist = 50;
let sum = p5.createVector(0, 0); // Start with empty vector to accumulate all locations
let count = 0;
for (let i = 0; i < boids.length; i++) {
let d = p5.Vector.dist(this.position, boids[i].position);
if ((d > 0) && (d < neighbordist)) {
sum.add(boids[i].position); // Add location
count++;
}
}
if (count > 0) {
sum.div(count);
return this.seek(sum); // Steer towards the location
} else {
return p5.createVector(0, 0);
}
}
}
Again, the connection to App() is good cause it works with smaller sketches. I think I'm just missing something in the syntax.
Any help would be GREATLY appreciated!
I made a few changes and got this running (although with errors in the p5 code that kind of fall out of the scope of the answer).
This might be a typo, but to use this sketch with ReactP5Wrapper, you need to export it as a function, so your code above is missing
export const sketch = (p5) => {
at the start. You actually have the closing bracket at the end, so it's probably a typo in the question.
Later in the code, anywhere you are passing p5 into a function, like
Boid.prototype.separate = function (boids, p5) {
you are not passing anything in for p5 - like
let sep = this.separate(boids);
so that would cause undefined errors. The p5 passed into the sketch function can be used throughout the sketch so you can remove p5 from the parameter lists of these other functions.
The really breaking issue here though was that you were trying to load images from the /src/ folder.
Make a directory in the /public/ folder at the top level of your project, call it /assets/ and put your images in there.
Then you can reference them like:
p5.preload = () => {
img = p5.loadImage("assets/test.png");
}
After I do all that, I get the sketch starting, creating the canvas and doing some of the algorithm but it fails at various points that I'm sure will have loads of fun debugging after you get it running to begin with :)
Btw, I'm displaying the sketch in my App component like this, using the ReactP5Wrapper:
import React from 'react';
import { ReactP5Wrapper } from 'react-p5-wrapper';
import { sketch } from './sketch/sketch';
export const App = () => {
return(
<div>
<ReactP5Wrapper sketch={sketch}/>
</div>
)
}
This might be less difficult than I'm making it out to be, but I'm trying to make a Discord.JS bot command, that will take however many arguments I have. For example: !randomize 1,2,3,4,5,6,7,8,9,10
And the bot would respond with something like: "I have chosen: 4,2,7,3,9!" Any help?
Current attempt: Not exactly sure what I'm doing.
function shuffleArray(array) {
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}`
`bot.on('message', async msg => {
if(msg.content === "!add") {
//message.member.user.tag
var msgArray = msg.content.split(" ");
var args = msgArray.slice(1);
var user = args[1];
//if(!args[1]) return msg.channel.send("Please specify an argument!");
if(nameList.includes(user)) {
msg.reply("You're already on the list.")
} else {
nameList.push(args[1]);
msg.channel.send(`${args[1]} has been added to the list!\n Current List:` + nameList);
}
}
if(msg.content === "!bonus") {
if(nameList.length === 0) {
msg.reply("Either the list is empty, or I'm not in the mood!");
} else {
shuffleArray(nameList);
var chosenOne = nameList.pop();
nameList = [];
msg.reply(chosenOne + ' has been chosen! Good luck!');
}
}
if(msg.content === "!list") {
if(nameList.length === 0) {
msg.channel.send("Either the list is empty, or I'm not in the mood!");
} else {
msg.channel.send('The current list:' + nameList);
}
});```
Here's some simple steps to select 5 random elements from an array...
Construct an array of possible selections. In this example I've used names for the first 10 letters of the alphabet. In your code, it'll be the command arguments or predefined nameList.
Make a new array to hold the elements picked.
At some point before #3, you should check to make sure the pool the user has provided is large enough to make 5 selections (Array.length).
Use a for loop to execute the next code multiple times.
Generate a random number representing the index of a selected element (Math.random(), Math.floor()/double NOT bitwise operator).
Push the selection into the array.
Remove the chosen element from the original pool (Array.splice()).
Return the results.
const pool = ['Albert', 'Bob', 'Charlie', 'David', 'Edward', 'Francis', 'George', 'Horacio', 'Ivan', 'Jim'];
const selected = [];
for (let i = 0; i < 5; i++) {
const num = ~~(Math.random() * pool.length);
selected.push(pool[num]);
pool.splice(num, 1);
}
console.log(`I have chosen: ${selected.join(', ')}`);
Take this example and manipulate it within your code to suit your purpose.
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
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 want to make a program in which mines will appear on the screen after a user inputs the number of mines. Then, the user will click on one mine and set off a chain reaction that explodes the nearest two mines.
So far, my code can prompt the user for the number of mines, and then display them. The mines are buttons in which, when clicked, will be removed and an explosion will appear.
However, I am stuck with how I can handle the chain reaction. I am relatively new to coding in AS3 and therefore am stumped with no clue on how to approach this part of my program.
Code:
package
{
import flash.display.MovieClip;
import flash.text.TextField;
import flash.text.TextFieldType;
import flash.events.*;
public class Minefield extends MovieClip
{
var integer:int;
var iField:TextField = new TextField();
var button:iButton = new iButton();
var i:int;
var mines:Array = new Array();
public function Minefield()
{
var explosion:iExplosion = new iExplosion();
iField.type = "input";
iField.height = 18;
iField.x = 460;
iField.y = 275;
iField.border = true;
iField.restrict = "0-9";
iField.maxChars = 2;
stage.focus = iField;
addChild(iField);
addChild(button);
button.x = 450;
button.y = 175;
button.buttonMode = true;
button.addEventListener(MouseEvent.CLICK, UponClick);
}
function AddMines()
{
for (i = 0; i < integer; i++)
{
CreatorOfMine();
mines[i].addEventListener(MouseEvent.CLICK, UponMineClick)
mines[i].buttonMode = true;
}
}
function CreatorOfMine()
{
mines[i] = new Mine();
MineLocation()
}
function MineLocation()
{
mines[i].x = Math.round(Math.random() * 925);
mines[i].y = Math.round(Math.random() * 525);
mines[i].rotation = Math.random() * 360;
addChild(mines[i]);
}
function UponClick(e:MouseEvent)
{
integer = int(iField.text);
RemoverOfChildren();
}
function RemoverOfChildren()
{
removeChild(button);
removeChild(iField);
AddMines();
}
function UponMineClick(event:MouseEvent){
var mineObject:Mine = Mine(event.currentTarget)
var expl:iExplosion = new iExplosion()
expl.x = mineObject.x
expl.y = mineObject.y
expl.rotation = mineObject.rotation
addChild(expl)
removeChild(mineObject)
}
}
}
}
Information you may need/want:
Stage size is 1024 x 600 (px)
Size of mine(s) is 40 x 40 (px)
Size of explosion is 40 x 40 (px)
Looks like a good case for recursion. Pseudo-code:
Function ExplodeMine(mine) {
mine.boooooooooom()
nearest = findNearestUnexplodedMines()
foreach(nextMine in nearest) {
ExplodMine(nextMine);
}
}
Just start ExplodeMine on the first mine that is clicked.