애니메이션 클론코딩 스터디
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
}
}
+추가 질문
'32YB SOPT > 애니메이션 스터디' 카테고리의 다른 글
[SOPT] 애니메이션_4주차 (Bezier Path, CAnimations) (0) | 2023.06.21 |
---|---|
[SOPT] 애니메이션_3주차 (ContentOffset, Skeleton Animation) (0) | 2023.06.13 |