0
\$\begingroup\$

I'm working on a test game in Cocos2d for Python (code is a little dirty). I'm trying to run and shoot at the same time without having my character's (running) legs go back to the beginning position every time I fire. So as it runs it will have freedom to shoot.

The code uses a custom action class (PlatformerController) that's implemented on a sprite, which is placed on its own layer. There are three sprites that represent the character: its running arms and the body.

The code checks if the player is running. If it is, then it removes the player's running arms, and replaces them with its shooting arms. This works fine if I replace the running body as well, but if I just remove the arms and replace them while keeping the running body, the arms are added right behind the body. Maybe someone has a better suggestion on doing this?

What I've tried:

  • using a separate platformer controller for the arms
  • aligning each arm sprite with the character's body rect
  • using the same .position of the body on the arms
  • using the built-in moving (interval action, different than the custom action class) classes

Here is the full code (it should be noted that I added the "tag" parameter to sprite):

import pyglet
from pyglet.window import key
from pyglet.image import load_animation

import cocos
from cocos import layer, mapcolliders, actions, tiles, sprite, scene

from cocos.director import director
import cocos.euclid as eu
import cocos.collision_model as cm

class evaHero(layer.ScrollableLayer):

    def __init__(self):

        super(evaHero, self).__init__()
        theAnim = load_animation("evaStanceArmless.gif")
        self.sprite = sprite.Sprite(theAnim)
        self.sprite.tag = 5
        self.add(self.sprite,z=2)
        self.sprite.do(PlatformerController())
        self.spriteLeftArm = sprite.Sprite(load_animation("evaLeftArm.gif"))
        self.spriteRightArm = sprite.Sprite(load_animation("evaRightArm.gif"))
        self.add(self.spriteLeftArm,z=1)
        self.add(self.spriteRightArm,z=3)
        self.spriteLeftArm.do(PlatformerController())
        self.spriteRightArm.do(PlatformerController())
        # self.shot = sprite.Sprite("pulseShot.png")
        # return is done automatically when you init

    def evaStance(self,mapCollider, collideTiles, is_left, is_shooting):

        if is_left:
            if is_shooting:
                anim = load_animation("evaStanceArmless.gif")
                animL = load_animation("shootRunLeftArm.gif")
                animR = load_animation("shootRunRightArm.gif")
            else:
                anim = load_animation("evaStanceArmless.gif")
                animL = load_animation("evaLeftArm.gif")
                animR = load_animation("evaRightArm.gif")
        else:
            if is_shooting:
                anim = load_animation("evaStanceArmless.gif")
                animL = load_animation("shootRunLeftArm.gif")
                animR = load_animation("shootRunRightArm.gif")
            else:
                anim = load_animation("evaStanceArmless.gif")
                animL = load_animation("evaLeftArm.gif")
                animR = load_animation("evaRightArm.gif")

        tempSprite = sprite.Sprite(anim)
        tempArmLeft = sprite.Sprite(animL)
        tempArmRight = sprite.Sprite(animR)
        for x in [tempArmLeft, tempSprite, tempArmRight]:
            x.position = self.sprite.x, self.sprite.y
            if x == tempSprite:
                x.do(PlatformerController())
                self.sprite = tempSprite
            else:
                x.do(PlatformerController())
            x.collision_handler = mapcolliders.make_collision_handler(mapCollider, collideTiles)
        self.spriteLeftArm = tempArmLeft
        self.spriteRightArm = tempArmRight

        for x in self.children:
            if x[1].tag == 1 or x[1].tag == 2: # does not remove shots
                pass
            else:
                self.remove(x[1])

        zPos = 1
        for x in [tempArmLeft, tempSprite, tempArmRight]:
            self.add(x,z=zPos)
            zPos += 1

    def evaRun(self,mapCollider,collideTiles,is_left,is_shooting):

        if is_shooting:

            if is_left:
                # runSprite = sprite.Sprite(load_animation("runLeft.gif"))
                leftArmRun = sprite.Sprite(load_animation("shootRunLeftArm.gif"))
                rightArmRun = sprite.Sprite(load_animation("shootRunRightArm.gif"))
            else:
                # runSprite = sprite.Sprite(load_animation("evaRun.gif"))
                leftArmRun = sprite.Sprite(load_animation("shootRunLeftArm.gif"))
                rightArmRun = sprite.Sprite(load_animation("shootRunRightArm.gif"))


                # if x == runSprite:
                #     x.do(PlatformerController())
                # else:
            for x in self.children:
                if x[1].tag == 1 or x[1].tag == 2 or x[1].tag == 5:
                    pass
                else:
                    self.remove(x[1])

            # self.sprite = runSprite
            self.spriteLeftArm = leftArmRun
            self.spriteRightArm = rightArmRun
            self.sprite.stop()
            self.sprite.do(PlatformerController())
            for x in [self.spriteLeftArm,self.spriteRightArm]:
                x.collision_handler = \
                    mapcolliders.make_collision_handler(mapCollider, collideTiles)
                x.position = self.sprite.position
                x.do(PlatformerController())

            zPositions = 0
            for y in [self.spriteLeftArm, self.spriteRightArm]:
                self.add(y,z=zPositions)
                zPositions += 2

        else:

            if is_left:
                runSprite = sprite.Sprite(load_animation("runLeft.gif"))
                leftArmRun = sprite.Sprite(load_animation("runLeftArmL.gif"))
                rightArmRun = sprite.Sprite(load_animation("runRightArmL.gif"))
            else:
                runSprite = sprite.Sprite(load_animation("evaRun.gif"))
                leftArmRun = sprite.Sprite(load_animation("runLeftArm.gif"))
                rightArmRun = sprite.Sprite(load_animation("runRightArm.gif"))


            for x in [leftArmRun,runSprite,rightArmRun]:
                x.position = self.sprite.position
                if x == runSprite:
                    x.tag = 5
                    x.do(PlatformerController())
                else:
                    x.do(PlatformerController())
                x.collision_handler = \
                    mapcolliders.make_collision_handler(mapCollider, collideTiles)

            self.sprite = runSprite
            self.spriteLeftArm = leftArmRun
            self.spriteRightArm = rightArmRun
            for x in self.children:
                if x[1].tag == 1 or x[1].tag == 2:
                    pass
                else:
                    self.remove(x[1])

            zPositions = 0
            for y in [leftArmRun, runSprite, rightArmRun]:
                self.add(y,z=zPositions)
                zPositions += 1

