Merhaba,


Bu yazı serisinde Android Studio, Socket.io ve Mysql ile online oyun yapımından bahsediyorum.


Videolu anlatım için buraya tıklayınız.
Serinin ilk yazısı için buraya tıklayınız.
Serinin ikinci yazısı için buraya tıklayınız.
Serinin üçüncü yazısı için buraya tıklayınız.


Serinin bu dördüncü yazısında oyun ekranını tasarlayıp dinamik olarak çalıştırıyoruz.


Android Studio projesi üzerinde;


activity_game.xml dosyasını açarak şu kodları yazıyoruz ve oyun ekranı tasarımını yapıyoruz:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.activity.GameActivity">

    <TextView
        android:id="@+id/tvHome"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/text_player"
        android:fontFamily="@font/arbutus"
        android:layout_margin="10dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

    <TextView
        android:id="@+id/tvHomeScore"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/text_score"
        android:fontFamily="@font/arbutus"
        app:layout_constraintStart_toStartOf="@id/tvHome"
        app:layout_constraintEnd_toEndOf="@id/tvHome"
        app:layout_constraintTop_toBottomOf="@id/tvHome"/>

    <TextView
        android:id="@+id/tvAway"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/text_player"
        android:layout_margin="10dp"
        android:fontFamily="@font/arbutus"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

    <TextView
        android:id="@+id/tvAwayScore"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/text_score"
        android:fontFamily="@font/arbutus"
        app:layout_constraintStart_toStartOf="@id/tvAway"
        app:layout_constraintEnd_toEndOf="@id/tvAway"
        app:layout_constraintTop_toBottomOf="@id/tvAway"/>

    <TextView
        android:id="@+id/tvTime"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/text_time"
        android:fontFamily="@font/arbutus_slab"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginTop="50dp"
        android:background="@drawable/bg_circle"
        android:gravity="center"
        android:textColor="@color/white"
        android:textSize="18sp"/>

    <TextView
        android:id="@+id/tvQuestion"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/text_question"
        android:fontFamily="@font/arbutus"
        android:textSize="25sp"
        android:textColor="@color/colorPrimary"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tvTime"
        app:layout_constraintBottom_toTopOf="@id/etAnswer" />

    <EditText
        android:id="@+id/etAnswer"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:ems="10"
        android:inputType="number"
        android:fontFamily="@font/arbutus_slab"
        android:hint="@string/hint_answer"
        android:padding="10dp"
        android:background="@drawable/bg_rectangle"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent" />

    <Button
        android:id="@+id/bSend"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/button_send"
        android:background="@color/colorPrimary"
        android:fontFamily="@font/arbutus"
        android:textColor="@color/white"
        android:textAllCaps="false"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toBottomOf="@id/etAnswer"/>

</androidx.constraintlayout.widget.ConstraintLayout>


GameActivity.kt dosyasını açarak şu kodları yazıyoruz:

class GameActivity : AppCompatActivity() {

    lateinit var socket: Socket
    var answer  = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_game)

        socket = App.socket

        val home = intent.extras?.getString("home", "")
        val away = intent.extras?.getString("away", "")
        tvHome.text = home
        tvAway.text = away

        val players = JSONObject()
        players.put("home", home)
        players.put("away", away)
        socket.emit("game", players)

        bSend.setOnClickListener {
            val playerAnswer = etAnswer.text.toString()
            socket.emit("answer", playerAnswer)
            etAnswer.setText("")
        }

        socket
            .on("game") {
                runOnUiThread {
                    run {
                        val game = JSONObject(it[0].toString())
                        tvHomeScore.text = game.getInt("homeScore").toString()
                        tvAwayScore.text = game.getInt("awayScore").toString()
                        tvTime.text = game.getInt("time").toString()
                        tvQuestion.text = game.getString("question")
                        answer = game.getInt("answer")
                    }
                }
            }
            .on("time") {
                runOnUiThread {
                    run {
                        val time = it[0].toString()
                        tvTime.text = time
                    }
                }
            }
            .on("finish") {
                runOnUiThread {
                    run {
                        val data = JSONObject(it[0].toString())
                        val intent = Intent(applicationContext, ResultActivity::class.java)
                        intent.putExtra("homePlayer", data.getString("homePlayer"))
                        intent.putExtra("homeScore", data.getInt("homeScore"))
                        intent.putExtra("awayPlayer", data.getString("awayPlayer"))
                        intent.putExtra("awayScore", data.getInt("awayScore"))
                        intent.putExtra("result", data.getString("result"))
                        startActivity(intent)
                        finish()
                    }
                }
            }
            .on("homeScore") {
                runOnUiThread {
                    run {
                        val homeScore = it[0].toString()
                        tvHomeScore.text = homeScore
                    }
                }
            }
            .on("awayScore") {
                runOnUiThread {
                    run {
                        val awayScore = it[0].toString()
                        tvAwayScore.text = awayScore
                    }
                }
            }
            .on("questionAnswer") {
                runOnUiThread {
                    run {
                        val questionAnswer = JSONObject(it[0].toString())
                        tvQuestion.text = questionAnswer.getString("question")
                        answer = questionAnswer.getInt("answer")
                    }
                }
            }
    }
}

Burada yaptığımız işlemler;

  • Anasayfadan Intent ile gelen home ve away değerlerini bir json objesi içerisine alarak server tarafına game emit'i yaptık.
  • Server tarafından gelen game emit'ini karşıladık. Oyuncuların ilk skorlarını, sorusunu ve süresini ekrana yazdırdık. Ayrıca ilk sorunun cevabını da answer değişkenine aktardık.
  • Server tarafından gelen time emit'ini karşıladık. Böylece saniyede bir sürenin azalmasını sağladık.
  • Gönder butonuna basıldığında oyuncunun cevabını answer emit'i olarak gönderdik.
  • Server tarafından gelen homeScore ve awayScore emit'lerini karşılayarak oyuncuların skorlarını güncelledik.
  • Server tarafından gelen questionAnswer emit'ini karşılayarak yeni soruyu ve cevabı elde ettik.
  • Server tarafından gelen finish emit'ini karşılayarak oyuncuların adlarını, skorlarını ve oyunun sonucunu sonuç ekranına (ResultActivity) aktardık.


Node.js projesi üzerinde;


server.js dosyasını açarak şu kodları dahil ediyoruz:


Client tarafından gelen game emit'inin karşılandığı kısım:

socket.on('game', (players) => {
    let game = games.find(x => x.home.username == players.home && x.away.username == players.away);        
    let questionAnswer = makeQuestionAnswer();
    game.question = questionAnswer.question;
    game.answer = questionAnswer.answer;
    io.to(game.home.id).emit('game', game);
    io.to(game.away.id).emit('game', game);
    if (game.emit == false) {            
        let timer = setInterval(() => {
            if (game.time > 0) {
                game.time--;
                io.to(game.home.id).emit('time', game.time);
                io.to(game.away.id).emit('time', game.time);
            } else {
                let homePlayer = game.home.username;                    
                let homeScore = game.homeScore;
                let awayPlayer = game.away.username;
                let awayScore = game.awayScore;
                let result = getResult(homePlayer, homeScore, awayPlayer, awayScore);
                let data = { homePlayer, homeScore, awayPlayer, awayScore, result };
                io.to(game.home.id).emit('finish', data);
                io.to(game.away.id).emit('finish', data);
                clearInterval(timer);                    

                let homeUser = users.find(user => user.username === homePlayer);
                homeUser.status = true;
                let awayUser = users.find(user => user.username === awayPlayer);
                awayUser.status = true;
                io.emit('online_users', users);
            }
        }, 1000);
        game.emit = true;
    }        
});
  • Client tarafından gelen home ve away değerlerine göre games dizisi içerisindeki game objesini bulduk.
  • Soru ve cevap üretme fonksiyonu (makeQuestionAnswer) ile bir questionAnswer objesi oluşturduk. Soru ve cevabı game objesi üzerine aktarıp her iki kullanıcıya da emit ettik.
  • Eğer iki kullanıcıdan birisi game emit'i yaptıysa game objesi üzerindeki emit değerini true yaptık. Böylelikle soru, cevap ve süre değerlerinin iki kullanıcıda da aynı olmasını sağladık.
  • setInterval ile bir timer oluşturduk. 1 saniyede bir sürenin azalmasını sağladık.
  • Süre bittiğinde sonuçları almak için getResult fonksiyonuna gittik. Sonuçları her iki kullanıcıya da emit ettikten sonra timer'ı temizledik (clearInterval).
  • Oyuncuların müsaitlik durumunu müsait olarak güncelleyip emit ettik.


