My first attempt at a game - Dice

Return to Showcase

My first attempt at a game - Dice

Postby martan3d » Thu Feb 24, 2011 11:06 am

Well, not sure I would call it 'showcase' material, it's my first attempt. But it was great fun and thanks to everyone for helping me learn the basics. I'm sure there are bugs and I do lose a die now and then so my ODE and frame rate and all that need some work but it does work pretty good considering.

I 'packpanda'd the whole thing and put it up if you would care to try it out-

http://www.virginiahelicopter.net/game/dice.exe
martan3d
 
Posts: 33
Joined: Mon Dec 27, 2010 7:16 pm

Postby coppertop » Thu Feb 24, 2011 11:09 am

Is it possible for you to provide it in a system-neutral form? I.e. source code? Many people don't use Windows but would still like to check it out.
User avatar
coppertop
 
Posts: 527
Joined: Sat Apr 18, 2009 5:48 am

Postby Inkaster » Thu Feb 24, 2011 12:18 pm

You can use Winrar to compress the folder "game" inside your Panda3d folder. Those who have panda3d will be able to play the game with the main.py.
Inkaster
 
Posts: 12
Joined: Tue Dec 14, 2010 5:06 pm

Postby martan3d » Thu Feb 24, 2011 12:25 pm

Sorry about that. Here is the code:

Code: Select all
from pandac.PandaModules import NodePath, DirectionalLight, AmbientLight, VBase4, Vec4, Vec3
from pandac.PandaModules import OdeWorld, OdeBody, OdeMass, Quat, OdePlaneGeom, BitMask32
from pandac.PandaModules import OdeSimpleSpace, OdeJointGroup, OdeBoxGeom
from direct.showbase.ShowBase import ShowBase
from direct.gui.OnscreenText import OnscreenText
from direct.gui.DirectGui import *
from pandac.PandaModules import TextNode
from direct.task import Task
import random, sys, time

FRAMESKIP = 4

