A brief answer and program verification
(1) The essence of game object movement
Use matrix transformation (translation, rotation, scaling) to change the spatial attributes of game objects,
(2) Three methods to realize parabolic motion of objects
Method1: directly modify position in transform
public class Motion1 : MonoBehaviour { // Use this for initialization void Start () { this.transform.position = new Vector3(0, -5, 0); } // Update is called once per frame void Update () { this.transform.position += Vector3.right * Time.deltaTime; this.transform.position += Vector3.up * (5 - this.transform.position.x * this.transform.position.x)*Time.deltaTime; } }
Method2: use Vector3.MoveTowards
void Update () { Debug.Log (nowVy + Vy); if (nowVy+Vy > 0.00001) { Vector3 target = this.transform.position + Vector3.up * Time.deltaTime * nowVy + Vector3.left * Time.deltaTime * Vx; this.transform.position = Vector3.MoveTowards (this.transform.position, target, Time.deltaTime); nowVy -= 10 * Time.deltaTime; } else { } }
Method3: use transform.Translate
public float Vx; public float Vy; private float nowVy; private Vector3 speed; private Vector3 Gravity; // Use this for initialization void Start () { Gravity = Vector3.zero; speed = new Vector3 (Vx, Vy, 0); } // Update is called once per frame void Update () { if (2*Vy+Gravity.y > 0.00001) { Gravity.y -= 10 * Time.fixedDeltaTime; this.transform.Translate (speed*Time.fixedDeltaTime); this.transform.Translate (Gravity*Time.fixedDeltaTime); } else { } }
(3) Write a program to realize a complete solar system. The rotation speed of other planets around the sun must be different and not on a normal plane.
Direct code:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class plantMove : MonoBehaviour { public Transform Sun; public Transform Mercury; public Transform Venus; public Transform Earth; public Transform Moon; public Transform Mars; public Transform Jupiter; public Transform Saturn; public Transform Uranus; public Transform Neptune; // Start is called before the first frame update void Start () { Sun.position = Vector3.zero; Mercury.position = new Vector3 (4, 0, 0); Venus.position = new Vector3 (6, 0, 0); Earth.position = new Vector3 (8, 0, 0); Moon.position = new Vector3 (10, 0, 0); Mars.position = new Vector3 (12, 0, 0); Jupiter.position = new Vector3 (16, 0, 0); Saturn.position = new Vector3 (20, 0, 0); Uranus.position = new Vector3 (24, 0, 0); Neptune.position = new Vector3 (28, 0, 0); } // Update is called once per frame void Update () { Vector3 a1 = new Vector3 (0, 9, 2); Vector3 a2 = new Vector3 (0, 257, 135); Vector3 a3 = new Vector3 (0, 45, 339); Vector3 a4 = new Vector3 (0, 4, 9); Vector3 a5 = new Vector3 (0, 8, 19); Vector3 a6 = new Vector3 (0, 11, 9); Vector3 a7 = new Vector3 (0, 6, 137); Vector3 a8 = new Vector3 (0, 3, 13); Vector3 a9 = new Vector3 (0, 13, 122); Mercury.RotateAround (Sun.position, a1, 20*Time.deltaTime); Mercury.Rotate (Vector3.up*50*Time.deltaTime); Venus.RotateAround (Sun.position, a2, 10*Time.deltaTime); Venus.Rotate (Vector3.up*30*Time.deltaTime); Earth.RotateAround (Sun.position, a3, 10*Time.deltaTime); Earth.Rotate (Vector3.up*30*Time.deltaTime); Moon.transform.RotateAround (Earth.position, Vector3.up, 359 * Time.deltaTime); Mars.RotateAround (Sun.position, a4, 8*Time.deltaTime); Mars.Rotate (Vector3.up*30*Time.deltaTime); Jupiter.RotateAround (Sun.position, a5, 7*Time.deltaTime); Jupiter.Rotate (Vector3.up*30*Time.deltaTime); Saturn.RotateAround (Sun.position, a6, 6*Time.deltaTime); Saturn.Rotate (Vector3.up*30*Time.deltaTime); Uranus.RotateAround (Sun.position, a7, 5*Time.deltaTime); Uranus.Rotate (Vector3.up*30*Time.deltaTime); Neptune.RotateAround (Sun.position, a8, 4*Time.deltaTime); Neptune.Rotate (Vector3.up*30*Time.deltaTime); } }
The specific project is put together with the priest and devil project. The following is the MVC framework of the solar system project:
II. Programming practice - priest and devil
The priest and the devil is a puzzle game. You will help the priest and the devil cross the river within a limited time. There are three priests and three demons on one side of the river. They all wanted to go to the other side of the river, but there was only one boat, which could only carry two people at a time. Someone must drive the boat from one side to the other. In the flash game, you can click them to move them, and then click the go button to move the boat in another direction. If the number of priests on both sides of the river is less than the devil, they will be killed and the game is over. You can try it in many ways. Keep all priests alive!
- Objects mentioned in the game:
- minister
- devil
- river
- ship
- bank
-
Use the table to list the player's action table (rule table). Note that the fewer actions, the better
|Player action | execution conditions|
|–|--|
|Priest / Devil aboard the ship | there is a space on the ship and the ship is on the shore|
Set sail | there are people on board
The priest / Devil disembarked and reached the shore -
On MVC architecture
MVC Architecture (Model View Controller) organizes code by separating logic, data and interface display, and gathers business into one component. It does not need to rewrite business logic while improving and customizing interface and user interaction. MVC is uniquely developed to map the traditional input, processing and output functions in a logical graphical user interface structure. It divides the software system into three basic parts:
- M (Model): in unity, the Model is the GameObject in the game scene, its relationship with them, and the background data Model (such as user information), which are controlled by the corresponding Controller
- V(View): View plays the function of interacting with users in the game. They are responsible for reporting the game results to users, handling GUI events, and interacting with the Controller through IUserAction interface.
- C (controller): the controller controls all objects in the game scene. It is responsible for receiving instructions from the user of View and updating Models and View status according to the instructions entered by the user. It can be considered as a bridge between Model and View.
The view and controller together constitute the user interface. And each view has an associated controller component. The controller accepts input, usually as a time to encode mouse movement, mouse button activity, or keyboard input. Time is translated into a model or attempted server request. The user interacts with the system only through the controller.
The realization of a dynamic program design is the modification, expansion and simplification of the program, and makes the reuse of a part of the program possible.
-
The UML framework of the game is as follows:
-
Next, start programming based on the game framework
Director
The director's responsibilities include:
- Get the scene of the current game
- Control scene operation, switching, stack entry and exit, pause, resume and exit
- Manage the global state of the game
- Set the configuration of the game
- Set game global view
In this game, the director is mainly responsible for obtaining the current game scene. The specific code is as follows:
public class GameDirector : System.Object { private static GameDirector _instance; public ISceneController currentSceneController { get; set; } public bool running { set; get; } public static GameDirector getInstance() { if (_instance == null) { _instance = new GameDirector(); return _instance; } return _instance; } public int getFPS() { return Application.targetFrameRate; } public void setFPS(int fps) { Application.targetFrameRate = fps; } }
When the Director is running, only one instance is running. In the above code, we can see that it applies the singleton pattern to ensure that only one instance is generated, which facilitates the event communication between classes and the implementation of MVC.
As the highest level Controller of the game, Director does not specifically control any object in the game. Through the maintenance of currentSceneController, Director gives the task of controlling game objects to different scenecontrollers.
ISceneController
ISCeneController is the scene manager in the game, commonly known as the game record. Its main responsibilities are as follows:
- Manage all game objects in the game scene
- Coordinate communication between game objects and game object controllers in response to external input events
- Manage all the rules of the game scene
- Manage something else
The implementation of the unified interface is the key to the management of each game scene by GameDirector. It is the channel for the director to control the scene. GameDirector can simply realize various management of game scenes through the maintenance of currentSceneController
//interface of Scene public interface ISceneController { void genGameObjects(); }
IUserAction
IUserAction embodies the facade mode of game programming. The communication between the outside of the game and a subsystem must be carried out through a unified facade object, and the IUserAction we now implement is such an object. The GUI class communicates with the Controller through IUserAction. We define the interaction between the Controller and the GUI by implementing the IUserAction interface, In this way, when implementing the Controller class, we only need to implement the interface corresponding to IUserAction, and he can call any View that uses the IUserAction interface, that is, the GUI class. The GUI class also only needs to call the methods in the interface to communicate with the Controller, which facilitates the implementation of our MVC architecture.
public interface IUserAction { void restart(); void ToggleBoat(); void ClickCharacter(ICharacterController chracter); }
UserGUI
public class UserGUI : MonoBehaviour { public int status = 0; private IUserAction action; GUIStyle headerStyle; GUIStyle buttonStyle; // Use this for initialization void Start () { action = GameDirector.getInstance().currentSceneController as IUserAction; headerStyle = new GUIStyle(); headerStyle.fontSize = 40; headerStyle.alignment = TextAnchor.MiddleCenter; buttonStyle = new GUIStyle("button"); buttonStyle.fontSize = 30; } // Update is called once per frame void OnGUI () { GUI.Label(new Rect(Screen.width / 2 - 100, 10, 200, 50), "Priests & Demons", headerStyle); if (status == 1) { GUI.Label(new Rect(Screen.width / 2 - 45, Screen.height / 2 - 90, 100, 50), "Gameover!", headerStyle); if (GUI.Button(new Rect(Screen.width / 2 - 65, Screen.height / 2, 140, 70), "Restart", buttonStyle)) { status = 0; action.restart(); } } else if (status == 2) { GUI.Label(new Rect(Screen.width / 2 - 50, Screen.height / 2 - 90, 100, 50), "Win!", headerStyle); if (GUI.Button(new Rect(Screen.width / 2 - 70, Screen.height / 2, 140, 70), "Restart", buttonStyle)) { status = 0; action.restart(); } } } }
From the above we can see that UserGUI simply displays the game information according to the state of the game, and then interacts with Controller through IUserAction. This is what we mentioned above. We only need to call the IUserAction in GUI to achieve the control of the game scene quickly. So is the ClickGUI below.
ClickGUI
public class ClickGUI : MonoBehaviour { IUserAction action; ICharacterController character; public void setController(ICharacterController characterController) { character = characterController; } void Start() { action = GameDirector.getInstance().currentSceneController as IUserAction; } void OnMouseDown() { if (gameObject.name == "boat") { action.ToggleBoat(); } else { action.ClickCharacter(character); } } }
In ClickGUI, we also use IUserAction. ClickGUI monitors the occurrence of events, and then passes user instructions to the Controller for execution through functions similar to mobile boat and game character boarding and disembarking provided by IUserAction. This enables the interaction between View and Controller.
FirstController
public class FirstController : MonoBehaviour, ISceneController, IUserAction { readonly Vector3 riverPosition = new Vector3(0, -0.25f, 0); UserGUI userGUI; public LandController rightLand; public LandController leftLand; public BoatController boat; public ICharacterController[] characters; void Awake() { GameDirector director = GameDirector.getInstance(); director.currentSceneController = this; userGUI = gameObject.AddComponent<UserGUI>() as UserGUI; genGameObjects(); } public void genGameObjects() { characters = new ICharacterController[6]; GameObject river = Instantiate(Resources.Load("Prefabs/River", typeof(GameObject)), riverPosition, Quaternion.identity, null) as GameObject; river.name = "river"; boat = new BoatController(); leftLand = new LandController(-1); rightLand = new LandController(1); for (int i = 0; i < 3; i++) { ICharacterController priest = new ICharacterController(0, "priest" + i); priest.setPosition(rightLand.getEmptyPosition()); priest.getOnLand(rightLand); rightLand.getOnLand(priest); characters[i] = priest; } for (int i = 0; i < 3; i++) { ICharacterController demon = new ICharacterController(1, "demon" + i); demon.setPosition(rightLand.getEmptyPosition()); demon.getOnLand(rightLand); rightLand.getOnLand(demon); characters[i+3] = demon; } } public void ClickCharacter(ICharacterController character) { if (userGUI.status != 0 || !boat.available()) { return; } if (character.isOnBoat()) { LandController land; if (boat.getBoatPos() == 0) { land = leftLand; } else { land = rightLand; } boat.getOffBoat(character.getName()); character.MoveTo(land.getEmptyPosition()); character.getOnLand(land); land.getOnLand(character); } else { LandController land = character.getLandController(); if (boat.getEmptyIndex() == -1) return; int landPos = land.getType(), boatPos = (boat.getBoatPos() == 0) ? -1 : 1; if (landPos != boatPos) return; land.getOffLand(character.getName()); character.MoveTo(boat.getEmptyPosition()); character.getOnBoat(boat, boat.getEmptyIndex()); boat.getOnBoat(character); } userGUI.status = checkResult(); } public void ToggleBoat() { if (userGUI.status != 0 || boat.isEmptty()) return; boat.Move(); userGUI.status = checkResult(); } int checkResult() { int leftPriests = 0; int rightPriests = 0; int leftDemons = 0; int rightDemons = 0; int[] leftStatus = leftLand.getStatus(); leftPriests += leftStatus[0]; leftDemons += leftStatus[1]; if (leftPriests + leftDemons == 6) return 2; int[] rightStatus = rightLand.getStatus(); rightPriests += rightStatus[0]; rightDemons += rightStatus[1]; int[] boatStatus = boat.getBoatStatus(); if (boat.getBoatPos() == 0) { leftPriests += boatStatus[0]; leftDemons += boatStatus[1]; } else { rightPriests += boatStatus[0]; rightDemons += boatStatus[1]; } if (leftPriests > 0 && leftPriests < leftDemons) return 1; if (rightPriests > 0 && rightPriests < rightDemons) return 1; return 0; } public void restart() { boat.reset(); leftLand.reset(); rightLand.reset(); for (int i = 0; i < characters.Length; i++) characters[i].reset(); } }
move of Gameobject
public class Move : MonoBehaviour { readonly float speed = 20; int status;//0: stationary, 1: moving in the front segment, 2: moving in the back segment Vector3 middle; Vector3 destination; void Update() { if (status == 1) { this.transform.position = Vector3.MoveTowards(this.transform.position, middle, Time.deltaTime * speed); if (transform.position == middle) { status = 2; } } else if (status == 2) { this.transform.position = Vector3.MoveTowards(this.transform.position, destination, Time.deltaTime * speed); if (this.transform.position == destination) { status = 0; } } } public int getStatus() { return status; } public void moveTo(Vector3 _destination) { destination = _destination; middle = _destination; if (_destination.y == this.transform.position.y) { status = 2; return; } else if (_destination.y < this.transform.position.y) { middle.y = transform.position.y; } else { middle.x = transform.position.x; } status = 1; } public void reset() { status = 0; } }
In order to prevent the object from directly passing through the wall of land, when character gets on the ship, first move the object to the corresponding x coordinate of destination, and then move the object to the corresponding position. When character gets off the ship, first move the object to the corresponding y coordinate of destination, and then move the object to the corresponding position, so as to ensure that the object will not move through the wall, And the implementation method is relatively simple.
ICharacterController
Because the actions of priests and demons are the same, but their shapes and types are different, I Abstract them into a Controller and distinguish them by type
public class ICharacterController { readonly GameObject character; readonly Move moveAction; readonly int type;//0: Priest, 1: Demon readonly ClickGUI clickGUI; bool onBoat; LandController landController; public ICharacterController(int chracterType, string name) { if (chracterType == 0) { this.character = Object.Instantiate(Resources.Load("Prefabs/Priest", typeof(GameObject)), Vector3.zero, Quaternion.identity, null) as GameObject; this.type = 0; } else { this.character = Object.Instantiate(Resources.Load("Prefabs/Demon", typeof(GameObject)), Vector3.zero, Quaternion.identity, null) as GameObject; this.type = 1; } character.name = name; moveAction = character.AddComponent(typeof(Move)) as Move; clickGUI = character.AddComponent(typeof(ClickGUI)) as ClickGUI; clickGUI.setController(this); } public string getName() { return character.name; } public void setPosition(Vector3 pos) { character.transform.position = pos; } public void MoveTo(Vector3 destination) { moveAction.moveTo(destination); } public int getType() { return type; } public void getOnBoat(BoatController boatController, int pos) { landController = null; if (pos == 0) { character.transform.rotation = Quaternion.AngleAxis(90, Vector3.up); } else { character.transform.rotation = Quaternion.AngleAxis(270, Vector3.up); } character.transform.parent = boatController.getBoat().transform; onBoat = true; } public void getOnLand(LandController land) { landController = land; if (land.getType() == -1) { character.transform.rotation = Quaternion.AngleAxis(90, Vector3.up); } else { character.transform.rotation = Quaternion.AngleAxis(270, Vector3.up); } character.transform.parent = null; onBoat = false; } public bool isOnBoat() { return onBoat; } public LandController getLandController() { return landController; } public void reset() { moveAction.reset(); landController = (GameDirector.getInstance().currentSceneController as FirstController).rightLand; getOnLand(landController); setPosition(landController.getEmptyPosition()); landController.getOnLand(this); } }
The implementation of the chartercontroller class is very simple, which is to import prefabrication according to the type, and then realize boarding, landing, reset and other get and set functions. See the implementation of the above code for details.
BoatController
public class BoatController { readonly GameObject boat; readonly Move moveAction; readonly Vector3 right = new Vector3(3.5f, 0, 0); readonly Vector3 left = new Vector3(-3.5f, 0, 0); readonly Vector3[] right_positions; readonly Vector3[] left_positions; int status;// 0: left, 1: right ICharacterController[] characterOnBoat = new ICharacterController[2]; public BoatController() { status = 1; right_positions = new Vector3[] { new Vector3(2.5F, 0.2F, 0), new Vector3(4.5F, 0.2F, 0) }; left_positions = new Vector3[] { new Vector3(-4.5F, 0.2F, 0), new Vector3(-2.5F, 0.2F, 0) }; boat = Object.Instantiate(Resources.Load("Prefabs/Boat", typeof(GameObject)), right, Quaternion.identity, null) as GameObject; boat.name = "boat"; moveAction = boat.AddComponent(typeof(Move)) as Move; boat.AddComponent(typeof(ClickGUI)); } public void Move() { if (status == 1) { moveAction.moveTo(left); } else { moveAction.moveTo(right); } status = 1 - status; } public int getEmptyIndex() { for (int i = 0; i < characterOnBoat.Length; i++) { if (characterOnBoat[i] == null) { return i; } } return -1; } public Vector3 getEmptyPosition() { int index = getEmptyIndex(); if (status == 0) { return left_positions[index]; } else { return right_positions[index]; } } public bool isEmptty() { for (int i = 0; i < characterOnBoat.Length; i++) { if (characterOnBoat[i] != null) { return false; } } return true; } public void getOnBoat(ICharacterController character) { characterOnBoat[getEmptyIndex()] = character; } public ICharacterController getOffBoat(string name) { for (int i = 0; i < characterOnBoat.Length; i++) { if (characterOnBoat[i] != null && characterOnBoat[i].getName() == name) { ICharacterController character = characterOnBoat[i]; characterOnBoat[i] = null; return character; } } return null; } public GameObject getBoat() { return boat; } public int getBoatPos() { return status; } public int[] getBoatStatus() { int[] boatStatus = { 0, 0 }; for (int i = 0; i < characterOnBoat.Length; i++) { if (characterOnBoat[i] == null) continue; boatStatus[characterOnBoat[i].getType()]++; } return boatStatus; }// 0: Priests, 1: Demon public bool available() { return (moveAction.getStatus() == 0); } public void reset() { moveAction.reset(); if (status == 0) { Move(); } characterOnBoat = new ICharacterController[2]; } }
The implementation of Boat is similar. You only need to implement the functions related to initialization, toggleboat and character boarding and disembarking.
LandController
public class LandController { readonly GameObject land; readonly Vector3 leftPos = new Vector3(-8.5f, 0f, 0f); readonly Vector3 rightPos = new Vector3(8.5f, 0f, 0f); readonly Vector3[] landPositions; readonly int type;// 1:right, -1: left ICharacterController[] characterOnLand; public LandController(int _type) { landPositions = new Vector3[] { new Vector3(6F,0.5F,0), new Vector3(7F,0.5F,0), new Vector3(8F,0.5F,0), new Vector3(9F,0.5F,0), new Vector3(10F,0.5F,0), new Vector3(11F,0.5F,0) }; characterOnLand = new ICharacterController[6]; type = _type; if (type == 1) { land = Object.Instantiate(Resources.Load("Prefabs/Land", typeof(GameObject)), rightPos, Quaternion.identity, null) as GameObject; land.name = "rightLand"; } else { land = Object.Instantiate(Resources.Load("Prefabs/Land", typeof(GameObject)), leftPos, Quaternion.identity, null) as GameObject; land.name = "leftLand"; } } public Vector3 getEmptyPosition() { Vector3 pos = landPositions[getEmptyIndex()]; pos.x *= type; return pos; } public int getEmptyIndex() { for (int i = 0; i < characterOnLand.Length; i++) { if (characterOnLand[i] == null) return i; } return -1; } public void getOnLand(ICharacterController chracter) { characterOnLand[getEmptyIndex()] = chracter; } public ICharacterController getOffLand(string name) { for (int i = 0; i < characterOnLand.Length; i++) { if (characterOnLand[i] != null && characterOnLand[i].getName() == name) { ICharacterController tmp = characterOnLand[i]; characterOnLand[i] = null; return tmp; } } return null; } public int getType() { return type; } public int[] getStatus() { int[] status = { 0, 0 }; for (int i = 0; i < characterOnLand.Length; i++) { if (characterOnLand[i] == null) continue; status[characterOnLand[i].getType()]++; } return status; }// 0: priests, 1: Demon public void reset() { characterOnLand = new ICharacterController[6]; } }
The implementation of landController is similar to that of the above Controller. It also implements initialization, landing and landing related functions
The implementation of this game puts the interface and Director in the same namespace of the same script, so there are only three scripts in total, which will be expanded when the project is extended later