makeQuestionAnswer fonksiyonu:

function makeQuestionAnswer() {
    let number1 = Math.floor(Math.random() * 100) + 1;
    let number2 = Math.floor(Math.random() * 100) + 1;
    let question = number1 + " + " + number2 + " = ?";
    let answer = number1 + number2;
    let questionAnswer = {
        question,
        answer
    };
    return questionAnswer;
}
  • İki tane rastgele sayı oluşturuyoruz. Örneğin sayılar 2 ve 5 olsun. question değişkeni "2 + 5 = ?" şeklinde, answer değişkeni ise 7 şeklinde tanımlanıyor. Bu değişkenleri bir obje içerisine alıp geri döndürüyoruz.


getResult fonksiyonu:

function getResult(homePlayer, homeScore, awayPlayer, awayScore) {
    let result = '';
    if (homeScore > awayScore) {
        result = "Oyunu " + homePlayer + " kazandı!";
        connection.query("UPDATE users SET score = score + 3 WHERE username = '" + homePlayer + "'");    
    } else if (awayScore > homeScore) {
        result = "Oyunu " + awayPlayer + " kazandı!";
        connection.query("UPDATE users SET score = score + 3 WHERE username = '" + awayPlayer + "'");       
    } else {
        result = "Oyun berabere bitti!";
        connection.query("UPDATE users SET score = score + 1 WHERE username = '" + homePlayer + "'"); 
        connection.query("UPDATE users SET score = score + 1 WHERE username = '" + awayPlayer + "'");      
    }    
    return result;
}
  • Hangi oyuncunun skoru daha fazlaysa result değişkeni ona göre şekilleniyor. Örneğin "Oyunu aliveli kazandı!" gibi. Veritabanındaki users tablosunda kazanan kullanıcının skoruna 3 ekleniyor.
  • Oyun berabere bittiyse result değişkeni "Oyun berabere bitti!" şeklinde oluyor. Veritabanındaki users tablosunda da her iki kullanıcının skoruna 1 ekleniyor.


Client tarafından gelen answer emit'inin karşılandığı kısım:

socket.on("answer", (answer) => {
    let game = games.find(x => x.home.id == socket.id || x.away.id == socket.id);
    if (game.answer == answer) {
        if (game.home.id == socket.id) {
            game.homeScore += 10;
            io.to(game.home.id).emit('homeScore', game.homeScore);
            io.to(game.away.id).emit('homeScore', game.homeScore);             
        } else {
            game.awayScore += 10;
            io.to(game.home.id).emit('awayScore', game.awayScore);
            io.to(game.away.id).emit('awayScore', game.awayScore);             
        }               
        let questionAnswer = makeQuestionAnswer();
        game.question = questionAnswer.question;
        game.answer = questionAnswer.answer;
        let newQuestionAnswer = {
            question: game.question,
            answer: game.answer
        };            
        io.to(game.home.id).emit('questionAnswer', newQuestionAnswer);
        io.to(game.away.id).emit('questionAnswer', newQuestionAnswer);  
    } else {
        if (game.home.id == socket.id) {
            game.homeScore -= 5;
            io.to(game.home.id).emit('homeScore', game.homeScore);
            io.to(game.away.id).emit('homeScore', game.homeScore);             
        } else {
                game.awayScore -= 5;
                io.to(game.home.id).emit('awayScore', game.awayScore);
                io.to(game.away.id).emit('awayScore', game.awayScore);             
            }  
        }
    });
  • games dizisinin içindeki game objesini bulup, gelen cevap doğruysa, doğru yapan oyuncunun skoru 10 arttırılır. Yeni soru ve cevap oluşturulup oyunculara emit edilir. Cevap yanlışsa, yanlış yapan oyuncunun skoru 5 azaltılır.


Bir sonraki yazıda görüşmek üzere...