Development of an H5 game

Keywords: Mobile

Role control


After the map and character are created, you can use the mouse, keyboard, or instructions from the server to run, fight Wait for the animation action. If you simply control the execution of the animation directly, there will be another one playing before the action is finished. Multiple operations will only execute the last one. Make the animation look very chaotic and uncontrollable
At this time, we need to add a set of state to the animation, and then design a set of mechanism that can issue a command - > execute a command according to the State - > change the State - > the command is executed. In this way, the animation looks very natural, and then the control is relatively simple

Role

To facilitate control, we further encapsulate some data such as roleobject and roledata

/**
 * role
 */
class Role implements IPooledObject {
	protected _roleData:RoleData;//Role data
	protected _roleObject:RoleObject;//Character display object
	protected _pauseState:boolean = false;
	protected _pauseObject:boolean = false;
	protected _playRate:number = 1;
	protected _controller:ActionStateController;//Action state controller
	protected _actionCommandProcessor:ActionCommandProcessor;// Action command processor
	protected _roleActionListener:IRoleActionListener;//Role action monitor interface
	protected _roleActionCommandListener:IRoleActionCommandListener;//Role action command listener interface
	protected _roleMapListener:IRoleMapListener;

Role status

The status is as follows, which can be adjusted according to the needs of the game
NONE = 0, none
IDLE = 1, idle
MOVE = 2, move
ATTACK = 3, attack
BE_ATTACK = 4, attacked
DEADING = 5, dying
DEAD = 6, dead
COLLECT = 7, collect
ACTIVE = 8, active
How to handle state

	/**
	 * state
	 */
	public get state():RoleActionState {
		return this._state;
	}

	/**
	 * Get whether it is the current state
	 */
	protected isCurrentState():boolean {
		return this.roleData.isInState(this._state);
	}
    /**
     * Is it in the current state
     */
    public isInState(state:RoleActionState):boolean {
        return this.state == state;
    }

    /**
     * Whether the previous state is the specified state
     */
    public isInStateLast(state:RoleActionState):boolean {
        return this.lastState == state;
    }

    /**
     * Whether the status can be set
     */
    public canSetState(state:RoleActionState):boolean {
        return this.state != state;
    }

    /**
     * Set status
     */
    public setState(state:RoleActionState):boolean {
        if (this.state != state) {
            if (this.state != RoleActionState.NONE && this.state != RoleActionState.IDLE) {
                this.lastNoneEmptyState = this.state;
            }
            this.lastState = this.state;
            this.state = state;
            return true;
        } else {
            this.lastState = this.state;
            return false;
        }
    }

    /**
     * Cancel status or not
     */
    public canCancelState(state:RoleActionState):boolean {
        return this.state == state;
    }

    /**
     * Cancel status
     */
    public cancelState(state:RoleActionState):boolean {
        if (this.state == state) {
            this.setState(RoleActionState.NONE);
            return true;
        }
        return false;
    }

    /**
     * Is it idle
     */
    public isIdleState():boolean {
        return this.state == RoleActionState.NONE || this.state == RoleActionState.IDLE;
    }

Perform animation

command

Command is the process of changing the execution of a character's actions on a server or player,

Command classification

The command is set to correspond to the action of the animation. Move has move command, attack has attack command

Command processor

It is used to manage commands, add and remove commands, stop commands, clean commands, etc

/**
 * Action command processor
 */
class ActionCommandProcessor implements IActionCommandListener {

    public role:Role;
    public currentCommand:IActionCommand;
    protected _nextCommands:Array<IActionCommand>;

    public constructor(role:Role) {
        this.role = role;
        this._nextCommands = new Array<IActionCommand>();
    }

    /**
     * Whether the action command is being executed
     */
    public get isCommandRunning():boolean {
        return this.currentCommand != null;
    }

    /**
     * Include action command or not
     */
    public hasCommand(clazz:any):boolean {
        if (this.currentCommand != null && this.currentCommand instanceof clazz) {
            return true;
        }
        return this.hasDelayCommand(clazz);
    }

    /**
     * Include delay command or not
     */
    public hasDelayCommand(clazz:any):boolean {
         for (var command of this._nextCommands) {
            if (command instanceof clazz) {
                return true;
            }
        }
        return false;
    }

    /**
     * Remove delay command
     */
    public removeDelayCommand(clazz:any):boolean {
        var result:boolean = false;
        for (var i = this._nextCommands.length - 1; i >= 0; i--) {
            if (this._nextCommands[i] instanceof clazz) {
                this._nextCommands.splice(i, 1);
                result = true;
            }
        }
        return result;
    }

    /**
     * Start command execution
     */
    public startCommand(command:IActionCommand, isDelay:boolean = false):boolean {
        if (this.currentCommand != null) {
            if (isDelay) {
                this._nextCommands.push(command);
            }
            return false;
        }
        this.currentCommand = command;
        this.currentCommand.role = this.role;
        this.currentCommand.setListener(this);
        this.currentCommand.start();
        return true;
    }

    /**
     * Stop command execution
     */
    public stopCommand(immediate:boolean = false):void {
        if (this.currentCommand != null) {
            if (this.currentCommand.canStop == false && immediate == false) {
                return;
            }
            this.clearNextCommands();
            this.currentCommand.stop(immediate);
            if (immediate) {
                this.currentCommand = null;
            }
        }
    }

    /**
     * List of cleanup commands
     */
    protected clearNextCommands():void {
        for (var command of this._nextCommands) {
            ObjectPoolManager.inst.releaseObject(command);
        }
        this._nextCommands.length = 0;
    }

    /**
     * to update
     */
    public update(deltaTime:number):void {
        if (this.currentCommand != null) {
            this.currentCommand.update(deltaTime);
        }
    }

    /**
     * Remove target 
     */
    public removeTarget(target:Role):void {
        if (this.currentCommand != null) {
            this.currentCommand.removeTarget(target);
        }
    }

    /**
     * Action command complete
     */
    public onActionCommandComplete(command:IActionCommand):void {
        if (this.currentCommand == command) {
            this.role.onActionCommandComplete(this.currentCommand);
            ObjectPoolManager.inst.releaseObject(command);
            this.currentCommand = null;
            if (this._nextCommands.length > 0) {
                var tmpCommand:IActionCommand = this._nextCommands.shift();
                this.startCommand(tmpCommand);
            }
        } else {
            ObjectPoolManager.inst.releaseObject(command);
        }
    }

    /**
     * Reset
     */
    public reset():void {
        this.clearNextCommands();
        if (this.currentCommand != null) {
            ObjectPoolManager.inst.releaseObject(this.currentCommand);
            this.currentCommand = null;
        }
    }
}

Command execution

The core logic of specific execution of each command is to create corresponding action data and then execute the action

	public start():void {
		super.start();

		if (this.position != null) {
			GameModules.role.setRolePosition(this.role, this.position);
		}
		
		var attackAction:AttackAction = ObjectPoolManager.inst.getObject(AttackAction) as AttackAction;
		attackAction.skillResID = this.skillResID;
		attackAction.direction = this.direction;
		attackAction.targets = this.targets;
		attackAction.targetPositions = this.targetPositions;
		attackAction.position = this.position;
		attackAction.destPosition = this.destPosition;
		attackAction.moveTime = this.moveTime;
		attackAction.animationLoop = this.animationLoop;
		attackAction.actionCallback = this._finishCallback;
		attackAction.onlyAction = this.onlyAction;
		this.doAction(attackAction);
	}

Action data is basically divided by action

Action execution

Action classification

In command this.doAction Because the action involves playing speed, direction, zoom A lot of different needs of processing. So for different needs of the implementation of action design action classification

Action control

Similar to the command processor, it controls the switching of actions

	/**
	 * Update controller
	 */
	public update(deltaTime:number):void {
		if (this._currentController != null) {
			this._currentController.update(deltaTime);
		} else if (this._defaultController != null) {
			this._defaultController.update(deltaTime);
		}
	}

	/**
	 * Enable controller
	 */
	public activateController(state:RoleActionState, action:IRoleAction, ignoreState:boolean = false):void {
		var controller:IRoleActionController = this._controllerDict[state];
		if (controller != null) {
			if (controller.activate(action, ignoreState)) {
				if (this._currentController != null && this._currentController != controller) {
					this._currentController.deactivateImmediately();
				}
				this._currentController = controller;
			}
		}
	}

	/**
	 * Disable controller
	 */
	public deactivateController(state:RoleActionState):void {
		var controller:IRoleActionController = this._controllerDict[state];
		if (controller != null) {
			if (controller.deactivate()) {
				if (this._currentController == controller) {
					this._currentController = null;
				}
			}
		}
	}

Action execution

In short, it's OK to play the animation directly in the action class

	protected activateInner(action:IRoleAction):boolean {
		this._moveAction = action as MoveAction;
		if (this._moveAction != null) {
			this._maxTime = this._moveAction.moveTime;
			// calculation speed
			this.caculateMoveSpeed();
			// Set roleData
			this.roleData.direction = this._moveAction.direction;
			this.roleData.destPosition.copyFrom(this._moveAction.destPosition);
			// Play action
			this.roleObject.playAction(this._moveAction.action, this._moveAction.direction);
			this.roleObject.frameIndex = this.roleData.moveFrame;
			// Set transparency
			if (GameModules.map.getTransparentByMapPosition(this._moveAction.destPosition)) {
				this.roleObject.setAlpha(RoleSetting.ROLE_ALPHA_TRANSPARENT);
			} else {
				this.roleObject.setAlpha(RoleSetting.ROLE_ALPHA_NORMAL);
			}
			return true;
		} else {
			return false;
		}
	}

	protected caculateMoveSpeed():void {
		this._speedX = (this._moveAction.destPosition.realX - this.roleObject.position.x) / this._moveAction.moveTime;
		this._speedY = (this._moveAction.destPosition.realY - this.roleObject.position.y) / this._moveAction.moveTime;
	}

	protected updateInner(deltaTime:number):void {
		if (this.isMaxTime()) {
			this.roleObject.setPositionXYZ(this._moveAction.destPosition.realX, this._moveAction.destPosition.realY);
		} else {
			this.roleObject.setPositionOffsetXYZ(this._speedX * deltaTime, this._speedY * deltaTime);
		}
		if (this._role.roleMapListener != null) {
			this._role.roleMapListener.onRolePositionChanged(this._role, this.roleObject.position.x, this.roleObject.position.y);
		}
	}

This code is a mobile animation playback, from the previous chapters know
this.roleObject.playAction (this._ moveAction.action , this._ moveAction.direction ); it is the real play sequence frame animation
The logic of other places involved
Transparency of roles
this.roleObject.setAlpha
Role position changes on the map
this.roleObject.setPositionXYZ
For example, playing attack involves skill effect, which is more complex. So it is necessary to do action processing. For complex attacks, the next chapter is expected to talk about the realization of attack skill effect

Posted by kenwvs on Tue, 16 Jun 2020 21:56:05 -0700