3dMax 制作动画技巧总结

2017年1月11日 没有评论
3dMax 制作动画技巧总结



基本操作

  • 打开默认动画面板:自定义->显示UI->显示轨迹栏
  • 调整帧的密度:Ctrl + Alt + 鼠标右键
  • 切换线框和实体显示: F3 。增加线框 F4
  • 多选子物体:选中多个根节点后 ctrl+PangDown
  • 合并多个Bip动作:动作->Biped应用程序->混合器->右键轨道->添加源->从文件导入->选择对应的Bip->右键界面中的骨骼名称->计算合成->将合成复制到Biped

Biped操作

  • 创建:Inspector中创建->系统->Biped
  • 修改:选中骨骼->Inspector中运动->Biped体型模式->结构
  • 添加额外骨骼:选中骨骼->Inspector中运动->Biped体型模式->结构->XTra
  • 选择子骨骼:双击父骨骼
  • 多Biped的模型需要创建链接。要注意体型模式下链接功能是失效的,因此设置链接需要切换体型模式。

动作操作

  • 镜像复制:运动->复制/粘贴->复制收集->姿态->复制姿态->向对面镜像粘贴
  • 通过IK添加父层关系:点击添加滑动关键帧来激活IK->选择IK对象指定父物体。
  • 脱手:将属性中的位置空间和旋转空间改为世界
  • 整体修改:使用层功能,进行叠加。如果需要限定范围修改,则应借助捕捉功能,抓取下面层的信息进行锁帧。
  • 加位移与去位移:加位移使用层,去位移使用运动->Biped->模式与显示->原地模式
  • 动作混合:在混合器中加入动作轨->载入动画片段->设置过滤骨骼。
  • 模拟定帧:添加时间扭曲->在动画上创建分割线->拖动分割线上部分调整缩放。
  • 动作序列:Biped->运动流模式->运动流->显示图形->创建多个剪辑->定义脚本->按顺序点击
  • 多人运动:复制一堆对象->运动流模式->共享运动流->新建->添加Bip->在多个对象中播放运动流(绿箭头)->确定->显示图形->弹出界面中选择随机开始剪辑->右侧创建随机运动->去掉创建统一运动->创建共享该运动流的所有Biped的运动。
  • 对于四元数的自动平滑如果有接受不了的效果,可以使用四元数/Euler->Euler来切换动画类型。按住Shift可以改单方向的键

流程

模型自检

  1. 模型检查:归为零点
  2. 顶点缝合:选中模型->修改->顶点->焊接->设置参数为0.01。之前与之后的数字应该完全一样。
  3. 加入ResetTransform: 实用工具->重置变换。修改面板中应出现x变换,右键点击->塌陷。

绑骨

  • 隐藏部件: 修改->多边形->选择相关面片->隐藏选定对象
  • 打开体型模式,关闭自动关键帧。选中模型,右键冻结模型

加骨

修改->添加修改器(编辑多边形)->选择“元素”->附加->选择骨骼点

蒙皮

  • 选中对应的模型->编辑修改器中添加蒙皮->在骨骼中选择添加->添加对应骨骼->更改封套范围进行权重设置
  • 封套长期显示:封套属性->封套可见性
  • 局部复制:选择部分封套->封套属性->复制/粘贴
  • 细修:封套->参数->(选择)勾选顶点,可在下面手动修改权重,也可以打开权重工具界面详细设置

蒙皮后调缩放

  1. 先用Box测量身高算出比例。点击实例应用->配置按钮集->蒙皮实用程序拖动到右侧。
  2. 点击上方层级管理器,创建一个新的层。在新层中点击蒙皮实用工具中的将蒙皮数据提取到网格。
  3. 创建一个虚拟体,将要缩放的模型链接到虚拟体上,然后可以整体缩放住模型和配件。Biped模型可以单独调整高度缩放。
  4. 为缩放后的模型创建蒙皮,并重新绑骨。在场景中选中缩放过的模型和数据模型,点击从网格导入到蒙皮数据,点击按名称匹配,点确定。

如果你觉得这篇文章对你有帮助,可以顺手点个,不但不会喜当爹,还能让更多人能看到它… 3dMax 制作动画技巧总结

分类: 未分类 标签:

Scroll Segmented Control(Swift)

2017年1月10日 没有评论

今天用了一个github上一个比较好用的Segmented Control但是发现不是我要效果,我需要支持scrollView。当栏目数量超过一屏幕,需要能够滑动。

由于联系作者没有回复,我就自己在其基础上增加了下scrollView的支持。

Scroll Segmented Control(Swift)

代码比较简单,直接在UIControl下写的。

其中有一个比较有意思的地方,IndicatorView下面放了一个titleMaskView作为mask。用来遮罩选用的titles标签。已达到过渡效果。

源代码:

//
//  SwiftySegmentedControl.swift
//  SwiftySegmentedControl
//
//  Created by LiuYanghui on 2017/1/10.
//  Copyright © 2017年 Yanghui.Liu. All rights reserved.
//

import UIKit

