เมื่อเริ่มต้นการเดินทางเพื่อแก้ปัญหา Hangman Challenge หนึ่งในขั้นตอนแรกที่เกี่ยวข้องกับการค้นหาการอ้างอิงการออกแบบเพื่อทำให้ UI ของเกมมีชีวิตชีวา หลังจากการวิจัยและพิจารณาอย่างถี่ถ้วนแล้ว ฉันสะดุดกับเทมเพลตการออกแบบที่น่าทึ่งบน Figma ที่เรียกว่า "Hangman UI" ที่สร้างโดย "Valentin" การออกแบบนี้เป็นรากฐานที่ดีเยี่ยมในการสร้างเกม

ต่อไปนี้เป็นภาพรวมเบื้องต้นของเทมเพลตการออกแบบที่ฉันเริ่มด้วย:

ความสวยงามของเทมเพลตนี้คือความเรียบง่ายและชัดเจน ทำให้เป็นจุดเริ่มต้นที่ดีเยี่ยมสำหรับโครงการของเรา อย่างไรก็ตาม ฉันตั้งเป้าที่จะปรับปรุงให้ดียิ่งขึ้นโดยการเพิ่มองค์ประกอบแบบโต้ตอบที่จะช่วยให้ผู้ใช้มีส่วนร่วมในเกมอย่างแข็งขัน

การเปลี่ยนแปลงครั้งแรกของฉันคือการรวมปุ่มตัวอักษรเข้ากับดีไซน์ ช่วยให้ผู้เล่นเดาตัวอักษรและไขปริศนาได้ สิ่งนี้ไม่เพียงแต่ทำให้เกมมีส่วนร่วมมากขึ้น แต่ยังเพิ่มองค์ประกอบของการโต้ตอบที่จำเป็นสำหรับเกมอีกด้วย

ต่อไปนี้เป็นข้อมูลคร่าวๆ ว่าฉันเปลี่ยนการออกแบบให้มีปุ่มตัวอักษรเหล่านี้ได้อย่างไร:

แต่การเดินทางยังอีกยาวไกล! เพื่อจับภาพแก่นแท้ของเกม Hangman แบบคลาสสิกอย่างแท้จริง ฉันจึงตัดสินใจนำเสนอภาพกระบวนการแขวนคอ จากเทมเพลตการออกแบบ ฉันแบ่งภาพเพชฌฆาตออกเป็นเจ็ดส่วน แต่ละส่วนแสดงถึงส่วนหนึ่งของร่างกายของเพชฌฆาต เมื่อเกมดำเนินไป ชิ้นส่วนเหล่านี้จะถูกเปิดเผย ซึ่งสะท้อนถึงความก้าวหน้าของผู้เล่นและสุขภาพที่เหลืออยู่

นี่คือภาพรวมของการออกแบบที่แสดงชิ้นส่วนเหล่านี้:

การเปลี่ยนแปลงนี้ไม่เพียงแต่เพิ่มองค์ประกอบภาพที่น่าตื่นเต้นเท่านั้น แต่ยังช่วยให้ผู้เล่นได้ทราบถึงความพยายามที่เหลืออยู่อย่างชัดเจน เพิ่มความลุ้นระทึกและความท้าทายของเกม

ต่อไป เราจะเจาะลึกลงไปในโค้ดและตรรกะเบื้องหลังเกม! - ติดกับมัน!

การสร้างคุณสมบัติ

สิ่งเหล่านี้คือคุณสมบัติที่เป็นแกนหลักของอินเทอร์เฟซผู้ใช้และฟังก์ชันการทำงานของเกม Hangman...

var scoreLabel: UILabel!
var hintLabel: UILabel!
var hangmanImage: UIImageView!

var words = [String]()
var answer: String!
var answerLetters = [AnswerLetter]()
var lettersStack: UIStackView!

var letterButtons = [UIButton]()
    
var letters: [String] = {
    let letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    return letters.map { String($0) }
}()
    
var incorrectCount = 0 {
    didSet {
        hangmanImage.image = UIImage(named: "hangman\(incorrectCount)")
    }
}
    
var score = 0 {
    didSet {
        scoreLabel.text = "Score: \(score)"
    }
}
    
var hint = "" {
    didSet {
        hintLabel.text = "Hint: \(hint)"
    }
}

เริ่มต้นด้วยป้ายกำกับ คุณสมบัติ scoreLabel จะถูกสร้างขึ้นเพื่อแสดงคะแนนหลังจากคาดเดาคำได้สำเร็จ ในขณะเดียวกัน คุณสมบัติ hintLabel ถูกสร้างขึ้นเพื่อให้คำใบ้ที่ช่วยให้ผู้เล่นเดาคำศัพท์ได้

