{"id":15805362,"url":"https://github.com/csr632/priests-and-devils","last_synced_at":"2025-09-11T23:32:30.052Z","repository":{"id":119608560,"uuid":"84826135","full_name":"csr632/Priests-and-devils","owner":"csr632","description":"Unity 小游戏：牧师与恶魔","archived":false,"fork":false,"pushed_at":"2017-03-18T17:10:07.000Z","size":11431,"stargazers_count":10,"open_issues_count":1,"forks_count":5,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-10-06T02:20:17.096Z","etag":null,"topics":["csharp","unity"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/csr632.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-03-13T12:53:03.000Z","updated_at":"2024-01-02T06:50:39.000Z","dependencies_parsed_at":null,"dependency_job_id":"cb3a2891-2ec9-4326-a5ac-0e270df0cf1f","html_url":"https://github.com/csr632/Priests-and-devils","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/csr632%2FPriests-and-devils","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/csr632%2FPriests-and-devils/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/csr632%2FPriests-and-devils/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/csr632%2FPriests-and-devils/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/csr632","download_url":"https://codeload.github.com/csr632/Priests-and-devils/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":232670354,"owners_count":18558569,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","unity"],"created_at":"2024-10-05T02:20:27.625Z","updated_at":"2025-01-06T03:54:23.878Z","avatar_url":"https://github.com/csr632.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 游戏规则：\n* 你要运用智慧帮助3个牧师（方块）和3个魔鬼（圆球）渡河。\n* 船最多可以载2名游戏角色。\n* 船上有游戏角色时，你才可以点击这个船，让船移动到对岸。\n* 当有一侧岸的魔鬼数多余牧师数时（包括船上的魔鬼和牧师），魔鬼就会失去控制，吃掉牧师（如果这一侧没有牧师则不会失败），游戏失败。\n* 当所有游戏角色都上到对岸时，游戏胜利。\n****\n# 项目资源\nhttps://github.com/csr632/Priests-and-devils\n# 游戏截图：\n![开始游戏](http://upload-images.jianshu.io/upload_images/4888929-b91221deeb85c0ad.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n![游戏失败](http://upload-images.jianshu.io/upload_images/4888929-f2c28caa984232e5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n![游戏胜利](http://upload-images.jianshu.io/upload_images/4888929-a35353d419752977.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n****\n# 在Unity中体验\n从Github中下载[我的项目](https://github.com/csr632/Priests-and-devils)。\n将我的Asserts文件夹覆盖你的Unity项目中的Asserts文件夹。在你的Assets窗口中双击“ass”，然后就可以点击运行按钮了！\n\n****\n# 游戏架构\n使用了MVC架构。\n* 场景中的所有GameObject就是Model，它们受到Controller的控制，比如说牧师和魔鬼受到MyCharacterController类的控制，船受到BoatController类的控制，河岸受到CoastController类的控制。\n* View就是UserGUI和ClickGUI，它们展示游戏结果，并提供用户交互的渠道（点击物体和按钮）。\n* Controller：除了刚才说的MyCharacterController、BoatController、CoastController以外，还有更高一层的Controller：**FirstController（场景控制器）**，FirstController控制着这个场景中的所有对象，包括其加载、通信、用户输入。\n**最高层的Controller是Director类**，一个游戏中只能有一个实例，它控制着场景的创建、切换、销毁、游戏暂停、游戏退出等等最高层次的功能。\n\n****\n## Director\nDirector的定义：\n\n\tpublic class Director : System.Object {\n\t\tprivate static Director _instance;\n\t\tpublic SceneController currentSceneController { get; set; }\n\n\t\tpublic static Director getInstance() {\n\t\t\tif (_instance == null) {\n\t\t\t\t_instance = new Director ();\n\t\t\t}\n\t\t\treturn _instance;\n\t\t}\n\t}\nDirector是最高层的控制器，运行游戏时始终只有一个实例，它掌控着场景的加载、切换等，也可以控制游戏暂停、结束等等。\n\u003e 虽然Director控制着场景，但是它并不控制场景中的具体对象，控制场景对象的任务交给了SceneController（场景控制器），我们等一下会谈到。\n\nDirector类使用了单例模式。第一次调用Director.getInstance()时，会创建一个新的Director对象，保存在_instance，此后每次调用getInstance，都回返回_instance。也就是说Director最多只有一个实例。这样，我们在任何Script中的任何地方通过`Director.getInstance()`都能得到同一个Director对象，也就可以获得同一个currentSceneController，这样我们就可以轻易实现类与类之间的通信，比如说我在其他控制器中就可以使用`Director.getInstance().somethingHappen()`来告诉导演某一件事情发生了，导演就可以在`somethingHappen()`方法中做出对应的反应。\n\n****\n## SceneController接口\nSceneController接口定义：\n\n\tpublic interface SceneController {\n\t\tvoid loadResources ();\n\t}\n\ninterface（接口）不能直接用来创建对象！必须先有一个类实现（继承）它，在我的这个游戏中就是FirstController类。\nSceneController 是用来干什么的呢？它是导演控制场景控制器的渠道。在上面的Director 类中，currentSceneController （FirstController类）就是SceneController的实现，所以Director可以调用SceneController接口中的方法，来实现对场景的生杀予夺。\n\n\u003e 在这个游戏中SceneController的定义非常简单，因为这个游戏做得并不完整。我们刚才说过导演可以加载、切换、销毁场景、暂停游戏，所以SceneController 还可以规定`void switchScene()`、`void destroyScene()`、`void pause()`这些方法，供给导演来调用。\n\n****\n## Moveable\nMoveable是一个可以挂载在GameObject上的类：\n\n\tpublic class Moveable: MonoBehaviour {\n\t\t\n\t\treadonly float move_speed = 20;\n\n\t\t// change frequently\n\t\tint moving_status;\t// 0-\u003enot moving, 1-\u003emoving to middle, 2-\u003emoving to dest\n\t\tVector3 dest;\n\t\tVector3 middle;\n\n\t\tvoid Update() {\n\t\t\tif (moving_status == 1) {\n\t\t\t\ttransform.position = Vector3.MoveTowards (transform.position, middle, move_speed * Time.deltaTime);\n\t\t\t\tif (transform.position == middle) {\n\t\t\t\t\tmoving_status = 2;\n\t\t\t\t}\n\t\t\t} else if (moving_status == 2) {\n\t\t\t\ttransform.position = Vector3.MoveTowards (transform.position, dest, move_speed * Time.deltaTime);\n\t\t\t\tif (transform.position == dest) {\n\t\t\t\t\tmoving_status = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tpublic void setDestination(Vector3 _dest) {\n\t\t\tdest = _dest;\n\t\t\tmiddle = _dest;\n\t\t\tif (_dest.y == transform.position.y) {\t// boat moving\n\t\t\t\tmoving_status = 2;\n\t\t\t}\n\t\t\telse if (_dest.y \u003c transform.position.y) {\t// character from coast to boat\n\t\t\t\tmiddle.y = transform.position.y;\n\t\t\t} else {\t\t\t\t\t\t\t\t// character from boat to coast\n\t\t\t\tmiddle.x = transform.position.x;\n\t\t\t}\n\t\t\tmoving_status = 1;\n\t\t}\n\n\t\tpublic void reset() {\n\t\t\tmoving_status = 0;\n\t\t}\n\t}\nGameObject挂载上Moveable以后，Controller就可以通过`setDestination()`方法轻松地让GameObject移动起来。\n\u003e 在这里我没有让物体直接移动到目的地dest，因为那样可能会直接穿过河岸物体。我用middle来保存一个中间位置，让物体先移动到middle，再移动到dest，这就实现了一个折线的移动，不会穿越河岸。moving_status记录着目前该物体处于哪种移动状态。\n\n****\n## MyCharacterController\nMyCharacterController封装了一个GameObject，表示游戏角色（牧师或恶魔）。\n\n\tpublic class MyCharacterController {\n\t\treadonly GameObject character;\n\t\treadonly Moveable moveableScript;\n\t\treadonly ClickGUI clickGUI;\n\t\treadonly int characterType;\t// 0-\u003epriest, 1-\u003edevil\n\n\t\t// change frequently\n\t\tbool _isOnBoat;\n\t\tCoastController coastController;\n\n\n\t\tpublic MyCharacterController(string which_character) {\n\t\t\t\n\t\t\tif (which_character == \"priest\") {\n\t\t\t\tcharacter = Object.Instantiate (Resources.Load (\"Perfabs/Priest\", typeof(GameObject)), Vector3.zero, Quaternion.identity, null) as GameObject;\n\t\t\t\tcharacterType = 0;\n\t\t\t} else {\n\t\t\t\tcharacter = Object.Instantiate (Resources.Load (\"Perfabs/Devil\", typeof(GameObject)), Vector3.zero, Quaternion.identity, null) as GameObject;\n\t\t\t\tcharacterType = 1;\n\t\t\t}\n\t\t\tmoveableScript = character.AddComponent (typeof(Moveable)) as Moveable;\n\n\t\t\tclickGUI = character.AddComponent (typeof(ClickGUI)) as ClickGUI;\n\t\t\tclickGUI.setController (this);\n\t\t}\n\n\t\tpublic void setName(string name) {\n\t\t\tcharacter.name = name;\n\t\t}\n\n\t\tpublic void setPosition(Vector3 pos) {\n\t\t\tcharacter.transform.position = pos;\n\t\t}\n\n\t\tpublic void moveToPosition(Vector3 destination) {\n\t\t\tmoveableScript.setDestination(destination);\n\t\t}\n\n\t\tpublic int getType() {\t// 0-\u003epriest, 1-\u003edevil\n\t\t\treturn characterType;\n\t\t}\n\n\t\tpublic string getName() {\n\t\t\treturn character.name;\n\t\t}\n\n\t\tpublic void getOnBoat(BoatController boatCtrl) {\n\t\t\tcoastController = null;\n\t\t\tcharacter.transform.parent = boatCtrl.getGameobj().transform;\n\t\t\t_isOnBoat = true;\n\t\t}\n\n\t\tpublic void getOnCoast(CoastController coastCtrl) {\n\t\t\tcoastController = coastCtrl;\n\t\t\tcharacter.transform.parent = null;\n\t\t\t_isOnBoat = false;\n\t\t}\n\n\t\tpublic bool isOnBoat() {\n\t\t\treturn _isOnBoat;\n\t\t}\n\n\t\tpublic CoastController getCoastController() {\n\t\t\treturn coastController;\n\t\t}\n\n\t\tpublic void reset() {\n\t\t\tmoveableScript.reset ();\n\t\t\tcoastController = (Director.getInstance ().currentSceneController as FirstController).fromCoast;\n\t\t\tgetOnCoast (coastController);\n\t\t\tsetPosition (coastController.getEmptyPosition ());\n\t\t\tcoastController.getOnCoast (this);\n\t\t}\n\t}\n在构造函数中实例化了一个perfab，创建GameObject，因此我们每`new MyCharacterController()`一次，场景中就会多一个游戏角色。\n构造函数还将clickGUI挂载到了这个角色上，以监测“鼠标点击角色”的事件。\n\nMyCharacterController还定义了一些方法提供给场景控制器来调用，方法名已经能够表明这个方法是做什么的了。\n\n****\n## BoatController和CoastController\nBoatController和CoastController也类似MyCharacterController，封装了船GameObject和河岸GameObject。实现这两个类的难度主要在于它们是一种“容器”，游戏角色要进入它们的空位中。因此它们要提供`getEmptyPosition()`方法，给出自己的空位，让游戏角色能够移动到合适的位置。\n\n\t/*-----------------------------------CoastController------------------------------------------*/\n\tpublic class CoastController {\n\t\treadonly GameObject coast;\n\t\treadonly Vector3 from_pos = new Vector3(9,1,0);\n\t\treadonly Vector3 to_pos = new Vector3(-9,1,0);\n\t\treadonly Vector3[] positions;\n\t\treadonly int to_or_from;\t// to-\u003e-1, from-\u003e1\n\n\t\t// change frequently\n\t\tMyCharacterController[] passengerPlaner;\n\n\t\tpublic CoastController(string _to_or_from) {\n\t\t\tpositions = new Vector3[] {new Vector3(6.5F,2.25F,0), new Vector3(7.5F,2.25F,0), new Vector3(8.5F,2.25F,0), \n\t\t\t\tnew Vector3(9.5F,2.25F,0), new Vector3(10.5F,2.25F,0), new Vector3(11.5F,2.25F,0)};\n\n\t\t\tpassengerPlaner = new MyCharacterController[6];\n\n\t\t\tif (_to_or_from == \"from\") {\n\t\t\t\tcoast = Object.Instantiate (Resources.Load (\"Perfabs/Stone\", typeof(GameObject)), from_pos, Quaternion.identity, null) as GameObject;\n\t\t\t\tcoast.name = \"from\";\n\t\t\t\tto_or_from = 1;\n\t\t\t} else {\n\t\t\t\tcoast = Object.Instantiate (Resources.Load (\"Perfabs/Stone\", typeof(GameObject)), to_pos, Quaternion.identity, null) as GameObject;\n\t\t\t\tcoast.name = \"to\";\n\t\t\t\tto_or_from = -1;\n\t\t\t}\n\t\t}\n\n\t\tpublic int getEmptyIndex() {\n\t\t\tfor (int i = 0; i \u003c passengerPlaner.Length; i++) {\n\t\t\t\tif (passengerPlaner [i] == null) {\n\t\t\t\t\treturn i;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn -1;\n\t\t}\n\n\t\tpublic Vector3 getEmptyPosition() {\n\t\t\tVector3 pos = positions [getEmptyIndex ()];\n\t\t\tpos.x *= to_or_from;\n\t\t\treturn pos;\n\t\t}\n\n\t\tpublic void getOnCoast(MyCharacterController characterCtrl) {\n\t\t\tint index = getEmptyIndex ();\n\t\t\tpassengerPlaner [index] = characterCtrl;\n\t\t}\n\n\t\tpublic MyCharacterController getOffCoast(string passenger_name) {\t// 0-\u003epriest, 1-\u003edevil\n\t\t\tfor (int i = 0; i \u003c passengerPlaner.Length; i++) {\n\t\t\t\tif (passengerPlaner [i] != null \u0026\u0026 passengerPlaner [i].getName () == passenger_name) {\n\t\t\t\t\tMyCharacterController charactorCtrl = passengerPlaner [i];\n\t\t\t\t\tpassengerPlaner [i] = null;\n\t\t\t\t\treturn charactorCtrl;\n\t\t\t\t}\n\t\t\t}\n\t\t\tDebug.Log (\"cant find passenger on coast: \" + passenger_name);\n\t\t\treturn null;\n\t\t}\n\n\t\tpublic int get_to_or_from() {\n\t\t\treturn to_or_from;\n\t\t}\n\n\t\tpublic int[] getCharacterNum() {\n\t\t\tint[] count = {0, 0};\n\t\t\tfor (int i = 0; i \u003c passengerPlaner.Length; i++) {\n\t\t\t\tif (passengerPlaner [i] == null)\n\t\t\t\t\tcontinue;\n\t\t\t\tif (passengerPlaner [i].getType () == 0) {\t// 0-\u003epriest, 1-\u003edevil\n\t\t\t\t\tcount[0]++;\n\t\t\t\t} else {\n\t\t\t\t\tcount[1]++;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn count;\n\t\t}\n\n\t\tpublic void reset() {\n\t\t\tpassengerPlaner = new MyCharacterController[6];\n\t\t}\n\t}\n\n\t/*-----------------------------------BoatController------------------------------------------*/\n\tpublic class BoatController {\n\t\treadonly GameObject boat;\n\t\treadonly Moveable moveableScript;\n\t\treadonly ClickGUI clickGUI;\n\t\treadonly Vector3 fromPosition = new Vector3 (5, 1, 0);\n\t\treadonly Vector3 toPosition = new Vector3 (-5, 1, 0);\n\t\treadonly Vector3[] from_positions;\n\t\treadonly Vector3[] to_positions;\n\n\t\t// change frequently\n\t\tint to_or_from; // to-\u003e-1; from-\u003e1\n\t\tMyCharacterController[] passenger = new MyCharacterController[2];\n\n\t\tpublic BoatController() {\n\t\t\tto_or_from = 1;\n\n\t\t\tfrom_positions = new Vector3[] { new Vector3 (4.5F, 1.5F, 0), new Vector3 (5.5F, 1.5F, 0) };\n\t\t\tto_positions = new Vector3[] { new Vector3 (-5.5F, 1.5F, 0), new Vector3 (-4.5F, 1.5F, 0) };\n\n\t\t\tboat = Object.Instantiate (Resources.Load (\"Perfabs/Boat\", typeof(GameObject)), fromPosition, Quaternion.identity, null) as GameObject;\n\t\t\tboat.name = \"boat\";\n\n\t\t\tmoveableScript = boat.AddComponent (typeof(Moveable)) as Moveable;\n\t\t\tclickGUI = boat.AddComponent (typeof(ClickGUI)) as ClickGUI;\n\t\t}\n\n\n\t\tpublic void Move() {\n\t\t\tif (to_or_from == -1) {\n\t\t\t\tmoveableScript.setDestination(fromPosition);\n\t\t\t\tto_or_from = 1;\n\t\t\t} else {\n\t\t\t\tmoveableScript.setDestination(toPosition);\n\t\t\t\tto_or_from = -1;\n\t\t\t}\n\t\t}\n\n\t\tpublic int getEmptyIndex() {\n\t\t\tfor (int i = 0; i \u003c passenger.Length; i++) {\n\t\t\t\tif (passenger [i] == null) {\n\t\t\t\t\treturn i;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn -1;\n\t\t}\n\n\t\tpublic bool isEmpty() {\n\t\t\tfor (int i = 0; i \u003c passenger.Length; i++) {\n\t\t\t\tif (passenger [i] != null) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\n\t\tpublic Vector3 getEmptyPosition() {\n\t\t\tVector3 pos;\n\t\t\tint emptyIndex = getEmptyIndex ();\n\t\t\tif (to_or_from == -1) {\n\t\t\t\tpos = to_positions[emptyIndex];\n\t\t\t} else {\n\t\t\t\tpos = from_positions[emptyIndex];\n\t\t\t}\n\t\t\treturn pos;\n\t\t}\n\n\t\tpublic void GetOnBoat(MyCharacterController characterCtrl) {\n\t\t\tint index = getEmptyIndex ();\n\t\t\tpassenger [index] = characterCtrl;\n\t\t}\n\n\t\tpublic MyCharacterController GetOffBoat(string passenger_name) {\n\t\t\tfor (int i = 0; i \u003c passenger.Length; i++) {\n\t\t\t\tif (passenger [i] != null \u0026\u0026 passenger [i].getName () == passenger_name) {\n\t\t\t\t\tMyCharacterController charactorCtrl = passenger [i];\n\t\t\t\t\tpassenger [i] = null;\n\t\t\t\t\treturn charactorCtrl;\n\t\t\t\t}\n\t\t\t}\n\t\t\tDebug.Log (\"Cant find passenger in boat: \" + passenger_name);\n\t\t\treturn null;\n\t\t}\n\n\t\tpublic GameObject getGameobj() {\n\t\t\treturn boat;\n\t\t}\n\n\t\tpublic int get_to_or_from() { // to-\u003e-1; from-\u003e1\n\t\t\treturn to_or_from;\n\t\t}\n\n\t\tpublic int[] getCharacterNum() {\n\t\t\tint[] count = {0, 0};\n\t\t\tfor (int i = 0; i \u003c passenger.Length; i++) {\n\t\t\t\tif (passenger [i] == null)\n\t\t\t\t\tcontinue;\n\t\t\t\tif (passenger [i].getType () == 0) {\t// 0-\u003epriest, 1-\u003edevil\n\t\t\t\t\tcount[0]++;\n\t\t\t\t} else {\n\t\t\t\t\tcount[1]++;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn count;\n\t\t}\n\n\t\tpublic void reset() {\n\t\t\tmoveableScript.reset ();\n\t\t\tif (to_or_from == -1) {\n\t\t\t\tMove ();\n\t\t\t}\n\t\t\tpassenger = new MyCharacterController[2];\n\t\t}\n\t}\n\n\n\n\n另外一个需要注意的是MyCharacterController、BoatController、CoastController有一些方法名是重复的，比如说getOnBoat在MyCharacterController和BoatController中都有（BoatController中的GetOnBoat是我当时手抖了，第一个字母应该小写）。看起来似乎功能有点重复，为什么不只用一个函数操控游戏角色的上船呢？原因是**不要在一个类中操作另一个类，那会加强两个类之间的耦合性**。MyCharacterController中的`getOnBoat()`只应该操作MyCharacterController中的成员，BoatController中的`GetOnBoat()`只应该操作BoatController中的成员。\n我们在FirstController中想让游戏角色上船的时候，两个类的getOnBoat都要调用：\n```\nwhichCoast.getOffCoast(characterCtrl.getName());\ncharacterCtrl.moveToPosition (boat.getEmptyPosition());\ncharacterCtrl.getOnBoat (boat);\nboat.GetOnBoat (characterCtrl);\n```\n****\n## UserAction\n这个接口实际上使用了门面模式。\nFirstController必须要实现这个接口才能对用户的输入做出反应。\n```\npublic interface UserAction {\n\tvoid moveBoat();\n\tvoid characterIsClicked(MyCharacterController characterCtrl);\n\tvoid restart();\n}\n```\n在这个游戏中，对用户输入做出反应，有这三个方法就够了。\nUserAction是如何得到用户的输入的呢？原来，在ClickGUI和UserGUI这两个类中，都保存了一个UserAction的引用。当ClickGUI监测到用户点击GameObject的时候，就会调用这个引用的characterIsClicked方法，这样FirstController就知道哪一个游戏角色被点击了。UserGUI同理，只不过它监测的是“用户点击Restart按钮”的事件。\n\n门面模式的好处：通过一套接口（UserAction）来定义Controller与GUI交互的渠道，这样实现Controller类的程序员只需要实现UserAction接口，他的代码就可以被任何**支持这个接口的GUI类**所使用；实现GUI类的程序员也不需要知道Controller的实现方式，它只需要调用接口中的方法，后面的事情就交给Controller吧！\n****\n## ClickGUI\nClickGUI类是用来监测用户点击，并调用SceneController进行响应的。\n\n```\npublic class ClickGUI : MonoBehaviour {\n\tUserAction action;\n\tMyCharacterController characterController;\n\n\tpublic void setController(MyCharacterController characterCtrl) {\n\t\tcharacterController = characterCtrl;\n\t}\n\n\tvoid Start() {\n\t\taction = Director.getInstance ().currentSceneController as UserAction;\n\t}\n\n\tvoid OnMouseDown() {\n\t\tif (gameObject.name == \"boat\") {\n\t\t\taction.moveBoat ();\n\t\t} else {\n\t\t\taction.characterIsClicked (characterController);\n\t\t}\n\t}\n}\n```\n\n我们可以看到`UserAction action`实际上是FirstController的对象，它实现了UserAction接口。ClickGUI与FirstController打交道，就是通过UserAction接口的API。ClickGUI不知道这些API是怎么被实现的，但它知道FirstController类一定有这些方法。\n****\n# 可以做的扩展：\n* 游戏失败以后不能再响应用户点击的事件，用户只能点击Restart。\n* 增加计时的功能（这应该由SceneController来控制）。\n* 增加暂停/恢复游戏的功能（这应该由Director来控制）。\n* 在开始游戏之前做一个欢迎界面，与用户进行交互（这就是另一个场景了）。\n* 让用户可以在游戏中切换到欢迎界面，再切换回游戏界面的时候，游戏状态要和之前一样（场景的切换）。用户可以在游戏中放弃游戏，回到欢迎页面（场景的销毁）。\n* 让用户能够在欢迎界面指定有几个牧师几个恶魔，然后开始游戏。（运行时决定场景的创建）\n* 增加一种更难的模式，开始3秒以后牧师和恶魔外观相同，玩家需要凭借记忆来操作。\n* 美化游戏对象！\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcsr632%2Fpriests-and-devils","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcsr632%2Fpriests-and-devils","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcsr632%2Fpriests-and-devils/lists"}