Multi-Dimensional Arrays
What is it?
Hopefully, you've had some experience with arrays. In Danmakufu, they're a bit different than most other arrays in other languages, but the functionality is about the same: they store multiple values under the same name. For example, instead of having 10 different variables called Name1, Name2, Name3, Name4, etc., you can just have an array with a length of 10 storing all 10 names. However, what happens if, instead of putting numbers of strings in an array, you put more arrays?
This is what we call a multi-dimensional array. Arrays in arrays have their uses in organizing information more easily than single split arrays. However, they do tend to be more confusing to use than normal arrays. An array containing more arrays is called a two-dimensional array. If those arrays in turn also contain arrays, then it becomes three-dimensional. This pattern can go on indefinitely, meaning it is possible to make a 49205-dimensional array if you really really wanted to.
A Word of Warning
Do note that because Danmakufu is rather quirky, the construction and usage of multi-dimensional arrays may become tricky at times.
First of all, as you may remember, Danmakufu arrays can only hold one type of value: either numbers, strings, booleans, or arrays. If you try to mix these in an array, Danmakufu will raise an error. Well, arrays containing different types are also considered different types. If you try to put a string array with a number array in an another array, Danmakufu will raise an error. However, different length arrays are allowed to be in the same array.
Incidentally, you CAN change the type of value an array stores if you empty it first. Basically, any array that is just [] can become whatever you want it to be, even if it wasn't originally empty.
One last warning while dealing with multi-dimensional arrays in Danmakufu: Danmakufu can only change values one level deep. What I mean by this is that, while Danmakufu can retrieve values from, let's say, a two-dimensional array with let value = ArrayName[0][2]. It cannot set that value with Arrayname[0][2] = 5. The only way to change a single value would be to reconstruct the entire inner array. As you can probably imagine, the more dimensions the array has, the more of a pain it becomes to manage it.
Why would you use this?
In general, multi-dimensional arrays are used to keep track of information. Tasks are generally used to manage the information in the arrays though there are some exceptions.
One common use is to manage multiple instances of something. One instance of that something's attributes are stored as numbers in an array and every instance of that something is stored in a two-dimensional array. For example, if you wanted to draw a variable number of effects, but you wanted to use @DrawLoop instead of effect objects, you would need to store the positions of the effects. In that case, each array in the two-dimensional array would correspond to one of the effects. The first value in the inner array would be the x-coordinate and the second value would be the y-coordinate. For example, EffectArray[3][0] and EffectArray[3][1] would give the x- and y-coordinates for the 4th (0 is the first element in an array) effect. Of course you can store more information in that array such as alpha, color, blending styles, etc.
As a side note, many things you may not expect to be numbers are actually stored as numbers in Danmakufu. For example, whenever you call RED01 without a shot replace, it is really looking at a predefined variable that holds the number -65536. Other things like ADD, ALPHA, PRIMITIVE_TRIANGLESTRIP, NULL, YOUMU, and KOUMA -- basically anything in all caps and no quotes -- are also stored as numbers. This will make storing attributes as numbers much easier so that you don't get conflicting types in your arrays.
Examples
General example:
let 2DArray = [[0, 1, 2, 3], [2, -5, 39.20, 20], [305938.2834, -0.00002, 1.1111, RED05]];
let 3DArray = [[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]], [[13, 14, 15], [16, 17,18]]];
let BetterOrganize2DArray = [
["hi", "hello" ],
["bye", "goodbye"],
["yo", "wassup" ]
];
let BetterOrganized3DArray = [
[
[10, 20, 30, 40 ],
[50, 60, 70, 80 ],
[90, 100, 110, 120]
],
[
[11, 21, 31, 41 ],
[51, 61, 71, 81 ],
[91, 101, 111, 121]
],
[
[15, 25, 35, 45 ],
[55, 65, 75, 85 ],
[95, 105, 115, 125]
]
];
There's many different ways to organize how your arrays look. the first two examples condense everything onto one line, which is fine if you don't really need to look at it. However, it's incredibly hard to tell which element of each array belongs to which indexes and therefore hard to get the correct value out when it is needed. The other two examples show how to organize it so you can more easily tell what each number corresponds to. It will take up more lines, but in the long run it'll be easier to read and maintain.
An example on changing values in a 2D array:
let Array = [
[0, 1, 2, 3, 4],
[1, 3, 5, 7, 9],
[2, 4, 6, 8, 10]
];
let Variable = Array[1][3]; // Perfectly valid. Variable now holds the value of 7.
Array[1][3] = 8; // Not valid. Danmakufu cannot redefine a value more than 1 level deep.
Array[1] = [1, 3, 5, 8, 9]; // Valid and the only way to change a value more than 1 level deep.
An example to manage falling and rotating snowflakes in a 3D background (this example is rather long):
sub DrawSnowFlakes {
SetTexture(imgSnowFlake);
SetGraphicRect(0, 0, 64, 64);
ascent(i in 0..length(SnowFlakeData)){
SetGraphicAngle(SnowFlakeData[i][6], SnowFlakeData[i][7], SnowFlakeData[i][8]);
DrawGraphic3D(SnowFlakeData[i][0], SnowFlakeData[i][1], SnowFlakeData[i][2]);
}
}
task RunSnowFlakes(UpperXBound, LowerXBound, UpperYBound, LowerYBound, UpperZBound, LowerZBound){
LoadGraphic(imgSnowFlake); // Load the graphic.
CreateSnowFlakes; // Make it create snowflakes periodically.
UpdateLogic; // Make it update the snowflakes every frame.
task CreateSnowFlakes {
loop {
loop(60){yield;} // Wait 60 frames;
SnowFlakeData = SnowFlakeData ~ [[ // Create a new snowflake
rand(LowerXBound, UpperXBound), // at a random x-pos between bounds
UpperYBound, // at the top of the y bound
rand(LowerZBound, UpperZBound), // at a random z-pos between bounds
rand(-1, 1), // with a random x-speed between -1 and 1
-1, // with a y-speed of -1
rand(-1, 1), // with a random z-speed between -1 and 1
rand(0, 360), // at a random x-angle
rand(0, 360), // at a random y-angle
0, // at a z-angle of 0
rand(-5, 5), // with a random x-rotation between -5 and 5
rand(-5, 5), // with a random y-rotation between -5 and 5
0 // with no z-rotation
]];
}
}
task UpdateLogic {
loop {
descent(i in 0..length(SnowFlakeData)){ // For every snowflake...
if( // If the snowflake's...
SnowFlakeData[i][0] > UpperXBound || // x-pos is greater than the upper x bound
SnowFlakeData[i][0] < LowerXBound || // or less than the lower x bound
SnowFlakeData[i][1] > UpperYBound || // or its y-pos is greater than the upper y bound
SnowFlakeData[i][1] < LowerYBound || // or less than the lower y bound
SnowFlakeData[i][2] > UpperZBound || // or its z-pos is greater than the upper z bound
SnowFlakeData[i][2] < LowerZBound // or less than the lower z bound
){
SnowFlakeData = erase(SnowFlakeData, i); // then delete the snowflake.
}
else { // Otherwise...
SnowFlakeData[i] = [
SnowFlakeData[i][0] + SnowFlakeData[i][3], // Add x-speed to x-pos
SnowFlakeData[i][1] + SnowFlakeData[i][4], // Add y-speed to y-pos
SnowFlakeData[i][2] + SnowFlakeData[i][5], // Add z-speed to z-pos
SnowFlakeData[i][3], // x-speed stays the same
SnowFlakeData[i][4], // y-speed stays the same
SnowFlakeData[i][5], // z-speed stays the same
SnowFlakeData[i][6] + SnowFlakeData[i][9], // Add x-rotate to x-angle
SnowFlakeData[i][7] + SnowFlakeData[i][10], // Add y-rotate to y-angle
SnowFlakeData[i][8] + SnowFlakeData[i][11], // Add z-rotate to z-angle
SnowFlakeData[i][9], // x-rotate stays the same
SnowFlakeData[i][10], // y-rotate stays the same
SnowFlakeData[i][11] // y-rotate stays the same
];
}
}
yield;
}
}
}
The parts you should pay attention to is how I manage the different attributes in the array in the nested UpdateLogic task as well as how I use the attributes in the DrawSnowFlakes sub. If you're wondering why the creation logic has the z-angle and z-rotate set at 0, it's because I noticed that the flipping effect was extremely strange when done in Danmakufu. The logic would work though if you changed those values.
And an example of bad multi-dimensional arrays:
let MismatchedValueArray = [
["lol", "hi", "rawr"],
[1, 2, 3 ]
];
let JustPlainWrongArray = [
[1, 2, 3, "hi", "lol", [11, -49]],
"like zomg",
18,
BLUE32
];
Both of these examples will raise an error in Danmakufu. Strangely, these are perfectly valid arrays in other langauges.