Unity-Mini Game Priest and Demon Action Separated Version

Unity-Small Game Priest and Demon

game introduction

When the game starts, three priests and three demons are on one side of the river. The object of the game is to help them cross the river to the other side, and the number of priests cannot be less than the number of demons on either side, otherwise the demons will attack the priests. The rules of the game are as follows:

Characters and props:
  • Priest (P): There are three priests in the game, represented by P.
  • Devil (D): There are three demons in the game, represented by D.
  • Ship (B): There is a ship in the game, represented by B. The maximum capacity of the boat is two people.
Initial state:
  • Initially, three priests (PPP), three demons (DDD) and the boat (B) are on one side of the river.
Target status:
  • The goal of the game is to get all priests and demons safely to the other side of the river, and there can be no more demons than priests on any side (e.g. PD or PDD are legal on either side of the river) status, but DD is an illegal status).
Movement rules:
  • A boat can carry up to two characters (clerics, demons, or a mixture of the two) across the river at a time.
  • There cannot be more demons than priests on any side, otherwise the demons will attack the priests and the game is lost.
  • Both clerics and demons can operate the ship, but no more than two characters can be on board.
game over:

The game ends in two situations:

  • When all priests and demons have safely crossed to the other side of the river and meet the conditions (the number of demons is no more than the number of priests), the game is won.
  • If there are more demons than priests on either side, the demons will attack the priests and the game is lost.

"Priests and Demons" is a classic intellectual game that requires players to cleverly arrange the positions of characters and passengers on the boat according to the rules to ensure that all characters can cross the river safely. It is a game that tests logical thinking and planning skills. .

Design architecture (based on action separation)

Introduction to MVC framework

Using the MVC (Model-View-Controller) idea, the game structure is clearer.

1. Class introduction
  • Director class: Implemented using single instance mode, responsible for global game settings, scene management, etc.

  • SceneManager class: Implements game model management and game logic, conforms to the MVC framework, and separates the logic of the user view and the game scene model.

  • Human-computer interaction (IUserAction) interface: Describes the operations corresponding to the game rules and defines the user interaction behavior in the game.

2. MVC framework workflow
  1. Director object settings: The director class is responsible for the global settings of the game, including initializing game scenes, characters and other information.

  2. Scene switching: The scene manager is responsible for managing game scenes, including switching between different game scenes to ensure the smooth progress of the game process.

  3. User operation processing: User operations are processed by classes that implement the IUserAction interface, which defines operations corresponding to game rules, such as player Move, attack, etc.

3. Advantages
  • Clear structure: The MVC framework makes the game structure clearer and separates the data model, user view and control logic.

  • Modular development: Different modules (Model, View, Controller) can be developed and tested independently, which improves the maintainability and scalability of the code.

  • Logical separation: Through MVC, the logical separation of the user view and the game scene model is achieved, reducing the coupling.

  • Unified scene control: A unified scene control interface has been established to support different business logic in different scenarios, increasing flexibility.

Insert image description here

The specific design plan is as follows:
Insert image description here

Action Separation Design Ideas

As business or game rules continue to increase, action combinations also become more complex. The scene controller must not only handle user interaction events, but also load game objects, implement game rules, implement motion, etc., which makes it very bloated.

It can be seen that if we still use the above MVC framework for game development and maintenance, it will undoubtedly increase the cost of maintenance and development. A better solution is, based on the idea of ​​object-oriented programming, we are Relevant elements such as actions are extracted, and interfaces and classes are separated for separate processing. This not only makes the program more adaptable to changes in requirements, but also makes objects easier to reuse and the program easier to maintain.
Insert image description here
Combined with the specific game logic, we need to design and implement an action manager to manage actions uniformly, and we need to separate the judgment of the judgment rules into a referee class for implementation.

