简易版GameFramework游戏框架搭建教程(六)Event

事件广播机制可以说是在各种框架里比较常见的解耦机制了,GF自然也不例外,在本篇文章中我们便来编写Event模块

下面是官网的介绍

 

在正式开始编写Event模块之前,我们需要完成一个前置的框架基础功能——ReferencePool(引用池),ReferencePool提供了一个IReference接口,只有实现了该接口的对象才会被纳入引用池管理当中,而在GF里,主要由Event模块的事件基类实现该接口,也就是说ReferencePool在框架里主要是负责管理Event模块的事件的引用的

 

首先在工程的Base文件夹下创建一个ReferencePool文件夹,新建IReference,ReferenceCollection和ReferencePool

其中

IReference上面已经说明过了

ReferenceCollection是引用集合类,负责管理实现了IReference的对象

ReferencePool是引用池类,负责管理所有的ReferenceCollection

 

打开IReference,将其修改为接口,并添加清理引用的方法

/// <summary>
/// 引用池对象接口
/// </summary>
public interface IReference{
    /// <summary>
    /// 清理引用
    /// </summary>
    void Clear();
}

接下来打开ReferenceCollection,为其添加字段与构造方法

/// <summary>
/// 引用集合
/// </summary>
public class ReferenceCollection{
    /// <summary>
    /// 引用队列
    /// </summary>
    private Queue<IReference> m_References;

    public ReferenceCollection()
    {
        m_References = new Queue<IReference>();
    }
}

并添加引用队列的相关方法

    /// <summary>
    /// 获取引用
    /// </summary>
    public T Acquire<T>() where T : class, IReference, new()
    {
        lock (m_References)
        {
            if (m_References.Count > 0)
            {
                return m_References.Dequeue() as T;
            }
        }

        return new T();
    } 

     /// <summary>
     /// 获取引用
     /// </summary>
     public IReference Acquire(Type referenceType)
     {
         lock (m_References)
         {
             if (m_References.Count > 0)
             {
                    return m_References.Dequeue();
             }
         }
         
         return (IReference)Activator.CreateInstance(referenceType);
     }

    /// <summary>
    /// 释放引用
    /// </summary>
    public void Release<T>(T reference) where T : class, IReference
    {
        reference.Clear();
        lock (m_References)
        {
            m_References.Enqueue(reference);
        }
    }

    /// <summary>
    /// 添加引用
    /// </summary>
    public void Add<T>(int count) where T : class, IReference, new()
    {
        lock (m_References)
        {
            while (count-- > 0)
            {
                m_References.Enqueue(new T());
            }
        }
    }

    /// <summary>
    /// 删除引用
    /// </summary>
    public void Remove<T>(int count) where T : class, IReference
    {
        lock (m_References)
        {
            if (count > m_References.Count)
            {
                count = m_References.Count;
            }

            while (count-- > 0)
            {
                m_References.Dequeue();
            }
        }
    }

    /// <summary>
    /// 删除所有引用
    /// </summary>
    public void RemoveAll()
    {
        lock (m_References)
        {
            m_References.Clear();
        }
    }

最后打开ReferencePool,将其修改为静态类,并为其添加字段与属性

/// <summary>
/// 引用池
/// </summary>
public static class ReferencePool{

    /// <summary>
    /// 引用集合的字典
    /// </summary>
    private static Dictionary<string, ReferenceCollection> s_ReferenceCollections = new Dictionary<string, ReferenceCollection>();

    /// <summary>
    /// 获取引用池的数量
    /// </summary>
    public static int Count
    {
        get
        {
            return s_ReferenceCollections.Count;
        }
    }

}

添加获取引用集合与清除所有引用集合的方法

    /// <summary>
    /// 获取引用集合
    /// </summary>
    private static ReferenceCollection GetReferenceCollection(string fullName)
    {
        ReferenceCollection referenceCollection = null;
        lock (s_ReferenceCollections)
        {
            if (!s_ReferenceCollections.TryGetValue(fullName, out referenceCollection))
            {
                referenceCollection = new ReferenceCollection();
                s_ReferenceCollections.Add(fullName, referenceCollection);
            }
        }

        return referenceCollection;
    }
    /// <summary>
    /// 清除所有引用集合
    /// </summary>
    public static void ClearAll()
    {
        lock (s_ReferenceCollections)
        {
            foreach (KeyValuePair<string, ReferenceCollection> referenceCollection in s_ReferenceCollections)
            {
                referenceCollection.Value.RemoveAll();
            }

            s_ReferenceCollections.Clear();
        }
    }

