2009-07-26 35 views
21

Làm việc trên một trò chơi trong Objective-C/Cocoa cho OS X, và tôi đã hoàn thành mẫu thử nhiều như nó đáng để hoàn thành. Đó là một mớ hỗn độn của mã, là trò chơi đầu tiên của tôi, nhưng mọi thứ đều hoạt động. Tôi đã đọc lên cách tốt nhất để đặt mọi thứ lại với nhau, và MVC có vẻ có ý nghĩa nhất, nhưng tôi hơi bối rối.Triển khai Model-View-Controller đúng cách

Bắt đầu từ đâu? Với bộ điều khiển? Điều đó dường như có ý nghĩa nhất đối với tôi, nhưng nó bắt đầu như thế nào? Trong mớ hỗn độn của tôi về nguyên mẫu, tôi có mọi thứ bắt đầu từ số init của chế độ xem và đi từ đó. Tôi có thể làm điều tương tự cho bộ điều khiển và đưa những thứ cần thiết vào số init? Hoặc có cái gì khác tôi có thể sử dụng để làm điều này? Nếu nó bắt đầu từ init, làm cách nào để tôi init bộ điều khiển?

Làm cách nào để thiết lập thế giới trò chơi? Tôi hiện đang sử dụng hai mảng, một cho thế giới (Tường, Sàn nhà, Cửa, Nước, Dung nham, v.v.) và một cho các mục (Tôi sẽ thêm một phần ba cho các ký tự). Bản đồ (a .plist) được nạp, và sau đó các đối tượng được tạo và thêm vào mảng mà nó thuộc về. Các mảng này đi đâu? Trong nguyên mẫu, chúng cũng là một phần của khung nhìn, vì vậy tôi đoán bạn có thể nói tôi đã kết hợp cả hai (View và Controller) với nhau. Sẽ có một đối tượng Map được tạo cho mỗi bản đồ? Sẽ có một đối tượng Bản đồ có chứa tất cả các bản đồ không?

Mọi thứ hoạt động cùng nhau như thế nào? Người chơi nhấn một phím, di chuyển nhân vật trong game. Quan điểm sẽ xử lý đầu vào, phải không? Bạn sẽ gửi điều đó đến bộ điều khiển, nó sẽ kiểm tra mọi thứ (tường, quái vật, vv) trong bản đồ/mảng khác, và sau đó trả lại kết quả? Hoặc bạn sẽ gửi nó cho người chơi, mà sẽ đi đến bộ điều khiển, mà sẽ làm tất cả các kiểm tra, và sau đó trả lại kết quả?

Tôi nghĩ rằng tôi đã có nó khá độc đáo đặt ra trong đầu của tôi, nhưng tôi càng nghĩ về nó, các ý tưởng của tôi càng ít rắn chắc và tôi càng bối rối. Bởi tất cả các phương tiện không ngần ngại vẽ một cái gì đó lên nếu bạn nghĩ rằng nó sẽ có được điểm trên hiệu quả hơn.

Nếu bạn đã dành thời gian để đọc tất cả những điều này, cảm ơn sự kiên nhẫn của bạn. Từ những gì tôi đã thu thập, hầu hết mọi người viết mã không sử dụng bất kỳ loại thiết kế nào. Sau khi đọc về điều này, tôi có thể thấy lý do tại sao một số người sẽ tránh nó, nó gây nhầm lẫn và mọi người dường như nghĩ rằng nó không phải là giá trị thời gian. Cá nhân tôi nghĩ rằng những lợi thế hoàn toàn vượt trội hơn những nhược điểm (có bất kỳ?) Và nó chỉ có ý nghĩa để giữ cho mọi thứ được tổ chức theo cách mà bạn sẽ không phải viết lại mỗi khi bạn muốn thực hiện một tính năng mới. Bạn sẽ không xây nhà, xe hơi hay thiết bị không có thiết kế, tại sao bạn lại viết một chương trình phức tạp mà không có một chương trình?

Tôi đã đặt câu hỏi này bởi vì tôi muốn làm điều đó đúng cách, thay vì hacking và một nửa assing theo cách của tôi để "chiến thắng".

+0

Tôi đang thêm 300 tiền thưởng. Tôi muốn một câu trả lời tốt chết tiệt. – Sneakyness

+0

Nếu bạn cần tôi làm rõ điều gì đó hoặc bạn cần chi tiết hơn, hãy cho tôi biết ngay tại đây, tôi sẵn lòng hơn. – Sneakyness

+0

Xin lỗi để trả lời một câu trả lời crappy lần đầu tiên, tôi không có nhiều thời gian nhưng nghĩ rằng bài thuyết trình có thể giúp :-) –

Trả lời

17

Bạn có thể quan tâm đến bản trình bày tôi đã cung cấp cho ACCU '09 - "Adopting Model-View-Controller in Cocoa and Objective-C".

Bắt đầu từ đâu? Với bộ điều khiển ? Điều đó dường như làm cho số ý nghĩa nhất đối với tôi, nhưng làm cách nào để bắt đầu? ?

Tạo dự án ứng dụng Cocoa mới và bạn sẽ thấy rằng đã có lớp điều khiển do mẫu cung cấp - đó là lớp đại biểu ứng dụng. Bây giờ hãy xem MainMenu.xib. Có một ví dụ về đại biểu ứng dụng và nó được kết nối với ổ cắm delegate của đối tượng "Chủ sở hữu tệp". Trong trường hợp này, NSApplication là Chủ sở hữu tệp; đó là thứ muốn MainMenu được giải nén. Vì vậy, đây thực sự là đại biểu của ứng dụng.

Điều đó có nghĩa là chúng tôi có thứ gì đó là đối tượng điều khiển, có thể nói chuyện với cá thể NSApplication và có thể có các cửa hàng cho tất cả các đối tượng khác trong XIB. Điều đó làm cho nó trở thành một nơi tuyệt vời để thiết lập trạng thái ban đầu của ứng dụng - tức là là "điểm vào" cho ứng dụng của bạn. Trong thực tế, điểm vào phải là phương thức -applicationDidFinishLaunching:. Điều đó được gọi là khi ứng dụng đã hoàn thành tất cả những thứ cần thiết để ứng dụng của bạn trở thành trạng thái ổn định, chạy - nói cách khác Cocoa rất vui vì nó đã hoàn thành những gì cần thiết và mọi thứ khác tùy thuộc vào bạn.

-applicationDidFinishLaunching: là nơi bạn muốn tạo hoặc khôi phục Mô hình ban đầu, là biểu diễn trạng thái của ứng dụng (bạn cũng có thể nghĩ nó là đại diện cho tài liệu của người dùng, nếu tương tự phù hợp với ứng dụng - tài liệu của bạn các ứng dụng dựa trên chỉ phức tạp hơn một chút so với các ứng dụng mặc định) và cho Chế độ xem biết cách thể hiện mọi thứ cho người dùng. Trong nhiều ứng dụng bạn không cần tải toàn bộ Mô hình khi ứng dụng đã khởi chạy; cho một sự khởi đầu nó có thể được làm chậm và sử dụng bộ nhớ nhiều hơn bạn cần, và thứ hai lần xem đầu tiên có lẽ không hiển thị tất cả các bit về mô hình. Vì vậy, bạn chỉ cần tải các bit bạn cần để hiển thị người dùng những gì đang lên; bạn đang Kiểm soát tương tác giữa Chế độ xem và Mô hình.

Nếu bạn cần hiển thị thông tin khác trong Chế độ xem khác - ví dụ: nếu Chế độ xem chính của bạn là chế độ xem chính và bạn cần hiển thị trình chỉnh sửa chi tiết - thì khi người dùng cho bạn biết họ muốn làm gì điều đó. Họ cho bạn biết bằng cách thực hiện một số hành động mà bạn có thể xử lý trong ủy nhiệm ứng dụng. Sau đó, bạn tạo một Bộ điều khiển mới để quay lại Chế độ xem mới và cho biết nơi cần lấy thông tin về Mô hình cần thiết. Bạn có thể giữ các đối tượng Xem khác trong một XIB riêng biệt, vì vậy chúng chỉ được tải khi cần.

Làm cách nào để thiết lập thế giới trò chơi? Tôi hiện đang sử dụng hai mảng, một cho thế giới (Tường, Sàn nhà, Cửa, Nước, Nham thạch, v.v.) và một cho các mục (Tôi sẽ thêm 1/3 cho ký tự). Bản đồ (a .plist) là được tải, và sau đó các đối tượng là được tạo và thêm vào mảng nó thuộc về. Các mảng này đi đâu? Trong nguyên mẫu, chúng cũng là một phần của chế độ xem, vì vậy tôi đoán bạn có thể nói tôi kết hợp cả hai (Xem và Điều khiển) với nhau. Sẽ có một đối tượng Bản đồ được tạo cho mỗi bản đồ không? Sẽ có một đối tượng Bản đồ có chứa tất cả các bản đồ không?