class PlatformerController(actions.Action):

    on_ground = True
    is_running = False
    is_left = False
    MOVE_SPEED = 300
    JUMP_SPEED = 400
    GRAVITY = -1200
    deltaTime = 0.0

    def start(self): #actions don't need __init__
        self.target.velocity = (0,0)
        #no need for target.gravity because the "walls" layer stops the sprite

    def step(self, dt):
        global keyboard, scroller
        if dt > 0.1:
            return #if dt is too big, collision will suffer
        self.deltaTime = dt
        # print("before ",self.target.x, self.target.y)
        # print("target: ",self.target.tag, " velocity: ",self.target.velocity, dt)
        vx, vy = self.target.velocity

        #update velocity
        vx = (keyboard[key.RIGHT] - keyboard[key.LEFT]) * self.MOVE_SPEED
        vy += self.GRAVITY * self.deltaTime

        if self.on_ground and keyboard[key.SPACE]:
            vy = self.JUMP_SPEED

        #with updated velocity calculate (tentative) displacement
        dx = vx * self.deltaTime
        dy = vy * self.deltaTime

        #player's current bounding rectangle
        last = self.target.get_rect()

        #build the tentative displaced rect
        new = last.copy()
        new.x += dx
        new.y += dy

        #account for hitting obstacles, it will adjust to new vx, vy
        self.target.velocity = self.target.collision_handler(last, new, vx, vy)

        #update on_ground status
        self.on_ground = (new.y == last.y) #gives you False

        #update player position; player position is anchored to the center of the image rect
        self.target.position = new.center
        #move the scrolling view to center on the player
        if self.target.tag == 5:
            scroller.set_focus(*new.center)


class GameScene(layer.ColorLayer):

    is_event_handler = True

    def __init__(self):

        super(GameScene, self).__init__(102, 102, 255, 255)
        # create a layer to put the player in
        self.player_layer = evaHero()
        self.is_run_and_gun = False
        self.mapcollider = mapcolliders.RectMapCollider
        self.colliderList = set()

        if __name__ == '__main__':
            self.main()

    def main(self): #did move to view
        global keyboard, tilemap, scroller, fullmap
        self.schedule(self.update)

        # NOTE: the anchor for this sprite is in the CENTER (the cocos default)
        # which means all positioning must be done using the center of its rect

        #add the tilemaps and the player sprite layer to a scrolling manager
        scroller = layer.ScrollingManager()
        print("loading tiles")
        fullmap = tiles.load_tmx("firstLevel.tmx")
        self.tilemap_walls = fullmap["walls"]
        scroller.add(self.tilemap_walls,z=2)
        tilemap_decoration = fullmap["decoration"]#this is the tmx object layer
        scroller.add(tilemap_decoration,z=0)

        self.tilemap_other = fullmap["other"]#this is the trees, grass, sky, and clouds
        scroller.add(self.tilemap_other,z=1)

        scroller.add(self.player_layer,z=3)

        #baddie
        self.baddie = sprite.Sprite(load_animation("redPUThingy.gif"))
        self.baddie.tag = 2
        self.player_layer.add(self.baddie)

        #set the player start using the player_start token from the map
        start = tilemap_decoration.objects[0]
        start2 = tilemap_decoration.find_cells(player_start=True)

        baddieRect = self.baddie.get_rect()
        baddieRect.midbottom = start.midbottom
        self.baddie.position = baddieRect.center
        self.baddie.x *= 8
        self.baddie.cshape = cm.CircleShape(eu.Vector2(self.baddie.x,self.baddie.y),
                                            32)

        self.colliderList.add(self.baddie)
        self.collman = cm.CollisionManagerGrid(0,self.tilemap_other.px_width,
                                               0,self.tilemap_other.px_height,32,32)

        print("start: ", start.midbottom)
        print("start2: ", start2)

        r = self.player_layer.sprite.get_rect()

        #align the midbottom of the player rect to the mid bottom of the start cell
        r.midbottom = start.midbottom

        # player image anchor (position) is in the center of the sprite
        self.player_layer.sprite.position = r.center
        self.player_layer.spriteLeftArm.position = r.center
        self.player_layer.spriteRightArm.position = r.center

        #give a collision handler to the player
        self.mapcollider = mapcolliders.RectMapCollider(velocity_on_bump="slide")
        self.player_layer.sprite.collision_handler = \
            mapcolliders.make_collision_handler(self.mapcollider, self.tilemap_walls)
        self.player_layer.spriteLeftArm.collision_handler = \
            mapcolliders.make_collision_handler(self.mapcollider, self.tilemap_walls)
        self.player_layer.spriteRightArm.collision_handler = \
            mapcolliders.make_collision_handler(self.mapcollider, self.tilemap_walls)

        #construct the scene with a background layer color and scrolling layers
        self.add(scroller)

        #track keyboard presses
        keyboard = key.KeyStateHandler()
        director.window.push_handlers(keyboard)

        def on_key_press(key, modifier):
            if key == pyglet.window.key.D:
                print("debug walls!")
                self.tilemap_walls.set_debug(True)
            if key == pyglet.window.key.LEFT:
                print("left")
                self.is_run_and_gun = True
                self.player_layer.evaRun(self.mapcollider,self.tilemap_walls,True,False)
            elif key == pyglet.window.key.RIGHT:
                print("right")
                self.is_run_and_gun = True
                self.player_layer.evaRun(self.mapcollider, self.tilemap_walls,False,False)
            elif key == pyglet.window.key.A:
                print("fire!!")
                if self.is_run_and_gun == True:
                    print("run shoot")
                    self.player_layer.evaRun(self.mapcollider, self.tilemap_walls, False, True)
                    shot = sprite.Sprite("pulseShot.png")
                    shot.tag = 1
                    shot.position = self.player_layer.spriteLeftArm.get_rect().midright
                    shot.velocity = 800, 0
                    shot.cshape = cm.CircleShape(eu.Vector2(shot.x,shot.y),12)
                    self.colliderList.add(shot)
                    shot.do(actions.Move())
                    self.player_layer.add(shot, z=0)
                elif self.is_run_and_gun == False:
                    print("stand shoot")
                    self.player_layer.evaStance(self.mapcollider, self.tilemap_walls, False, True)

                if PlatformerController.on_ground == True:
                    #: stops from having player jumping higher and higher
                    PlatformerController.on_ground = False


        def on_key_release(key, modifier):

            if key == pyglet.window.key.LEFT:
                print("release left")
                self.player_layer.evaStance(self.mapcollider, self.tilemap_walls,True,False)
                self.is_run_and_gun = False
            elif key == pyglet.window.key.RIGHT:
                print("release right")
                self.player_layer.evaStance(self.mapcollider, self.tilemap_walls,False,False)
                self.is_run_and_gun = False
            elif key == pyglet.window.key.A:
                print("stuff")


        director.window.push_handlers(on_key_press, on_key_release)

    def update(self,dt):
        self.colliderList.clear()
        for x in self.player_layer.children:

            if x[1].tag == 1: #: remove shot if x passes beyond bounds
                x[1].cshape.center = x[1].position #update shot position
                if x[1].x > (self.player_layer.sprite.x + self.width) or x[1].x < 0:
                    print("removed!")
                    self.player_layer.remove(x[1])

            if x[1].tag == 1 or x[1].tag == 2:
                self.colliderList.add(x[1])

        self.collman.clear()
        # numCheck = 0

        for x in self.colliderList:
            # numCheck += 1
            # print(numCheck)#this shows that it's the moment I intro the shot that causes the error
            self.collman.add(x)
        try:
            for shot, badguy in self.collman.iter_all_collisions():
                print("hit")
                self.player_layer.remove(badguy)
                self.player_layer.remove(shot)
        except TypeError:
            print("no overlap")

if __name__ == "__main__":

    director.init(width=800, height=600, autoscale=False, resizable=True)
    director.run(scene.Scene(GameScene()))
\$\endgroup\$
1
\$\begingroup\$

I was able to get what I was asking for by copying the sprite, then removing it and adding it back. I also made sure to not "add" another action on top on top of the copied sprite, as Cocos2d keeps the action on the copied sprite.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.