// MARK: - SwiftySegmentedControl
@IBDesignable open class SwiftySegmentedControl: UIControl {
    // MARK: IndicatorView
    fileprivate class IndicatorView: UIView {
        // MARK: Properties
        fileprivate let titleMaskView = UIView()
        fileprivate let line = UIView()
        fileprivate let lineHeight: CGFloat = 2.0
        fileprivate var cornerRadius: CGFloat = 0 {
            didSet {
                layer.cornerRadius = cornerRadius
                titleMaskView.layer.cornerRadius = cornerRadius
            }
        }
        override open var frame: CGRect {
            didSet {
                titleMaskView.frame = frame
                let lineFrame = CGRect(x: 0, y: frame.size.height - lineHeight, width: frame.size.width, height: lineHeight)
                line.frame = lineFrame
            }
        }

        open var lineColor = UIColor.clear {
            didSet {
                line.backgroundColor = lineColor
            }
        }

        // MARK: Lifecycle
        init() {
            super.init(frame: CGRect.zero)
            finishInit()
        }
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            finishInit()
        }
        fileprivate func finishInit() {
            layer.masksToBounds = true
            titleMaskView.backgroundColor = UIColor.black
            addSubview(line)
        }

        override open func layoutSubviews() {
            super.layoutSubviews()

        }
    }

    // MARK: Constants
    fileprivate struct Animation {
        fileprivate static let withBounceDuration: TimeInterval = 0.3
        fileprivate static let springDamping: CGFloat = 0.75
        fileprivate static let withoutBounceDuration: TimeInterval = 0.2
    }
    fileprivate struct Color {
        fileprivate static let background: UIColor = UIColor.white
        fileprivate static let title: UIColor = UIColor.black
        fileprivate static let indicatorViewBackground: UIColor = UIColor.black
        fileprivate static let selectedTitle: UIColor = UIColor.white
    }

    // MARK: Error handling
    public enum IndexError: Error {
        case indexBeyondBounds(UInt)
    }

    // MARK: Properties
    /// The selected index
    public fileprivate(set) var index: UInt
    /// The titles / options available for selection
    public var titles: [String] {
        get {
            let titleLabels = titleLabelsView.subviews as! [UILabel]
            return titleLabels.map { $0.text! }
        }
        set {
            guard newValue.count > 1 else {
                return
            }
            let labels: [(UILabel, UILabel)] = newValue.map {
                (string) -> (UILabel, UILabel) in

                let titleLabel = UILabel()
                titleLabel.textColor = titleColor
                titleLabel.text = string
                titleLabel.lineBreakMode = .byTruncatingTail
                titleLabel.textAlignment = .center
                titleLabel.font = titleFont
                titleLabel.layer.borderWidth = titleBorderWidth
                titleLabel.layer.borderColor = titleBorderColor
                titleLabel.layer.cornerRadius = indicatorView.cornerRadius

                let selectedTitleLabel = UILabel()
                selectedTitleLabel.textColor = selectedTitleColor
                selectedTitleLabel.text = string
                selectedTitleLabel.lineBreakMode = .byTruncatingTail
                selectedTitleLabel.textAlignment = .center
                selectedTitleLabel.font = selectedTitleFont

                return (titleLabel, selectedTitleLabel)
            }

            titleLabelsView.subviews.forEach({ $0.removeFromSuperview() })
            selectedTitleLabelsView.subviews.forEach({ $0.removeFromSuperview() })

            for (inactiveLabel, activeLabel) in labels {
                titleLabelsView.addSubview(inactiveLabel)
                selectedTitleLabelsView.addSubview(activeLabel)
            }

            setNeedsLayout()
        }
    }
    /// Whether the indicator should bounce when selecting a new index. Defaults to true
    public var bouncesOnChange = true
    /// Whether the the control should always send the .ValueChanged event, regardless of the index remaining unchanged after interaction. Defaults to false
    public var alwaysAnnouncesValue = false
    /// Whether to send the .ValueChanged event immediately or wait for animations to complete. Defaults to true
    public var announcesValueImmediately = true
    /// Whether the the control should ignore pan gestures. Defaults to false
    public var panningDisabled = false
    /// The control's and indicator's corner radii
    @IBInspectable public var cornerRadius: CGFloat {
        get {
            return layer.cornerRadius
        }
        set {
            layer.cornerRadius = newValue
            indicatorView.cornerRadius = newValue - indicatorViewInset
            titleLabels.forEach { $0.layer.cornerRadius = indicatorView.cornerRadius }
        }
    }
    /// The indicator view's background color
    @IBInspectable public var indicatorViewBackgroundColor: UIColor? {
        get {
            return indicatorView.backgroundColor
        }
        set {
            indicatorView.backgroundColor = newValue
        }
    }
    /// Margin spacing between titles. Default to 33.
    @IBInspectable public var marginSpace: CGFloat = 33 {
        didSet { setNeedsLayout() }
    }
    /// The indicator view's inset. Defaults to 2.0
    @IBInspectable public var indicatorViewInset: CGFloat = 2.0 {
        didSet { setNeedsLayout() }
    }
    /// The indicator view's border width
    public var indicatorViewBorderWidth: CGFloat {
        get {
            return indicatorView.layer.borderWidth
        }
        set {
            indicatorView.layer.borderWidth = newValue
        }
    }
    /// The indicator view's border width
    public var indicatorViewBorderColor: CGColor? {
        get {
            return indicatorView.layer.borderColor
        }
        set {
            indicatorView.layer.borderColor = newValue
        }
    }
    /// The indicator view's line color
    public var indicatorViewLineColor: UIColor {
        get {
            return indicatorView.lineColor
        }
        set {
            indicatorView.lineColor = newValue
        }
    }
    /// The text color of the non-selected titles / options
    @IBInspectable public var titleColor: UIColor  {
        didSet {
            titleLabels.forEach { $0.textColor = titleColor }
        }
    }
    /// The text color of the selected title / option
    @IBInspectable public var selectedTitleColor: UIColor {
        didSet {
            selectedTitleLabels.forEach { $0.textColor = selectedTitleColor }
        }
    }
    /// The titles' font
    public var titleFont: UIFont = UILabel().font {
        didSet {
            titleLabels.forEach { $0.font = titleFont }
        }
    }
    /// The selected title's font
    public var selectedTitleFont: UIFont = UILabel().font {
        didSet {
            selectedTitleLabels.forEach { $0.font = selectedTitleFont }
        }
    }
    /// The titles' border width
    public var titleBorderWidth: CGFloat = 0.0 {
        didSet {
            titleLabels.forEach { $0.layer.borderWidth = titleBorderWidth }
        }
    }
    /// The titles' border color
    public var titleBorderColor: CGColor = UIColor.clear.cgColor {
        didSet {
            titleLabels.forEach { $0.layer.borderColor = titleBorderColor }
        }
    }

    // MARK: - Private properties
    fileprivate let contentScrollView: UIScrollView = {
        let scrollView = UIScrollView()
        scrollView.showsVerticalScrollIndicator = false
        scrollView.showsHorizontalScrollIndicator = false
        return scrollView
    }()
    fileprivate let titleLabelsView = UIView()
    fileprivate let selectedTitleLabelsView = UIView()
    fileprivate let indicatorView = IndicatorView()
    fileprivate var initialIndicatorViewFrame: CGRect?

    fileprivate var tapGestureRecognizer: UITapGestureRecognizer!
    fileprivate var panGestureRecognizer: UIPanGestureRecognizer!

    fileprivate var width: CGFloat { return bounds.width }
    fileprivate var height: CGFloat { return bounds.height }
    fileprivate var titleLabelsCount: Int { return titleLabelsView.subviews.count }
    fileprivate var titleLabels: [UILabel] { return titleLabelsView.subviews as! [UILabel] }
    fileprivate var selectedTitleLabels: [UILabel] { return selectedTitleLabelsView.subviews as! [UILabel] }
    fileprivate var totalInsetSize: CGFloat { return indicatorViewInset * 2.0 }
    fileprivate lazy var defaultTitles: [String] = { return ["First", "Second"] }()
    fileprivate var titlesWidth: [CGFloat] {
        return titles.map {
            let statusLabelText: NSString = $0 as NSString
            let size = CGSize(width: width, height: height - totalInsetSize)
            let dic = NSDictionary(object: titleFont,
                                   forKey: NSFontAttributeName as NSCopying)
            let strSize = statusLabelText.boundingRect(with: size,
                                                       options: .usesLineFragmentOrigin,
                                                       attributes: dic as? [String : AnyObject],
                                                       context: nil).size
            return strSize.width
        }
    }

    // MARK: Lifecycle
    required public init?(coder aDecoder: NSCoder) {
        index = 0
        titleColor = Color.title
        selectedTitleColor = Color.selectedTitle
        super.init(coder: aDecoder)
        titles = defaultTitles
        finishInit()
    }
    public init(frame: CGRect,
                titles: [String],
                index: UInt,
                backgroundColor: UIColor,
                titleColor: UIColor,
                indicatorViewBackgroundColor: UIColor,
                selectedTitleColor: UIColor) {
        self.index = index
        self.titleColor = titleColor
        self.selectedTitleColor = selectedTitleColor
        super.init(frame: frame)
        self.titles = titles
        self.backgroundColor = backgroundColor
        self.indicatorViewBackgroundColor = indicatorViewBackgroundColor
        finishInit()
    }

    @available(*, deprecated, message: "Use init(frame:titles:index:backgroundColor:titleColor:indicatorViewBackgroundColor:selectedTitleColor:) instead.")
    convenience override public init(frame: CGRect) {
        self.init(frame: frame,
                  titles: ["First", "Second"],
                  index: 0,
                  backgroundColor: Color.background,
                  titleColor: Color.title,
                  indicatorViewBackgroundColor: Color.indicatorViewBackground,
                  selectedTitleColor: Color.selectedTitle)
    }

    @available(*, unavailable, message: "Use init(frame:titles:index:backgroundColor:titleColor:indicatorViewBackgroundColor:selectedTitleColor:) instead.")
    convenience init() {
        self.init(frame: CGRect.zero,
                  titles: ["First", "Second"],
                  index: 0,
                  backgroundColor: Color.background,
                  titleColor: Color.title,
                  indicatorViewBackgroundColor: Color.indicatorViewBackground,
                  selectedTitleColor: Color.selectedTitle)
    }


    fileprivate func finishInit() {
        layer.masksToBounds = true

        addSubview(contentScrollView)
        contentScrollView.addSubview(titleLabelsView)
        contentScrollView.addSubview(indicatorView)
        contentScrollView.addSubview(selectedTitleLabelsView)
        selectedTitleLabelsView.layer.mask = indicatorView.titleMaskView.layer

        tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(SwiftySegmentedControl.tapped(_:)))
        addGestureRecognizer(tapGestureRecognizer)

        panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(SwiftySegmentedControl.panned(_:)))
        panGestureRecognizer.delegate = self
        addGestureRecognizer(panGestureRecognizer)
    }
    override open func layoutSubviews() {
        super.layoutSubviews()
        guard titleLabelsCount > 1 else {
            return
        }

        contentScrollView.frame = bounds
        let allElementsWidth = titlesWidth.reduce(0, {$0 + $1}) + CGFloat(titleLabelsCount) * marginSpace
        contentScrollView.contentSize = CGSize(width: max(allElementsWidth, width), height: 0)

        titleLabelsView.frame = bounds
        selectedTitleLabelsView.frame = bounds

        indicatorView.frame = elementFrame(forIndex: index)

        for index in 0...titleLabelsCount-1 {
            let frame = elementFrame(forIndex: UInt(index))
            titleLabelsView.subviews[index].frame = frame
            selectedTitleLabelsView.subviews[index].frame = frame
        }
    }

    // MARK: Index Setting
    /*!
     Sets the control's index.

     - parameter index:    The new index
     - parameter animated: (Optional) Whether the change should be animated or not. Defaults to true.

     - throws: An error of type IndexBeyondBounds(UInt) is thrown if an index beyond the available indices is passed.
     */
    public func setIndex(_ index: UInt, animated: Bool = true) throws {
        guard titleLabels.indices.contains(Int(index)) else {
            throw IndexError.indexBeyondBounds(index)
        }
        let oldIndex = self.index
        self.index = index
        moveIndicatorViewToIndex(animated, shouldSendEvent: (self.index != oldIndex || alwaysAnnouncesValue))
        fixedScrollViewOffset(Int(self.index))
    }

    // MARK: Fixed ScrollView offset
    fileprivate func fixedScrollViewOffset(_ focusIndex: Int) {
        guard contentScrollView.contentSize.width > width else {
            return
        }

        let targetMidX = self.titleLabels[Int(self.index)].frame.midX
        let offsetX = contentScrollView.contentOffset.x
        let addOffsetX = targetMidX - offsetX - width / 2
        let newOffSetX = min(max(0, offsetX + addOffsetX), contentScrollView.contentSize.width - width)
        let point = CGPoint(x: newOffSetX, y: contentScrollView.contentOffset.y)
        contentScrollView.setContentOffset(point, animated: true)
    }

    // MARK: Animations
    fileprivate func moveIndicatorViewToIndex(_ animated: Bool, shouldSendEvent: Bool) {
        if animated {
            if shouldSendEvent && announcesValueImmediately {
                sendActions(for: .valueChanged)
            }
            UIView.animate(withDuration: bouncesOnChange ? Animation.withBounceDuration : Animation.withoutBounceDuration,
                           delay: 0.0,
                           usingSpringWithDamping: bouncesOnChange ? Animation.springDamping : 1.0,
                           initialSpringVelocity: 0.0,
                           options: [UIViewAnimationOptions.beginFromCurrentState, UIViewAnimationOptions.curveEaseOut],
                           animations: {
                            () -> Void in
                            self.moveIndicatorView()
            }, completion: { (finished) -> Void in
                if finished && shouldSendEvent && !self.announcesValueImmediately {
                    self.sendActions(for: .valueChanged)
                }
            })
        } else {
            moveIndicatorView()
            sendActions(for: .valueChanged)
        }
    }

    // MARK: Helpers
    fileprivate func elementFrame(forIndex index: UInt) -> CGRect {
        // 计算出label的宽度,label宽度 = (text宽度) + marginSpace
        // | <= 0.5 * marginSpace => text1 <= 0.5 * marginSpace => | <= 0.5 * marginSpace => text2 <= 0.5 * marginSpace => |
        // 如果总宽度小于bunds.width,则均分宽度 label宽度 = bunds.width / count
        let allElementsWidth = titlesWidth.reduce(0, {$0 + $1}) + CGFloat(titleLabelsCount) * marginSpace
        if allElementsWidth < width {
            let elementWidth = (width - totalInsetSize) / CGFloat(titleLabelsCount)
            return CGRect(x: CGFloat(index) * elementWidth + indicatorViewInset,
                          y: indicatorViewInset,
                          width: elementWidth,
                          height: height - totalInsetSize)
        } else {
            let titlesWidth = self.titlesWidth
            let frontTitlesWidth = titlesWidth.enumerated().reduce(CGFloat(0)) { (total, current) in
                return current.0 < Int(index) ? total + current.1 : total
            }
            let x = frontTitlesWidth + CGFloat(index) * marginSpace
            return CGRect(x: x,
                          y: indicatorViewInset,
                          width: titlesWidth[Int(index)] + marginSpace,
                          height: height - totalInsetSize)
        }
    }
    fileprivate func nearestIndex(toPoint point: CGPoint) -> UInt {
        let distances = titleLabels.map { abs(point.x - $0.center.x) }
        return UInt(distances.index(of: distances.min()!)!)
    }
    fileprivate func moveIndicatorView() {
        indicatorView.frame = titleLabels[Int(self.index)].frame
        layoutIfNeeded()
    }

    // MARK: Action handlers
    @objc fileprivate func tapped(_ gestureRecognizer: UITapGestureRecognizer!) {
        let location = gestureRecognizer.location(in: contentScrollView)
        try! setIndex(nearestIndex(toPoint: location))
    }
    @objc fileprivate func panned(_ gestureRecognizer: UIPanGestureRecognizer!) {
        guard !panningDisabled else {
            return
        }

        switch gestureRecognizer.state {
        case .began:
            initialIndicatorViewFrame = indicatorView.frame
        case .changed:
            var frame = initialIndicatorViewFrame!
            frame.origin.x += gestureRecognizer.translation(in: self).x
            frame.origin.x = max(min(frame.origin.x, bounds.width - indicatorViewInset - frame.width), indicatorViewInset)
            indicatorView.frame = frame
        case .ended, .failed, .cancelled:
            try! setIndex(nearestIndex(toPoint: indicatorView.center))
        default: break
        }
    }
}