จากนั้น ฉันสร้างคุณสมบัติ hangmanImage เพื่อแสดงรูปภาพของ Hangman ในขณะที่เกมดำเนินไป มันอัปเดตด้วยสายตาเมื่อผู้เล่นเดาผิด

คุณสมบัติเหล่านี้ได้รับการอัปเดตแบบไดนามิกโดยใช้ ผู้สังเกตการณ์คุณสมบัติ ทำได้โดยการสังเกตการเปลี่ยนแปลงในคุณสมบัติ คะแนน คำใบ้และการนับที่ไม่ถูกต้อง

ต่อไป ฉันสร้างคุณสมบัติ คำ เพื่อให้มีคำที่ผู้เล่นจะพยายามเดา ในขณะที่คุณสมบัติ คำตอบ จะมีคำตอบที่ถูกต้องสำหรับรอบปัจจุบัน โดยเลือกแบบสุ่มจาก < คุณสมบัติ>คำที่แข็งแกร่ง

เพื่อติดตามตัวอักษรและป้ายกำกับแต่ละตัวในคำตอบ ฉันได้สร้างคุณสมบัติ answerLettersแต่ละป้ายกำกับภายในคุณสมบัติ answerLetters มีอยู่ใน คุณสมบัติ letterStack

หลังจากนั้น ฉันได้สร้างคุณสมบัติ letterButtons เพื่อให้มีปุ่มที่แสดงตัวอักษรตั้งแต่ A ถึง Z ผู้เล่นจะใช้ปุ่มเหล่านี้ในการเดา

สุดท้ายนี้ ในการตั้งชื่อให้กับแต่ละปุ่ม ฉันได้สร้างคุณสมบัติ ตัวอักษร สำหรับการแปลงอักขระในค่าคงที่ของคุณสมบัติให้เป็นอาร์เรย์ของสตริงแต่ละตัวโดยใช้ฟังก์ชัน map

การสร้าง UI

อินเทอร์เฟซผู้ใช้สำหรับเกมนี้ค่อนข้างเรียบง่าย นอกจากนี้ การออกแบบอัตโนมัติยังอยู่ที่นี่เพื่อจัดการงานส่วนใหญ่สำหรับ UI อีกด้วย หากเราประกอบทีละชิ้น นี่คือประเด็นสำคัญที่ฉันติดตามในการสร้าง UI:

  • ด้านบนของรูปภาพชื่อเรื่องจะถูกปักหมุดไว้ที่ด้านบนของมุมมองหลัก โดยเยื้องไว้ 40 จุดเพื่อให้ดูเรียบร้อยยิ่งขึ้น
  • รูปภาพเพชฌฆาตบรรจุอยู่ในสแต็กแนวนอน ซึ่งมีสแต็กแนวตั้งด้วย กลุ่มแนวตั้งนี้ประกอบด้วยป้ายคำตอบ มุมมองปุ่มตัวอักษร และข้อมูลเพิ่มเติมของเกม (ป้ายคำใบ้และคะแนน)
  • รูปภาพเพชmanฆาตมีจุดยึดความกว้าง 0.25 ของความกว้างของมุมมองหลัก ส่วนความสูงจะมีความสูง 0.7 ของความกว้างของมุมมองหลัก
  • ด้านบนของสแต็กแนวนอนจะถูกปักหมุดไว้ที่ด้านล่างของรูปภาพชื่อ โดยเยื้อง 40 พอยต์ ส่วนความสูงจะมีความสูง 0.7 ของความกว้างของมุมมองหลัก สุดท้าย จัดให้อยู่กึ่งกลางแนวนอนในมุมมองหลัก
  • มุมมองปุ่มตัวอักษรมีขนาดคงที่คือกว้าง 510 พอยต์และสูง 425 พอยต์
  • สแต็กแนวตั้งมีจุดยึดความกว้างที่เท่ากับความกว้างของมุมมองปุ่มตัวอักษร

การสร้างตรรกะ

ตอนนี้เพื่อความสนุก — ตรรกะ!

ก่อนอื่น ฉันเรียกใช้ฟังก์ชัน loadRound ใน viewDidLoad และเริ่มเขียนมัน จากนั้น ในฟังก์ชัน ฉันกำหนดคุณสมบัติ hint ด้วยค่า 'Animal'