Chúng tôi có thể tìm ra những đối tượng mà chúng tôi đang lập mô hình bằng cách phân tích tuyên bố của bạn ở trên - bạn có thể không nhận ra, nhưng bạn đã phác thảo một đặc điểm :-). Có một thế giới chứa các bức tường, cửa ra vào, v.v., vì vậy chúng ta biết chúng ta cần các vật thể cho chúng, và rằng chúng nên thuộc về cho một vật thể thế giới. Nhưng chúng ta cũng có các vật phẩm và nhân vật - chúng tương tác với thế giới như thế nào? Một nơi có thể chứa nước và một nhân vật? Nếu vậy, có lẽ thế giới được tạo thành từ Địa điểm và mỗi Vị trí có thể có tường hoặc cửa hoặc bất kỳ thứ gì và nó cũng có thể có các vật phẩm và nhân vật. Lưu ý rằng nếu tôi viết nó như thế này, có vẻ như là mục thuộc về vị trí, không phải là vị trí cho mục đó. Tôi sẽ nói "mat có một con mèo trên đó" thay vì "con mèo có một tấm thảm bên dưới nó".

Vì vậy, chỉ cần nghĩ về những gì bạn muốn thế giới trò chơi của mình thể hiện và mối quan hệ giữa những thứ trong trò chơi. Điều này được gọi là mô hình miền, bởi vì bạn đang mô tả những thứ trong thế giới trò chơi thay vì cố mô tả mọi thứ trong thế giới phần mềm. Nếu nó giúp, hãy viết ra một vài câu mô tả thế giới game, và tìm kiếm động từ và danh từ giống như tôi đã làm trong đoạn cuối.

Bây giờ một số danh từ của bạn sẽ trở thành đối tượng trong phần mềm, một số sẽ trở thành thuộc tính của các đối tượng khác. Động từ sẽ là hành động (tức là phương pháp). Nhưng dù bằng cách nào, nó sẽ dễ dàng hơn để suy nghĩ về nếu bạn xem xét những gì bạn đang cố gắng để mô hình đầu tiên, thay vì nhảy thẳng xuống phần mềm.

Mọi thứ hoạt động cùng nhau như thế nào? Trình phát nhấn một phím, di chuyển ký tự trong trò chơi. Quan điểm sẽ được xử lý đầu vào, phải không? bạn gửi cho bộ điều khiển, nào sẽ kiểm tra mọi thứ (tường, quái vật, v.v.) trong bản đồ/các mảng khác và sau đó trả về kết quả? Hoặc bạn sẽ gửi cho người chơi, trong đó sẽ chuyển đến bộ điều khiển, trong đó sẽ thực hiện tất cả séc và sau đó trả lại kết quả?

Tôi thích làm theo chính sách "nói, đừng hỏi", cho biết bạn chỉ huy một đối tượng để làm điều gì đó thay vì yêu cầu nó cung cấp cho bạn thông tin để đưa ra quyết định. Bằng cách đó, nếu hành vi thay đổi, bạn chỉ cần sửa đổi đối tượng đang được nói. Điều này có nghĩa cho ví dụ của bạn là Chế độ xem xử lý sự kiện nhấn phím (nó do chúng được xử lý bởi NSControl) và nó cho Bộ điều khiển biết rằng sự kiện này đã xảy ra. Giả sử Chế độ xem nhận được phím tắt "mũi tên trái" và Trình điều khiển quyết định điều này có nghĩa là người chơi phải di chuyển sang trái. Tôi chỉ bảo người chơi "di chuyển sang trái", và để người chơi phân loại những gì xảy ra khi di chuyển trái nghĩa là va vào tường hoặc quái vật.

Để giải thích lý do tại sao tôi muốn thực hiện theo cách đó, hãy tưởng tượng bạn thêm vào trò chơi 1.1 khả năng để người chơi bơi. Bây giờ người chơi có một số tài sản ableToSwim, vì vậy bạn cần thay đổi trình phát. Nếu bạn yêu cầu người chơi di chuyển sang trái, thì bạn cập nhật trình phát để biết những gì di chuyển sang trái trên mặt nước có nghĩa là tùy thuộc vào việc họ có thể bơi hay không. Nếu thay vào đó, Bộ điều khiển yêu cầu người chơi di chuyển sang trái và đưa ra quyết định, thì Bộ điều khiển cần biết để hỏi về việc có thể bơi, và cần biết ý nghĩa của nó ở gần nước. Cũng như bất kỳ đối tượng điều khiển nào khác trong trò chơi có thể tương tác với người chơi, cũng như bộ điều khiển trong trò chơi dành cho iPhone ;-).

+0

