存档

2016年11月 的存档

MacOS获取辅助功能权限控制鼠标点击事件

2016年11月29日 没有评论

昨晚玩一个模拟经营的游戏,由于升级太慢我就不停的种树卖树来换取经验值。不过重复点击10几分钟后,实在受不了。网上本来准备找个鼠标自动点击的软件用用。结果没找到趁手的。如是自己写了个。

自己设置需要点击的一组动作,长按(100,200),点击(576,789),点击(750,550)。类似在对应坐标点操作鼠标。

原理非常简单,我就不贴代码了,主要说下如何控制鼠标点击事件。

注册系统辅助权限,这里会触发用户授权

let opts = NSDictionary(object: kCFBooleanTrue,
                        forKey: kAXTrustedCheckOptionPrompt.takeUnretainedValue() as NSString
            ) as CFDictionary

guard AXIsProcessTrustedWithOptions(opts) == true else { return }

鼠标事件

// 鼠标左键按下
let mouseDown = CGEvent(mouseEventSource: nil,
                        mouseType: .leftMouseDown,
                        mouseCursorPosition: CGPoint(x: 200, y: 300),
                        mouseButton: .left
)
mouseDown?.post(tap: .cghidEventTap)

// 鼠标左键抬起
let mouseUp = CGEvent(mouseEventSource: nil,
                      mouseType: .leftMouseUp,
                      mouseCursorPosition: CGPoint(x: 200, y: 300),
                      mouseButton: .left
)
mouseUp?.post(tap: .cghidEventTap)

以上两个事件,组成了一个鼠标左键在坐标(200,300)点击事件

MouseType支持类型

/* Constants that specify the different types of input events. */
public enum CGEventType : UInt32 {


    /* The null event. */
    case null


    /* Mouse events. */
    case leftMouseDown

    case leftMouseUp

    case rightMouseDown

    case rightMouseUp

    case mouseMoved

    case leftMouseDragged

    case rightMouseDragged


    /* Keyboard events. */
    case keyDown

    case keyUp

    case flagsChanged


    /* Specialized control devices. */
    case scrollWheel

    case tabletPointer

    case tabletProximity

    case otherMouseDown

    case otherMouseUp

    case otherMouseDragged


    /* Out of band event types. These are delivered to the event tap callback
       to notify it of unusual conditions that disable the event tap. */
    case tapDisabledByTimeout

    case tapDisabledByUserInput
}

长按事件

有人可能会留意到上述没有长按事件

长按事件就是 .leftMouseDown, 达到你需要长按的时间后,再触发.leftMouseUp

扩展

一般情况你做好一系列点击组合后,是用快捷键触发开始的。那么MacOS中如何监听系统快捷键呢或者键盘事件?

添加系统键盘监听

NSEvent.addGlobalMonitorForEvents(matching: .keyDown, handler: {
    [unowned self] event in
    // num1:18, num2:19
    if event.keyCode == 18 {
        // self.startAction()
    }
})

分类: 未分类 标签:

Unity3d开发(十八) 监听编辑器状态改变,制定自定义回调

2016年11月24日 没有评论
Unity3d开发(十八) 监听编辑器状态改变,制定自定义回调



做编辑器插件时,我总是想要拿到监听编辑器的状态变化。比如在打开编辑器开始运行自己的服务。这时就需要用户打开编辑器的事件。再比如我希望在游戏退出运行模式之前,把一些编辑的东西缓存出来,然后对这些数据做自动化处理,那么我就需要退出运行模式的事件。诸如此类吧。

另一方面,我希望用观察者模式,并且能自动化注册。因为我注意到,导入资源时的AssetImporter回调就是这样做的。用户只需要实现一个接口,就可以收到回调。极大的简化了扩展流程。编辑器代码又不必考虑效率问题,借助C#的反射,可以很容易的实现这种功能。

概述

整套框架的启动核心是属性InitializeOnLoad。当Unity3d运行或启动时,会重新加载有脚本。当使用这个宏时,编辑器会自动将被标注的类实例化到内存中。因此我们可以利用这个特性,在它的构造函数中拉起我们整个服务。 这里有个小技巧。在启动Unity编辑器的情况下,如果在构造函数中创建对象,会被其他清除函数干掉。我认为是脚本初始化顺序,或是场景切换引起的,具体原因得问Unity了。为了解决这个问题,我借助了update函数,跳了一帧执行应有的逻辑。

自动注册是借助C#的反射,通过GetAssembliesGetTypes获取到所有的类,然后创建出对应的实例。

包装

这个类我觉得有个特别适合的名字——NightWatch。如果你没看过冰与火之歌,可能理解这个框架还算有点难度。总的说来,这个框架讲述了一个少年加入守夜人队伍,并去长城之外战斗的故事…

  Unity3d开发(十八) 监听编辑器状态改变,制定自定义回调

实现

接口类如下:

public interface ICrow
{
    /// <summary>
    /// Join the Nights Watch 
    /// </summary>
    void Enroll();

    /// <summary>
    /// Before to Enter Wild
    /// </summary>
    void PrepareForBattle();

    /// <summary>
    /// To the Weirwood outside the wall
    /// </summary>
    void FaceWeirwood();

    /// <summary>
    /// Back To the Castle Black
    /// </summary>
    void OpenTheGate();

    /// <summary>
    /// Tell Vow to the Old God
    /// </summary>
    void Vow();
}

实例类如下:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;

[InitializeOnLoad]
public class NightsWatch
{
    #region Public Attributes

    #endregion

    #region Private Attributes
    private static List<ICrow> m_crows = new List<ICrow>();
    #endregion

    #region Public Methods

    static NightsWatch()
    {
        if (!EditorApplication.isPlayingOrWillChangePlaymode)
        {
            EditorApplication.update += WelcomeToCastleBlack;
        }
        else 
        {
            EditorApplication.update += BeyondTheWall;
        }
    }

    static void WelcomeToCastleBlack()
    {
        EditorApplication.update -= WelcomeToCastleBlack;

        //Debug.Log("Welcome To castle black");
        m_crows.Clear();
        var crows = GetAllImplementTypes<ICrow>(System.AppDomain.CurrentDomain);
        foreach (var eachCrow in crows)
        {
            eachCrow.Enroll();
            m_crows.Add(eachCrow);
        }

        EditorApplication.update += WaitForWild;
    }

    static void WaitForWild()
    {
        if (EditorApplication.isPlayingOrWillChangePlaymode)
        {
            foreach (var eachCrow in m_crows)
            {
                eachCrow.PrepareForBattle();
            }
            EditorApplication.update -= WaitForWild;
        }
    }

    static void BeyondTheWall()
    {
        EditorApplication.update -= BeyondTheWall;

        //Debug.Log("Welcome To The Wild");
        m_crows.Clear();
        var crows = GetAllImplementTypes<ICrow>(System.AppDomain.CurrentDomain);
        foreach (var eachCrow in crows)
        {
            eachCrow.FaceWeirwood();
            m_crows.Add(eachCrow);
        }

        EditorApplication.update += WaitForCrowReturn;
    }
    
    static void WaitForCrowReturn()
    {
        if (!EditorApplication.isPlayingOrWillChangePlaymode )
        {
            //Debug.Log("Open the Door");
            EditorApplication.update -= WaitForCrowReturn;
            foreach (var eachCrow in m_crows)
            {
                eachCrow.OpenTheGate();
            }
            EditorApplication.update += WelcomeToCastleBlack;
        }
    }

    public static void CrowsVow()
    {
        foreach (var eachCrow in m_crows)
        {
            eachCrow.Vow();
        }
    }

    [MenuItem("Land/CastleBlack")]
    public static void MakeVow()
    {
        NightsWatch.CrowsVow();
    }
    #endregion

    #region Override Methods

    #endregion

    #region Private Methods
    public static T[] GetAllImplementTypes<T>(System.AppDomain aAppDomain) where T : class
    {
        var result = new List<T>();
        var assemblies = aAppDomain.GetAssemblies();
        foreach (var assembly in assemblies)
        {
            var types = assembly.GetTypes();
            foreach (var type in types)
            {
                if (typeof(T).IsAssignableFrom(type))
                {
                    if (!type.IsAbstract)
                    {
                        var tar = assembly.CreateInstance(type.FullName) as T;
                        result.Add(tar);
                    }
                }
            }
        }
        return result.ToArray();
    }
    #endregion
}

简单解释一下,所有的接口都是按照冰与火之歌中的剧情定义。当在编辑状态下时,会创建对应的实例类,并调用Enroll函数,这相当于Jon刚刚进入CastleBlack。当点击Play运行时,会先调用PrepareForBattle,相当于在城堡中准备出征。当游戏开始运行时,会调用FaceToWeirWood,这里对应的是城外那颗鱼梁木,一般出征之前都是要去祈祷一下。然后当游戏运行结束时,会调用OpenTheGate,对应出征回来,在长城下面喊门。然后有个Vow接口,这个是用来点名的,城堡里的乌鸦都要列队答“道”。

使用

新建两个实例: 一个是JonSnow:

  Unity3d开发(十八) 监听编辑器状态改变,制定自定义回调

public class JonSnow :  ICrow
{
    public void Enroll()
    {
        Debug.Log(this + " join the NightWatch!");
    }

    public void PrepareForBattle()
    {
        Debug.Log(this + " follow your lead!");
    }

    public void FaceWeirwood()
    {
        Debug.Log("I'm the wolf in the north");
    }

    public void OpenTheGate()
    {
        Debug.Log(this + " request enter Castle Black");
    }

    public void Vow()
    {
        Debug.Log(this + " For The Watch");
    }
}

一个是Samwell: 

Unity3d开发(十八) 监听编辑器状态改变,制定自定义回调

public class Samwell :  ICrow
{
    public void Enroll()
    {
        Debug.Log(this + " I came form Lord Randyll Tarly,and I even his oldest son ...");
    }

    public void PrepareForBattle()
    {
        Debug.Log(this + " is not ready yet...");
    }

    public void FaceWeirwood()
    {
        Debug.Log("I'm a useless warrior,but may be ... helpful");
    }

    public void OpenTheGate()
    {
        Debug.Log(this + " also want enter");
    }

    public void Vow()
    {
        Debug.Log(this + " For The ... alive");
    }
}

测试

当写好代码编译完成时,就能在输出中看到他俩到长城去报道了。点击运行程序,关闭运行程序,会分别有日志输出,效果如下:

Unity3d开发(十八) 监听编辑器状态改变,制定自定义回调

其中红线是点击Play操作,绿线是停止Unity运行的操作,红线以上的日志是打开unity或重新编译时输出的。一切按照预期实现,收工。

如果你觉得这篇文章对你有帮助,可以顺手点个,不但不会喜当爹,还能让更多人能看到它… Unity3d开发(十八) 监听编辑器状态改变,制定自定义回调

分类: 未分类 标签:

Swift下多个Storyboard的项目结构

2016年11月14日 没有评论

我是个比较喜欢用storyboard和xib的人。我个人的习惯就是,能用storyboard的一定不用代码手工撸。当然自己业余个人写的项目,基本上一个storyboard就搞定了。但涉及到多人合作下时候,一个storyboard还是挺蛋疼的,冲突难解决,打开storyboard极容易出现修改。结构大的时候打开还卡(也可能是我电脑太屌丝啦。。。)

下面介绍下我使用多个storyboard的习惯,仅供参看,不一定是最好的方案。

一、storyboard结构

Swift下多个Storyboard的项目结构

  1. 默认的Main.storyboard下我只有一个UITabBarController,如果你建立的是TabItem的模板项目,这个应该是默认的。注意,没有tabItem。
  2. 给对应的每个TabBarItem建立对应的storyboard,比如我建立的MyTabItemOne.storyboard和MyTabItemTwo.storyboard。
  3. MyTabItemOne.storyboard下我放的是一个UINavigationController作为初始Controller。
  4. MyTabItemTwo.storyboard类似

二、代码层面

自定义UITabBarController和UINavigationController。将Main.storyboard下的UITabBarController改为自定义的SPTabBarController.swift。将MyTabItemOne(Two).storyboard下的UINavigationController改为自定义的SPNavigationController.swift

SPTabBarController.swift

//
//  SPTabBarController.swift
//  SampleProject
//
//  Created by LiuYanghui on 2016/11/14.
//  Copyright © 2016年 LiuYanghui. All rights reserved.
//

import UIKit

class SPTabBarController: UITabBarController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        // 添加所有子控制器
        addChildViewControllers()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    // MARK: - private method

    /// 添加所有子控制器
    private func addChildViewControllers() {
        setChildrenController(title: "One", image: UIImage(), selectedImage: UIImage(), storyboard: UIStoryboard(name: "MyTabItemOne", bundle: nil))

        setChildrenController(title: "Two", image: UIImage(), selectedImage: UIImage(), storyboard: UIStoryboard(name: "MyTabItemTwo", bundle: nil))

    }

    /// 添加一个子控制器
    private func setChildrenController(title:String, image:UIImage,selectedImage:UIImage, storyboard:UIStoryboard) {
        let controller = storyboard.instantiateInitialViewController()!
        controller.tabBarItem.title = title
        controller.tabBarItem.image = image
        controller.tabBarItem.selectedImage = selectedImage
        addChildViewController(controller)
    }
}

SPNavigationController.swift

//
//  SPNavigationController.swift
//  SampleProject
//
//  Created by LiuYanghui on 2016/11/14.
//  Copyright © 2016年 LiuYanghui. All rights reserved.
//

import UIKit

class SPNavigationController: UINavigationController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        self.interactivePopGestureRecognizer?.delegate = nil
    }

    override func pushViewController(_ viewController: UIViewController, animated: Bool) {
        if viewControllers.count > 0 {
            //隐藏tabbar
            viewController.hidesBottomBarWhenPushed = true

            viewController.navigationItem.leftBarButtonItem = setBackBarButtonItem()
        }
        super.pushViewController(viewController, animated: animated)
    }

    func back(){
        self.popViewController(animated: true)
    }

    // MARK: - Private method
    private func setBackBarButtonItem() -> UIBarButtonItem{
        let button = UIButton(type: .custom)
        button.setImage(UIImage(named: "navigationButtonReturn"), for: .normal)
        button.setImage(UIImage(named: "navigationButtonReturnClick"), for: .highlighted)
        button.setTitle("返回", for: .normal)
        button.setTitleColor(UIColor(red: 81/255, green: 81/255, blue: 81/255, alpha: 1), for: .normal)
        button.addTarget(self, action: #selector(SPNavigationController.back), for: .touchUpInside)
        button.frame.size = CGSize(width: 100, height: 30)
        // button 里的内容左对齐
        button.contentHorizontalAlignment = .left
        button.contentEdgeInsets = UIEdgeInsets(top: 0, left: -10, bottom: 0, right: 0)

        return UIBarButtonItem(customView: button)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

Xcode工程
Swift下多个Storyboard的项目结构

Sample Project Github

分类: 未分类 标签: