Reputation: 77
I am currently creating a Hangman game application in iOS using Swift.
I have all of the game mechanics completed, and wanted to use Core Graphics to draw the hangman and gallows. I can draw the gallows and hangman using UIBezierPath, and have broken the drawing of each part of the view (gallows, head, body, left arm,...) into separate functions that are called to create the entire image. All of this is inside a custom UIView class.
What I can't seem to figure out is how to add successive parts of the Hangman image as the user makes incorrect guesses. I am trying to avoid having a series of images that I cycle between. Instead, I want to use the same custom view to draw the stick figure piece by piece using Core Graphics.
How can I implement a function that will draw each successive part of the figure?
Something like "self.customView.updateDrawing()" where updateDrawing method in the customView would take info from the model about the number of incorrect guesses and call the appropriate methods to draw the needed parts of the stick figure.
UPDATE
Here is my attempt at implementing the view from this post https://codereview.stackexchange.com/questions/97424/hangman-in-swift into my own hangman game. They work great for drawing the gallows, but I can't actually get the image to update itself and draw successive body parts to add the hangman. Here is my code so far:
GameViewTwo : this is the GameView from above post, and I've tried to split it up and add the different parts as layers to the view.
import UIKit
enum BodyPart: Int {
case Head = 1
case Body = 2
case LeftArm = 5
case RightArm = 6
case LeftLeg = 3
case RightLeg = 4
case LeftEye = 7
case RightEye = 8
case Mouth = 9
case Unknown = 10
}
class GameViewTwo: UIView {
var path = UIBezierPath()
var newPartLayer = CAShapeLayer()
private var bodyStart: CGPoint = CGPoint.zero
private var bodyEnd: CGPoint = CGPoint.zero
private var headMiddle: CGPoint = CGPoint.zero
struct DrawingConstants {
static let gallowBaseStartScale: CGFloat = 0.15
static let gallowBaseEndScale: CGFloat = 0.85
static let gallowBaseHeight: CGFloat = 10
static let gallowHeight: CGFloat = 0.05 //static let gallowHeight: CGFloat = 0.15
static let gallowHeightStart: CGFloat = 0.175
static let gallowHeightWidth: CGFloat = 10
static let gallowAcrossScale: CGFloat = 0.5
static let gallowTipHeight: CGFloat = 17.5
static let headRadius: CGFloat = 16
static let bodyLength: CGFloat = 25
static let bodyHeight: CGFloat = 25
static let legLength: CGFloat = 50
static let grassHeightScale: CGFloat = 0.68
static let armBack: CGFloat = 5
}
struct ScaleConstants {
static let bodyLength: CGFloat = 50
static let limbLength: CGFloat = 25
static let handHeightScale: CGFloat = 0.4
static let headRadius: CGFloat = 20
static let eyeRadius = CGFloat(0.15 * ScaleConstants.headRadius)
static let eyeOffset = CGFloat(0.3 * ScaleConstants.headRadius)
static let mouthOffSet = CGFloat(0.3 * ScaleConstants.headRadius)
static let mouthRadius = CGFloat(0.25 * ScaleConstants.headRadius)
}
var part : BodyPart?
func setPart(part: BodyPart){
self.part = part
}
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
drawGallow()
}
func add(part: BodyPart){
let partPath = path(forPart: part)
newPartLayer.frame = bounds
newPartLayer.path = partPath.cgPath
newPartLayer.strokeColor = UIColor.black.cgColor
let strokeAnim = CABasicAnimation(keyPath: "strokeEnd")
strokeAnim.fromValue = 0
strokeAnim.toValue = 1
strokeAnim.duration = 1
layer.addSublayer(newPartLayer)
newPartLayer.add(strokeAnim, forKey: "path")
}
func path(forPart: BodyPart)-> UIBezierPath {
switch forPart {
case BodyPart.Head :
let centerX = CGFloat(bounds.size.width * DrawingConstants.gallowAcrossScale - (DrawingConstants.gallowHeightWidth / 2))
let centerY = CGFloat(bounds.size.height * DrawingConstants.gallowHeight + DrawingConstants.gallowBaseHeight + DrawingConstants.gallowTipHeight + ScaleConstants.headRadius)
let center = CGPoint(x: centerX, y: centerY)
headMiddle = center
path = UIBezierPath(arcCenter: center, radius: ScaleConstants.headRadius, startAngle: CGFloat(0), endAngle: CGFloat(2 * M_PI), clockwise: true)
path.lineWidth = CGFloat(2)
return path
case BodyPart.Body :
let add = CGFloat(DrawingConstants.gallowBaseHeight + DrawingConstants.gallowTipHeight + 2 * ScaleConstants.headRadius)
let startPointY = CGFloat(bounds.size.height * DrawingConstants.gallowHeight + add)
let startPointX = CGFloat(bounds.size.width * DrawingConstants.gallowAcrossScale - (DrawingConstants.gallowHeightWidth / 2))
let startPoint = CGPoint(x: startPointX, y: startPointY)
let endPoint = CGPoint(x: startPoint.x, y: startPoint.y + ScaleConstants.bodyLength)
bodyStart = startPoint
bodyEnd = endPoint
path.lineWidth = CGFloat(2)
path.move(to: startPoint)
path.addLine(to: endPoint)
return path
case BodyPart.LeftLeg :
let startPoint = CGPoint(x: bodyEnd.x, y: bodyEnd.y)
let endPoint = CGPoint(x: startPoint.x - ScaleConstants.limbLength, y: startPoint.y + ScaleConstants.limbLength)
path.lineWidth = CGFloat(2)
path.move(to: startPoint)
path.addLine(to: endPoint)
return path
case BodyPart.RightLeg :
let startPoint = CGPoint(x: bodyEnd.x, y: bodyEnd.y)
let endPoint = CGPoint(x: startPoint.x + ScaleConstants.limbLength, y: startPoint.y + ScaleConstants.limbLength)
path.lineWidth = CGFloat(2)
path.move(to: startPoint)
path.addLine(to: endPoint)
return path
case BodyPart.LeftArm :
let startPoint = CGPoint(x: bodyStart.x, y: bodyStart.y + ScaleConstants.handHeightScale * ScaleConstants.bodyLength)
let endPoint = CGPoint(x: startPoint.x - ScaleConstants.limbLength, y: startPoint.y - ScaleConstants.limbLength * ScaleConstants.handHeightScale)
path.lineWidth = CGFloat(2)
path.move(to: startPoint)
path.addLine(to: endPoint)
return path
case BodyPart.RightArm :
let startPoint = CGPoint(x: bodyStart.x, y: bodyStart.y + ScaleConstants.handHeightScale * ScaleConstants.bodyLength)
let endPoint = CGPoint(x: startPoint.x + ScaleConstants.limbLength, y: startPoint.y - ScaleConstants.limbLength * ScaleConstants.handHeightScale)
path.lineWidth = CGFloat(2)
path.move(to: startPoint)
path.addLine(to: endPoint)
return path
case BodyPart.LeftEye :
UIColor.black.set()
let eyeMiddle = CGPoint(x: headMiddle.x - ScaleConstants.eyeOffset, y: headMiddle.y - ScaleConstants.eyeOffset)
let path = UIBezierPath(arcCenter: eyeMiddle, radius: ScaleConstants.eyeRadius, startAngle: 0, endAngle: CGFloat(2 * M_PI), clockwise: true)
path.lineWidth = CGFloat(1)
return path
case BodyPart.RightEye :
UIColor.black.set()
let eyeMiddle = CGPoint(x: headMiddle.x + ScaleConstants.eyeOffset, y: headMiddle.y - ScaleConstants.eyeOffset)
let path = UIBezierPath(arcCenter: eyeMiddle, radius: ScaleConstants.eyeRadius, startAngle: 0, endAngle: CGFloat(2 * M_PI), clockwise: true)
path.lineWidth = CGFloat(1)
return path
default:
return path
}
}
/******************************************************************************************/
func connectPoints(bottomLeftPoint: CGPoint, bottomRightPoint: CGPoint, topLeftPoint: CGPoint, topRightPoint: CGPoint, color: UIColor) {
color.set()
let path = UIBezierPath()
path.move(to: bottomLeftPoint)
path.addLine(to: topLeftPoint)
path.addLine(to: topRightPoint)
path.addLine(to: bottomRightPoint)
path.close()
path.fill()
path.stroke()
}
func calculateMidPoint(point1: CGPoint, point2: CGPoint) -> CGPoint {
return CGPoint(x: (point1.x + point2.x) / 2, y: (point1.y + point2.y) / 2)
}
/*****************************************************************************************/
func drawGrass() {
let topStartPoint = CGPoint(x: CGFloat(0), y: CGFloat(bounds.size.height * DrawingConstants.grassHeightScale))
let topRightPoint = CGPoint(x: CGFloat(bounds.size.width), y: topStartPoint.y)
let bottomRightPoint = CGPoint(x: topRightPoint.x, y: CGFloat(bounds.size.height))
let bottomLeftPoint = CGPoint(x: CGFloat(0), y: bottomRightPoint.y)
connectPoints(bottomLeftPoint: bottomLeftPoint, bottomRightPoint: bottomRightPoint, topLeftPoint: topStartPoint, topRightPoint: topRightPoint, color: UIColor.green)
}
func drawSky() {
let bottomLeftPoint = CGPoint(x: CGFloat(0), y: CGFloat(bounds.size.height * DrawingConstants.grassHeightScale))
let topLeftPoint = CGPoint(x: CGFloat(0), y: CGFloat(0))
let topRightPoint = CGPoint(x: CGFloat(bounds.size.width), y: CGFloat(0))
let bottomRightPoint = CGPoint(x: CGFloat(bounds.size.width), y: CGFloat(bounds.size.height * DrawingConstants.grassHeightScale))
connectPoints(bottomLeftPoint: bottomLeftPoint, bottomRightPoint: bottomRightPoint, topLeftPoint: topLeftPoint, topRightPoint: topRightPoint, color: UIColor.cyan)
}
func drawGallow() {
drawGallowBase()
drawGallowHeight()
drawGallowAcross()
drawGallowTip()
}
func drawGallowBase() {
let bottomLeftPoint = CGPoint(x: CGFloat(bounds.size.width * DrawingConstants.gallowBaseStartScale), y: CGFloat(bounds.size.height * DrawingConstants.grassHeightScale))
let topLeftPoint = CGPoint(x: bottomLeftPoint.x, y: bottomLeftPoint.y - DrawingConstants.gallowBaseHeight)
let topRightPoint = CGPoint(x: CGFloat(bounds.size.width * DrawingConstants.gallowBaseEndScale), y: topLeftPoint.y)
let bottomRightPoint = CGPoint(x: topRightPoint.x, y: bottomLeftPoint.y)
connectPoints(bottomLeftPoint: bottomLeftPoint, bottomRightPoint: bottomRightPoint, topLeftPoint: topLeftPoint, topRightPoint: topRightPoint, color: UIColor.brown)
}
func drawGallowHeight() {
let bottomLeftPoint = CGPoint(x: CGFloat(bounds.size.width * DrawingConstants.gallowHeightStart), y: CGFloat(bounds.size.height * DrawingConstants.grassHeightScale - DrawingConstants.gallowBaseHeight))
let bottomRightPoint = CGPoint(x: bottomLeftPoint.x + DrawingConstants.gallowHeightWidth, y: bottomLeftPoint.y)
let topLeftPoint = CGPoint(x: bottomLeftPoint.x, y: bounds.size.height * DrawingConstants.gallowHeight)
let topRightPoint = CGPoint(x: bottomRightPoint.x, y: topLeftPoint.y)
connectPoints(bottomLeftPoint: bottomLeftPoint, bottomRightPoint: bottomRightPoint, topLeftPoint: topLeftPoint, topRightPoint: topRightPoint, color: UIColor.brown)
}
func drawGallowAcross() {
let bottomLeftPoint = CGPoint(x: CGFloat(bounds.size.width * DrawingConstants.gallowHeightStart) + DrawingConstants.gallowHeightWidth, y: CGFloat(bounds.size.height * DrawingConstants.gallowHeight + DrawingConstants.gallowBaseHeight))
let bottomRightPoint = CGPoint(x: CGFloat(bounds.size.width * DrawingConstants.gallowAcrossScale), y: bottomLeftPoint.y)
let topLeftPoint = CGPoint(x: bottomLeftPoint.x, y: CGFloat(bounds.size.height * DrawingConstants.gallowHeight))
let topRightPoint = CGPoint(x: CGFloat(bottomRightPoint.x), y: topLeftPoint.y)
connectPoints(bottomLeftPoint: bottomLeftPoint, bottomRightPoint: bottomRightPoint, topLeftPoint: topLeftPoint, topRightPoint: topRightPoint, color: UIColor.brown)
}
func drawGallowTip() {
let topLeftPoint = CGPoint(x: CGFloat(bounds.size.width * DrawingConstants.gallowAcrossScale - DrawingConstants.gallowHeightWidth), y: CGFloat(bounds.size.height * DrawingConstants.gallowHeight + DrawingConstants.gallowBaseHeight))
let topRightPoint = CGPoint(x: CGFloat(bounds.size.width * DrawingConstants.gallowAcrossScale), y: topLeftPoint.y)
let bottomLeftPoint = CGPoint(x: topLeftPoint.x, y: topLeftPoint.y + DrawingConstants.gallowTipHeight)
let bottomRightPoint = CGPoint(x: topRightPoint.x, y: bottomLeftPoint.y)
connectPoints(bottomLeftPoint: bottomLeftPoint, bottomRightPoint: bottomRightPoint, topLeftPoint: topLeftPoint, topRightPoint: topRightPoint, color: UIColor.brown)
}
/*******************************************************************************************/
}
and then it is called from my GameViewController like this
let newPart = BodyPart(rawValue: (model?.attempts)!)
self.hangmanView.add(part: newPart!)
where hangmanView is a GameViewTwo
Upvotes: 1
Views: 1740
Reputation: 1198
Create an enumeration for each body part. Make it an integer so that you can instantiate it with the number of wrong answers.
enum BodyPart: Int {
case head = 0
case leftArm
case rightArm
...
}
to make a new body part
let newPart = BodyPart(rawValue: incorrectGuesses - 1)
Give your view a few functions...
func add(part: BodyPart){
// first get a drawing path according to that part
let partPath = path(for: part)
// lets add a new layer so we can animate it seperately
let newPartLayer = CAShapeLayer()
newPartLayer.frame = bounds
newPartLayer.path = partPath
// linewidth, linecap you might want to play with
let strokeAnim = CABasicAnimation(keyPath: "strokeEnd")
strokeAnim.fromValue = 0
strokeAnim.toValue = 1
strokeAnim.duration = 0.5 // play with it, see what you like
layer.addSublayer(layer: newPartLayer)
newPartLayer.add(anim: strokeAnim)
}
func path(for: BodyPart) -> CGPath {
// draw your part path, return it as a CGPath
}
Then from your controller, instantiate a new part and add it to your hangmanview, it will draw the layer as you drew it in the drawing code. UIBezierPath has a .cgPath variable to cast as a core graphics path.
let newPart = BodyPart(rawValue: incorrectGuesses - 1)
hangmanView.add(part: newPart)
When you want a newgame, just remove all the sublayers from the hangmanView. CAShapeLayers are really useful for simply little shapes, and you can animate changing the path (keyPath: "path") or stroking the path in either direction. It'll look good. You could even move parts around independently or do other fun things with them if you kept a reference to all the part layers. This way, you kind of abide by the model-view-controller paradigm. You keep the state of the game away from your view, all your view does it add on body parts, and your controller provides the parts to do so. It's not a huge deal with simple things, but it's good to keep in mind as you get better and things get more complicated.
Quick edit: you might want to try keyPath: "path", give it a start path(maybe the body path) and an end path, it will look like the part grows out of the body. Good luck!
*** Edited answer.
I cleaned up your code here. I'm not totally sure why you have certain variables in there or the little class private struct, but thats ok, I left em in. I moved some stuff around but mostly kept it the same except where it was clear you were drawing a rectangle instead of some other shape. I didn't try running it. Lemme know how it goes.
enum BodyPart: Int {
case noose = 0
case head
case body
case leftLeg
case rightLeg
case leftArm
case rightArm
case leftEye
case rightEye
case mouth
case unknown
}
class HangmanView: UIView {
var partLayers = [CAShapeLayer]()
private var bodyStart: CGPoint = CGPoint.zero
private var bodyEnd: CGPoint = CGPoint.zero
private var headMiddle: CGPoint = CGPoint.zero
var currentPart: BodyPart?
func clear() {
for i in partLayers {
i.removeFromSuperlayer()
}
partLayers.removeAll()
}
func add(part: BodyPart){
currentPart = part
let newPartLayer = CAShapeLayer()
let partPath = path(forPart: part)
newPartLayer.frame = bounds
newPartLayer.path = partPath.cgPath
newPartLayer.strokeColor = UIColor.black.cgColor
newPartLayer.fillColor = UIColor.clear.cgColor
newPartLayer.lineCap = kCALineCapRound
newPartLayer.lineWidth = part == .rightEye || part == .leftEye ? 1 : 2
let strokeAnim = CABasicAnimation(keyPath: "strokeEnd")
strokeAnim.fromValue = 0
strokeAnim.toValue = 1
strokeAnim.duration = 1
layer.addSublayer(newPartLayer)
newPartLayer.add(strokeAnim, forKey: "path")
partLayers.append(newPartLayer)
}
func path(forPart: BodyPart) -> UIBezierPath {
switch forPart {
case .noose:
return UIBezierPath()
case .head :
let centerX = CGFloat(bounds.size.width * DrawingConstants.gallowAcrossScale - (DrawingConstants.gallowHeightWidth / 2))
let centerY = CGFloat(bounds.size.height * DrawingConstants.gallowHeight + DrawingConstants.gallowBaseHeight + DrawingConstants.gallowTipHeight + ScaleConstants.headRadius)
let center = CGPoint(x: centerX, y: centerY)
headMiddle = center
let path = UIBezierPath(arcCenter: center, radius: ScaleConstants.headRadius, startAngle: CGFloat(0), endAngle: CGFloat(2 * M_PI), clockwise: true)
return path
case .body :
let add = CGFloat(DrawingConstants.gallowBaseHeight + DrawingConstants.gallowTipHeight + 2 * ScaleConstants.headRadius)
let startPointY = CGFloat(bounds.size.height * DrawingConstants.gallowHeight + add)
let startPointX = CGFloat(bounds.size.width * DrawingConstants.gallowAcrossScale - (DrawingConstants.gallowHeightWidth / 2))
let startPoint = CGPoint(x: startPointX, y: startPointY)
let endPoint = CGPoint(x: startPoint.x, y: startPoint.y + ScaleConstants.bodyLength)
bodyStart = startPoint
bodyEnd = endPoint
let path = UIBezierPath()
path.move(to: startPoint)
path.addLine(to: endPoint)
return path
case .leftLeg :
let startPoint = CGPoint(x: bodyEnd.x, y: bodyEnd.y)
let endPoint = CGPoint(x: startPoint.x - ScaleConstants.limbLength, y: startPoint.y + ScaleConstants.limbLength)
let path = UIBezierPath()
path.move(to: startPoint)
path.addLine(to: endPoint)
return path
case .rightLeg :
let startPoint = CGPoint(x: bodyEnd.x, y: bodyEnd.y)
let endPoint = CGPoint(x: startPoint.x + ScaleConstants.limbLength, y: startPoint.y + ScaleConstants.limbLength)
let path = UIBezierPath()
path.move(to: startPoint)
path.addLine(to: endPoint)
return path
case .leftArm :
let startPoint = CGPoint(x: bodyStart.x, y: bodyStart.y + ScaleConstants.handHeightScale * ScaleConstants.bodyLength)
let endPoint = CGPoint(x: startPoint.x - ScaleConstants.limbLength, y: startPoint.y - ScaleConstants.limbLength * ScaleConstants.handHeightScale)
let path = UIBezierPath()
path.move(to: startPoint)
path.addLine(to: endPoint)
return path
case .rightArm :
let startPoint = CGPoint(x: bodyStart.x, y: bodyStart.y + ScaleConstants.handHeightScale * ScaleConstants.bodyLength)
let endPoint = CGPoint(x: startPoint.x + ScaleConstants.limbLength, y: startPoint.y - ScaleConstants.limbLength * ScaleConstants.handHeightScale)
let path = UIBezierPath()
path.move(to: startPoint)
path.addLine(to: endPoint)
return path
case .leftEye :
let eyeMiddle = CGPoint(x: headMiddle.x - ScaleConstants.eyeOffset, y: headMiddle.y - ScaleConstants.eyeOffset)
let path = UIBezierPath(arcCenter: eyeMiddle, radius: ScaleConstants.eyeRadius, startAngle: 0, endAngle: CGFloat(2 * M_PI), clockwise: true)
path.lineWidth = CGFloat(1)
return path
case .rightEye :
let eyeMiddle = CGPoint(x: headMiddle.x + ScaleConstants.eyeOffset, y: headMiddle.y - ScaleConstants.eyeOffset)
let path = UIBezierPath(arcCenter: eyeMiddle, radius: ScaleConstants.eyeRadius, startAngle: 0, endAngle: CGFloat(2 * M_PI), clockwise: true)
path.lineWidth = CGFloat(1)
return path
default:
return UIBezierPath()
}
}
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
// get the drawing context and save the ground state
context.saveGState()
// add sky and grass, now they are just rectangles
context.setFillColor(UIColor.cyan.cgColor)
context.fill(sky(rect))
context.setFillColor(UIColor.green.cgColor)
context.fill(grass(rect))
context.setFillColor(UIColor.brown.cgColor)
context.addPath(gallowBase(rect))
context.addPath(gallowHeight(rect))
context.addPath(gallowAcross(rect))
context.addPath(gallowTip(rect))
context.fillPath()
context.restoreGState()
}
func gallowBase(_ rect: CGRect) -> CGPath {
let bottomLeftPoint = CGPoint(x: CGFloat(rect.width * DrawingConstants.gallowBaseStartScale), y: CGFloat(rect.height * DrawingConstants.grassHeightScale))
let topLeftPoint = CGPoint(x: bottomLeftPoint.x, y: bottomLeftPoint.y - DrawingConstants.gallowBaseHeight)
let topRightPoint = CGPoint(x: CGFloat(rect.width * DrawingConstants.gallowBaseEndScale), y: topLeftPoint.y)
let bottomRightPoint = CGPoint(x: topRightPoint.x, y: bottomLeftPoint.y)
let path = CGMutablePath()
path.addLines(between: [bottomLeftPoint,topLeftPoint, topRightPoint,bottomRightPoint])
path.closeSubpath()
return path
}
func gallowHeight(_ rect: CGRect) -> CGPath {
let bottomLeftPoint = CGPoint(x: CGFloat(rect.width * DrawingConstants.gallowHeightStart), y: CGFloat(rect.height * DrawingConstants.grassHeightScale - DrawingConstants.gallowBaseHeight))
let bottomRightPoint = CGPoint(x: bottomLeftPoint.x + DrawingConstants.gallowHeightWidth, y: bottomLeftPoint.y)
let topLeftPoint = CGPoint(x: bottomLeftPoint.x, y: rect.height * DrawingConstants.gallowHeight)
let topRightPoint = CGPoint(x: bottomRightPoint.x, y: topLeftPoint.y)
let path = CGMutablePath()
path.addLines(between: [bottomLeftPoint,topLeftPoint, topRightPoint,bottomRightPoint])
path.closeSubpath()
return path
}
func gallowAcross(_ rect: CGRect) -> CGPath {
let bottomLeftPoint = CGPoint(x: CGFloat(rect.width * DrawingConstants.gallowHeightStart) + DrawingConstants.gallowHeightWidth, y: CGFloat(rect.height * DrawingConstants.gallowHeight + DrawingConstants.gallowBaseHeight))
let bottomRightPoint = CGPoint(x: CGFloat(rect.width * DrawingConstants.gallowAcrossScale), y: bottomLeftPoint.y)
let topLeftPoint = CGPoint(x: bottomLeftPoint.x, y: CGFloat(rect.height * DrawingConstants.gallowHeight))
let topRightPoint = CGPoint(x: CGFloat(bottomRightPoint.x), y: topLeftPoint.y)
let path = CGMutablePath()
path.addLines(between: [bottomLeftPoint,topLeftPoint, topRightPoint,bottomRightPoint])
path.closeSubpath()
return path
}
func gallowTip(_ rect: CGRect) -> CGPath {
let topLeftPoint = CGPoint(x: CGFloat(rect.width * DrawingConstants.gallowAcrossScale - DrawingConstants.gallowHeightWidth), y: CGFloat(rect.height * DrawingConstants.gallowHeight + DrawingConstants.gallowBaseHeight))
let topRightPoint = CGPoint(x: CGFloat(rect.width * DrawingConstants.gallowAcrossScale), y: topLeftPoint.y)
let bottomLeftPoint = CGPoint(x: topLeftPoint.x, y: topLeftPoint.y + DrawingConstants.gallowTipHeight)
let bottomRightPoint = CGPoint(x: topRightPoint.x, y: bottomLeftPoint.y)
let path = CGMutablePath()
path.addLines(between: [bottomLeftPoint,topLeftPoint, topRightPoint,bottomRightPoint])
path.closeSubpath()
return path
}
func grass(_ rect: CGRect) -> CGRect {
let grassRect = CGRect(x: 0, y: rect.height * DrawingConstants.grassHeightScale, width: rect.width, height: (1 - DrawingConstants.grassHeightScale) * rect.height)
return grassRect
}
func sky(_ rect: CGRect) -> CGRect {
let skyRect = CGRect(x: 0, y: 0, width: rect.width, height: DrawingConstants.grassHeightScale * rect.height)
return skyRect
}
func calculateMidPoint(point1: CGPoint, point2: CGPoint) -> CGPoint {
return CGPoint(x: (point1.x + point2.x) / 2, y: (point1.y + point2.y) / 2)
}
struct DrawingConstants {
static let gallowBaseStartScale: CGFloat = 0.15
static let gallowBaseEndScale: CGFloat = 0.85
static let gallowBaseHeight: CGFloat = 10
static let gallowHeight: CGFloat = 0.05 //static let gallowHeight: CGFloat = 0.15
static let gallowHeightStart: CGFloat = 0.175
static let gallowHeightWidth: CGFloat = 10
static let gallowAcrossScale: CGFloat = 0.5
static let gallowTipHeight: CGFloat = 17.5
static let headRadius: CGFloat = 16
static let bodyLength: CGFloat = 25
static let bodyHeight: CGFloat = 25
static let legLength: CGFloat = 50
static let grassHeightScale: CGFloat = 0.68
static let armBack: CGFloat = 5
}
struct ScaleConstants {
static let bodyLength: CGFloat = 50
static let limbLength: CGFloat = 25
static let handHeightScale: CGFloat = 0.4
static let headRadius: CGFloat = 20
static let eyeRadius = 0.15 * headRadius
static let eyeOffset = 0.3 * headRadius
static let mouthOffSet = 0.3 * headRadius
static let mouthRadius = 0.25 * headRadius
}
}
Upvotes: 2
Reputation: 154513
An approach like this would work. Have a property of your custom HangmanView
class called incorrectGuesses
. Changing that property will trigger a redraw of the view by calling setNeedsDisplay
on itself.
The use of the switch
statement and fallthough
will allow more of the drawing to appear as the incorrectGuesses
increases.
class HangmanView: UIView {
var incorrectGuesses = 0 {
didSet {
self.setNeedsDisplay()
}
}
override func draw(_ rect: CGRect) {
switch incorrectGuesses {
case let x where x > 5:
drawRightLeg()
fallthrough
case 5:
drawLeftLeg()
fallthrough
case 4:
drawRightArm()
fallthrough
case 3:
drawLeftArm()
fallthrough
case 2:
drawBody()
fallthrough
case 1:
drawHead()
fallthrough
case 0:
drawGallows()
default:
break
}
}
func drawGallows() {
// code to draw gallows
}
func drawHead() {
// code to draw head
}
func drawBody() {
// code to draw body
}
func drawLeftArm() {
// code to draw left arm
}
func drawRightArm() {
// code to draw right arm
}
func drawLeftLeg() {
// code to draw left leg
}
func drawRightLeg() {
// code to draw right leg
}
}
To use it, you could have an outlet for the HangmanView
in your ViewController
, and you'd just set the incorrectGuesses
to update the view:
class ViewController: UIViewController {
@IBOutlet weak var hangmanView: HangManView!
var incorrectGuesses = 0
func gameplay() {
...
if letter not in word {
incorrectGuesses += 1
// Update the HangmanView
hangmanView.incorrectGuesses = incorrectGuesses
}
}
}
In your Storyboard, add a UIView
and set the Class to HangmanView
in the Identity Inspector and then connect it to the outlet.
If you want to animate the drawing of the HangmanView
, you could add an animationCount
property to the HangmanView
. When that is set to a value, such as 3
, it will animate the drawing bit by bit every 0.5
seconds. To use it, you'd set hangmanView.animationCount = 3
.
var animationCount = 0 {
didSet {
incorrectGuesses = 0
if animationCount > incorrectGuesses {
_ = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
self.incorrectGuesses += 1
if self.incorrectGuesses >= self.animationCount {
timer.invalidate()
}
}
}
}
}
Upvotes: 1