Action manager design ideas
  1. Facade mode (controller mode) outputs combined actions:

    • Through the facade mode, several combined actions are output for the original program to call.
    • The advantage of is that the combination of actions becomes an internal transaction of the action module. This facade is called CCActionManager.
  2. Combination mode implements action combination:

    • Use the combination pattern design method to ensure that there is an abstract thing that represents the commonality of this type of thing, such as SSAction, which represents an action, whether it is a basic action or a combined action.
    • Basic actions are designed by the user, such as CCMoveToAction.
    • A combo action is composed of basic or combo actions, such as CCSequenceAction.
  3. Interface callback (function callback) realizes decoupling:

    • The composite object implements an event abstract interface ISSCallback, which serves as a listener to listen for events of sub-actions.
    • The combined object uses a listener to pass messages to the manager, and the specific processing of the manager is determined by the person who implements the listener.
  4. Template methods reduce detail requirements:

    • Using the template method, users do not need to care about the details of the action management process.
    • SSActionManager, as the base class ofCCActionManager, provides basic template methods.

This design idea can ensure that the combination of actions and implementation details are encapsulated, making the entire system more flexible, easy to expand and maintain.
Insert image description here

Specific code implementation

SSActionCallBack
/**
 * 定义一个接口用于动作完成后的回调操作。
 */
public interface SSActionCallback {
    
    
    /**
     * 当动作完成时调用的方法。
     */
    void actionDone(SSAction source);
}

InterfaceSSActionCallBack defines a callback operation after the action is completed. The interface only contains one abstract method, actionDone, which receives a parameter source, which represents the object that initiated the action.

SSAction
// SSAction 类继承自 ScriptableObject,表示游戏对象的动作行为。
public class SSAction : ScriptableObject
{
    
    
    // 是否启用该动作,默认为 true。
    public bool enable = true;

    // 是否在动作完成后销毁游戏对象,默认为 false。
    public bool destroy = false;

    // 游戏对象的引用。
    public GameObject gameObject;

    // 游戏对象的变换组件引用。
    public Transform transform;

    // 动作完成后通知的回调接口。
    public SSActionCallback whoToNotify;

    // 虚拟方法,表示动作开始时的操作,需要在子类中进行实现。
    public virtual void Start()
    {
    
    
        // 抛出未实现异常,子类需要覆盖该方法。
        throw new System.NotImplementedException();
    }

    // 虚拟方法,表示动作更新时的操作,需要在子类中进行实现。
    public virtual void Update()
    {
    
    
        // 抛出未实现异常,子类需要覆盖该方法。
        throw new System.NotImplementedException();
    }
}

EntranceSSActionAnswer:

  1. SSActionThe class is a class inherited from ScriptableObject and is used to represent the action behavior of game objects.
  2. enable indicates whether the action is enabled, the default is true.
  3. destroy indicates whether to destroy the game object after the action is completed. The default is false.
  4. gameObjectIs a reference to the game object associated with this action.
  5. transformIs the transform component reference of the game object.
  6. whoToNotify is a callback interface for notification after the action is completed, the type is ActionCallback.
  7. Start()Is a virtual method that represents the operation at the beginning of the action. In the base class it throws an unimplemented exception and needs to be implemented in the subclass.
  8. Update()Is a virtual method that represents the operation when the action is updated. Likewise, it throws an unimplemented exception and needs to be implemented in a subclass.

This class provides the basic structure of the action and supports a callback mechanism. When the action is completed, other parts of the code can be notified through the callback interface specified by the whoToNotify attribute.

SSActionManage
// SSActionManage 类,用于管理游戏中的动作,实现了 SSActionCallback 接口。
public class SSActionManage : MonoBehaviour, SSActionCallback {
    
    
    // 存储动作的字典,键为动作的实例ID,值为 SSAction 对象。
    private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>();
    // 待添加的动作列表。
    private List<SSAction> waitingToAdd = new List<SSAction>();
    // 待删除的动作实例ID列表。
    private List<int> waitingToDelete = new List<int>();

    // 每帧调用一次,用于更新动作的状态。
    protected void Update() {
    
    
        // 将待添加的动作加入到字典中。
        foreach (SSAction ac in waitingToAdd) {
    
    
            actions[ac.GetInstanceID()] = ac;
        }
        waitingToAdd.Clear();

        // 遍历字典中的动作,更新动作状态,如果动作被标记为销毁,则加入待删除列表。
        foreach (KeyValuePair<int, SSAction> kv in actions) {
    
    
            SSAction ac = kv.Value;
            if (ac.destroy) {
    
    
                waitingToDelete.Add(ac.GetInstanceID());
            } else if (ac.enable) {
    
    
                ac.Update();
            }
        }

        // 遍历待删除列表,从字典中移除相应的动作,然后销毁动作对象。
        foreach (int key in waitingToDelete) {
    
    
            SSAction ac = actions[key];
            actions.Remove(key);
            DestroyObject(ac);
        }
        waitingToDelete.Clear();
    }

    // 添加动作到管理器中的方法,初始化动作的属性,并将其加入待添加列表,然后调用动作的 Start 方法。
    public void addAction(GameObject gameObject, SSAction action, SSActionCallback whoToNotify) {
    
    
        action.gameObject = gameObject;
        action.transform = gameObject.transform;
        action.whoToNotify = whoToNotify;
        waitingToAdd.Add(action);
        action.Start();
    }

    // 实现 SSActionCallback 接口的方法,当动作完成时被调用。
    public void actionDone(SSAction source) {
    
    
        // 在这里可以添加动作完成后的逻辑处理。
    }
}

Code analysis:

  1. SSActionManageThe class is a class used to manage actions in the game. It inherits the MonoBehaviour class and implements the SSActionCallback interface.

  2. actionsThe dictionary is used to store actions in the game. The key is the instance ID of the action and the value is the SSAction object.

  3. waitingToAddList is used to store actions to be added.

  4. waitingToDeleteThe list is used to store the instance ID of the action to be deleted.

  5. Update()Method is called every frame to update the action's state. It traverses the list of actions to be added and adds the action to the dictionary; then traverses the actions in the dictionary, updates the action status, and if the action is marked for destruction, adds it to the list to be deleted; finally it traverses the list to be deleted and removes it from the dictionary The corresponding action, and then destroy the action object.

  6. addAction()The method is used to add actions to the manager. It initializes the action's properties, adds the action to the list to be added, and calls the action's Start() method.

  7. actionDone()The method is a method that implements the SSActionCallback interface and is called when the action is completed.

SSMoveToAction
// SSMoveToAction 类,继承自 SSAction,用于实现游戏对象的移动动作。
public class SSMoveToAction : SSAction {
    
    
    // 移动的目标位置。
    public Vector3 target;
    // 移动的速度。
    public float speed;

    // 私有的构造函数,确保该类不能被外部直接实例化。
    private SSMoveToAction() {
    
     }

    // 静态方法,用于获取 SSMoveToAction 的实例。
    public static SSMoveToAction getAction(Vector3 target, float speed) {
    
    
        SSMoveToAction action = ScriptableObject.CreateInstance<SSMoveToAction>();
        action.target = target;
        action.speed = speed;
        return action;
    }

    // 重写基类的 Update 方法,实现移动逻辑。
    public override void Update() {
    
    
        // 使用 MoveTowards 方法将当前位置移向目标位置。
        this.transform.position = Vector3.MoveTowards(this.transform.position, target, speed * Time.deltaTime);
        // 如果当前位置达到目标位置,则标记为销毁,并调用回调接口的 actionDone 方法。
        if (this.transform.position == target) {
    
    
            this.destroy = true;
            this.whoToNotify.actionDone(this);
        }
    }

    // 重写基类的 Start 方法。
    public override void Start() {
    
     }
}

Code analysis:

  1. SSMoveToActionThe class inherits from SSAction and is used to implement the movement of game objects.

  2. targetThe attribute indicates the target position of movement, and the speed attribute indicates the speed of movement.

  3. The private constructor ensures that the class cannot be instantiated directly by the outside, but obtains the instance of through the static method getAction(Vector3 target, float speed). SSMoveToAction

  4. Update()The method is a method that overrides the base class and is called in every frame to implement the movement logic. Use the Vector3.MoveTowards method to move the current position to the target position. The speed of movement is affected by speed and Time.deltaTime. If the current position reaches the target position, mark destroy as true and notify that the action has been completed through the actionDone method of the callback interface.

  5. Start()Methods are methods that override base classes.

This class implements the moving action of the game object. It updates the position of the game object in each frame through the Update() method. When the object moves to the target position, it is marked for destruction and Notification callback interface.

SequenceAction

Code explanation and text analysis:

// SequenceAction 类,继承自 SSAction,实现了 SSActionCallback 接口,用于按顺序执行一系列动作。
public class SequenceAction : SSAction, SSActionCallback {
    
    
    // 保存要执行的一系列动作。
    public List<SSAction> sequence;
    // 动作重复执行的次数,1->只执行一次,-1->无限重复。
    public int repeat = 1;
    // 当前执行的动作在序列中的索引。
    public int currentActionIndex = 0;

    // 静态方法,用于获取 SequenceAction 的实例。
    public static SequenceAction getAction(int repeat, int currentActionIndex, List<SSAction> sequence) {
    
    
        SequenceAction action = ScriptableObject.CreateInstance<SequenceAction>();
        action.sequence = sequence;
        action.repeat = repeat;
        action.currentActionIndex = currentActionIndex;
        return action;
    }

    // 重写基类的 Update 方法,用于更新动作的状态。
    public override void Update() {
    
    
        // 如果序列中没有动作,则直接返回。
        if (sequence.Count == 0) return;

        // 如果当前执行的动作索引在序列范围内,更新当前动作的状态。
        if (currentActionIndex < sequence.Count) {
    
    
            sequence[currentActionIndex].Update();
        }
    }

    // 实现 SSActionCallback 接口的方法,在动作完成时被调用。
    public void actionDone(SSAction source) {
    
    
        // 标记当前动作为未销毁状态,切换到下一个动作。
        source.destroy = false;
        this.currentActionIndex++;

        // 如果当前动作索引超过了序列的长度。
        if (this.currentActionIndex >= sequence.Count) {
    
    
            this.currentActionIndex = 0;

            // 如果动作需要重复执行,减少重复次数。
            if (repeat > 0) repeat--;

            // 如果重复次数为0,标记该动作为销毁状态,并通知回调接口动作完成。
            if (repeat == 0) {
    
    
                this.destroy = true;
                this.whoToNotify.actionDone(this);
            }
        }
    }

    // 重写基类的 Start 方法,用于初始化动作。
    public override void Start() {
    
    
        // 将每个动作关联到当前游戏对象,设置回调接口,并调用动作的 Start 方法。
        foreach (SSAction action in sequence) {
    
    
            action.gameObject = this.gameObject;
            action.transform = this.transform;
            action.whoToNotify = this;
            action.Start();
        }
    }

    // 当对象被销毁时,销毁序列中的每个动作。
    void OnDestroy() {
    
    
        foreach (SSAction action in sequence) {
    
    
            DestroyObject(action);
        }
    }
}

Code analysis:

  1. SequenceActionThe class inherits from the SSAction class and implements the logic of executing a series of actions in sequence. At the same time, it implements the SSActionCallback interface for callback operations when the action is completed.

  2. sequenceProperties hold a sequence of actions to be performed in sequence.

  3. repeatThe attribute represents the number of times the action is repeated, 1 means it will be executed only once, -1 means it will be repeated infinitely.

  4. currentActionIndexThe attribute represents the index in the sequence of the currently executed action.

  5. getAction is a static method used to obtain an instance of SequenceAction and initialize its properties.

  6. UpdateThe method overrides the base class method and is used to update the status of the action. It checks whether the current action index is within the range of the sequence, and if so, updates the current action's state.

  7. actionDoneThe method implements the method of the SSActionCallback interface and is called when the action is completed. It marks the current action as undestructed, switches to the next action, and handles the repeated execution and destruction notification of the action according to the setting of the number of repetitions.

  8. StartThe method overrides the base class method and is used to initialize the action. It associates each action to the current game object, sets the callback interface, and calls the action's Start method.

  9. OnDestroymethod is a Unity lifecycle method that is called when the object is destroyed and is used to destroy each action object in the sequence.

SceneActionMange
// SceneActionManager 类,继承自 SSActionManager,负责管理游戏场景中的动作。
public class SceneActionManager : SSActionManager {
    
    
    // 移动船到目标位置的方法。
    public void moveBoat(BoatController boat) {
    
    
        // 创建一个移动动作,将船移动到目标位置。
        SSMoveToAction action = SSMoveToAction.getAction(boat.getDestination(), boat.movingSpeed);
        // 将动作添加到动作管理器中,设置回调为当前实例。
        this.addAction(boat.getGameobj(), action, this);
    }

    // 移动角色到目标位置的方法。
    public void moveCharacter(MyCharacterController characterCtrl, Vector3 destination) {
    
    
        // 获取当前角色的位置。
        Vector3 currentPos = characterCtrl.getPos();
        Vector3 middlePos = currentPos;

        // 根据目标位置和当前位置的关系,确定中间位置,确保角色移动路径合理。
        if (destination.y > currentPos.y) {
    
          // 从低处(船)到高处(岸)
            middlePos.y = destination.y;
        } else {
    
        // 从高处(岸)到低处(船)
            middlePos.x = destination.x;
        }

        // 创建两个移动动作,分别将角色移动到中间位置和目标位置。
        SSAction action1 = SSMoveToAction.getAction(middlePos, characterCtrl.movingSpeed);
        SSAction action2 = SSMoveToAction.getAction(destination, characterCtrl.movingSpeed);

        // 创建一个顺序动作,依次执行两个移动动作。
        SSAction seqAction = SequenceAction.getAction(1, 0, new List<SSAction> {
    
     action1, action2 });
        // 将顺序动作添加到动作管理器中,设置回调为当前实例。
        this.addAction(characterCtrl.getGameobj(), seqAction, this);
    }
}

Code analysis:

  1. SceneActionManagerThe class inherits from the SSActionManager class and is used to manage actions in the game scene.

  2. moveBoatThe method is responsible for moving the ship to the target location. It creates a move action SSMoveToAction, passing the ship's target position and movement speed as parameters to the action. The action is then added to the action manager with the callback set to the current instance.

  3. moveCharacterThe method is responsible for moving the character to the target location. It first determines an intermediate position based on the relationship between the target position and the current position to ensure that the character's movement path is reasonable. Then create two move actions SSMoveToAction to move the character to the middle position and the target position respectively. Next, create a sequence action SequenceAction and pass these two move actions as sub-actions to the sequence action. Finally, add the sequence action to the action manager with the callback set to the current instance.

  4. The purpose of these methods is to move the characters and ships in the game scene to the target location according to the specified path to realize the interaction between the characters and the ship in the game.

Development summary

In the process of developing this "Priests and Demons" game, I learned many important concepts and skills about game development by drawing on many reference blogs and codes online. Here are some key points from the development summary:

1. Game design and planning

  • Game rules definition: Game rules need to be clearly defined, including character abilities, game victory conditions and failure conditions, etc.
  • Character design: The characters in the game should have clear identities and characteristics, so that the game has sufficient depth and strategy.

2. Programming skills

  • Interface and implementation: Use interfaces to separate game logic and user operations, improving the modularity and maintainability of the code.
  • Single-case mode: Use single-case mode to manage game state to ensure that there is only one instance during the entire game life cycle.
  • GUI Programming: Learn to use Unity’s GUI system to create game interfaces, including buttons, labels and other UI elements.

3. Logical thinking and problem solving

  • Game status judgment: Writing game logic and judging whether the game is successful or failed requires logical thinking and problem-solving skills.
  • Game mechanism design: Designing game rules and character abilities requires in-depth thinking about the balance and fun of the game.

4. User experience and interface design

  • User interface design: Design a user-friendly game interface, including button location, font size, etc., to provide a good user experience.
  • Game feedback: Provide appropriate feedback in the game, such as displaying corresponding prompt information when the game is successful or failed, to increase the interactivity of the game.

Video display

Unity game Priest and Demon

Reference blog

http://t.csdnimg.cn/UbqPT
http://t.csdnimg.cn/T5rqW

Guess you like

Origin blog.csdn.net/helianthus_254/article/details/133863921