ต่อไป ฉันพยายามค้นหาไฟล์ข้อความชื่อ “animals.txt” ในกลุ่มหลักของแอป ไฟล์นี้ประกอบด้วยรายการคำที่เกี่ยวข้องกับสัตว์ อาร์เรย์ของคำนี้ถูกจัดเก็บไว้ในคุณสมบัติ คำ

หลังจากนั้น คำสุ่มจะถูกเลือกจากคุณสมบัติ คำ และกำหนดให้กับคุณสมบัติ คำตอบ นี่คือคำที่ผู้เล่นจะพยายามเดาในเกม

สำหรับตัวอักษรแต่ละตัวในคุณสมบัติ answer ฉันจะสร้าง UILabel ใหม่ชื่อ letterLabel ป้ายกำกับนี้ถูกตั้งค่าให้แสดงขีดล่าง ('_') เพื่อแสดงตัวอักษรที่ไม่รู้จักในเกม จากนั้น ฉันใช้โครงสร้างแบบกำหนดเองที่เรียกว่า AnswerLetter เพื่อรวมป้ายกำกับนี้เข้ากับตัวอักษรที่เกี่ยวข้องจากคำนั้น และผลลัพธ์ที่ได้จะถูกเพิ่มลงในอาร์เรย์ answerLetters

สุดท้าย โค้ดจะวนซ้ำผ่านคุณสมบัติ answerLetters และเพิ่มแต่ละป้ายกำกับจากคุณสมบัตินั้นไปยัง UIStackView ที่เรียกว่า lettersStack

นี่คือลักษณะที่ปรากฏในโค้ด:

func loadRound() {
    hint = "Animal"
        
    if let fileURL = Bundle.main.url(forResource: "animals", withExtension: "txt") {
        if let contents = try? String(contentsOf: fileURL) {
            words = contents.components(separatedBy: "\n")
            words.removeLast()
            answer = words.randomElement()!
                
            for letter in answer {
                let letterLabel = UILabel()
                letterLabel.text = "_"
                letterLabel.font = UIFont.boldSystemFont(ofSize: 45)
                answerLetters.append(AnswerLetter(label: letterLabel, letter: String(letter)))
            }
        }
    }    
        
    for index in 0..<answerLetters.count {
        lettersStack.addArrangedSubview(answerLetters[index].label)
    }
}

ตอนนี้ เมื่อผู้ใช้แตะตัวอักษร ฉันต้องการให้บางสิ่งเกิดขึ้น:

  • มันดึงตัวอักษรที่แสดงโดยปุ่มที่แตะ
  • หากตัวอักษรอยู่ในคำคำตอบ ระบบจะอัปเดตจอแสดงผลเพื่อแสดงตัวอักษรที่เดาถูกต้อง จากนั้นตรวจสอบว่าเดาตัวอักษรทั้งหมดถูกต้องหรือไม่ และหากเป็นเช่นนั้น ก็จะแสดงข้อความชนะ
  • หากตัวอักษรไม่อยู่ในคำตอบ จะเพิ่มจำนวนการทายผิด หากผู้เล่นทายผิดเจ็ดครั้ง (ถึงขีดจำกัดแล้ว) จะแสดงข้อความแพ้
  • ไม่ว่าตัวอักษรจะถูกหรือไม่ก็ตาม มันจะซ่อนปุ่มที่แตะไว้เพื่อป้องกันการเลือกตัวอักษรเดิมต่อไป

นี่คือวิธีการเข้ารหัส:

@objc func letterTapped(_ sender: UIButton) {
    guard let buttonLetter = sender.titleLabel?.text else { return }
        
    if answer.contains(buttonLetter) {
        for answer in answerLetters {
            if answer.letter == buttonLetter {
                answer.label.text = buttonLetter
            }
        }
            
        if !answerLetters.contains(where: { $0.label.text == "_"}) {
            showAlert(isWin: true)
        }
    } else {
        incorrectCount += 1
        if incorrectCount == 7 {
            showAlert(isWin: false)
        }
      }
      sender.isHidden = true
}

แค่นั้นแหละ!

ขอบคุณที่ติดตามฉันจนถึงตอนนี้ การสนับสนุนและผู้อ่านของคุณมีความหมายต่อฉันมาก และฉันอยากได้ยินความคิดเห็นและข้อเสนอแนะของคุณเกี่ยวกับ UIKit หรือหัวข้อใดๆ ที่คุณต้องการให้ฉันสำรวจเพิ่มเติมในอนาคต

ก่อนที่เราจะเสร็จสิ้น โปรดตรวจสอบโค้ดเต็มได้ที่ my GitHub repo: