What I am trying to do is to have individual star counts per level based on player performance. (1-2-3 star awards.) This will be based on what region the player reaches. I know how to award the stars but keeping track of it all is throwing me problems. First lets say a player plays level 2 and receives 1 star for their performance. Then at a later time, s/he returns to the level and gets a 2 star. I would like the star count for that specific scene to update to two stars, while only adding 1 star ( The one extra s/he got this time) to the totalStarCount.
My initial plan was to have variables:
OldStarCount
NewStarCount
TotalStarCount
Then when a player reaches say region1, and is awarded one star, then NewStarCount would be set to one, then
TotalStarCount = TotalStarCount + (NewStarCount - OldStarCount);
Then update OldStarCount = NewStarCount;
Set NewStarCount = 0;
Move On to next Scene;
Am I approaching this the correct way? Any help would be greatly appreciated.
You could have something like this
int result = 0;
int totalStars = 0;
int[] starCounts = new int[NumberOfRegions};
...
currentRegion = 42;
result = play(currentRegion);
if(result > starCounts[currentRegion]){
totalStars += result - starCounts[currentRegion];
starCounts[currentRegion] = result;
}
This is just an example of what you could do. There are obvious scalability issues with this (what happens when you want to add new regions, etc), but you get the gist.
Related
I'm developing a game that involves a number of Sprite Arrays and I want to detect collisions between them and specify functions depending on which etc.
So say I have an array of 16 balls ballArray[I] and 16 blocks blockaArray[I] which I can easily iterate through using the index number I.
I have given the balls a Physics Category - Balls and similar to for Blocks. Then I have 16 ID Physics categories say ID1, ID2, ID3, ID4
So I can detect a collision, know that is was a Ball hitting a Block but I then need to know which ball and which block.
What the best or easiest way to do this? I'm reading about enumerateChildNodes(withName) function but have not used it. Or can I create array of PhysicsCategories which I could iterate through along with the SpriteArray to compare and identify.
EDIT:
Thanks Everyone for the help. I have finally cracked it. Surprisingly in the end the code a lot simpler than I first thought. Still not fully understanding where the bits are sitting in my categories but have it working .
I'll try to post my final working code - you may have suggestions to improve. Many thanks again and apologies for my poor StackFlow etiquette - I am new here :-)
So my Physics Categories were defined.
struct PhysicsCategories {
static let BoxCategoryMask = UInt32(1<<7)
static let BallCategoryMask = UInt32(1<<8)
}
and then in my function to build an array of Sprites
boxBloqArray[i].physicsBody?.categoryBitMask = PhysicsCategories.BoxCategoryMask | UInt32(i)
boxBloqArray[i].physicsBody!.contactTestBitMask = PhysicsCategories.BallCategoryMask
and the same for the ball array but just the categoryBitMask
ballBloqArray[i].physicsBody?.categoryBitMask = PhysicsCategories.BallCategoryMask | UInt32(i)
I'm still not really sure why it has to be this way round but that was the final problem this evening that I had the two bodies the wrong way round in the && comparison in the final working detection code:
var body1 = SKPhysicsBody()
var body2 = SKPhysicsBody()
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
body1 = contact.bodyA
body2 = contact.bodyB
}
else {
body1 = contact.bodyB
body2 = contact.bodyA
}
// Check node collisions
for n in 0...15 {
for i in 0...15 {
if body2.categoryBitMask == PhysicsCategories.BallCategoryMask | UInt32(n) && body1.categoryBitMask == PhysicsCategories.BoxCategoryMask | UInt32(i) {
//if body1.node != nil {
print("Ball\(n) hit Box\(i)")
//}
}
}
}
and that is now printing the correct collisions.... lovely!... onwards to
the next step... thanks again
Once you have the two nodes involved in the collision as discussed in the answer by #Luca Angeletti, you can turn those into an index in various ways.
If you've made each type of node a specialized subclass and you have the appropriate indexes stored as class members, then you can convert to the appropriate class and look at the index fields, e.g.,
if let block = nodeA as? BlockNode, let ball = nodeB as? BallNode {
print("block \(block.blockIndex) hit ball \(ball.ballIndex)")
}
Nodes are hashable, so you can have dictionaries to map them back to indexes:
if let blockIndex = blockIndexes[nodeA], let ballIndex = ballIndexes[nodeB] {
print("block \(blockIndex) hit ball \(ballIndex)")
}
You can use the userData property of nodes to store whatever you like, including the indexes. The mucking around with NS things gets kind of ugly though.
https://developer.apple.com/documentation/spritekit/sknode/1483121-userdata
You can do the linear scan through each array.
if let blockIndex = blocks.firstIndex(of: nodeA), let ballIndex = balls.firstIndex(of: nodeB) {
print("block \(blockIndex) hit ball \(ballIndex)")
}
It sounds like from your question that you might have a separate category bit mask for each individual block and each individual ball. Or if you don't, that is possible if there are at most 16 of each. Anyway, if that's the case, then you can do some bit flicking to take the categoryBitMask from the physics bodies, shift the ball/block one by 16 bits (whichever is using the high bits gets shifted), and then take log2 of the bit masks to get your indexes. You can find various bit flicking techniques for log2 here:
https://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious
Given 16 things of each type, I'd say just do #4. If you already have subclass nodes, #1 is fine. Number 2 is spreading state around a bit, so I'm not such a fan of that. Number 3 I would not really recommend because of the NS stuff. Number 5 is too cute for its own good.
Edit: Now that I read again, it sounds like maybe you've got separate ID's for categories 1...16, so your block category bit masks are like:
blockCategoryMask | ID1, blockCategoryMask | ID2, etc. That can also work (basically a variant of #5). If you're going down that route though, you may as well just stick the index directly into the category masks:
let blockCategoryMask = UInt32(1<<4)
let ballCategoryMask = UInt32(1<<5)
Then the physics body for a block gets mask blockCategoryMask | UInt32(index), and similarly for a ball. In that case the index extraction is just categoryBitMask & UInt32(0xf). Or if you put the block and ball categories in bits 0 and 1 and the indexes in bits 2-5, then right shift by 2 to get the index.
Edit in response to comment:
OK, so let's take the case of 6 distinct categories of objects, and each object can fall into one of 16 distinct subcategories. To be able to control which contacts are reported, you'd assign a bit mask to each of the 6 main categories:
enum Category: UInt32 {
// Basic categories
case block = 0b000001
case ball = 0b000010
case shot = 0b000100
case obstacle = 0b001000
case wizard = 0b010000
case food = 0b100000
}
Since you've used 6 bits for the main category, you have 26 bits remaining. To encode the 16 subcategories needs 4 bits. You can put those in the category bit mask above the main 6 bits. Example manipulations:
func encodeObject(category: Category, subcategory: Int) -> UInt32 {
return category.rawValue | (UInt32(subcategory) << 6)
}
func nodeIsA(node: SKNode, category: Category) -> Bool {
guard let body = node.physicsBody else { return false }
return (body.categoryBitMask & category.rawValue) != 0
}
func subcategory(node: SKNode) -> Int {
guard let body = node.physicsBody else { fatalError("missing physicsbody") }
return Int(body.categoryBitMask >> 6)
}
Note that the subcategories are just sort of tagging along for the ride; all your contactBitMasks would deal only to the main categories.
Essentially you're using the fact that you've got some extra bits in the physics body category bit masks to just store random information. I've done that before with simple sprites. But if the information needed is going to get any more complex that a simple number or index, I'd recommend making subclasses of nodes rather than trying to squirrel stuff away in the unused bits.
Using contact.bodyA.node and contact.bodyB.node you can get the SKNode(s) which are involved in the contact
extension GameScene: SKPhysicsContactDelegate {
func didBegin(_ contact: SKPhysicsContact) {
switch (contact.bodyA.node, contact.bodyB.node) {
case (let ball as Ball, let block as Block):
didBeginContactBetween(ball: ball, andBlock: block)
case (let block as Block, let ball as Ball):
didBeginContactBetween(ball: ball, andBlock: block)
default:
break
}
}
func didBeginContactBetween(ball: Ball, andBlock block: Block) {
// TODO: put your code here
}
}
Starting point (boundary conditions):
There is a set of recurring events each week (weekly events section
row 9:11).
My colleagues can set a starting date A6 and how many weeks they'd like to add A5.
Start situation:
Goal:
Running the script should add A5 number of weeks starting from the date A6 to the "upcoming events" section (row 22 and following) with the correct date.
This is how it would look like after the script ran successfully:
What works so far:
The script is able to add the recurring events for one week in the right order to the upcoming events section. It works as well if the starting date is in the middle of the week. (Not shown here as it is probably not relevant.)
My code:
function recurringWeeks() {
var ss = SpreadsheetApp.getActiveSheet(); // selects the active spreadsheet
var repeatingWeeks = ss.getRange(5,1).getValue(); // gets how many weeks it should repeat
var regWeek = ss.getRange(9, 2, 3, 7).getValues(); // gets the regular week data
var regWeekRepeated = ss.getRange(9, 2, repeatingWeeks*3, 7); // create an array to store the events for all weeks
// fill regWeekRepeated with regWeek
for (var j = 0; j < repeatingWeeks; j++){
for (var i = 0; i < 3; i++){
regWeekRepeated[i+j*3] = regWeek[i];
}
}
// Repeat week for "A5" times and add to start/end date a week
for (var j = 0; j < repeatingWeeks; j++){
for (var i = 0; i < 3; i++){
regWeekRepeated[i+j*3][0] = new Date(regWeek[i][0].getTime() + j*7*3600000*24); // <-This line leads to an error message.
}
}
ss.getRange(ss.getLastRow()+1,2,repeatingWeeks*3,7).setValues(regWeekRepeated); // copies weekly events after the last row
}
Edit of [i+j*6] to [i+j*3] in Repeat week for "A5" times and add to start/end date a week
Approach:
As I have solved how to add one week of recurring events with the correct date and right order, I use this as my "point of attack". I'm pretty sure that a for-loop does the job and this is currently my preferred tool.
create an array (regWeek) filled with the recurring event for one
week with the right order and dates. DONE
create an array (regWeekRepeated) and fill it with A5 number of
regular weeks (regWeek) starting from the date A6. ERROR 1:
Object does not allow properties to be added or changed.
make changes to the filled array regWeekRepeated. ERROR 2: TypeError: Cannot set property "0.0" of undefined to "(class)#3d8e4650"
Copy the values into the "upcoming events" section. DONE
Best hit I've found in the search results:
creating 2 dimension arrays
However, this uses .push and as far as I understand this means an element (can be a row) is placed at the end of an array. I've tried to use push as well but have not been successful yet.
Questions:
ERROR 1: Why is it not possible to assign the value of an element from array regWeek to array regWeekRepeated? Solved
ERROR 2: Is this property issue related to ERROR 1 or something different? I've tried to solve both errors individually. Solved
Which approach makes more sense (logically or performance wise) in this context: push individual rows at the end of an existing array or use the whole week as array building blocks?
A demo version of the spreadsheet
Update V01:
Changes: regWeekRepeated is now an array.
I've changed the for loop due to the feedback I've received.
// fill regWeekRepeated with regWeek
var regWeekRepeated = [];
for (var j = 0; j < repeatingWeeks; j++){
for (var i = 0; i < 3; i++){
regWeekRepeated.push(regWeek[i]);
}
}
Logger.log(regWeekRepeated)
Update V02:
// Repeat week for "A5" times and add to start/end date a week
for (var j = 0; j < repeatingWeeks; j++){
for (var i = 0; i < 3; i++){
regWeekRepeated[i+j*3][0] = new Date(regWeek[i][0].getTime() + j*7*3600000*24); //adds a week to the dates for each cycle
//Logger.log(regWeekRepeated[i]); // log is as expected and desired
}
Logger.log(regWeekRepeated); // second part of log not as expected.
}
//Logger.log(regWeekRepeated);
ss.getRange(ss.getLastRow()+1,2,repeatingWeeks*3,7).setValues(regWeekRepeated); // copies weekly events after the last row
Here the log output placed in the "outer" for loop.
1 represents the first cycle, 2 the second cycle
It looks like the second for loop overwrites the elements 0 to 2.
And here the output in google sheets
Update V03:
This makes sure that the changes don't affect the copy.
// fill regWeekRepeated with regWeek
for (var j = 0; j < repeatingWeeks; j++){
for (var i = 0; i < 3; i++){
regWeekRepeated[i+j*3] = regWeek[i].slice(); // shallow copy of an array
}
}
regWeekRepeated is not a array. getRange() doesn't return a array.
Try changing from
var regWeekRepeated = ss.getRange(9, 2, repeatingWeeks*3, 7); // create an array to store the events for all weeks
To
var regWeekRepeated = ss.getRange(9, 2, repeatingWeeks*3, 7).getValues(); // create an array to store the events for all weeks
Creating a array without touching the spreadsheet will increase performance.
var regWeekRepeated =[];
Making scripts for spreadsheets could be tricked because the spreadsheet and JavaScript jargons/lexics use the same terms in different ways. Perhaps this is what is happening here.
ERROR 1: Why is it not possible to assign the value of an element from array regWeek to array regWeekRepeated?
regWeekRepeated is a Range object not a JavaScript Array
ERROR 2: Is this property issue related to ERROR 1 or something different? I've tried to solve both errors individually.
Yes it's related. See the previous answer.
Which approach makes more sense (logically or performance wise) in this context: push individual rows at the end of an existing array or use the whole week as array building blocks?
We could say that calling Google Apps Scripts classes and methods are "expensive" so we should try to minimize the number calls to these kind of elements. One way to do this is by passing the range values to a JavaScript Array , then make all the changes directly to it and we finish pass the resulting values to the corresponding range.
To pass the values of a range to a JavaScript 2D array, use range.getValues() and to pass the JavaScript 2D array values to a range use range.setValues().
I have an image acting as a health bar, and i want to give it a cistume value more than 1:
public Image healthBar;
// Use this for initialization
void Start () {
float health = 4;
healthBar.fillAmount = health;
Debug.Log (gameObject + ""+ healthBar.fillAmount);
}
Problem is no matter what value I give health, the fillAmount always goes back to 1. Is there any way to make it higher ?
fillAmount is a value of 0-1, with at 1 the full image being shown. There is no behavior yet for numbers over 1.
You could change the width of the image based on the maximum health and calculate the percentage that needs to be filled.
healthBar.rectTransform.rect.width = maxHealth * healthBarWidth;
healthBar.fillAmount = health / maxHealth;
If you don't want one bar, but multiple images like hearts that still are filled partially, you might be able to do it with a tiled image which automatically truncates at the edges.
I think you made a spelling error when creating the heath float. You call health when you declared "healt"
healthBar.fillAmount += health;
Try to do the increment in the fillAmount value, as it will assign the value you want it, and then change the code little bit in order to reach the exact number.
Hello everyone and anyone!
Ok... I have been banging my head against the wall with this issue for literally weeks, but I still cannot find an answer that has successfully resolved the issue.
I created an FLA and placed a FLV component with the instance name of videoPlay on the stage.
videoPlay is pathed to a streaming FLV with embedded event cue points. The cue points are numbered sequentially from narration1 to narration16.
I established a listener object:
var videoPlayCuePointListener:Object = new Object();
The event listener for the cue points:
videoPlayCuePointListener.cuePoint = function(eventObject:Object):Void{
if(eventObject.info.name == "narration1"){_root.cc_box.cc_txt.htmlText = cueTxt1);}
else if(eventObject.info.name == "narration2"){_root.cc_box.cc_txt.htmlText = cueTxt2);}
etc, through narration16 }
and so on through narration16.
Then I attached the event listener to the FLV component on stage:
videoPlay.addEventListener("cuePoint", videoPlayCuePointListener);
All of this works very well. As the FLV plays, each event cue point fires off the correct text to the cc_txt dynamic text box.
The issue I am having is that I cannot find the nearest cue point to the FLV playhead so that I can fire events when the user scrubs the timeline.
I have researched this as thoroughly as I possibly could before finally deciding to post the issue, but although the documentation and various postings regarding findNearestCuePoint discovered throughout the web have provided numerous examples, not a single one has been successful.
I have attempted to add a listener to videoPlay that creates an object (nearestCue) and gives nearestCue the value of videoPlay.findNearestCuePoint(videoPlay.playheadTime), then read out nearestCue's name, label, etc. No dice.
Nothing suggested in any posts I have reviewed (many, many posts) has provided an answer.
This seems like it would be the easiest thing to accomplish but I have not been successful a single time.
Any suggestions or assistance would be much appreciated.
Thank you for your time!
Haven't touched AS2 in a long time. I've done a basic test and findNearestCuePoint worked. You're using the FLVPlayback component, right ?
Here's what I've tried:
videoPlayer.autoPlay = false;
onEnterFrame = function():Void{
videoPlayer.seekPercent(_xmouse/Stage.width * 100);
videoPlayer.play();
trace(videoPlayer.findNearestCuePoint(videoPlayer.playheadTime).name);
}
The recommended way would be to find the nearest cue point in an playheadUpdate handler which is triggered after the playhead changes it's value. (e.g. 1. tell the playhead to move, 2. the playhead actually changes the value, 3. the playheadUpdate gets called)
Here's a more basic approach:
onEnterFrame = function():Void{
if(videoPlayer.metadata) trace(videoPlayer.findNearestCuePoint(_xmouse/Stage.width * videoPlayer.metadata.duration).name);
}
In my test I've added 4 cue points. Tried them all: actionscript/event/navigation.
The strange thing was when I tried to access the cuePoints property through videoPlayer
or through videoPlayer.metadata I got an array of 8 undefined objects, and the length of the array was 4 when I traced it. Don't know what the issue is, maybe encoding/codec and as2 compatibility, not sure.
Anyway...as long as you've got your cuePoints array, you can manually find the closest one by looping though all of them and getting the smallest absolute difference between each cue point time and the current time:
function getClosestCuePoint(cuePoints:Array,time:Number):Object{
var numCuePoints:Number = cuePoints.length;
var minDist:Number = 100000000,result:Object;
for(var i:Number = 0 ; i < numCuePoints ; i++){
if(Math.abs(cuePoints[i].time - time) < minDist){
minDist = Math.abs(cuePoints[i].time - time);
result = cuePoints[i];
}
}
return result;
}
Here's a mockup example: let's pretend some boxes on the screen are the cue points and the _xmouse position would be the playhead time. Try this in a new document:
//fake cue points
var numCuePoints:Number = 5;
var cuePoints = [];
for(var i:Number = 0 ; i < numCuePoints ; i++) cuePoints[i] = {name:'narration ' + (i+1),time: 10 + (80 + Math.random() * 20) * i}
//visual hint - separated from the cue points
for(var i:Number = 0 ; i < numCuePoints ; i++) drawBox(this,0x009900,10,15,cuePoints[i].time,Stage.width * .5);
var playhead:TextField = drawText(this,'playhead');
//playhead update
onEnterFrame = function():Void{
playhead._x = _xmouse;
playhead.setTextFormat(new TextFormat('Verdana',11));
playhead.text = 'time: ' + _xmouse+' / cue ' + getClosestCuePoint(cuePoints,_xmouse).name;
}
//find the shortest marker within the shortest distance from the current value
function getClosestCuePoint(cuePoints:Array,time:Number):Object{
var numCuePoints:Number = cuePoints.length;
var minDist:Number = 100000000,result:Object;
for(var i:Number = 0 ; i < numCuePoints ; i++){
if(Math.abs(cuePoints[i].time - time) < minDist){
minDist = Math.abs(cuePoints[i].time - time);
result = cuePoints[i];
}
}
return result;
}
//utils
function drawBox(target:MovieClip,color:Number,width:Number,height:Number,x:Number,y:Number):Void{
target.lineStyle(3,color);
target.moveTo(x,y);
target.lineTo(x+width,y);
target.lineTo(x+width,y+height);
target.lineTo(x,y+height);
target.lineTo(x,y);
}
function drawText(target:MovieClip,name:String):TextField{
var result:TextField = target.createTextField(name,target.getNextHighestDepth(),0,Stage.width * .5-20,100,20);
result.autoSize = 'left';
result.border = true;
result.selectable = false;
return result;
}
HTH
George,
I believe I discovered the issue, and I think it was something that you covered in your previous post, but I glossed over it by accident.
The F4V I was working with had the cue points embedded using Adobe Media Encoder... and that was the entire issue.
I went back and exported the cue points out to XML, then stripped them out of the F4V and re-encoded it without them. Then I edited the XML to change all of the event cue point to actionscript and imported them into the FLA file using the FLV component properties dialogue.
Presto, amazingly enough, I had no issues finding the cue points, tracing them, and using them for any purpose.
So in the future, I just need to remember to set up the cue points in the Properties dialogue and set them to actionscript and I should be golden. So far, it has worked flawlessly with all of the F4V files since making the change.
Thank you very much for your detailed response and your follow up!
I just started playing with the Task Parallel Library, and ran into interesting issues; I have a general idea of what is going on, but would like to hear comments from people more competent than me to help understand what is happening. My apologies for the somewhat lengthy code.
I started with a non-parallel simulation of a random walk:
var random = new Random();
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
var simulations = new List<int>();
for (var run = 0; run < 20; run++)
{
var position = 0;
for (var step = 0; step < 10000000; step++)
{
if (random.Next(0, 2) == 0)
{
position--;
}
else
{
position++;
}
}
Console.WriteLine(string.Format("Terminated run {0} at position {1}.", run, position));
simulations.Add(position);
}
Console.WriteLine(string.Format("Average position: {0} .", simulations.Average()));
stopwatch.Stop();
Console.WriteLine(string.Format("Time elapsed: {0}", stopwatch.ElapsedMilliseconds));
Console.ReadLine();
I then wrote my first attempt at a parallel loop:
var localRandom = new Random();
stopwatch.Reset();
stopwatch.Start();
var parallelSimulations = new List<int>();
Parallel.For(0, 20, run =>
{
var position = 0;
for (var step = 0; step < 10000000; step++)
{
if (localRandom.Next(0, 2) == 0)
{
position--;
}
else
{
position++;
}
}
Console.WriteLine(string.Format("Terminated run {0} at position {1}.", run, position));
parallelSimulations.Add(position);
});
Console.WriteLine(string.Format("Average position: {0} .", parallelSimulations.Average()));
stopwatch.Stop();
Console.WriteLine(string.Format("Time elapsed: {0}", stopwatch.ElapsedMilliseconds));
Console.ReadLine();
When I ran it on a virtual machine set to use 1 core only, I observed a similar duration, but the runs are no longer processed in order - no surprise.
When I ran it on a dual-core machine, things went odd. I saw no improvement in time, and observed some very weird results for each run. Most runs end up with results of -1,000,000, (or very close), which indicates that Random.Next is returning 0 quasi all the time.
When I make the random local to each loop, everything works just fine, and I get the expected duration improvement:
Parallel.For(0, 20, run =>
{
var localRandom = new Random();
var position = 0;
My guess is that the problem has to do with the fact that the Random object is shared between the loops, and has some state. The lack of improvement in duration in the "failing parallel" version is I assume due to that fact that the calls to Random are not processed in parallel (even though I see that the parallel version uses both cores, whereas the original doesn't). The piece I really don't get is why the simulation results are what they are.
One separate worry I have is that if I use Random instances local to each loop, I may run into the problem of having multiple loops starting with the same seed (the issue you get when you generate multiple Randoms too close in time, resulting in identical sequences).
Any insight in what is going on would be very valuable to me!
Neither of these approaches will give you really good random numbers.
This blog post covers a lot of approaches for getting better random numbers with Random
Link
These may be fine for many day to day applications.
However if you use the same random number generator on multiple threads even with different seeds you will still impact the quality of your random numbers. This is because you are generating sequences of pseudo-random numbers which may overlap.
This video explains why in a bit more detail:
http://software.intel.com/en-us/videos/tim-mattson-use-and-abuse-of-random-numbers/
If you want really random numbers then you really need to use the crypto random number generator System.Security.Cryptography.RNGCryptoServiceProvider. This is threadsafe.
The Random class is not thread-safe; if you use it on multiple threads, it can get messed up.
You should make a separate Random instance on each thread, and make sure that they don't end up using the same seed. (eg, Environment.TickCount * Thread.CurrentThread.ManagedThreadId)
One core problem:
random.Next is not thread safe.
Two ramifications:
Quality of the randomness is destroyed by race conditions.
False sharing destroys scalability on multicores.
Several possible solutions:
Make random.Next thread safe: solves quality issue but not scalability.
Use multiple PRNGs: solves scalability issue but may degrade quality.
...