32YB SOPT/애니메이션 스터디

[SOPT] 애니메이션_2주차 (UIGestureRecognizer 중 UIPanGestureRecognizer)

신_이나 2023. 4. 30. 16:06

 

 

애니메이션 클론코딩 스터디 

in SOPT iOS

 

 

 

 

2주차는 UIGestureRecognizer 에 대하여 공부하였다.

 

 

UIGesureRecognizer 는 class 의 한 종류이고 말 그대로 gesture 와 관련된 class 다.

아래와 같이 8개의 제스처를 제공한다.

 

- UITapGestureRecognizer

- UIPinchGestureRecognizer

- UIRotainGestureRecognizer

- UISwipeGestureRecognizer

- UIPanGestureRecognizer

- UIScreenEdgePanGestureRecognizer

- UILongPressGestureRecognizer

- UIHoverGestureRecognizer

 

구체적인 제스처 사용법은 따로 포스팅하도록 하겠다.

 

 

 

 이번 스터디에서는 레이디버그 게임의 클론 코딩이 주제였다. 레이디버그는 가운데 레이디버그를 움직여서 다가오는 적들을 피하여 점수를 높이는 게임이다. 사실 난 레이디버그 게임은 몰랐고, 드래곤플라이트 같은 게임이라고 생각했다.

좌 : 레이디버그 게임, 우 : 드래곤 플라이트

 사용한 제스처는 UIPanGestureRecognizer 였다. UIPanGestureRecognizer 는 팬처럼 빙빙 돌리면서 그리는 제스처다. 따라서 레이디버그나, 드래곤을 빙빙 돌리면서 적을 피하는 제스처를 취해야 하기 때문에 UIPanGestureRecognizer 를 선택하였다.

스터디에서 스장님이 구현하신 코드를 뜯어보면서 하나하나 파훼해보자!

 

- 기초작업

 var score: Int = 0 //score 를 선언해주었다.
    
    override func viewDidLoad() { //처음 load할 화면 선언
        super.viewDidLoad()
        self.setLayout()
        self.makeEnemy()
        self.startTimer()
    }
    
    var timer: Timer? = nil //timer 변수선언 과 초기화 처리
    var isPause: Bool = true //멈춤 버튼 선언

 

- timer 설정

//open func 는 모듈 외부에서도 접근할 수 있게 한 것이다.
open func startTimer() { 
        guard timer == nil else { return } //만약 nill 이 아니라면 ㅃ2
        self.timer = Timer.scheduledTimer(timeInterval: 0.5,
                                          target: self,
                                          selector: #selector(self.enemyMove),
                                          userInfo: nil,
                                          repeats: true)
        //시간에 대해서 설정해주었다. 적들의 움직임을 0.5로 맞춰주었다.                                  
    }
    
    open func stopTimer() { 
        timer?.invalidate() 
        //invalidate 는 무효화한다는 의미로 타이머가 다시 발사되는 것을 멈추고 실행 루프에서 제거하는 함수다.
        timer = nil
    }

 

- 적의 X,Y좌표를 top, bottom, leading, trailing 에 맞춰 선언

@objc private func enemyMove() {

        var topEnemyY = self.topEnemy.frame.origin.y //top적의 y값 확인 
        topEnemyY += 10 //y좌표 변화주기
        self.topEnemy.frame = .init(origin: .init(x: self.topEnemy.frame.origin.x,
                                                  y: topEnemyY),
                                    size: self.topEnemy.frame.size)
        //적 좌표 나타낼거임 = x축은 변화 안주고, Y축만 10씩 늘리면서 나타낸다.                         
        
        var bottomEnemyY = self.bottomEnemy.frame.origin.y 
        bottomEnemyY -= 10 
        self.bottomEnemy.frame = .init(origin: .init(x: self.bottomEnemy.frame.origin.x,
                                                     y: bottomEnemyY),
                                       size: self.bottomEnemy.frame.size)
        
        var leadingEnemyX = self.leadingEnemy.frame.origin.x
        leadingEnemyX += 10
        self.leadingEnemy.frame = .init(origin: .init(x: leadingEnemyX,
                                                      y: self.leadingEnemy.frame.origin.y),
                                        size: self.leadingEnemy.frame.size)
        
        var trailingEnemyX = self.trailingEnemy.frame.origin.x
        trailingEnemyX -= 10
        self.trailingEnemy.frame = .init(origin: .init(x: trailingEnemyX,
                                                       y: self.trailingEnemy.frame.origin.y),
                                         size: self.trailingEnemy.frame.size)
        self.calculatePositionReached() //적의 좌표 확인해주는 함수(아래에 선언)
    }

 

- UIPanGestureRecognizer 를 사용하여 좌표 설정 (계속해서 좌표 바꿔줌)

드래깅하는 제스처 발생시 함수를 실행시키도록 만들었음sender 는 caller 의 의미로 어떤 것을 불러왔는지 나타낸다. 만약 두 개의 버튼이 있다면 어떤 버튼을 불러왔는지를 확인할 수 있다.

//이제 나왔다! 배운 UIPanGestureRecognizer!
@objc private func didImageViewMoved(_ sender: UIPanGestureRecognizer) {
        let transition = sender.translation(in: soptView)
        //transiton = 부모 좌표 공간에 대한 x,y,z 축 반환
        //soptView의 좌표를 transition에 전달하는 방법
        
        //변화한 좌표 설정
        let changedX = soptView.center.x + transition.x
        let changedY = soptView.center.y + transition.y
        
        //변화한 좌표를 soptView 중심으로 설정
        self.soptView.center = .init(x: changedX,
                                     y: changedY)
        
        //setTranslation 은 좌표계에서 PanGesture를 해석하는 방법이다.
        //.zero 란 말그대로 zero의 값을 나타냄
        sender.setTranslation(.zero, in: self.soptView)
    }

 

- 적이 sopt image 안으로 완벽하여 들어왔는지를 측정

private func calculatePositionReached() {
		//만약 적의 top,bottom,leading,trailing 이 모두 soptView 안에 들어왔을 때 Stop
        
        if self.soptView.frame.minX <= self.topEnemy.frame.minX &&
            self.soptView.frame.maxX >= self.topEnemy.frame.maxX &&
            self.soptView.frame.minY <= self.topEnemy.frame.minY &&
            self.soptView.frame.maxY >= self.topEnemy.frame.maxY
        {
            self.scoreLabel.text = "Game End, Score:\(self.score)"
            self.stopTimer()
        } else {
            self.score += 10
        }
        
        if self.soptView.frame.minX <= self.leadingEnemy.frame.minX &&
            self.soptView.frame.maxX >= self.leadingEnemy.frame.maxX &&
            self.soptView.frame.minY <= self.leadingEnemy.frame.minY &&
            self.soptView.frame.maxY >= self.leadingEnemy.frame.maxY
        {
            self.scoreLabel.text = "Game End, Score:\(self.score)"
            self.stopTimer()
        } else {
            self.score += 10
        }
        
        if self.soptView.frame.minX <= self.trailingEnemy.frame.minX &&
            self.soptView.frame.maxX >= self.trailingEnemy.frame.maxX &&
            self.soptView.frame.minY <= self.trailingEnemy.frame.minY &&
            self.soptView.frame.maxY >= self.trailingEnemy.frame.maxY
        {
            self.scoreLabel.text = "Game End, Score:\(self.score)"
            self.stopTimer()
        } else {
            self.score += 10
        }
        
        if self.soptView.frame.minX <= self.bottomEnemy.frame.minX &&
            self.soptView.frame.maxX >= self.bottomEnemy.frame.maxX &&
            self.soptView.frame.minY <= self.bottomEnemy.frame.minY &&
            self.soptView.frame.maxY >= self.bottomEnemy.frame.maxY
        {
            self.scoreLabel.text = "Game End, Score:\(self.score)"
            self.stopTimer()
        } else {
            self.score += 10
        }
    }

 

- Layout 코드

private func setLayout() {
		//view 색을 white로 선언
		view?.backgroundColor = .white
        self.view.addSubview(soptView)
        self.view.addSubview(scoreLabel)
        soptView.snp.makeConstraints {
            $0.centerX.centerY.equalToSuperview()
            $0.width.height.equalTo(100)
        }
        scoreLabel.snp.makeConstraints {
            $0.centerX.equalToSuperview()
            //safeAreaLayoutGuide 가려진 곳을 보기 위함
            $0.top.equalTo(self.view.safeAreaLayoutGuide).offset(20)
        }
    }

 

-enemy에 대한 layout 코드

private func makeEnemy() {

        self.view.addSubview(topEnemy)
        self.view.addSubview(bottomEnemy)
        self.view.addSubview(leadingEnemy)
        self.view.addSubview(trailingEnemy)
        
        topEnemy.snp.makeConstraints {
            $0.top.centerX.equalToSuperview()
            $0.width.height.equalTo(50)
        }
        leadingEnemy.snp.makeConstraints {
            $0.leading.centerY.equalToSuperview()
            $0.width.height.equalTo(50)
        }
        trailingEnemy.snp.makeConstraints {
            $0.trailing.centerY.equalToSuperview()
            $0.width.height.equalTo(50)
        }
        bottomEnemy.snp.makeConstraints {
            $0.bottom.centerX.equalToSuperview()
            $0.width.height.equalTo(50)
        }
    }

 

- Image를 선언

Image View 에 PanGesture 를 선언하고, 팬 제스처가 발생했을 때 didImageViewMoved 가 실행하도록 설정하였다.

private lazy var soptView = UIImageView(image: UIImage(named: "image")).then {
		//didImageViewMoved(_:)는 위에서 선언
        let gesture = UIPanGestureRecognizer(target: self,
                                             action: #selector(didImageViewMoved(_:)))
        $0.addGestureRecognizer(gesture)
        $0.isUserInteractionEnabled = true //사용자의 event를 사용할수 있고, 없고를 설정하는 property
    }
    private let topEnemy = UIImageView(image: UIImage(named: "enemy"))
    private let bottomEnemy = UIImageView(image: UIImage(named: "enemy"))
    private let leadingEnemy = UIImageView(image: UIImage(named: "enemy"))
    private let trailingEnemy = UIImageView(image: UIImage(named: "enemy"))
    private let scoreLabel = UILabel().then {
        $0.textAlignment = .center
    }

 

 

- 전체코드

더보기
import UIKit
import Then
import SnapKit

class TestVC: UIViewController {
    var score: Int = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.setLayout()
        self.makeEnemy()
        self.startTimer()
    }
    
    var timer: Timer? = nil
    var isPause: Bool = true
    
    open func startTimer() {
        guard timer == nil else { return }
        self.timer = Timer.scheduledTimer(timeInterval: 0.5,
                                          target: self,
                                          selector: #selector(self.enemyMove),
                                          userInfo: nil,
                                          repeats: true)
    }
    
    open func stopTimer() {
        timer?.invalidate()
        timer = nil
    }
    
    @objc private func enemyMove() {
        var topEnemyY = self.topEnemy.frame.origin.y
        topEnemyY += 10
        self.topEnemy.frame = .init(origin: .init(x: self.topEnemy.frame.origin.x,
                                                  y: topEnemyY),
                                    size: self.topEnemy.frame.size)
        
        var bottomEnemyY = self.bottomEnemy.frame.origin.y
        bottomEnemyY -= 10
        self.bottomEnemy.frame = .init(origin: .init(x: self.bottomEnemy.frame.origin.x,
                                                     y: bottomEnemyY),
                                       size: self.bottomEnemy.frame.size)
        
        var leadingEnemyX = self.leadingEnemy.frame.origin.x
        leadingEnemyX += 10
        self.leadingEnemy.frame = .init(origin: .init(x: leadingEnemyX,
                                                      y: self.leadingEnemy.frame.origin.y),
                                        size: self.leadingEnemy.frame.size)
        
        var trailingEnemyX = self.trailingEnemy.frame.origin.x
        trailingEnemyX -= 10
        self.trailingEnemy.frame = .init(origin: .init(x: trailingEnemyX,
                                                       y: self.trailingEnemy.frame.origin.y),
                                         size: self.trailingEnemy.frame.size)
        self.calculatePositionReached()
    }
    
    @objc private func didImageViewMoved(_ sender: UIPanGestureRecognizer) {
        let transition = sender.translation(in: soptView)
        let changedX = soptView.center.x + transition.x
        let changedY = soptView.center.y + transition.y
        
        self.soptView.center = .init(x: changedX,
                                     y: changedY)
        sender.setTranslation(.zero, in: self.soptView)
    }
    
    
    private func calculatePositionReached() {
        if self.soptView.frame.minX <= self.topEnemy.frame.minX &&
            self.soptView.frame.maxX >= self.topEnemy.frame.maxX &&
            self.soptView.frame.minY <= self.topEnemy.frame.minY &&
            self.soptView.frame.maxY >= self.topEnemy.frame.maxY
        {
            self.scoreLabel.text = "Game End, Score:\(self.score)"
            self.stopTimer()
        } else {
            self.score += 10
        }
        
        if self.soptView.frame.minX <= self.leadingEnemy.frame.minX &&
            self.soptView.frame.maxX >= self.leadingEnemy.frame.maxX &&
            self.soptView.frame.minY <= self.leadingEnemy.frame.minY &&
            self.soptView.frame.maxY >= self.leadingEnemy.frame.maxY
        {
            self.scoreLabel.text = "Game End, Score:\(self.score)"
            self.stopTimer()
        } else {
            self.score += 10
        }
        
        if self.soptView.frame.minX <= self.trailingEnemy.frame.minX &&
            self.soptView.frame.maxX >= self.trailingEnemy.frame.maxX &&
            self.soptView.frame.minY <= self.trailingEnemy.frame.minY &&
            self.soptView.frame.maxY >= self.trailingEnemy.frame.maxY
        {
            self.scoreLabel.text = "Game End, Score:\(self.score)"
            self.stopTimer()
        } else {
            self.score += 10
        }
        
        if self.soptView.frame.minX <= self.bottomEnemy.frame.minX &&
            self.soptView.frame.maxX >= self.bottomEnemy.frame.maxX &&
            self.soptView.frame.minY <= self.bottomEnemy.frame.minY &&
            self.soptView.frame.maxY >= self.bottomEnemy.frame.maxY
        {
            self.scoreLabel.text = "Game End, Score:\(self.score)"
            self.stopTimer()
        } else {
            self.score += 10
        }
    }
    
    private func setLayout() {
        self.view.addSubview(soptView)
        self.view.addSubview(scoreLabel)
        soptView.snp.makeConstraints {
            $0.centerX.centerY.equalToSuperview()
            $0.width.height.equalTo(100)
        }
        scoreLabel.snp.makeConstraints {
            $0.centerX.equalToSuperview()
            $0.top.equalTo(self.view.safeAreaLayoutGuide).offset(20)
        }
    }
    
    private func makeEnemy() {
        self.view.addSubview(topEnemy)
        self.view.addSubview(bottomEnemy)
        self.view.addSubview(leadingEnemy)
        self.view.addSubview(trailingEnemy)
        
        topEnemy.snp.makeConstraints {
            $0.top.centerX.equalToSuperview()
            $0.width.height.equalTo(50)
        }
        leadingEnemy.snp.makeConstraints {
            $0.leading.centerY.equalToSuperview()
            $0.width.height.equalTo(50)
        }
        trailingEnemy.snp.makeConstraints {
            $0.trailing.centerY.equalToSuperview()
            $0.width.height.equalTo(50)
        }
        bottomEnemy.snp.makeConstraints {
            $0.bottom.centerX.equalToSuperview()
            $0.width.height.equalTo(50)
        }
    }
    
    private lazy var soptView = UIImageView(image: UIImage(named: "image")).then {
        let gesture = UIPanGestureRecognizer(target: self,
                                             action: #selector(didImageViewMoved(_:)))
        $0.addGestureRecognizer(gesture)
        $0.isUserInteractionEnabled = true
    }
    private let topEnemy = UIImageView(image: UIImage(named: "enemy"))
    private let bottomEnemy = UIImageView(image: UIImage(named: "enemy"))
    private let leadingEnemy = UIImageView(image: UIImage(named: "enemy"))
    private let trailingEnemy = UIImageView(image: UIImage(named: "enemy"))
    private let scoreLabel = UILabel().then {
        $0.textAlignment = .center
    }
}

 

 

 

+추가 질문