// MARK: - UIGestureRecognizerDelegate
extension SwiftySegmentedControl: UIGestureRecognizerDelegate {
    override open func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        if gestureRecognizer == panGestureRecognizer {
            return indicatorView.frame.contains(gestureRecognizer.location(in: contentScrollView))
        }
        return super.gestureRecognizerShouldBegin(gestureRecognizer)
    }
}

使用方式

fileprivate func setupControl() {
        let viewSegmentedControl = SwiftySegmentedControl(
            frame: CGRect(x: 0.0, y: 430.0, width: view.bounds.width, height: 50.0),
            titles: ["All", "New", "Pictures", "One", "Two", "Three", "Four", "Five", "Six", "Artists", "Albums", "Recent"],
            index: 1,
            backgroundColor: UIColor(red:0.11, green:0.12, blue:0.13, alpha:1.00),
            titleColor: .white,
            indicatorViewBackgroundColor: UIColor(red:0.11, green:0.12, blue:0.13, alpha:1.00),
            selectedTitleColor: UIColor(red:0.97, green:0.00, blue:0.24, alpha:1.00))
        viewSegmentedControl.autoresizingMask = [.flexibleWidth]
        viewSegmentedControl.indicatorViewInset = 0
        viewSegmentedControl.cornerRadius = 0.0
        viewSegmentedControl.titleFont = UIFont(name: "HelveticaNeue", size: 16.0)!
        viewSegmentedControl.selectedTitleFont = UIFont(name: "HelveticaNeue", size: 16.0)!
        viewSegmentedControl.bouncesOnChange = false
        // 是否禁止拖动选择,注意,这里有个问题我还没改,如果titles长度超过1屏幕,或者说,你想使用下划线,建议禁止拖动选择。
        viewSegmentedControl.panningDisabled = true 
        // 下划线颜色。默认透明
        viewSegmentedControl.indicatorViewLineColor = UIColor.red
        view.addSubview(viewSegmentedControl)
    }

Github: SwiftySegmentedControl

分类: 未分类 标签:

临时关闭Mac SIP系统完整性保护机制

2016年12月27日 没有评论

今天pip安装个python组件,各种试都是

$ pip install -Ur requirements.txt
error: [Errno 13] Permission denied: '/Users/liuyanghui/Library/Python/2.7'

基于用户权限安装也不行

$ pip install -Ur requirements.txt --user -U

好吧,暂时没找到好办法,关闭SIP吧。突然发现关闭SIP的命令忘记了,哎。。。

所以现在写下了这篇记下来。。。

关闭SIP

  • 重启电脑,按住Command+R(直到出现苹果标志)进入Recovery Mode(恢复模式)
  • 左上角菜单里找到实用工具 -> 终端
  • 输入$ csrutil disable回车
  • 重启Mac即可
  • 如果想重新启动SIP机制重复上述步骤改用$ csrutil enable即可

查看SIP状态:

$ csrutil status

System Integrity Protection status: enabled.

分类: 未分类 标签:

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

分类: 未分类 标签:

C# 的常见误区整理

2016年10月11日 没有评论
C# 的常见误区整理



本文为《编写高质量的代码:改善C#程序的157个建议》中整理出来对我有用的知识,算是读书笔记吧。所以并不全,有兴趣了解详情的童鞋可以去买纸质书一探究竟。

  1. 当容易出错时,使用TryParse会更高效。
  2. 使用?可以对一个值类型赋空值,这种算符对应的基类为Nullable<T>。使用??可以对其是否为空进行简化判断,因此可以这样写:
int? i = 123;
int j = i ?? 0;

//等价于
int? i = 123;
int j;
if(i.HasValue)
{
    j = i.Value;
}
else 
{
    j = 0;
}
  1. const是编译期常量,所以效率比readonly高。
  2. 不要手动为枚举赋值,因为它的值是可重复的。
  3. 让一个类继承IComparable<T>接口,当把它的实例放到集合中时,直接调用Sort方法,就可以按规则排序。虽然我还是喜欢用Lambda单写排序…
  4. 深拷贝不一定非要一个个手动复制变量,也可以采用内存复制的形式:
public Employee DeepClone()
{
    using(Stream objectStream = new MemoryStream())
    {
        IFormatter formatter = new BinaryForamtter();
        formatter.Serialize(objectStream, this);
        objectStream.Seek(0,SeekOrigin.Begin);
        return formatter.Deserialize(objectStream) as Employee;
    }
}
  1. C#集合的分类:
    C# 的常见误区整理
  2. System.Collections.Concurrent命名空间下有线程安全的集合类。
  3. 为了防止公开变量的写入,可以为其添加一个带参数的构造方法,然后把它设为对外只读。
  4. 一般说来,使用Action、Func等预定义好的委托就足够了,没必要自己定义委托。还有些其他用途的,比如EventHandler、AsyncCallback等会作为一些接口参数出现,但本质是一样的。可以在MSDN页面上搜委托,然后在左侧的导航列表中就能看到所有的类型了。
  5. 所有的委托都应封装成事件,大多数情况是不需要自定义委托类型的。例如下面的代码,就是使用了EventHandler:
class FileUploadedEventArgs : EventArgs
{
    public int FileProgress {get;set;}
}

class FileUploader
{
    public  event EventHandler<FileUploadedEventArgs> FileUploaded;

    public void Upload()
    {
        FileUploadedEventArgs e = new 
                        FileUploadedEventArgs() { FileProgress = 100};
        while(e.FileProgress > 0)
        {
            //Up Load File
            e.FileProgress--;
            if(FileUploaded != null)
            {
                FileUploaded(this,e);
            }
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        FileUploader fl = new FileUploader();
        fl.FileUploaded +=Progress;
        fl.Upload();
    }

    static void Progress(object sender,FileUploadedEventArgs e)
    {
        Console.writeLine(e.FileProgress);
    }

}
  1. 委托命名建议采用动词或形容词。对于使用+=注册的事件处理器,使用"事件变量所属对象+下划线+事件变量名"来命名,委托或委托中间的回调应按照”委托变量所属对象+On+事件来源+委托变量名”为命名规则,例如:
public Main()
{
    Button btn = new Button();
    btn.Click+= new EventHandler(button_Click);
}

void button_Click(object sender,EventArgs e)
{
}

void SalaryOnEmployeeChanged(object sender,EventArgs e)
{

}

如果你觉得这篇文章对你有帮助,可以顺手点个,不但不会喜当爹,还能让更多人能看到它… C# 的常见误区整理

分类: 未分类 标签:

Unity3d开发(十七)UGUI 事件体系分析

2016年10月10日 没有评论
Unity3d开发(十七)UGUI 事件体系分析



很多Unity3D项目都使用了UGUI,但并不是所有人都研究过它的内部结构。由于准备定制自己的UI,不弄明白它内部的机制有点说不过去呢。这篇文章主要分析一下它的事件体系结构,以及点击事件的逻辑流程。本篇文章依托于UGUI的源码,如何获取UGUI的源码可以参考Unity3d(十六) 重写UGUI组件

事件体系

事件体系总体上说由四部分组成,分别是:监测器,派发器,采集器,响应器。

监测器指的是EventSystem类,它重写了MonoBehavior的Update方法,会在每一帧更新挂载在同一个GameObject上的BaseInputModule组件状态,并判断是否应该激活Module,如果是,则去调用各个Module的Process。

派发器指的就是BaseInputModule,最常用的是它的子类StandaloneInputMoudle。它完成了实际的事件生成。包括且不限于:事件类型的确定,事件数据的收集,派发对象的过滤。其中对派发对象的获取需要借助采集器,但需要通过监测器来获取,这种设计可以带来效率上的优势,虽然实现时并没有相关的代码。

采集器是指BaseRaycaster,在UGUI中使用的是其子类GraphicRaycaster。当事件发生时,会由Module请求一个射线点触,返回所有能点到的物体并返回,交由派发器进行过滤。它有一个内部的管理类RaycasterManager,用来做链接采集器和监测器的单向桥梁。

响应器是指IEventSystemHandler及其子类,例如最常用的IPointerClickHandler,它处理的是点击事件。通过ExecuteEvents类,可以将发生事件的对象上所有的响应器都获取到并调用其响应逻辑。以点击为例,事件最终会被派发到OnClick的代理上。完成逻辑的执行。

这四个模块大致的依赖关系如下: 

Unity3d开发(十七)UGUI 事件体系分析

类图

类有好多,但相比于流程要简单不少。类分两部分,一部分是功能类,一部分是编辑器类。

功能类按照上一节的事件体系可以清晰的找到重要的基类,UML图如下:

Unity3d开发(十七)UGUI 事件体系分析

这个图只是和点击相关的部分,当然还有很多其他的功能例如拖拽,滑动等等。但如果能找到这些重点,其他分支逻辑相信大家分分钟都能看懂了。

另一类是编辑器类,这个比较简单,我就没单独梳理类图,直接用VS自动生成的类图就够用了:

Unity3d开发(十七)UGUI 事件体系分析

你若是觉得这些编辑器类没什么用,那你一定是对编辑器代码理解还不深。UGUI的编辑器界面还算获得了广泛的好评,当需要自定义Inspector界面时,一些功能一定用得上,如果有空我也准备深入研究一下。

点击的逻辑流程

下面基于事件体系的划分,针对一次按钮点击梳理一下调用逻辑,流程图如下:

Unity3d开发(十七)UGUI 事件体系分析

总结

“动手改之前,要先弄明白别人的程序逻辑”。说起来很简单,但又有几个人能坚持做好呢?虽然我也是在外层包装了各种控件之后,才下决心重写UGUI Unity3d开发(十七)UGUI 事件体系分析 。 看到现在这种程度可真心花了不少时间,整理出来希望对大家有帮助 Unity3d开发(十七)UGUI 事件体系分析

如果你觉得这篇文章对你有帮助,可以顺手点个,不但不会喜当爹,还能让更多人能看到它… Unity3d开发(十七)UGUI 事件体系分析

分类: 未分类 标签:

Unity3d(十六) 重写UGUI组件

2016年9月27日 没有评论
Unity3d(十六) 重写UGUI组件



作为Unity3d新版本的UI系统,UGUI以其易用性,逐渐被多数团队所接纳。但随着应用的深入,团队中总有需要自定义一套UI系统的需求。所幸UGUI是一个开源项目,可以在Unity3d的官方的Bitbucket代码库中找到源码。有了源码当然我们可以采用一劳永逸的办法,重编DLL库,并替换掉Unity/Editor/Data/UnityExtensions/Unity/GUISystem/UnityEngine.UI.dll,实现重写。但我还是不太推荐这样,因为一旦如此做,就无法跟随Unity3d版本升级了。因此本文主要介绍如何通过子类实现重写.

整体思路

首先推荐去官网下载一份代码。总体上看来大概分为以下几个部分:

  • UnityEngine.UI/UI/Core/DefaultControls是创建具体控件的层级与属性设置。
  • UnityEditor.UI/UI/MenuOptions是具体创建控件的包装方法。
  • UnityEditor.UI/UIUnityEngine.UI/UI/Core这两部分是对应的控件实现。

因此整体上重写的思路为:

  1. 为每个控件包装一个子类
  2. 创建时更改创建的脚本设置
  3. 重定向点击菜单按钮的生成

更改文字组件

下面以Text为例,重写一个组件。首先要创建一个子类:

using UnityEngine;
using System.Collections;
using UnityEngine.UI;

public class MyText :  Text
{

}

弄个空的就好了。

然后更改MenuOption类,由于这个类是Inner的,没有办法重写。所以我就将源码复制了一份。由于Unity会优先编译DLL后编译代码,所以复写public static 的菜单注册函数也不会有问题。

最后创建一个DefaultControls的复写类,将源码中创建Text的逻辑复制出来。

public class MenuOptionOverwrite
{
    //...
    [MenuItem("GameObject/UI/Text", false, 2000)]
    static public void AddText(MenuCommand menuCommand)
    {
        GameObject go = DefaultControlsOverwrite.CreateText(GetStandardResources());
        PlaceUIElementRoot(go, menuCommand);
    }
    //...
}

public static class DefaultControlsOverwrite 
{
    //...
    public static GameObject CreateText(DefaultControls.Resources resources)
    {
        GameObject obj2 = CreateUIElementRoot("Text", s_ThickElementSize);
        Text lbl = obj2.AddComponent<MyText>();
        lbl.text = "New Text";
        SetDefaultTextValues(lbl);

        lbl.raycastTarget = false;
        return obj2;
    }
    //...
}

经过这样更改在Hierarchy中创建UI组件时,就会直接创建名为MyText的组件。至于直接把DefaultControls里面的代码复制出来,有利有弊,直接复制出来改起来比较方便,但如果Unity3d升级之后,代码更改可能会遗漏。反过来,先调用DefaultControls的接口,回头再将脚本换掉也可以,但这样就涉及到组件的赋值,写起来可能会特别傻 Unity3d(十六) 重写UGUI组件 其中利弊大家自己衡量吧

创建效果

Unity3d(十六) 重写UGUI组件

右键创建Text就直接是这个效果,而且可以自定义属性,比如这里我们就默认将Raycast勾掉啦

如果你觉得这篇文章对你有帮助,可以顺手点个,不但不会喜当爹,还能让更多人能看到它… Unity3d(十六) 重写UGUI组件

分类: 未分类 标签:

为你的MacOS App添加开机自启动(Swift)

2016年8月3日 没有评论

猴子原创,欢迎转载。转载请注明: 转载自Cocos2Der-CSDN,谢谢!
原文地址: http://blog.csdn.net/cocos2der/article/details/52104828

关于Mac下如何给自己App添加开机自启动功能,你可以了解下Mac Developer Library中的说明。

There are two ways to add a login item: using the Service Management framework, and using a shared file list

Login items installed using the Service Management framework are not visible in System Preferences and can only be removed by the application that installed them.

Login items installed using a shared file list are visible in System Preferences; users have direct control over them. If you use this API, your login item can be disabled by the user, so any other application that communicates with it it should have reasonable fallback behavior in case the login item is disabled.

可以看出,Apple推荐了两种方式:Service Management framework 和 shared file list。

这两种方式有差别:

  • 使用Service Management framework 在系统的登录项中是不可见的。只有卸载App才能移除登录项

  • 使用 shared file list 在系统的登录项中是可见的。用户可以直接在面板上控制他们。(If you use this API, your login item can be disabled by the user, so any other application that communicates with it it should have reasonable fallback behavior in case the login item is disabled.) 原文还有一句大意是指这个API有隐患,所以在OS X 10.10系统上 API被大量Deprecated

下面我主要介绍的是使用Service Management framework 的方式添加开机自启动。

一、创建主工程

新建一个MainApp的osx工程App
为你的MacOS App添加开机自启动(Swift)

二、添加自动启动Target

  • 我们需要注册一个Helper Target App用来作为开机自启动我们的MainApp,点击Targets下面的加号.
    为你的MacOS App添加开机自启动(Swift)

  • 添加一个新的OS X application。取名为MainAppHelper
    为你的MacOS App添加开机自启动(Swift)

三、设置配置属性

  • 删除MainAppHelper中的windows,让它没有可展示的Window。
    为你的MacOS App添加开机自启动(Swift)

  • 设置MainAppHelper的Info中Application is background only为YES

  • 设置MainAppHelper中Build Setting下skip install为YES
    为你的MacOS App添加开机自启动(Swift)

  • 在MainApp中添加CopyFile到Contents/Library/LoginItems
    为你的MacOS App添加开机自启动(Swift)

  • 在MainApp中设置Build Setting 下Strip Debug Symbols During Copy为NO, 这个是默认的为No

  • 分别开启MainApp和MainAppHelper的App Sandbox
    为你的MacOS App添加开机自启动(Swift)

四、添加启动代码

  • 请根据你自己的实际情况添加startupAppWhenLogin函数到你MainApp主工程中,程序运行后根据情况调用开启或者关闭自启动, 注意其中的BundleID改为你自己的,以及主App的名称。如果不确定名称是否是自己的工程名,可以直接右键自己的ipa,显示包内容,看下里面的文件夹你就明白了。
func startupAppWhenLogin(startup: Bool) {
        // 这里请填写你自己的Heler BundleID
        let launcherAppIdentifier = "com.cocos2dev.MainApp.MainAppHelper"

        // 开始注册/取消启动项
        SMLoginItemSetEnabled(launcherAppIdentifier, startup)

        var startedAtLogin = false
        for app in NSWorkspace.sharedWorkspace().runningApplications {
            if app.bundleIdentifier == launcherAppIdentifier {
                startedAtLogin = true
            }
        }

        if startedAtLogin {            NSDistributedNotificationCenter.defaultCenter().postNotificationName("killhelper", object: NSBundle.mainBundle().bundleIdentifier!)
        }
    }
  • 在MainAppHelper中,修改AppDelegate为如下代码:

import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
    func applicationDidFinishLaunching(aNotification: NSNotification) {
        // Insert code here to initialize your application

        let mainAppIdentifier = "com.cocos2dev.MainApp"
        let running           = NSWorkspace.sharedWorkspace().runningApplications
        var alreadyRunning    = false

        for app in running {
            if app.bundleIdentifier == mainAppIdentifier {
                alreadyRunning = true
                break
            }
        }

        if !alreadyRunning {
            NSDistributedNotificationCenter.defaultCenter().addObserver(self, selector: "terminate", name: "killhelper", object: mainAppIdentifier)

            let path = NSBundle.mainBundle().bundlePath as NSString
            var components = path.pathComponents
            components.removeLast()
            components.removeLast()
            components.removeLast()
            components.append("MacOS")
            components.append("MainApp") //main app name

            let newPath = NSString.pathWithComponents(components)
            NSWorkspace.sharedWorkspace().launchApplication(newPath)
        } else {
            self.terminate()
        }
    }

    func applicationWillTerminate(aNotification: NSNotification) {
        // Insert code here to tear down your application
    }

    func terminate() {
        //      NSLog("I'll be back!")
        NSApp.terminate(nil)
    }

}

以上就是一个自启动App的做法。当然如果你上架Mac Store,建议你不要默认就开启自启动,放到设置中,让用户自己选择开启/关闭。

参考博客
Mac Developer Library
First OS X tutorial: How to launch an OS X app at login?
在SandBox沙盒下实现程序的开机启动

分类: 未分类 标签: