Käigupõhise mängu eripärad¶
Käigupõhisus on mänguloogika, kus mängijad ei käi samaaegselt vaid kordamööda. Ehk üks mängija korraga valib oma tegevuse enne teisi ning see käiguvalik on teistele mängijatele nähtav, et ajavahe ei omandaks strateegilist mõju.
Seetõttu on käigupõhised mängud sageli:
Taktikalisemad: Mängijad saavad oma samme hoolikalt ette planeerida ja vastase tegevusi hoolikamalt jälgida (näiteks male ja pokker).
Sobivad aeglasemate refleksidega mängijatele: Kiire reageerimine vahetult toimuvatele sündmustele ei ole osa strateegiast.

Ebarealistlik võistluse stseen, sest mängijste käigukord ei toimu samal ajal.
Käigupõhised mängud kipuvad olema:
Reegliterohked.
Aeglasema tempoga
multiplayer
-mängudes: Pikemate ooteaegade vältimiseks on soovitatav rakendada ajapiiranguid. Mängijad, kes limiidist üle lähevad, kaotavad oma käigu.Üksluisemad pärast mitmeid läbimängimisi: Käigumustrid muutuvad sageli korduvaks ja seetõttu ettearvatavaks.
Alternatiivid "üks mängija, üks käik" struktuurile¶
Ajapõhine käikude arv: Käigu jooksul on lubatud teha ükskõik kui palju käike, kuid kindla ajavahemiku piires.
Fikseeritud arv käike: Ühe käigu asemel võib olla suurem, kuid fikseeritud arv mängukäike.
Progressikellad: visualiseerimaks sündmusi, mis kestavad kauem kui üks käik. Need aitavad mängijatel jälgida pikaajalisi arenguid, nagu tegelase edenemine lõppeesmärgi suunas, vastase lähenemise või ajaliselt piiratud võimaluste avamine.
![]() |
![]() |
Progressikellade näide nii võrdlev kell kui ka Inventory laadne.
Käigukorra järjekord¶
Liitumisjärjekorra alusel.
Rollide järgi ehk mängusisene hierarhia määrab käigujärjekorra (näiteks "Dungeons & Dragons").
Juhuslikkuse alusel, näiteks täringuviskega mängus "Wizard101".
Ettearvatavuse vähendamine¶
Käigukorra muutmine: “Dungeons & Dragons” mängus on rünnak “attacks of opportunity” mängijatele võimalust käia oma käik enne oma korda ette ära.
Käigupõhise ja reaalajas mängu hübriid: sarnaselt vabalöögiga jalgpallis ja vabaviskega korvpallis on niimoodi strateegiate kombineerimise eesmärk teha taktikalisemaks mängufaase, millest mängutulemus suuresti sõltub ja dünaamilisemaks teise näiteks ringiuitamist. Näiteks “Fallout”’is on lahingufaas käigupõhine, kuid ülejäänud mäng toimub reaalajas.
Mängukäikudele lisapiirangute ja -võimaluste seadmine: erilised käikud, näiteks “King Arthur: The Role-Playing Wargame” saab hooneid ehitada vaid talvel, mis saabub iga neljanda käigu järel. Sõjamängus “Napoleon” on iga mängija kolmas mängukäik “öökäik,” mille jooksul lahingupidamine pole lubatud.

Muidu reaalajas toimuv mäng muudetakse käigupõhiseks võistluse stseeni ajal, et paremat sihtimist võimaldada.
Serveri kood käigupõhiseks virtuaalseks lauamänguks¶
Packetite ja Player klassi loomine
Kõigepealt loome 4 Packetit ja Player
klass. Käigupõhist kaardimängu luues pole vaja mängija koordinaate saata ehk PositionPacket
on üleliigne.
ConnectionPacket
PositionPacket
ScorePacket
CurrentActivePlayerPacket
ConnectionPacket
ja CurrentActivePlayerPacket
sisaldavad ühte muutujat, milleks on int id
muutujat ja meetodeid getId
ning setId
.
PositionPacket
ja ScorePacket
sisaldavad kõik kahte muutujat. PositionPacket
sisaldab int id
ja Vector2
(mängija x ja y koordinaadid) ning mõlemal muutujal on get
ja set
meetodid. ScorePacket
'il on int id
ja int score
muutujad, kuid ühtegi meetodi jaoks pole vajadust.
Player klass
Muutujad
- private Vector2 position
- private int id
- private boolean yourTurn = false
- private int currentPoints
(algväärtuse seame konstruktoris nulliks)
Kõigile muutujatele peale yourTurn
loome lisaks harilikul viisil get
ja set
meetodid väljaarvatud setCurrentPoints
meetod, kus punkte määratakse juurdeliitmisega olemasolevale punktisummale. Loome ka updatePosition
meetodi, sest kliendi pool täpsustame x
ja y
koordinaate.
public class Player {
private Vector2 position; // lisaks get ja set meetod
private int id; // lisaks get ja set meetod
private int currentPoints; // lisaks get meetod
private boolean yourTurn = false
public Player() {
this.currentPoints = 0;
}
public void setCurrentPoints(int points) {
this.currentPoints += points;
}
public void flipTurn() {
yourTurn = !yourTurn;
}
public void updatePosition(Vector2 position) {
this.position = position;
}
}
ChatServer klass
Muutujad
- private static Server server
- private HashMap<Integer, Player> playersId = new HashMap<>()
- private int currentActiveIndex = 0
- private boolean gameStarted = false
ChatServer
konstruktoris loome uue serveri instantsi ja registreerime selle Network
'i.
public ChatServer() throws IOException {
Log.set(Log.LEVEL_WARN);
server = new Server();
Network.register(server);
Chatserver
konstruktorisse panema ka Event Listener
'i, mille sees on kolm meetodit:
connected
received
disconnected
Connected meetod
Alustuseks Loome uue Player
instantsi ja lisame ta playersId HashMap
'i ning saadame ConnectionPacket
'i teistele mängijatele.
public void connected(Connection c) {
if (!gameStarted) {
Player player = new Player();
player.setId(c.getID());
playersId.put(c.getID(), player);
ConnectionPacket packet = new ConnectionPacket();
packet.setId(c.getID());
// teavitame teisi uuest mängijast
server.sendToAllExceptTCP(c.getID(), packet);
for (Player player : playersId.values()) {
if (player.getId() != c.getID()) {
// saadame teiste mängijate positsioonid uuele mängijale
ConnectionPacket connectionPacket = new ConnectionPacket();
connectionPacket.setId(player.getId());
server.sendToTCP(c.getID(), connectionPacket);
}
}
}
}
connected
meetod lõppeb ühenduse sulgemisega kui proovitakse uut mängu alustada, serveris, kus juba mäng käib.
} else {
c.close();
}
Disconnected meetod
Siin ühtegi Packet
'it ei saada.
public void disconnected(Connection c) {
playersId.remove(c.getID());
sendMessageToAllExcept(c.getID(), "User #%d has left the chat.".formatted(c.getID()));
if (playersId.isEmpty()) {
playersId = new HashMap<>();
currentActiveIndex = 0;
}
}
Received meetod
Sama Event Listener
i sees kontrollime, kas kliendi poolt on saadetud PositionPacket
, et uuendada mängija asukohta ja jagada seda asukohta teiste mängijatega.
if (o instanceof PositionPacket pkg) {
Player player = playersId.get(c.getID());
Vector2 vector2 = pkg.getVector2();
player.updatePosition(vector2);
server.sendToAllExceptTCP(c.getID(), pkg);
}
ScorePacket
, et uuendada nii mängija skoori, kui ka määrata, millise mängija kord nüüd on käia (ehk saadame ka CurrentActivePlayerPacket
'i), sest ScorePacket
saadetakse käigu lõpus.
if (o instanceof ScorePacket pkg) {
Player player = playersId.get(pkg.id);
player.setCurrentPoints(pkg.score);
ScorePacket scorePacket = new ScorePacket();
scorePacket.id = pkg.id;
scorePacket.score = pkg.score;
server.sendToAllExceptTCP(c.getID(), scorePacket);
currentActiveIndex = (currentActiveIndex + 1) % playersId.size();
List<Integer> id = new ArrayList<>(playersId.keySet());
int currentId = id.get(currentActiveIndex);
CurrentActivePlayerPacket packet = new CurrentActivePlayerPacket();
packet.setId(currentId);
server.sendToAllUDP(packet);
}
Seejärel saame Event Listener
’i sulgeda ning lisame konstruktorisse veel serveri käivitamise.
server.start();
server.bind(Network.TCP_PORT, Network.UTP_PORT);
Peale konstruktori vajab ChatServer
klass lisaks veel sendMessageToAllExcept
meetodit.
private void sendMessageToAllExcept(int clientId, String text) {
Network.Message msg = new Network.Message();
msg.text = text;
server.sendToAllExceptUDP(clientId, msg);
}
Ära unusta Network
klassis kyro.register
teha kõigile Packetitele, Player
klassile ning Chatserver
'ile.
Kliendi kood käigupõhiseks virtuaalseks lauamänguks¶
Kõik Packetid ja Player
klass, mida serveri repository's kasutasime on identsed kliendipoolsega. Vaid updatePosition
meetod on kliendi klassis teistsugune.
public void updatePosition(float x, float y) {
this.position.x = x;
this.position.y = y;
}
MyClient klass
Loome MyClient
klassi ChatServer
klassiga suhtlemiseks. kui serveri pool oli kolm meetodit Event Listener
'i all, siis kliendi pool on vaid received
meetod, kuid siid väljaspool konstrukorit on siin klassis rohkem get
ja set
meetodeid.
Muutujad
- private final Client client
- private final int clientID
- private boolean yourTurn = false
- private boolean firstPlayer = false
- private final HashMap<Integer, Player> playersId = new HashMap<>()
public MyClient(LobbyScreen lobbyScreen) {
client = new Client();
Network.register(client);
client.addListener(new Listener.ThreadedListener(new Listener() {
@Override
public void received(Connection connection, Object object) {
if (object instanceof ConnectionPacket pkg) {
Player Player = new Player();
Player.setId(pkg.getId());
playersId.put(pkg.getId(), Player);
}
if (object instanceof PositionPacket pkg) {
Player player = playersId.get(pkg.getId());
if (player != null) {
ee.taltech.mygdxgame.packets.Vector2 vector2 = pkg.getVector2();
float x = vector2.x;
float y = vector2.y;
player.updatePosition(x, y);
}
}
if (object instanceof ScorePacket pkg) {
int score = pkg.score;
int id = pkg.id;
Player player = playersId.get(id);
player.setCurrentPoints(score);
}
if (object instanceof CurrentActivePlayerPacket pkg) {
yourTurn = clientID == pkg.getId();
}
}
}));
client.start();
try {
client.connect(5000, "193.40.255.27", 8080, 8081);
} catch (IOException e) {
throw new RuntimeException(e);
}
clientID = client.getID();
}
public int getID() {
return client.getID();
}
public void sendUDP(Object object) {
client.sendUDP(object);
}
public void sendTCP(Object object) {
client.sendTCP(object);
}
public Map<Integer, Player> getPlayersId() {
return playersId;
}
public boolean isYourTurn() {
return yourTurn;
}
public Client getClient() {
return client;
}
Põhimängu klass
Mängijate positsiooni uuendamine ja joonistamine toimub mängu põhiklassis. Kõigepealt on vaja muutujat private Map<int, Player> playersId
.
Meil on vaja render
meetodis enne mängija käiku (ehk enne batch.begin()
) mängija asukohta uuendada, saates PositionPacketi
.
if (client.getPlayersId().size() > 0) {
client.getPlayersId().get(clientID).setPosition(this.x, this.y);
PositionPacket positionPacket = new PositionPacket();
positionPacket.setId(clientID);
positionPacket.setVector2(new ee.taltech.mygdxgame.packets.Vector2(this.x, this.y));
client.sendUDP(positionPacket);
}
Peale mängija käiguvalikut on vaja saata ScorePacket
.
ScorePacket scorePkg = new ScorePacket();
scorePkg.id = clientID;
scorePkg.score = 0;
getClient().sendTCP(scorePkg);
Järgmisena saab kõiki mängijaid luua, andes neule uue positsiooni Player
klassi meetodi kaudu (järgnevas koodis on kahe mängija ja solo-player positsiooni uuendamise näide).
for (Map.Entry<Integer, Player> entry : client.getPlayersId().entrySet()) {
int id = entry.getKey();
Player player = entry.getValue();
float playerX = client.getPlayersId().get(id).getPosition().x;
float playerY = client.getPlayersId().get(id).getPosition().y;
if (client.getPlayersId().size() == 2) {
player = new Player();
player.setTexture(pic2);
player.setPosition(new Vector2(playerX, playerY));
} else {
player.updatePosition(playerX, playerY);
player.setTexture(pic1);
}
}
Siis saame mängijaid joonistama hakata nende asukoha järgi.
batch.begin();
for (Player player : client.getPlayersId().values()) {
Vector2 playerPosition = player.getPosition();
if (client.getPlayersId() == 1) {
batch.draw(pic1, playerPosition.x, playerPosition.y);
}
if (client.getPlayersId() == 2) {
batch.draw(pic2, playerPosition.x, playerPosition.y);
}
}
batch.end()
PS!
Hetkel joonistame render
meetodis mängijad iga kord uuesti Texture
(PNG) järgi. Protsessori-sõbralikum oleks:
Luua üks Atlas-fail kõigi karakterivalikute
Texture
'itega.Lobby Screenil teha (Event)
ChangeListener
, mis vahetabActor
'eid mängija karakterivaliku järgi.Teha Sprite'i päriv klass, kus
super
märgusõnaga konstruktoris saab Atlas-failist leida valitud karakteriscreen.getAtlas().findRegion()
'iga.Main
klassis teha SpriteBatchile muutuja.Siis saame peamise mänguklassi
render
'is kõiki mängijaid joonistada üherealise koodiga:main.getPlayers().getValue().getSprite().draw(main.getBatch())
.