Tôi thích nó, cảm ơn. Nhưng, điều này vẫn không cho tôi biết nó bắt đầu từ đâu. Bộ điều khiển khởi động như thế nào? Từ awakefromnib của xem? Có cái gì đó tôi phải mất tích. – Sneakyness

+0

Ok vì vậy tôi sử dụng applicationDidFinishLaunching, nhưng làm thế nào? tôi có sử dụng nó giống như bạn sử dụng awakefromnib không? – Sneakyness

+0

Re -applicationDidFinishLaunching: nó có hiệu quả chỉ là một chức năng được gọi lại bởi Cocoa. Hãy nghĩ về nó như là điểm vào giống như main() là trong một chương trình C, ngoại trừ tất cả các đối tượng bạn đã tạo trong XIB của bạn có thể đã được sử dụng. –

4

Bạn nên tạo mô hình cho toàn bộ trò chơi. Nó phải chứa mọi thứ về trò chơi ngoại trừ tương tác GUI. Các khung nhìn, ở phía bên kia, chứa tất cả các công cụ GUI mà không biết gì về luồng game.

Toàn bộ điểm là các mô hình và chế độ xem được dự kiến ​​sẽ có thể sử dụng lại được. Các lớp mô hình nên chơi với bất kỳ GUI nào (và thậm chí cả giao diện điều khiển hoặc dòng lệnh). Các lớp xem sẽ có thể được sử dụng với các trò chơi tương tự khác. Mô hình và quan điểm nên được tách hoàn toàn.

Sau đó, bộ điều khiển sẽ lấp đầy khoảng trống. Nó phản ứng trên đầu vào của người dùng, yêu cầu các lớp mô hình thực hiện một di chuyển trò chơi cụ thể và yêu cầu các khung nhìn hiển thị tình huống mới. Bộ điều khiển không được dự kiến ​​có thể sử dụng lại được. Đó là keo giữ trò chơi với nhau. Bộ điều khiển đảm bảo rằng các lớp mô hình và các lớp xem vẫn giữ nguyên và có thể tái sử dụng được.

Ngoài ra, đừng cố gắng làm cho thiết kế hoàn hảo ngay từ đầu. Đừng ngần ngại refactor bất cứ lúc nào. Quyết định thiết kế tồi tệ hơn được sửa chữa nhanh hơn, nó càng ít xấu đi. Thiết kế mọi thứ trả trước có nghĩa là một quyết định thiết kế tồi tệ sẽ không được sửa chữa chút nào, trừ khi bạn thực hiện một thiết kế hoàn hảo trả trước, điều đơn giản là không thể hiểu được ngay cả với nhiều thập kỷ kinh nghiệm.

Luôn nhớ quy tắc thiết kế thứ ba của Hệ thống cửa sổ X: "Điều tồi tệ nhất so với khái quát hóa từ một ví dụ là khái quát hóa từ không có ví dụ nào".

+0

Đó là những gì tôi đã cố gắng làm, nhưng tôi tiếp tục bị treo lên nơi đặt mọi thứ . – Sneakyness

+0

BTW bạn sẽ không có một mô hình, chế độ xem hoặc bộ điều khiển duy nhất cho toàn bộ trò chơi. Bạn sẽ có một mô hình quái vật, xem quái vật, bộ điều khiển quái vật ... mọi thứ nên được chia nhỏ theo mục đích, theo nghĩa hướng đối tượng. Mảng quái vật của bạn sẽ là một phần của mô hình trong một số mô hình khác, giống như mô hình cấp độ. –

+3

@emddudley: Bạn thường có mô hình _one_, nhưng nó bao gồm nhiều lớp và/hoặc chức năng. :-) – vog

0

Đối với trò chơi của bạn, mô hình sẽ bao gồm những thứ như vị trí hiện tại của nhân vật, số điểm sức khỏe và các giá trị khác liên quan đến "trạng thái" của trò chơi. Nó sẽ thông báo cho xem bất cứ khi nào một cái gì đó thay đổi để xem có thể cập nhật chính nó. Chế độ xem chỉ đơn giản là mã được yêu cầu để hiển thị mọi thứ cho người dùng. Bộ điều khiển chịu trách nhiệm đáp ứng đầu vào của người dùng và cập nhật mô hình khi cần thiết.

Bộ điều khiển là thành phần trung tâm trong điều này và đó là những gì sẽ khởi tạo mô hình và chế độ xem.

Khi trình phát nhấn phím, khung nhìn chỉ cần chuyển lệnh đó vào bộ điều khiển, quyết định phải làm gì.

