sprite_sprites - CSDN
精华内容
参与话题
  • Sprite(精灵)全解

    千次阅读 2014-02-02 11:05:59
    Sprite(精灵)全解   精灵是游戏中十分重要的组成部分,随处可见,如:游戏背景、NPC、人物、道具等。在cocos2d-x引擎中,只要是用图片展示的,基本上需要使用精灵类。   1. 首先来了解一下跟精灵相关的几个类: (1...

    Sprite(精灵)全解

     

    精灵是游戏中十分重要的组成部分,随处可见,如:游戏背景、NPC、人物、道具等。在cocos2d-x引擎中,只要是用图片展示的,基本上需要使用精灵类。

     

    1. 首先来了解一下跟精灵相关的几个类:

    (1) Texture2D

    可以把它看成一个纹理,它是cocos2d-x渲染图形的重要参数,用来贴图,因为cocos2d-x使用openglES绘制2d图形的,它的尺寸是2的n次方。一般通过以下方式获得: 

    //cocos2d-x3.0beta的用法
    Texture2D * texture                                                   
    = Director::getInstance()->getTextureCache()->addImage("hero.png"); 

    (2) Sprite

    这个就是精灵类,是Node的子类,它的内部封装了Texture2D(纹理),可以通过下面几种方式初始化精灵对象。  

    static Sprite* create();
    static Sprite* create(const std::string& filename);
    //Rect表示图片的指定范围,即从图片的指定矩形区域裁剪
    static Sprite* create(const std::string& filename, const Rect& rect);
    //Texture2D表示精灵包含的图片,范围是整张图片
    static Sprite* createWithTexture(Texture2D *texture);
    
    //SpriteFrame表示精灵的某一帧,大多数情况下精灵本身的图片有多帧。它内部封装了Texture2D和Rect,可以从一个大图片取出一部分作为一帧。
    static Sprite* createWithSpriteFrame(SpriteFrame *pSpriteFrame);
    //SpriteFrameName表示帧的名字,根据帧名从内存中取出SpriteFrame
    static Sprite* createWithSpriteFrameName(const std::string& spriteFrameName);

    (3) TextureCache

    它相当于Texture2D的容器,是内存池,用来缓存Texture2D对象的。当调用它的addImage函数添加图片时,会先根据图片名称去内存中查找是否已存在,是则直接取出返回。

    如果需要一次加载多张图片的时候,可以先把图片加载到TextureCache中,这样使用图片的时候速度就会很快了。


    (4 SpriteFrameCache

    它是管理SpriteFrame的内存池,跟TextureCache功能一样,不过跟TextureCache不同的是,如果内存池中不存在要查找的帧,它会提示找不到,而不会去本地加载图片。SpriteFrameCache一般用来处理plist文件(这个文件指定了每个独立的精灵在这张“大图”里面的位置和大小),该文件对应一张包含多个精灵的大图,plist文件可以使用TexturePacker制作。如下图所示:


    创建精灵方法:利用帧缓存中的一帧的名称声称一个对象,适合于plist打包好的文件:

    SpriteFrameCache::getInstance()->addSpriteFramesWithFile("animations/grossini.plist", "animations/grossini.png");
    sprite* hero = sprite::createWithSpriteFrameName("grossini_dance_01.png");

    只要plist文件跟对应的png图片在同一目录下,且名字相同,则addSpriteFramesWithFile(“animations/ grossini.plist”, “animations / grossini.png”);

    可以改成addSpriteFramesWithFile(“animations/grossini.plist”);

     

    创建精灵方法:利用另外一帧生成一个精灵对象,适合于做帧动画使用。

    SpriteFrame* frame = SpriteFrame::create("icon.png", Rect(0, 0, 40, 30));
    Sprite* sprite = Sprite::createWithSpriteFrame(frame);
    

     (5) SpriteBatchNode

    它是批处理绘制精灵,主要是用来提高精灵的绘制效率的,需要绘制的精灵数量越多,效果越明显。因为cocos2d-x采用opengl es绘制图片的,opengl es绘制每个精灵都会执行:open-draw-close流程。而SpriteBatchNode是把多个精灵放到一个纹理上,绘制的时候直接统一绘制该texture,不需要单独绘制子节点,这样opengles绘制的时候变成了:open-draw()-draw()…-draw()-close(),节省了多次open-close的时间。注意:因为绘制的时候只open-close一次,所以SpriteBatchNode对象的所有子节点都必须和它是用同一个texture(同一张图片),类似下面这样的图片,4个贝壳都在同一纹理上:

     

    1.  直接使用Layer进行添加精灵,Layer上有几个精灵,那么底层就会绘制几次精灵;

    简单可以理解成底层绘制方式如下:

    for(int i= 0;i<100;i++){open-draw-close;}

    但是使用集合的话,Layer只需要对精灵集合进行一次渲染,

    简单可以理解成底层绘制方式如下:

    open-draw(100次绘制)-close

    从以上两种方式可以看出两者的区别了,第二种使用精灵集合省去了99次open和close的过程,从而达到优化作用;

    2.使用SpriteBathNode虽然能达到优化,但是要注意一点:

    初始化精灵集合SpriteBatchNode的时候会加载一张图片资源(或者pvr文件等),那么限制其精灵集合的子精灵都必须使用集合加载的这张图才行,否则会给出警告;

    3.使用SpriteBatchNode还要注意一点,因为精灵都存放在集合中,那么这个集合SpriteBatchNode中的节点(精灵)都将在同一个z轴上,同一深度上;

    一般使用TexturePacker工具都会将很多精灵图片或者动作帧放在一起打包成“.pvr.z”、”.plist”、“-hd.pvr.z”和”-hd.plist”的四个文件,其中两个-hd的是使用工具生成的打包资源的高清版本(940*480)使用的,这个不再强调了;


    那么肯定会有童鞋说,那么如果把这资源文件与SpriteBathNode结合使用岂不是更嗨皮,没错,可以的,加载的时候只需要将如下创建集合即可:


    创建精灵方法:SpriteBathNode与SpriteFrameCache结合使用。

    //加载图片资源
    SpriteFrameCache::getInstance()->addSpriteFramesWithFile("xx.plist");
    SpriteBatchNode aParenet = SpriteBatchNode::create("xx.pvr.z");
    this->addChild(aParenet);
    
    Sprite *pFather = Sprite::spriteWithSpriteFrameName("father.gif");
    pFather->setPosition(p(s.width / 2, s.height / 2));
    aParenet->addChild(pFather);
    


    展开全文
  • Pygame中的Sprite

    万次阅读 多人点赞 2018-07-21 13:11:25
    Sprite源代码 pygame中sprite模块的Sprite类是一个简单的基类,它的源代码如下: class Sprite(object): def __init__(self, *groups): self.__g = {} # The groups the sprite is in if groups: self.add(...

    Sprite源代码

    pygame中sprite模块的Sprite类是一个简单的基类,它的源代码如下:

    class Sprite(object):
        def __init__(self, *groups):
            self.__g = {} # The groups the sprite is in
            if groups:
                self.add(*groups)
    
        def add(self, *groups):
            has = self.__g.__contains__
            for group in groups:
                if hasattr(group, '_spritegroup'):
                    if not has(group):
                        group.add_internal(self)
                        self.add_internal(group)
                else:
                    self.add(*group)
    
        def remove(self, *groups):
            has = self.__g.__contains__
            for group in groups:
                if hasattr(group, '_spritegroup'):
                    if has(group):
                        group.remove_internal(self)
                        self.remove_internal(group)
                else:
                    self.remove(*group)
    
        def add_internal(self, group):
            self.__g[group] = 0
    
        def remove_internal(self, group):
            del self.__g[group]
    
        def update(self, *args):
            pass
    
        def kill(self):
            for c in self.__g:
                c.remove_internal(self)
            self.__g.clear()
    
        def groups(self):
            return list(self.__g)
    
        def alive(self):
            return truth(self.__g)
    
        def __repr__(self):
            return "<%s sprite(in %d groups)>" % (self.__class__.__name__, len(self.__g))
    

    派生类一般是要重写Sprite类的update方法 ,并给Sprite类的image属性和rect属性赋值。

    image属性一般是要赋一个·Surface对象的值。
    rect属性一般是要赋一个Rect对象的值。

    Surface对象

    官网上说是pygame Object for representing images, 即用于显示图形的图形对象,它有一些重要的函数。
    假设有一Surface实例,我们定义为A

    blit方法

    A.blit(source, dest, area=None, special_flags = 0) -> Rect。
    blit方法是将source的Surface渲染到A这个Surface上的方法。
    dest这个参数代之渲染的位置,dest既可以用一个代表source左上角坐标对的元组表示,也可以用一个Rect对象表示,Rect的topleft属性自动被用作dest

    convert方法

    A.convert(Surface) -> Surface
    A.convert(depth, flags=0) -> Surface
    A.convert(masks, flags=0) -> Surface
    A.convert() -> Surface
    convert方法的作用是使用更改的像素格式创建Surface的新副本。

    可以从另一个现有Surface确定新的像素格式。 否则可以使用depth,flags和mask参数,类似于pygame.Surface()调用。

    如果没有传递参数,则新Surface将具有与显示Surface相同的像素格式。 这总是最快的blitting格式。 最好在将所有Surface多次blit之前对其进行转换。

    转换后的Surface将没有像素alpha。 如果原件A有它们,它们将被剥离。
    请参阅Surface.convert_alpha()以保留或创建每像素alpha。

    新副本将与复制的表面具有相同的类。 这使得Surface子类继承此方法而无需覆盖,除非子类特定的实例属性也需要复制。

    convert_alpha

    A.convert_alpha(Surface) -> Surface
    A.convert_alpha() -> Surface
    使用所需的像素格式创建曲面的新副本。 新表面将采用适合于以每像素alpha快速blitting到给定格式的格式。 如果没有给出表面,则新表面将被优化以用于对当前显示进行blitting。

    与Surface.convert()方法不同,新图像的像素格式与请求的源不完全相同,但它将针对到目标的快速alpha blitting进行优化。

    fill

    A.fill(color, rect=None, special_flags=0) -> Rect
    用纯色填充表面。 如果没有给出rect参数,则将填充整个Surface。 rect参数将填充限制为特定区域。 填充也将包含在Surface剪辑区域中。

    color参数可以是RGB序列,RGBA序列或映射颜色索引。 如果使用RGBA,则忽略Alpha(RGBA的一部分),除非表面使用每像素alpha(Surface具有SRCALPHA标志)。

    get_rect()

    A.get_rect(**kwargs) -> Rect
    返回覆盖整个曲面的新矩形。 此矩形始终从(0,0)开始,宽度和高度与图像大小相同。

    您可以将关键字参数值传递给此函数。 这些命名值将在返回之前应用于Rect的属性。
    一个例子是’mysurf.get_rect(center =(100,100))’来创建一个以给定位置为中心的Surface矩形。

    subsurface()

    A.subsurface(Rect) -> Surface

    get_at()

    get_at((x, y)) -> Color
    返回给定像素处的RGBA Color值的副本。 如果Surface没有每像素alpha,那么alpha值将始终为255(不透明)。 如果像素位置在Surface区域之外,则会引发IndexError异常。

    一次获取和设置一个像素通常太慢,无法在游戏或实时情况下使用。 最好使用一次操作多个像素的方法,比如blit,fill和draw方法 - 或者使用surfarray / PixelArray。

    此功能将根据需要临时锁定和解锁Surface。

    返回颜色而不是元组,pygame 1.9.0中的新增功能。 如果你想要一个元组而不是一个颜色,请使用元组(surf.get_at((x,y)))。 这应该只在您想要将颜色用作字典中的键时才有意义。

    set_colorkey()

    set_colorkey(Color, flags=0) -> None
    set_colorkey(None) -> None

    设置Surface的当前颜色键。 将此Surface光照到目标上时,与colorkey具有相同颜色的任何像素都将是透明的。 颜色可以是RGB颜色或映射的颜色整数。 如果未传递,则将取消设置颜色键。

    如果将Surface格式化为使用每像素alpha值,则将忽略colorkey。 colorkey可以与完整的Surface alpha值混合使用。

    可选的flags参数可以设置为pygame.RLEACCEL,以便在非加速显示上提供更好的性能。 RLEACCEL曲面修改速度较慢,但作为源更快速地进行blit。

    Rect对象

    Pygame使用Rect对象来存储和操作矩形区域。 可以从left,top,width和height值的组合创建Rect。 也可以从已经是Rect或具有名为“rect”的属性的python对象创建Rects。

    任何需要Rect参数的pygame函数也接受这些值中的任何一个来构造Rect。 这样可以更容易地动态创建Rects作为函数的参数。

    更改Rect的位置或大小的Rect函数返回具有受影响的更改的Rect的新副本。 原始的Rect不会被修改。 某些方法有一个备用的“就地”版本,它返回None但会影响原始的Rect。 这些“就地”方法用“ip”后缀表示。

    Rect对象有几个虚拟属性,可用于移动和对齐Rect:

    • x,y
    • top,bottom,left,right
    • topleft, bottomleft, topright, bottomright
    • midtop, midleft, midbottom, midright
    • center, centerx, centery
    • size, width, height
    • w,h

    clamp方法

    clamp(Rect) -> Rect

    将矩形在另一个内部移动

    返回一个新的矩形,该矩形完全移动到参数Rect中。 如果矩形太大而无法放入内部,则它在参数Rect内居中,但其大小不会更改。

    sprite的方法

    • update方法,适用于控制sprite对象行为的方法。基类中该方法没有任何实现,这是等着我们自己去覆盖的。
    • add方法,将该sprite对象增加到group中,存疑。
    • remove方法。sprite对象会从group中删掉。
    • kill方法,将该sprite对象从所有groups中删掉。
    • groups方法,返回sprite对象所在的所有组。
    • alive方法,判断sprite方法是否还在组中。

    简单的例子

    它的实例我们参考这个博主的代码:
    Sprite使用代码示例

    这个博主是写了一个简单的小球弹弹弹的代码。

    import cStringIO, base64
    import pygame
    from pygame.locals import *
    
    class Ball(pygame.sprite.Sprite):
        def __init__(self, color, initial_position):
            pygame.sprite.Sprite.__init__(self)
            ball_file = cStringIO.StringIO(base64.decodestring(
    """iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAYAAADE6YVjAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJ
    bWFnZVJlYWR5ccllPAAABBJJREFUeNqsVj2PG1UUvfPp8XictXfHa+9mlyJCNEQRWiToqACJAgGC
    LqJNlQZR0IFEj8RPSJkGGooUpEWJkGhR0tAAElI2tsfjjxnPjIdz7oyDF2wSUK72yN43793z7rkf
    Y8N2HFmbbVliGIYiyzIpy1Isy3oHeMswzLOyXJ2tVit9VhTFAxz5Cfge+A7IZIcZmySObQudwIE0
    veanraB1w/O8l5x6D9eXy6UkSaJYLBa6BvsNuAV8uY3sCQlvX4LANM0Xw/Dgdhj2Xm02m+K6LqPR
    PXmeS5qmMp/PZTabyXQ6lclkosS1/QJcB+5vkthrAkoAuc4uHx//0B8MvCAIxG/5jEg0kpIkmcwX
    icTxBIhlHWEURXoedgW4B3wIfHuBJM9yMQ3j5PTk5N7g6MjtdrrS3e9Ku90GUUvc2hkdMYJx5Ivn
    NRC19UReRlRLR/sGeB34UUkMJBcJlcHg6K4SdDvS7/el1+tJp7MnQdCWRqMhDGWZLmWCCFog9rBm
    GBYc50rOKON4uqkSC+IQSC3moeX7N09PX/i4AwLkAoQDxeFhHziU8CCUzt6e+EFLc2QaJi4mFQHy
    kQLZMpME+WJF1sabdYA7Nq4jQbv9OZPs+75cgkSMYH9/X6PhJ9dpTLjruFLkBRyjACBd1BoLzzY8
    T3O0IRntJvCZDXsTTnq262CzrzmgRHu4+QEIQhAxNzRWU1mTxfjOwvBIAOlIYNnWtja5bqM33mN/
    sBEdx9bNPOQ1PWlqZJdAFKoMrEI6R+9gj6t7cUl1zjKnjFvsfaybr1Uqlv94ypXSKCud+aefpezs
    7O3LL9s4c5U65gCrhGDDpUkqyWIuU1STweNlJRe7nAlmA+ZaVbnmiD4KFNEWC+3VqjB5YImDdMA+
    YKONx2OVgxefojRL8CzmCxkOhxLhWYy+mGIvz6RKmv096X91PErP4Byazapbs3vZB45bVQqTzBzQ
    kjQBQSTnjx7JcDTCRSLkKNY9SbKACsttHKZdrIqHILnGCNhoDU0qG83U5mNUVTOKShRPYo3m8fAc
    nT/S/3mWFy2KrXKNOFbuI+Rr1FvLsB731Ho2m2pU7I1Sx8pSHTLaESIZjob6nfso2w77mSR3IMsN
    zh4mmLOIBAkO6fjAgESdV1MYiV4kiUZHRDjD3E0Qza580D+rjsUdAQEj4fRl8wUkqBttPeo5RlJI
    uB71jIASc8D+i4W8IoX8CviC5cuI+JlgpLsgcF1ng6RQyaoX1oWX1i67DTxe9w+9/EHW9VOrngCW
    ZfNFpmvVWOfUzZ/mfG0HwHBz4ZV1kz8nvLuL+YPnRPDJ00J8A/j9fzrnW+sjeUbjbP8amDyj86z+
    tXL5PwzOC4njj4K3gavA8cazczYacLd+p/+6y8mfAgwAsRuLfp/zVLMAAAAASUVORK5CYII="""))
            self.image = pygame.image.load(ball_file, 'file').convert_alpha()
            self.rect = self.image.fill(color, None, BLEND_ADD)
            self.rect.topleft = initial_position
    
    class MoveBall(Ball):
        def __init__(self,color,initial_position,speed,border):
            super(MoveBall,self).__init__(color,initial_position)
            self.speed = speed
            self.border = border
            self.update_time = 0
    
        def update(self,current_time):
            if self.update_time < current_time:
                if self.rect.left < 0 or self.rect.left > self.border[0]-self.rect.w:
                    self.speed[0]*=-1
                if self.rect.top < 0 or self.rect.top > self.border[1]-self.rect.h:
                    self.speed[1]*=-1
                self.rect.x += self.speed[0]
                self.rect.y += self.speed[1]
                self.update_time = current_time+10
            pass
    
    
    
    pygame.init()
    screen = pygame.display.set_mode([350, 350])
    
    #ball = Ball((255, 0, 0), (100, 100))
    balls = []
    for color,location,speed in [([255, 0, 0], [50, 50],[2,3]),
                            ([0, 255, 0], [100, 100],[3,2]),
                            ([0, 0, 255], [150, 150],[4,3])]:
        balls.append(MoveBall(color,location,speed,[350,350]))
    
    while True:
        if pygame.event.poll().type == QUIT:
            break
        screen.fill((0,0,0))
        current_time = pygame.time.get_ticks()
        for b in balls:
            b.update(current_time)
            screen.blit(b.image,b.rect)
        pygame.display.update()

    可以看到代码中Ball类继承类Sprite基类,然后MoveBall类继承了Ball类,这里只是用了Sprite类的update方法,还没有用到Group相关的方法。

    Group类

    用于保存和管理多个Sprite对象的容器类。
    创建Group实例

    g1 = pygame.sprite.Group()

    add方法

    g1.add(*sprites) -> None

    向此组添加任意数量的Sprite。 这只会添加尚未成为该组成员的Sprite。

    update方法

    g1.update(*args) -> None

    在组中的所有Sprite上调用update()方法。 基础Sprite类有一个更新方法,它接受任意数量的参数并且什么也不做。 传递给Group.update()的参数将传递给每个Sprite。

    无法从Sprite.update()方法获取返回值。

    draw()方法

    g1.draw(Surface) -> None

    将包含的Sprite绘制到Surface参数。 这使用源表面的Sprite.image属性和位置的Sprite.rect。

    g1不会以任何顺序保留精灵,因为绘制的顺序是任意的。

    clear()方法

    g1.clear(Surface_dest, background) -> None

    删除最后一次Group.draw()调用中使用的Sprite。 通过用背景填充绘制的Sprite位置来清除目标Surface。

    背景通常是与目标Surface相同尺寸的Surface图像。 但是,它也可以是一个带有两个参数的回调函数; 目的地Surface和要清除的区域。 每次清除后,将调用几次背景回调函数。

    展开全文
  • 本次来实现Sprite类和鼠标事件。 说起这个Sprite啊,涉及过2D游戏研究领域的看官应该都听说过它。它中文原意是“精灵”,不过在不同人的眼中,它所表示的意义不同。比如说在cocos2d中,它可以是一张图片。不过在...

    本次来实现Sprite类和鼠标事件。
    说起这个Sprite啊,涉及过2D游戏研究领域的看官应该都听说过它。它中文原意是“精灵”,不过在不同人的眼中,它所表示的意义不同。比如说在cocos2d中,它可以是一张图片。不过在flash中,Sprite是一个类似于“层”的家伙。当然你把它定义为层并不是很准确,实际上它是一个含显示列表的显示对象。什么意思呢?各位看官如果阅读了前面的章节,那对显示列表并不陌生。它说白了就是一个包含其他显示对象的容器。
    那也许你会想,为什么要有这个类呢?举个例子大家就明白了。在一款RPG游戏中(如:口袋妖怪),我们的地图上有树林、小河等一系列地图元件。玩过此类游戏的同学都知道,如果我们的人物走到了地图中央继续前进的话,地图会进行卷轴移动,显示出下部分地图。这个时候我们如果要把每个地图元件进行移动,操作起来会相当麻烦。因此flash为我们提供的Sprite就是为了统一处理一系列显示对象而生的。
    经过上面的介绍,大家可能仍然无法理解这么抽象的一个类。那姑且把它视作一个层吧,我们可以通过SpriteaddChild来向这个层添加显示对象。添加进去的对象所进行的操作都是相对的,比如说移动,旋转。

    以下是前面章节目录:

    Python游戏引擎开发(一):序

    Python游戏引擎开发(二):创建窗口以及重绘界面

    Python游戏引擎开发(三):显示图片

    Python游戏引擎开发(四):TextField文本类

    Sprite的实现

    以下是实现代码:

    class Sprite(DisplayObject):
        def __init__(self):
            super(Sprite, self).__init__()
    
            self.childList = []
            self.mouseList = []
    
        def addChild(self, child):
            self.childList.append(child)
    
        def removeChild(self, child):
            self.childList.remove(child)
    
            child.parent = None
    
        def _loopDraw(self, c):
            stage._showDisplayList(self.childList)

    可以看到,这个类的实现代码很简单,就是添加了显示列表属性(childList)、鼠标事件列表(mouseList)和添加/删除对象的方法。实际上在flash中,这个类还有很多功能,比如说以后会提及的矢量绘图。看过第二章的同学应该会注意到stage._showDisplayList这个方法,他负责遍历显示列表并显示遍历得到的对象(及子对象)。由于这个方法是在QPainter变换(平移,旋转,拉伸)之后,QPainter.restore()之前被调用的,所以再次调用到这个子对象显示方法时,显示方法中对QPainter的变换就是相对于先前QPainter变换而言的。因此,我们就实现了子对象相对父对象变换的效果。

    鼠标事件

    我们要来实现鼠标事件方面的功能了。首先需要了解的是,由于我们无法直接对我们写的显示对象添加事件,所以只能先对QWidget添加鼠标事件然后在进一步进行计算来判断是否触发到我们的事件。

    鼠标事件大致传递过程如下:

    Created with Raphaël 2.1.0点击窗口QWidget窗口部件舞台stage对象直接addChild到stage上的一级Sprite一级Sprite的子对象二级Sprite(一级Sprite中的Sprite)的子对象……

    为了给QWidget添加鼠标事件,我们需要改动CanvasWidget类:

    class CanvasWidget(QtGui.QWidget):
        def __init__(self):
            super(CanvasWidget, self).__init__()
    
            self.setMouseTracking(True)
    
        def paintEvent(self, event):
            stage._onShow()
    
        def mousePressEvent(self, event):
            self.__enterMouseEvent(event, "mouse_down")
    
        def mouseMoveEvent(self, event):
            self.__enterMouseEvent(event, "mouse_move")
    
        def mouseReleaseEvent(self, event):
            self.__enterMouseEvent(event, "mouse_up")
    
        def __enterMouseEvent(self, event, eventType):
            e = {"offsetX" : event.x(), "offsetY" : event.y(), "eventType" : eventType, "target" : None}
    
            stage._enterMouseEvent(e, {"x" : 0, "y" : 0, "scaleX" : 1, "scaleY" : 1})

    主要是重写了QWidget中的几个事件回调(mouseReleaseEventmouseMoveEventmousePressEvent)以及添加事件进入显示对象的入口__enterMouseEvent,该函数的参数一个是Qt发来的事件对象,保存了一些事件信息,如鼠标位置,另一个是鼠标事件类型,比如”mouse_up”,”mouse_down”。
    值得注意的是,setMouseTracking方法是用于不停地触发移动事件,否则Qt默认只处理一次。
    我们在使用鼠标事件时,多数情况下要获取鼠标的位置,所以我们将鼠标信息也记录下来,记入变量e,这个变量将随着事件的传递,一直传下去。顺便对其中的属性进行说明:

    • offsetX,offsetY:鼠标相对于屏幕左上角的位置
    • eventType:鼠标事件类型
    • target:鼠标所点击的显示对象,初始值为None,当事件传递到需要获取此属性的时候,就会被赋值为被点击的对象

    最后进入Stage._enterMouseEvent函数,将事件传递到舞台对象上。

    Stage._enterMouseEvent的代码如下:

    def _enterMouseEvent(self, event, cd):
            childList = self.childList[:: -1]
    
            currentCd = {"x" : cd["x"], "y" : cd["y"], "scaleX" : cd["scaleX"], "scaleY" : cd["scaleY"]}
    
            for o in childList:
                if hasattr(o, "_enterMouseEvent") and hasattr(o._enterMouseEvent, "__call__") and o._enterMouseEvent(event, currentCd):
                    break

    在这个方法中,首先接受两个参数,一个就是鼠标信息,另一个是坐标对象(包含x,y坐标,scaleX,scaleY拉升值)。为什么要弄个坐标对象呢?因为我们在判断显示对象是否被点击时,需要用到坐标计算,而进行坐标计算时,需要获取对象的绝对位置,这个坐标对象就是用于计算绝对位置用的。随着事件往下级对象的传递,坐标对象会作为上级对象的坐标数据往下级对象传递,从而进行递归式坐标计算,节省效率。
    在其中,我们遍历了所有底层子对象并且判断是否可以进入鼠标事件向下级子对象循环,如果可以(及判断有无_enterMouseEvent方法),则进行。除此之外,为了实现鼠标事件遮挡效果,我们特地的反着遍历显示列表,也就是说先遍历得到显示在上层的对象,调用这些对象的_enterMouseEvent方法,该方法返回值若为True则代表鼠标在该显示对象上面,通过break中断遍历。

    一般情况下,有_enterMouseEvent的,大半是Sprite对象。所以我们为Sprite添加这个方法:

    def _enterMouseEvent(self, e, cd):
            if not self.visible:
                return
    
            currentCd = self.__getVisualCoordinate(cd, self)
    
            isOn = self._isMouseOn(e, currentCd)
    
            if isOn:
                for o in self.childList[::-1]:                  
                    if (hasattr(o, "_enterMouseEvent") and hasattr(o._enterMouseEvent, "__call__") and o._enterMouseEvent(e, currentCd)):
                        break
    
                self.__dispatchMouseEvent(e, currentCd)
    
            return False
    

    和Stage中的_enterMouseEvent非常类似。其中用了子对象的_isMouseOn方法,用于判断是否点击到该对象上。而__dispatchMouseEvent用于触发鼠标事件。__getVisualCoordinate用于得到一个显示坐标,这个好比我们看三维图形直观图,实际的大小和看到的不是一样的。由于显示对象的x,y坐标是相对父对象的,所以我们通过这个方法来实现得到看到的大小和位置,及相对于屏幕左上角的绝对位置。__getVisualCoordinate代码:

    def __getVisualCoordinate(self, origin, obj):
            return {
                "x" : origin["x"] + obj.x * origin["scaleX"],
                "y" : origin["y"] + obj.y * origin["scaleY"],
                "scaleX" : origin["scaleX"] * obj.scaleX,
                "scaleY" : origin["scaleY"] * obj.scaleY
            }

    这个方法的具体功能,如图所示:
    Image1

    对Object B使用该方法,那么传入的origin参数相当于Object A的坐标信息,参数obj就是Object B,返回的对象中,x属性就是90,y属性就是70。scaleXscaleY同理。

    虽然Sprite是一个显示对象,但是可见的其实是里面的BitmapTextField等子对象,所以我们在为这些显示对象添加 _isMouseOn方法时,要区分对待。对于Bitmap等多数显示对象,我们采用判断点击的位置是否在显示对象所处的矩形范围内(毕竟我们几乎所有显示对象都是矩形的,暂时这么简单实现一下),给DisplayObject类添加该方法:

    def _isMouseOn(self, e, cd):
        if not self.visible:
            return
    
        ox = e["offsetX"]
        oy = e["offsetY"]
        x = cd["x"]
        y = cd["y"]
        scaleX = cd["scaleX"]
        scaleY = cd["scaleY"]
        w = self._getOriginalWidth()
        h = self._getOriginalHeight()
    
        if x <= ox <= x + w * scaleX and y <= oy <= y + h * scaleY:
            e["target"] = self
    
            return True
    
        return False

    以上代码还是很好理解的,至于_getOriginalWidth_getOriginalHeight二厮,不知道大家还记得不,是前面提到的获取显示对象原始宽高(忽略scaleXscaleY)的方法。通过if x <= ox <= x + w * scaleX and y <= oy <= y + h * scaleY:判断点击的位置是否在显示对象内。

    对于Sprite,添加不同的_isMouseOn方法:

    def _isMouseOn(self, e, cd):
        if not self.visible:
            return
    
        childList = self.childList[::-1]
    
        for o in childList:
            childCd = self.__getVisualCoordinate(cd, o)
    
            if o._isMouseOn(e, childCd):
                e["target"] = o
    
                return True
    
        return False

    和其他显示对象不同的是,它通过遍历子对象,并调用它们的_isMouseOn来完成判定鼠标是否盘旋在该Sprite上。其中设置了target属性,用于方便使用者获取点击对象。

    还有一个_dispatchMouseEvent方法,用于触发鼠标事件:

    def __dispatchMouseEvent(self, e, cd):
        for o in self.mouseList:
            t = o["eventType"]
            l = o["listener"]
    
            if t == e["eventType"]:
                eve = object()
                eve.offsetX = e["offsetX"]
                eve.offsetY = e["offsetY"]
                eve.selfX = (e["offsetX"] - cd["x"]) / cd["scaleX"]
                eve.selfY = (e["offsetY"] - cd["y"]) / cd["scaleY"]
                eve.target = e["target"]
                eve.currentTarget = self
    
                l(eve)

    该方法中,首先是遍历了鼠标事件列表,找到对应的事件,然后触发事件监听器(即回调函数)。注意,监听器接受一个参数,该参数是一个object,储存了鼠标相对于屏幕左上角的坐标(offsetXoffsetY),以及相对于Sprite对象的坐标(selfXselfY),还可以通过currentTarget属性获取触发事件的Sprite对象,以及通过target属性获取点击到的对象。

    最后添加加入事件方法addEventListenerremoveEventListener即可。顾名思义,它们分别用于添加事件和移除事件,主要用到listdict来完成事件存储。代码如下:

    def addEventListener(self, eventType, listener):
        self.mouseList.append({
            "eventType" : eventType,
            "listener" : listener
        })
    
    def removeEventListener(self, eventType, listener):
        for o in self.mouseList:
            if o["eventType"] == eventType and o["listener"] == listener:
                self.mouseList.remove(o)
    
                break

    最后加入MouseEvent类:

    class MouseEvent(object):
        MOUSE_DOWN = "mouse_down"
        MOUSE_UP = "mouse_up"
        MOUSE_MOVE = "mouse_move"
        MOUSE_OVER = "mouse_over"
        MOUSE_OUT = "mouse_out"
        DOUBLE_CLICK = "mouse_dbclick"
    
        def __init__():
            raise Exception("MouseEvent cannot be instantiated.")

    使用时,这么写就可以了:

    def main():
        layer = Sprite()
        layer.scaleX = 3
        addChild(layer)
    
        txt = TextField()
        txt.text = "Test"
        txt.textColor = "red"
        txt.x = 50
        txt.y = 100
        txt.size = 50
        layer.addChild(txt)
    
        # mouse down event
        layer.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown)
        # mouse up event
        layer.addEventListener(MouseEvent.MOUSE_UP, onMouseUp)
        # mouse move event
        layer.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove)
    
    def onMouseDown(e):
        print("mouse down", e.offsetX, e.offsetY)
    
    def onMouseUp(e):
        print("mouse up", e.selfX, e.selfY)
    
    def onMouseMove(e):
        print("mouse move", e.target, e.currentTarget)
    
    init(30, "Sprite and Mouse Event", 800, 600, main)

    运行截图如下:
    Image2

    至此,我们就把Sprite和鼠标事件大致实现了。


    预告:下一篇我们实现动画类。


    欢迎大家继续关注我的博客

    转载请注明出处:Yorhom’s Game Box

    http://blog.csdn.net/yorhomwang

    展开全文
  • sprite的使用方法

    千次阅读 2016-04-12 11:06:09
    Sprite *sp = Sprite::create("diyiming.png"); 2、方法二 Texture2D *textture = TextureCache::getInstance()->addImage("kaishi.png"); Sprite *sp = Sprite::createWithTexture(textture);这里get instance

    1、创建方法一
    <span style="font-family: Arial, Helvetica, sans-serif;"></span><pre name="code" class="cpp">Sprite *sp = Sprite::create("diyiming.png");
    
    
    <span style="font-family: Arial, Helvetica, sans-serif;">2、方法二</span>
    
    
    <pre name="code" class="cpp">Texture2D *textture = TextureCache::getInstance()->addImage("kaishi.png");
    Sprite *sp = Sprite::createWithTexture(textture);


    
    这里get instance()被提示为过时函数,可能是编译器抽风
    

    3、方法三

    SpriteFrameCache::getInstance()->addSpriteFramesWithFile("dqqIMG.plist");
    auto spriteFrame = SpriteFrameCache::getInstance()->getSpriteFrameByName("slingshot1.png");
    slingshotSprite = Sprite::createWithSpriteFrame(spriteFrame);
    4、中途修改sprite的图片

    initWithFile

    initWithSpriteFrameName 

    setTexture

    注意:被修改的精灵尽可能是一个本身有图片的精灵。否则出错的概率很大,尤其使用box2d引擎的时候,引擎将失效。一个很懒的解决办法是,每次创建精灵都默认设置一张图片。方法有点笨,但是简单使用。


    4、精灵播放帧动画

    说到帧动画,不得不提一下骨骼动画,感觉骨骼动画真的很酷,但是目前我的水平来讲,也只能老老实实用帧动画咯。

    cocos2d::Vector<cocos2d::SpriteFrame*>slingshotFrames;

    auto spriteFrame = SpriteFrameCache::getInstance()->getSpriteFrameByName("slingshot0.png"); slingshotFrames.pushBack(spriteFrame);

    auto spriteFrame1 = SpriteFrameCache::getInstance()->getSpriteFrameByName("slingshot1.png");  

    slingshotFrames.pushBack(spriteFrame1);

    auto spriteFrame2 = SpriteFrameCache::getInstance()->getSpriteFrameByName("slingshot2.png");  

    slingshotFrames.pushBack(spriteFrame2);

    auto spriteFrame3 = SpriteFrameCache::getInstance()->getSpriteFrameByName("slingshot1.png");  

    slingshotFrames.pushBack(spriteFrame3)

    Animation *anim = Animation::createWithSpriteFrames(slingshotFrames,1.0f/20.0f);
    slingshotSprite->runAction(Animate::create(anim));



    展开全文
  • 动态创建Sprite

    千次阅读 2016-07-30 16:53:56
    Texture2D texture = Resources.Load(path) as Texture2D; SpriteRenderer spriteRenderer = transform.GetComponent();...spriteRenderer.sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.he
  • spriteJs 初尝试

    千次阅读 2018-11-30 17:11:15
    spriteJs 初尝试 一开始看到 spriteJs ,一脸懵逼,啥时候"雪碧图"出了个插件,后来看了介绍,不得不嘲笑自己的无知,呵呵,井底之蛙!! 简介 spriteJs 是一款由 360 奇舞团开源的跨终端 canvas 绘图框架...
  • Unity2D:Sprite和Image的区别

    万次阅读 多人点赞 2018-11-27 13:11:21
    Sprite和Image区别 面试的时候被问到Sprite和Image的区别,我当时就感觉这有什么区别呢? 后来查了下资料,还是自己无知啊! 下面就具体的区别做个笔记 Image 和Sprite 区别 下图是一个Sprite 在Unity中可以在...
  • CSS精灵图(sprite

    万次阅读 多人点赞 2018-09-09 11:30:17
    说到精灵图(雪碧图),大家肯定和我一样觉得这个很新鲜。在学习了有关精灵图(雪碧图)的一些知识后,就来和大家讨论一下,我个人对精灵图(雪碧图)的一些理解和实现方法吧。 1、精灵图技术产生的目的:很多大型...
  • Sprite 和Texture 的区别

    万次阅读 2018-08-07 22:57:53
    一,导入   1,无论是什么格式的图片(最好直接使用PS直接导出的PSD格式),Unity都会自己搞一套格式,并且打包的时候也不会用你文件夹下图片的格式,而是Unity自己的格式。 2,都可以在导入时设置,图片在发生...
  • Sprite Editor【精灵编辑器】

    千次阅读 2017-10-13 17:24:23
    精灵编辑器【Sprite Editor】   有时,精灵纹理只包含一个图形元素,但是将几个相关的图形合并到一个单独的图像中通常会更加方便。列如:图像可以包含单个字符的组成部分,就像汽车的轮子独立于车身一样。Unity...
  • unity3d 更换sprite renderer中sprite的图片

    万次阅读 2014-09-29 10:26:48
    unity3d 更换sprite renderer中sprite的图片  所更换图片尺寸与原图片相同
  • Unity为Image组件动态添加sprite

    万次阅读 2017-05-10 10:08:17
    Image组件的Source Image属性是Sprite类型的,然而图片的type设置为Sprite之后,它包含两个部分(如图): 以1001为例,其第一部分类型为Texture2D,第二部分为Sprite的。 一种动态获取方式(Resources.Load): ...
  • 版本:1.9.1 情景:从服务器下载一个Texture...使用这种方式可以替换成功,但是所有使用这个SpriteFrame的Sprite都会被替换纹理,所以证明Creator中所有使用同一张图片的SpriteFrame都是引用,改变其中的_textur...
  • Tex保存在GPU缓冲中的一张...SpriteFrame是Texture+Rect,过程是这样的, Texture是通过指定图片得到的, 然后,指定用这个Texture指定具体的Rect就是SpriteFrame, SpriteFrame就是用户可以看到的CCSprite
  • UGUI中动态改变image的sprite

    万次阅读 2017-01-25 23:24:33
    UGUI中的Image组件就相当NGUI中的sprite,那么除了用拖动图片的方式设置Image的背景外,还可以用下面的方式在代码中动态的修改它的背景图片。using UnityEngine; using System.Collections; using UnityEngine.UI; ....
  • Sprite被为设置Multiple的情况下,如何根据子Sprite的名字设置精灵图片呢: 找不到直接根据名字取子Sprite的方法,但是我们可以通过  Sprite[] sprites = Resources.LoadAll("path"); // path为Sprite在Resources中...
  • 如何通过代码动态设置Sprite Renderer的Sprite? Sprite精灵有两种情况,一种是single模式的sprite,另外一种是multiple的sprite。下面分别写写这两种类型的处理方法: 一、Sprite(single): SpriteRenderer ...
  • 对于旧版本的unity,一般用sprite packer: 使用之前,先Enable这个Mode,在Editor Setting中设置如下: 然后给需要打包的图片一个共同的Packing Tag,这样相同packing tag图片就都打包到一个图集中了: 打包好...
  • Unity SpriteRender 和Image的区别

    千次阅读 2017-12-11 23:06:42
    1. 首先,spriterender是sprite显示图片需要的组件,通常来说sprite应该是transfrom,也就是世界坐标,而image被归类为UI组件,使用的是recttransfrom,使用UI坐标。 2.spriterender(以下简称sr)控制大小是使用...
  • Unity2017新功能Sprite Atlas详解

    万次阅读 2018-07-09 23:54:00
    Sprite Atlas(精灵图集)Sprite Atlas 针对现有的图集打包系统Sprite Packer在性能和易用性上的不足,进行了全面改善。除此之外,相比Sprite Packer,Sprite Atlas将对精灵更多的控制权交还给用户。由用户来掌控...
1 2 3 4 5 ... 20
收藏数 66,736
精华内容 26,694
关键字:

sprite