CocosCreator+TS实现“祖马”小游戏中的路径角色回退效果

CocosCreator+TS实现“祖马”小游戏中的路径角色回退效果。
image
如图打掉中间的珠子,后面的珠子会回退并无缝连接上前面的珠子。

思路:
大概就是,击中的珠子消除前,先做位置置换。
位置直接交换就像“闪现”一样肯定不行,需要的效果是沿着途中的路径线性回退。
有两种方案:
1.保存击中珠子以及之后的所有珠子的位置,后面的珠子逐个(遍历)位置移动
(无脑但是有点麻烦)
2.后面的珠子逐个向前移动,使用await promise连接起来,消失几个珠子就异步几次移动(比第一种方式更安全)

这里以一个简单的例子来实现效果:
image

import { _decorator, Color, color, Component, instantiate, Node, Prefab, Sprite, tween, v3 } from 'cc';
import { Monster } from './Monster';
const { ccclass, property } = _decorator;

@ccclass('MonsterMgr')
export class MonsterMgr extends Component {
    @property(Prefab)
    pre_monster:Prefab = null;
    @property()
    max_count:number = 50;
    @property(Node)
    birth_node:Node = null;
    @property(Node)
    end_node:Node = null;
    back_list:number[] = [];
    
    m_colors = [
        color(255,255,255,255),
        color(245,221,113,255),
        color(200,240,99,255),
        color(160,240,99,255),
    ];

    exist_monster_arr:Node[] = [];
    m_grap:number = 60;

    m_spwan_index:number = -1;
    m_speed:number = 100;
    m_cur_color:Color = null;


    public static Instance:MonsterMgr = null;
    protected onLoad(): void {
        if(!MonsterMgr.Instance){
            MonsterMgr.Instance = this;
        }
    }
    start() {
        this.spwanMonster();
    }
    spwanMonster(){

        this.m_spwan_index += 1;
        if(this.m_spwan_index >= this.max_count)return;
        let m = instantiate(this.pre_monster);
        m.parent = this.node;
        if(this.exist_monster_arr.length > 0){
            m.position = v3(this.exist_monster_arr[0].position.x - this.m_grap,this.exist_monster_arr[0].position.y);
        }else{
            m.position = this.birth_node.position;
        };
        this.exist_monster_arr.unshift(m);
        let groupIndex = Math.floor(this.m_spwan_index / 5);
        let mIndex = this.m_spwan_index % 5;
        if(mIndex == 0){
            this.m_cur_color = this.m_colors[Math.floor(Math.random()* this.m_colors.length)];
        }
        m.getComponent(Monster).init(groupIndex,mIndex,this.m_speed);
        
        m.getComponent(Sprite).color = this.m_cur_color;
        this.scheduleOnce(()=>{
            this.spwanMonster();
        },0.001);
    }

    stopMove(){
        for(let i = 0;i < this.exist_monster_arr.length;i++){
            let m = this.exist_monster_arr[i];
            if(m){
                m.getComponent(Monster).pauseMove();
            }
        }
    }
    deathGroupMonster(group_index:number){
        let start_m_index = this.max_count - 1 - group_index*5;
        for(let i = start_m_index;i > start_m_index - 5;i--){
            console.log("i = " + i);
            let m = this.exist_monster_arr[i];
            if(m){
                console.log('death + ')
                m.getComponent(Monster).death();
            }
        };
        //判断是否需要移动
        let can_move = false;
        for(let j = start_m_index + 1;j < this.max_count;j++){
            let m = this.exist_monster_arr[j];
            if(m && !(m.getComponent(Monster).is_death)){
                can_move = true;
            }
        }
        if(can_move){
            let index = 0;
            this.checkBackMove(index,start_m_index);
        }
       
    }
    async checkBackMove(index:number,start_m_index:number){
        if(index >= 5){
            return;
        }
        await this.backMove(start_m_index);
        this.resetGroups();
        this.checkBackMove(index + 1,start_m_index - 1);
    }
    backMove(start_m_index:number){
        let move_promises:Promise<any>[] = [];
        let temp_start_index = start_m_index + 5;
        //需要移动5步
        for(let j = temp_start_index;j > temp_start_index - 5;j--){
            let front_m_index = j - 1;
            let front_m = this.exist_monster_arr[front_m_index];
            let front_m_pos = front_m.position.clone();
            let cur_m = this.exist_monster_arr[j];
            move_promises.push(new Promise<void>((resolve, reject) => {
                tween(cur_m)
                .to(1,{position:front_m_pos})
                .call(()=>resolve())
                .start()
            }))
        };
        let up_front_m = this.exist_monster_arr[temp_start_index];
        for(let k = temp_start_index - 5;k < temp_start_index;k++){
            this.exist_monster_arr[k] = this.exist_monster_arr[k + 1];
        };
        this.exist_monster_arr[temp_start_index + 5] = up_front_m;
        return Promise.all(move_promises);
    }

