본문 바로가기
안드로이드 프로젝트/개발자 키우기 게임

[Kotlin]잔디 수확 시스템 만들기(GrassActivity 마무리)

by joh9911 2022. 9. 8.

 

자신의 잔디 정보를 보기만 한다는 것은 너무 심심하지 않을까 생각했다.

따라서 플레이 시간에 따라 잔디에서 돈을 얻을 수 있는 수확 시스템을 만들기로 결정했다.

 

 

 

다음과 같이 시스템을 정하였다.

 

1. 각각의 잔디를 클릭하면 각 잔디의 정보와 쌓인 돈을 볼 수 있다.

 

2. 잔디가 빈곳에서는 돈을 얻을 수 없다.

 

3. 플레이 시간에 따라 각각의 잔디에 돈이 차오른다.

 

4. 잔디의 색깔(contribution 수)에 따라 최대 저장 액수를 설정한다.

 

5. 수확 버튼을 누르면 돈이 해당 달의 잔디들로부터 빠져나와 내 지갑에 들어온다.

 

6. 잔디의 돈은 초기화 되며 다시 차오르기 시작한다.

 

 

 

 

먼저 각각의 잔디의 정보를 띄어주는 창을 디자인 했다. 

 

<grass_info_dialog.xml>

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="300dp"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:background="@color/background_color">

    <LinearLayout
        android:id="@+id/layout_position"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintWidth_percent="0.6"
        app:layout_constraintHeight_percent="0.15"
        app:layout_constraintHorizontal_bias="0.1"
        app:layout_constraintVertical_bias="0.1"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:orientation="vertical">
        <TextView
            android:id="@+id/grass_info_dialog_date"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="2022-08-12"
            android:textSize="30dp"
            android:textColor="@color/black"
            android:autoSizeTextType="uniform"
            android:gravity="center"
            />

        <TextView
            android:id="@+id/grass_info_dialog_contribution_count"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="contribution = 3"
            android:textSize="20dp"
            android:autoSizeTextType="uniform"
            android:gravity="center"
            android:paddingTop="30dp"
            android:textColor="@color/black"
            />

    </LinearLayout>


    <ImageView
        android:id="@+id/grass_info_dialog_grass_image"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintWidth_percent="0.35"
        app:layout_constraintHeight_percent="0.3"
        app:layout_constraintVertical_bias="0.04"
        app:layout_constraintHorizontal_bias="1"
        android:background="@drawable/border_square"
        android:src="@mipmap/grass_one"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="저장된 금액"
        android:textSize="30dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.55"/>


    <ProgressBar
        android:id="@+id/grass_info_dialog_progress_bar"
        android:layout_width="0dp"
        android:layout_height="0dp"
        style="@style/Widget.AppCompat.ProgressBar.Horizontal"
        android:scaleY="8"
        android:progress="0"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintHeight_percent="0.2"
        app:layout_constraintWidth_percent="0.9"
        app:layout_constraintVertical_bias="0.75"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="용량:"
        android:textSize="20dp"
        android:textColor="@color/white"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.65"
        app:layout_constraintHorizontal_bias="0.1"/>

    <TextView
        android:id="@+id/grass_info_dialog_progress_bar_value"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="30dp"
        android:textColor="@color/white"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.65"
        app:layout_constraintHorizontal_bias="0.8"/>

    <Button
        android:id="@+id/grass_info_dialog_grass_shop_button"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:text="상점"
        android:textSize="30dp"
        android:textColor="@color/black"
        android:backgroundTint="@color/deep_green"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintHeight_percent="0.2"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintVertical_bias="1"
        app:layout_constraintWidth_percent="0.4" />



</androidx.constraintlayout.widget.ConstraintLayout>

 

grass_info_dialog.xml

 

 

 

각각의 잔디 버튼을 누르면 다음과 같은 디자인의 창을 띄우게끔 할 것이다.

저 "용량" 이라는 progress Bar 를 통해 돈이 실시간으로 차는 모습을 볼 수 있다.

(아직 상점 부분은 설계를 하지 못해 보류하였다.)

 

 

 

 

돈은 게임을 켜놓는 시간에 따라 차야한다.

따라서 게임 메인 페이지의 onCreate에 시간을 측정해주는 쓰레드를 생성하고 실행시켰다.

 

 

<MainActivity.kt>

// 플레이 시간
var playTime = 0
inner class PlayTime: Runnable {
    override fun run() {
        while (!isThreadStop) {
            playTime+=1
            Thread.sleep(1000)
        }
    }
}
var isThreadStop = false

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main_page)
        // ....
        
        val thread = Thread(PlayTime())
        thread.start()
}

 

 

MainActivity

 

 

설명하기 쉽게 메인 페이지 화면을 들고왔다.

"터치수당" 이라 적혀있는 부분이 돈이 모이는 곳(지갑) 이며,

밑에 5개의 버튼 중 잔디 버튼을 누르면

 

 

 

GrassActivity

 

지난 포스팅에서 만들었던 GrassActivity가 띄워진다.

 

 

 

저 여러개의 잔디들과 수확 버튼 까지가 Fragment이며,

수확 버튼을 누르면 해당 달의 모든 잔디들로부터 돈을 뽑아내여

오른쪽 상단의 칸에 저장한다.

 

 

 

 

다시 메인페이지로 이동하면 저장된 돈 지갑에 들어가져야 한다.

이를 구현하기 위해 GrassActivity는 registerForActivityResult() 를 사용하였다.

또한 플레이 타임을 intent를 통해 전달해주었다.

 

 

lateinit var getResultText: ActivityResultLauncher<Intent>
fun setActivityResultInit(){
    getResultText = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){ result ->
        if(result.resultCode == RESULT_OK){
            val data = result.data?.getIntExtra("grassMoney",0)
            grassMoney = data!!
            personalMoney += grassMoney //personalMoney는 지갑 속 돈 액수를 뜻함
            findViewById<TextView>(R.id.main_page_text_view_personal_money).text =
                "${personalMoney}만원" //가져온 값을 더해서 text 설정해주기
        }
    }
}

fun btnEventGrass() {
        val grassBtn = findViewById<ImageButton>(R.id.grass_btn)

        grassBtn.setOnClickListener {
            val intent= Intent(this,GrassPageActivity::class.java)
            intent.putExtra("playTime",playTime)
            getResultText.launch(intent)
        }
    }
    
//각각의 초기화 함수들이며 onCreate에서 초기화 해줌

 

 

 

전달받은 플레이 타임은 끊기지 않아야 하므로, GrassActvity의 onCreate에 쓰레드를 생성하였다.

다시 메인 엑티비티로 돌아가는 버튼을 따로 만들지 않았으므로,

registerForActivityResult()에 결과값 전달 코드를 onBackPressed 에 작성하였다.

 

 

 

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

        val thread = Thread(PlayTime())
        thread.start()
        // ...
    }

    inner class PlayTime: Runnable {
        override fun run() {
            playTime = intent.getIntExtra("playTime",0)
            while (!isThreadStop) {
                playTime+=1
                Thread.sleep(1000)
            }
        }
    }

    override fun onBackPressed() {
        val intent = Intent(this, MainActivity::class.java)
        intent.putExtra("grassMoney", grassMoney)
        setResult(RESULT_OK, intent)
        finish()
    }

 

 

 

플레이 타임을 그대로 Fragment에 전달해주려는 찰나, 문제가 하나 생겼는데.

Fragment는 (처음을 제외하고) 화면을 옆으로 넘길 때마다 생성되며, 최대 3개의 Fragment까지 유지된다는 것이다.

 

 

설명하게 쉽게 gif 파일로 올리겠다

 

 

 

보다시피 9월에 해당하는 Fragment는 11월의 Fragment 가 생성될 때까지 유지되다가

12월 Fragment가 생성되면 삭제됨을 볼 수 있다. 

 

즉 각각의 Fragment 내의 잔디들은 Fragment가 삭제되어도 이전 정보를 기억해야 한다는 것이다.

 

 

 

어떻게 해결할까 고민하다가 sharedPreference를 통해 정보를 저장해두는 방법을 생각해냈다.

 

 

 

 

 

먼저 Fragment의 인자로 플레이 타임과 각 Fragment의 position을 넘긴다.

inner class ScreenSlidePagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {

    override fun getItemCount(): Int = githubDataArray.size

    override fun createFragment(position: Int): Fragment = GrassPageFragment(githubDataArray[position], githubData, playTime, position)
}
//GrassActivity

 

 

 

<GrassPageFragment.kt>

class GrassPageFragment(githubDataArray: List<String>, githubData: List<GithubCommitQuery.Week>?, playTime: Int, position:Int): Fragment() {
    lateinit var pref: SharedPreferences
    lateinit var editor: SharedPreferences.Editor

    val position = position
    var grassMaxValue = arrayListOf<Int>() //잔디 색깔에 따른 progressBar의 maxValue를 저장하는 곳
    var grassHarvestMoney = 0

    var githubDataArray = githubDataArray // grassPageActivity 에서 해당 페이지의 날짜 정보 배열을 가져옴 (22,08,12) 이런식으로
    var githubData = githubData //깃허브 정보
    var playTime = playTime
    
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.grass_page_view_pager_layout, container, false)
        gridLayoutSetting(view)
        pref = requireActivity().getSharedPreferences("fragmentPlayTime",0)
        editor = pref.edit()
        // 프리퍼런스 값이 있으면
        if(pref.getInt("fragment${position}",-1) != -1){
            playTime -= pref.getInt("fragment${position}",0)
        }
        val thread = Thread(PlayTime())
        thread.start()
        return view
    }
    
    inner class PlayTime: Runnable {
        override fun run() {
            while (!isThreadStop) {
                playTime+=1
                Thread.sleep(1000)
            }
        }
    }

 

 

 

fragmentPlayTime이라는 preference를 만들었다.

수확 버튼을 누르면 잔디에 쌓인 돈이 초기화 되어 0원 부터 다시 늘어나야 하므로,

수확 버튼을 누를시, preference에 "fragment${position}"이란 이름으로 playTime을 저장해놓을 것이다.

 