然后添加引用池的相关方法

追加与移除

    /// <summary>
    /// 向引用集合中追加指定数量的引用
    /// </summary>
    /// <typeparam name="T">引用类型</typeparam>
    /// <param name="count">追加数量</param>
    public static void Add<T>(int count) where T : class, IReference, new()
    {
        GetReferenceCollection(typeof(T).FullName).Add<T>(count);
    }

    /// <summary>
    /// 从引用集合中移除指定数量的引用
    /// </summary>
    /// <typeparam name="T">引用类型</typeparam>
    /// <param name="count">移除数量</param>
    public static void Remove<T>(int count) where T : class, IReference
    {
        GetReferenceCollection(typeof(T).FullName).Remove<T>(count);
    }

    /// <summary>
    /// 从引用集合中移除所有的引用
    /// </summary>
    /// <typeparam name="T">引用类型</typeparam>
    public static void RemoveAll<T>() where T : class, IReference
    {
        GetReferenceCollection(typeof(T).FullName).RemoveAll();
    }

获取与归还

    /// <summary>
    /// 从引用集合获取引用
    /// </summary>
    public static T Acquire<T>() where T : class, IReference, new()
    {
        return GetReferenceCollection(typeof(T).FullName).Acquire<T>();
    }

     /// <summary>
     /// 从引用池获取引用
     /// </summary>
     public static IReference Acquire(Type referenceType)
     {
         return GetReferenceCollection(referenceType.FullName).Acquire(referenceType);
     }

    /// <summary>
    /// 将引用归还引用集合
    /// </summary>
    /// <typeparam name="T">引用类型</typeparam>
    /// <param name="reference">引用</param>
    public static void Release<T>(T reference) where T : class, IReference
    {
        if (reference == null)
        {
            Debug.LogError("要归还的引用为空");
        }

        GetReferenceCollection(typeof(T).FullName).Release(reference);
    }

Ok,到这里ReferencePool功能编写完毕,可以正式开始Event模块的编写了

 

新建Event文件夹,在其中新建GlobalEventArgs,EventPool与EventManager

GlobalEventArgs是全局事件的基类,EventPool负责将某一类型的GlobalEventArgs封装后进行管理,EventManager负责管理所有的EventPool

首先打开GlobalEventArgs,将其修改为抽象类,继承EventArgs并实现IReference

/// <summary>
/// 全局事件基类(继承该类的事件类才能被事件池管理)
/// </summary>
public abstract class GlobalEventArgs : EventArgs,IReference {
    // <summary>
    /// 事件类型ID
    /// </summary>
    public abstract int Id { get; }

    public abstract void Clear();
}

然后打开EventPool,为其添加泛型与约束,并编写一个内部类Event封装GlobalEventArgs,用于进行线程安全的事件抛出

/// <summary>
/// 事件池
/// </summary>
public class EventPool<T> where T : GlobalEventArgs{

    /// <summary>
    /// 事件结点
    /// </summary>
    private class Event
    {

        public Event(object sender, T e)
        {
            Sender = sender;
            EventArgs = e;
        }
        /// <summary>
        /// 事件发送者
        /// </summary>
        public object Sender { get; private set; }

        /// <summary>
        /// 事件参数
        /// </summary>
        public T EventArgs { get; private set; }
    }

}

这里之所以要这么封装是因为事件处理方法的委托采用了C#的EventHandler,其形参为object与EvntArgs

接下来添加字段与属性,并在构造方法里进行初始化

    /// <summary>
    /// 事件码与对应处理方法的字典
    /// </summary>
    private Dictionary<int, EventHandler<T>> m_EventHandlers;

    /// <summary>
    /// 事件结点队列
    /// </summary>
    private Queue<Event> m_Events;

    public EventPool()
    {
        m_EventHandlers = new Dictionary<int, EventHandler<T>>();
        m_Events = new Queue<Event>();
    }

添加事件的订阅与取消的方法

    /// <summary>
    /// 检查订阅事件处理方法是否存在
    /// </summary>
    public bool Check(int id, EventHandler<T> handler)
    {
        if (handler == null)
        {
             Debug.LogError("事件处理方法为空");
             return false;
        }

        EventHandler<T> handlers = null;
        if (!m_EventHandlers.TryGetValue(id, out handlers))
        {
            return false;
        }

        if (handlers == null)
        {
            return false;
        }

        //遍历委托里的所有方法
        foreach (EventHandler<T> i in handlers.GetInvocationList())
        {
            if (i == handler)
            {
                return true;
            }
        }

        return false;
    }

    /// <summary>
    /// 订阅事件
    /// </summary>
    public void Subscribe(int id, EventHandler<T> handler)
    {
        if (handler == null)
        {
            Debug.LogError("事件处理方法为空,无法订阅");
            return;
        }

        EventHandler<T> eventHandler = null;
        //检查是否获取处理方法失败或获取到的为空
        if (!m_EventHandlers.TryGetValue(id, out eventHandler) || eventHandler == null)
        {
            m_EventHandlers[id] = handler;
        }
        //不为空,就检查是否处理方法重复了
        else if (Check(id, handler))
        {
            Debug.LogError("要订阅事件的处理方法已存在");
        }
        else
        {
            eventHandler += handler;
            m_EventHandlers[id] = eventHandler;
        }
    }

    /// <summary>
    /// 取消订阅事件
    /// </summary>
    public void Unsubscribe(int id, EventHandler<T> handler)
    {
        if (handler == null)
        {
            Debug.LogError("事件处理方法为空,无法取消订阅");
            return;
        }

        if (m_EventHandlers.ContainsKey(id))
        {
            m_EventHandlers[id] -= handler;
        }
    }

添加事件处理方法

    /// <summary>
    /// 处理事件
    /// </summary>
    /// <param name="sender">事件来源</param>
    /// <param name="e">事件参数</param>
    private void HandleEvent(object sender, T e)
    {
        //尝试获取事件的处理方法
        int eventId = e.Id;
        EventHandler<T> handlers = null;
        if (m_EventHandlers.TryGetValue(eventId, out handlers))
        {
            if (handlers != null)
            {
                handlers(sender, e);
            }
            else
            {
                Debug.LogError("事件没有对应处理方法:" + eventId);
            }        
        }
        
         //向引用池归还事件引用
         ReferencePool.Release(e);
    }

    /// <summary>
    /// 事件池轮询(用于处理线程安全的事件)
    /// </summary>
    public void Update(float elapseSeconds, float realElapseSeconds)
    {
        while (m_Events.Count > 0)
        {
            Event e = null;
            lock (m_Events)
            {
                e = m_Events.Dequeue();
            }
            //从封装的Event中取出事件数据进行处理
            HandleEvent(e.Sender, e.EventArgs);
        }
    }

添加事件抛出方法

    /// <summary>
    /// 抛出事件(线程安全)
    /// </summary>
    /// <param name="sender">事件源。</param>
    /// <param name="e">事件参数。</param>
    public void Fire(object sender, T e)
    {
        //将事件源和事件参数封装为Event加入队列
        Event eventNode = new Event(sender, e);
        lock (m_Events)
        {
            m_Events.Enqueue(eventNode);
        }
    }


    /// <summary>
    /// 抛出事件(线程不安全)
    /// </summary>
    /// <param name="sender">事件源</param>
    /// <param name="e">事件参数</param>
    public void FireNow(object sender, T e)
    {
        HandleEvent(sender, e);
    }

最后添加关闭与清理事件池的方法

    /// <summary>
    /// 清理事件。
    /// </summary>
    public void Clear()
    {
        lock (m_Events)
        {
            m_Events.Clear();
        }
    }

    /// <summary>
    /// 关闭并清理事件池。
    /// </summary>
    public void Shutdown()
    {
        Clear();
        m_EventHandlers.Clear();
    }

到这里EventPool的编写就完成了

接下来打开EventManager,继承ManagerBase,添加需要的字段与构造方法

/// <summary>
/// 事件管理器
/// </summary>
public class EventManager : ManagerBase
{
    /// <summary>
    /// 事件池
    /// </summary>
    private EventPool<GlobalEventArgs> m_EventPool;

    public override int Priority
    {
        get
        {
            return 100;
        }
    }

    public EventManager()
    {
        m_EventPool = new EventPool<GlobalEventArgs>();
    }

    public override void Init()
    {
        
    }

    public override void Shutdown()
    {
        //关闭并清理事件池
        m_EventPool.Shutdown();
    }

    /// <summary>
    /// 轮询事件池
    /// </summary>
    public override void Update(float elapseSeconds, float realElapseSeconds)
    {
        m_EventPool.Update(elapseSeconds, realElapseSeconds);
    }
}

添加事件的订阅与取消(其实就是对事件池的代理)

    /// <summary>
    /// 检查订阅事件处理方法是否存在
    /// </summary>
    public bool Check(int id, EventHandler<GlobalEventArgs> handler)
    {
        return m_EventPool.Check(id, handler);
    }

    /// <summary>
    /// 订阅事件
    /// </summary>
    public void Subscribe(int id, EventHandler<GlobalEventArgs> handler)
    {
        m_EventPool.Subscribe(id, handler);
    }

    /// <summary>
    /// 取消订阅事件
    /// </summary>
    public void Unsubscribe(int id, EventHandler<GlobalEventArgs> handler)
    {
        m_EventPool.Unsubscribe(id, handler);
    }

以及事件的抛出

    /// <summary>
    /// 抛出事件(线程安全)
    /// </summary>
    public void Fire(object sender, GlobalEventArgs e)
    {
        m_EventPool.Fire(sender, e);
    }

    /// <summary>
    /// 抛出事件(线程不安全)
    /// </summary>
    public void FireNow(object sender, GlobalEventArgs e)
    {
        m_EventPool.FireNow(sender, e);
    }

很简单的就完成了EventManager的编写,那么就轮到测试环节了

按照惯例,新建好文件夹,测试脚本,以及测试场景

打开EventTestArgs,使其继承GlobalEventArgs,并添加一个string字段

public class EventTestArgs : GlobalEventArgs
{

   public string m_Name;


    public override int Id
    {
        get
        {
            return 1;
        }
    }

    public override void Clear()
    {
        m_Name = string.Empty;
    }


    /// <summary>
    /// 事件填充
    /// </summary>
    public EventTestArgs Fill(string name)
    {
        m_Name = name;

        return this;
    }
}

打开测试脚本,进行事件的订阅与派发编写

public class EventTestMain : MonoBehaviour {


    private void Start()
    {
        //订阅事件
        FrameworkEntry.Instance.GetManager<EventManager>().Subscribe(1, EventTestMethod);
    }


    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
             EventTestArgs e = ReferencePool.Acquire<EventTestArgs>();
           
            //派发事件
            FrameworkEntry.Instance.GetManager<EventManager>().Fire(this, e.Fill("EventArgs"));
        }
    }


    /// <summary>
    /// 事件处理方法
    /// </summary>
    private void EventTestMethod(object sender, GlobalEventArgs e)
    {
        EventTestArgs args = e as EventTestArgs;
        Debug.Log(args.m_Name);
    }
}