    resetGroups(){
        let group_index = Math.floor(this.max_count / 5) - 1;
        let mIndex = 5;
        for(let i = 0;i < this.max_count;i++){
            let m = this.exist_monster_arr[i];
            mIndex = mIndex - 1;
            if(mIndex < 0){
                group_index = group_index - 1;
                mIndex = 4;
            }
            if(m){
                m.getComponent(Monster).resetGroup(group_index,mIndex);
            }
        }
    }
    

    update(deltaTime: number) {
        
    }
}


管理怪物的生成和销毁(珠子的创建和回收)

import { _decorator, Component, Label, Node, tween, UIOpacity, v3, Vec3 } from 'cc';
import { MonsterMgr } from './MonsterMgr';
const { ccclass, property } = _decorator;

@ccclass('Monster')
export class Monster extends Component {

    group_index:number = -1;
    m_index:number = -1;
    ismoving:boolean = true;
    m_speed:number = 100;
    temp_Vec:Vec3 = v3();
    is_death:boolean = false;
    init(groupIndex:number,mIndex:number,mSpeed:number){
        this.group_index = groupIndex;
        this.m_index = mIndex;
        this.m_speed = mSpeed;
        this.node.getChildByName('g').getComponent(Label).string = 'g:'+this.group_index;
        this.node.getChildByName('m').getComponent(Label).string = 'm:'+this.m_index;
        this.is_death = false;
    }
    resetGroup(groupIndex:number,mIndex:number){
        this.group_index = groupIndex;
        this.m_index = mIndex;
        this.node.getChildByName('g').getComponent(Label).string = 'g:'+this.group_index;
        this.node.getChildByName('m').getComponent(Label).string = 'm:'+this.m_index;
    }

    pauseMove(){
        this.ismoving = false;
    }
    reuseMove(){
        this.ismoving = true;
    }


    protected start(): void {
        this.node.on(Node.EventType.TOUCH_START,this.onTouch,this);
    }
    onTouch(){
        if(!this.ismoving)return;
        if(this.is_death)return;
        MonsterMgr.Instance.stopMove();
        MonsterMgr.Instance.deathGroupMonster(this.group_index);
    }

    death(){
        this.is_death = true;
        tween(this.node.getComponent(UIOpacity))
        .to(0.5,{opacity:0})
        .start();
    }

    protected update(dt: number): void {
        if(!this.ismoving)return;
        this.temp_Vec.x = this.node.position.x + this.m_speed*dt;
        this.temp_Vec.y = this.node.position.y;
        this.temp_Vec.z = 0;
        this.node.position = this.temp_Vec;
    }
}


核心逻辑是checkBackMove和backMove

这里由于把monster存在了一个类似链表中,所以每次异步移动后更新一下链表。
至于resetGroups,可以自定义一些需要更新的数据在里面,方便monster属性的扩展。

posted @ 2025-06-09 20:04  EricShx  阅读(72)  评论(0)    收藏  举报