val harvestCoinButton = view.findViewById<Button>(R.id.harvestCoinButton)
harvestCoinButton.setOnClickListener {
    for (index in 0 until numberOfDateArray.size){
        if (grassMaxValue[index] != 0){ //수확 돈 정해주는 코드
            if (playTime >= grassMaxValue[index]) {
                grassHarvestMoney += grassMaxValue[index]
                //fragment 에서 activity로 값을 전달해 주기 위한 interface
                fragmentToActivityGrassMoney.onReceivedMoney(grassHarvestMoney)
            }
            else{//수확 돈 정해주는 코드
                grassHarvestMoney += playTime
                //fragment 에서 activity로 값을 전달해 주기 위한 interface
                fragmentToActivityGrassMoney.onReceivedMoney(grassHarvestMoney)
            }
        }
    }
    if (pref.getInt("fragment${position}",-1) == -1){
        editor.putInt("fragment${position}",playTime).apply()
        playTime = 0
    }
    else{
        val pastPreferenceValue = pref.getInt("fragment${position}",-1)
        editor.remove("fragment${position}").apply()
        editor.putInt("fragment${position}",pastPreferenceValue + playTime).apply()
        playTime = 0
    }

 

이전에 다뤘던 GrassFragment 내의 함수 gridLayoutSetting()의 한 부분이다.

수확 버튼을 눌렀을 때 "fragment${position}"란 이름의 preference 값이 없을 경우

현재 playTime 값을 저장하고

"fragment${position}"란 이름의 preference 값이 있을 경우 해당 preference 값을 지운 후

원래 저장된 값에 playTime값을 더하여 다시 저장하도록 하였다.

 

 

customView.setOnClickListener { // 클릭하면 잔디 정보 다이알로그 띄움
    val grassInfoDialog = GrassInfoDialog(numberOfDateArray[index], contributionCountArray[index], grassColorArray[index], playTime)
    grassInfoDialog.show(parentFragmentManager,"grassShopDialog")
}

 

 

잔디를 클릭하면 맨 위에 올려놨던 DialogFragment로 이루어진 다이알로그를 띄우게 했다.

인자로는 해당 잔디의 날짜, contribution 정보,  잔디 색깔, playTime을 전달한다.

 

 

 

<GrassInfoDialog>

package com.example.raise_developer

import ...

class GrassInfoDialog(date: String, contributionCount: String, grassColor: String, playTime: Int): DialogFragment() {
    var playTime = playTime
    var maxValue = 0
    var isThreadStop = false
    lateinit var progressBar: ProgressBar
    lateinit var progressBarValue: TextView

    var date = date
    var contributionCount = contributionCount
    var grassColor = grassColor

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
        val view = inflater.inflate(R.layout.grass_info_dialog,container,false)
        view.findViewById<TextView>(R.id.grass_info_dialog_date).text = date
        view.findViewById<TextView>(R.id.grass_info_dialog_contribution_count).text = "contributions=${contributionCount}"

        if (grassColor == "#9be9a8"){
            maxValue = 80
            view.findViewById<ImageView>(R.id.grass_info_dialog_grass_image).setImageResource(R.mipmap.grass_one)
        }

        else if(grassColor == "#40c463"){
            maxValue = 100
            view.findViewById<ImageView>(R.id.grass_info_dialog_grass_image).setImageResource(R.mipmap.grass_two)
        }

        else if(grassColor == "#30a14e"){
            maxValue = 120
            view.findViewById<ImageView>(R.id.grass_info_dialog_grass_image).setImageResource(R.mipmap.grass_three)
        }

        else if(grassColor == "#216e39"){
            maxValue = 140
            view.findViewById<ImageView>(R.id.grass_info_dialog_grass_image).setImageResource(R.mipmap.grass_four)
        }

        else{
            maxValue = 0
            view.findViewById<ImageView>(R.id.grass_info_dialog_grass_image).setImageResource(R.mipmap.empty_grass)

        }
        progressBar = view.findViewById(R.id.grass_info_dialog_progress_bar)
        progressBarValue = view.findViewById(R.id.grass_info_dialog_progress_bar_value)
        progressBar.max = maxValue
        if (maxValue != 0){
            val thread = Thread(PlayTime())
            thread.start()
        }
        return view
    }

    override fun onResume() {
        super.onResume()
        context?.dialogFragmentResize(this, 0.7f,0.5f) // 다이알로그 크기 조정
    }


    inner class PlayTime(): Runnable {
        override fun run() {
            while (!isThreadStop) {
                activity?.runOnUiThread {
                    if (playTime >= maxValue){
                        progressBarValue.text = "${maxValue} / ${maxValue}만원"
                        progressBar.progress = maxValue
                    }
                    else{
                        progressBarValue.text = "${playTime} / ${maxValue}만원"
                        progressBar.progress = playTime
                    }
                }
                playTime+=1
                Thread.sleep(1000)
            }
        }
    }


    fun Context.dialogFragmentResize(dialogFragment: DialogFragment, width: Float, height: Float) { // 다이알로그 크기 설정하는 함수
        val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager

        if (Build.VERSION.SDK_INT < 30) {

            val display = windowManager.defaultDisplay
            val size = Point()

            display.getSize(size)

            val window = dialogFragment.dialog?.window

            val x = (size.x * width).toInt()
            val y = (size.y * height).toInt()
            window?.setLayout(x, y)

        } else {

            val rect = windowManager.currentWindowMetrics.bounds

            val window = dialogFragment.dialog?.window

            val x = (rect.width() * width).toInt()
            val y = (rect.height() * height).toInt()

            window?.setLayout(x, y)
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        isThreadStop = true
    }
}

 

전달받은 잔디 색깔에 따라 progressBar의 max값을 다르게 설정한다.

전달받은 playTime 또한 쓰레드를 돌리며, progressBar의 progress로 설정한다.

 

 

 

 

잔디에서 돈 수확하기

 

 

 

 

 

댓글