# Panda Render Window
class PandaApp(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)

        # global things to do
        base.disableMouse()     # turn off internal camera control
        base.setBackgroundColor(0.2, 0.2, 0.2) # not that we care really, can't see it

        # std random startup stuff
        random.seed()

        # main physics world lives here, set the gravity to earth std
        self.myWorld = OdeWorld()
        self.myWorld.setGravity(0, 0, -9.81)

        # Create a space and add a contactgroup for our physics system
        self.space = OdeSimpleSpace()
        self.space.enable()
        self.space.setAutoCollideWorld(self.myWorld)
        self.contactgroup = OdeJointGroup()
        self.space.setAutoCollideJointGroup(self.contactgroup)

        # The surface table is needed for autoCollide
        self.myWorld.initSurfaceTable(1)
        self.myWorld.setSurfaceEntry(0, 0, 20, 3.8, 1.1, 0.9, 0.00001, 0.0, 0.002)

        # Load Sounds (notes) for dice models
        self.loadSounds()

        # load dice models and set them for dynamics
        self.LoadModels()

        # load the room we bounce in, set the physics floor and walls
        self.LoadRoom()
       
        # setup global collision system callback
        self.space.setCollisionEvent("ode-collision")
        base.accept("ode-collision", self.onCollision)

        # add a directional light
        dlight = DirectionalLight('dlight')
        dlight.setColor(VBase4(0.9, 0.9, 0.9, 1))
        dlnp = render.attachNewNode(dlight)
        dlnp.setHpr(0, -60, 0)
        render.setLight(dlnp)

        # and an ambient light too
        alight = AmbientLight('alight')
        alight.setColor(VBase4(0.15, 0.15, 0.15, 1))
        alnp = render.attachNewNode(alight)
        render.setLight(alnp)

        # camera coords
        self.cameraX = 0
        self.cameraY = -25
        self.cameraZ = 11
        self.cameraH = 0
        self.cameraP = -16
        self.cameraR = 0

        # fix the camera here
        self.camera.setPos(self.cameraX, self.cameraY, self.cameraZ)
        self.camera.setHpr(self.cameraH, self.cameraP, self.cameraR)

        # add keyboard controls
        self.accept('space', self.throwDice)
        self.accept('escape', sys.exit)
        self.accept('c', self.addCredits)
        self.accept('b', self.addBet)
         
        # setup some flags and things
        self.frame = 0
        self.computedWin = True
        self.addForce = False
        self.runSimulation = False
        self.inSimulation = False
        self.rotationRunning = False
        self.collision = False
        self.winlist = {}
        self.positionlist = {}
        self.prevlist = {}

        # money and such
        self.credits = 10
        self.bet = 1
        self.winnings = 0

        # wins per face
        self.ones = 0
        self.twos = 0
        self.threes = 0
        self.fours = 0
        self.fives = 0
        self.sixes = 0

        # setup the screen text displays and cute little dice button
        self.drawScreenText()

        # start everything and we are done with initialize
        taskMgr.doMethodLater(0.1, self.startRot, "StartRotation")
        taskMgr.doMethodLater(0.7, self.startGrav, "StartGravity")

    def loadSounds(self):
        f0 = loader.loadSfx("models/f.wav")
        g0 = loader.loadSfx("models/g.wav")
        b0 = loader.loadSfx("models/bb.wav")
        c0 = loader.loadSfx("models/c.wav")
        e0 = loader.loadSfx("models/eb.wav")
        f1 = loader.loadSfx("models/f1.wav")
        g1 = loader.loadSfx("models/g1.wav")
        b0 = loader.loadSfx("models/bb1.wav")
        c1 = loader.loadSfx("models/c1.wav")
        self.sounds = [f0, g0, c0, f1, g1, c1, f0, g0, c1 ]
        self.soundIndex = 0

    # Text on the Screen
    def drawScreenText(self):
        text = ""
        self.textObject = OnscreenText(text=text, \
                                       pos=(-1.05,-0.95), \
                                       scale = 0.10, \
                                       fg=(1, 1, 1, 1), \
                                       align=TextNode.ALeft, \
                                       mayChange=1)
        text = "Credits:"
        self.CreditsText = OnscreenText(text=text, \
                                        pos=(-1.3, 0.9), \
                                        scale = 0.1, \
                                        fg=(1, 1, 1, 1), \
                                        align=TextNode.ALeft, \
                                        mayChange=1)
        text = "%d" % self.credits
        self.CreditsNum = OnscreenText(text=text, \
                                        pos=(-0.9, 0.9), \
                                        scale = 0.13, \
                                        fg=(1, 1, 1, 1), \
                                        align=TextNode.ALeft, \
                                        mayChange=1)
        text = "Bet:"
        self.BetText = OnscreenText(text=text, \
                                        pos=(.94, 0.9), \
                                        scale = 0.1, \
                                        fg=(1, 1, 1, 1), \
                                        align=TextNode.ALeft, \
                                        mayChange=1)
        text = "%d" % self.bet
        self.BetNum = OnscreenText(text=text, \
                                        pos=(1.14, 0.9), \
                                        scale = 0.13, \
                                        fg=(1, 1, 1, 1), \
                                        align=TextNode.ALeft, \
                                        mayChange=1)
        text = "Win: 0"
        self.textWin = OnscreenText(text=text, \
                                       pos=(.94,-0.95), \
                                       scale = 0.10, \
                                       fg=(1, 1, 1, 1), \
                                       align=TextNode.ALeft, \
                                       mayChange=1)
        text = "Idle"
        self.textStatus = OnscreenText(text=text, \
                                       pos=(.94,-0.85), \
                                       scale = 0.04, \
                                       fg=(1, 1, 1, 1), \
                                       align=TextNode.ALeft, \
                                       mayChange=1)

        # Add button
        buttonText = ("", "", "Throw", "")
        self.bDie = DirectButton(text = buttonText, \
                    geom = self.displayDie, \
                    geom_pos = (0,0,0), \
                    geom_scale = (0.1, 0.1, 0.1), \
                    geom_hpr = (0, 0, 0), \
                    relief = None, \
                    text_fg = (1, 1, 1, 1), \
                    text_scale = (0.3, 0.3), \
                    text_shadow = (0, 0, 0, 1), \
                    text_style = ScreenTitle, \
                    frameColor = (1, 1, 1, 1), \
                    borderWidth = (0.05, 0.05), \
                    #image = 'symbols\symbol_1.png', \
                    pos = (-1.2, 0, -.90), \
                    scale = 0.3, \
                    hpr = (0,0,0), \
                    command=self.throwDice)

    #start up the physics task
    def startGrav(self, task):
        self.startGravitySim()

    # spin the dice weirdly rotate task
    def startRot(self, task):
        self.startRotation()

    # add some credits to our pot
    def addCredits(self):
        self.credits = self.credits + 1
        s = "%d" % self.credits
        self.CreditsNum.setText(s)

    # bet some credits on this spin
    def addBet(self):
        self.bet = self.bet + 1
        if self.bet > 5:
           self.bet = 1
        s = "%d" % self.bet
        self.BetNum.setText(s)

    # spin the models randomly
    def rotateModels(self, task):
        self.frame = self.frame + 1
        if self.frame >= FRAMESKIP:
           self.frame = 0
           for die in self.dice:
               x = random.randint(0,359)
               y = random.randint(0,359)
               z = random.randint(0,359)
               die[0].setHpr(x,y,z)
               die[3].setPosition(die[0].getPos(render))
               die[3].setQuaternion(die[0].getQuat(render))
        if self.collision == False:
           return Task.cont
        else:
           return Task.done

    # start the dice rotating
    def startRotation(self):
        if self.rotationRunning == False:
           self.rotationRunning = True
           self.inSimulation = False
           self.taskMgr.add(self.rotateModels, "rotateModels")

    # force (explode) the dice
    def forceDie(self):
        for i in range(0,9):
            body = self.dice[i][3]
            a = random.randint(-17000000,17000000)
            b = random.randint(-17000000,17000000)
            c = random.randint(10000000,33000000) # up is always big to overcome gravity
            body.setForce(Vec3(a, b, c))

    # do a throw
    def throwDice(self):
        if self.credits - self.bet < 0:
           return

        if self.computedWin == False:
           return

        self.computedWin = False

        self.textStatus.setText("Throw Dice")
        #print "THROW DICE"
        self.credits = self.credits - self.bet
        s = "%d" % self.credits
        self.CreditsNum.setText(s)

        self.winnings = 0
        w = "Win: %d" % self.winnings
        self.textWin.setText(w)

        for i in range(0,9):
            self.prevlist[i] = (0.0, 0.0, 0.0)
        self.moving = 0
        self.forceDie()
        taskMgr.doMethodLater(0.2, self.evaluateWinList, "EvaluateWinList")

    def checkMotion(self):
        moving = False
        for d in self.winlist.keys():
            (l,m,n) = self.winlist[d]
            a = "%03.0f" % l
            b = "%03.0f" % m
            c = "%03.0f" % n
           
            (o,p,q) = self.prevlist[d]
            e = "%03.0f" % o
            f = "%03.0f" % p
            g = "%03.0f" % q

            if a != e:
               moving = True
            if b != f:
               moving = True
            if c != g:
               moving = True

        for d in self.winlist.keys():
            self.prevlist[d] = self.winlist[d]

        return moving

    # evaluate (count up faces) dice as they come to rest on the floor, a task that runs every frame
    def evaluateWinList(self, task):

        if self.checkMotion() == False:
           self.moving = self.moving + 1
        else:
           self.moving = 0
       
        ones = 0
        twos = 0
        threes = 0
        fours = 0
        fives = 0
        sixes = 0

        a = b = c = 0

        for d in self.winlist.keys():
            l, m, n = self.winlist[d]
            a = int(l+0.5)
            b = int(m+0.5)
            c = int(n+0.5)
            if b == 0 and c == -89:
               ones = ones + 1
            elif b == 0 and c == 0:
               twos = twos + 1
            elif b == 90:
               threes = threes + 1
            elif b < -88:
               fours = fours + 1
            elif b == 0 and c != 90:
               fives = fives + 1
            elif b == 0 and c == 90:
               sixes = sixes + 1

        if self.ones == ones and self.twos == twos and self.threes == threes \
           and self.fours == fours and self.fives == fives and self.sixes == sixes:

           t = ""
           if ones > 3:
              t = t + "%d ones " % ones
           if twos > 3:
              t = t + "%d twos " % twos
           if threes > 3:
              t = t + "%d threes " % threes
           if fours > 3:
              t = t + "%d fours " % fours
           if fives > 3:
              t = t + "%d fives " % fives
           if sixes > 3:
              t = t + "%d sixes " % sixes
           if ones > 0 and twos > 0 and threes > 0 and fours > 0 and fives > 0 and sixes > 0:
              t = t + "six sequence"
           self.textObject.setText(t)

        else:
            self.ones = ones
            self.twos = twos
            self.threes = threes
            self.fours = fours
            self.fives = fives
            self.sixes = sixes

        self.bDie["geom_hpr"] = (a,b,c)
        if self.moving > 5:
           taskMgr.doMethodLater(0.1, self.computeTotals, "compute total wins")
           return Task.done
        return Task.cont

    def computeTotals(self, task):
        self.textStatus.setText("Compute win")
        self.winnings = 0
       
        # five of a kind or greater pays bet * number of a kind
        if self.ones > 3:
           self.winnings = self.winnings + (self.ones * self.bet)
           
        if self.twos > 3:
           self.winnings = self.winnings + self.twos * self.bet

        if self.threes > 3:
           self.winnings = self.winnings + (self.threes * self.bet)
           
        if self.fours > 3:
           self.winnings = self.winnings + self.fours * self.bet
           
        if self.fives > 3:
           self.winnings = self.winnings + self.fives * self.bet
           
        if self.sixes > 3:
           self.winnings = self.winnings + self.sixes * self.bet

        if self.ones > 0 and self.twos > 0 and self.threes > 0 and self.fours > 0 and self.fives > 0 and self.sixes > 0:
           self.winnings = self.winnings + (2 * self.bet)

        w = "Win: %d" % self.winnings
        self.textWin.setText(w)

        self.credits = self.credits + self.winnings
        s = "%d" % self.credits
        self.CreditsNum.setText(s)

        self.computedWin = True

        return Task.done

    # start the gravity/collision simulation       
    def startGravitySim(self):
        self.addForce = False
        if self.runSimulation == False:
           self.deltaTimeAccumulator = 0.0
           self.stepSize = 1.0 / 90.0
           self.runSimulation = True
           taskMgr.doMethodLater(0.1, self.simulationTask, "Physics Simulation")

    # callback for collisions of the dice
    def onCollision(self, entry):
        geom1 = entry.getGeom1()
        geom2 = entry.getGeom2()
        body1 = entry.getBody1()
        body2 = entry.getBody2()

        for np, geom, sound, body in self.dice:
            if geom == geom1 or geom == geom2:
               velocity = body1.getAngularVel()
               if velocity[0] > 2.0 and sound.status != sound.PLAYING:
                  sound.setVolume(velocity[0] / 80.0)
                  sound.play()
                  self.collision = True
       
    # run the gravity/collision simulation task
    def simulationTask(self, task):
        self.inSimulation = True
        self.space.autoCollide()
        #self.myWorld.quickStep(globalClock.getDt())
        self.myWorld.quickStep(0.016)
        i = 0
        for np, geom, sound, body in self.dice:
            self.positionlist[i] = np.getPos()
            self.winlist[i] = np.getHpr()
            i = i + 1
            if not np.isEmpty():
               np.setPosQuat(render, geom.getBody().getPosition(), Quat(geom.getBody().getQuaternion()))
               self.contactgroup.empty() # Clear the contact joints
        if self.runSimulation == True:
           return task.cont

    # set already existing models to standard places
    def setModels(self):
        i = 0
        z = 2.0
        for x in [-1.0, 0.0, 1.0]:
            die = self.dice[i][0]
            die.setPos(x, 0.4, z)
            die.setHpr(0,0,0)
            body = self.dice[i][3]
            body.setPosition(die.getPos(render))
            body.setQuaternion(die.getQuat(render))
            i = i + 1
        z = 3.0
        for x in [-1.0, 0.0, 1.0]:
            die = self.dice[i][0]
            die.setPos(x, 0.0, z)
            die.setHpr(0,0,0)
            body = self.dice[i][3]
            body.setPosition(die.getPos(render))
            body.setQuaternion(die.getQuat(render))
            i = i + 1
        z = 4.0
        for x in [-1.0, 0.0, 1.0]:
            die = self.dice[i][0]
            die.setPos(x, 0.4, z)
            die.setHpr(0,0,0)
            body = self.dice[i][3]
            body.setPosition(die.getPos(render))
            body.setQuaternion(die.getQuat(render))
            i = i + 1

    # build individual die, set mass, collision and physics
    def buildModel(self, x, y, modelDice):
        die = modelDice.copyTo(self.root)
        die.setPos(x, 0.0, y)
        die.setScale(0.25, 0.25, 0.25)
        diceBody = OdeBody(self.myWorld)
        mass = OdeMass()
        wt = random.randint(10000, 14240)
        mass.setBox(wt, 1, 1, 1)
        diceBody.setMass(mass)
        diceBody.setPosition(die.getPos(render))
        diceBody.setQuaternion(die.getQuat(render))
        diceBody.enable()
        diceGeom = OdeBoxGeom(self.space, 1, 1, 1)
        diceGeom.setCollideBits(BitMask32(0x00000001))
        diceGeom.setCategoryBits(BitMask32(0x00000001))
        diceGeom.setBody(diceBody)
        Sound = self.sounds[self.soundIndex]
        self.soundIndex = self.soundIndex + 1
        return die, diceGeom, Sound, diceBody

    # import the dice model base and build all nine
    def LoadModels(self):
        self.displayDie = loader.loadModel("models/die")    # for button
        self.displayDie.setDepthTest(True)
        self.displayDie.setDepthWrite(True)

        modelDice = loader.loadModel("models/die")
        self.root = render.attachNewNode("Root")
        self.dice = []

        y = 2.0
        for x in [-1.0, -0.4, 1.0]:
            die, diceGeom, Sound, body = self.buildModel(x, y, modelDice)
            self.dice.append((die, diceGeom, Sound, body))

        y = 3.0
        for x in [-1.0, 0.0, 1.0]:
            die, diceGeom, Sound, body = self.buildModel(x, y, modelDice)
            self.dice.append((die, diceGeom, Sound, body))

        y = 4.0
        for x in [-1.0, 0.4, 1.0]:
            die, diceGeom, Sound, body = self.buildModel(x, y, modelDice)
            self.dice.append((die, diceGeom, Sound, body))

    def LoadRoom(self):
        # put dice in a box, model only
        room = loader.loadModel("models/room")
        room.reparentTo(render)
        room.setScale(1, 2, 1)
        room.setPos(0, -1, 0)
        room.setTwoSided(True)

        # install physics - floor
        groundGeom = OdeBoxGeom(self.space, (2000, 2000, 1))
        groundGeom.setCollideBits(BitMask32( 0xffffffff))
        groundGeom.setCategoryBits(BitMask32(0xffffffff))

        # wall left
        wall0Geom = OdeBoxGeom(self.space, (1, 2000, 2000))
        wall0Geom.setPosition(-6.5,0,0)
        wall0Geom.setCollideBits(BitMask32(0xffffffff))
        wall0Geom.setCategoryBits(BitMask32(0xffffffff))

        # wall right
        wall1Geom = OdeBoxGeom(self.space, (1, 2000, 2000))
        wall1Geom.setPosition(6.5,0,0)
        wall1Geom.setCollideBits(BitMask32(0xffffffff))
        wall1Geom.setCategoryBits(BitMask32(0xffffffff))

        # wall back
        wall2Geom = OdeBoxGeom(self.space, (2000, 1, 2000))
        wall2Geom.setPosition(0,6.5,0)
        wall2Geom.setCollideBits(BitMask32(0xffffffff))
        wall2Geom.setCategoryBits(BitMask32(0xffffffff))

        # wall behind cam
        wall3Geom = OdeBoxGeom(self.space, (2000, 1, 2000))
        wall3Geom.setPosition(0,-6.8,0)
        wall3Geom.setCollideBits(BitMask32(0xffffffff))
        wall3Geom.setCategoryBits(BitMask32(0xffffffff))

        # ceiling
        wall4Geom = OdeBoxGeom(self.space, (2000, 2000, 1))
        wall4Geom.setPosition(0,0,11)
        wall4Geom.setCollideBits(BitMask32(0xffffffff))
        wall4Geom.setCategoryBits(BitMask32(0xffffffff))
       


# MAIN - start everything
panda = PandaApp()
run()
martan3d
 
Posts: 33
Joined: Mon Dec 27, 2010 7:16 pm

Postby martan3d » Thu Feb 24, 2011 12:37 pm

The models are a die from turbosquid which I had to tweak, and a simple box. I used the free SoftImageModTool for those. I exported to .x format then used x2egg to convert them.

I put the egg and one jpg file in the same place:
http://www.virginiahelicopter.net/game/

Code reviews and suggestions welcome, just don't bash me too hard :)

There are some things I don't like, particularly how I determine that the dice have quit moving and the scoring is way too easy, the house needs to keep more!
martan3d
 
Posts: 33
Joined: Mon Dec 27, 2010 7:16 pm

Postby AnimateDream » Thu Mar 03, 2011 9:48 pm

I had a chance to check this out. I like what you did with the sound. Its a great at what it does. I've been working on games in my spare time for a decade, and I still have a hard time dialing back my ambition enough to make make something simple, but polished like this.
AnimateDream
 
Posts: 89
Joined: Tue Feb 16, 2010 2:36 am

Postby martan3d » Fri Mar 04, 2011 2:19 pm

Thanks! It was a lot of fun.
martan3d
 
Posts: 33
Joined: Mon Dec 27, 2010 7:16 pm

Postby enc » Fri Mar 04, 2011 5:38 pm

Nice and clean.
You need to clean up ode stuff before you sys.exit or it will throw and error on exiting.
Know your self and you shall know everything you need to know.
My games: http://moba.ee/
My doodles: http://hendrikkangro.deviantart.com/gallery/
enc
 
Posts: 166
Joined: Wed May 27, 2009 3:35 am
Location: Estonia, Tartu

Postby martan3d » Sat Mar 05, 2011 9:59 pm

I'm going to revisit it soon and try to put it on a web page. And refactor as I can. I have another little game I'm working on too.
martan3d
 
Posts: 33
Joined: Mon Dec 27, 2010 7:16 pm


Return to Showcase

Who is online

Users browsing this forum: Google [Bot] and 0 guests