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 bu üçüncü yazısında anasayfa ekranını tasarlayıp dinamik olarak çalıştırıyoruz. Online kullanıcılara oyun isteği yapıp, isteğin kabul ve red durumlarını inceliyoruz.
Android Studio projesi üzerinde;
activity_main.xml dosyasını açarak şu kodları yazıyoruz ve anasayfa 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.MainActivity"> <TextView android:id="@+id/tvWelcome" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="10dp" android:fontFamily="@font/arbutus_slab" android:text="@string/text_welcome" android:textSize="20sp" android:gravity="center" android:textColor="@color/colorPrimary" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <ImageView android:id="@+id/ivLeaderboard" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/leaderboard" android:layout_marginTop="10dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tvWelcome" /> <TextView android:id="@+id/tvOnlineUsers" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/text_online_users" android:fontFamily="@font/arbutus" android:textSize="20sp" android:gravity="center" android:layout_margin="10dp" android:textColor="@color/colorPrimaryDark" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/ivLeaderboard" /> <View android:id="@+id/view" android:layout_width="match_parent" android:layout_height="1dp" android:background="@color/colorPrimaryDark" android:layout_margin="10dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tvOnlineUsers" /> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rvOnlineUsers" android:layout_width="match_parent" android:layout_height="0dp" android:layout_margin="10dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/view" /> </androidx.constraintlayout.widget.ConstraintLayout>
Anasayfada bulunan RecyclerView içerisinde online kullanıcılar listelenecek. Listede bulunacak elemanların tasarımı için de list_item.xml adında bir layout dosyası oluşturup içeriğini şu şekilde dolduruyoruz:
<?xml version="1.0" encoding="utf-8"?> <androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="10dp"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" android:padding="10dp"> <ImageView android:id="@+id/ivStatus" android:contentDescription="@string/app_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_available" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" /> <TextView android:id="@+id/tvUserName" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/text_player" android:textSize="20sp" android:fontFamily="@font/arbutus_slab" android:layout_marginStart="10dp" android:textColor="@color/available" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toEndOf="@id/ivStatus" /> </androidx.constraintlayout.widget.ConstraintLayout> </androidx.cardview.widget.CardView>
Burada kullanıcıların müsaitlik durumu ve kullanıcı adı görüntülenecek. Müsaitlik durumunu simgeleyen ikonları da drawable klasörü içine ic_available.xml (müsait) ve ic_busy.xml şeklinde iki dosya açarak şu şekilde oluşturuyoruz:
ic_available.xml:
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:tint="@color/available" android:viewportWidth="24.0" android:viewportHeight="24.0"> <path android:fillColor="#FF000000" android:pathData="M12,12m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"/> </vector>
ic_busy.xml:
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:tint="@color/busy" android:viewportWidth="24.0" android:viewportHeight="24.0"> <path android:fillColor="#FF000000" android:pathData="M12,12m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"/> </vector>
Online kullanıcılar listesini dinamik şekilde çalıştırmak ve kullanıcılara tıklayıp oyun isteği yapabilmek için model ve adapter sınıflarına ihtiyacımız olacak. Projemizin paket adının bulunduğu klasörde şu şekilde bir paket yapısı oluşturuyoruz:
com.yusufborucu.onlinemathgame
-> model
-> ui
--> activity
--> adapter
App.kt
model klasörü içerisine User.kt adında bir veri sınıfı (data class) oluşturuyoruz:
package com.yusufborucu.onlinemathgame.model data class User ( val id: String = "", val username: String = "", val status: Boolean = true )
adapter klasörü içerisine de UserAdapter.kt adında bir adapter oluşturuyoruz:
package com.yusufborucu.onlinemathgame.ui.adapter import android.app.AlertDialog import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast import androidx.core.content.ContextCompat import androidx.recyclerview.widget.RecyclerView import com.yusufborucu.onlinemathgame.App import com.yusufborucu.onlinemathgame.R import com.yusufborucu.onlinemathgame.model.User import kotlinx.android.synthetic.main.list_item.view.* class UserAdapter(private val users: MutableList<User>): RecyclerView.Adapter<UserAdapter.UserViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false) return UserViewHolder(view) } override fun getItemCount(): Int { return users.size } override fun onBindViewHolder(holder: UserViewHolder, position: Int) { val user = users[position] if (user.status) { holder.itemView.ivStatus.setImageResource(R.drawable.ic_available) holder.itemView.tvUserName.setTextColor(ContextCompat.getColor(holder.itemView.context, R.color.available)) } else { holder.itemView.ivStatus.setImageResource(R.drawable.ic_busy) holder.itemView.tvUserName.setTextColor(ContextCompat.getColor(holder.itemView.context, R.color.busy)) } holder.itemView.tvUserName.text = user.username holder.itemView.setOnClickListener { if (user.status) { val builder = AlertDialog.Builder(holder.itemView.context) builder.setTitle("Oyun İsteği") builder.setMessage(user.username + " kişisine oyun isteği yapmak istediğinize emin misiniz?") builder.setPositiveButton("İstek yap") { dialog, which -> App.socket.emit("request", user.id) Toast.makeText(holder.itemView.context, user.username + " kişisine oyun isteği yapıldı.", Toast.LENGTH_SHORT).show() } builder.setNegativeButton("Vazgeç") { dialog, which -> } val dialog: AlertDialog = builder.create() dialog.show() } else { val builder = AlertDialog.Builder(holder.itemView.context) builder.setTitle("Oyun İsteği") builder.setMessage(user.username + " kişisi meşgul olduğu için ona oyun isteği gönderemezsiniz.") builder.setNegativeButton("Tamam") { dialog, which -> } val dialog: AlertDialog = builder.create() dialog.show() } } } class UserViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) }
Burada yaptığımız işlemler;
- list_item.xml adındaki layout dosyamızı adapter e view olarak tanımladık.
- Adapter e parametre olarak gelen users dizisindeki her bir elemanı kontrol ettik. Müsaitlik durumuna (status) göre ekrandaki ivStatus'ün resmini ve tvUserName'in rengini değiştirdik.
- tvUserName'in text'ine kullanıcının adını yazdırdık.
- Listede bulunan her bir elemana tıklandığında kullanıcı müsaitse "aliveli kişisine oyun isteği yapmak istediğinize emin misiniz?" gibi bir AlertDialog açtırdık. İstek yap butonuna basılırsa server tarafına, istek yapılan kullanıcının id'si request emit'i ile gönderdik. Kullanıcı müsait değilse de "aliveli kişisi meşgul olduğu için ona oyun isteği gönderemezsiniz." gibi bir AlertDialog açtırdık.
Node.js projesi üzerinde;
server.js dosyamızı açıyoruz;
... let games = []; io.on('connection', socket => { ... socket.on('online_users', () => { io.emit('online_users', users); }); socket.on('disconnect', () => { users = users.filter((user) => { return user.id !== socket.id; }); games = games.filter((game) => { return game.home.id !== socket.id && game.away.id !== socket.id; }); io.emit('online_users', users); }); socket.on('request', (id) => { let user = users.find(user => user.id === socket.id); io.to(id).emit('request', user); }); socket.on('accept', (id) => { let homePlayer = users.find(user => user.id === id); let awayPlayer = users.find(user => user.id === socket.id); let joinPlayers = { home: homePlayer.username, away: awayPlayer.username }; io.to(socket.id).emit('accept', joinPlayers); io.to(id).emit('accept', joinPlayers); homePlayer.status = false; awayPlayer.status = false; io.emit('online_users', users); let game = { home: homePlayer, away: awayPlayer, time: 30, homeScore: 0, awayScore: 0, emit: false }; games.push(game); }); socket.on('reject', (id) => { let user = users.find(user => user.id === socket.id); io.to(id).emit('reject', user.username); }); ...
Burada yaptığımız işlemler;
- Client tarafından gelen online_users emit'ini (birazdan MainActivity.kt üzerinde yapacağız) karşılıyoruz. Aynı şekilde client tarafına online_users emit'i yaparak users dizisini gönderiyoruz.
- Bir kullanıcı uygulamayı kapattığında socket bağlantısı da kapanmış oluyor. Bunu da disconnect kısmında karşılayarak bağlantısı kapanan kullanıcıyı users dizisinden siliyoruz. Tekrardan online_users emit'i yaparak online kullanıcılar listesini güncelliyoruz. Ayrıca kullanıcılar arasındaki oyunları tutacağımız games dizisini üst kısımda oluşturuyoruz. Bağlantısı kapanan kullanıcıya ait bir oyun varsa onu da games dizisinden siliyoruz.
- Client tarafından gelen request emit'ini karşılıyoruz. İstek yapan kullanıcıyı bulup, istek yapılan kullanıcıya request emit'i gerçekleştiriyoruz.
- Client tarafından gelen accept emit'ini karşılıyoruz. İstek yapan kullanıcıyı homePlayer, isteği kabul eden kullanıcıyı awayPlayer olarak tanımlıyoruz. Bu kullanıcıları joinPlayers objesinde birleştirip iki tarafa da accept emit'i yapıyoruz. İki tarafında müsaitlik durumunu false yapıp online_users emit'i ile online kullanıcılar listesini güncelliyoruz. Son olarak da game objesi oluşturup games dizisine ekliyoruz.
- Client tarafından gelen reject emit'ini karşılıyoruz. İsteği reddeden kullanıcıyı bulup, istek yapan kullanıcıya reject emit'i yapıyoruz.
Android Studio projesi üzerinde;
MainActivity.kt dosyasını açarak şu kodları yazıyoruz:
package com.yusufborucu.onlinemathgame.ui.activity import android.annotation.SuppressLint import android.content.Context import android.content.Intent import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.recyclerview.widget.LinearLayoutManager import com.github.nkzawa.socketio.client.Socket import com.yusufborucu.onlinemathgame.App import com.yusufborucu.onlinemathgame.R import com.yusufborucu.onlinemathgame.model.User import com.yusufborucu.onlinemathgame.ui.adapter.UserAdapter import kotlinx.android.synthetic.main.activity_main.* import org.json.JSONArray import org.json.JSONObject class MainActivity : AppCompatActivity() { lateinit var socket: Socket @SuppressLint("SetTextI18n") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val preferences = getSharedPreferences("app", Context.MODE_PRIVATE) val username = preferences.getString("username", "") tvWelcome.text = "Hoşgeldin $username" socket = App.socket socket.emit("online_users") socket .on("online_users") { runOnUiThread { run { val onlineUsers = mutableListOf<User>() val users = JSONArray(it[0].toString()) for (i in 0 until users.length()) { val item = users.getJSONObject(i) if (item.getString("id") != socket.id()) onlineUsers.add(User(item.getString("id"), item.getString("username"), item.getBoolean("status"))) } rvOnlineUsers.layoutManager = LinearLayoutManager(applicationContext) rvOnlineUsers.adapter = UserAdapter(onlineUsers) } } } .on("request") { runOnUiThread { run { val user = JSONObject(it[0].toString()) val username = user.getString("username") val id = user.getString("id") val builder = AlertDialog.Builder(this) builder.setTitle("Oyun İsteği") builder.setMessage("$username seninle oynamak istiyor.") builder.setPositiveButton("Kabul Et") { dialog, which -> socket.emit("accept", id) Toast.makeText(applicationContext, "$username kişisinden gelen oyun isteği kabul edildi.", Toast.LENGTH_SHORT).show() } builder.setNegativeButton("Reddet") { dialog, which -> socket.emit("reject", id) Toast.makeText(applicationContext, "$username kişisinden gelen oyun isteği reddedildi.", Toast.LENGTH_SHORT).show() } val dialog: AlertDialog = builder.create() dialog.show() } } } .on("accept") { runOnUiThread { run { val users = JSONObject(it[0].toString()) val home = users.getString("home") val away = users.getString("away") val intent = Intent(applicationContext, GameActivity::class.java) intent.putExtra("home", home) intent.putExtra("away", away) startActivity(intent) finish() } } } .on("reject") { runOnUiThread { run { val username = it[0].toString() val builder = AlertDialog.Builder(this) builder.setTitle("Oyun İsteği Cevabı") builder.setMessage("$username kişisine yaptığınız oyun isteği reddedildi.") builder.setNegativeButton("Tamam") { _, _ -> } val dialog: AlertDialog = builder.create() dialog.show() } } } } }
Burada yaptığımız işlemler;
- Login ekranında sharedPreferences üzerine kaydettiğimiz kullanıcı adı (username) değerini ekranda yazdırıyoruz.
- Server tarafına online_users emit'i yapıyoruz.
- Server tarafından gelen online_users emit'ini karşılıyoruz. onlineUsers adında bir liste oluşturup elimize ulaşan veriyi bu listeye aktarıyoruz. Socket bağlantısını yapan kullanıcıyı bu listeye dahil etmiyoruz ki kullanıcı kendisini online kullanıcılar listesinde görüp de kendi kendine oyun isteği yollamasın :)
- Server tarafından gelen request emit'ini karşılıyoruz. Elimize ulaşan id ve kullanıcı adı verisini kullanarak hangi kullanıcıdan oyun isteği geldiğini AlertDialog ile gösteriyoruz. Kabul ve red durumlarına göre de accept ve reject emit'i yapıyoruz.
- Server tarafından gelen accept emit'ini karşılıyoruz. Elimize ulaşan home ve away verilerini GameActivity'e yolluyoruz.
- Server tarafından gelen reject emit'ini karşılıyoruz. Elimize ulaşan kullanıcı adı verisini AlertDialog üzerinde gösteriyoruz.
Bir sonraki yazıda görüşmek üzere...
Yorumlar Henüz yorum yapılmamış
Yeni Yorum