I'll share some technical notes about the transition effects since they were so fun.
Background Info
Three years ago I actually had a playable game. Then I transferred to a university, and dropped my game dev hobby when my obsession with math and computer science sparked up instead. When I returned to game dev earlier this year, I was disgusted by my code, especially its lack of encapsulation. After considering my options for how to clean up my code, I painfully decided that rewriting everything from scratch was the lesser headache.
This game was started in 2011 with the UDK engine, and I have refused to switch due to how much I enjoy the aesthetic I worked so hard to achieve with all the lighting and materials. UDK is not at all friendly with UI, so I write a lot of UI code from scratch. After months of my eyes bleeding through UI code, programming these transition effects has been a much needed change of pace.
Transition Effects
UDK (kind of?) allows fancy shader code, I think? Seems like most shader stuff is done in the editor interface rather than in code, which makes me sad. I chose to implement my transition effects with a big array of black sprite tiles instead.
The premise of the algorithm for transition effects that I outlined before coding is this:
- Add all tiles to a dynamic array, initialize visibility on (or off)
- Sort the array based on each tiles distance to an equation of your choice
- With each tick from a timer, randomly select "k" of the first "m" elements out the total array of "n" tiles
- Change the visibility of the selected tiles, and remove them from the list
- Repeat until list is empty
All I needed to accomplish this was a static array of tiles, dynamic array of tile indices and distance calculations, a custom comparitor for sorting tiles by distance, and a dynamic array of function pointers to delegate sorting equations for the various effects. I use the fancy delegate array so that I can customize what set of effects will be randomly picked from (notice fading into battle is always the vertical down one.)
I'm more of a C/C++ kind of guy, but I dont mind working in UDK's unrealscript language. Here's the code.
Data
// Tile count for transition effect
// 60x60 tiles, 1440x900 resolution, 1440/60 * 900/60
const TILE_COUNT = 360;
const TILES_PER_ROW = 24;
const COLUMN_PER_ROW = 15;
// Tile sorting information
struct TileInfo {
var int index;
var int distance;
};
var private array<TileInfo> tileIndices;
// Sprite tiles
var private GUISprite blackSquares[TILE_COUNT];
// Effect functions
var private array<delegate<tileSorter> > sorterEffects;
delegate int tileSorter(int index);
Algorithm
/*=============================================================================
* tileSort()
*
* Sorts tiles for transition effect
*===========================================================================*/
private function tileSort(delegate<tileSorter> sorter) {
local TileInfo newTile;
local int i;
// Reset array
tileIndices.length = 0;
// Populate list
for (i = 0; i < TILE_COUNT; i++) {
newTile.index = i;
newTile.distance = sorter(i);
tileIndices.addItem(newTile);
}
tileIndices.sort(tileComparison);
}
/*=============================================================================
* tileComparison()
*
* Compares distance between tiles. Negative result signifies out of order.
*===========================================================================*/
private function int tileComparison(TileInfo a, TileInfo b) {
if (a.distance < b.distance) {
return -1;
} else if (a.distance > b.distance) {
return 1;
} else {
return 0;
}
}
/*=============================================================================
* renderTiles()
*
* Timer allocated function for drawing tiles effects to the screen
*===========================================================================*/
private function renderTiles() {
local bool displayState;
local int index, k;
// Transition direction
displayState = (transitionStyle == TRANSITION_OUT);
for (k = 0; k < tilesPerTick; k++) {
// Check for remaining indices
if (tileIndices.length == 0) {
endTransition();
return;
}
// Render a random batch from the sorted list
index = rand(20);
if (index >= tileIndices.length) index = rand(tileIndices.length);
blackSquares[tileIndices[index].index].setEnabled(displayState);
tileIndices.remove(index, 1);
}
}
Effect Equations
/*=============================================================================
* sortMethod1()
*
* Circle effect
*===========================================================================*/
private function int sortMethod1(int index) {
// Distance to x=1/2, y=1/2
return distanceFormula(
blackSquares[index].posX,
blackSquares[index].posy,
NATIVE_WIDTH / 2,
NATIVE_HEIGHT / 2
);
}
/*=============================================================================
* sortMethod2()
*
* Four corners effect
*===========================================================================*/
private function int sortMethod2(int index) {
local float d1, d2, d3, d4;
// Distance to MIN(four corners)
d1 = distanceFormula(
blackSquares[index].posX,
blackSquares[index].posy,
0,
0
);
d2 = distanceFormula(
blackSquares[index].posX,
blackSquares[index].posy,
NATIVE_WIDTH,
0
);
d3 = distanceFormula(
blackSquares[index].posX,
blackSquares[index].posy,
0,
NATIVE_HEIGHT
);
d4 = distanceFormula(
blackSquares[index].posX,
blackSquares[index].posy,
NATIVE_WIDTH,
NATIVE_HEIGHT
);
d1 = fMin(d1, d2);
d2 = fMin(d3, d4);
return fMin(d1, d2);
}
/*=============================================================================
* sortMethod3()
*
* Vertical lines effect
*===========================================================================*/
private function int sortMethod3(int index) {
local float d1, d2;
// Distance to MIN(x=1/3, x=2/3)
d1 = abs(blackSquares[index].posX - (NATIVE_WIDTH / 4));
d2 = abs(blackSquares[index].posX - (3 * NATIVE_WIDTH / 4));
return fMin(d1, d2);
}
/*=============================================================================
* sortMethod4()
*
* Horizontal line effect
*===========================================================================*/
private function int sortMethod4(int index) {
// Distance to Y = 1/2
return abs(blackSquares[index].posY - (NATIVE_HEIGHT / 2));
}
/*=============================================================================
* sortMethod5()
*
* Randomized effect
*===========================================================================*/
private function int sortMethod5(int index) {
// Random method
return rand(10);
}
/*=============================================================================
* sortMethod6()
*
* Vertical curtain effect, used only for opening combat
*===========================================================================*/
private function int sortMethod6(int index) {
// Distance to Y = 1
return abs(blackSquares[index].posY - NATIVE_HEIGHT);
}
Final thoughts
Even though the mixing of 2D / 3D is the most frequently criticized design choice in my work, I'm personally happy with how this is turning out aesthetically.