Mängu ja võrgu optimeerimine (LibGDX ja KryoNet)¶
Optimeerimine (ingl. optimization) on programmi koodi efektiivsemaks tegemine. Mänguarenduses on see eriti vajalik, kuna arvuti peab 60 korda sekundis kalkuleerima ja renderdama maailma olekut ja loogikat.
Objektide Puulimine (Object Pooling)¶
Objektide loomine ning hävitamine võtab aega, Java puhul võtab objektide hävitamine sellest suure osa. Java Garbage Collector vabastab automaatselt objekte, mida enam ei kasutata. See võib tekitada suvalisi viivitusi programmi käigus.
Puulimine ehk Object Pooling on tehnika, kus taaskasutatakse objekte. See on eriti kasulik objektide puhul, mida luuakse sageli, näiteks kuulid või osakesed.
- Objektipuulidel on mõned puudused, millega tuleb arvestada:
Neil on maksimaalne maht, Libgdx puhul seda saab ületada kui on soov, kuid siis tekivad need probleemid, mispärast me puule tahame kasutada.
Kui ei ole tahet ületada maksimaalset mahtu, peab kas taaskasutama objekti, mis on ikka kasutusel, või ootama kuni on vabanenud objekt.
Puul kasutab mälu, isegi kui ükski selle objektidest pole kasutusel.
LibGDX pakub sisseehitatud klassi Pool<T>.
Pool<Bullet> bulletPool = new Pool<Bullet>() {
@Override
protected Bullet newObject() {
return new Bullet();
}
};
public void shootBullet(float x, float y) {
Bullet bullet = bulletPool.obtain();
bullet.init(x, y);
bullets.add(bullet);
}
public void removeBullet(Bullet bullet) {
bullets.removeValue(bullet, true);
bulletPool.free(bullet);
}
Klass Bullet peab implementeerima liidese Poolable, mis nõuab meetodit reset(). See
meetod kutsutakse välja automaatselt, kui objekt vabastatakse puuli tagasi. See on kriitiline, ilma
selleta jätkaks taaskasutatud objekt oma eelmise olekuga.
public class Bullet implements Pool.Poolable {
public float x, y;
public boolean alive;
public void init(float x, float y) {
this.x = x;
this.y = y;
this.alive = true;
}
@Override
public void reset() {
x = 0;
y = 0;
alive = false;
}
}
Ajutised arvutusobjektid (Temporary Scratch Objects)¶
Mänguloogikas on tavaliseks vajaduseks lühiajalised vektorid, näiteks suuna arvutamiseks kahe punkti vahel. Võib tekkida olukord, kus on vaja normaliseeritud suunavektorit:
Vector2 position;
Vector2 otherPosition;
void tick() {
// cpy() loob uue instantsi
Vector2 direction = position.cpy().sub(otherPosition).nor();
}
Siin teeb iga instants uue Vector2 objekti, 60 korda sekundis. Sellises olukorras oleks mõistlik
kasutada objekti, mis luuakse ühe korra terve klassiga:
Vector2 position;
Vector2 otherPosition;
Vector2 direction = new Vector2();
void tick() {
// set kopeerib positioni väärtused directioni sisse
direction.set(position);
direction.sub(otherPosition).nor();
}
Primitiivsed tüübid võrgusõnumites¶
Oletame, et server ja klient mõlemad sisaldavad Player objekti, mille positsioon on Vector2-na
hoitud. Võib tekkida tahe saata Vector2 objet sõnumis kliendile, kui see muutub. Selle tagajärel
peab Garbage Collector koristama vana positsiooni vektori. Mõistlikum võib olla saata primitiivsed
float väärtused:
class PlayerPositionMessage {
public float x;
public float y;
}
void sendPosition(Player player) {
sendUDP(new PlayerPositionMessage(player.position.x, player.position.y));
}
void receivePosition(PlayerPositionMessage m) {
player.setPosition(m.x, m.y);
}
Sõnumite pakkimine (Message Batching)¶
On efektiivsem saata üks sõnum iga positsiooniga, kui eraldi sõnum iga positsiooni jaoks.
public class BatchedPositionMessage {
public int[] ids;
public float[] x;
public float[] y;
public int count;
}
public void serverTick() {
packet.count = 0;
for (Player p : players) {
packet.ids[packet.count] = p.id;
packet.x[packet.count] = p.x;
packet.y[packet.count] = p.y;
packet.count++;
}
server.sendToAllUDP(packet);
}
Dirty flag, ainult muutunud andmete saatmine¶
Kui mängija seisab paigal, pole mingit põhjust saata tema asukohta 60 korda sekundis. Dirty flag on tehnika, kus objekt märgib ennast muutunuks, ehk dirtyks, kui tema olek muutub. Server saadab ainult neid objekte, mis on märgitud muutunuks.
public void serverTick() {
for (Player p : players) {
if (p.isDirty) {
sendPosition(p);
}
}
}
Kuid peaks ikka saatma infot objekti kohta, mis pole muutunud, mingisuguse intervalli tagant, et vältida desync olukorda.