然后启动游戏,点击左键,控制台成功输出了EventTest

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为您提供一个简单的实现,但请注意,这只是一个简易版游戏画面较简单,游戏规则也较为简单。 首先,您需要安装pygame模块,它是一个用于游戏开发的Python库。您可以使用以下命令进行安装: ``` pip install pygame ``` 接下来,您可以按照以下步骤进行游戏的开发: 1. 导入pygame库和其他必要的库: ```python import pygame import random import os ``` 2. 定义游戏窗口的大小和其他必要的常量: ```python WIDTH = 480 HEIGHT = 600 FPS = 60 # 颜色常量 WHITE = (255, 255, 255) BLACK = (0, 0, 0) RED = (255, 0, 0) GREEN = (0, 255, 0) BLUE = (0, 0, 255) ``` 3. 初始化pygame库和游戏窗口: ```python pygame.init() pygame.mixer.init() screen = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("雷霆战机") clock = pygame.time.Clock() ``` 4. 加载游戏图片和音效: ```python # 加载游戏图片 background_img = pygame.image.load(os.path.join("img", "background.png")).convert() player_img = pygame.image.load(os.path.join("img", "player.png")).convert() enemy_img = pygame.image.load(os.path.join("img", "enemy.png")).convert() # 加载游戏音效 shoot_sound = pygame.mixer.Sound(os.path.join("sound", "shoot.wav")) explosion_sound = pygame.mixer.Sound(os.path.join("sound", "explosion.wav")) ``` 5. 定义游戏中的各种对象,包括玩家、敌人、子弹等: ```python class Player(pygame.sprite.Sprite): def __init__(self): pygame.sprite.Sprite.__init__(self) self.image = pygame.transform.scale(player_img, (50, 38)) self.image.set_colorkey(BLACK) self.rect = self.image.get_rect() self.rect.centerx = WIDTH / 2 self.rect.bottom = HEIGHT - 10 self.speedx = 0 def update(self): self.speedx = 0 keystate = pygame.key.get_pressed() if keystate[pygame.K_LEFT]: self.speedx = -5 if keystate[pygame.K_RIGHT]: self.speedx = 5 self.rect.x += self.speedx if self.rect.right > WIDTH: self.rect.right = WIDTH if self.rect.left < 0: self.rect.left = 0 def shoot(self): bullet = Bullet(self.rect.centerx, self.rect.top) all_sprites.add(bullet) bullets.add(bullet) class Enemy(pygame.sprite.Sprite): def __init__(self): pygame.sprite.Sprite.__init__(self) self.image = pygame.transform.scale(enemy_img, (50, 38)) self.image.set_colorkey(BLACK) self.rect = self.image.get_rect() self.rect.x = random.randrange(WIDTH - self.rect.width) self.rect.y = random.randrange(-100, -40) self.speedy = random.randrange(1, 8) def update(self): self.rect.y += self.speedy if self.rect.top > HEIGHT + 10: self.rect.x = random.randrange(WIDTH - self.rect.width) self.rect.y = random.randrange(-100, -40) self.speedy = random.randrange(1, 8) class Bullet(pygame.sprite.Sprite): def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) self.image = pygame.Surface((10, 20)) self.image.fill(GREEN) self.rect = self.image.get_rect() self.rect.bottom = y self.rect.centerx = x self.speedy = -10 def update(self): self.rect.y += self.speedy if self.rect.bottom < 0: self.kill() ``` 6. 定义游戏主循环: ```python # 创建游戏对象 all_sprites = pygame.sprite.Group() enemies = pygame.sprite.Group() bullets = pygame.sprite.Group() player = Player() all_sprites.add(player) # 创建敌人 for i in range(8): enemy = Enemy() all_sprites.add(enemy) enemies.add(enemy) # 游戏主循环 running = True while running: clock.tick(FPS) # 处理游戏事件 for event in pygame.event.get(): if event.type == pygame.QUIT: running = False elif event.type == pygame.KEYDOWN: if event.key == pygame.K_SPACE: player.shoot() # 更新游戏对象 all_sprites.update() # 检测子弹是否击中敌人 hits = pygame.sprite.groupcollide(enemies, bullets, True, True) for hit in hits: explosion_sound.play() enemy = Enemy() all_sprites.add(enemy) enemies.add(enemy) # 检测敌人是否与玩家碰撞 hits = pygame.sprite.spritecollide(player, enemies, False) if hits: running = False # 绘制游戏画面 screen.fill(BLACK) screen.blit(background_img, (0, 0)) all_sprites.draw(screen) pygame.display.flip() pygame.quit() ``` 这就是一个简易版雷霆战机小游戏的实现。当然,您可以根据自己的需要对游戏进行修改和完善。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值