Bạn sai mà hầu hết mọi người không thiết kế. Có lẽ hầu hết những người nghiệp dư, hoặc có lẽ là những người đặt nhiều câu hỏi trực tuyến nhất, nhưng không phải bất cứ ai làm việc trên một dự án thậm chí còn hơi phức tạp. Các lập trình viên chuyên nghiệp đều có kinh nghiệm trong thiết kế phần mềm; nếu không có nó, họ sẽ không thể thực hiện công việc của họ (viết phần mềm để làm X).

Trò chơi của bạn có vẻ như đủ phức tạp để đảm bảo mẫu kiến ​​trúc như MVC. Có một số trường hợp trong đó một phần mềm đơn giản là đủ để MVC quá tải và không cần thiết làm phức tạp mọi thứ, nhưng ngưỡng sử dụng MVC là khá thấp.

+0

Vâng, tôi đã chọn viết một Roguelike cho trò chơi/chương trình đầu tiên của mình. Hóa ra đó là một lựa chọn khó khăn, nhưng cá nhân tôi nghĩ đó là sự lựa chọn đúng đắn. – Sneakyness

0

Bạn có thể tìm thấy bài viết này Introduction to MVC design using C# hữu ích. Mặc dù ví dụ là trong C#, nguyên tắc nên áp dụng.

+0

Điều đó vẫn nói với tôi hoàn toàn không có gì là bắt đầu mọi thứ. Hoặc thậm chí làm thế nào để bắt đầu mọi thứ. – Sneakyness

7

Dường như sự nhầm lẫn chính của bạn là cách mọi thứ được tạo trong khi khởi động ứng dụng. Rất nhiều người bị mắc kẹt về điều này, bởi vì nó cảm thấy như một cái gì đó ma thuật đang xảy ra, nhưng hãy để tôi xem nếu tôi có thể phá vỡ nó xuống.

  1. App được đưa ra bởi người sử dụng
  2. C main() hàm được gọi
  3. main() gọi NSApplicationMain()
  4. NSApplicationMain() nạp MainMenu.nib (hoặc một ngòi bút theo quy định tại thông tin plist)
  5. nib nạp khởi tạo tất cả các đối tượng quy định tại các nib (bao gồm cả các đại biểu ứng dụng)
  6. nib tải làm cho tất cả các kết nối được định nghĩa trong nib
  7. Nib tải cuộc gọi awakeFromNib trên tất cả các đối tượng mà nó vừa tạo
  8. -ứng dụngWillFinishLaunching: được gọi trong ứng dụng đại biểu.
  9. NSApplication (hoặc một phân lớp được chỉ định trong Info.plist) được khởi tạo
  10. -ứng dụngDidFinishLaunching: được gọi trong ứng dụng đại biểu.

Lưu ý rằng ủy nhiệm ứng dụng được khởi tạo TRƯỚC KHI lớp NSApplication (phụ). Đó là lý do tại sao ứng dụng (Will | Did) FinishLaunching: nhận một thông báo trái với NSApplication nó là một đại biểu của.Hậu quả chung của việc này là các khung nhìn của bạn được tạo ra cho bạn thông qua các ngòi, và các bộ điều khiển của bạn được tạo ra như là một tác dụng phụ của việc khởi chạy nib vì chúng có xu hướng trở thành đối tượng cấp cơ sở trong ngòi hoặc chủ sở hữu tệp của nib. . Mô hình của bạn thường được tạo hoặc trong ứng dụng đại biểu hoặc như là một singleton đó là lười biếng khởi tạo khi một cái gì đó đầu tiên truy cập nó.

+0

Ứng dụng ủy nhiệm không được chỉ định trong Info.plist và không được khởi tạo bởi NSApplicationMain(). Nó được đăng trên Nib và kết nối với NSApp thông qua File's Owner. Nó cũng không thể gửi -applicationWillFinishLaunching: trước khi NSApp được khởi tạo, vì ứng dụng chưa tồn tại với willFinishLaunching. –

+0

Bạn đúng là nó không được khai báo trong Info.plist, tôi sẽ thay đổi và sắp xếp lại một cách thích hợp. applicationWillFinishLaunching: hoàn toàn có thể được gửi trước khi ứng dụng đã được khởi tạo và đó là hành vi được tài liệu của nó: "Được gửi bởi trung tâm thông báo mặc định ngay trước khi đối tượng ứng dụng được khởi tạo." –

+0

Vì vậy, tôi vừa thử nghiệm và có vẻ như NSApp được khởi tạo trước khi applicationWillFinishLaunching :, nhưng điều đó không cần phải đúng, nó không phải là hành vi được ghi lại tài liệu và tôi chắc chắn đó không phải là cách nó hoạt động trong các phiên bản cũ hơn của OS X. –

Các vấn đề liên quan