Kuuliloogika¶
Serveri-kliendi suhtluse peamine reegel on jätta kõik olulised arvutused serverile. Shooter
-mängude kontekstis peaks server vastutama kuulilennu koordinaatide arvutamise ja tabamuste määramise eest ning klient hoolitsema ainult visuaalse kujutamise eest.
Kuulilennu trajektoori kujutamisviisid¶
Kuna tulistamise suund on harilikult sama mängija liikumissuunaga, siis eristatakse rohkemate piirangutega ning vähemate piirangutega kuulilennu trajektoori:
Kraadinurgaga trajektoor – enim levinud
top-down
-vaatega mängudes. Kuul saab liikuda iga kraadinurgaga alt.Horisontaalne trajektor – enim levinud
platformer
-vaatega mängudes. Kuul saab liikuda vaid paremale või vasakule lineaarselt ning saab rakendada füüsikatBox2D
-ga.

Kraadinurgaga tulistamisee näide hoopis platformer-vaatega mängus¶
Kuulilennu etapid ja serverilt tulev info¶
Kuulilend jaguneb kolmeks hästi eristatavaks serveri-kliendi suhtluse faasiks. Kuna server vastab alati viivitusega, pole mõistlik samu arvutusi teha mõlemal pool.
1. Tulistamine (klient saadab serverile)¶
Mängija hetkeasukoht ja liikumissuund.
Kas mängija saab ise sihtida (näiteks hiirega) ning on vaja arvutada sihitud-punkti kaugus mängijast või on tulistamise suund vaid liikumissuunaga seotud?
Kas vastase koordinaate on vaja? Kui kuulilennu arvutus toimub kliendi poolel (mittesoovitatav), võib vastane vahepeal liikuda, kuid uusi vastase koordinaate teab ainult server.

Mängijale rakendub tagasiviiv jõud ehk knockback tulistades¶
2. Kuulilend (server saadab kliendile)¶
Server arvutab kuulilennu koordinaadid ja saadab need klientidele joonistamiseks.
Kas trajektoor sõltub füüsikast? Kas kuul aeglustub/kiireneb? Kas kiiruse määral on piirang?
Kui trajektoor on lineaarne, kas kuvada seda animatsioonina või
Sprite
-ina?
![]() Lineaarne tulistamine¶ |
![]() Füüsikaga tulistamine¶ |
3. Tabamus või möödalask (server saadab kliendile)¶
Kui kuul tabab mängijat:
Füüsikaga mängus tuleb luua
hitbox
fixture
-i abil ning tagasilöögiks on vajaContactListener
-i.Füüsikata mängus, kuidas kaotada kuul pärast tabamist?
Kui kuul lendab mööda:
Millal kuul eemaldada? Kaamera vaateväljast lahkudes või füüsikaga mängus maastikku tabades?
Kas kuul tekitab kahju ka teistele objektidele maailmas? Mis raadiuses?

Maailma reageering kuulile¶
Kuuli loomine:¶
Box2D füüsika Body ja Fixture'iga:¶
Kasutatakse
Body
Sprite
-i jaoks ningFixture
-it kujuks,hitbox
-i tegemiseks ja pinnaga reageerimiseks.Realistlik erinevate impulsside ja jõudude tõttu, kuid
CPU
-le koormav.Võimalik ka ilma
Fixture
-it kasutamata, kui kuulitabamusele järgneb eriloogika.Box2D
maailmasse saab panna nii dünaamilisi, staatilisi kui ka kinemaatilisi kehasid ehk panna kuulile reageerima rohkem kui ainult mängijad.
Muud meetodid:¶
SpriteBatch
-iga joonistamine ningActor
-ite massiivi kuulideks.Actionite
Interpolation
-klass lubab veidi füüsikat jäljendada, et kuulilend täiesti lineaarne poleks.CPU
seisukohast keskmiselt koormav.Animation<TextureRegion>
kasutamine. Väga kasulik, kui palju eriefekte kujutavaid kuule.CPU
seisukohast keskmiselt kuni vähe-koormav.Stage
jaActor
-i kasutamine. Kõige lihtsaim haldamine, kuid võibCPU
ülekoormata.SpriteBatchiga
ainultSprite
-ide joonistamine – kõigeCPU
-sõbralikum, kuid nõuab palju käsitsi haldamist.
Koodinäited¶
Body ja Fixture loomine kuulile¶
BodyDef bodyDef = new BodyDef();
bodyDef.type = BodyDef.BodyType.DynamicBody;
bodyDef.position.set(startX, startY);
Body body = world.createBody(bodyDef);
CircleShape shape = new CircleShape(); // ümmargune hitbox
shape.setRadius(0.2f); // Box2D maailm on meetrites mitte pikselites
FixtureDef fixtureDef = new FixtureDef();
fixtureDef.shape = shape;
fixtureDef.density = 1f;
fixtureDef.friction = 0f;
fixtureDef.restitution = 1f; // kui bouncy
fixtureDef.filter.categoryBits = BULLET_CATEGORY; // Kui kasutad kategooriaid fixtureDef.filter.maskBits = ENEMY_CATEGORY; // kui tahad näidata, mis objektide vastu saab vaid põrkuda
Fixture fixture = body.createFixture(fixtureDef);
shape.dispose();
Vector2 direction = new Vector2((targetX - startX), (targetY - startY)).nor(); // liikumissuund
body.setLinearVelocity(direction.scl(10f));
SpriteSheeti mitme animatsiooni-kaadriga ühes reas:¶
Texture bulletTexture = new Texture(Gdx.files.internal("bullets.png"));
TextureRegion[][] frames2D = TextureRegion.split(bulletTexture, oneFrameWidth, oneFrameHeight);
TextureRegion[] frames1D = new TextureRegion[frameRecurrence];
for (int i = 0; i < frameRecurrence; i++) {
frames1D [i] = frames[0][i];
}
animation = new Animation<>(frameDuration, frames1D);
Tavaliselt on Spritesheet
loodud stiilis, et tegelased või objektid on vaikimisi paremale suunatud. Vasakule suunamiseks saab peegeldada frames1D [i].flip(true, false)
kaudu.
frameDuration
on vaja asendada float
väärtusega ning frameRecurrence
, oneFrameWidth
, oneFrameHeight
int
väärtusega.
Knockbacki loomine:¶
private void applyBulletHitForce() {
if (bulletHitForce != 0) {
body.applyForceToCenter(new Vector2(bulletHitForce, 0), true);
bulletHitForce *= 0.9f; // vaikselt jõud kaob
if (Math.abs(bulletHitForce) < Math.abs(bulletHitForce / 10f)) {
bulletHitForce = 0;
}
}
Objektidele füüsika lisamine¶
// bitimaskide andmine kokkupõrke määramiseks
public static final short LEVEL_BITS = 0x0001; // põrkub kuulidega, maailma elemdid nagu seinad ja maapind
public static final short FRIENDLY_BITS = 0x0002; // mängija
public static final short ENEMY_BITS = 0x0004; // põrkub kuulidega, vaenlane
public static final short NEUTRAL_BITS = 0x0008;
public static final short FOOT_SENSOR = 0x0010; // kas puutub maad mängija juures
public static final short RIGHT_WALL_SENSOR = 0x0020;
public static final short LEFT_WALL_SENSOR = 0x0040;
public void createPhysics(TiledMap map, String layerName, World world) {
MapLayer layer = map.getLayers().get(layerName);
MapObjects objects = layer.getObjects();
Iterator<MapObject> objectIt = objects.iterator();
while (objectIt.hasNext()) {
MapObject object = objectIt.next();
if (object instanceof TextureMapObject) continue;
Shape shape = null;
BodyDef bodyDef = new BodyDef();
bodyDef.awake = false;
bodyDef.type = BodyDef.BodyType.StaticBody;
if (object instanceof RectangleMapObject) {
shape = getRectangle((RectangleMapObject) object);
} else if (object instanceof PolygonMapObject) {
shape = getPolygon((PolygonMapObject) object);
} else if (object instanceof PolylineMapObject) {
shape = getPolyline((PolylineMapObject) object);
} else if (object instanceof CircleMapObject) {
shape = getCircle((CircleMapObject) object);
} else {
Gdx.app.log("Unrecognized shape", "" + object.toString());
continue;
}
FixtureDef fixtureDef = new FixtureDef();
fixtureDef.shape = shape;
fixtureDef.filter.categoryBits = 0x0001;
fixtureDef.filter.maskBits = (short) (PhysicsManager.FRIENDLY_BITS | PhysicsManager.ENEMY_BITS | PhysicsManager.NEUTRAL_BITS | PhysicsManager.FOOT_SENSOR | PhysicsManager.RIGHT_WALL_SENSOR | PhysicsManager.LEFT_WALL_SENSOR);
Body body = world.createBody(bodyDef);
body.createFixture(fixtureDef).setUserData("LEVEL");
fixtureDef.shape = null;
shape.dispose();
}
}
Hiljem saame ContactListener
-iga Collision
-i "LEVEL" kutsudes.